Rubato and Chord

Reiley's technical blog

  • Rubato and Chord

    A Debugging Approach to OutputDebugString

    • 0 Comments

    Using OutputDebugString is a common debugging technique for user mode debugging. It is easy but quite useful if you are debugging services or trouble shooting loader problem.


    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    
    int __cdecl main()
    {
      OutputDebugStringA("Hello, world! (first chance)\n");
      OutputDebugStringW(L"Hello, world! (second chance)\n");
    }

    When you debug this demo application using Visual Studio, you would see the text messages from the Output window:

    And if you are running this demo without debugging it, you can actually use DbgView to see the outputs (the Microsoft Platform SDK also comes with a console application called Dbmon.Exe):

    Have you ever wondered how it works?

    Let's launch the demo application from WinDBG (I'm using 32bit Win7, you might see different things if you are using other version of Windows):

    windbg.exe outputdebugstring.exe

    0:000> x kernel*!OutputDebugString*
    760c1321          KERNELBASE!OutputDebugStringA
    760c1585          KERNELBASE!OutputDebugStringW
    766e6df2          kernel32!OutputDebugStringW
    766ef0f0          kernel32!OutputDebugStringA
    0:000> bp kernelbase!OutputDebugStringW
    0:000> bl
     0 e 760c1585     0001 (0001)  0:**** KERNELBASE!OutputDebugStringW
    0:000> g
    Hello, world! (first chance)
    Breakpoint 0 hit
    KERNELBASE!OutputDebugStringW:
    760c1585 8bff            mov     edi,edi
    0:000> pc
    KERNELBASE!OutputDebugStringW+0x1d:
    760c15a2 ff15ec110a76    call    dword ptr [KERNELBASE+0x11ec (760a11ec)]
                   ds:0023:760a11ec={ntdll!RtlInitUnicodeStringEx (77d86f0a)}
    0:000> pc
    KERNELBASE!OutputDebugStringW+0x33:
    760c15b8 ff1500100a76    call    dword ptr [KERNELBASE+0x1000 (760a1000)]
             ds:0023:760a1000={ntdll!RtlUnicodeStringToAnsiString (77d89e8e)}
    0:000> pc
    KERNELBASE!OutputDebugStringW+0x42:
    760c15c7 e855fdffff      call    KERNELBASE!OutputDebugStringA (760c1321)

    If we continue tracing, eventually we will reach this point:

    ChildEBP RetAddr
    0015f610 77d762a4 ntdll!KiFastSystemCall
    0015f614 77d770fd ntdll!ZwRaiseException+0xc
    0015f8f8 760ad36f ntdll!RtlRaiseException+0x35
    0015f954 760c1375 KERNELBASE!RaiseException+0x58
    0015fbc0 01351021 KERNELBASE!OutputDebugStringA+0x286

    Dump the SEH chain:

    0:000> dt nt!_EXCEPTION_REGISTRATION_RECORD -r poi(fs:[0])
    ntdll!_EXCEPTION_REGISTRATION_RECORD
     +0x000 Next    : 0x0016fd58 _EXCEPTION_REGISTRATION_RECORD
      +0x000 Next    : 0x0016fdac _EXCEPTION_REGISTRATION_RECORD
       +0x000 Next    : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
       +0x004 Handler : 0x77d4e0ed _EXCEPTION_DISPOSITION ntdll!_except_handler4+0
      +0x004 Handler : 0x00a72440 _EXCEPTION_DISPOSITION outputdebugstring!_except_handler4+0
     +0x004 Handler : 0x760c1425 _EXCEPTION_DISPOSITION KERNELBASE!_except_handler4+0

    0:000> !exchain
    0029fcb0: KERNELBASE!_except_handler4+0 (760c1425)
      CRT scope  0, filter: KERNELBASE!OutputDebugStringA+60 (760c13bc)
                    func:   KERNELBASE!OutputDebugStringA+74 (760c11d1)
    0029fd10: outputdebugstring!_except_handler4+0 (00e72440)
      CRT scope  0, filter: outputdebugstring!__tmainCRTStartup+1f4 (00e71584)
                    func:   outputdebugstring!__tmainCRTStartup+20f (00e7159f)
    0029fd64: ntdll!_except_handler4+0 (77d4e0ed)
      CRT scope  0, filter: ntdll!__RtlUserThreadStart+2e (77da7eeb)
                    func:   ntdll!__RtlUserThreadStart+63 (77da8260)

    An SEH was chained, and the control is returned to the OS kernel.

    0:000> u 760c11d1 L20
    KERNELBASE!OutputDebugStringA+0x74:
    760c11d1 8b65e8          mov     esp,dword ptr [ebp-18h]
    760c11d4 33ff            xor     edi,edi
    760c11d6 89bdd0fdffff mov dword ptr [ebp-230h],edi
    760c11dc 89bddcfdffff    mov     dword ptr [ebp-224h],edi
    760c11e2 89bdd4fdffff    mov     dword ptr [ebp-22Ch],edi
    760c11e8 89bde0fdffff    mov     dword ptr [ebp-220h],edi
    760c11ee e86d56feff      call    KERNELBASE!GetLastError (760a6860)
    760c11f3 8985c8fdffff    mov     dword ptr [ebp-238h],eax
    760c11f9 393d28490e76    cmp     dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)],edi
    760c11ff 0f8457040000    je      KERNELBASE!OutputDebugStringA+0xa4 (760c165c)
    760c1205 a128490e76      mov     eax,dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)]
    760c120a 3bc7            cmp     eax,edi
    760c120c 7441            je      KERNELBASE!OutputDebugStringA+0x166 (760c124f)
    760c120e 6810270000      push    2710h
    760c1213 50              push    eax
    760c1214 e83666feff      call    KERNELBASE!WaitForSingleObject (760a784f)
    760c1219 3bc7            cmp     eax,edi
    760c121b 0f8570040000    jne     KERNELBASE!OutputDebugStringA+0xf3 (760c1691)
    760c1221 6870120c76      push    offset KERNELBASE!`string' (760c1270)
    760c1226 57              push    edi
    760c1227 6a02            push    2
    760c1229 e8df7bfeff      call    KERNELBASE!OpenFileMappingW (760a8e0d)
    760c122e 8985d0fdffff    mov     dword ptr [ebp-230h],eax
    760c1234 3bc7            cmp     eax,edi
    760c1236 0f85dea00000    jne     KERNELBASE!OutputDebugStringA+0x111 (760cb31a)
    760c123c 39bde0fdffff    cmp     dword ptr [ebp-220h],edi
    760c1242 750b            jne     KERNELBASE!OutputDebugStringA+0x166 (760c124f)
    760c1244 ff3528490e76    push    dword ptr [KERNELBASE!BaseDataFileHandleTableLock+0x4 (760e4928)]
    760c124a e81204feff      call    KERNELBASE!ReleaseMutex (760a1661)
    760c124f c745fc01000000  mov     dword ptr [ebp-4],1
    760c1256 c745fc02000000  mov     dword ptr [ebp-4],2
    760c125d 8b85ccfdffff    mov     eax,dword ptr [ebp-234h]

    As we could see from the disassembly, the byte value on 0x760c11d1 is 0x8b, let's change it to INT3:

    0:000> ?? *(char*)(0x760c11d1) = 0xcc
    char 0n-52 ''

    Before we can continue, let's make sure WinDBG is installed as the Just-In-Time debugger by running the following command from an Elevated Command Prompt:

    windbg.exe -IS

    Now we are ready to continue:

    0:000> .detach
    Detached
    NoTarget> q

    On the same time, Just-In-Time debugging is triggered and another WinDBG just pops up with the following information:

    (aa0.11cc): Break instruction exception - code 80000003 (!!! second chance !!!)
    KERNELBASE!OutputDebugStringA+0x74:
    760c11d1 cc              int     3

    Change the value on 0x760c11d1 back (or did you just forget the value?).

    0:000> ?? *(char*)(0x760c11d1) = 0x8b
    char 0n-117 ''

    Euphoria:

    KERNELBASE!DbgPrint:
    760c11cb ff251c130a76    jmp     dword ptr [KERNELBASE!_imp__DbgPrint (760a131c)]
    760c11d1 8b65e8          mov     esp,dword ptr [ebp-18h] ss:0023:0022fbb8=0022f97c
    760c11d4 33ff            xor     edi,edi
    760c11d6 89bdd0fdffff    mov     dword ptr [ebp-230h],edi

    I'll leave the rest things for you to figure out. Hints:

    1. You may want to use dda esp to dump the stack.

      0:000> dda esp
      0022f97c  4464c9b1
      0022f980  00000000
      0022f984  0022fbdc ""
      0022f988  7ffd4000 ""
      0022f98c  0000001e
      0022f990  008f573c "Hello, world! (first chance)."
      0022f994  002c00c4 ".,,"
      0022f998  00000000

    2. You would eventually see something like CreateDBWinMutex.
    3. When you encountered with OpenEvent or similar function call, you can use da to check the string parameter.

      0:000> da 760cb478
      760cb478  "DBWIN_BUFFER_READY"

    Now we have finished the debugging approach to OutputDebugString, it's time to start writing a simple user mode dbgview:


    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    #include <stdio.h>
    
    #define IfFalseRet(c) do{if(!(c)){return dwLastError = ::GetLastError();}}while(false)
    
    class CHandle
    {
    public:
      CHandle(HANDLE h = NULL): m_h(h)
      {
      }
      ~CHandle()
      {
        Release();
      }
      void Release()
      {
        if(*this)
        {
          ::CloseHandle(m_h);
        }
        m_h = NULL;
      }
      operator bool() const
      {
        return m_h != INVALID_HANDLE_VALUE && m_h != NULL;
      }
      operator HANDLE() const
      {
        return m_h;
      }
      CHandle& operator= (const HANDLE& h)
      {
        Release();
        m_h = h;
        return *this;
      }
      CHandle& operator= (CHandle& h)
      {
        if(this != &h)
        {
          HANDLE hSwap = m_h;
          m_h = h.m_h;
          h.m_h = hSwap;
          h.Release();
        }
        return *this;
      }
    private:
      HANDLE m_h;
    };
    
    LPCTSTR DBWIN_BUFFER = TEXT("DBWIN_BUFFER");
    LPCTSTR DBWIN_BUFFER_READY = TEXT("DBWIN_BUFFER_READY");
    LPCTSTR DBWIN_DATA_READY = TEXT("DBWIN_DATA_READY");
    LPCTSTR DBWIN_MUTEX = TEXT("DBWinMutex");
    
    #pragma pack(push, 1)
    struct CDBWinBuffer
    {
      DWORD dwProcessId;
      BYTE  abData[4096 - sizeof(DWORD)];
    };
    #pragma pack(pop)
    
    bool g_fContinue = true;
    
    BOOL CtrlHandler(DWORD fdwCtrlType)
    {
      switch(fdwCtrlType)
      {
      case CTRL_C_EVENT:
      case CTRL_CLOSE_EVENT:
      case CTRL_LOGOFF_EVENT:
      case CTRL_SHUTDOWN_EVENT:
        g_fContinue = false;
        return TRUE;
      }
      return FALSE;
    }
    
    int __cdecl main()
    {
      DWORD dwLastError = ERROR_SUCCESS;
    
      IfFalseRet(SetConsoleCtrlHandler((PHANDLER_ROUTINE)(CtrlHandler), TRUE) == TRUE);
    
      CHandle hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, DBWIN_MUTEX);
      if(!hMutex)
      {
        IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND);
        IfFalseRet(hMutex = CreateMutex(NULL, FALSE, DBWIN_MUTEX));
      }
      
      CHandle hEventBufferReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_BUFFER_READY);
      if(!hEventBufferReady)
      {
        IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND);
        IfFalseRet(hEventBufferReady = CreateEvent(NULL, FALSE, TRUE, DBWIN_BUFFER_READY));
      }
      
      CHandle hEventDataReady = OpenEvent(EVENT_MODIFY_STATE, FALSE, DBWIN_DATA_READY);
      if(!hEventDataReady)
      {
        IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND);
        IfFalseRet(hEventDataReady = CreateEvent(NULL, FALSE, FALSE, DBWIN_DATA_READY));
      }
    
      CHandle hFileMappingBuffer = OpenFileMapping(FILE_MAP_READ, FALSE, DBWIN_BUFFER);
      if(!hFileMappingBuffer)
      {
        IfFalseRet(GetLastError() == ERROR_FILE_NOT_FOUND);
        IfFalseRet(hFileMappingBuffer = CreateFileMapping(
          INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
          0, sizeof(CDBWinBuffer), DBWIN_BUFFER));
      }
    
      CDBWinBuffer* pDbgBuffer = (CDBWinBuffer*)(MapViewOfFile(
        hFileMappingBuffer, SECTION_MAP_READ, 0, 0, 0));
      IfFalseRet(pDbgBuffer);
    
      while(g_fContinue)
      {
        if(WaitForSingleObject(hEventDataReady, 100) == WAIT_OBJECT_0)
        {
          printf("%s", pDbgBuffer->abData);
          SetEvent(hEventBufferReady);
        }
      }
    
      UnmapViewOfFile(pDbgBuffer);
    
      return dwLastError;
    
    }

    Notes:

    1. You will need to run the sample from an Elevated Command Prompt.
    2. The debug buffer guarantees you that the string is NUL terminated (any string longer than 4091 will be truncated).
    3. To make the sample easier, I just pass pDbgBuffer->abData to printf without performing any decoding (I just treated them as ASCII string).
    4. DbgPrint is defined for each execution environment, for user mode this would be ntdll!DbgPrint. ntdll!DbgPrint would call into KdpTrap which would in turn output the bytes to the Debug Port. You may find it more convenient to use DbgPrint instead of OutputDebugString as DbgPrint supports string formatting, but be cautious with the potential breaking changes in NTDLL.

    Homework:

    1. As you might have discovered already, the underneath mechanism of OutputDebugString makes use of shared objects. What would happen if we forget to set the DBWIN_BUFFER_READY event?
    2. On 64bit Windows, the 32bit application runs under WoW64. Does the WoW64 own a separate set of OutputDebugString objects, or share with the 64bit environment?
    3. How would OutputDebugStringA|W be implemented using a same shared buffer?

    For the homework, you may find some hints from the WinDBG output, where I've already marked in color.

  • 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...)

  • Rubato and Chord

    x86 Segment Addressing Revisited

    • 0 Comments

    Memory segmentation was first introduced to x86 family with 8086, to make it possible to access 1MB physical memory under 16bit addressing mode.

    Real Mode

    Logical address points directly into physical memory location.

    Logical address consists of two parts: segment and offset.

    Physical address is calculated as Segment * 16 + Offset, and if the A20 line is not enabled, the physical address is wrapped around into 1MiB range.

    Protected Mode

    Two stages of address translation would be used to arrive at a physical address: logical-address translation and linear address space paging.

    Logical address consists of two parts: segment selector and offset.

    Segment selector is used to determine the base address as well as the access rights. Segment selelctors are maintained in a table known as GDT (Global descriptor-table) or LDT (Local descriptor-table), which is referenced by GDTR or LDTR register.

    When a thread is running in kernel mode, fs:[0] points to the PCR (Processor Control Region). Let's find it by manually translating the logical address:

    kd> r fs
    fs=00000030
    
    kd> .formats @fs
    Evaluate expression:
      Hex:     00000030
      Decimal: 48
      Octal:   00000000060
      Binary:  00000000 00000000 00000000 00110000
      Chars:   ...0
      Time:    Thu Jan 01 08:00:48 1970
      Float:   low 6.72623e-044 high 0
      Double:  2.37152e-322
    

    According to the x86 architecture specification, we have Index = 6 (0000000000110), Table Indicator = GDT (0) and RPL (Requested Privilege Level) = 0.

    kd> dd @gdtr
    8003f000  00000000 00000000 0000ffff 00cf9b00
    8003f010  0000ffff 00cf9300 0000ffff 00cffb00
    8003f020  0000ffff 00cff300 200020ab 80008b04
    8003f030  f0000001 ffc093df 00000fff 0040f300
    8003f040  0400ffff 0000f200 00000000 00000000
    8003f050  a0000068 80008954 a0680068 80008954
    8003f060  2f40ffff 00009302 80003fff 0000920b
    8003f070  700003ff ff0092ff 0000ffff 80009a40
    

    We can get the layout of segment descriptor from x86 architecture specification:

    Now we can verify the address translation by looking at the memory contents:

    kd> dg @fs
                                      P Si Gr Pr Lo
    Sel    Base     Limit     Type    l ze an es ng Flags
    ---- -------- -------- ---------- - -- -- -- -- --------
    0030 ffdff000 00001fff Data RW Ac 0 Bg Pg P  Nl 00000c93
    kd> dd fs:[0]
    0030:00000000  805495d0 80549df0 80547000 00000000
    0030:00000010  00000000 00000000 00000000 ffdff000
    0030:00000020  ffdff120 0000001c 00000000 00000000
    0030:00000030  ffff2050 80545bb8 8003f400 8003f000
    0030:00000040  80042000 00010001 00000001 00000064
    0030:00000050  00000000 00000000 00000000 00000000
    0030:00000060  00000000 00000000 00000000 00000000
    0030:00000070  00000000 00000000 00000000 00000000
    
    kd> dd ffdff000
    ffdff000  805495d0 80549df0 80547000 00000000
    ffdff010  00000000 00000000 00000000 ffdff000
    ffdff020  ffdff120 0000001c 00000000 00000000
    ffdff030  ffff2050 80545bb8 8003f400 8003f000
    ffdff040  80042000 00010001 00000001 00000064
    ffdff050  00000000 00000000 00000000 00000000
    ffdff060  00000000 00000000 00000000 00000000
    ffdff070  00000000 00000000 00000000 00000000

    It's time to dump the KPCR structure:

    kd> dt nt!_KPCR ffdff000
       +0x000 NtTib            : _NT_TIB
       +0x01c SelfPcr          : 0xffdff000 _KPCR
       +0x020 Prcb             : 0xffdff120 _KPRCB
       +0x024 Irql             : 0x1c ''
       +0x028 IRR              : 0
       +0x02c IrrActive        : 0
       +0x030 IDR              : 0xffff2050
       +0x034 KdVersionBlock   : 0x80545bb8 Void
       +0x038 IDT              : 0x8003f400 _KIDTENTRY
       +0x03c GDT              : 0x8003f000 _KGDTENTRY
       +0x040 TSS              : 0x80042000 _KTSS
       +0x044 MajorVersion     : 1
       +0x046 MinorVersion     : 1
       +0x048 SetMember        : 1
       +0x04c StallScaleFactor : 0x64
       +0x050 DebugActive      : 0 ''
       +0x051 Number           : 0 ''
       +0x052 Spare0           : 0 ''
       +0x053 SecondLevelCacheAssociativity : 0 ''
       +0x054 VdmAlert         : 0
       +0x058 KernelReserved   : [14] 0
       +0x090 SecondLevelCacheSize : 0
       +0x094 HalReserved      : [16] 0
       +0x0d4 InterruptMode    : 0
       +0x0d8 Spare1           : 0 ''
       +0x0dc KernelReserved2  : [17] 0
       +0x120 PrcbData         : _KPRCB
    
    kd> !pcr
    KPCR for Processor 0 at ffdff000:
        Major 1 Minor 1
    	NtTib.ExceptionList: 805495d0
    	    NtTib.StackBase: 80549df0
    	   NtTib.StackLimit: 80547000
    	 NtTib.SubSystemTib: 00000000
    	      NtTib.Version: 00000000
    	  NtTib.UserPointer: 00000000
    	      NtTib.SelfTib: 00000000
    
    	            SelfPcr: ffdff000
    	               Prcb: ffdff120
    	               Irql: 0000001c
    	                IRR: 00000000
    	                IDR: ffff2050
    	      InterruptMode: 00000000
    	                IDT: 8003f400
    	                GDT: 8003f000
    	                TSS: 80042000
    
    	      CurrentThread: 80552840
    	         NextThread: 00000000
    	         IdleThread: 80552840
    
    	          DpcQueue: 
    

     

  • 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

    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

    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

    Visualize Assembly using DGML

    • 0 Comments

    Starting from Visual Studio 2010 Ultimate there is a cool feature called DGML (Directed Graph Markup Language).

    I wrote a small script to convert the disassembled code from WinDBG into a DGML.

    In order to use it, simply type the following commands under a debug session:

    .shell -o LoadLibraryA.dgml -ci "uf kernel32!LoadLibraryA" cscript.exe /nologo dasm2dgml.js

    A DGML file will be generated with the given name, and here is what it looks like:

    Here is the source code:


    var EBB = [];
    
    var hypertext=function(s){
      var r=[],L=s.length;
      for(var i=0;i<L;i++){
        var c=s.charAt(i);
        switch(c){
          case '"':r.push('&quot;');break;
          case '&':r.push('&amp;');break;
          case '<':r.push('&lt;');break;
          case '>':r.push('&gt;');break;
          default:r.push(c);}}
      return r.join('');
    };
    
    var map=function(f,v){var L=v.length,r=[];for(var i=0;i<L;i++)r.push(f(v[i]));return r;};
    
    (function(){
      var blk;
    
      var CExtendedBasicBlock = function(name, previous, next){
        this.Address = '';
        this.Code = [];
        this.Name = name;
        this.Previous = previous;
        this.Next = next;
      };
    
      while(true)
      {
        if(WScript.StdIn.AtEndOfStream)
          break;
        var strSourceLine = WScript.StdIn.ReadLine().replace(/(^\s+)|(\s+$)/g, '');
        if(!strSourceLine)
          continue;
        if(strSourceLine.match(/.*:$/))
        {
          blk = new CExtendedBasicBlock(strSourceLine.slice(0, -1));
          EBB.push(blk);
        }
        else
        {
          blk.Address = blk.Address || strSourceLine.match(/^[^\s]+/)[0];
          blk.Code.push(strSourceLine.replace(/[^\s]*\s+/, '').replace(/[^\s]*\s+/, ''));
        }
      }
    })();
    
    EBB = EBB.sort(function(x, y){ return x.Address == y.Address ? 0 : x.Address > y.Address ? 1 : -1; });
    for(var i = 1; i < EBB.length; i++)
    {
      EBB[i].Previous = EBB[i - 1];
      EBB[i].Previous.Next = EBB[i];
    }
    
    WScript.Echo('<DirectedGraph Background="#FFFFFF" GraphDirection="TopToBottom" xmlns="http://schemas.microsoft.com/vs/2009/dgml">');
    WScript.Echo('  <Nodes>');
    map(function(blk){
      var content = hypertext(blk.Name + ' (' + blk.Address + ')') + '&#xD;&#xA;';
      map(function(instruction){
        content += '&#xD;&#xA;' + hypertext(instruction);
      }, blk.Code);
      WScript.Echo('    <Node Id="' + hypertext(blk.Name) + '" Label="' + content + '" />');
    }, EBB);
    WScript.Echo('  </Nodes>');
    WScript.Echo('  <Links>');
    map(function(blk){
      map(function(instruction){
        map(function(x){
          var idx = instruction.indexOf(x.Name);
          idx = idx >= 0 ? instruction.charAt(idx + x.Name.length) : -1;
          if(idx == '' || idx == ' ')
            WScript.Echo('    <Link Source="' + hypertext(blk.Name) + '" Target="' + hypertext(x.Name) + '" />');
        }, EBB);
      }, blk.Code);
      if(blk.Next && !(blk.Code[blk.Code.length - 1].match(/^[^\s]+/)[0] in {jmp: 0, ret: 0}))
        WScript.Echo('    <Link Category="FallThrough" Source="' + hypertext(blk.Name) + '" Target="' + hypertext(blk.Next.Name) + '" />');
    }, EBB);
    WScript.Echo('  </Links>');
    WScript.Echo('  <Styles>');
    WScript.Echo('    <Style TargetType="Node">');
    WScript.Echo('      <Setter Property="FontFamily" Value="Consolas" />');
    WScript.Echo('      <Setter Property="FontSize" Value="11" />');
    WScript.Echo('      <Setter Property="Background" Value="White" />');
    WScript.Echo('      <Setter Property="NodeRadius" Value="2" />');
    WScript.Echo('    </Style>');
    WScript.Echo('    <Style TargetType="Link">');
    WScript.Echo('        <Condition Expression="HasCategory(\'FallThrough\')" />');
    WScript.Echo('        <Setter Property="Background" Value="Red" />');
    WScript.Echo('        <Setter Property="Stroke" Value="Red" />');
    WScript.Echo('    </Style>');
    WScript.Echo('  </Styles>');
    WScript.Echo('</DirectedGraph>');
    

    Notes:

    1. This script cannot generate 100% accurate control flow diagram, you will have to do further analysis (e.g. jmp eax).
    2. I haven't got a chance to test under WOA (ARM32), so I leave it as a homework for our readers.

    Enjoy:)

  • Rubato and Chord

    Data Breakpoints

    • 2 Comments

    The Visual Studio debugger supports a kind of breakpoint called Data Breakpoint, sometimes it is also called watchpoint. Data breakpoint is architecture dependant, as it requires hardware support provided by CPU. For x86, this will be the DR (Debug Register).

    The following code demonstrates how to use the x86 debug register by implementing a very simple native debugger.


    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    #include <stdio.h>
    
    HMODULE    g_hModule;                            /* linear address of exe   */
    LPVOID     g_pOEP;                               /* original entry point    */
    BYTE       g_bBreakPoint;
    BYTE       g_bINT3 = 0xcc;                       /* debug break instruction */
    BOOL       g_fDebugRegisterSupported = FALSE;    /* hardware DR supported   */
    
    int WINAPI ExeEntry()
    {
      if(IsDebuggerPresent())
      {
        g_hModule = GetModuleHandle(NULL); /* HMODULE is always 4 bytes aligned */
        printf("HMODULE: %p\n", g_hModule /* insert a space here... */,
               *(INT32*)(g_hModule) /* read 4 bytes */);
      }
      else
      {
        CONTEXT context;
        context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
        PROCESS_INFORMATION processInformation;
        STARTUPINFO startupInfo = {sizeof(STARTUPINFO)};
        if(CreateProcess(NULL, GetCommandLine(), NULL, NULL, FALSE, DEBUG_PROCESS,
                         NULL, NULL, &startupInfo, &processInformation))
        {
          DWORD dwContinueDebugStatus = DBG_CONTINUE;
          while(dwContinueDebugStatus)
          {
            DEBUG_EVENT debugEvent;
            WaitForDebugEvent(&debugEvent, INFINITE);
            switch(debugEvent.dwDebugEventCode)
            {
            case CREATE_PROCESS_DEBUG_EVENT:
              g_pOEP = (LPVOID)(debugEvent.u.CreateProcessInfo.lpStartAddress);
              g_hModule = (HMODULE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage);
              CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
              printf("CREATE_PROCESS_DEBUG_EVENT @%p OEP=%p\n", g_hModule, g_pOEP);
              break;
            case EXCEPTION_DEBUG_EVENT:
              printf("EXCEPTION_DEBUG_EVENT PID=%d TID=%d @%p\n",
                     debugEvent.dwProcessId, debugEvent.dwThreadId,
                     debugEvent.u.Exception.ExceptionRecord.ExceptionAddress);
              GetThreadContext(processInformation.hThread, &context);
              switch(debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
              {
              case EXCEPTION_BREAKPOINT:
                if(debugEvent.u.Exception.dwFirstChance)
                {
                  if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP)
                  {
                    LPVOID IP = (LPVOID)(--context.Eip);
                    WriteProcessMemory(processInformation.hProcess, IP,
                                       &g_bBreakPoint, 1, NULL);
                    FlushInstructionCache(processInformation.hProcess, IP, 1);
                    context.Dr0 = (DWORD)(g_hModule);
                    context.Dr7 = 0x000f0101;
                  }
                  else
                  {
                    printf("\tbp $exentry\n");
                    ReadProcessMemory(processInformation.hProcess, g_pOEP,
                                      &g_bBreakPoint, 1, NULL);
                    WriteProcessMemory(processInformation.hProcess, g_pOEP,
                                      &g_bINT3, 1, NULL);
                    FlushInstructionCache(processInformation.hProcess, g_pOEP, 1);
                  }
                }
                break;
              case EXCEPTION_SINGLE_STEP:
                printf("EXCEPTION_SINGLE_STEP DR6=%08X\n", context.Dr6);
                g_fDebugRegisterSupported = TRUE;
                break;
              }
              context.Dr6 = 0;
              SetThreadContext(processInformation.hThread, &context);
              break;
            case EXIT_PROCESS_DEBUG_EVENT:
              dwContinueDebugStatus = 0;
              printf("EXIT_PROCESS_DEBUG_EVENT\n");
              break;
            case LOAD_DLL_DEBUG_EVENT:
              CloseHandle(debugEvent.u.LoadDll.hFile);
              break;
            }
            ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId,
                               dwContinueDebugStatus);
          }
          CloseHandle(processInformation.hThread);
          CloseHandle(processInformation.hProcess);
        }
        printf("Debug Register Test: %s\n", g_fDebugRegisterSupported ?
               "Hardware DR supported" : "Hardware DR not supported");
      }
      return ERROR_SUCCESS;
    }
    

    To compile the source code, you may use Visual Studio or either of the following compilers (x86 32bit):

    cl.exe watchpoint.cpp kernel32.lib msvcrt.lib /GS- /link /ENTRY:ExeEntry /NODEFAULTLIB /SUBSYSTEM:CONSOLE

    gcc.exe -fno-exceptions -fno-rtti -s -Os -o watchpoint watchpoint.cpp -Wl,--stack,65536


    A few things to mention:

    1. Data breakpoints respect the CPU working mode, which means the linear address is used if paging enabled, and physical address used if paging disabled.
    2. Each CPU core has its own set of debug registers. This wouldn't be a problem for user mode as the operating system maintains context switching, but it will be very different if you are implementing kernel mode driver which runs above dispatch level, or custom interrupt vector.
    3. Virtual machine sometimes does not implement DR, this is true for Virtual PC and VMware if you don't have hardware virtualization enabled.
    4. To make the sample easier, our trivial debugger assumes we only have one debugee and the debugee is single-threaded. For the real debugger, PID and TID should be used to get the correct handle (and should have it cached) in order to support multi-threaded debugee, as well as debugging multiple programs in a single debugger instance.
    5. A little modification is required in order to support 64bit:
                    if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP)
                    {
      #if defined(_M_IX86)
                      LPVOID IP = (LPVOID)(--context.Eip);
      #elif defined(_M_X64)
                      LPVOID IP = (LPVOID)(--context.Rip);
      #endif
                      WriteProcessMemory(processInformation.hProcess, IP,
                                         &g_bBreakPoint, 1, NULL);
                      FlushInstructionCache(processInformation.hProcess, IP, 1);
      #if defined(_M_IX86)
                      context.Dr0 = (DWORD)(g_hModule);
      #elif defined(_M_X64)
                      context.Dr0 = (DWORD64)(g_hModule);
      #endif
                      context.Dr7 = 0x000f0101;
                    }
      
    6. ExeEntry is used as the entry point instead of the one provided by the C Runtime, which is not a good practice, as this might cause subtle CRT initialization problem. The main reason of doing this is that CRT initialization code reads from the module header just as we did, which would also trigger our watchpoint. To verify this:
      1. Change the following code by inserting a blank space:
            printf("HMODULE: %p\n", g_hModule /* insert a space here... */,
                   *(INT32*)(g_hModule) /* read 4 bytes */);
      2. After the change, your code would look like:
            printf("HMODULE: %p\n", g_hModule /* insert a space here... * /,
                   *(INT32*)(g_hModule) /* read 4 bytes */);
      3. Now change ExeEntry to int __cdecl main(), and recompile without the /ENTRY:ExeEntry flag.
      4. Run and see what happened, on my machine, this would look like:
        CREATE_PROCESS_DEBUG_EVENT @00250000 OEP=00251978
        EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @77DD04F6
                 bp $exentry EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @00251978
        EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @002518CA
        EXCEPTION_SINGLE_STEP DR6=FFFF0FF1
        EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @0025104F
        EXCEPTION_SINGLE_STEP DR6=FFFF0FF1 HMODULE: 00250000
        EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @67D278FC
        EXCEPTION_SINGLE_STEP DR6=FFFF0FF1
        EXIT_PROCESS_DEBUG_EVENT
        Debug Register Test: Hardware DR supported

    Homework:

    1. What if multiple watchpoints (either Read, Write or Execute, for 1, 2 or 4 bytes) were added to a single address?
    2. What if the target address is not aligned?
    3. What if the data access triggers a GPF (General Protection Fault)?
    4. What would happen if a watchpoint was applied on an INT3?
    5. Why are we setting the watchpoint when the debugee reaches OEP, instead of setting it at the first DebugBreak?
    6. You might have noticed that we decreased EIP by 1 when we received the EXCEPTION_BREAKPOINT debug event, why? What would happen if the interrupt was caused by opcode CD 03 instead of CC?

    For the homework, you would want to either check the Intel x86 specification and do verification by playing around the code.

  • Rubato and Chord

    Windows 8 and conhost.exe

    • 1 Comments

    While debugging a console application on Windows 8, I noticed the console application is trying to create a process in the very beginning:

    windbg.exe -xe ld:ntdll.dll -c "bm ntdll!*CreateProcess*; g; k" cmd.exe

    CommandLine: cmd.exe
    ModLoad: 000007ff`01d60000 000007ff`01f1e000   ntdll.dll
    ntdll!RtlUserThreadStart:
    000007ff`01d7c3d0 4883ec48        sub     rsp,48h
    Processing initial command 'bm ntdll!*CreateProcess*; g; k'
    0:000> bm ntdll!*CreateProcess*; g; k
      1: 000007ff`01d90f60 @!"ntdll!RtlCreateProcessParametersEx"
      2: 000007ff`01d63070 @!"ntdll!NtCreateProcessEx"
    breakpoint 2 redefined
      2: 000007ff`01d63070 @!"ntdll!ZwCreateProcessEx"
      3: 000007ff`01e1bf74 @!"ntdll!RtlCreateProcessReflection"
      4: 000007ff`01da8bb4 @!"ntdll!RtlpCreateProcessRegistryInfo"
      5: 000007ff`01e1ceac @!"ntdll!RtlCreateProcessParameters"
      6: 000007ff`01d63651 @!"ntdll!ZwCreateProcess"
    breakpoint 6 redefined
      6: 000007ff`01d63651 @!"ntdll!NtCreateProcess"
    Breakpoint 1 hit
    Child-SP          RetAddr           Call Site
    000000bf`8268e558 000007fe`feea02a4 ntdll!RtlCreateProcessParametersEx
    000000bf`8268e560 000007fe`feea00be KERNELBASE!ConsoleLaunchServerProcess+0x60
    000000bf`8268e5f0 000007fe`fee95d40 KERNELBASE!ConsoleAllocate+0xf6
    000000bf`8268e8c0 000007fe`fee7f6db KERNELBASE!ConsoleInitialize+0x1d1
    000000bf`8268e950 000007fe`fee7230d KERNELBASE!KernelBaseBaseDllInitialize+0x4dd
    000000bf`8268ec20 000007ff`01d6b9be KERNELBASE!KernelBaseDllInitialize+0xd
    000000bf`8268ec50 000007ff`01d8b3fc ntdll!LdrpCallInitRoutine+0x3e
    000000bf`8268eca0 000007ff`01d8a88b ntdll!LdrpInitializeNode+0x192
    000000bf`8268eda0 000007ff`01d8e74e ntdll!LdrpInitializeGraph+0x6f
    000000bf`8268ede0 000007ff`01d8c322 ntdll!LdrpInitializeGraph+0x8d
    000000bf`8268ee20 000007ff`01d8cc02 ntdll!LdrpPrepareModuleForExecution+0x1a5
    000000bf`8268ee70 000007ff`01d8337b ntdll!LdrpLoadDll+0x344
    000000bf`8268f0a0 000007ff`01d9264f ntdll!LdrLoadDll+0xa7
    000000bf`8268f120 000007ff`01d91826 ntdll!LdrpInitializeProcess+0x1664
    000000bf`8268f420 000007ff`01d7c1ae ntdll!_LdrpInitialize+0x1565e
    000000bf`8268f490 00000000`00000000 ntdll!LdrInitializeThunk+0xe

    0:000> dc rbx
    000000bf`8268e660  00000000 00000000 00000000 00000000  ................
    000000bf`8268e670  003f005c 005c003f 003a0043 0057005c  \.?.?.\.C.:.\.W.
    000000bf`8268e680  004e0049 004f0044 00530057 0073005c  I.N.D.O.W.S.\.s.
    000000bf`8268e690  00730079 00650074 0033006d 005c0032  y.s.t.e.m.3.2.\.
    000000bf`8268e6a0  006f0063 0068006e 0073006f 002e0074  c.o.n.h.o.s.t...
    000000bf`8268e6b0  00780065 00200065 00780030 00660066  e.x.e. .0.x.f.f.
    000000bf`8268e6c0  00660066 00660066 00660066 00000000  f.f.f.f.f.f.....
    000000bf`8268e6d0  8268e960 000000bf 00000008 00000000  `.h.............

    This means conhost.exe process on Windows 8 will be created by the console application itself, instead of the CSRSS. And conhost.exe would always have the native bitness (on Windows 8 64bit version, only 64bit version of conhost.exe is available).

    Now debug into conhost.exe using .childdbg, it's pretty clear that conhost.exe is in charge of drawing the console window, handling user inputs and communicate with the console application:

    0  Id: 124c.d34 Suspend: 1 Teb: 000007f6`3311b000 Unfrozen
    Child-SP          RetAddr           Call Site
    00000094`85aefb38 000007f6`33b91146 ntdll!NtWaitForSingleObject+0xa
    00000094`85aefb40 000007ff`00c9167e conhost!ConsoleIoThread+0xda
    00000094`85aefd80 000007ff`01d7c3f1 KERNEL32!BaseThreadInitThunk+0x1a
    00000094`85aefdb0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
    #  1  Id: 124c.1428 Suspend: 1 Teb: 000007f6`3311e000 Unfrozen
    Child-SP          RetAddr           Call Site
    00000094`85b6fd28 000007ff`0140171e conhost!ConsoleWindowProc
    00000094`85b6fd30 000007ff`014014d7 USER32!UserCallWinProcCheckWow+0x13a
    00000094`85b6fdf0 000007f6`33b92fcc USER32!DispatchMessageWorker+0x1a7
    00000094`85b6fe70 000007ff`00c9167e conhost!ConsoleInputThread+0xd2
    00000094`85b6fed0 000007ff`01d7c3f1 KERNEL32!BaseThreadInitThunk+0x1a
    00000094`85b6ff00 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

    And cmd.exe itself doesn't draw the console window at all:

    0  Id: 1aec.5ec Suspend: 1 Teb: 000007f7`b280d000 Unfrozen
    Child-SP          RetAddr           Call Site
    00000008`50fff5b8 000007fe`fee8f17c ntdll!NtDeviceIoControlFile+0xa
    00000008`50fff5c0 000007fe`fef0bb29 KERNELBASE!ConsoleCallServerGeneric+0x118
    00000008`50fff710 000007fe`fef0b986 KERNELBASE!ReadConsoleInternal+0x131
    00000008`50fff850 000007f7`b3621025 KERNELBASE!ReadConsoleW+0x1a
    00000008`50fff890 000007f7`b362bd3e cmd!ReadBufFromConsole+0x111
    00000008`50fff960 000007f7`b3604aae cmd!_chkstk+0x3820
    00000008`50fffae0 000007f7`b36042e4 cmd!Lex+0x4be
    00000008`50fffb50 000007f7`b362d560 cmd!Parser+0x128
    00000008`50fffba0 000007f7`b361b721 cmd!_chkstk+0x5032
    00000008`50fffc00 000007ff`00c9167e cmd!mystrchr+0x27d
    00000008`50fffc40 000007ff`01d7c3f1 KERNEL32!BaseThreadInitThunk+0x1a
    00000008`50fffc70 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

    The interesting thing is that if we use Spy++, it would report that the console window is associated with the main thread of cmd.exe process! I believe this is a hack in the underlying implementation of GetWindowThreadProcessId for backward compatibility. Also, Spy++ cannot be used to inspect conhost.exe message loop.

    Due to the side effects of IFEO Debugger, console application would fail to start if IFEO Debugger is enabled for conhost.exe.

    The following call stack showed conhost.exe just created a normal window from Console Input Thread:

    USER32!CreateWindowExW
    conhost!CreateWindowsWindow+0x105
    conhost!InitWindowsSubsystem+0x69
    conhost!ConsoleInputThread+0x22
    KERNEL32!BaseThreadInitThunk+0x1a
    ntdll!RtlUserThreadStart+0x1d
    0:001> du @rdx
    000007f6`33b9d460  "ConsoleWindowClass"
    0:001> du @r8
    00000025`f7dc37c0  "C:\WINDOWS\SYSTEM32\cmd.exe"

    When cmd.exe exits, the Console I/O Thread would be notified:

    ntdll!NtTerminateProcess+0xa
    ntdll!RtlExitUserProcess+0xb6
    conhost!ConsoleIoThread+0xac4
    KERNEL32!BaseThreadInitThunk+0x1a
    ntdll!RtlUserThreadStart+0x1d

    (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).
Page 1 of 4 (35 items) 1234