less ./b00010111/blog

Volshell Process Examination Notes

I recently played around a little bit with volshell, as I had realized I do not have sufficient notes on how to use it. I’ll share the notes below.

Volshell uses virtual memory addresses, if you are searching or using offsets keep that in mind. Starting volshell without entering the context of a process is pretty straigh forward.

1
vol.py -f memory_img.raw --kdbg=$KDBG_OFSETT --profile=$VOLATILITY_PROFILE volshell

Getting some help for the first usage can be done by the hh() command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
In [1]: hh()

Use addrspace() for Kernel/Virtual AS
Use addrspace().base for Physical AS
Use proc() to get the current process object
  and proc().get_process_address_space() for the current process AS
  and proc().get_load_modules() for the current process DLLs

addrspace()                              : Get the current kernel/virtual address space. 
cc(offset=None, pid=None, name=None, physical=False) : Change current shell context.
db(address, length=128, space=None)      : Print bytes as canonical hexdump.
dd(address, length=128, space=None)      : Print dwords at address.
dis(address, length=128, space=None, mode=None) : Disassemble code at a given address.
dq(address, length=128, space=None)      : Print qwords at address.
dt(objct, address=None, space=None, recursive=False, depth=0) : Describe an object or show type info.
find(needle, max=1, shift=0, skip=0, count=False, length=128) : 
getmods()                                : Generator for kernel modules (scripting).
getprocs()                               : Generator of process objects (scripting).
hh(cmd=None)                             : Get help on a command.
list_entry(head, objname, offset=-1, fieldname=None, forward=True, space=None) : Traverse a _LIST_ENTRY.
modules()                                : Print loaded modules in a table view.
proc()                                   : Get the current process object.
ps()                                     : Print active processes in a table view.
sc()                                     : Show the current context.

For help on a specific command, type 'hh(<command>)'

To list the processes you can run the ps() command within the interactive volshell prompt.

1
2
3
4
5
6
7
In [1]: ps()
Name             PID    PPID   Offset  
System           4      0      0x85c50958
smss.exe         280    4      0x86ecaa70
csrss.exe        412    404    0x86cfa540
wininit.exe      464    404    0x87d85d40
<snip>

This output gives you an overview about the name, the process id (PID), the parent process (PPID) and the offset of each process. The PID or the name can be used to change the context to a specific process, more on this can be seen below. For now we will work with the offset only.

In order to make structures like the Proccess Environment Block (PCB) accessible, volatility has its own structures. To view one of those structures you can use the dt() command.
If you would like to understand what the _EPROCESS structure contains use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [5]: dt("_EPROCESS")
 '_EPROCESS' (728 bytes)
0x0   : Pcb                            ['_KPROCESS']
0x98  : ProcessLock                    ['_EX_PUSH_LOCK']
0xa0  : CreateTime                     ['WinTimeStamp', {'is_utc': True}]
0xa8  : ExitTime                       ['WinTimeStamp', {'is_utc': True}]
0xb0  : RundownProtect                 ['_EX_RUNDOWN_REF']
0xb4  : UniqueProcessId                ['unsigned int']
0xb8  : ActiveProcessLinks             ['_LIST_ENTRY']
0xc0  : ProcessQuotaUsage              ['array', 2, ['unsigned long']]
0xc8  : ProcessQuotaPeak               ['array', 2, ['unsigned long']]
0xd0  : CommitCharge                   ['unsigned long']
<snip>
0x1a8 : Peb                            ['pointer', ['_PEB']]
<snip>

As you can see above, at the offset of 0x1a8 in the _EPROCESS structure, the Process Environment Block (PEB) can be found. It is a pointer, pointing to a structure named _PEB. If we would like to understand the _PEB structure, we can again use the dt() command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [6]: dt("_PEB")
 '_PEB' (584 bytes)
0x0   : InheritedAddressSpace          ['unsigned char']
0x1   : ReadImageFileExecOptions       ['unsigned char']
0x2   : BeingDebugged                  ['unsigned char']
0x3   : BitField                       ['unsigned char']
0x3   : ImageUsesLargePages            ['BitField', {'end_bit': 1, 'start_bit': 0, 'native_type': 'unsigned char'}]
0x3   : IsImageDynamicallyRelocated    ['BitField', {'end_bit': 4, 'start_bit': 3, 'native_type': 'unsigned char'}]
0x3   : IsLegacyProcess                ['BitField', {'end_bit': 3, 'start_bit': 2, 'native_type': 'unsigned char'}]
0x3   : IsProtectedProcess             ['BitField', {'end_bit': 2, 'start_bit': 1, 'native_type': 'unsigned char'}]
0x3   : SkipPatchingUser32Forwarders   ['BitField', {'end_bit': 5, 'start_bit': 4, 'native_type': 'unsigned char'}]
0x3   : SpareBits                      ['BitField', {'end_bit': 8, 'start_bit': 5, 'native_type': 'unsigned char'}]
<snip>
0x10  : ProcessParameters              ['pointer', ['_RTL_USER_PROCESS_PARAMETERS']]
<snip>

If we would like to know more about the process parameters we can learn that from the _RTL_USER_PROCESS_PARAMETERS structure, which is part of the Process Environment Block. For example it contains the command line the process was started with.
You can use these defined structures as an overlay for every offset. Volshell will than use the given structure and fill it with data starting the offset provided. As memory is just bits and bytes, the overlay can be applied to addresses not representing the corresponding structure. Be warned to double check your offsets and validate the output.
In order to use a structure as an overlay at a specific offset you have to tell volshell the offset. You can do this by providing it as a second parameter to the dt() command: dt(“$STRUCTUR_NAME”, $HEXADECIMAL_OFFSET)

So lets start to dig down and see what the command line of the process cmd process was. Fist step is to determine the offset by looking at the ps() command output

1
2
3
4
5
In [1]: ps()
Name             PID    PPID   Offset  
<snip>
cmd.exe          208    1208   0x860f2578
<snip>

Next use the _EPROCESS structure as on overlay at the offset of the process

1
2
3
4
5
6
7
8
9
10
11
12
In [2]: dt("_EPROCESS", 0x862f9a58)
[_EPROCESS _EPROCESS] @ 0x862F9A58
0x0   : Pcb                            2251266648
0x98  : ProcessLock                    2251266800
0xa0  : CreateTime                     2012-04-06 14:03:11 UTC+0000
0xa8  : ExitTime                       1970-01-01 00:00:00 UTC+0000
0xb0  : RundownProtect                 2251266824
0xb4  : UniqueProcessId                5192
0xb8  : ActiveProcessLinks             2251266832
<snip>
0x1a8 : Peb                            2147348480 
<snip>

Given the output above, we can see that the Process Environment Block points to 2147348480. This value is in decimal and we will convert it into hex in a standard bash shell:

1
2
~$ printf '0x%x\n' 2147348480
0x7ffdf000

If we are not changing the context to the process we are investigating we might get the following error.

1
2
3
4
In [7]: dt('_PEB',0x7FFDF000)
ERROR: could not instantiate object

Reason:  Invalid Address 0x7FFDF000, instantiating _PEB

So it is time to switch context and apply the above command again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [8]: cc(pid=208)
Current context: cmd.exe @ 0x860f2578, pid=208, ppid=1208 DTB=0x7ecce4c0

In [9]: dt('_PEB',0x7FFDF000)
[CType _PEB] @ 0x7FFDF000
0x0   : InheritedAddressSpace          0
0x1   : ReadImageFileExecOptions       0
0x2   : BeingDebugged                  0
0x3   : BitField                       8
0x3   : ImageUsesLargePages            0
0x3   : IsImageDynamicallyRelocated    1
0x3   : IsLegacyProcess                0
<snip>
0x10  : ProcessParameters              3674456
<snip>

Our ProcessParameters are located at 3674456, which is 0x381158 in hex. Using the _RTL_USER_PROCESS_PARAMETER overlay and calculated offset, we can already see the command line used to start the process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [12]: dt("_RTL_USER_PROCESS_PARAMETERS", 0x381158)
[CType _RTL_USER_PROCESS_PARAMETERS] @ 0x00381158
0x0   : MaximumLength                  1728
0x4   : Length                         1728
0x8   : Flags                          24577
0xc   : DebugFlags                     0
0x10  : ConsoleHandle                  2840
0x14  : ConsoleFlags                   0
0x18  : StandardInput                  628
0x1c  : StandardOutput                 624
0x20  : StandardError                  624
0x24  : CurrentDirectory               3674492
0x30  : DllPath                        C:\Windows\system32;C:\Windows\system32;C:\Windows\system;C:\Windows;.;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\
0x38  : ImagePathName                  C:\Windows\system32\cmd.exe
0x40  : CommandLine                    C:\Windows\system32\cmd.exe
0x48  : Environment                    3748560
<snip>
0x290 : EnvironmentSize                2484
0x294 : EnvironmentVersion             5

But what if we want to see the content of the environment variables of the process?

1
2
3
4
5
6
dt("_RTL_USER_PROCESS_PARAMETERS") shows us the following:
<snip>
0x48  : Environment                    ['pointer', ['void']]
<snip>
0x290 : EnvironmentSize                ['unsigned long']
0x294 : EnvironmentVersion             ['unsigned long']

So it seems we cannot apply a predefined structure to view the content of the environment variables. How about to dump them with the db() command? Again we converted the decimal pointer value (in this case to Environemt) to hex.

1
2
3
4
5
6
7
8
9
In [16]: db (0x3932d0)
0x003932d0  3d 00 43 00 3a 00 3d 00 43 00 3a 00 5c 00 57 00   =.C.:.=.C.:.\.W.
0x003932e0  69 00 6e 00 64 00 6f 00 77 00 73 00 5c 00 53 00   i.n.d.o.w.s.\.S.
0x003932f0  79 00 73 00 74 00 65 00 6d 00 33 00 32 00 5c 00   y.s.t.e.m.3.2.\.
0x00393300  64 00 6c 00 6c 00 68 00 6f 00 73 00 74 00 00 00   d.l.l.h.o.s.t...
0x00393310  41 00 4c 00 4c 00 55 00 53 00 45 00 52 00 53 00   A.L.L.U.S.E.R.S.
0x00393320  50 00 52 00 4f 00 46 00 49 00 4c 00 45 00 3d 00   P.R.O.F.I.L.E.=.
0x00393330  43 00 3a 00 5c 00 50 00 72 00 6f 00 67 00 72 00   C.:.\.P.r.o.g.r.
0x00393340  61 00 6d 00 44 00 61 00 74 00 61 00 00 00 41 00   a.m.D.a.t.a...A.

In order to view the complete content we need to overwrite the default returned size of 1024. Lucky for us the size of the environment is part of the _RTL_USER_PROCESS_PARAMETER structure.

1
2
In [17]: db (0x3932d0, 2484)
In [18]: db (0x3932d0, 0x9b4)

Both will return the complete content of the environment variables for the given process. This first command supplies the length db() should read and return in decimal form, the second used hexadecimal.

If you do not want to deal with the offsets, there is a different way as well.
We start volshell again and directly jump into the context of the process by supplying the -p $PID parameter, in our example we jump to the process used above (PID 208).

1
vol.py -f memory_img.raw --kdbg=$KDBG_OFSETT --profile=$VOLATILITY_PROFILE volshell -p 208

We are now already in the context of the process with PID 208 and we can reference the structures via “self” for the selected process. Of cause you can also reference structures via “self” if you manually switched the context to the process you are investigating via cc(pid=$PID_OF_CHOICE).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [2]: dt(self._proc)
[_EPROCESS _EPROCESS] @ 0x860F2578
0x0   : Pcb                            2249139576
0x98  : ProcessLock                    2249139728
0xa0  : CreateTime                     2012-04-04 18:43:24 UTC+0000
0xa8  : ExitTime                       1970-01-01 00:00:00 UTC+0000
0xb0  : RundownProtect                 2249139752
0xb4  : UniqueProcessId                208
0xb8  : ActiveProcessLinks             2249139760
0xc0  : ProcessQuotaUsage              -
0xc8  : ProcessQuotaPeak               -
0xd0  : CommitCharge                   426
0xd4  : QuotaBlock                     2281628672
0xd8  : CpuQuotaBlock                  0
<snip>
0x1a8 : Peb                            2147348480
<snip>

Printing the ProcessEnvironmentBlock now works easily by just adding the corresponding name.

1
2
3
4
5
6
7
8
In [3]: dt(self._proc.Peb)
[CType Peb] @ 0x7FFDF000
0x0   : InheritedAddressSpace          0
0x1   : ReadImageFileExecOptions       0
0x2   : BeingDebugged                  0
<snip>
0x10  : ProcessParameters              3674456
<snip>

THe ProcessParameter would be the next step in the journey:

1
2
3
4
5
6
7
8
9
In [4]: dt(self._proc.Peb.ProcessParameters)
<CType pointer to [0x00381158]>
0x0   : MaximumLength                  1728
0x4   : Length                         1728
<snip>
0x48  : Environment                    3748560
<snip>
0x290 : EnvironmentSize                2484
<snip>

As you have seen above, Environment is a pointer to data in memory not being parsed with an object overlay like the ProcessParameters for example. So running something like “dt(self._proc.Peb.ProcessParameters.Environment)” will cause an error. We have to use db to print the context, as we have already done above. From above we further know, we need to include the length wen want to read in order to print the whole environment variables.

1
2
3
4
5
6
7
8
In [5]: db(self._proc.Peb.ProcessParameters.Environment,2484)
0x003932d0  3d 00 43 00 3a 00 3d 00 43 00 3a 00 5c 00 57 00   =.C.:.=.C.:.\.W.
0x003932e0  69 00 6e 00 64 00 6f 00 77 00 73 00 5c 00 53 00   i.n.d.o.w.s.\.S.
0x003932f0  79 00 73 00 74 00 65 00 6d 00 33 00 32 00 5c 00   y.s.t.e.m.3.2.\.
0x00393300  64 00 6c 00 6c 00 68 00 6f 00 73 00 74 00 00 00   d.l.l.h.o.s.t...
0x00393310  41 00 4c 00 4c 00 55 00 53 00 45 00 52 00 53 00   A.L.L.U.S.E.R.S.
0x00393320  50 00 52 00 4f 00 46 00 49 00 4c 00 45 00 3d 00   P.R.O.F.I.L.E.=.
<snip>

By now we have successfully reviewed various data about a process and also printed out the hex representation of the process environment variable by using volshell.