Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic: Add first attempt at pgdscan plugin #1321

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

eve-mem
Copy link
Contributor

@eve-mem eve-mem commented Oct 25, 2024

Hi! 👋

This PR adds a basic pgdscan plugin.

I often find myself in the situation where no ISF is available for my linux sample. The debugging symbols are not provided and no information such as system map or kallsyms was collected when a memory capture is performed. I know it's sometimes a similar situation for others.

Without an ISF vol is quite limited in what it can do, and rightly so! You need the information on the complex strutures in order to correctly parse the memory.

This plugin is designed to help in this, disappointingly common, ISF-less situation. It will scan through the memory and locate heuristically what are likely to be PGDs for the various processes in the memory. You don't have an ISF so it cannot tell you the pid or comm etc.

The user part of the address space can then be dumped out allowing analysis in other tools (e.g. strings, yara, hex editor, ghidra, etc). While not as powerful as vol with a full ISF it allows you to explore the user address space in a way that would have otherwise been impossible.

Sometimes all you really need to do is find the user process you care about and dig into it's private memory - and this plugin should help with that.

It currently only supports Intel32e. I've tried my best to make it generic by reading as much information as possible from the intel layer. I simply don't have a lot of samples with 32bit OSes on to test with.

It would be possible to modify existing plugins such as linux.bash or linux.vmayarascan to accept an offset to a PGD and still provide the same results. Any plugin that focuses on scanning private memory to find results and doesn't rely on the kernel ISF (other than to parse the pslist etc) could be made to work this way.

Here is some example output:

(volatility3) eve@xps:~/Documents/volatility3$ python vol.py -r pretty -f linux-sample-1.dmp pgdscan
Volatility 3 Framework 2.11.0
Formatting...0.00               PDB scanning finished                      
  | PGD offset |     size | config
* |  0x1605000 |        0 |      -
* |  0x1ee6000 |  4239360 |      -
* |  0x4407000 |  4268032 |      -
* |  0x450a000 |   544768 |      -
* |  0x4572000 |   835584 |      -
* |  0x4590000 |  2850816 |      -
* | 0x1ac16000 |  2031616 |      -
* | 0x1aca1000 |  4517888 |      -
* | 0x1acf5000 |   200704 |      -
<snip>

N.B. the size 0 PGD is for the kernel itself and so it's actually an expected result.

This example shows dumping out the memory regions for one of the recovered PGDs and running file on the results. (I think there is probably improvements to be made to ensure that pages that are close together get mapped to a single file. At the moment I just use the output of mapping() directly.)

(volatility3) eve@xps:~/Documents/volatility3$ python vol.py -r pretty -f linux-sample-1.dmp pgdscan --dump --offset 0x4572000
Volatility 3 Framework 2.11.0
Formatting...0.00               PDB scanning finished                      
  | PGD offset |   size | config
* |  0x4572000 | 835584 |      -
(volatility3) eve@xps:~/Documents/volatility3$ file pgd.0x4572000.start.0x*
pgd.0x4572000.start.0x1223000.dmp:      data
pgd.0x4572000.start.0x1224000.dmp:      data
pgd.0x4572000.start.0x1225000.dmp:      data
pgd.0x4572000.start.0x1226000.dmp:      data
pgd.0x4572000.start.0x400000.dmp:       ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, missing section headers at 14768
pgd.0x4572000.start.0x401000.dmp:       data
pgd.0x4572000.start.0x402000.dmp:       data
pgd.0x4572000.start.0x602000.dmp:       data
pgd.0x4572000.start.0x603000.dmp:       data
pgd.0x4572000.start.0x7fd341093000.dmp: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter *empty*, missing section headers at 47552
pgd.0x4572000.start.0x7fd341094000.dmp: data
pgd.0x4572000.start.0x7fd34109c000.dmp: data
<snip>

Here is an example of saving out a config for that same PGD and dropping into volshell with the config. In volshell we can then investigate the private memory as normal.

(volatility3) eve@xps:~/Documents/volatility3$ python vol.py -f linux-sample-1.dmp pgdscan --save-configs --offset 0
x4572000
Volatility 3 Framework 2.11.0
Progress:  100.00               PDB scanning finished                      
PGD offset      size    config
Progress:    0.00               Scanning memory_layer using PageGlobalDirectoryScanner
0x4572000       835584  pgd.0x4572000.json
(volatility3) eve@xps:~/Documents/volatility3$ python volshell.py -c pgd.0x4572000.json
Volshell (Volatility 3 Framework) 2.11.0
Readline imported successfully  PDB scanning finished  

    Call help() to see available functions

    Volshell mode        : Generic
    Current Layer        : primary
    Current Symbol Table : None
    Current Kernel Name  : None

(primary) >>> db(0x400000)
0x400000    7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00    .ELF............
0x400010    02 00 3e 00 01 00 00 00 64 17 40 00 00 00 00 00    ..>.....d.@.....
0x400020    40 00 00 00 00 00 00 00 f0 32 00 00 00 00 00 00    @........2......
0x400030    00 00 00 00 40 00 38 00 09 00 40 00 1c 00 1b 00    [email protected]...@.....
0x400040    06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00    ........@.......
0x400050    40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00    @.@.....@.@.....
0x400060    f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00    ................
0x400070    08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00    ................
(primary) >>> 

I'm not happy with how I've messed with build_configuration() in order to produce a config file that can be loaded into volshell. It feels like there must be an easier way...!

I'm messing with the guts of a config and private reading values out of the intel layer, there is likely to be lots of ways to do this better/smarter.... 🙈

I welcome any pointers or advice!

Thanks again!
🦊

# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#

import enum

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'enum' is not used.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix these

import enum
import logging
import struct
import os

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'os' is not used.
import os
import json
import math
import struct

Check notice

Code scanning / CodeQL

Module is imported more than once Note

This import of module struct is redundant, as it was previously imported
on line 7
.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant