[WinInternals] Reverse Engineering of kdbgctrl - How are builded Kernel Triage Dumps

Rating: 2 votes, 3.00 average.

In this little blog post I'm going to reverse kdbgctrl.exe an handy tool delivered with Windows Debugging Tools kit (windbg package). This tool has the handy functionality to allow Kernel Triage Dumps building, also from a non /DEBUG bootted machine.

As written before, from nynaeve, Triage Dump writing make able the kernel to create small dump files which contains the basical and usual informations of a Dump, like processes with associated threads and relative stacks.

There is not many documentation about the usage of kdbgctrl, we have only a basical:
kdbgctrl -td pid file

Let's now see how internally works kdbgctrl.exe; why this?..curiosity and the potentiality to know which functions are used and became able to rewrite it, and also (as asked by some people) to show a subset of considerations that make you able to fastly and effectively reverse the functionality of an application.

What we have is an application that takes two arguments, one of them is a filename, so after disassembling it we can immediately search for File Management Operations, like CreateFile and WriteFile, at this point we can trace back to the origins, locating the requestor and consecutively identify the functions that provide data dumped into file.

.text:0100340F loc_100340F:                            ; CODE XREF: sub_1003310+BF j
.text:0100340F                 push    0               ; hTemplateFile
.text:01003411                 push    80h             ; dwFlagsAndAttributes
.text:01003416                 push    2               ; dwCreationDisposition
.text:01003418                 push    0               ; lpSecurityAttributes
.text:0100341A                 push    0               ; dwShareMode
.text:0100341C                 push    40000000h       ; dwDesiredAccess
.text:01003421                 mov     ecx, [ebp+lpFileName]
.text:01003424                 push    ecx             ; lpFileName
.text:01003425                 call    ds:CreateFileA
the first line denotes a Cross Reference, so let's follow it:

.text:010033AE loc_10033AE:                            ; CODE XREF: sub_1003310+84 j
.text:010033AE                 lea     eax, [ebp+nNumberOfBytesToWrite]
.text:010033B1                 push    eax
.text:010033B2                 mov     ecx, [ebp+dwSize]
.text:010033B5                 push    ecx
.text:010033B6                 mov     edx, [ebp+lpAddress]
.text:010033B9                 push    edx
.text:010033BA                 push    24h
.text:010033BC                 lea     eax, [ebp+var_44]
.text:010033BF                 push    eax
.text:010033C0                 push    1Dh
.text:010033C2                 call    ds:NtSystemDebugControl
This is the heart of kdbgctrl algorithm, the Triage Dump is obtained by calling NtSystemDebugControl, we have now to uncover its parameters to be able to reproduce the code.
IN ULONG InputBufferLength,
IN ULONG OutputBufferLength,
In our case SYSDBG_COMMAND has the value 1D, with a little research we can discover that this value belongs to SysDbgGetTriageDump, and immediately after the involved struct

typedef struct _SYSDBG_TRIAGE_DUMP
    ULONG Flags;
    ULONG BugCheckCode;
    ULONG_PTR BugCheckParam1;
    ULONG_PTR BugCheckParam2;
    ULONG_PTR BugCheckParam3;
    ULONG_PTR BugCheckParam4;
    ULONG ProcessHandles;
    ULONG ThreadHandles;
    PHANDLE Handles;
Immediately before NtSystemDebugControl we meet:

.text:0100336C                 mov     [ebp+var_40], 69696969h
.text:01003373                 mov     [ebp+dwSize], 400000h
.text:0100337A                 push    4               ; flProtect
.text:0100337C                 push    1000h           ; flAllocationType
.text:01003381                 mov     edx, [ebp+dwSize]
.text:01003384                 push    edx             ; dwSize
.text:01003385                 push    0               ; lpAddress
.text:01003387                 call    ds:VirtualAlloc
69696969 belongs to the tipical BugCheck of Triage Dumps. A bit upper we have

.text:0100335D                 push    ecx
.text:0100335E                 call    sub_1002EB0
.text:01003363                 test    eax, eax
.text:01003365                 jz      short loc_100336C ;Prosecute with Dumping Process
.text:01003367                 jmp     loc_100357C    ;Jump Out
Let's see what happens inside call sub_1002EB0

.text:01002EE3                 push    offset aDbgeng_dll ; "dbgeng.dll"
.text:01002EE8                 call    ds:LoadLibraryA
.text:01002F45                 push    offset aDebugcreate ; "DebugCreate"
.text:01002F4A                 mov     edx, [ebp+arg_8]
.text:01002F4D                 mov     eax, [edx]
.text:01002F4F                 push    eax             ; hModule
.text:01002F50                 call    ds:GetProcAddress
The DebugCreate function creates a new client object and returns an interface pointer to it, the parameters passed to DebugCreate are the same as those passed to IUnknown::QueryInterface, and they are treated the same way.

.text:0100307A                 call    sub_1004D90
.text:0100307F                 push    eax
.text:01003080                 push    offset aAttachprocessF ; "AttachProcess failed, %s\n"
.text:01003085                 call    ds:printf
kdbgctrl attempts to a Debug Attach by using the pid inserted by user.

.text:010030BA                 call    sub_1004D90
.text:010030BF                 push    eax
.text:010030C0                 push    offset aWaitforeventFa ; "WaitForEvent failed, %s\n"
.text:010030C5                 call    ds:printf
and waits for events.

.text:010030FA                 call    sub_1004D90
.text:010030FF                 push    eax
.text:01003100                 push    offset aGetnumberthrea ; "GetNumberThreads failed, %s\n"
.text:01003105                 call    ds:printf
Retrieve the number of Threads, this value is necessary to define the limits of a cycle that will perform a per thread scan.

.text:010031AB                 call    sub_1004D90
.text:010031B0                 push    eax
.text:010031B1                 push    offset aGetthreadidsby ; "GetThreadIdsByIndex failed, %s\n"
.text:010031B6                 call    ds:printf
.text:010031BC                 add     esp, 8
.text:010031BF                 jmp     loc_1003252
GetThreadIdsByIndex belongs (Like GetNumberThreads) to the interface IDebugSystemObjects, the GetThreadIdsByIndex method returns the engine and system thread IDs for the specified threads in the current process.
.text:010031E8                 call    sub_1004D90
.text:010031ED                 push    eax
.text:010031EE                 push    offset aSetcurrentthre ; "SetCurrentThreadId failed, %s\n"
.text:010031F3                 call    ds:printf
.text:010031F9                 add     esp, 8
.text:010031FC                 jmp     short loc_1003252
The currently indexed thread, became the Current Thread, at this point we can know the thread handle, by calling GetCurrentThreadHandle.

This enumeration routine provides all informations that will be stored into the Triage Dump.

Should be now clear how is builded a Triage Dump, just an hint if you want to uncover other kdbgctrl functionalities, as you have seen the core function is NtSystemDebugControl, so easly list all xRefs of this function.

See you to the next post..
Giuseppe 'Evilcry' Bonfa

Submit "[WinInternals] Reverse Engineering of kdbgctrl - How are builded Kernel Triage Dumps" to Digg Submit "[WinInternals] Reverse Engineering of kdbgctrl - How are builded Kernel Triage Dumps" to del.icio.us Submit "[WinInternals] Reverse Engineering of kdbgctrl - How are builded Kernel Triage Dumps" to StumbleUpon Submit "[WinInternals] Reverse Engineering of kdbgctrl - How are builded Kernel Triage Dumps" to Google