I'm currently using Python3.9 in Linux to obtain the necessary information from a minidump file. I used WinDBG on my windows system to check whether the information I got was right.
While [1], [2] and [3] have helped, there are still some holes that aren't covered. The purpose of this is to create a script that can disect the minidump. I've managed to get the ThreadList, MemoryList, MemoryInfoList and moduleList. But I'm missing the stack information, which seems to be within the MINIDUMP_THREAD info's Stack field as shown below:
typedef struct _MINIDUMP_THREAD {
ULONG32 ThreadId;
ULONG32 SuspendCount;
ULONG32 PriorityClass;
ULONG32 Priority;
ULONG64 Teb;
MINIDUMP_MEMORY_DESCRIPTOR Stack;
MINIDUMP_LOCATION_DESCRIPTOR ThreadContext;
} MINIDUMP_THREAD, *PMINIDUMP_THREAD;
It's a MINIDUMP_MEMORY_DESCRIPTOR which has the following structure:
typedef struct _MINIDUMP_MEMORY_DESCRIPTOR {
ULONG64 StartOfMemoryRange;
MINIDUMP_LOCATION_DESCRIPTOR Memory;
} MINIDUMP_MEMORY_DESCRIPTOR, *PMINIDUMP_MEMORY_DESCRIPTOR;
The Memory field has the following structure:
typedef struct _MINIDUMP_LOCATION_DESCRIPTOR {
ULONG32 DataSize;
RVA Rva;
} MINIDUMP_LOCATION_DESCRIPTOR;
So all in all, the Stack.Rva contains the relative virtual address in the minidump file.
Going to that address, I see 'stuff' but at this point in the documentation, there's no indication of what structure is stored there. I thought it'd be a STACKFRAME structure (was grasping at straws) which is given as:
typedef struct _tagSTACKFRAME {
ADDRESS AddrPC;
ADDRESS AddrReturn;
ADDRESS AddrFrame;
ADDRESS AddrStack;
PVOID FuncTableEntry;
DWORD Params[4];
BOOL Far;
BOOL Virtual;
DWORD Reserved[3];
KDHELP KdHelp;
ADDRESS AddrBStore;
} STACKFRAME, *LPSTACKFRAME;
But looking at the hex values, it doesn't make sense:
00 00 00 00 D1 F8 AF 77 29 16 6B 77 C8 01 00 00
00 00 00 00 00 00 00 00 D0 87 C0 BD E0 60 85 00
c8 01 00 00 28 c1 39 00 24 00 00 00 01 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 2B 00 2B 00
87 02 21 00 00 00 00 00 AC BF 39 00 AB F0 6A 77
...
That would mean AddrPC = {Offset: 00 00 00 00, Segment: D1 F8 AF 77, Mode: 29}
So I figured I'd cheat by running Windbg on this crashdump file to find the corresponding info; but I don't see how the above hex dump can be translated to the following:
00 0039bf98 776b1629 000001c8 00000000 00000000 ntdll!NtWaitForSingleObject+0x15
01 0039c004 75491194 000001c8 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98
02 0039c01c 75491148 000001c8 ffffffff 00000000 kernel32!WaitForSingleObjectExtImplementation+0x75
03 0039c030 5a581e3a 000001c8 ffffffff 00000000 kernel32!WatiForSingleObject+0x12
...
While I can see some of the info from windbg's call stack, the information isn't a contiguous set of info.
Unfortunately, my understanding of C++/C is limited at best so I couldn't grasp the information as given in [5]
Might anyone have suggestion on how to reverse engineer the structure of what's at this address?
I know it's some sort of structure that includes a list of stackframes; but the documentation at [1] doesn't specify what kind of structure. I'm guessing there's a header and some array of structure. Unfortunately, I haven't found (yet) documentation that shows a map of a minidump (akin to [4]). Something like this would make my understanding easier.
*Addendum:*
Having worked on this on and off, I still haven't figured it out despite @blabb's help. I went and took a look at [6] which points out the Stack structure for X86. Having lost my original dump file, I used a new dump file. I used a minidump_stackwalker binary that came up with the following: (The rva of the crashing thread was 0x0141ff - binary dump follows)
Crash reason: EXCEPTION_ACCESS_VIOLATION_READ
Crash address: 0x8
Thread 0 (crashed)
0 k.dll + 0x310bf3f
eip = 0x5d42bf3f esp = 0x0053bea0 ebp = 0x0053bf74 ebx = 0x0053bed0
esi = 0x00a1b000 edi = 0x0053bf98 eax = 0x00000008 ecx = 0x00000004
edx = 0x0053bfb0 efl = 0x00210287
Found by: given as instruction pointer in context
1 k.dll + 0x310bc3b
eip = 0x5d42bc3c esp = 0x0053bf7c ebp = 0x0053bfd8
Found by: previous frame's frame pointer
2 k.dll + 0x311e7b3
eip = 0x5d43e7b4 esp = 0x0053bfe0 ebp = 0x0053c004
Found by: previous frame's frame pointer
3 k.dll + 0x3280958
eip = 0x5d5a0959 esp = 0x0053c00c ebp = 0x0053c064
Found by: previous frame's frame pointer
...
binary dump at 0x0141ff:
000141f0h: FC EC 23 00 00 00 00 AC 03 00 00 34 7B 03 00 8B
00014200h: 55 18 8B 45 0C FF 24 8D CC 4A D7 5D C7 44 24 14
00014210h: 00 00 00 00 8D 4E 10 89 4C 24 0C 8B 56 10 89 54
00014220h: 24 10 8D 54 24 0C 89 56 10 8B 00 89 44 24 04 8B
00014230h: 07 89 44 24 38 89 4c 24 30 89 54 24 34 c7 44 24
...
From what I gathered from [6], since this is a x86 binary, I assumed [possibly wrongly] that it'd be using the stack structure as given by [6] and not [7].
That would mean the context_flags starts at 0x000141ff which gives me 8B 55 18 8B. From the comments in [6], this context_flag means this stack is a MD_CONTEXT_X86_ALL. So after using the following script:
#!/bin/env python
import os
import sys
hdrs_x86 = {
"context_flags": 4,
"dr0": 4,
"dr1": 4,
"dr2": 4,
"dr3": 4,
"dr6": 4,
"dr7": 4,
"fs_control_word": 4,
"fs_status_word": 4,
"fs_tag_word": 4,
"fs_error_offset": 4,
"fs_error_selector": 4,
"fs_data_offset": 4,
"fs_data_selector": 4,
"fs_register_area": (1, 80),
"fs_cr0_npx_state": 4,
"gs": 4,
"fs": 4,
"es": 4,
"edi": 4,
"esi": 4,
"ebx": 4,
"edx": 4,
"ecx": 4,
"eax": 4,
"ebp": 4,
"eip": 4,
"cs": 4,
"eflags": 4,
"esp": 4,
"ss": 4,
"extended_registers": (1, 80)
}
hdrs_x64 = {
"p1_home": 8,
"p2_home": 8,
"p3_home": 8,
"p4_home": 8,
"p5_home": 8,
"p6_home": 8,
"context_flags": 4,
"mx_csr": 4,
"cs": 2,
"ds": 2,
"es": 2,
"fs": 2,
"gs": 2,
"ss": 2,
"eflags": 4,
"dr0": 8,
"dr1": 8,
"dr2": 8,
"dr3": 8,
"dr6": 8,
"dr7": 8,
"rax": 8,
"rcx": 8,
"rdx": 8,
"rbx": 8,
"rsp": 8,
"rsp": 8,
"rbp": 8,
"rsi": 8,
"rdi": 8,
"r8": 8,
"r9": 8,
"r10": 8,
"r11": 8,
"r12": 8,
"r13": 8,
"r14": 8,
"r15": 8,
"rip": 8
}
MDCTXX86 = 0x00010000
MDCTXX86_CONTROL = MDCTXX86 | 0x00000001
MDCTXX86_INTEGER = MDCTXX86 | 0x00000002
MDCTXX86_SEGMENTS = MDCTXX86 | 0x00000004
MDCTXX86_FLOATING_POINT = MDCTXX86 | 0x00000008
MDCTXX86_DEBUG_REGISTERS = MDCTXX86 | 0x00000010
MDCTXX86_EXTENDED_REGISTERS = MDCTXX86 | 0x00000020
MDCTXX86_XSTATE = MDCTXX86 | 0x00000040
MDCTXX86_FULL = MDCTXX86_CONTROL | MDCTXX86_INTEGER | MDCTXX86_SEGMENTS
ALL_P1 = MDCTXX86_FULL | MDCTXX86_FLOATING_POINT
ALL_P2 = MDCTXX86_DEBUG_REGISTERS | MDCTXX86_EXTENDED_REGISTERS
MDCTXX86_ALL = ALL_P1 | ALL_P2
def rev_item(in_bytes, no_rev=False):
tmp = [x for x in in_bytes]
if not no_rev:
tmp.reverse()
retval = []
for item in tmp:
hv = hex(item).replace("0x", "")
if len(hv) < 2:
hv = "0" + hv
retval.append(hv)
return retval
def is_dr(in_ctx, in_item):
return in_ctx is not None and \
in_item in ["dr0", "dr1", "dr2", "dr3", "dr6", "dr7"] and \
in_ctx & MDCTXX86_DEBUG_REGISTERS > 0
def is_seg(in_ctx, in_item):
return in_ctx is not None and \
in_item in ["gs", "fs", "es", "ds"] and \
in_ctx & MDCTXX86_SEGMENTS
def is_int(in_ctx, in_item):
return in_ctx is not None and \
in_item in ["edi", "esi", "ebx", "edx", "ecx", "eax"] and \
in_ctx & MDCTXX86_INTEGER
def is_fp(in_ctx, in_item):
return in_ctx is not None and \
in_item.startswith("fs_") and \
in_ctx & MDCTXX86_FLOATING_POINT
def is_control(in_ctx, in_item):
return in_ctx is not None and \
in_item in ["ebp", "eip", "cs", "eflags", "esp", "ss"] and \
in_ctx & MDCTXX86_CONTROL
def is_ext_reg(in_ctx, in_item):
return in_ctx is not None and \
in_item in ['extended_registers'] and \
in_ctx & MDCTXX86_EXTENDED_REGISTERS
def check_for_ctx(in_ctx, in_item):
retval = False
for itemfn in [is_dr, is_seg, is_int, is_fp,
is_control, is_ext_reg]:
retval = itemfn(in_ctx, in_item)
if retval:
break
return retval
res = []
res2 = []
hdrv = {}
hdrs = hdrs_x86
with open("e:\\test.dmp", 'rb') as fp:
addr = 0x141ff
fp.seek(addr)
ctx = None
for item, item_rl in hdrs.items():
read_len = item_rl
add_item = False
if isinstance(item_rl, tuple):
vr = []
read_len = item_rl[0]
for i in range(item_rl[1]):
tmp = fp.read(read_len)
tmph = tmp.hex().replace("0x", "")
vr.append(tmph)
read_len = item_rl[1]
else:
v = fp.read(item_rl)
vr = rev_item(v, no_rev=True)
if item == "context_flags":
ctx = int("".join(vr), 16)
if check_for_ctx(ctx, item):
if item not in hdrv:
hdrv[item] = vr
addr += read_len
for item, iteminfo in hdrv.items():
print(item, "".join(iteminfo))
It displays
dr0 450cff24
dr1 8dcc4ad7
dr2 5dc74424
dr3 14000000
dr6 008d4e10
dr7 894c240c
fs_control_word 8b561089
fs_status_word 5424108d
fs_tag_word 54240c89
fs_error_offset 56108b00
fs_error_selector 89442404
fs_data_offset 8b078944
fs_data_selector 2438894c
fs_register_area 243089542434c744242c00000000894c24248d442438895c24288d4c2424894e108d4c242c31ff515056e89a6adfff83c40c84c074178d4424186a09ff74243050e813aedfff83c40c8b7c24188b4424
fs_cr0_npx_state 248b4c24
gs 2889088b
fs 4424308b
es 4c243489
edi 08897c24
esi 1485ffb3
ebx 010f841a
edx 0300008d
ecx 4424148b
eax 54240489
ebp d1c1f91f
eip 6a005152
cs e9de0200
eflags 000fbe00
esp e94d0100
ss 00c74424
extended_registers 14000000008d4e10894c240c8b5610895424108d54240c8956108b00894424048b0789442438894c243089542434c744242c00000000894c24248d442438895c24288d4c2424894e108d4c242c31ff51
But this doesn't make any sense as it doesn't even bear any resemblance to what's given in the results. Like I got d1c1f91f
as the EBP, but it's actually 0x0053BF74
Ergo, I've misunderstood this whole thing.
*Additional Addendum*
:
The addendum was wrong on two points.
- I was barking up the wrong tree. I mistook the
minidump Memory info
list as where the stack was. - I was working on the same minidump. Just was confused with what section I was working on.
I've opted to keep the Addendum section and not delete it. (Along the lines of 1000 ways of not doing something.)
Any help greatly appreciated,
:ewong
[1] - https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/
[2] - https://github.com/utds3lab/sigpath/blob/master/scripts/minidump.py
[6] - https://github.com/google/breakpad/blob/main/src/google_breakpad/common/minidump_cpu_x86.h
[7] - https://github.com/google/breakpad/blob/main/src/google_breakpad/common/minidump_cpu_amd64.h