Make a plugin for WinDbg
WinDbg is a powerfull Windows debugger, it can debug x86 application and x64 application, in user-land or in kernel-land. Despite its useful commands, we would like to make some plugin to do a faster and better debugging session. Fortunately for us, it’s possible to write WinDbg plugins in C or C++ to add a lot of commands in our favorite debugger.
I will use C++, but it’s possible to use another language if this language support the DLL creation. With Python, you can use PyKd. The plugin that you will be able to code after this article and with a little documentation looks like that:
0:004> !load C:\Users\Dimitri\Desktop\DbgDf0\Release\DbgDf0.dll
0:004> !df0 mod
Image Base Size ASLR DEP Safe SEH Module Name
0x00400000 0x001C0000 Yes Yes No HxD.exe
0x77820000 0x00180000 Yes Yes No ntdll.dll
0x75930000 0x00110000 Yes Yes Yes kernel32.dll
0x753E0000 0x00047000 Yes Yes Yes KERNELBASE.dll
0x77300000 0x00100000 Yes Yes Yes user32.dll
0x75FA0000 0x00090000 Yes Yes Yes GDI32.dll
0x76100000 0x0000A000 Yes Yes Yes LPK.dll
0x761C0000 0x0009D000 Yes Yes Yes USP10.dll
0x76110000 0x000AC000 Yes Yes Yes msvcrt.dll
0x75EA0000 0x000A0000 Yes Yes Yes ADVAPI32.dll
0x77400000 0x00019000 Yes Yes Yes sechost.dll
0x75690000 0x000F0000 Yes Yes Yes RPCRT4.dll
0x75380000 0x00060000 Yes Yes Yes SspiCli.dll
0x75370000 0x0000C000 Yes Yes Yes CRYPTBASE.dll
0x75430000 0x0008F000 Yes Yes Yes oleaut32.dll
0x76260000 0x0015C000 Yes Yes Yes ole32.dll
0x752A0000 0x00009000 Yes Yes Yes version.dll
0x72820000 0x0019E000 Yes Yes Yes comctl32.dll
0x75F40000 0x00057000 Yes Yes Yes SHLWAPI.dll
0x76680000 0x00C49000 Yes Yes Yes shell32.dll
0x754D0000 0x000F5000 Yes Yes Yes wininet.dll
0x75D60000 0x00136000 Yes Yes Yes urlmon.dll
0x75810000 0x0011E000 Yes Yes Yes CRYPT32.dll
0x76670000 0x0000C000 Yes Yes Yes MSASN1.dll
0x763E0000 0x001FF000 Yes Yes Yes iertutil.dll
0x75630000 0x00060000 Yes Yes Yes imm32.dll
0x76030000 0x000CC000 Yes Yes Yes MSCTF.dll
0x75240000 0x00051000 Yes Yes Yes winspool.drv
0x75CE0000 0x0007B000 Yes Yes Yes comdlg32.dll
0x71FD0000 0x00032000 Yes Yes Yes winmm.dll
0x10000000 0x0005A000 Yes Yes Yes guard32.dll
0x75230000 0x00007000 Yes Yes Yes fltlib.dll
0x71E90000 0x00080000 Yes Yes Yes uxtheme.dll
0x71E70000 0x00013000 Yes Yes Yes dwmapi.dll
0x754C0000 0x00005000 Yes Yes No PSAPI.dll
0x71CE0000 0x00005000 Yes Yes Yes msimg32.dll
0x75B00000 0x0019D000 Yes Yes Yes SETUPAPI.dll
0x75A40000 0x00027000 Yes Yes Yes CFGMGR32.dll
0x763C0000 0x00012000 Yes Yes Yes DEVOBJ.dll
0x765E0000 0x00083000 Yes Yes Yes CLBCatQ.DLL
0x71D30000 0x000F5000 Yes Yes Yes propsys.dll
0x75200000 0x00021000 Yes Yes Yes ntmarta.dll
0x755D0000 0x00045000 Yes Yes Yes WLDAP32.dll
0:004> !unload DbgDf0
Unloading C:\Users\Dimitri\Desktop\DbgDf0\Release\DbgDf0.dll extension DLL
This command looks in the Process Environment Block (PEB) the module list in memory and check if ASLR, DEP or SafeSEH is activated on these modules. An useful functionality! So, after that, you can add a ROP gadgets searcher for example to save a precious time.
Bases
First of all, you need to know that a WinDbg plugin is just a DLL which export by name its functions. Some exported functions are needed for WinDbg to load our plugin, and the others are just our commands! Let me show you an example with my DbgDf0 plugin:
extern "C" {
_declspec(dllexport) VOID CheckVersion();
_declspec(dllexport) LPEXT_API_VERSION ExtensionApiVersion();
_declspec(dllexport) VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis, USHORT usMajorVersion, USHORT usMinorVersion);
_declspec(dllexport) DECLARE_API(df0);
}
The first is CheckVersion()
, it’s optional and it helps you to check the
WinDbg version and show a warning if you don’t have the correct version.
Personnally, I never implement this function but I have implemented it to show
you that it’s possible.
Another function, ExtensionApiVersion()
, is required and return the plugin
version. An example with ExtensionApiVersion()
wich return 1.0.0:
0:003> .chain
Extension DLL search Path:
...
Extension DLL chain:
C:\Users\Dimitri\Desktop\DbgDf0\Release\DbgDf0.dll: API 1.0.0, built Fri Feb 28 19:24:19 2014
[path: C:\Users\Dimitri\Desktop\DbgDf0\Release\DbgDf0.dll]
The next function, WinDbgExtensionDllInit()
is required and important because
it saves a pointer to WinDbg API list. Finally, DECLARE_API(df0)
is our
function that we can call with !df0
in WinDbg:
#define DECLARE_API(s) \
VOID \
s( \
HANDLE hCurrentProcess, \
HANDLE hCurrentThread, \
ULONG dwCurrentPc, \
ULONG dwProcessor, \
PCSTR args \
)
Last thing to know, all WinDbg SDK files are make for Visual Studio 2013. And if
you don’t like Windows 8, you can find the structures in
C:\Program Files (x86)\Windows Kits\8.1\Debuggers\inc\wdbgexts.h
and copy them
in your own header (windbg.h
for me).
Functions
To get WinDbg API, you need to recover a specific pointer:
VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis,
USHORT usMajorVersion, USHORT usMinorVersion) {
ExtensionApis = *lpExtensionApis;
}
Some preprocessor will help you to make a more readable source:
#define dprintf (ExtensionApis.lpOutputRoutine)
#define GetExpression (ExtensionApis.lpGetExpressionRoutine)
#define CheckControlC (ExtensionApis.lpCheckControlCRoutine)
#define GetContext (ExtensionApis.lpGetThreadContextRoutine)
#define SetContext (ExtensionApis.lpSetThreadContextRoutine)
#define Ioctl (ExtensionApis.lpIoctlRoutine)
#define Disasm (ExtensionApis.lpDisasmRoutine)
#define GetSymbol (ExtensionApis.lpGetSymbolRoutine)
#define ReadMemory (ExtensionApis.lpReadProcessMemoryRoutine)
#define WriteMemory (ExtensionApis.lpWriteProcessMemoryRoutine)
#define StackTrace (ExtensionApis.lpStackTraceRoutine)
Now, we will use dprintf
to show a message in WinDbg: it works like printf
from the C standard library.
To read WinDbg debugged process memory, we can use ReadMemory
wich works like
ReadProcessMemory
.
Code
You have now the base to writte a plugin like this:
#include <Windows.h>
#include "windbg.h"
#include "ntdll.h"
#define ASLR_ENABLED 1
#define DEP_ENABLED 2
#define SAFESEH_ENABLED 4
extern "C" {
_declspec(dllexport) VOID CheckVersion();
_declspec(dllexport) LPEXT_API_VERSION ExtensionApiVersion();
_declspec(dllexport) VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis, USHORT usMajorVersion, USHORT usMinorVersion);
_declspec(dllexport) DECLARE_API(df0);
}
EXT_API_VERSION ApiVersion = { 1, 0, 0, 0 };
WINDBG_EXTENSION_APIS ExtensionApis;
VOID CheckVersion() {
return;
}
LPEXT_API_VERSION ExtensionApiVersion() {
return &ApiVersion;
}
VOID WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis, USHORT usMajorVersion, USHORT usMinorVersion)
{
ExtensionApis = *lpExtensionApis;
dprintf("DbgDf0 loaded! \n\n");
}
int checkProtections(DWORD baseAddress) {
int ret = 0;
ULONG returnLength;
IMAGE_OPTIONAL_HEADER optionalHeader;
ReadMemory((ULONG_PTR) baseAddress + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER), &optionalHeader, sizeof(optionalHeader), &returnLength);
if (optionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) {
ret |= ASLR_ENABLED;
}
if (optionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT) {
ret |= DEP_ENABLED;
}
if (optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress != 0 && optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size != 0) {
ret |= SAFESEH_ENABLED;
}
return ret;
}
int listMod(HANDLE hCurrentProcess) {
HMODULE pNtdll = GetModuleHandle(L"ntdll.dll");
_NtQueryInformationProcess NtQueryInformationProcess = (_NtQueryInformationProcess) GetProcAddress(pNtdll, "NtQueryInformationProcess");
PROCESS_BASIC_INFORMATION basicInfo;
ULONG returnLength;
NtQueryInformationProcess(hCurrentProcess, 0, &basicInfo, sizeof(basicInfo), &returnLength);
PEB peb;
ReadMemory((ULONG_PTR) basicInfo.PebBaseAddress, &peb, sizeof(peb), &returnLength);
PEB_LDR_DATA ldrList;
ReadMemory((ULONG_PTR) peb.Ldr, &ldrList, sizeof(ldrList), &returnLength);
LIST_ENTRY listModules;
LDR_DATA_TABLE_ENTRY module;
DWORD firstModule = (DWORD) ldrList.InLoadOrderModuleList.Flink;
ReadMemory((ULONG_PTR) firstModule, &listModules, sizeof(listModules), &returnLength);
ReadMemory((ULONG_PTR) firstModule, &module, sizeof(module), &returnLength);
wchar_t wModuleName[1024] = {0};
char aModuleName[1024] = {0};
int protections = 0;
dprintf("Image Base \tSize \t\tASLR \tDEP \tSafe SEH \tModule Name \n");
do {
ReadMemory((ULONG_PTR) module.BaseDllName.Buffer, wModuleName, module.BaseDllName.Length+2, &returnLength);
WideCharToMultiByte(CP_ACP, 0, wModuleName, -1, aModuleName, sizeof(aModuleName), NULL, NULL);
protections = checkProtections((DWORD) module.DllBase);
dprintf("0x%0.8X \t0x%0.8X", module.DllBase, module.SizeOfImage);
(protections & ASLR_ENABLED) ? dprintf("\tYes") : dprintf("\tNo");
(protections & DEP_ENABLED) ? dprintf("\tYes") : dprintf("\tNo");
(protections & SAFESEH_ENABLED) ? dprintf("\tYes") : dprintf("\tNo");
dprintf("\t\t%s \n", aModuleName);
ReadMemory((ULONG_PTR) listModules.Flink, &module, sizeof(module), &returnLength);
ReadMemory((ULONG_PTR) listModules.Flink, &listModules, sizeof(listModules), &returnLength); // Next
} while ((DWORD) listModules.Flink != firstModule);
return 1;
}
DECLARE_API(df0)
{
if (strcmp(args, "mod") == 0) {
listMod(hCurrentProcess);
}
}
And the headers to test:
// windbg.h
#if !defined(WDBGAPI)
#define WDBGAPI __stdcall
#endif
#if !defined(WDBGAPIV)
#define WDBGAPIV __cdecl
#endif
typedef
VOID
(WDBGAPIV*PWINDBG_OUTPUT_ROUTINE)(
PCSTR lpFormat,
...
);
typedef
ULONG_PTR
(WDBGAPI*PWINDBG_GET_EXPRESSION)(
PCSTR lpExpression
);
typedef
ULONG
(WDBGAPI*PWINDBG_GET_EXPRESSION32)(
PCSTR lpExpression
);
typedef
ULONG64
(WDBGAPI*PWINDBG_GET_EXPRESSION64)(
PCSTR lpExpression
);
typedef
VOID
(WDBGAPI*PWINDBG_GET_SYMBOL)(
PVOID offset,
PCHAR pchBuffer,
ULONG_PTR *pDisplacement
);
typedef
VOID
(WDBGAPI*PWINDBG_GET_SYMBOL32)(
ULONG offset,
PCHAR pchBuffer,
PULONG pDisplacement
);
typedef
VOID
(WDBGAPI*PWINDBG_GET_SYMBOL64)(
ULONG64 offset,
PCHAR pchBuffer,
PULONG64 pDisplacement
);
typedef
ULONG
(WDBGAPI*PWINDBG_DISASM)(
ULONG_PTR *lpOffset,
PCSTR lpBuffer,
ULONG fShowEffectiveAddress
);
typedef
ULONG
(WDBGAPI*PWINDBG_DISASM32)(
ULONG *lpOffset,
PCSTR lpBuffer,
ULONG fShowEffectiveAddress
);
typedef
ULONG
(WDBGAPI*PWINDBG_DISASM64)(
ULONG64 *lpOffset,
PCSTR lpBuffer,
ULONG fShowEffectiveAddress
);
typedef
ULONG
(WDBGAPI*PWINDBG_CHECK_CONTROL_C)(
VOID
);
typedef
ULONG
(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE)(
ULONG_PTR offset,
PVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesRead
);
typedef
ULONG
(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE32)(
ULONG offset,
PVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesRead
);
typedef
ULONG
(WDBGAPI*PWINDBG_READ_PROCESS_MEMORY_ROUTINE64)(
ULONG64 offset,
PVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesRead
);
typedef
ULONG
(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE)(
ULONG_PTR offset,
LPCVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesWritten
);
typedef
ULONG
(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE32)(
ULONG offset,
LPCVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesWritten
);
typedef
ULONG
(WDBGAPI*PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE64)(
ULONG64 offset,
LPCVOID lpBuffer,
ULONG cb,
PULONG lpcbBytesWritten
);
typedef
ULONG
(WDBGAPI*PWINDBG_GET_THREAD_CONTEXT_ROUTINE)(
ULONG Processor,
PCONTEXT lpContext,
ULONG cbSizeOfContext
);
typedef
ULONG
(WDBGAPI*PWINDBG_SET_THREAD_CONTEXT_ROUTINE)(
ULONG Processor,
PCONTEXT lpContext,
ULONG cbSizeOfContext
);
typedef
ULONG
(WDBGAPI*PWINDBG_IOCTL_ROUTINE)(
USHORT IoctlType,
PVOID lpvData,
ULONG cbSize
);
typedef
ULONG
(WDBGAPI*PWINDBG_OLDKD_READ_PHYSICAL_MEMORY)(
ULONGLONG address,
PVOID buffer,
ULONG count,
PULONG bytesread
);
typedef
ULONG
(WDBGAPI*PWINDBG_OLDKD_WRITE_PHYSICAL_MEMORY)(
ULONGLONG address,
PVOID buffer,
ULONG length,
PULONG byteswritten
);
typedef struct _EXTSTACKTRACE {
ULONG FramePointer;
ULONG ProgramCounter;
ULONG ReturnAddress;
ULONG Args[4];
} EXTSTACKTRACE, *PEXTSTACKTRACE;
typedef struct _EXTSTACKTRACE32 {
ULONG FramePointer;
ULONG ProgramCounter;
ULONG ReturnAddress;
ULONG Args[4];
} EXTSTACKTRACE32, *PEXTSTACKTRACE32;
typedef struct _EXTSTACKTRACE64 {
ULONG64 FramePointer;
ULONG64 ProgramCounter;
ULONG64 ReturnAddress;
ULONG64 Args[4];
} EXTSTACKTRACE64, *PEXTSTACKTRACE64;
typedef
ULONG
(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE)(
ULONG FramePointer,
ULONG StackPointer,
ULONG ProgramCounter,
PEXTSTACKTRACE StackFrames,
ULONG Frames
);
typedef
ULONG
(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE32)(
ULONG FramePointer,
ULONG StackPointer,
ULONG ProgramCounter,
PEXTSTACKTRACE32 StackFrames,
ULONG Frames
);
typedef
ULONG
(WDBGAPI*PWINDBG_STACKTRACE_ROUTINE64)(
ULONG64 FramePointer,
ULONG64 StackPointer,
ULONG64 ProgramCounter,
PEXTSTACKTRACE64 StackFrames,
ULONG Frames
);
typedef struct _WINDBG_EXTENSION_APIS {
ULONG nSize;
PWINDBG_OUTPUT_ROUTINE lpOutputRoutine;
PWINDBG_GET_EXPRESSION lpGetExpressionRoutine;
PWINDBG_GET_SYMBOL lpGetSymbolRoutine;
PWINDBG_DISASM lpDisasmRoutine;
PWINDBG_CHECK_CONTROL_C lpCheckControlCRoutine;
PWINDBG_READ_PROCESS_MEMORY_ROUTINE lpReadProcessMemoryRoutine;
PWINDBG_WRITE_PROCESS_MEMORY_ROUTINE lpWriteProcessMemoryRoutine;
PWINDBG_GET_THREAD_CONTEXT_ROUTINE lpGetThreadContextRoutine;
PWINDBG_SET_THREAD_CONTEXT_ROUTINE lpSetThreadContextRoutine;
PWINDBG_IOCTL_ROUTINE lpIoctlRoutine;
PWINDBG_STACKTRACE_ROUTINE lpStackTraceRoutine;
} WINDBG_EXTENSION_APIS, *PWINDBG_EXTENSION_APIS;
#define DECLARE_API(s) \
VOID \
s( \
HANDLE hCurrentProcess, \
HANDLE hCurrentThread, \
ULONG dwCurrentPc, \
ULONG dwProcessor, \
PCSTR args \
)
#define dprintf (ExtensionApis.lpOutputRoutine)
#define GetExpression (ExtensionApis.lpGetExpressionRoutine)
#define CheckControlC (ExtensionApis.lpCheckControlCRoutine)
#define GetContext (ExtensionApis.lpGetThreadContextRoutine)
#define SetContext (ExtensionApis.lpSetThreadContextRoutine)
#define Ioctl (ExtensionApis.lpIoctlRoutine)
#define Disasm (ExtensionApis.lpDisasmRoutine)
#define GetSymbol (ExtensionApis.lpGetSymbolRoutine)
#define ReadMemory (ExtensionApis.lpReadProcessMemoryRoutine)
#define WriteMemory (ExtensionApis.lpWriteProcessMemoryRoutine)
#define StackTrace (ExtensionApis.lpStackTraceRoutine)
typedef struct EXT_API_VERSION {
USHORT MajorVersion;
USHORT MinorVersion;
USHORT Revision;
USHORT Reserved;
} EXT_API_VERSION, *LPEXT_API_VERSION;
// ntdll.h
#include <Windows.h>
typedef NTSTATUS (WINAPI* _NtQueryInformationProcess)(
_In_ HANDLE ProcessHandle,
_In_ DWORD ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
_ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
BOOLEAN ShutdownInProgress;
HANDLE ShutdownThreadId;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
DWORD ProcessParameters;
BYTE Reserved4[104];
PVOID Reserved5[52];
DWORD PostProcessInitRoutine;
BYTE Reserved6[128];
PVOID Reserved7[1];
ULONG SessionId;
} PEB, *PPEB;
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;