process explorer minidump

As in last post, I started putting together a utility for generation of minidump, intention is to have it be part of bigger workflow. I got most of it right, but had some fun getting the flags and process access right.

First, I started with OpenProcess but had opened the process with PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access rights only. Documentation link.

Second, referring documentation for MiniDumpWriteDump , I was looking for flags to be passed as documented for MINIDUMP_TYPE. The way enumeration is done it is hard to specify MiniDumpWithHandleData and MiniDumpWithThreadInfo at the same time, so really doesn’t help and it does not even addup with the options as listed by .dumpdebug command in windbg.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
0:000> .dumpdebug
----- User Mini Dump Analysis

MINIDUMP_HEADER:
Version         A793 (A063)
NumberOfStreams 15
Flags           1105
                0001 MiniDumpWithDataSegs
                0004 MiniDumpWithHandleData
                0100 MiniDumpWithProcessThreadData
                1000 MiniDumpWithThreadInfo
                


MINIDUMP_TYPE MS documentation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
typedef enum _MINIDUMP_TYPE {
00  MiniDumpNormal,
01  MiniDumpWithDataSegs,
02  MiniDumpWithFullMemory,
03  MiniDumpWithHandleData,
04  MiniDumpFilterMemory,
05  MiniDumpScanMemory,
06  MiniDumpWithUnloadedModules,
07  MiniDumpWithIndirectlyReferencedMemory,
08  MiniDumpFilterModulePaths,
09  MiniDumpWithProcessThreadData,
10  MiniDumpWithPrivateReadWriteMemory,
11  MiniDumpWithoutOptionalData,
12  MiniDumpWithFullMemoryInfo,
13  MiniDumpWithThreadInfo,
...
25  MiniDumpValidTypeFlags
} MINIDUMP_TYPE;


On futher digging I found this in dotnet repository on github: link

 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
27
28
public enum MINIDUMP_TYPE : uint
{
    MiniDumpNormal                         = 0,
    MiniDumpWithDataSegs                   = 1 << 0,
    MiniDumpWithFullMemory                 = 1 << 1,
    MiniDumpWithHandleData                 = 1 << 2,
    MiniDumpFilterMemory                   = 1 << 3,
    MiniDumpScanMemory                     = 1 << 4,
    MiniDumpWithUnloadedModules            = 1 << 5,
    MiniDumpWithIndirectlyReferencedMemory = 1 << 6,
    MiniDumpFilterModulePaths              = 1 << 7,
    MiniDumpWithProcessThreadData          = 1 << 8,
    MiniDumpWithPrivateReadWriteMemory     = 1 << 9,
    MiniDumpWithoutOptionalData            = 1 << 10,
    MiniDumpWithFullMemoryInfo             = 1 << 11,
    MiniDumpWithThreadInfo                 = 1 << 12,
    MiniDumpWithCodeSegs                   = 1 << 13,
    MiniDumpWithoutAuxiliaryState          = 1 << 14,
    MiniDumpWithFullAuxiliaryState         = 1 << 15,
    MiniDumpWithPrivateWriteCopyMemory     = 1 << 16,
    MiniDumpIgnoreInaccessibleMemory       = 1 << 17,
    MiniDumpWithTokenInformation           = 1 << 18,
    MiniDumpWithModuleHeaders              = 1 << 19,
    MiniDumpFilterTriage                   = 1 << 20,
    MiniDumpWithAvxXStateContext           = 1 << 21,
    MiniDumpWithIptTrace                   = 1 << 22,
    MiniDumpValidTypeFlags                 = (-1) ^ ((~1) << 22)
}


This aligns quite well with the flags we have observed so far. Now, the question is why am I not able to get handles in the dump file. My first guess is that I have not opened file with proper acess rights. To confirm I went about looking at what is process explorer doing. :). I attached to procexp64 process and started dumping trace

first, set the breakpoint at function dbgcore!MiniDumpWriteDump and go

1
2
0:012> bp dbgcore!MiniDumpWriteDump
0:012> g


after selecting the location to save memory-dump, break point is hit and looking at traceback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Breakpoint 0 hit
dbgcore!MiniDumpWriteDump:
00007ff9`73686790 4055            push    rbp
0:000> k
 # Child-SP          RetAddr           Call Site
00 00000075`94cfe938 00007ff7`eb92869c dbgcore!MiniDumpWriteDump
01 00000075`94cfe940 00007ff7`eb91d04b procexp64+0x7869c
02 00000075`94cfe990 00007ff7`eb8fc2b0 procexp64+0x6d04b
03 00000075`94cfef30 00007ff7`eb924d59 procexp64+0x4c2b0
04 00000075`94cfef70 00007ff7`eb8fc3af procexp64+0x74d59
05 00000075`94cff490 00007ff7`eb9285f6 procexp64+0x4c3af
06 00000075`94cff4d0 00007ff9`84e274d6 procexp64+0x785f6
07 00000075`94cff510 00007ff9`84e26ff2 USER32!UserCallWinProcCheckWow+0x266
08 00000075`94cff690 00007ff7`eb9559ad USER32!DispatchMessageWorker+0x1b2
09 00000075`94cff710 00007ff7`eb95dcc4 procexp64+0xa59ad
0a 00000075`94cff8c0 00007ff9`82387974 procexp64+0xadcc4
0b 00000075`94cff900 00007ff9`851ba261 KERNEL32!BaseThreadInitThunk+0x14
0c 00000075`94cff930 00000000`00000000 ntdll!RtlUserThreadStart+0x21


Function procexp64+0x7869c calls MiniDumpWriteDump, so basically it will setup and pass in the parameters. On Winx64, first 4 parameters are passed in registers and rest on stack. Luckily we need the first four params to confirm, based on signature of MiniDumpWriteDump.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0:000> r
rax=0000000000000000 rbx=0000000000001105 rcx=0000000000000ad4
rdx=0000000000002d10 rsi=0000000000000ad4 rdi=0000000000000000
rip=00007ff973686790 rsp=0000007594cfe938 rbp=0000000000000e44
 r8=0000000000000e44  r9=0000000000001105 r10=0000000000000000
r11=0000000000000246 r12=0000000000010003 r13=0000000000009f20
r14=0000000000000e44 r15=0000000000000ad4
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
dbgcore!MiniDumpWriteDump:
00007ff9`73686790 4055            push    rbp


The params are passed in this order: ProcessHandle (rcx), ProcessId (rdx), File (r8) & MinDumpOpts (r9). Here the rcx is set to 0xad4 and r8 is set to 0xe44 and r9 is 0x1105. This 0x1105 (decimal: 4357) is our option value for sure, but what about process.

!handle is a great extension to query status/details of various handles:

1
2
3
0:000> !handle ad4
Handle ad4
  Type         Process

Per documentation here, you can pass 0x7 as option to print further information:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
0:000> !handle ad4 7
Handle ad4
  Type         Process
  Attributes   0
  GrantedAccess 0x1fffff:
         Delete,ReadControl,WriteDac,WriteOwner,Synch
         Terminate,CreateThread,,VMOp,VMRead,VMWrite,DupHandle,CreateProcess,SetQuota,SetInfo,QueryInfo,SetPort
  HandleCount   15
  PointerCount 435679
  Name         <none>

The process is opend with access 0x1fffff, which is actually PROCESS_ALL_ACCESS. So that answers my query about how the process should be opened. :)

I just wanted to check how procexp opened the file (I specified the register r8 directly and windbg translated it to 0xe44 handle value):

1
2
3
4
5
6
7
8
9
0:000> !handle r8 7
Handle e44
  Type         File
  Attributes   0
  GrantedAccess 0x120196:
         ReadControl,Synch
         Write/Add,Append/SubDir/CreatePipe,WriteEA,ReadAttr,WriteAttr
  HandleCount   2
  PointerCount 32769


So far we have established that the first 4 parameters to MiniDumpWriteDump, now about remaining 3:

recall the stack we are at is:

1
2
3
4
5
0:000> k
 # Child-SP          RetAddr           Call Site
00 00000075`94cfe938 00007ff7`eb92869c dbgcore!MiniDumpWriteDump
01 00000075`94cfe940 00007ff7`eb91d04b procexp64+0x7869c
02 00000075`94cfe990 00007ff7`eb8fc2b0 procexp64+0x6d04b

to look at the calling function disassembly type uf command, it disassmbles function containing specified address. Here we will pass in the return address from calling function: procexp64+0x7869c. Here Line#19 is MiniDumpWriteDump

 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
0:000> uf procexp64+0x7869c
procexp64+0x78610:
00007ff7`4fd58610 48895c2408      mov     qword ptr [rsp+8],rbx
00007ff7`4fd58615 48896c2410      mov     qword ptr [rsp+10h],rbp
00007ff7`4fd5861a 4889742418      mov     qword ptr [rsp+18h],rsi
00007ff7`4fd5861f 57              push    rdi
00007ff7`4fd58620 4883ec40        sub     rsp,40h
...
...
00007ff7`4fd58674 ff15667e0500    call    qword ptr [procexp64+0xd04e0 (00007ff7`4fdb04e0)]
00007ff7`4fd5867a 448bcb          mov     r9d,ebx
00007ff7`4fd5867d 4c8bc5          mov     r8,rbp
00007ff7`4fd58680 8bd0            mov     edx,eax
00007ff7`4fd58682 33c0            xor     eax,eax
00007ff7`4fd58684 488bce          mov     rcx,rsi
00007ff7`4fd58687 4889442430      mov     qword ptr [rsp+30h],rax
00007ff7`4fd5868c 4889442428      mov     qword ptr [rsp+28h],rax
00007ff7`4fd58691 4889442420      mov     qword ptr [rsp+20h],rax
00007ff7`4fd58696 ff15fc860b00    call    qword ptr [procexp64+0x130d98 (00007ff7`4fe10d98)]
00007ff7`4fd5869c 488b5c2450      mov     rbx,qword ptr [rsp+50h]
00007ff7`4fd586a1 488b6c2458      mov     rbp,qword ptr [rsp+58h]
00007ff7`4fd586a6 488b742460      mov     rsi,qword ptr [rsp+60h]
00007ff7`4fd586ab 4883c440        add     rsp,40h
00007ff7`4fd586af 5f              pop     rdi
00007ff7`4fd586b0 c3              ret

To confirm that just type, here .frame shows current frame. We set breakpoint at dbgcore!MiniDumpWriteDump so that sounds right.

1
2
0:000> .frame
00 00000075`94cfe938 00007ff7`eb92869c dbgcore!MiniDumpWriteDump

One can inspect the register values at a particular frame doc link

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
0:000> .frame /r 0
00 00000075`94cfe938 00007ff7`eb92869c dbgcore!MiniDumpWriteDump
rax=0000000000000000 rbx=0000000000001105 rcx=0000000000000ad4
rdx=0000000000002d10 rsi=0000000000000ad4 rdi=0000000000000000
rip=00007ff973686790 rsp=0000007594cfe938 rbp=0000000000000e44
 r8=0000000000000e44  r9=0000000000001105 r10=0000000000000000
r11=0000000000000246 r12=0000000000010003 r13=0000000000009f20
r14=0000000000000e44 r15=0000000000000ad4
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000244
dbgcore!MiniDumpWriteDump:
00007ff9`73686790 4055            push    rbp

to look at registers of previous frame:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
0:000> .frame /r 1
01 00000075`94cfe940 00007ff7`eb91d04b procexp64+0x7869c
rax=0000000000000000 rbx=0000000000001105 rcx=0000000000000ad4
rdx=0000000000002d10 rsi=0000000000000ad4 rdi=0000000000000000
rip=00007ff7eb92869c rsp=0000007594cfe940 rbp=0000000000000e44
 r8=0000000000000e44  r9=0000000000001105 r10=0000000000000000
r11=0000000000000246 r12=0000000000010003 r13=0000000000009f20
r14=0000000000000e44 r15=0000000000000ad4
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000244
procexp64+0x7869c:
00007ff7`eb92869c 488b5c2450      mov     rbx,qword ptr [rsp+50h] ss:00000075`94cfe990=0000023d59070420

Here the these are just before calling dbgcore!MiniDumpWriteDump and disassembly at this location:

1
2
3
4
5
6
7
8
9
00007ff7`4fd5867a 448bcb          mov     r9d,ebx
00007ff7`4fd5867d 4c8bc5          mov     r8,rbp
00007ff7`4fd58680 8bd0            mov     edx,eax
00007ff7`4fd58682 33c0            xor     eax,eax
00007ff7`4fd58684 488bce          mov     rcx,rsi
00007ff7`4fd58687 4889442430      mov     qword ptr [rsp+30h],rax
00007ff7`4fd5868c 4889442428      mov     qword ptr [rsp+28h],rax
00007ff7`4fd58691 4889442420      mov     qword ptr [rsp+20h],rax
00007ff7`4fd58696 ff15fc860b00    call    qword ptr [procexp64+0x130d98 (00007ff7`4fe10d98)]

Lines 6-8 is where the parameters are passed on stack for this function, note rax is 0\nullptr.

Thus the call to MiniDumpWriteDump is:

1
2
MiniDumpWriteDump(0xad4, 0x2d10, 0xe44, 0x1105, nullptr, nullptr, nullptr);
MiniDumpWriteDump(hProc, pid,    hFile, 0x1105, nullptr, nullptr, nullptr);

With this information set, I know that I need to:

  1. Open the process with PROCESS_ALL_ACCESS
  2. Pass flags 0x1105
  3. pass nullptr to last 3 parameters

 

Links of interest (click to expand..)
  1. Process Access Rights
  2. MiniDumpWriteDump
  3. MINIDUMP_TYPE
  4. MiniDumpType-dotnet
  5. !handle
  6. .frame