Thus far we have seen startup activities performed by any Windows process; and except for setting a local variable to TRUE if the process is .NET before the code entered LdrpInitializeProcess, there has not been much to distinguish Wilderland.EXE from any other native program. However, that is about to change in a bit. Slide the disassembly listing down until you locate a call to _RtlDoesFileExists_U (sic) followed by a call to _RtlFreeHeap. Let the debugee continue until the statement after RtlFreeHeap has returned.
0x77F5C8C0: CALL _RtlFreeHeap@12 ; (0x77F5156B) 0x77F5C8C5: MOV EDI,DWORD PTR [EBP-0x4] <<<<<<<<<<< Run to here. 0x77F5C8C8: XOR ESI,ESI 0x77F5C8CA: TEST BYTE PTR [EBX+0x69],0x1 0x77F5C8CE: JNZ 0x77F7D387 0x77F5C8D4: CMP BYTE PTR [EBP+0x78],0x0 0x77F5C8D8: JNE 0x77F7D395 [for SP2:] 0x7C9222A9: CALL _RtlFreeHeap@12 ; (0x7C91043D) 0x7C9222AE: MOV ESI,DWORD PTR [EBP-0xFC] 0x7C9222B4: TEST BYTE PTR [EBX+0x69],0x1 ; <==0x7C9221DF(*-0xD5) 0x7C9222B8: JNZ 0x7C93EB70 ; (*+0x1C8B8) 0x7C9222BE: CMP BYTE PTR [EBP+0x14],0x0 ; <==0x7C93EB7B(*+0x1C8BD) 0x7C9222C2: JNE 0x7C9338B6 ; (*+0x115F4)
Now, carefully single-step over the next few instructions until you encounter the second compare and jump combination. Note that the leftmost column in the JNE instruction now displays a "v" sign, and at the bottom of the disassembly window the word "JUMP" is now showing. These are two indicators that the code is about to take a slight detour as a result of testing the .NET process variable that was set so long ago. If you have enabled the jump/call target bubble popup feature you may see briefly in the hint a call to LdrpCorValidateImage -- well, this looks interesting! Single-stepping to and over the call to LdrpCorValidateImage creates a short pause in my debugger and, if you look carefully at the status bar located at the bottom of the main window, you will find the phrase, "REFRESH INDEX" -- refresh the Index now by press View/Refresh Index. You may have also noticed that several notifications were added to the Debug Log window. The call to this routine caused a number of new DLLs to be loaded:
MSCOREE.DLL KERNEL32.DLL ADVAPI32.DLL RPCRT4.DLL [for SP2:] MSCOREE.DLL ADVAPI32.DLL KERNEL32.DLL RPCRT4.DLL
Windows spelunkers might find the order of the above DLLs a bit odd. There is a good reason -- MSCOREE was loaded before KERNEL32 (and with SP2 before ADVAPI32 and only then KERNEL32!). Switching our attention over the Index now, we locate the node for MSCOREE.DLL and expand it and its "Imports" node. The explanation now becomes clear -- the loader code first loaded MSCOREE, which because of its dependencies caused the loading of KERNEL32 and ADVAPI32, which in addition also caused RPCRT4 to load as well. The "DelayLoad" dependencies have not been loaded and will later be added to the process when code in MSCOREE first requires them (or another DLL's dependency triggers the load).
After a test of the return code from LdrpCorValidateImage, the loader code checks the image base of the loading process, and after determining that it is the same as the ImageBase field in the PEB (prove this to yourself if you wish be repeating the UDS operation explained above), it skips over some error-handling code only to run into a call to _LdrpCorReplaceStartContext. This is a short routine, so let's see what it does. Stepping into the routine we see that it is taking the contents of a global variable named CorExeMain and is changing a variable in the stack to this value. Expand the "Debug" and "Debug Symbols" nodes for NTDLL and scroll down until you find the token, "_CorExeMain" and double-click it (see Figure 7).
Figure 7
If we check the registers window, ECX will at some point contain the value of the first DWORD in the memory window:
ECX: 0x7917D08C (mscoree.dll:__CorExeMain@0) [for SP2:] ECX: 0x788110A1 (mscoree.dll:_CorExeMain)
Select the ECX item and bring up the popup command Disassemble mscoree.dll and open a new disassembly window. Add a breakpoint on the first instruction of this routine (Edit/Add Breakpoint or F9) and return from this short routine (Double-clicking the ECX line causes the creation of a memory dump window instead of a disassembly; this is because PEBrowse Interactive will not know at this point which display is correct so it defaults to the memory display). You can close the new disassembly window now, but if you wish to check to see that the breakpoint has been set, select the command View/Breakpoints (or Ctrl+N or locate the breakpoints button along the top) and you will now be presented with the window you see in Figure 8:
Figure 8
From this window you can create conditional breakpoints and enable/disable breakpoints as well.
The code now returns to the mainline where the normal call to LdrpLoadDll will attempt to load KERNEL32 again. Note that after stepping over this call there is no indicator to refresh the Index because everything has already been loaded (and refreshed). We are now going to skip over the final activities before each DLL's initialization code is called, so activate the disassembly window and perform a find on the token, "LdrpRunInitializeRoutines." Run to this line. If some of you are following this tutorial along on your own systems, you may be surprised at this point by the sudden appearance of the disassembly for DbgBreakPoint. If that is the case, then your configuration has the Debug tab sheet checkbox for "Reset IsDebuggerPresent flag?" unchecked and the Exceptions tab sheet checkbox for "Break on an embedded INT3/DbgBreakPoint?" checked. This is the so-called "loader breakpoint" and will normally be the first breakpoint notification received by most debuggers. Select Debug/Go (or F5) and the debugger will stop on the call to LdrpRunInitializeRoutines.
| | 1st page | next page |