Midway upon the journey of our life I found myself within a forest dark, For the straightforward pathway had been lost.
[INFERNO CANTO 1]
In the world of debugging, one could easily get lost without sufficient knowledge of the underlying mechanism. While well known examples being DLL (Dynamic-Link Libraries), FPO (Frame-Pointer Omission), LTCG (Link-time Code Generation), PE/COFF and SEH (Structured Exception Handling), there are many other technologies used by Microsoft:
Detours
The following disassembly is directly related to Detours, MOV EDI, EDI is a placeholder which has 2 bytes for holding a NEAR JMP instruction. The NOP instructions has 5 bytes in total for holding an FAR JMP instruction (x86). In a short words, many Windows system DLLs have Detours in mind. The Visual C++ compiler has a command line option called /hotpatch (Create Hotpatchable Image) which does all the magic.
7541b4c1 0400 add al,0 7541b4c3 90 nop 7541b4c4 90 nop 7541b4c5 90 nop 7541b4c6 90 nop 7541b4c7 90 nop KERNELBASE!LoadLibraryExW: 7541b4c8 8bff mov edi,edi 7541b4ca 55 push ebp
NTDLL is not using the hot patch approach, the NOP instructions are just for padding to make sure each entry is aligned.
ntdll!NtQueueApcThread: 77236278 b80d010000 mov eax,10Dh 7723627d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) 77236282 ff12 call dword ptr [edx] 77236284 c21400 ret 14h 77236287 90 nop ntdll!ZwQueueApcThreadEx: 77236288 b80e010000 mov eax,10Eh 7723628d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) 77236292 ff12 call dword ptr [edx] 77236294 c21800 ret 18h 77236297 90 nop
With the introduction of KERNELBASE, a lot of kernel32 exported functions were forwarded.
0:000> .call kernel32!SetErrorMode(1) ^ Symbol not a function in '.call kernel32!SetErrorMode(1)'
0:000> u kernel32!SetErrorMode L1kernel32!SetErrorMode:75ac016d ff25b41da775 jmp dword ptr [kernel32!_imp__SetErrorMode (75a71db4)]
0:001> u poi(75a71db4)KERNELBASE!SetErrorMode:75417991 8bff mov edi,edi75417993 55 push ebp75417994 8bec mov ebp,esp75417996 51 push ecx75417997 56 push esi75417998 e836000000 call KERNELBASE!GetErrorMode (754179d3)7541799d 8bf0 mov esi,eax7541799f 8b4508 mov eax,dword ptr [ebp+8]
Basic Block Tools
BBT would merge duplicated blocks, rearrange binary blocks and do a lot crazy things to the symbol files (PDB). Your callstack will look weired as functions might get merged and overlapped, especially if C++ templates are used heavily. You can tell if optimization was performed on basic block level by examining the function body.
Frame-Pointer Omission
FPO was introduced with Windows NT 3.51 thanks to 80386 making ESP available for indexing, thus allowing EBP to be used as a general purpose register. But FPO makes stack unwinding unreliable, which in turn makes it painful to debug. You can tell if FPO was used by examining the function prologue/epilogue.
FPO disabled:
BOOL WINAPI Foobar(){55 push ebp8B EC mov ebp, esp return TRUE;B8 01 00 00 00 mov eax, 1}5D pop ebpC3 ret
FPO enabled:
BOOL WINAPI Foobar(){ return TRUE;B8 01 00 00 00 mov eax, 1}C3 ret
FPO information is available from both public and private PDB files, WinDBG has a command kv which can be used to examine this information:
0:000> kv ChildEBP RetAddr Args to Child 002bfdac 75d9339a 7efde000 002bfdf8 76f39ed2 notepad!WinMainCRTStartup (FPO: [0,0,0]) 002bfdb8 76f39ed2 7efde000 7b449f70 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo]) 002bfdf8 76f39ea5 005b3689 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 002bfe10 00000000 005b3689 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
Link-time Code Generation
LTCG was introduced with the first version of .NET. It can be used with or without PGO (Profile Guided Optimization). If you were debugging optimized C++ application, you should already know that local variables and inline functions can be very different. With LTCG, cross-module inlining is even possible, in addition, calling convention and parameters can be optimized. Similar as BBT, functions might get merged.
Profile Guided Optimization
PGO (a.k.a. POGO) does a lot of optimization such as inlining, virtual call speculation, conditional branch optimization. What's more, POGO is able to perform optimizations at extended basic block level.
Incremental Linking
The Microsoft Incremental Linker has an option /INCREMENTAL (don't confuse it with an incremental compiler which makes use of precompiled header) which would affect debugging. In fact, the native EnC (Edit and Continue) is built on top of incremental linking technology. Sometimes we may get symbols like module!ILT+0(_main), the ILT (Incremental Link Table) serves the incremental linker by adding a layer of indirection, thus provides the flexibility for binary patching. The bad news is that incremental linker has to generate correct symbols and patch them into PDB as well. The patching process doesn't discard unused symbols in a reliable manner. This would be challenging for debugger authors, since the integrity of symbols is not guaranteed by the MSPDB layer.
Function Inlining
Function inlining means there will be no actual call. The stepper and symbol binding components in debugger might get confused.
Intrinsic Function
Intrinsic functions are a special kind of function generated by the compiler toolchain (instead of coming from libraries or your code).
Starting from Visual Studio 2010 Ultimate there is a cool feature called DGML (Directed Graph Markup Language).
I wrote a small script to convert the disassembled code from WinDBG into a DGML.
In order to use it, simply type the following commands under a debug session:
.shell -o LoadLibraryA.dgml -ci "uf kernel32!LoadLibraryA" cscript.exe /nologo dasm2dgml.js
A DGML file will be generated with the given name, and here is what it looks like:
Here is the source code:
var EBB = []; var hypertext=function(s){ var r=[],L=s.length; for(var i=0;i<L;i++){ var c=s.charAt(i); switch(c){ case '"':r.push('"');break; case '&':r.push('&');break; case '<':r.push('<');break; case '>':r.push('>');break; default:r.push(c);}} return r.join(''); }; var map=function(f,v){var L=v.length,r=[];for(var i=0;i<L;i++)r.push(f(v[i]));return r;}; (function(){ var blk; var CExtendedBasicBlock = function(name, previous, next){ this.Address = ''; this.Code = []; this.Name = name; this.Previous = previous; this.Next = next; }; while(true) { if(WScript.StdIn.AtEndOfStream) break; var strSourceLine = WScript.StdIn.ReadLine().replace(/(^\s+)|(\s+$)/g, ''); if(!strSourceLine) continue; if(strSourceLine.match(/.*:$/)) { blk = new CExtendedBasicBlock(strSourceLine.slice(0, -1)); EBB.push(blk); } else { blk.Address = blk.Address || strSourceLine.match(/^[^\s]+/)[0]; blk.Code.push(strSourceLine.replace(/[^\s]*\s+/, '').replace(/[^\s]*\s+/, '')); } } })(); EBB = EBB.sort(function(x, y){ return x.Address == y.Address ? 0 : x.Address > y.Address ? 1 : -1; }); for(var i = 1; i < EBB.length; i++) { EBB[i].Previous = EBB[i - 1]; EBB[i].Previous.Next = EBB[i]; } WScript.Echo('<DirectedGraph Background="#FFFFFF" GraphDirection="TopToBottom" xmlns="http://schemas.microsoft.com/vs/2009/dgml">'); WScript.Echo(' <Nodes>'); map(function(blk){ var content = hypertext(blk.Name + ' (' + blk.Address + ')') + '
'; map(function(instruction){ content += '
' + hypertext(instruction); }, blk.Code); WScript.Echo(' <Node Id="' + hypertext(blk.Name) + '" Label="' + content + '" />'); }, EBB); WScript.Echo(' </Nodes>'); WScript.Echo(' <Links>'); map(function(blk){ map(function(instruction){ map(function(x){ var idx = instruction.indexOf(x.Name); idx = idx >= 0 ? instruction.charAt(idx + x.Name.length) : -1; if(idx == '' || idx == ' ') WScript.Echo(' <Link Source="' + hypertext(blk.Name) + '" Target="' + hypertext(x.Name) + '" />'); }, EBB); }, blk.Code); if(blk.Next && !(blk.Code[blk.Code.length - 1].match(/^[^\s]+/)[0] in {jmp: 0, ret: 0})) WScript.Echo(' <Link Category="FallThrough" Source="' + hypertext(blk.Name) + '" Target="' + hypertext(blk.Next.Name) + '" />'); }, EBB); WScript.Echo(' </Links>'); WScript.Echo(' <Styles>'); WScript.Echo(' <Style TargetType="Node">'); WScript.Echo(' <Setter Property="FontFamily" Value="Consolas" />'); WScript.Echo(' <Setter Property="FontSize" Value="11" />'); WScript.Echo(' <Setter Property="Background" Value="White" />'); WScript.Echo(' <Setter Property="NodeRadius" Value="2" />'); WScript.Echo(' </Style>'); WScript.Echo(' <Style TargetType="Link">'); WScript.Echo(' <Condition Expression="HasCategory(\'FallThrough\')" />'); WScript.Echo(' <Setter Property="Background" Value="Red" />'); WScript.Echo(' <Setter Property="Stroke" Value="Red" />'); WScript.Echo(' </Style>'); WScript.Echo(' </Styles>'); WScript.Echo('</DirectedGraph>');
Notes:
Enjoy:)
Using OutputDebugString is a common debugging technique for user mode debugging. It is easy but quite useful if you are debugging services or trouble shooting loader problem.
#define WIN32_LEAN_AND_MEAN #include <Windows.h> int __cdecl main() { OutputDebugStringA("Hello, world! (first chance)\n"); OutputDebugStringW(L"Hello, world! (second chance)\n"); }
When you debug this demo application using Visual Studio, you would see the text messages from the Output window:
And if you are running this demo without debugging it, you can actually use DbgView to see the outputs (the Microsoft Platform SDK also comes with a console application called Dbmon.Exe):
Have you ever wondered how it works?
Let's launch the demo application from WinDBG (I'm using 32bit Win7, you might see different things if you are using other version of Windows):
windbg.exe outputdebugstring.exe
0:000> x kernel*!OutputDebugString*760c1321 KERNELBASE!OutputDebugStringA760c1585 KERNELBASE!OutputDebugStringW766e6df2 kernel32!OutputDebugStringW766ef0f0 kernel32!OutputDebugStringA0:000> bp kernelbase!OutputDebugStringW0:000> bl 0 e 760c1585 0001 (0001) 0:**** KERNELBASE!OutputDebugStringW0:000> gHello, world! (first chance)Breakpoint 0 hitKERNELBASE!OutputDebugStringW:760c1585 8bff mov edi,edi0:000> pcKERNELBASE!OutputDebugStringW+0x1d:760c15a2 ff15ec110a76 call dword ptr [KERNELBASE+0x11ec (760a11ec)] ds:0023:760a11ec={ntdll!RtlInitUnicodeStringEx (77d86f0a)}0:000> pcKERNELBASE!OutputDebugStringW+0x33:760c15b8 ff1500100a76 call dword ptr [KERNELBASE+0x1000 (760a1000)] ds:0023:760a1000={ntdll!RtlUnicodeStringToAnsiString (77d89e8e)}0:000> pcKERNELBASE!OutputDebugStringW+0x42:760c15c7 e855fdffff call KERNELBASE!OutputDebugStringA (760c1321)
If we continue tracing, eventually we will reach this point:
ChildEBP RetAddr0015f610 77d762a4 ntdll!KiFastSystemCall0015f614 77d770fd ntdll!ZwRaiseException+0xc0015f8f8 760ad36f ntdll!RtlRaiseException+0x350015f954 760c1375 KERNELBASE!RaiseException+0x580015fbc0 01351021 KERNELBASE!OutputDebugStringA+0x286
Dump the SEH chain:
0:000> dt nt!_EXCEPTION_REGISTRATION_RECORD -r poi(fs:[0])ntdll!_EXCEPTION_REGISTRATION_RECORD +0x000 Next : 0x0016fd58 _EXCEPTION_REGISTRATION_RECORD +0x000 Next : 0x0016fdac _EXCEPTION_REGISTRATION_RECORD +0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD +0x004 Handler : 0x77d4e0ed _EXCEPTION_DISPOSITION ntdll!_except_handler4+0 +0x004 Handler : 0x00a72440 _EXCEPTION_DISPOSITION outputdebugstring!_except_handler4+0 +0x004 Handler : 0x760c1425 _EXCEPTION_DISPOSITION KERNELBASE!_except_handler4+0
0:000> !exchain0029fcb0: KERNELBASE!_except_handler4+0 (760c1425) CRT scope 0, filter: KERNELBASE!OutputDebugStringA+60 (760c13bc) func: KERNELBASE!OutputDebugStringA+74 (760c11d1)0029fd10: outputdebugstring!_except_handler4+0 (00e72440) CRT scope 0, filter: outputdebugstring!__tmainCRTStartup+1f4 (00e71584) func: outputdebugstring!__tmainCRTStartup+20f (00e7159f)0029fd64: ntdll!_except_handler4+0 (77d4e0ed) CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77da7eeb) func: ntdll!__RtlUserThreadStart+63 (77da8260)
An SEH was chained, and the control is returned to the OS kernel.
0:000> u 760c11d1 L20KERNELBASE!OutputDebugStringA+0x74:760c11d1 8b65e8 mov esp,dword ptr [ebp-18h]760c11d4 33ff xor edi,edi760c11d6 89bdd0fdffff mov dword ptr [ebp-230h],edi760c11dc 89bddcfdffff mov dword ptr [ebp-224h],edi760c11e2 89bdd4fdffff mov dword ptr [ebp-22Ch],edi760c11e8 89bde0fdffff mov dword ptr [ebp-220h],edi760c11ee e86d56feff call KERNELBASE!GetLastError (760a6860)760c11f3 8985c8fdffff mov dword ptr [ebp-238h],eax760c11f9 393d28490e76 cmp dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)],edi760c11ff 0f8457040000 je KERNELBASE!OutputDebugStringA+0xa4 (760c165c)760c1205 a128490e76 mov eax,dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)]760c120a 3bc7 cmp eax,edi760c120c 7441 je KERNELBASE!OutputDebugStringA+0x166 (760c124f)760c120e 6810270000 push 2710h760c1213 50 push eax760c1214 e83666feff call KERNELBASE!WaitForSingleObject (760a784f)760c1219 3bc7 cmp eax,edi760c121b 0f8570040000 jne KERNELBASE!OutputDebugStringA+0xf3 (760c1691)760c1221 6870120c76 push offset KERNELBASE!`string' (760c1270)760c1226 57 push edi760c1227 6a02 push 2760c1229 e8df7bfeff call KERNELBASE!OpenFileMappingW (760a8e0d)760c122e 8985d0fdffff mov dword ptr [ebp-230h],eax760c1234 3bc7 cmp eax,edi760c1236 0f85dea00000 jne KERNELBASE!OutputDebugStringA+0x111 (760cb31a)760c123c 39bde0fdffff cmp dword ptr [ebp-220h],edi760c1242 750b jne KERNELBASE!OutputDebugStringA+0x166 (760c124f)760c1244 ff3528490e76 push dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)]760c124a e81204feff call KERNELBASE!ReleaseMutex (760a1661)760c124f c745fc01000000 mov dword ptr [ebp-4],1760c1256 c745fc02000000 mov dword ptr [ebp-4],2760c125d 8b85ccfdffff mov eax,dword ptr [ebp-234h]
As we could see from the disassembly, the byte value on 0x760c11d1 is 0x8b, let's change it to INT3:
0:000> ?? *(char*)(0x760c11d1) = 0xccchar 0n-52 ''
Before we can continue, let's make sure WinDBG is installed as the Just-In-Time debugger by running the following command from an Elevated Command Prompt:
windbg.exe -IS
Now we are ready to continue:
0:000> .detachDetachedNoTarget> q
On the same time, Just-In-Time debugging is triggered and another WinDBG just pops up with the following information:
(aa0.11cc): Break instruction exception - code 80000003 (!!! second chance !!!)KERNELBASE!OutputDebugStringA+0x74:760c11d1 cc int 3
Change the value on 0x760c11d1 back (or did you just forget the value?).
0:000> ?? *(char*)(0x760c11d1) = 0x8bchar 0n-117 ''
Euphoria:
KERNELBASE!DbgPrint:760c11cb ff251c130a76 jmp dword ptr [KERNELBASE!_imp__DbgPrint (760a131c)]760c11d1 8b65e8 mov esp,dword ptr [ebp-18h] ss:0023:0022fbb8=0022f97c760c11d4 33ff xor edi,edi760c11d6 89bdd0fdffff mov dword ptr [ebp-230h],edi
I'll leave the rest things for you to figure out. Hints:
0:000> dda esp0022f97c 4464c9b10022f980 000000000022f984 0022fbdc ""0022f988 7ffd4000 ""0022f98c 0000001e0022f990 008f573c "Hello, world! (first chance)."0022f994 002c00c4 ".,,"0022f998 00000000
0:000> da 760cb478760cb478 "DBWIN_BUFFER_READY"
Now we have finished the debugging approach to OutputDebugString, it's time to start writing a simple user mode dbgview:
#define WIN32_LEAN_AND_MEAN #include <Windows.h> #include <stdio.h> #define IfFalseRet(c) do{if(!(c)){return dwLastError = ::GetLastError();}}while(false) class CHandle { public: CHandle(HANDLE h = NULL): m_h(h) { } ~CHandle() { Release(); } void Release() { if(*this) { ::CloseHandle(m_h); } m_h = NULL; } operator bool() const { return m_h != INVALID_HANDLE_VALUE && m_h != NULL; } operator HANDLE() const { return m_h; } CHandle& operator= (const HANDLE& h) { Release(); m_h = h; return *this; } CHandle& operator= (CHandle& h) { if(this != &h) { HANDLE hSwap = m_h; m_h = h.m_h; h.m_h = hSwap; h.Release(); } return *this; } private: HANDLE m_h; }; LPCTSTR DBWIN_BUFFER = TEXT("DBWIN_BUFFER"); LPCTSTR DBWIN_BUFFER_READY = TEXT("DBWIN_BUFFER_READY"); LPCTSTR DBWIN_DATA_READY = TEXT("DBWIN_DATA_READY"); LPCTSTR DBWIN_MUTEX = TEXT("DBWinMutex"); #pragma pack(push, 1) struct CDBWinBuffer { DWORD dwProcessId; BYTE abData[4096 - sizeof(DWORD)]; }; #pragma pack(pop) bool g_fContinue = true; BOOL CtrlHandler(DWORD fdwCtrlType) { switch(fdwCtrlType) { case CTRL_C_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: g_fContinue = false; return TRUE; } return FALSE; } int __cdecl main() { DWORD dwLastError = ERROR_SUCCESS; IfFalseRet(SetConsoleCtrlHandler((PHANDLER_ROUTINE)(CtrlHandler), TRUE) == TRUE); CHandle hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, DBWIN_MUTEX); if(!hMutex) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hMutex = CreateMutex(NULL, FALSE, DBWIN_MUTEX)); } CHandle hEventBufferReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_BUFFER_READY); if(!hEventBufferReady) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hEventBufferReady = CreateEvent(NULL, FALSE, TRUE, DBWIN_BUFFER_READY)); } CHandle hEventDataReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_DATA_READY); if(!hEventDataReady) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hEventDataReady = CreateEvent(NULL, FALSE, FALSE, DBWIN_DATA_READY)); } CHandle hFileMappingBuffer = OpenFileMapping(FILE_MAP_READ, FALSE, DBWIN_BUFFER); if(!hFileMappingBuffer) { IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND); IfFalseRet(hFileMappingBuffer = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CDBWinBuffer), DBWIN_BUFFER)); } CDBWinBuffer* pDbgBuffer = (CDBWinBuffer*)(MapViewOfFile( hFileMappingBuffer, SECTION_MAP_READ, 0, 0, 0)); IfFalseRet(pDbgBuffer); while(g_fContinue) { if(WaitForSingleObject(hEventDataReady, 100) == WAIT_OBJECT_0) { printf("%s", pDbgBuffer->abData); SetEvent(hEventBufferReady); } } UnmapViewOfFile(pDbgBuffer); return dwLastError;
}
Homework:
For the homework, you may find some hints from the WinDBG output, where I've already marked in color.
A target program might behave differently if it is being debugged, sometimes this can be very annoying. Also, these behavior deviations can be leveraged by anti-debugging.
IsDebuggerPresent and CheckRemoteDebuggerPresent are well known APIs to tell if a program is attached by a debugger.
0:000> uf KERNELBASE!IsDebuggerPresent KERNELBASE!IsDebuggerPresent: 7512f41b 64a118000000 mov eax,dword ptr fs:[00000018h] 7512f421 8b4030 mov eax,dword ptr [eax+30h] 7512f424 0fb64002 movzx eax,byte ptr [eax+2] 7512f428 c3 ret
CloseHandle would raise an exception under a debugger, as stated by MSDN:
If the application is running under a debugger, the function will throw an exception if it receives either a handle value that is not valid or a pseudo-handle value.
Windows heap manager would use debug heap (note: this has nothing to do with the CRT Debug Heap) if a program was launched from debugger:
OutputDebugString, we've have a dedicated topic on it.
SetUnhandledExceptionFilter, a decent article can be found at debuginfo.com. A simple detouring is to intercept IsDebugPortPresent and return FALSE.
NtSetInformationThread can be used to hide (detach) a thread from debugger.
In addition, the target program can check its own integrity or the integrity of the system.
A few things to mention:
Abstraction and encapsulation are good because they make it easier to build complex systems, however, there are times you have to peek inside the abstraction and demistify the encapsulation. This is especially true for debugging and performance tuning (I will not talk about reverse engineering this time). Familiar yourself with the right tools are very important, and one way to achieve this is to debug into these tools...
Before getting started, I would like to wetting your appetite with a few questions:
Let's revisit the commands in WinDBG. There are three types of commands available:
The documented way of working with extensions contains a few commands like .chain, .extmatch, .load, .loadby, .setdll, .unload and .unloadall, an undocumented command .extcmds is also available.
0:000> .extmatch /e dbghelp * !dbghelp.chksym !dbghelp.dh !dbghelp.homedir !dbghelp.lmi !dbghelp.stackdbg !dbghelp.sym
However, once you understand how these extension DLLs work, you can dump the PE/COFF Export Directory to get a full list:
0:000> .sympath . Symbol search path is: . Expanded Symbol search path is: . 0:000> .reload /f dbghelp.dll *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Debugging Tools for Windows (x64)\dbghelp.dll - 0:000> x dbghelp!* 00000000`68013dd0 dbghelp!ExtensionApiVersion (<no parameter info>) 00000000`68013de0 dbghelp!WinDbgExtensionDllInit (<no parameter info>) 00000000`68014090 dbghelp!fptr (<no parameter info>) 00000000`68014130 dbghelp!vc7fpo (<no parameter info>) 00000000`680141b0 dbghelp!stackdbg (<no parameter info>) 00000000`680146e0 dbghelp!stack_force_ebp (<no parameter info>) 00000000`680148c0 dbghelp!sym (<no parameter info>) 00000000`68014a50 dbghelp!symsrv (<no parameter info>) 00000000`68014bd0 dbghelp!lminfo (<no parameter info>) 00000000`68015060 dbghelp!lmi (<no parameter info>) 00000000`680164e0 dbghelp!itoldyouso (<no parameter info>) 00000000`68016720 dbghelp!chksym (<no parameter info>) 00000000`68016960 dbghelp!block (<no parameter info>) 00000000`68016b50 dbghelp!omap (<no parameter info>) 00000000`68016e10 dbghelp!homedir (<no parameter info>) 00000000`68017030 dbghelp!srcfiles (<no parameter info>) 00000000`68018a90 dbghelp!dh (<no parameter info>)
0:000> !itoldyouso !IToldYouSo <module> [symbol] !IToldYouSo tests the validity of a module against a symbol file. The module can be specified by either its name or base address. If a symbol file is not specified, then the loaded symbol is tested. Otherwise, if a pdb or dbg symbol file path is specified, it is tested against the loaded module.
Most of the meta commands are implemented in dbgeng.dll, a few are implemented inside specific debuggers (e.g. .browse and .wtitle are WinDBG specific meta commands). Some meta commands are only available for certain modes/targets/platforms, for example, .kdfiles is only available for live kernel debugging on x86-based and Itanium-based processors.
Some meta commands might be undocumented (e.g. .aliascmds and .sxcmds) or documented separately (e.g. .perfer_dml is documented in dml.doc which can be found under the WinDBG installation folder, .jdinfo is only documented in .help), there is no centralized place to get all these commands.
The following command would dump all the dot commands exported by dbgeng.dll (dbgeng!DotCommand is an exception, which is not a meta command):
0:000> x dbgeng!Dot* 00000000`517aba70 dbgeng!DotRestart = <no type information> 00000000`51801c70 dbgeng!DotExtMatch = <no type information> 00000000`517a9ac0 dbgeng!DotLogAppend = <no type information> 00000000`517ae600 dbgeng!DotTTime = <no type information> 00000000`517a3d70 dbgeng!DotEchoTimestamps = <no type information> 00000000`517aacd0 dbgeng!DotPromptAllow = <no type information> 00000000`517a53e0 dbgeng!DotEventStr = <no type information> 00000000`517aa0b0 dbgeng!DotOCommand = <no type information> 00000000`5179de00 dbgeng!DotPageIn = <no type information> 00000000`517a09f0 dbgeng!DotCache = <no type information> 00000000`517aaa80 dbgeng!DotPrintf = <no type information> 00000000`517a60a0 dbgeng!DotFnRet = <no type information> 00000000`5179d3c0 dbgeng!DotThread = <no type information> 00000000`517a9a40 dbgeng!DotLocale = <no type information> 00000000`517a46f0 dbgeng!DotEndSrv = <no type information> 00000000`517a33b0 dbgeng!DotDumpOff = <no type information> 00000000`517a5900 dbgeng!DotExr = <no type information> 00000000`517a2880 dbgeng!DotCxr = <no type information> 00000000`517a4f50 dbgeng!DotEvents = <no type information> 00000000`5179fb30 dbgeng!DotApplyDbp = <no type information> 00000000`517ae350 dbgeng!DotTrap = <no type information> 00000000`517a98c0 dbgeng!DotKFrames = <no type information> 00000000`517aca70 dbgeng!DotSleep = <no type information> 00000000`5179f9e0 dbgeng!DotAllowImageMapping = <no type information> 00000000`517a3e20 dbgeng!DotEcxr = <no type information> 00000000`517a5e30 dbgeng!DotFixImports = <no type information> 00000000`517aa3b0 dbgeng!DotOpenDump = <no type information> 00000000`517a0500 dbgeng!DotBpSync = <no type information> 00000000`5179d9b0 dbgeng!DotProcess = <no type information> 00000000`517ac280 dbgeng!DotServers = <no type information> 00000000`5179dd80 dbgeng!DotKernelKill = <no type information> 00000000`517a2ea0 dbgeng!DotDmlStart = <no type information> 00000000`517ab890 dbgeng!DotRecordBranches = <no type information> 00000000`517a06c0 dbgeng!DotBugCheck = <no type information> 00000000`517a2bd0 dbgeng!DotDebugSwWow = <no type information> 00000000`517a1510 dbgeng!DotContext = <no type information> 00000000`517a4740 dbgeng!DotEnumTag = <no type information> 00000000`517a8b40 dbgeng!DotIf = <no type information> 00000000`5179db50 dbgeng!DotFiber = <no type information> 00000000`517ab810 dbgeng!DotReboot = <no type information> 00000000`517a03a0 dbgeng!DotBpCmds = <no type information> 00000000`5183b550 dbgeng!DotDmlFlow = <no type information> 00000000`517ace20 dbgeng!DotSrcPath = <no type information> 00000000`517a3380 dbgeng!DotDumpDebug = <no type information> 00000000`517adf50 dbgeng!DotProcessInfo = <no type information> 00000000`5179f950 dbgeng!DotAllowBpBaConvert = <no type information> 00000000`517ab960 dbgeng!DotReCxr = <no type information> 00000000`518568c0 dbgeng!DotPCmd = <no type information> 00000000`517aba10 dbgeng!DotReload = <no type information> 00000000`5179fe60 dbgeng!DotAttach = <no type information> 00000000`517aa070 dbgeng!DotNoVersion = <no type information> 00000000`518eb230 dbgeng!DotLines = <no type information> 00000000`517a2dd0 dbgeng!DotDmlFile = <no type information> 00000000`517a7740 dbgeng!DotFormats = <no type information> 00000000`517accb0 dbgeng!DotSrcNoisy = <no type information> 00000000`517a3170 dbgeng!DotDumpCab = <no type information> 00000000`517a8e00 dbgeng!DotIgnoreWowKdContext = <no type information> 00000000`517ad880 dbgeng!DotTList = <no type information> 00000000`517ab3c0 dbgeng!DotReadMem = <no type information> 00000000`517acb20 dbgeng!DotSrcFix = <no type information> 00000000`517a5670 dbgeng!DotExPtr = <no type information> 00000000`517a6540 dbgeng!DotForceBranchTrace = <no type information> 00000000`517a3540 dbgeng!DotDumpPOff = <no type information> 00000000`517aec40 dbgeng!DotWake = <no type information> 00000000`517a0370 dbgeng!DotBlock = <no type information> 00000000`517abcb0 dbgeng!DotSendFile = <no type information> 00000000`517a62d0 dbgeng!DotFor = <no type information> 00000000`517a3140 dbgeng!DotDrivers = <no type information> 00000000`517a9b10 dbgeng!DotLogFile = <no type information> 00000000`517a37d0 dbgeng!DotDvAlloc = <no type information> 00000000`517ab0a0 dbgeng!DotPop = <no type information> 00000000`517d9c20 dbgeng!DotDump = <no type information> 00000000`517a7c30 dbgeng!DotFrame = <no type information> 00000000`517af540 dbgeng!DotHelp = <no type information> 00000000`517a9be0 dbgeng!DotLogOpen = <no type information> 00000000`517af8b0 dbgeng!DotCommand = <no type information> 00000000`517a9c00 dbgeng!DotNetSyms = <no type information> 00000000`517ace80 dbgeng!DotStepFilter = <no type information> 00000000`517a1660 dbgeng!DotContinue = <no type information> 00000000`517a05d0 dbgeng!DotBreakin = <no type information> 00000000`517a4170 dbgeng!DotEnableLongStatus = <no type information> 00000000`517a6680 dbgeng!DotForceSystemInit = <no type information> 00000000`517a5f90 dbgeng!DotFnEnt = <no type information> 00000000`517a0e70 dbgeng!DotCatch = <no type information> 00000000`517a9840 dbgeng!DotKdFiles = <no type information> 00000000`517a0ea0 dbgeng!DotChain = <no type information> 00000000`517a2380 dbgeng!DotCrash = <no type information> 00000000`5183a420 dbgeng!DotAsm = <no type information> 00000000`517ac900 dbgeng!DotShowSymFailures = <no type information> 00000000`517ac1b0 dbgeng!DotServer = <no type information> 00000000`517ac800 dbgeng!DotShowReadFailures = <no type information> 00000000`517a6810 dbgeng!DotForEach = <no type information> 00000000`517e9d60 dbgeng!DotEventLog = <no type information> 00000000`517ad210 dbgeng!DotSymOpt = <no type information> 00000000`517a6620 dbgeng!DotForceRadixOutput = <no type information> 00000000`517a7160 dbgeng!DotFpo = <no type information> 00000000`517a3fa0 dbgeng!DotEffMach = <no type information> 00000000`517a7d80 dbgeng!DotFrameRel = <no type information> 00000000`517ae430 dbgeng!DotTss = <no type information> 00000000`51725a30 dbgeng!DotAliasCmds = <no type information> 00000000`517a41d0 dbgeng!DotEnableUnicode = <no type information> 00000000`517aa030 dbgeng!DotNoShell = <no type information> 00000000`517aa540 dbgeng!DotOutMask = <no type information> 00000000`517a9880 dbgeng!DotKdTrans = <no type information> 00000000`517a3d20 dbgeng!DotEchoTime = <no type information> 00000000`517aa800 dbgeng!DotPreferDml = <no type information> 00000000`517a9a10 dbgeng!DotLeave = <no type information> 00000000`517a7ec0 dbgeng!DotFrameEbpFix = <no type information> 00000000`517a05a0 dbgeng!DotBreak = <no type information> 00000000`517a3a30 dbgeng!DotDvFree = <no type information> 00000000`517a54a0 dbgeng!DotExpr = <no type information> 00000000`517a30b0 dbgeng!DotDbgDbg = <no type information> 00000000`517a5930 dbgeng!DotExtCmds = <no type information> 00000000`517aed10 dbgeng!DotWriteMem = <no type information> 00000000`517ad040 dbgeng!DotSxCmds = <no type information> 00000000`517a59e0 dbgeng!DotExtPath = <no type information> 00000000`517aafc0 dbgeng!DotPush = <no type information> 00000000`517ad0e0 dbgeng!DotSymFix = <no type information> 00000000`517a2420 dbgeng!DotCreate = <no type information> 00000000`517ac2b0 dbgeng!DotShell = <no type information> 00000000`517a3010 dbgeng!DotDo = <no type information> 00000000`517aec90 dbgeng!DotWhile = <no type information> 00000000`517a9d40 dbgeng!DotNetUse = <no type information> 00000000`517ae170 dbgeng!DotTimeZone = <no type information> 00000000`517a0f90 dbgeng!DotChildDbg = <no type information> 00000000`517a1130 dbgeng!DotClients = <no type information> 00000000`517a4680 dbgeng!DotEndPSrv = <no type information> 00000000`517a8910 dbgeng!DotHoldMem = <no type information> 00000000`517a8ea0 dbgeng!DotImgScan = <no type information> 00000000`517ad3e0 dbgeng!DotTListFromNtQuerySystemInformation = <no type information> 00000000`517a4b40 dbgeng!DotEventCode = <no type information> 00000000`517a3630 dbgeng!DotDumpAddRgn = <no type information> 00000000`517a9790 dbgeng!DotJdInfo = <no type information> 00000000`517ad330 dbgeng!DotSymPath = <no type information> 00000000`517ade40 dbgeng!DotTime = <no type information> 00000000`517a5430 dbgeng!DotExePath = <no type information> 00000000`517a8d60 dbgeng!DotIgnoreMissingPages = <no type information> 00000000`517aeaa0 dbgeng!DotTxtSym = <no type information> 00000000`517a3c70 dbgeng!DotEchoCpuNum = <no type information> 00000000`517a3c20 dbgeng!DotEcho = <no type information> 00000000`517aa010 dbgeng!DotNoEngErr = <no type information> 00000000`517aa7d0 dbgeng!DotPCache = <no type information> 00000000`5179f8b0 dbgeng!DotAllowExecCmds = <no type information> 00000000`517a1ab0 dbgeng!DotCorDll = <no type information> 00000000`517a26e0 dbgeng!DotCreateDir = <no type information> 00000000`517a1860 dbgeng!DotCopySym = <no type information> 00000000`517a9af0 dbgeng!DotLogClose = <no type information> 00000000`517a4230 dbgeng!DotTypeOpt = <no type information> 00000000`517a0b70 dbgeng!DotCall = <no type information> 00000000`517abc10 dbgeng!DotSecure = <no type information> 00000000`517aa230 dbgeng!DotOFilter = <no type information> 00000000`517a11f0 dbgeng!DotCloseHandle = <no type information> 00000000`517ab180 dbgeng!DotQuitLock = <no type information> 00000000`517a9990 dbgeng!DotLastEvent = <no type information> 00000000`517a2220 dbgeng!DotCorStack = <no type information>
Also, you might have noticed that .elif and .else donnot have their corresponding exports like .if does, they just look like meta commands (the document called them Command Tokens). To understand how the command line got parsed and executed (like a simplified version of compiler front end), I would recommend debugging the debugger by setting a breakpoint on the Win32 Beep function (kernel32!Beep or KERNELBASE!Beep) and use .beep, below is what I got on my Windows XP box:
0:000> k kernel32!Beep windbg!DirectCommand+0x12a windbg!CmdExecuteCmd+0x90 windbg!WinCommand::OnNotify+0x467 windbg!WinBase::BaseProc+0xc91 USER32!InternalCallWinProc+0x28 USER32!UserCallWinProcCheckWow+0x150 USER32!SendMessageWorker+0x4a5 USER32!SendMessageW+0x7f MSFTEDIT!CW32System::SendMessage+0x3d MSFTEDIT!CTxtWinHost::TxNotify+0x97 MSFTEDIT!RichEditWndProc+0x19b USER32!InternalCallWinProc+0x28 USER32!UserCallWinProcCheckWow+0x150 USER32!DispatchMessageWorker+0x306 USER32!DispatchMessageW+0xf windbg!ProcessNonDlgMessage+0x2a2 windbg!ProcessPendingMessages+0x64 windbg!wmain+0x24a windbg!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep ntsd!UiCommand+0x287 ntsd!MainLoop+0x48f ntsd!main+0x232 ntsd!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep cdb!UiCommand+0x287 cdb!MainLoop+0x48f cdb!main+0x232 cdb!_initterm_e+0x163 kernel32!BaseProcessStart+0x23 0:000> k kernel32!Beep kd!UiCommand+0x287 kd!MainLoop+0x48f kd!main+0x1ed kd!_initterm_e+0x163 kernel32!BaseProcessStart+0x23
In order to answer why Visual Studio lacks support for child process debugging, you need to know how the user mode native debugger works, especially how to write a debug event loop. I have an example in Data Breakpoints, and I've purposely left a note in the "A few things to mention" section #4. You can use these hints and debug into the Visual Studio Debugger.
Regarding the symbol for remote debugging, you will need some background of metadata (ECMA-335 Partition 2) and PDB, also a bit of ICorDebug and ICLRData (mscordacwks!CLRDataCreateInstance). Launch procmon.exe and see who is consuming the symbol files.
What if a WinDBG command fails to do the job, or is not working as expected? There is no magic, just grab a debugger and debug it, in fact the debugger team has presented you a gift called .dbgdbg to make things a little easier!
0:000> uf dbgeng!DotReadMem dbgeng!DotReadMem: 0213fc90 8bff mov edi,edi 0213fc92 55 push ebp 0213fc93 8bec mov ebp,esp 0213fc95 81ec64080000 sub esp,864h 0213fc9b a10c513302 mov eax,dword ptr [dbgeng!__security_cookie (0233510c)] 0213fca0 33c5 xor eax,ebp 0213fca2 8945fc mov dword ptr [ebp-4],eax 0213fca5 833d4450360200 cmp dword ptr [dbgeng!g_Process (02365044)],0 0213fcac 750c jne dbgeng!DotReadMem+0x2a (0213fcba) dbgeng!DotReadMem+0x1e: 0213fcae 6a00 push 0 0213fcb0 6818100000 push 1018h 0213fcb5 e8b6ba1400 call dbgeng!ReportError (0228b770) dbgeng!DotReadMem+0x2a: 0213fcba a1004d3302 mov eax,dword ptr [dbgeng!g_SymOptions (02334d00)] 0213fcbf 2500000400 and eax,40000h 0213fcc4 740c je dbgeng!DotReadMem+0x42 (0213fcd2) dbgeng!DotReadMem+0x36: 0213fcc6 6a00 push 0 0213fcc8 682a100000 push 102Ah 0213fccd e89eba1400 call dbgeng!ReportError (0228b770) dbgeng!DotReadMem+0x42: 0213fcd2 c785c0f7ffff00000000 mov dword ptr [ebp-840h],0 0213fcdc 8d8dbcf7ffff lea ecx,[ebp-844h] 0213fce2 51 push ecx 0213fce3 6a0d push 0Dh 0213fce5 e876c41400 call dbgeng!StringValue (0228c160) 0213fcea 8985c4f7ffff mov dword ptr [ebp-83Ch],eax 0213fcf0 8b154c1a3502 mov edx,dword ptr [dbgeng!g_CurCmd (02351a4c)] 0213fcf6 8995e4f7ffff mov dword ptr [ebp-81Ch],edx 0213fcfc a14c1a3502 mov eax,dword ptr [dbgeng!g_CurCmd (02351a4c)] 0213fd01 668b8dbcf7ffff mov cx,word ptr [ebp-844h] 0213fd08 668908 mov word ptr [eax],cx 0213fd0b c785e8f7ffff00000100 mov dword ptr [ebp-818h],10000h 0213fd15 c785ecf7ffff00000000 mov dword ptr [ebp-814h],0 0213fd1f 6800001000 push 100000h 0213fd24 6a03 push 3 0213fd26 6a01 push 1 0213fd28 8d95e8f7ffff lea edx,[ebp-818h] 0213fd2e 52 push edx 0213fd2f 8d85c8f7ffff lea eax,[ebp-838h] 0213fd35 50 push eax 0213fd36 e8053b0400 call dbgeng!GetRange (02183840) 0213fd3b 33c9 xor ecx,ecx 0213fd3d 8b95e4f7ffff mov edx,dword ptr [ebp-81Ch] 0213fd43 66890a mov word ptr [edx],cx 0213fd46 6a00 push 0 0213fd48 6880000000 push 80h 0213fd4d 6a03 push 3 0213fd4f 6a00 push 0 0213fd51 6a00 push 0 0213fd53 6800000080 push 80000000h 0213fd58 8b85c4f7ffff mov eax,dword ptr [ebp-83Ch] 0213fd5e 50 push eax 0213fd5f ff158c743302 call dword ptr [dbgeng!kernel32_CreateFileW_Ptr (0233748c)] 0213fd65 8985f4f7ffff mov dword ptr [ebp-80Ch],eax 0213fd6b 83bdf4f7ffffff cmp dword ptr [ebp-80Ch],0FFFFFFFFh 0213fd72 7512 jne dbgeng!DotReadMem+0xf6 (0213fd86)
As you can see, the .readmem command makes use of kernel32!CreateFileW with dwDesiredAccess = GENERIC_READ and dwShareMode = 0 (instead of FILE_SHARE_READ), that would explain why you got an error "Unable to open file" in some cases. A quick debugging showed CreateFileW failed and last error value is ERROR_SHARING_VIOLATION.
The documented syntax for .readmem is .readmem FileName Range, while there is no notes for how big the Range value can go, I'll leave this as a homework for you, cheers.
IFEO (Image File Execution Options) is a feature provided by the NT based operating system. It can be helpful when you are trying to debug at the very beginning of an application launch. A few people also taked about IFEO on MSDN Blogs:
Debugger (REG_SZ)
Let's begin with the following demo (I'm using 32bit Win7, you might see different things if you are using other version of Windows):
#define WIN32_LEAN_AND_MEAN #include <Windows.h> #define IfFalseRet(c) do{if(!(c)){return dwLastError = GetLastError();}}while(0) STARTUPINFO g_startupInfo = {sizeof(STARTUPINFO)}; TCHAR g_szCommandLine[] = TEXT("notepad.exe"); int WINAPI ExeEntry(void) { DWORD dwLastError = ERROR_SUCCESS; PROCESS_INFORMATION processInformation; IfFalseRet(CreateProcess(NULL, g_szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &g_startupInfo, &processInformation)); CloseHandle(processInformation.hThread); CloseHandle(processInformation.hProcess); return dwLastError; }
To compile the source code, you may use Visual Studio Command Prompt (x86):
cl.exe createprocess.c /D UNICODE /GS- /Od /Oy /link /ENTRY:ExeEntry /NODEFAULTLIB /OPT:ICF /OPT:REF /RELEASE /SUBSYSTEM:CONSOLE kernel32.lib
Now with the Process Monitor opened, launch the demo, and you should be able to find the following record captured by Process Monitor:
00:01:17.8231352 createprocess.exe 1320 RegOpenKey HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe NAME NOT FOUND Desired Access: Query Value, Enumerate Sub Keys
Let's create an IFEO entry in the registry:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe] "Debugger"="\"C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe\""
After the IFEO entry got imported to the registry, launch the demo again and you will see notepad.exe was replaced by wordpad.exe, with the string "notepad.exe" passed in as an argument.
00:24:32.3322232 createprocess.exe 4668 RegQueryValue HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\New Key #1\Debugger SUCCESS Type: REG_SZ, Length: 108, Data: "C:\Program Files\Windows NT\Accessories\wordpad.exe"
00:24:32.3343642 createprocess.exe 4668 Process Create C:\Program Files\Windows NT\Accessories\wordpad.exe SUCCESS PID: 2724, Command line: "C:\Program Files\Windows NT\Accessories\wordpad.exe" notepad.exeLet's launch the demo application from WinDBG:
windbg.exe createprocess.exe
0:000> x *!*Create*Process*767007a2 kernel32!CreateProcessInternalW766b2082 kernel32!CreateProcessA766b204d kernel32!CreateProcessW7670c89c kernel32!CreateProcessInternalA77d756a8 ntdll!NtCreateProcessEx77d75698 ntdll!NtCreateProcess77d889fb ntdll!RtlpCreateProcessRegistryInfo77d75698 ntdll!ZwCreateProcess77d756a8 ntdll!ZwCreateProcessEx77d56bdf ntdll!RtlpCreateUserProcess77d56b71 ntdll!RtlCreateUserProcess77dd1072 ntdll!RtlCreateProcessReflection77d75778 ntdll!ZwCreateUserProcess77dd988a ntdll!RtlCreateProcessParameters77d75778 ntdll!NtCreateUserProcess77d96ee9 ntdll!RtlCreateProcessParametersEx
0:000> bp kernel32!CreateProcessInternalW
0:000> gBreakpoint 0 hit
0:000> k3ChildEBP RetAddr0018f7a8 7670c9bf kernel32!CreateProcessInternalW0018f888 766b20ae kernel32!CreateProcessInternalA+0x2f80018f8c0 00d71068 kernel32!CreateProcessA+0x2c
0:000> ddu esp L60018f7ac 7670c9bf "???..?"0018f7b0 000000000018f7b4 000000000018f7b8 00383a08 "notepad.exe"0018f7bc 000000000018f7c0 00000000
Continue tracing, eventually we will reach this point:
ChildEBP RetAddr 001af248 77d75784 ntdll!KiFastSystemCall001af24c 76700eff ntdll!NtCreateUserProcess+0xc001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe75001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8001af9c0 01371068 kernel32!CreateProcessA+0x2c
0:000> bp 77d757840:000> gBreakpoint 1 hit
From Process Monitor we can trace the IFEO registry key access from kernel mode. On my Win7 machine, it showed three operations (RegOpenKey, RegQueryValue, RegQueryValue), this is because IFEO was moved into kernel mode since Longhorn. On Windows XP and Windows Server 2003, IFEO is a pure user mode action, which means you can easily bypass it without having to write a kernel mode driver. Another thing is, kernel mode IFEO doesn't have the bitness problem, as it would never read from Wow6432Node.
Continue tracing until we have the following call stacks:
ChildEBP RetAddr0020efe4 77d9ce6f ntdll!RtlQueryImageFileExecutionOptions0020f008 76704ab1 ntdll!LdrQueryImageFileExecutionOptions+0x1e0020f034 76702abb kernel32!BasepGetDisableLocalOverrideConfig+0x330020f0ac 7670119b kernel32!BasepConstructSxsCreateProcessMessage+0x8e0020f728 7670c9bf kernel32!CreateProcessInternalW+0x16b10020f808 766b20ae kernel32!CreateProcessInternalA+0x2f80020f840 01371068 kernel32!CreateProcessA+0x2c
ChildEBP RetAddr001af264 7672d529 kernel32!BuildSubSysCommandLine001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xf87001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8001af9c0 01371068 kernel32!CreateProcessA+0x2c
0:000> ddu esp L6001af268 7672d529 "??????"001af26c 00000003001af270 00234348 ""C:\Program Files\Windows NT\Accessories\wordpad.exe""001af274 00000000001af278 00233a08 "notepad.exe"001af27c 001af4d8 ".."
Continue tracing:
ChildEBP RetAddr 001af1ac 76701693 ntdll!RtlCreateProcessParametersEx001af258 76700e90 kernel32!BasepCreateProcessParameters+0x148001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe06001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8001af9c0 01371068 kernel32!CreateProcessA+0x2c
0:000> dS poi(esp+8)00283cf8 "C:\Program Files\Windows NT\Acce"00283d38 "ssories\wordpad.exe"
ChildEBP RetAddr 001af24c 76700eff ntdll!NtCreateUserProcess001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe75001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8001af9c0 01371068 kernel32!CreateProcessA+0x2c
Now the user mode IFEO logic looks clear to us, let's restart WinDBG and do some tweaking:
0:000> .restart0:000> bp kernel32!BuildSubSysCommandLine; g; bcBreakpoint 0 hit
0:000> ezu poi(esp+8) "calc.exe"
0:000> g
As you could see, Calculator is launched instead of WordPad.
VerifierDlls (REG_SZ)
VerifierDlls is a list of verifier provider DLLs used by the AppVerifier. The NT loader consumes VerifierDlls during user mode process initialization, which happens in ntdll!LdrpInitializeProcess. If VerifierDlls was found, the standard verifier provider verifier.dll, which comes with the Windows system, will be loaded first. After that, each verifier provider specified by VerifierDlls will be loaded one by one:
ChildEBP RetAddr 0020f37c 7719578b ntdll!AVrfpLoadAndInitializeProvider+0x1170020f3a0 77170bd9 ntdll!AVrfInitializeVerifier+0xd30020f518 77156077 ntdll!LdrpInitializeProcess+0xe950020f568 77153663 ntdll!_LdrpInitialize+0x780020f578 00000000 ntdll!LdrInitializeThunk+0x10
ntdll!AVrfpLoadAndInitializeProvider:771950e1 push 1Ch771950e3 push offset ntdll! ?? ::FNODOBFM::`string'+0x762 (77140ed8)771950e8 call ntdll!_SEH_prolog4 (77142c0c)771950ed mov byte ptr [ebp-19h],0771950f1 mov esi,dword ptr [ebp+8]771950f4 test byte ptr [ntdll!AVrfpDebug (771ce380)],1771950fb je ntdll!AVrfpLoadAndInitializeProvider+0x2b (7719510c)771950fd push dword ptr [esi+0Ch]77195100 push offset ntdll! ?? ::FNODOBFM::`string' (7713a708)77195105 call ntdll!DbgPrint (7710f593)7719510a pop ecx7719510b pop ecx7719510c mov dword ptr [ebp-28h],offset ntdll!RtlpCsVerifyDoNotBreak+0x7 (771ce178)77195113 xor eax,eax77195115 mov word ptr [ebp-2Ch],ax77195119 mov eax,208h7719511e mov word ptr [ebp-2Ah],ax77195122 push offset SharedUserData+0x30 (7ffe0030)77195127 lea eax,[ebp-2Ch]7719512a push eax7719512b call ntdll!RtlAppendUnicodeToString (7714eed2)77195130 push offset ntdll!SlashSystem32SlashString (771551f0)77195135 lea eax,[ebp-2Ch]77195138 push eax77195139 call ntdll!RtlAppendUnicodeStringToString (771472be)7719513e lea ebx,[esi+10h]77195141 push ebx77195142 lea eax,[esi+8]77195145 push eax77195146 push 077195148 push dword ptr [ebp-28h]7719514b call ntdll!LdrLoadDll (771522b8)77195150 test eax,eax77195152 jge ntdll!AVrfpLoadAndInitializeProvider+0x98 (77195179)77195154 push dword ptr [ebp-28h]77195157 push eax77195158 push dword ptr [esi+0Ch]7719515b mov eax,dword ptr [ntdll!PebLdr+0xc (771c788c)]77195160 ff7030 push dword ptr [eax+30h]77195163 push offset ntdll! ?? ::FNODOBFM::`string' (7713a6c6)77195168 call ntdll!DbgPrint (7710f593)7719516d add esp,14h77195170 mov byte ptr [ebp-19h],177195174 jmp ntdll!AVrfpLoadAndInitializeProvider+0x21c (771952fd)77195179 and dword ptr [ebp-4],07719517d push dword ptr [ebx]7719517f call ntdll!RtlImageNtHeader (7714fa29)77195184 mov edi,eax
The AppVerifier package comes with a GUI frontend, together with a stack of verifier providers. A verifier provider is a special DLL which accepts DLL_PROCESS_VERIFIER. The WinNT.h header file in DDK 3790.1830 contains everything needed for implementing a verifier provider.
#define WIN32_LEAN_AND_MEAN #include <Windows.h> static RTL_VERIFIER_DLL_DESCRIPTOR aDlls[] = {{}}; static RTL_VERIFIER_PROVIDER_DESCRIPTOR vpd = {sizeof(RTL_VERIFIER_PROVIDER_DESCRIPTOR), aDlls}; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, PRTL_VERIFIER_PROVIDER_DESCRIPTOR* pVPD) { UNREFERENCED_PARAMETER(hinstDLL); if(fdwReason == DLL_PROCESS_VERIFIER) *pVPD = &vpd; return TRUE; }
You can read A Debugging Approach to Application Verifier for more information.
DllNXOptions
DisableExceptionChainValidation (REG_DWORD)
SEHOP (Structured Exception Handling Overwrite Protection) is a feature introduced by Windows Vista SP1 to protect a certain kind of attack. However this feature would cause compatibility issues to some existing applications, so the purpose of this value is to workaround for legacy applications by disabling SEHOP feature.
(to be continued...)
Memory segmentation was first introduced to x86 family with 8086, to make it possible to access 1MB physical memory under 16bit addressing mode.
Real Mode
Logical address points directly into physical memory location.
Logical address consists of two parts: segment and offset.
Physical address is calculated as Segment * 16 + Offset, and if the A20 line is not enabled, the physical address is wrapped around into 1MiB range.
Protected Mode
Two stages of address translation would be used to arrive at a physical address: logical-address translation and linear address space paging.
Logical address consists of two parts: segment selector and offset.
Segment selector is used to determine the base address as well as the access rights. Segment selelctors are maintained in a table known as GDT (Global descriptor-table) or LDT (Local descriptor-table), which is referenced by GDTR or LDTR register.
When a thread is running in kernel mode, fs:[0] points to the PCR (Processor Control Region). Let's find it by manually translating the logical address:
kd> r fs fs=00000030 kd> .formats @fs Evaluate expression: Hex: 00000030 Decimal: 48 Octal: 00000000060 Binary: 00000000 00000000 00000000 00110000 Chars: ...0 Time: Thu Jan 01 08:00:48 1970 Float: low 6.72623e-044 high 0 Double: 2.37152e-322
According to the x86 architecture specification, we have Index = 6 (0000000000110), Table Indicator = GDT (0) and RPL (Requested Privilege Level) = 0.
kd> dd @gdtr 8003f000 00000000 00000000 0000ffff 00cf9b00 8003f010 0000ffff 00cf9300 0000ffff 00cffb00 8003f020 0000ffff 00cff300 200020ab 80008b04 8003f030 f0000001 ffc093df 00000fff 0040f300 8003f040 0400ffff 0000f200 00000000 00000000 8003f050 a0000068 80008954 a0680068 80008954 8003f060 2f40ffff 00009302 80003fff 0000920b 8003f070 700003ff ff0092ff 0000ffff 80009a40
We can get the layout of segment descriptor from x86 architecture specification:
Now we can verify the address translation by looking at the memory contents:
kd> dg @fs P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- -------- -------- ---------- - -- -- -- -- -------- 0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P Nl 00000c93
kd> dd fs:[0] 0030:00000000 805495d0 80549df0 80547000 00000000 0030:00000010 00000000 00000000 00000000 ffdff000 0030:00000020 ffdff120 0000001c 00000000 00000000 0030:00000030 ffff2050 80545bb8 8003f400 8003f000 0030:00000040 80042000 00010001 00000001 00000064 0030:00000050 00000000 00000000 00000000 00000000 0030:00000060 00000000 00000000 00000000 00000000 0030:00000070 00000000 00000000 00000000 00000000 kd> dd ffdff000 ffdff000 805495d0 80549df0 80547000 00000000 ffdff010 00000000 00000000 00000000 ffdff000 ffdff020 ffdff120 0000001c 00000000 00000000 ffdff030 ffff2050 80545bb8 8003f400 8003f000 ffdff040 80042000 00010001 00000001 00000064 ffdff050 00000000 00000000 00000000 00000000 ffdff060 00000000 00000000 00000000 00000000 ffdff070 00000000 00000000 00000000 00000000
It's time to dump the KPCR structure:
kd> dt nt!_KPCR ffdff000 +0x000 NtTib : _NT_TIB +0x01c SelfPcr : 0xffdff000 _KPCR +0x020 Prcb : 0xffdff120 _KPRCB +0x024 Irql : 0x1c '' +0x028 IRR : 0 +0x02c IrrActive : 0 +0x030 IDR : 0xffff2050 +0x034 KdVersionBlock : 0x80545bb8 Void +0x038 IDT : 0x8003f400 _KIDTENTRY +0x03c GDT : 0x8003f000 _KGDTENTRY +0x040 TSS : 0x80042000 _KTSS +0x044 MajorVersion : 1 +0x046 MinorVersion : 1 +0x048 SetMember : 1 +0x04c StallScaleFactor : 0x64 +0x050 DebugActive : 0 '' +0x051 Number : 0 '' +0x052 Spare0 : 0 '' +0x053 SecondLevelCacheAssociativity : 0 '' +0x054 VdmAlert : 0 +0x058 KernelReserved : [14] 0 +0x090 SecondLevelCacheSize : 0 +0x094 HalReserved : [16] 0 +0x0d4 InterruptMode : 0 +0x0d8 Spare1 : 0 '' +0x0dc KernelReserved2 : [17] 0 +0x120 PrcbData : _KPRCB kd> !pcr KPCR for Processor 0 at ffdff000: Major 1 Minor 1 NtTib.ExceptionList: 805495d0 NtTib.StackBase: 80549df0 NtTib.StackLimit: 80547000 NtTib.SubSystemTib: 00000000 NtTib.Version: 00000000 NtTib.UserPointer: 00000000 NtTib.SelfTib: 00000000 SelfPcr: ffdff000 Prcb: ffdff120 Irql: 0000001c IRR: 00000000 IDR: ffff2050 InterruptMode: 00000000 IDT: 8003f400 GDT: 8003f000 TSS: 80042000 CurrentThread: 80552840 NextThread: 00000000 IdleThread: 80552840 DpcQueue:
The Visual Studio debugger supports a kind of breakpoint called Data Breakpoint, sometimes it is also called watchpoint. Data breakpoint is architecture dependant, as it requires hardware support provided by CPU. For x86, this will be the DR (Debug Register).
The following code demonstrates how to use the x86 debug register by implementing a very simple native debugger.
#define WIN32_LEAN_AND_MEAN #include <Windows.h> #include <stdio.h> HMODULE g_hModule; /* linear address of exe */ LPVOID g_pOEP; /* original entry point */ BYTE g_bBreakPoint; BYTE g_bINT3 = 0xcc; /* debug break instruction */ BOOL g_fDebugRegisterSupported = FALSE; /* hardware DR supported */ int WINAPI ExeEntry() { if(IsDebuggerPresent()) { g_hModule = GetModuleHandle(NULL); /* HMODULE is always 4 bytes aligned */ printf("HMODULE: %p\n", g_hModule /* insert a space here... */, *(INT32*)(g_hModule) /* read 4 bytes */); } else { CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; PROCESS_INFORMATION processInformation; STARTUPINFO startupInfo = {sizeof(STARTUPINFO)}; if(CreateProcess(NULL, GetCommandLine(), NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &startupInfo, &processInformation)) { DWORD dwContinueDebugStatus = DBG_CONTINUE; while(dwContinueDebugStatus) { DEBUG_EVENT debugEvent; WaitForDebugEvent(&debugEvent, INFINITE); switch(debugEvent.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: g_pOEP = (LPVOID)(debugEvent.u.CreateProcessInfo.lpStartAddress); g_hModule = (HMODULE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage); CloseHandle(debugEvent.u.CreateProcessInfo.hFile); printf("CREATE_PROCESS_DEBUG_EVENT @%p OEP=%p\n", g_hModule, g_pOEP); break; case EXCEPTION_DEBUG_EVENT: printf("EXCEPTION_DEBUG_EVENT PID=%d TID=%d @%p\n", debugEvent.dwProcessId, debugEvent.dwThreadId, debugEvent.u.Exception.ExceptionRecord.ExceptionAddress); GetThreadContext(processInformation.hThread, &context); switch(debugEvent.u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: if(debugEvent.u.Exception.dwFirstChance) { if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP) { LPVOID IP = (LPVOID)(--context.Eip); WriteProcessMemory(processInformation.hProcess, IP, &g_bBreakPoint, 1, NULL); FlushInstructionCache(processInformation.hProcess, IP, 1); context.Dr0 = (DWORD)(g_hModule); context.Dr7 = 0x000f0101; } else { printf("\tbp $exentry\n"); ReadProcessMemory(processInformation.hProcess, g_pOEP, &g_bBreakPoint, 1, NULL); WriteProcessMemory(processInformation.hProcess, g_pOEP, &g_bINT3, 1, NULL); FlushInstructionCache(processInformation.hProcess, g_pOEP, 1); } } break; case EXCEPTION_SINGLE_STEP: printf("EXCEPTION_SINGLE_STEP DR6=%08X\n", context.Dr6); g_fDebugRegisterSupported = TRUE; break; } context.Dr6 = 0; SetThreadContext(processInformation.hThread, &context); break; case EXIT_PROCESS_DEBUG_EVENT: dwContinueDebugStatus = 0; printf("EXIT_PROCESS_DEBUG_EVENT\n"); break; case LOAD_DLL_DEBUG_EVENT: CloseHandle(debugEvent.u.LoadDll.hFile); break; } ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, dwContinueDebugStatus); } CloseHandle(processInformation.hThread); CloseHandle(processInformation.hProcess); } printf("Debug Register Test: %s\n", g_fDebugRegisterSupported ? "Hardware DR supported" : "Hardware DR not supported"); } return ERROR_SUCCESS; }
To compile the source code, you may use Visual Studio or either of the following compilers (x86 32bit):
cl.exe watchpoint.cpp kernel32.lib msvcrt.lib /GS- /link /ENTRY:ExeEntry /NODEFAULTLIB /SUBSYSTEM:CONSOLE
gcc.exe -fno-exceptions -fno-rtti -s -Os -o watchpoint watchpoint.cpp -Wl,--stack,65536
if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP) { #if defined(_M_IX86) LPVOID IP = (LPVOID)(--context.Eip); #elif defined(_M_X64) LPVOID IP = (LPVOID)(--context.Rip); #endif WriteProcessMemory(processInformation.hProcess, IP, &g_bBreakPoint, 1, NULL); FlushInstructionCache(processInformation.hProcess, IP, 1); #if defined(_M_IX86) context.Dr0 = (DWORD)(g_hModule); #elif defined(_M_X64) context.Dr0 = (DWORD64)(g_hModule); #endif context.Dr7 = 0x000f0101; }
printf("HMODULE: %p\n", g_hModule /* insert a space here... */, *(INT32*)(g_hModule) /* read 4 bytes */);
printf("HMODULE: %p\n", g_hModule /* insert a space here... * /, *(INT32*)(g_hModule) /* read 4 bytes */);
CREATE_PROCESS_DEBUG_EVENT @00250000 OEP=00251978EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @77DD04F6 bp $exentry EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @00251978EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @002518CAEXCEPTION_SINGLE_STEP DR6=FFFF0FF1EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @0025104FEXCEPTION_SINGLE_STEP DR6=FFFF0FF1 HMODULE: 00250000EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @67D278FCEXCEPTION_SINGLE_STEP DR6=FFFF0FF1EXIT_PROCESS_DEBUG_EVENTDebug Register Test: Hardware DR supported
For the homework, you would want to either check the Intel x86 specification and do verification by playing around the code.
Macro is powerful, but few people understand how it works. In theory, syntax highlighting for C/C++ is impossible due to the presence of Preprocessing Directives FDIS N3290 16 [cpp]. Sometimes I do feel that C++ is a mixture of three languages instead of a single language, I have to keep in mind that there are several Phases of Translation FDIS N3290 2.2 [lex.phases] when I was coding.
NULL
It turns out that most people who have been using the Win32 API and C Runtime Library for years don't know NULL is complicated than it looks. It is defined by both Windows headers and C Runtime headers, and guarded by macro. The reason behind this is to make most people happy (e.g. C++ standard requires NULL to be 0, while Standard C does not).
/* WinDef.h */ #ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
WIN32_LEAN_AND_MEAN
You probably have noticed that I've used this macro extensively in my blogs, here goes the official voice:
WIN32_LEAN_AND_MEAN excludes APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
So, if you don't need these APIs, WIN32_LEAN_AND_MEAN would make the life of compiler easier, plus, Precompiled Header, Intellisense and other code analysis tools would also benifit from it.
UNICODE and _UNICODE
UNICODE is used by Windows header files to support generic Conventions for Function Prototypes and Generic Data Types.
_UNICODE is used by the C Runtime (CRT) header files to support Generic-Text Mappings.
The following interesting snippet is distilled from ATL headers:
/* atldef.h */ #ifdef _UNICODE #ifndef UNICODE #define UNICODE // UNICODE is used by Windows headers #endif #endif #ifdef UNICODE #ifndef _UNICODE #define _UNICODE // _UNICODE is used by C-runtime/MFC headers #endif #endif
TEXT __TEXT and _T _TEXT __T
The following snippet is distilled from WinNT.h, which can be found from DDK/WDK and SDK/PSDK:
/* WinNT.h */ #ifdef UNICODE #define __TEXT(quote) L##quote #else #define __TEXT(quote) quote #endif #define TEXT(quote) __TEXT(quote)
So the following code is correct:
_tprintf(TEXT("%s") TEXT("\n"), TEXT(__FILE__));
But this is wrong:
_tprintf(TEXT("%s" "\n"), __TEXT(__FILE__));
And if UNICODE is defined, it turns out that you can (evilly) use:
class LOST { }; TEXT(OST) lost;
The following snippet was distilled from tchar.h, which is a part of CRT:
/* tchar.h */ #ifdef _UNICODE #define __T(x) x #else #define __T(x) L ## x #endif #define _T(x) __T(x) #define _TEXT(x) __T(x)
Conclusion:
DEBUG _DEBUG and NDEBUG
NDEBUG is a part of the C Language Standard, which controls the behavior of assert:
/* assert.h */ #ifdef NDEBUG #define assert(_Expression) ((void)0) #else ...
_DEBUG is defined by the Microsoft C++ Compiler when you compile with /LDd, /MDd and /MTd. The runtime libraries such like ATL, CRT and MFC make use of this macro.
DEBUG is defined in ATL:
/* atldef.h */ #ifdef _DEBUG #ifndef DEBUG #define DEBUG #endif #endif
NTDDI_VERSION WINVER _WIN32_WINNT _WIN32_WINDOWS _WIN32_IE VER_PRODUCTVERSION_W
WINVER has been existing since 16bit Windows, and is still in using. Note that Windows NT 4.0 and Windows 95 both have WINVER defined as 0x0400.
_WIN32_WINDOWS is used by Windows 95/98/Me.
_WIN32_WINNT is used by the whole NT family.
NTDDI_VERSION was introduced by Windows 2000, as Win9x and NT evolved into a single operating system. Plus, NTDDI_VERSION contains more information and is able to distinguish service packs. The latest sdkddkver.h has all the information you would want to know.
_WIN32_IE was introduced because Internet Explorer shares many components with the shell (a.k.a. Windows Explorer), installing a new version of Internet Explorer would eventually replace a number of system components and even change the APIs.
VER_PRODUCTVERSION_W can be found in ntverp.h, which is used by the NT team to maintain the product build.
_X86_ _AMD64_ _IA64_ and _M_AMD64 _M_IX86 _M_IA64 _M_X64
_M_AMD64, _M_IX86, _M_IA64 and _M_X64 are defined by the Microsoft C++ Compiler according to the target processor architecture. _M_AMD64 and _M_X64 are equivalent.
_X86_, _AMD64_ and _IA64_ are defined by Windows.h (there is no _X64_ at all, because AMD invented x86-64).
/* Windows.h */ #if !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86) #define _X86_ #endif #if !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64) #define _AMD64_ #endif
_WIN32 _WIN64 WIN32 _WINDOWS
If bitness matters, but we don't care about architecture, we can use _WIN32 and _WIN64 provided by the Microsoft C++ Compiler. This is useful while defining data types and function prototypes. Note that _WIN32 and _WIN64 are not mutual exclusive, as _WIN32 is always defined (unless you are using DDK and writing 16bit code).
WIN32 is defined by Windows header file WinDef.h, and is not widely used in Windows header files (TAPI being a negative example).
_WINDOWS is a legacy thing in the 16bit era, you should hardly see it in 21st century.
/* WinDef.h */ // Win32 defines _WIN32 automatically, // but Macintosh doesn't, so if we are using // Win32 Functions, we must do it here #ifdef _MAC #ifndef _WIN32 #define _WIN32 #endif #endif //_MAC #ifndef WIN32 #define WIN32 #endif
UNREFERENCED_PARAMETER
This macro is defined in WinNT.h along with DBG_UNREFERENCED_PARAMETER and DBG_UNREFERENCED_LOCAL_VARIABLE.
/* WinNT.h */// // Macros used to eliminate compiler warning generated when formal // parameters or local variables are not declared. // // Use DBG_UNREFERENCED_PARAMETER() when a parameter is not yet // referenced but will be once the module is completely developed. // // Use DBG_UNREFERENCED_LOCAL_VARIABLE() when a local variable is not yet // referenced but will be once the module is completely developed. // // Use UNREFERENCED_PARAMETER() if a parameter will never be referenced. // // DBG_UNREFERENCED_PARAMETER and DBG_UNREFERENCED_LOCAL_VARIABLE will // eventually be made into a null macro to help determine whether there // is unfinished work. //
Many people who has been using Emacs for decades were shocked when they heard that Emacs is actually a text editor instead of an operating system.
- vi advocator
Sharing a similar spirit as Emacs, Windows Debuggers are also super good at non-debugging tasks.
Calculator
The builtin expression evaluator of Windows Debuggers can be used as a handy calculator:
0:000> ?? 1+2+3 int 0n6
0:000> .formats 0x00905a4dEvaluate expression: Hex: 00000000`00905a4d Decimal: 9460301 Octal: 0000000000000044055115 Binary: 00000000 00000000 00000000 00000000 00000000 10010000 01011010 01001101 Chars: ......ZM Time: Mon Apr 20 19:51:41 1970 Float: low 1.32567e-038 high 0 Double: 4.67401e-317
Process Manager
I try not to use the term Task Manager, since the name is already occupied by taskmgr.exe, and we have nothing to do with tasks (taskmgr also has nothing to do with tasks).
0:000> .tlist -v windbg*
Shell
0:000> .shell tlist -t
0:000> .shell dir
0:000> !!dir
Examine Export Table
cdb.exe -y . -z ntdll.dll -c "x ntdll!*; q"
Binary Editor
The basic idea is to load a portion of file data into the address space of a debuggee, perform some inspection or modification, then write back to the file. WinDBG even has a Memory Window, which makes it a perfect GUI Hex Editor!
To examine the file information such like name, permission and size, use the .shell command.
0:000> .shell dir *.netmodule 10/23/2011 12:12 PM 2,048 bar.netmodule 10/23/2011 12:12 PM 2,048 foo.netmodule .shell: Process exited Press ENTER to continue
We can load the file into the debugee's user mode virtual address space, given that the pages are commited.
0:000> .dvalloc 0n2048 Allocated 1000 bytes starting at 00020000
0:000> .readmem foo.netmodule 20000 L0n2048 Reading 800 bytes.
0:000> dt ntdll!_IMAGE_DOS_HEADER 20000 +0x000 e_magic : 0x5a4d +0x002 e_cblp : 0x90 +0x004 e_cp : 3 +0x006 e_crlc : 0 +0x008 e_cparhdr : 4 +0x00a e_minalloc : 0 +0x00c e_maxalloc : 0xffff +0x00e e_ss : 0 +0x010 e_sp : 0xb8 +0x012 e_csum : 0 +0x014 e_ip : 0 +0x016 e_cs : 0 +0x018 e_lfarlc : 0x40 +0x01a e_ovno : 0 +0x01c e_res : [4] 0 +0x024 e_oemid : 0 +0x026 e_oeminfo : 0 +0x028 e_res2 : [10] 0 +0x03c e_lfanew : 0n128
0:000> dt -r ntdll!_IMAGE_NT_HEADERS 20000+0n128 +0x000 Signature : 0x4550 +0x004 FileHeader : _IMAGE_FILE_HEADER +0x000 Machine : 0x14c +0x002 NumberOfSections : 2 +0x004 TimeDateStamp : 0x4ea39447 +0x008 PointerToSymbolTable : 0 +0x00c NumberOfSymbols : 0 +0x010 SizeOfOptionalHeader : 0xe0 +0x012 Characteristics : 0x2102 +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER +0x000 Magic : 0x10b +0x002 MajorLinkerVersion : 0x8 '' +0x003 MinorLinkerVersion : 0 '' +0x004 SizeOfCode : 0x400 +0x008 SizeOfInitializedData : 0x200 +0x00c SizeOfUninitializedData : 0 +0x010 AddressOfEntryPoint : 0x22de +0x014 BaseOfCode : 0x2000 +0x018 BaseOfData : 0x4000 +0x01c ImageBase : 0x400000 +0x020 SectionAlignment : 0x2000 +0x024 FileAlignment : 0x200 +0x028 MajorOperatingSystemVersion : 4 +0x02a MinorOperatingSystemVersion : 0 +0x02c MajorImageVersion : 0 +0x02e MinorImageVersion : 0 +0x030 MajorSubsystemVersion : 4 +0x032 MinorSubsystemVersion : 0 +0x034 Win32VersionValue : 0 +0x038 SizeOfImage : 0x6000 +0x03c SizeOfHeaders : 0x200 +0x040 CheckSum : 0 +0x044 Subsystem : 3 +0x046 DllCharacteristics : 0x8540 +0x048 SizeOfStackReserve : 0x100000 +0x04c SizeOfStackCommit : 0x1000 +0x050 SizeOfHeapReserve : 0x100000 +0x054 SizeOfHeapCommit : 0x1000 +0x058 LoaderFlags : 0 +0x05c NumberOfRvaAndSizes : 0x10 +0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY +0x000 VirtualAddress : 0 +0x004 Size : 0
0:000> !dh 20000
0:000> .writemem foo.netmodule 20000 L0n2048
0:000> .dvfree 20000 0
Be cautious while using .dvfree, the debugger simply backed this by VirtualFreeEx:
kernel32!VirtualFreeEx dbgeng!LiveUserDebugServices::FreeVirtual+0x1e dbgeng!LiveUserTargetInfo::FreeVirtual+0x47 dbgeng!DotDvFree+0xcb dbgeng!DotCommand+0x3f dbgeng!ProcessCommands+0x4e1 dbgeng!ProcessCommandsAndCatch+0x49 dbgeng!Execute+0x2b9 dbgeng!DebugClient::ExecuteWide+0x6a windbg!ProcessCommand+0x156 windbg!ProcessEngineCommands+0xb2 windbg!EngineLoop+0x366 kernel32!BaseThreadStart+0x37
This means .dvfree can be used to free any block of virtual memory owned by the target process.
When .dvfree is used without the /d option, it would use MEM_RELEASE instead of MEM_DECOMMIT (this can be verified with .dbgdbg), and you have to make sure BaseAddress is the value returned by !address and Size is always zero.