Rubato and Chord

Reiley's technical blog

  • Rubato and Chord

    Early Debugging

    • 0 Comments

    Early debugging is a wide topic, on a Windows PC it might be:

    Application Startup

    As we have demonstrated in the user mode debug event loop, when an application was launched from a debugger, the first debug event is CREATE_PROCESS_DEBUG_EVENT. Process creation event is the earliest point a user mode debugger could even reach to.

    Windows debuggers by default would break at ntdll!LdrpDoDebuggerBreak, but we can alter this behavior:

    cdb.exe -xe cpr -xe ld notepad.exe

    CommandLine: notepad.exe
    
    ModLoad: 01000000 01014000   notepad.exe
    
    0:000> lm
    start    end        module name
    01000000 01014000   notepad    (deferred)
    
    0:000> !teb
    TEB at 7ffdf000
    error InitTypeRead( TEB )...
    

    As you can see, debugger extension complains since we are too early. However there are always workarounds as we discussed in Undocumented WinDBG:

    0:000> .imgscan; * Where is Mark Zbikowski?
    MZ at 01000000, prot 00000002, type 01000000 - size 14000
      Name: notepad.exe
    MZ at 7c900000, prot 00000002, type 01000000 - size b2000
      Name: ntdll.dll
    
    0:000> .reload /s /f ntdll.dll=7c900000
    
    0:000> lm
    start    end        module name
    01000000 01014000   notepad    (deferred)
    7c900000 7c9b2000   ntdll      (pdb symbols)
    
    0:000> !teb
    TEB at 7ffdf000
        ExceptionList:        ffffffff
        StackBase:            00080000
        StackLimit:           0006f000
        SubSystemTib:         00000000
        FiberData:            00001e00
        ArbitraryUserPointer: 00000000
        Self:                 7ffdf000
        EnvironmentPointer:   00000000
        ClientId:             000007d4 . 000005b4
        RpcHandle:            00000000
        Tls Storage:          00000000
        PEB Address:          7ffd8000
        LastErrorValue:       0
        LastStatusValue:      0
        Count Owned Locks:    0
        HardErrorMode:        0
    

    If the application is launched by another process, IFEO might help, but always keep in mind there can be side effects.

    Service Startup

    Most of the knowledge about application debugging applies to services, since they are both user mode processes. The only difference is that service can share a single hosting process (e.g. svchost.exe), and would normally start in a different session.

    An excellent article about service debugging can be found at:

    Windows Setup, OS Loader, CSRSS and WinLogon

    The document shipped with Debugging Tools for Windows has some brief introduction.

    MSDN also described these debugging tasks in Specialized Debugging Techniques.

    POST and MBR

    Most of these are real mode code (although MBR might switch CPU to protected mode) dealing with the low level hardware. Not many people are still working on the old memory models (e.g. TINY, SMALL, COMPACT, MEDIUM, LARGE and HUGE) and the A20 line (do you remember Tim Paterson and his debug program?).

    MBR is relatively small, and can be simply debugged using a software emulator.

    I have never worked on POST, but I think people would use software emulators in combination with ICE (In-circuit emulator).

    WinDBG has limited support for real mode debugging.

    (to be continued...)

  • Rubato and Chord

    Undocumented WinDBG

    • 2 Comments

    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:

    1. Are there undocumented commands in the Windows Debuggers?
    2. WinDBG supports child process debugging via .childdbg, does Visual Studio Debugger provide the same thing?
    3. Why do I need to put symbols on the target machine while remote debugging .NET applications in Visual Studio 2010?
    4. Why does the command foobarbaz not working as expected?

    Let's revisit the commands in WinDBG. There are three types of commands available:

    1. Debugger Commands
      These are the internal commands, examples are bp, g and pt. Internal commands are case insensitive in most cases (as and aS are exceptions), you can get a brief help of these commands by typing a single question mark (?).
    2. Meta Commands (a.k.a. Dot Commands)
      Meta commands always start with a dot, examples are .childdbg, .dvalloc and .dvfree. Meta commands are case insensitive, you can get a brief help of these commands using the meta command .help.
    3. Extension Commands
      Extension commands are provided by separate debugger extension modules, example are !dh and !sym. Debugging Tools for Windows has provided the SDK for implementing such extension DLLs, and it's fully documented (Writing New Debugger Extensions). Extension commands are case sensitive, some extensions (e.g. Son of Strike) has exported a same function under different names (e.g. SOS!CLRStack, SOS!ClrStack and SOS!clrstack).

    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.

  • Rubato and Chord

    Use Windows Debuggers for Non-Debugging Tasks

    • 2 Comments

    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 0x00905a4d
    Evaluate 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.

  • Rubato and Chord

    Yet Another Hello World

    • 0 Comments

    Recently I heard there is a COOL programming language called C#, which runs on a popular environment called .NET platform (formally known as COMPLUS), so I decided to give it a try.

    It took me some time to understand why I need to define a class and a static method in order to say hello to the world, but finally I managed to come up with the following piece of code:

    /* hello.cs */
    class HelloWorld
    {
      static void Main()
      {
        System.Console.WriteLine("Hello, world!");
      }
    } 

    It's quite straightforward then, I launched csc.exe /debug+ hello.cs from command prompt, after a little while a file named hello.exe was generated and everything just worked fine.

    Then I launched hello.exe from WinDBG: 

    CommandLine: hello.exe
    ModLoad: hello.exe
    ModLoad: ntdll.dll
    ModLoad: C:\Windows\SYSTEM32\MSCOREE.DLL 
    ModLoad: C:\Windows\system32\KERNEL32.dll
    ModLoad: C:\Windows\system32\KERNELBASE.dll
    (780.b4): Break instruction exception - code 80000003 (first chance)
    
    0:000> bp $exentry 
    0:000> g
    Breakpoint 0 hit
    MSCOREE!ShellShim__CorExeMain:
    70917cef 8bff            mov     edi,edi
    

    As we can see, the entry point of our EXE falls into MSCOREE.DLL. By taking a look at the EXE image we can see the OEP actually points to ff250020cb00, which translates to jmp dword ptr [hello+0x2000], it turned out that MSCOREE patched the EXE's entry point during DLL loading. A slightly different approach is used for 64bit process, the EXE is mapped as PAGE_READONLY instead of PAGE_EXECUTE_READ, and there is no OEP patching. The side effect is that you cannot use bp $exentry while debugging a 64bit managed application.

    0:000> wt -l 3 -oR 
    Tracing MSCOREE!ShellShim__CorExeMain to return address 70914de3
        8     0 [  0] MSCOREE!ShellShim__CorExeMain
        4     0 [  1]   MSCOREE!GetShimImpl
       10     0 [  2]     MSCOREE!InitShimImpl
       17     0 [  3]       MSCOREE!_EH_prolog3 eax = 1ff5c4
       19    17 [  2]     MSCOREE!InitShimImpl
       14     0 [  3]       MSCOREE!BaseWrapper<unsigned short *,FunctionBase<unsigned short *,&DoNothing<unsigned short *>,&Delete<unsigned short>,2>,0,&CompareDefault<unsigned short *>,2>::operator& eax = 1ff5a8
       24    31 [  2]     MSCOREE!InitShimImpl
    ModLoad: C:\Windows\system32\ADVAPI32.dll
    ModLoad: C:\Windows\system32\msvcrt.dll
    ModLoad: C:\Windows\SYSTEM32\sechost.dll
    ModLoad: C:\Windows\system32\RPCRT4.dll
       35     0 [  3]       MSCOREE!FindLatestVersion eax = 0
       28    66 [  2]     MSCOREE!InitShimImpl
       11     0 [  3]       MSCOREE!BaseWrapper<unsigned short *,FunctionBase<unsigned short *,&DoNothing<unsigned short *>,&Delete<unsigned short>,2>,0,&CompareDefault<unsigned short *>,2>::TypedAddressInitHolder::~TypedAddressInitHolder eax = 1ff5b8
       37    77 [  2]     MSCOREE!InitShimImpl
      622     0 [  3]       MSCOREE!GetShimDllPathName eax = 0
       42   699 [  2]     MSCOREE!InitShimImpl
       23     0 [  3]       MSCOREE!FileExists eax = 1
       50   722 [  2]     MSCOREE!InitShimImpl
    ModLoad: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll 
       32     0 [  3]       MSCOREE!LoadLibraryShim eax = 0
       59   754 [  2]     MSCOREE!InitShimImpl
        5     0 [  3]       KERNEL32!GetProcAddressStub
        1     0 [  3]       KERNEL32!GetProcAddress
       37     0 [  3]       KERNELBASE!GetProcAddress eax = 70346975
       63   797 [  2]     MSCOREE!InitShimImpl
    
        5     0 [  3]       KERNEL32!GetProcAddressStub
        1     0 [  3]       KERNEL32!GetProcAddress
       35     0 [  3]       KERNELBASE!GetProcAddress eax = 0
       68   838 [  2]     MSCOREE!InitShimImpl
        5     0 [  3]       KERNEL32!GetProcAddressStub
        1     0 [  3]       KERNEL32!GetProcAddress
       35     0 [  3]       KERNELBASE!GetProcAddress eax = 0
       74   879 [  2]     MSCOREE!InitShimImpl
        5     0 [  3]       KERNEL32!GetProcAddressStub
        1     0 [  3]       KERNEL32!GetProcAddress
       37     0 [  3]       KERNELBASE!GetProcAddress eax = 703443ef
       80   922 [  2]     MSCOREE!InitShimImpl
       21     0 [  3]       ntdll!RtlEnterCriticalSection eax = 0
       91   943 [  2]     MSCOREE!InitShimImpl
       16     0 [  3]       mscoreei!RegisterShimImplCallback eax = 0
       97   959 [  2]     MSCOREE!InitShimImpl
      613     0 [  3]       MSCOREE!wcscpy_s eax = 0
      108  1572 [  2]     MSCOREE!InitShimImpl
       23     0 [  3]       ntdll!RtlLeaveCriticalSection eax = 0
      114  1595 [  2]     MSCOREE!InitShimImpl
        8     0 [  3]       MSCOREE!NewArrayHolder<unsigned char,Wrapper<unsigned char *,&DoNothing<unsigned char *>,&DeleteArray<unsigned char>,0,&CompareDefault<unsigned char *>,2> >::~NewArrayHolder<unsigned char,Wrapper<unsigned char *,&DoNothing<unsigned char *>,&DeleteArray<un eax = 7f
      123  1603 [  2]     MSCOREE!InitShimImpl
        3     0 [  3]       MSCOREE!__security_check_cookie eax = 7f
      127  1606 [  2]     MSCOREE!InitShimImpl eax = 7f
       15  1733 [  1]   MSCOREE!GetShimImpl eax = 2
       23  1748 [  0] MSCOREE!ShellShim__CorExeMain
        5     0 [  1]   KERNEL32!GetProcAddressStub
        1     0 [  1]   KERNEL32!GetProcAddress
       15     0 [  1]   KERNELBASE!GetProcAddress
       37     0 [  2]     ntdll!RtlInitString eax = 0
       23    37 [  1]   KERNELBASE!GetProcAddress
       11     0 [  2]     KERNELBASE!BasepMapModuleHandle eax = 70340000
       25    48 [  1]   KERNELBASE!GetProcAddress
        9     0 [  2]     ntdll!LdrGetProcedureAddress
       97     0 [  3]       ntdll!LdrGetProcedureAddressEx eax = ffffffff`c0000139
       11    97 [  2]     ntdll!LdrGetProcedureAddress eax = ffffffff`c0000139
       29   156 [  1]   KERNELBASE!GetProcAddress
        6     0 [  2]     KERNELBASE!BaseSetLastNTError
       14     0 [  3]       ntdll!RtlNtStatusToDosError eax = 7f
        9    14 [  2]     KERNELBASE!BaseSetLastNTError
       12     0 [  3]       ntdll!RtlSetLastWin32Error eax = 0
       13    26 [  2]     KERNELBASE!BaseSetLastNTError eax = 7f
       35   195 [  1]   KERNELBASE!GetProcAddress eax = 0
       33  1984 [  0] MSCOREE!ShellShim__CorExeMain
        5     0 [  1]   KERNEL32!GetProcAddressStub
        1     0 [  1]   KERNEL32!GetProcAddress
       15     0 [  1]   KERNELBASE!GetProcAddress
       29     0 [  2]     ntdll!RtlInitString eax = 0
       23    29 [  1]   KERNELBASE!GetProcAddress
       11     0 [  2]     KERNELBASE!BasepMapModuleHandle eax = 70340000
       25    40 [  1]   KERNELBASE!GetProcAddress
        9     0 [  2]     ntdll!LdrGetProcedureAddress
      109     0 [  3]       ntdll!LdrGetProcedureAddressEx eax = 0
       11   109 [  2]     ntdll!LdrGetProcedureAddress eax = 0
       30   160 [  1]   KERNELBASE!GetProcAddress
       11     0 [  2]     KERNELBASE!BasepMapModuleHandle eax = 70340000
       37   171 [  1]   KERNELBASE!GetProcAddress eax = 70345573
       39  2198 [  0] MSCOREE!ShellShim__CorExeMain
        7     0 [  1]   mscoreei!_CorExeMain
       18     0 [  2]     mscoreei!ShimLog::Log
        3     0 [  3]       mscoreei!__security_check_cookie eax = ffffffff`cf9f4bc6
       20     3 [  2]     mscoreei!ShimLog::Log eax = ffffffff`cf9f4bc6
       15    23 [  1]   mscoreei!_CorExeMain 
        3     0 [  2]     mscoreei!GetCorExeMainEntrypoint
       17     0 [  3]       mscoreei!_EH_prolog3 eax = 1ff7bc
       13    17 [  2]     mscoreei!GetCorExeMainEntrypoint
       14     0 [  3]       mscoreei!BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy *,&DoNothing<ICLRMetaHostPolicy *>,&DoTheRelease<ICLRMetaHostPolicy>,2>,0,&CompareDefault<ICLRMetaHostPolicy *>,2>::operator& eax = 1ff7d8
       18    31 [  2]     mscoreei!GetCorExeMainEntrypoint
      135     0 [  3]       mscoreei!CLRCreateInstance eax = 0
       34   166 [  2]     mscoreei!GetCorExeMainEntrypoint
       14     0 [  3]       mscoreei!BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy *,&DoNothing<ICLRMetaHostPolicy *>,&DoTheRelease<ICLRMetaHostPolicy>,2>,0,&CompareDefault<ICLRMetaHostPolicy *>,2>::operator& eax = 1ff7d8
       55   180 [  2]     mscoreei!GetCorExeMainEntrypoint
    ModLoad: C:\Windows\system32\SHLWAPI.dll
    ModLoad: C:\Windows\system32\GDI32.dll
    ModLoad: C:\Windows\system32\USER32.dll
    ModLoad: C:\Windows\system32\LPK.dll
    ModLoad: C:\Windows\system32\USP10.dll
    ModLoad: C:\Windows\system32\IMM32.DLL
    ModLoad: C:\Windows\system32\MSCTF.dll
       54     0 [  3]       mscoreei!CLRMetaHostPolicyImpl::GetRequestedRuntime eax = 0
       73   234 [  2]     mscoreei!GetCorExeMainEntrypoint
    ModLoad: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll 
    ModLoad: C:\Windows\system32\MSVCR100_CLR0400.dll 
       32     0 [  3]       mscoreei!CLRRuntimeInfoImpl::GetProcAddress eax = 0
       79   266 [  2]     mscoreei!GetCorExeMainEntrypoint
       17     0 [  3]       mscoreei!BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy *,&DoNothing<ICLRMetaHostPolicy *>,&DoTheRelease<ICLRMetaHostPolicy>,2>,0,&CompareDefault<ICLRMetaHostPolicy *>,2>::~BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy eax = 0
       82   283 [  2]     mscoreei!GetCorExeMainEntrypoint
       17     0 [  3]       mscoreei!BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy *,&DoNothing<ICLRMetaHostPolicy *>,&DoTheRelease<ICLRMetaHostPolicy>,2>,0,&CompareDefault<ICLRMetaHostPolicy *>,2>::~BaseWrapper<ICLRMetaHostPolicy *,FunctionBase<ICLRMetaHostPolicy eax = 0
       84   300 [  2]     mscoreei!GetCorExeMainEntrypoint
       11     0 [  3]       mscoreei!_EH_epilog3 eax = 0
       85   311 [  2]     mscoreei!GetCorExeMainEntrypoint eax = 0
       20   419 [  1]   mscoreei!_CorExeMain
        3     0 [  2]     clr!_CorExeMain
       21     0 [  3]       clr!_SEH_prolog4 eax = 1ff7c4
        8    21 [  2]     clr!_CorExeMain
    (780.b4): Unknown exception - code 04242420 (first chance)
    ModLoad: C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\mscorlib.ni.dll 
    ModLoad: C:\Windows\Microsoft.NET\Framework\v4.0.30319\nlssorting.dll
    ModLoad: C:\Windows\system32\ole32.dll
    ModLoad: C:\Windows\system32\CRYPTBASE.dll
    ModLoad: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll 
       70     0 [  3]       clr!_CorExeMainInternal

    A quick debugging on clr!_CorExeMainInternal function showed many interesting function invocations, such as clr!DoAdditionalPEChecks, clr!CorCommandLine::SetArgvW, clr!EnsureEEStarted, clr!IsStackProbingEnabled, and eventually clr!ExecuteEXE.

    clr!ExecuteEXE:
    657080dc 6a24            push    24h
    657080de 6888817065      push    offset clr! ?? ::FNODOBFM::`string'+0x6ee2c (65708188)
    657080e3 e8f898edff      call    clr!_SEH_prolog4 (655e19e0)
    657080e8 8b7508          mov     esi,dword ptr [ebp+8]
    657080eb 33ff            xor     edi,edi
    657080ed 3bf7            cmp     esi,edi
    657080ef 0f84be250b00    je      clr!ExecuteEXE+0x15 (657ba6b3)
    
    clr!ExecuteEXE+0x1c:
    657080f5 683c796165      push    offset clr!StartupId (6561793c)
    657080fa 6868817065      push    offset clr!ExecExe_V1 (65708168)
    657080ff ff359ca9bc65    push    dword ptr [clr!Microsoft_Windows_DotNETRuntimePrivateHandle+0x4 (65bca99c)]
    65708105 ff3598a9bc65    push    dword ptr [clr!Microsoft_Windows_DotNETRuntimePrivateHandle (65bca998)]
    6570810b e804f8f0ff      call    clr!ETWTraceStartup::StartupTraceEvent (65617914)
    65708110 897508          mov     dword ptr [ebp+8],esi
    65708113 897de4          mov     dword ptr [ebp-1Ch],edi
    65708116 897dfc          mov     dword ptr [ebp-4],edi
    65708119 8d4dcc          lea     ecx,[ebp-34h]
    6570811c e837b4edff      call    clr!CLRException::HandlerState::HandlerState (655e3558)
    65708121 c745fc01000000  mov     dword ptr [ebp-4],1
    65708128 57              push    edi
    65708129 ff7508          push    dword ptr [ebp+8]
    6570812c e8a2f7ffff      call    clr!SystemDomain::ExecuteMainMethod (657078d3)
    65708131 897dfc          mov     dword ptr [ebp-4],edi
    65708134 e8c1380000      call    clr!ExecuteEXE+0x64 (6570b9fa)
    65708139 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
    65708140 683c796165      push    offset clr!StartupId (6561793c)
    65708145 6878817065      push    offset clr!ExecExeEnd_V1 (65708178)
    6570814a ff359ca9bc65    push    dword ptr [clr!Microsoft_Windows_DotNETRuntimePrivateHandle+0x4 (65bca99c)]
    65708150 ff3598a9bc65    push    dword ptr [clr!Microsoft_Windows_DotNETRuntimePrivateHandle (65bca998)]
    65708156 e8b9f7f0ff      call    clr!ETWTraceStartup::StartupTraceEvent (65617914)
    6570815b 33c0            xor     eax,eax
    6570815d 40              inc     eax
    
    clr!ExecuteEXE+0xcb:
    6570815e e8c298edff      call    clr!_SEH_epilog4 (655e1a25)
    65708163 c20400          ret     4
    
    clr!ExecuteEXE+0x15:
    657ba6b3 33c0            xor     eax,eax
    657ba6b5 e9a4daf4ff      jmp     clr!ExecuteEXE+0xcb (6570815e)
    

    After a few steps into and over, I got an interesting callstack:

    0:000> k 
    ChildEBP RetAddr  
    001af6fc 657079b4 clr!PEFile::GetEntryPointToken
    001afbdc 65708131 clr!SystemDomain::ExecuteMainMethod+0xe1
    001afc30 65708032 clr!ExecuteEXE+0x58
    001afc7c 657468b0 clr!_CorExeMainInternal+0x19f
    001afcb4 703455ab clr!_CorExeMain+0x4e
    001afcc0 70917f16 mscoreei!_CorExeMain+0x38
    001afcd0 70914de3 MSCOREE!ShellShim__CorExeMain+0x99
    001afcd8 7677ed6c MSCOREE!_CorExeMain_Exported+0x8
    001afce4 773d37f5 KERNEL32!BaseThreadInitThunk+0xe
    001afd24 773d37c8 ntdll!__RtlUserThreadStart+0x70
    001afd3c 00000000 ntdll!_RtlUserThreadStart+0x1b
    
    0:000> uf eip 
    clr!PEFile::GetEntryPointToken:
    65726c97 6a08            push    8
    65726c99 b8c08fad65      mov     eax,offset clr! ?? ::FNODOBFM::`string'+0x6359 (65ad8fc0)
    65726c9e e8f5abebff      call    clr!_EH_prolog3 (655e1898)
    65726ca3 8bf1            mov     esi,ecx
    65726ca5 e8b0ffeeff      call    clr!PEFile::IsResource (65616c5a)
    65726caa 85c0            test    eax,eax
    65726cac 0f858ac10a00    jne     clr!PEFile::GetEntryPointToken+0x91 (657d2e3c)
    
    clr!PEFile::GetEntryPointToken+0x17:
    65726cb2 394604          cmp     dword ptr [esi+4],eax
    65726cb5 0f8481c10a00    je      clr!PEFile::GetEntryPointToken+0x91 (657d2e3c)
    
    clr!PEFile::GetEntryPointToken+0x1c:
    65726cbb 394608          cmp     dword ptr [esi+8],eax
    65726cbe 7461            je      clr!PEFile::GetEntryPointToken+0x87 (65726d21)
    
    clr!PEFile::GetEntryPointToken+0x21:
    65726cc0 8bce            mov     ecx,esi
    65726cc2 e8468befff      call    clr!PEFile::IsNativeLoaded (6561f80d)
    65726cc7 85c0            test    eax,eax
    65726cc9 7556            jne     clr!PEFile::GetEntryPointToken+0x87 (65726d21)
    
    clr!PEFile::GetEntryPointToken+0x2c:
    65726ccb 8b4e08          mov     ecx,dword ptr [esi+8]
    65726cce e82a78f4ff      call    clr!PEImage::IsOpened (6566e4fd)
    65726cd3 85c0            test    eax,eax
    65726cd5 0f840fc10a00    je      clr!PEFile::GetEntryPointToken+0x38 (657d2dea)
    
    clr!PEFile::GetEntryPointToken+0x82:
    65726cdb 8b4e08          mov     ecx,dword ptr [esi+8]
    
    clr!PEFile::GetEntryPointToken+0x8a:
    65726cde e806000000      call    clr!PEImage::GetEntryPointToken (65726ce9)
    
    clr!PEFile::GetEntryPointToken+0x93:
    65726ce3 e854aaebff      call    clr!_EH_epilog3 (655e173c)
    65726ce8 c3              ret
    
    clr!PEFile::GetEntryPointToken+0x87:
    65726d21 8b4e0c          mov     ecx,dword ptr [esi+0Ch]
    65726d24 ebb8            jmp     clr!PEFile::GetEntryPointToken+0x8a (65726cde)
    
    clr!PEFile::GetEntryPointToken+0x38:
    657d2dea 8bce            mov     ecx,esi
    657d2dec e8a61be8ff      call    clr!PEFile::GetNativeImageWithRef (65654997)
    657d2df1 8365f000        and     dword ptr [ebp-10h],0
    657d2df5 8945ec          mov     dword ptr [ebp-14h],eax
    657d2df8 85c0            test    eax,eax
    657d2dfa 7407            je      clr!PEFile::GetEntryPointToken+0x51 (657d2e03)
    
    clr!PEFile::GetEntryPointToken+0x4a:
    657d2dfc c745f001000000  mov     dword ptr [ebp-10h],1
    
    clr!PEFile::GetEntryPointToken+0x51:
    657d2e03 c745fc03000000  mov     dword ptr [ebp-4],3
    657d2e0a 8b4dec          mov     ecx,dword ptr [ebp-14h]
    657d2e0d 85c9            test    ecx,ecx
    657d2e0f 741a            je      clr!PEFile::GetEntryPointToken+0x76 (657d2e2b)
    
    clr!PEFile::GetEntryPointToken+0x5f:
    657d2e11 e8d33ef5ff      call    clr!PEImage::GetEntryPointToken (65726ce9)
    657d2e16 8bf0            mov     esi,eax
    657d2e18 834dfcff        or      dword ptr [ebp-4],0FFFFFFFFh
    657d2e1c 8d4dec          lea     ecx,[ebp-14h]
    657d2e1f e8e91be8ff      call    clr!BaseWrapper<PEImage *,FunctionBase<PEImage *,&DoNothing<PEImage *>,&DoTheRelease<PEImage>,2>,0,&CompareDefault<PEImage *>,2>::~BaseWrapper<PEImage *,FunctionBase<PEImage *,&DoNothing<PEImage *>,&DoTheRelease<PEImage>,2>,0,&CompareDefault<PEImage *>,2> (65654a0d)
    657d2e24 8bc6            mov     eax,esi
    657d2e26 e9b83ef5ff      jmp     clr!PEFile::GetEntryPointToken+0x93 (65726ce3)
    
    clr!PEFile::GetEntryPointToken+0x76:
    657d2e2b 834dfcff        or      dword ptr [ebp-4],0FFFFFFFFh
    657d2e2f 8d4dec          lea     ecx,[ebp-14h]
    657d2e32 e8d61be8ff      call    clr!BaseWrapper<PEImage *,FunctionBase<PEImage *,&DoNothing<PEImage *>,&DoTheRelease<PEImage>,2>,0,&CompareDefault<PEImage *>,2>::~BaseWrapper<PEImage *,FunctionBase<PEImage *,&DoNothing<PEImage *>,&DoTheRelease<PEImage>,2>,0,&CompareDefault<PEImage *>,2> (65654a0d)
    657d2e37 e99f3ef5ff      jmp     clr!PEFile::GetEntryPointToken+0x82 (65726cdb)
    
    clr!PEFile::GetEntryPointToken+0x91:
    657d2e3c 33c0            xor     eax,eax
    657d2e3e e9a03ef5ff      jmp     clr!PEFile::GetEntryPointToken+0x93 (65726ce3)
    

    This function would retrieve the EntryPointToken from the COR Header, which in turn is a part of the PE32/PE32+ header. Record down the return value of clr!PEFile::GetEntryPointToken for later use.

    As soon as clr.dll was loaded, we are ready to ask Son of Strike for help:

    0:000> .loadby sos clr

    Continue tracing until clr!AppDomain::LoadDomainAssembly, now the hello.exe assembly was loaded into the AppDomain:

    0:000> !EEVersion
    4.0.30319.237 free
    Workstation mode
    SOS Version: 4.0.30319.237 retail build
    0:000> !DumpDomain
    --------------------------------------
    System Domain:      65bd3478
    LowFrequencyHeap:   65bd3784
    HighFrequencyHeap:  65bd37d0
    StubHeap:           65bd381c
    Stage:              OPEN
    Name:               None
    --------------------------------------
    Shared Domain:      65bd3140
    LowFrequencyHeap:   65bd3784
    HighFrequencyHeap:  65bd37d0
    StubHeap:           65bd381c
    Stage:              OPEN
    Name:               None
    Assembly:           003e7988 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\mscorlib.dll]
    ClassLoader:        003e7a28
      Module Name
    64811000            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\mscorlib.dll
    
    --------------------------------------
    Domain 1:           003a24d0
    LowFrequencyHeap:   003a284c
    HighFrequencyHeap:  003a2898
    StubHeap:           003a28e4
    Stage:              OPEN
    SecurityDescriptor: 003a3c48
    Name:               DefaultDomain
    Assembly:           003e7988 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\mscorlib.dll]
    ClassLoader:        003e7a28
    SecurityDescriptor: 003e7870
      Module Name
    64811000            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\mscorlib.dll
    
    Assembly:           003efd40 [hello.exe]
    ClassLoader:        003eb2a0
    SecurityDescriptor: 003efc68
      Module Name
    00232e9c            hello.exe
    
    0:000> !DumpAssembly 003efd40
    Parent Domain:      003a24d0
    Name:               hello.exe
    ClassLoader:        003eb2a0
      Module Name
    00232e9c            hello.exe
    
    0:000> !DumpModule 00232e9c
    Name:       hello.exe
    Attributes: PEFile 
    Assembly:   003efd40
    LoaderHeap:              00000000
    TypeDefToMethodTableMap: 002300c4
    TypeRefToMethodTableMap: 002300d0
    MethodDefToDescMap:      002300ec
    FieldDefToDescMap:       002300f8
    MemberRefToDescMap:      002300fc
    FileReferencesMap:       00230118
    AssemblyReferencesMap:   0023011c
    MetaData start address:  00272068 (732 bytes)
    
    0:000> k
    ChildEBP RetAddr  
    001af4a0 65707f0b clr!PEFile::GetEntryPointToken+0x98
    001af6f8 65707d3f clr!Assembly::ExecuteMainMethod+0xa4
    001afbdc 65708131 clr!SystemDomain::ExecuteMainMethod+0x4ec
    001afc30 65708032 clr!ExecuteEXE+0x58
    001afc7c 657468b0 clr!_CorExeMainInternal+0x19f
    001afcb4 703455ab clr!_CorExeMain+0x4e
    001afcc0 70917f16 mscoreei!_CorExeMain+0x38
    001afcd0 70914de3 MSCOREE!ShellShim__CorExeMain+0x99
    001afcd8 7677ed6c MSCOREE!_CorExeMain_Exported+0x8
    001afce4 773d37f5 KERNEL32!BaseThreadInitThunk+0xe
    001afd24 773d37c8 ntdll!__RtlUserThreadStart+0x70
    001afd3c 00000000 ntdll!_RtlUserThreadStart+0x1b
    
    0:000> pt
    eax=06000001 ebx=00000000 ecx=65726ce8 edx=fffffdff esi=003efd40 edi=00000000
    eip=65726ce8 esp=001af034 ebp=001af4a0 iopl=0         nv up ei pl nz na po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
    clr!PEFile::GetEntryPointToken+0x98:
    65726ce8 c3              ret
    
    0:000> !Token2EE hello.exe 6000001
    Module:      00232e9c
    Assembly:    hello.exe
    Token:       06000001
    MethodDesc:  <not loaded yet>
    Name:        HelloWorld.Main 
    Not JITTED yet.
    
    0:000> !Token2EE hello.exe 6000002
    Module:      00232e9c
    Assembly:    hello.exe
    Token:       06000001
    MethodDesc:  <not loaded yet>
    Name:        HelloWorld..ctor 
    Not JITTED yet.
    

    Now that we know where the CLR entry point is, set a break point:

    0:000> !bpmd hello.exe HelloWorld.Main
    Adding pending breakpoints...
    0:000> go

    It turns out that the simple hello world application has 3 threads at least (far better than Notepad, though):

    0:000> ~*k 
       0  Id: a18.90c Suspend: 1 Teb: 7ffde000 Unfrozen
    ChildEBP RetAddr  
    0023f1e4 651821bb hello!HelloWorld.Main()
    0023f1e4 651a4be2 clr!CallDescrWorker+0x33
    0023f260 651a4d84 clr!CallDescrWorkerWithHandler+0x8e
    0023f39c 651a4db9 clr!MethodDesc::CallDescr+0x194
    0023f3b8 651a4dd9 clr!MethodDesc::CallTargetWorker+0x21
    0023f3d0 652a7e1d clr!MethodDescCallSite::Call+0x1c
    0023f534 652a7f28 clr!ClassLoader::RunMain+0x24c
    0023f79c 652a7d3f clr!Assembly::ExecuteMainMethod+0xc1
    0023fc80 652a8131 clr!SystemDomain::ExecuteMainMethod+0x4ec
    0023fcd4 652a8032 clr!ExecuteEXE+0x58
    0023fd20 652e68b0 clr!_CorExeMainInternal+0x19f
    0023fd58 6f1e55ab clr!_CorExeMain+0x4e
    0023fd64 70377f16 mscoreei!_CorExeMain+0x38
    0023fd74 70374de3 MSCOREE!ShellShim__CorExeMain+0x99
    0023fd7c 76f8ed6c MSCOREE!_CorExeMain_Exported+0x8
    0023fd88 76e637f5 KERNEL32!BaseThreadInitThunk+0xe
    0023fdc8 76e637c8 ntdll!__RtlUserThreadStart+0x70
    0023fde0 00000000 ntdll!_RtlUserThreadStart+0x1b
    
       1  Id: a18.b54 Suspend: 1 Teb: 7ffdd000 Unfrozen
    ChildEBP RetAddr  
    009cfb4c 76e46a04 ntdll!KiFastSystemCallRet
    009cfb50 75256a36 ntdll!NtWaitForMultipleObjects+0xc
    009cfbec 76f8bd1e KERNELBASE!WaitForMultipleObjectsEx+0x100
    009cfc34 76f8bd8c KERNEL32!WaitForMultipleObjectsExImplementation+0xe0
    009cfc50 652dd300 KERNEL32!WaitForMultipleObjects+0x18
    009cfcb4 652dd23e clr!DebuggerRCThread::MainLoop+0xd9
    009cfce4 652dd179 clr!DebuggerRCThread::ThreadProc+0xca
    009cfd10 76f8ed6c clr!DebuggerRCThread::ThreadProcStatic+0x83
    009cfd1c 76e637f5 KERNEL32!BaseThreadInitThunk+0xe
    009cfd5c 76e637c8 ntdll!__RtlUserThreadStart+0x70
    009cfd74 00000000 ntdll!_RtlUserThreadStart+0x1b
    
       2  Id: a18.e1c Suspend: 1 Teb: 7ffdc000 Unfrozen
    ChildEBP RetAddr  
    00c5f688 76e46a04 ntdll!KiFastSystemCallRet
    00c5f68c 75256a36 ntdll!NtWaitForMultipleObjects+0xc
    00c5f728 76f8bd1e KERNELBASE!WaitForMultipleObjectsEx+0x100
    00c5f770 76f8bd8c KERNEL32!WaitForMultipleObjectsExImplementation+0xe0
    00c5f78c 6520d4de KERNEL32!WaitForMultipleObjects+0x18
    00c5f7ac 6520d542 clr!WKS::WaitForFinalizerEvent+0xa6
    00c5f7c4 652c453a clr!WKS::GCHeap::FinalizerThreadWorker+0x4a
    00c5f7d8 652c45bc clr!Thread::DoExtraWorkForFinalizer+0x114
    00c5f888 652c4677 clr!Thread::ShouldChangeAbortToUnload+0x101
    00c5f8e8 65337ae7 clr!Thread::ShouldChangeAbortToUnload+0x399
    00c5f90c 65337afa clr!ManagedThreadBase_NoADTransition+0x35
    00c5f91c 652a82ab clr!ManagedThreadBase::FinalizerBase+0xf
    00c5f954 65286578 clr!WKS::GCHeap::FinalizerThreadStart+0x10c
    00c5f9ec 76f8ed6c clr!Thread::intermediateThreadProc+0x4b
    00c5f9f8 76e637f5 KERNEL32!BaseThreadInitThunk+0xe
    00c5fa38 76e637c8 ntdll!__RtlUserThreadStart+0x70
    00c5fa50 00000000 ntdll!_RtlUserThreadStart+0x1b
    

    Why are we getting extra threads? The answer is one for finalizer and another for debug helper. Another finding is that we were given workstation GC (WKS::GCHeap) by default (and dd clr!g_IGCConcurrent showed that concurrent GC mode was enabled). The !Threads -special command would show these extra threads as Finalizer and DbgHelper. Sometimes, there can be extra threads with the ntdll!TppWorkerThread frame, these are threads created by the operating system kernel, which can be verified by placing a breakpoint on ntdll!TpAllocPool. On my Win7 x64 machine the callstack would look like:

    ntdll!TpAllocPool
    KERNELBASE!CreateThreadpool+0x12
    RPCRT4!RPC_THREAD_POOL::InitializeCallbackEnvironmentIfNecessary+0x94
    RPCRT4!RPC_THREAD_POOL::CreateTimer+0x1d
    RPCRT4!GarbageCollectionNeeded+0xc3
    RPCRT4!LRPC_CASSOCIATION::RemoveReference+0x14c
    RPCRT4!LRPC_FAST_BINDING_HANDLE::ResetBindState+0x81
    RPCRT4!LRPC_BASE_BINDING_HANDLE::FreeObject+0x17
    RPCRT4!RpcBindingFree+0x48
    RPCRT4!NDRCContextUnmarshallInternal+0xb7
    RPCRT4!Ndr64UnmarshallHandle+0xad
    RPCRT4!Ndr64pClientUnMarshal+0x122
    RPCRT4!NdrpClientCall3+0x2a6
    RPCRT4!NdrClientCall3+0xf2
    ADVAPI32!LsaClose+0x2f
    ADVAPI32!InitializeSidLookupTable+0x279
    ADVAPI32!LocalConvertStringSDToSD_Rev1+0xb2
    ADVAPI32!ConvertStringSecurityDescriptorToSecurityDescriptorW+0x32
    clr!ProfilingAPIAttachDetach::GetSecurityDescriptor+0x172
    clr!ProfilingAPIAttachDetach::InitSecurityAttributes+0x19
    clr!ProfilingAPIAttachDetach::InitializeForOnDemandMode+0x58
    clr!ProfilingAPIAttachDetach::GetAttachEvent+0x1a
    clr!WKS::GCHeap::FinalizerThreadStart+0x6b
    clr!Thread::intermediateThreadProc+0x7d
    KERNEL32!BaseThreadInitThunk+0xd
    ntdll!RtlUserThreadStart+0x1d
    

    Now let's take a look at the JITted code:

    0:000> !u eip
    Normal JIT generated code
    HelloWorld.Main()
    Begin 003e0070, size 21
    
    003e0070 55              push    ebp
    003e0071 8bec            mov     ebp,esp
    003e0073 833d3c311c0000  cmp     dword ptr ds:[1C313Ch],0
    003e007a 7405            je      hello!HelloWorld.Main()+0x11 (003e0081)
    003e007c e8c6671b67      call    clr!JIT_DbgIsJustMyCode (67596847)
    003e0081 90              nop
    
    003e0082 8b0d30209f02    mov     ecx,dword ptr ds:[29F2030h] ("Hello, world!")
    003e0088 e81f701364      call    mscorlib_ni!System.Console.WriteLine(System.String) (645170ac)
    003e008d 90              nop
    
    003e008e 90              nop
    003e008f 5d              pop     ebp
    003e0090 c3              ret
    

    The disassembly looks interesting at least in three ways:

    1. The code is already JITted, otherwise the entry point in MethodDesc table would point to clr!PrecodeFixupThunk instead of the emitted machine instructions, this can be verified by placing a data breakpoint on the entry point field of MethodDesc, and take a look at clr!MethodDesc::SetStableEntryPointInterlocked and clr!MethodDesc::SetNativeCodeInterlocked from the callstack. One thing to keep in mind is that the breakpoint itself relies on JIT compiler to tell where the machine instruction was emitted.
    2. The generated code is not optimized in two ways. First, the CIL (MSIL) itself is not optimized as the code is compiled under debug mode with optimization turned off (/optimize-) by default, which also implied EnC (Edit and Continue). This can be verified using the !DumpIL command:
      IL_0000: nop 
      IL_0001: ldstr "Hello, world!"
      IL_0006: call System.Console::WriteLine
      IL_000b: nop
      IL_000c: ret
      Second, the code generation engine is working under debugging mode, this is one of the Side Effects of Debugger. However, there are several ways to enable the optimization, such as hijacking the clrjit!CLRConfig::GetConfigValue. One cheap way is to have a configuration file and save it as hello.ini (one chicken and egg question: why are we using .ini file instead of the modern & popular XML?) under the same folder as hello.exe:
      [.NET Framework Debugging Control]
      GenerateTrackingInfo=0
      AllowOptimize=1
      
    3. There is a special function, which is direct related to JMC (Just My Code).

    The next thing I noticed is that the C:\Windows\assembly folder looks strange from Windows Explorer, so I tried to rename the Desktop.ini file:

    cd /d %WINDIR%\assembly
    attrib -r -h -s Desktop.ini
    ren Desktop.ini Desktop.ini.bak

    I also tried to dump the folder structure using tree /f command under C:\Windows\assembly, and find the folder GAC, GAC_32, GAC_64, GAC_MSIL as well as a number of folders whose name started with NativeImages_. There are two other folders called tmp and temp which I haven't spend time investigating. A quick search on MSDN showed there is a tool comes with the .NET SDK called corflags.exe, so I gave it a try using the following command:

    for /r C:\Windows\assembly %f in (*.dll) do corflags.exe /nologo "%f" 

    When I used corflags.exe on my hello.exe application, I got the following output:

    Version   : v4.0.30319
    CLR Header: 2.5
    PE        : PE32
    CorFlags  : 1
    ILONLY    : 1
    32BIT     : 0
    Signed    : 0

    The loader that comes with NT 5.1 and later versions would acknowledge the CLR header with help from the MsCorEE.dll shim. On a 64bit system (either x64 or IA64) the loader would dynamically construct an in memory PE32+ header based on the original PE32 header, given that the module has a IMAGE_COR20_HEADER with MajorRuntimeVersion >= 2 and the module targets ANYCPU.

    The PE checksum (NT_HEADERS.OptionalHeader.CheckSum) is zero for the hello.exe module, no matter which flags I used for csc.exe (C# Compiler) or al.exe (Assembly Linker). It doesn't take much time to find the reason from ECMA-335 [25.2.3.2] PE header Windows NT-specific fields:

    File Checksum: should be 0

    Even many assemblies which are part of the .NET Framework have zero in their PE checksum. WinDBG would complain because PE checksum is used for symbol matching. Currently there are two workarounds on top of my head:

    1. Use link.exe instead of al.exe and csc.exe, with /RELEASE turned on. (link.exe would always merge modules and produce single module assembly)
      csc.exe /nologo /debug+ /t:module hello.cs
      link.exe /LTCG /DEBUG /RELEASE /ENTRY:HelloWorld.Main /PDB:hello.exe.pdb /SUBSYSTEM:CONSOLE hello.netmodule
      
    2. Use some PE editing utility to fix the PE checksum. (the associated PDB should be tweaked to reflect the PE checksum changes as well)

    To answer why File Checksum "should be 0", I guess the original team that designed the assembly format encountered chicken and egg problem while trying to put a digital sign into the assembly itself.

    Synchronization is always one of my favorites, so I tried the following code:

    class HelloWorld
    {
      static void Main()
      {
        lock("")
        {
          System.Console.WriteLine("Hello, world!");
        }
      }
    }
    

    When I tried to debug, I got the following thing:

    0:000> !Name2EE hello!HelloWorld.Main 
    Module:      009b2e9c
    Assembly:    hello.exe
    Token:       06000001
    MethodDesc:  009b33f0
    Name:        HelloWorld.Main()
    JITTED Code Address: 035f2670
    
    0:000> !DumpIL 009b33f0 
    ilAddr = 00402050
    IL_0000: ldstr ""
    IL_0005: dup
    IL_0006: stloc.0
    IL_0007: call System.Threading.Monitor::Enter
    .try 
    {
      IL_000c: ldstr "Hello, world!"
      IL_0011: call System.Console::WriteLine
      IL_0016: leave.s IL_001f
    } // end .try
    .finally 
    {
      IL_0018: ldloc.0
      IL_0019: call System.Threading.Monitor::Exit 
      IL_001e: endfinally
    
    } // end .finally
    IL_001f: ret
    
    0:000> !U 035f2670
    Normal JIT generated code
    HelloWorld.Main()
    Begin 035f2670, size 71
    035f2670 55              push    ebp
    035f2671 8bec            mov     ebp,esp
    035f2673 57              push    edi
    035f2674 56              push    esi
    035f2675 53              push    ebx
    035f2676 83ec18          sub     esp,18h
    035f2679 8d7ddc          lea     edi,[ebp-24h]
    035f267c b905000000      mov     ecx,5
    035f2681 33c0            xor     eax,eax
    035f2683 f3ab            rep stos dword ptr es:[edi]
    035f2685 33c0            xor     eax,eax
    035f2687 8945e8          mov     dword ptr [ebp-18h],eax
    035f268a 8b056020b201    mov     eax,dword ptr ds:[1B22060h] ("")
    035f2690 8945dc          mov     dword ptr [ebp-24h],eax
    035f2693 8bc8            mov     ecx,eax
    035f2695 e82004b575      call    clr!JIT_MonEnterWorker (79142aba)
    035f269a ff153c705c03    call    dword ptr ds:[35C703Ch] (System.Console.get_Out(), mdToken: 060008fd)
    035f26a0 8bc8            mov     ecx,eax
    035f26a2 8b152c37b201    mov     edx,dword ptr ds:[1B2372Ch] ("Hello, world!")
    035f26a8 8b01            mov     eax,dword ptr [ecx]
    035f26aa 8b403c          mov     eax,dword ptr [eax+3Ch]
    035f26ad ff5010          call    dword ptr [eax+10h]
    035f26b0 c745e400000000  mov     dword ptr [ebp-1Ch],0
    035f26b7 c745e8fc000000  mov     dword ptr [ebp-18h],0FCh
    035f26be 68d8265f03      push    35F26D8h
    035f26c3 eb00            jmp     035f26c5
    035f26c5 8b4ddc          mov     ecx,dword ptr [ebp-24h]
    035f26c8 e8bb07b575      call    clr!JIT_MonExitWorker (79142e88)
    035f26cd 58              pop     eax
    035f26ce ffe0            jmp     eax
    035f26d0 8d65f4          lea     esp,[ebp-0Ch]
    035f26d3 5b              pop     ebx
    035f26d4 5e              pop     esi
    035f26d5 5f              pop     edi
    035f26d6 5d              pop     ebp
    035f26d7 c3              ret
    035f26d8 c745e800000000  mov     dword ptr [ebp-18h],0
    035f26df ebef            jmp     035f26d0
    

    Two things seem interesting:

    1. The lock statement is translated to try/finally block by the compiler, and System.Threading.Monitor is used to do the actual synchronization.
    2. The Enter and Exit method of System.Threading.Monitor got translated to JIT_MonEnterWorker and JIT_MonExitWorker implemented inside the clr.dll. By looking at the metadata, it looks that System.Threading.Monitor::Enter is decorated by [MethodImpl(MethodImplOptions.InternalCall)], and there is no real CIL code. This is known as ECall (SSCLI vm/ecall.cpp), the JIT compiler would treat this in a special way, and instead of generating native code from CIL, it would look up from a table of native functions implemented by the CLR, and generate related thunk code.

    (to be continued...)

  • Rubato and Chord

    Side Effects of Debugger

    • 2 Comments

    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:

    • The heap manager makes use of FLG_HEAP_ENABLE_TAIL_CHECK, FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS flags in PEB!NtGlobalFlags.
    • Low Fragmentation Heap might be disabled.
    • Heap functions might throw SEH, an article covering this can be found at debuginfo.com.
    • Debug heap can be turned off by setting the environment variable _NO_DEBUG_HEAP = 1.
    • Windows debuggers has a command line option -hd which specifies that the debug heap should not be used.

    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.

    • PEB and TEB, this is exactly what IsDebuggerPresent has used.
    • DebugPort, this is used by the kernel (EPROCESS). NtQueryInformationProcess from NTDLL can be used to retrieve this information.
    • INT3 and thread context, as we've already demonstrated here.
    • Environment variable, parent process, process startup information.
    • Image File Execution Options.
    • Call stack and register. If the debugger makes use of func-eval, conditional breakpoints with side effects, or caused some execution flow changes, it can be detected.

    A few things to mention:

    • You cannot attach a debugger to a program if the program is already attached by another debugger.
    • Attaching a debugger to a program can fail in many ways, such like loader lock, timeout and break-in thread creation failure. That is one reason why JIT debugging failed to work.
    • 64bit application cannot be debugged by a 32bit debugger, if you try to create a 64bit process from a 32bit process with debug creation flag, you always ended in failure. DebugActiveProcess would fail if a 32bit debugger tried to attach to a 64bit target.
    • Digital media application can take advantage of the windows kernel to protect itself from being debugged.
    • You should be cautious if you are debugging something that the debugger relies on (a GUI symbolic source level debugger relies on even more things), otherwise you would end up with deadlock or other strange behaviors.
    • Global Flags can affect the behavior of a program if running under a debugger (e.g. loader snaps).
    • CLR behaves very differently under a debugger (e.g. JIT compiler, GC).
  • Rubato and Chord

    Did you know...

    • 0 Comments

    Have you ever seen the following window before? It was once very popular in the good old days, but has been abandoned in recent years (another good example being the pixel fonts). People just keep getting busier in the blooming new era.

    Windows 7

    1. Shift + Right Click on a file icon would give you additional context menu entries such as "Copy as path".
    2. Shift + Right Click on a folder icon would give you "Open command window here".

    Visual Studio 2010 (C++ mode)

    1. Use the Ctrl + R, Ctrl + W keyboard shortcut to toggle the spaces and tab marks.
    2. You can use $CMDLINE and $ENV pseudovariables in the Watch Window while debugging, and if you modified the value, take a look at the Output Window to see what happened.
    3. You can use $ERR,hr in the Watch Window to view the last error, the format specifier will translate the error code into text message.
    4. You can create a new toolchain by creating a new PlatformToolset, the default installation path is C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\Win32\PlatformToolsets.

    Hyper-V

    1. To tell if a machine is physical or virtual, the most reliable way I can think is to measure the system timing (e.g. DPC latency).
    2. You can determine if a machine is physical or virtual from the hardware info, HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS.
    3. You can get the host machine for a VM by taking look at the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters.

    A few things which are difficult than they look

    1. Parse command line.
    2. Manipulate file path and name.
    3. Mimic the behavior of NT loader.
  • Rubato and Chord

    MACRO Revisited

    • 1 Comments

    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:

    1. Use TEXT if you are using none of the ATL, CRT and MFC.
    2. Use _T if you are using the ATL, CRT and MFC.
    3. Use _TEXT instead of _T if you are not as lazy as me.
    4. Don't use __T and __TEXT unless you have a special reason.

    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.

    Conclusion:

    1. Use NTDDI_VERSION whenever possible.
    2. Don't use WINVER unless you have special reason.
    3. Forget about _WIN32_WINDOWS unless you are still targeting Win9x or Win32s.
    4. Don't use VER_PRODUCTVERSION_W unless you are writing low level code such like drivers and debugger extensions.

    _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. //

    (to be continued...)

  • Rubato and Chord

    Pop Quiz - Debug Event Loop and Timeslice Quota

    • 0 Comments

    You might have heard of the Popek and Goldberg Virtualization Requirements. In theory, debugger shares a similar set of problems as virtualization, this is especially true for func-eval (Function Evaluation). Here goes a pop quiz about the side effects of the presence of debugger:


    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    
    #define LOOPCOUNT 10
    
    ULONG g_ulVariableA;
    ULONG g_ulVariableB;
    
    DWORD WINAPI ThreadProcA(LPVOID lpParameter)
    {
      while(true)
      {
        for(int i = LOOPCOUNT; i; i--)
          ++g_ulVariableA;
      } // add a breakpoint here (BP1)
      return 0; } DWORD WINAPI ThreadProcB(LPVOID lpParameter) {   while(true)   {     for(int i = LOOPCOUNT; i; i--)       ++g_ulVariableB;   } // add a breakpoint here (BP2)
      return 0; } int ExeEntry(void) {   SetProcessAffinityMask(GetCurrentProcess(), 1);   CloseHandle(CreateThread(NULL, 4096, ThreadProcA, NULL, 0, NULL));   CloseHandle(CreateThread(NULL, 4096, ThreadProcB, NULL, 0, NULL));   return ERROR_SUCCESS; }

    Let's say we have two breakpoints BP1 and BP2 as illustrated:

    1. Each time I launched the application from the Visual Studio Debugger on my desktop machine (Xeon Quad core, Windows 7 64bit), BP2 would always get hit before BP1. On my laptop (Dual core, Windows 7 32bit), BP1 will get hit before BP2.
    2. If I made BP2 as a conditional breakpoint with a false condition (e.g. 0 == 1) on my desktop machine, I will have to wait a few seconds before BP1 got hit.
    3. If I made BP1 as a conditional breakpoint with a false condition (e.g. 0 == 1) on my laptop, I never got a chance to hit BP2, and my CPU usage would always stay at around 50%.

    Do you share a similar experience as I have? I have already put some hints on the title of this pop quiz, happy debugging :)

  • Rubato and Chord

    Microsoft Binary Technologies and Debugging

    • 0 Comments

    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:

    • BBT (Basic Block Tools) is a suite of optimization tools designed to help reduce the working-set requirements for a Win32 application by applying advanced static analysis and code layout heuristics, and integrating profile data gathered from monitoring the program execution flow. In addition, BBT rearranges static data and resources sections for additional paging reduction.
    • Detours is a library for instrumenting arbitrary Win32 functions on x86, x64, and IA64 machines. Detours intercepts Win32 functions by re-writing the in-memory code for target functions. The Detours package also contains utilities to attach arbitrary DLLs and data segments (called payloads) to any Win32 binary.
    • Vulcan is a single infrastructure for building a wide range of custom tools for program analysis, optimization, and testing. Through the Vulcan API, developers and testers can build custom tools with very few lines of code for basic block counting, memory tracing, memory allocation, coverage, failure insertion, optimization, compiler auditing etc. Vulcan scales to large commercial applications and has been used to improve the performance and reliability of products across 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 L1
    kernel32!SetErrorMode:
    75ac016d ff25b41da775    jmp     dword ptr [kernel32!_imp__SetErrorMode (75a71db4)]
    0:001> u poi(75a71db4)
    KERNELBASE!SetErrorMode:
    75417991 8bff            mov     edi,edi
    75417993 55              push    ebp
    75417994 8bec            mov     ebp,esp
    75417996 51              push    ecx
    75417997 56              push    esi
    75417998 e836000000      call    KERNELBASE!GetErrorMode (754179d3)
    7541799d 8bf0            mov     esi,eax
    7541799f 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 ebp
    8B EC           mov  ebp, esp
      return TRUE;
    B8 01 00 00 00  mov  eax, 1
    }
    5D              pop  ebp
    C3              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).

  • Rubato and Chord

    A Debugging Approach to IFEO

    • 2 Comments

    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:

    1. Image File Execution Options by Junfeng.
    2. Inside 'Image File Execution Options' debugging by Gregg.
    3. Beware the Image File Execution Options key by Raymond.
    4. IFEO and Managed-debugging by Mike.

    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.exe

    Let's launch the demo application from WinDBG:

    windbg.exe createprocess.exe

    0:000> x *!*Create*Process*
    767007a2          kernel32!CreateProcessInternalW
    766b2082          kernel32!CreateProcessA
    766b204d          kernel32!CreateProcessW
    7670c89c          kernel32!CreateProcessInternalA
    77d756a8          ntdll!NtCreateProcessEx
    77d75698          ntdll!NtCreateProcess
    77d889fb          ntdll!RtlpCreateProcessRegistryInfo
    77d75698          ntdll!ZwCreateProcess
    77d756a8          ntdll!ZwCreateProcessEx
    77d56bdf          ntdll!RtlpCreateUserProcess
    77d56b71          ntdll!RtlCreateUserProcess
    77dd1072          ntdll!RtlCreateProcessReflection
    77d75778          ntdll!ZwCreateUserProcess
    77dd988a          ntdll!RtlCreateProcessParameters
    77d75778          ntdll!NtCreateUserProcess
    77d96ee9          ntdll!RtlCreateProcessParametersEx

    0:000> bp kernel32!CreateProcessInternalW

    0:000> g
    Breakpoint 0 hit

    0:000> k3
    ChildEBP RetAddr
    0018f7a8 7670c9bf kernel32!CreateProcessInternalW
    0018f888 766b20ae kernel32!CreateProcessInternalA+0x2f8
    0018f8c0 00d71068 kernel32!CreateProcessA+0x2c

    0:000> ddu esp L6
    0018f7ac  7670c9bf "???..?"
    0018f7b0  00000000
    0018f7b4  00000000
    0018f7b8  00383a08 "notepad.exe"
    0018f7bc  00000000
    0018f7c0  00000000

    Continue tracing, eventually we will reach this point:

    ChildEBP RetAddr 
    001af248 77d75784 ntdll!KiFastSystemCall
    001af24c 76700eff ntdll!NtCreateUserProcess+0xc
    001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe75
    001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8
    001af9c0 01371068 kernel32!CreateProcessA+0x2c

    0:000> bp 77d75784
    0:000> g
    Breakpoint 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 RetAddr
    0020efe4 77d9ce6f ntdll!RtlQueryImageFileExecutionOptions
    0020f008 76704ab1 ntdll!LdrQueryImageFileExecutionOptions+0x1e
    0020f034 76702abb kernel32!BasepGetDisableLocalOverrideConfig+0x33
    0020f0ac 7670119b kernel32!BasepConstructSxsCreateProcessMessage+0x8e
    0020f728 7670c9bf kernel32!CreateProcessInternalW+0x16b1
    0020f808 766b20ae kernel32!CreateProcessInternalA+0x2f8
    0020f840 01371068 kernel32!CreateProcessA+0x2c

    ChildEBP RetAddr
    001af264 7672d529 kernel32!BuildSubSysCommandLine
    001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xf87
    001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8
    001af9c0 01371068 kernel32!CreateProcessA+0x2c

    0:000> ddu esp L6
    001af268  7672d529 "??????"
    001af26c  00000003
    001af270  00234348 ""C:\Program Files\Windows NT\Accessories\wordpad.exe""
    001af274  00000000
    001af278  00233a08 "notepad.exe"
    001af27c  001af4d8 ".."

    Continue tracing:

    ChildEBP RetAddr 
    001af1ac 76701693 ntdll!RtlCreateProcessParametersEx
    001af258 76700e90 kernel32!BasepCreateProcessParameters+0x148
    001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe06
    001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8
    001af9c0 01371068 kernel32!CreateProcessA+0x2c

    0:000> dS poi(esp+8)
    00283cf8  "C:\Program Files\Windows NT\Acce"
    00283d38  "ssories\wordpad.exe"

    Continue tracing:

    ChildEBP RetAddr 
    001af24c 76700eff ntdll!NtCreateUserProcess
    001af8a8 7670c9bf kernel32!CreateProcessInternalW+0xe75
    001af988 766b20ae kernel32!CreateProcessInternalA+0x2f8
    001af9c0 01371068 kernel32!CreateProcessA+0x2c

    Now the user mode IFEO logic looks clear to us, let's restart WinDBG and do some tweaking:

    0:000> .restart
    0:000> bp kernel32!BuildSubSysCommandLine; g; bc
    Breakpoint 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+0x117
    0020f3a0 77170bd9 ntdll!AVrfInitializeVerifier+0xd3
    0020f518 77156077 ntdll!LdrpInitializeProcess+0xe95
    0020f568 77153663 ntdll!_LdrpInitialize+0x78
    0020f578 00000000 ntdll!LdrInitializeThunk+0x10

    ntdll!AVrfpLoadAndInitializeProvider:
    771950e1 push 1Ch
    771950e3 push offset ntdll! ?? ::FNODOBFM::`string'+0x762 (77140ed8)
    771950e8 call ntdll!_SEH_prolog4 (77142c0c)
    771950ed mov byte ptr [ebp-19h],0
    771950f1 mov esi,dword ptr [ebp+8]
    771950f4 test byte ptr [ntdll!AVrfpDebug (771ce380)],1
    771950fb 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 ecx
    7719510b pop ecx
    7719510c mov dword ptr [ebp-28h],offset ntdll!RtlpCsVerifyDoNotBreak+0x7 (771ce178)
    77195113 xor eax,eax
    77195115 mov word ptr [ebp-2Ch],ax
    77195119 mov eax,208h
    7719511e mov word ptr [ebp-2Ah],ax
    77195122 push offset SharedUserData+0x30 (7ffe0030)
    77195127 lea eax,[ebp-2Ch]
    7719512a push eax
    7719512b call ntdll!RtlAppendUnicodeToString (7714eed2)
    77195130 push offset ntdll!SlashSystem32SlashString (771551f0)
    77195135 lea eax,[ebp-2Ch]
    77195138 push eax
    77195139 call ntdll!RtlAppendUnicodeStringToString (771472be)
    7719513e lea ebx,[esi+10h]
    77195141 push ebx
    77195142 lea eax,[esi+8]
    77195145 push eax
    77195146 push 0
    77195148 push dword ptr [ebp-28h]
    7719514b call ntdll!LdrLoadDll (771522b8)
    77195150 test eax,eax
    77195152 jge ntdll!AVrfpLoadAndInitializeProvider+0x98 (77195179)
    77195154 push dword ptr [ebp-28h]
    77195157 push eax
    77195158 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,14h
    77195170 mov byte ptr [ebp-19h],1
    77195174 jmp ntdll!AVrfpLoadAndInitializeProvider+0x21c (771952fd)
    77195179 and dword ptr [ebp-4],0
    7719517d 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...)

Page 3 of 4 (35 items) 1234