2

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.

  1. I was barking up the wrong tree. I mistook the minidump Memory info list as where the stack was.
  2. 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

[3] - https://github.com/libyal/libmdmp/blob/main/documentation/Minidump%20%28MDMP%29%20format.asciidoc#thread_information_stream

[4] - https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg/1920px-Portable_Executable_32_bit_Structure_in_SVG_fixed.svg.png

[5] - https://chromium.googlesource.com/breakpad/breakpad/+/refs/heads/main/src/client/minidump_file_writer.cc

[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

2
  • i can't check now but is this 64 bit ? if yes then ADDRESS is ADDRESS64 so it is DWORD64 word and address_mode from your windbg k result versus raw dump i can see the 1c8 77 6b correctly visually please check for bitness and try
    – blabb
    Jul 28, 2021 at 13:36
  • @blabb yes this is 64bit. Though the application is a 32bit version (dunno if this makes a difference). so it is Address64. Thanks. That makes a bit more sense.
    – ewokx
    Jul 28, 2021 at 23:32

1 Answer 1

1

the rva does not seem to point to _tagSTACKFRAME64 the size appears to be different 0x108 versus 0x4d0

is there a specific reason to use dbghelp ?

outputstacktrace from dbgeng is not acceptable?

have you looked at the com interfaces of DIA_SDK for an alternative

checked an arbitrary dump for sizeof(_tagStackFrame) versus size in dump using code below

#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
int main(void)
{
    HANDLE hFile = NULL;
    hFile = CreateFileA(
        "tdump.dmp", GENERIC_READ, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        printf("file handle is %p\n", hFile);
        HANDLE hMapFile = NULL;
        hMapFile = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
        if (hMapFile != NULL)
        {
            printf("file Map handle is %p\n", hMapFile);
            LPVOID lpMapAddress = NULL;
            lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
            if (lpMapAddress != NULL)
            {
                printf("view of map file is %p\n", lpMapAddress);
                PMINIDUMP_DIRECTORY dudir = NULL;
                PVOID strptr = NULL;
                ULONG ssiz = 0;
                BOOL res = FALSE;
                res = MiniDumpReadDumpStream(lpMapAddress, 3, &dudir, &strptr, &ssiz);
                if (res && strptr != NULL)
                {
                    PMINIDUMP_THREAD_LIST tlist = (PMINIDUMP_THREAD_LIST)strptr;
                    for (ULONG32 i = 0; i < tlist->NumberOfThreads; i++)
                    {
                        ULONG64 dsiz = tlist->Threads[i].ThreadContext.DataSize;
                        ULONG64 rva = tlist->Threads[i].ThreadContext.Rva;
                        ULONG64 memsta = tlist->Threads[i].Stack.StartOfMemoryRange;
                        ULONG64 memsiz = tlist->Threads[i].Stack.Memory.DataSize;
                        ULONG64 memrva = tlist->Threads[i].Stack.Memory.Rva;
                        printf("look in debugger %I64x\t%I64x\t%I64x\t%I64x\t%I64x\n",
                               dsiz, rva, memsta, memsiz, memrva);
                    }
                    _tagSTACKFRAME64 tsf = {0};
                    printf("%zx\n", sizeof(tsf));
                }
                UnmapViewOfFile(lpMapAddress);
                CloseHandle(hMapFile);
                CloseHandle(hFile);
            }
        }
    }
    return 0;
} 

compiled and executed

cl /Zi /W4 /analyze:autolog- /Od /EHsc /nologo dumpdis.cpp /link /release
dumpdis.cpp

dumpdis.exe
file handle is 000000000000009C
file Map handle is 00000000000000A0
view of map file is 0000029D96410000
look in debugger 4d0    2076    dce012edb0      1250    0
look in debugger 4d0    2546    dce01af858      7a8     0
look in debugger 4d0    2a16    dce047fa68      598     0
look in debugger 4d0    2ee6    dce04ffb48      4b8     0
108

here is stack frame using GetScope from dbgeng IDebugSymbols
code below is a windbg extension a dll but you can make standalone exe with dbgeng (see samples in windbg sdk )

code

#include <engextcpp.cpp>
#define bufsiz 0x2000
class EXT_CLASS : public ExtExtension
{
public:
    EXT_COMMAND_METHOD(gscope);
};
EXT_DECLARE_GLOBALS();
EXT_COMMAND(gscope, "", "")
{
    PULONG64 ip = 0;
    DEBUG_STACK_FRAME sfr = {0};
    BYTE scont[bufsiz] = {0};
    HRESULT hr = m_Symbols->GetScope(ip, &sfr, &scont, bufsiz);
    if (hr == S_OK)
    {
        Out("insptr\t=\t%I64x\n", ip);
        Out("instof\t=\t%I64x\n", sfr.InstructionOffset);
        Out("retoff\t=\t%I64x\n", sfr.ReturnOffset);
        Out("fraoff\t=\t%I64x\n", sfr.FrameOffset);
        Out("staoff\t=\t%I64x\n", sfr.StackOffset);
        Out("ftentr\t=\t%I64x\n", sfr.FuncTableEntry);
        Out("parone\t=\t%I64x\n", sfr.Params[0]);
        Out("partwo\t=\t%I64x\n", sfr.Params[1]);
        Out("partre\t=\t%I64x\n", sfr.Params[2]);
        Out("parfor\t=\t%I64x\n", sfr.Params[3]);
        Out("resone\t=\t%I64x\n", sfr.Reserved[0]);
        Out("virtua\t=\t%I64x\n", sfr.Virtual);
        Out("franum\t=\t%I64x\n", sfr.FrameNumber);
    }
}

compiled & linked with

cat complink.bat
cl /LD /nologo /W4 /Ox  /Zi /EHsc /I"C:\Program Files (x86)\Windows Kits\10\Debuggers\inc" %1.cpp /link /EXPORT:DebugExtensionInitialize /Export:%1 /Export:help /RELEASE

executed !gscope and kb1 for comparison

cdb -c ".load gscope;!gscope;kb1;q" -z ..\dumsta\tdump.dmp |awk "/Reading/,/quit/"     
0:000> cdb: Reading initial command '.load gscope;!gscope;kb1;q'
insptr  =       0
instof  =       7ffe652f108c
retoff  =       7ffe652f444f
fraoff  =       dce012ede0
staoff  =       dce012edb0
ftentr  =       0
parone  =       dce0245000
partwo  =       7ffe6534d4b0
partre  =       7ffe6534d4b0
parfor  =       7ffe6534d4b0
resone  =       0
virtua  =       1
franum  =       0
RetAddr           : Args to Child                                                           : Call Site      
00007ffe`652f444f : 000000dc`e0245000 00007ffe`6534d4b0 00007ffe`6534d4b0 00007ffe`6534d4b0 : ntdll!LdrpDoDebuggerBreak+0x30
quit:
6
  • The reason why I use dbhlp is because that it's the only documentation I find that allows me to understand minidumps. As I've only just started understanding this, I'm not familiar with dbgeng. Now that you've pointed it out, it looks like the DEBUG_STACK_FRAME might be it though the ULONG64 addresses seem to be a bit long.
    – ewokx
    Jul 28, 2021 at 23:38
  • added a snippet to fetch stack using GetScope fromdbgeng IDebugSymbols also issued kb1 inwindbg to compare DEBUG_STACK_FRAME With extension's output take a look
    – blabb
    Jul 29, 2021 at 18:19
  • sorry for the delay @blabb, just wanted to mention that I've been working on this problem on and off. I don't have VS so I can't compile that code.
    – ewokx
    Sep 28, 2021 at 7:07
  • what are you using clang? mingw? cygwin? why not add those details in your query. anyway dbgeng doesnt need vs
    – blabb
    Sep 28, 2021 at 7:22
  • I'm sorry. I screwed up and have realized I mistaken this crash dump as universal and that Microsoft's documentation was universal. Apparently it isn't as I should actually be looking at Google's Breakpad code which is what most likely created this dump. I'm so sorry.
    – ewokx
    Oct 19, 2021 at 3:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.