Learning Goals:
- Practice the use of WinDbg for intercepting DLL loading
- Use IMM Python scripts to log DLL loading sequence
- Understand the stealthy DLL loading/hickjacking technique by malware
Applicable to:
- Operating Systems
- Assembly Language
- Operating System Security
1. Introduction
The following is my favorite from Rumsfield about malware analysis, although he is not a computing professional:
"
There are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns – there are things we do not know we don't know."
----- Donald Rumsfield
This tutorial discusses one stealthy DLL loading behavior of Max++. It is something that we know we don't know. The analysis starts from
0x3C230F.
2. Lab Configuration
We could simply reuse the lab configuration of
Tutorial 20. In general, you need two windows XP instances, one for taking notes and the other for running the malware. Also you need a WinDbg instance sitting on the host to debug the win_debug instance. Specifically, in the win_debug instance, you need to set a breakpoint at
0x3C230F. This is the place right before the call of CALL WS2_32.WSASocketW, as shown in Figure 1 below.
|
Figure 1. Something that we do not know that we do not know |
As shown in Figure 1, at 0x3C230F, it is a call to
WS2_32.WSASocketW, which tries to establish a TCP/IP socket. Interestingly, if you try to step it by F8, you might notice that your IMM will freeze for about 10 seconds and on its status bar, it is stating: trying to access
http://74.117.114.86\max++.x86.dll.
This is clearly not a good sign, why does the standard WSASocketW call need to invoke a remote DLL? There must be something that
we do not know that we do not know!
3.IMM Debugger Scripts for Logging the Stack Frames When DLL Loading
Our first attempt will be the immunity debugger itself. If you look at the Pycommands folder in the IMM system directory, you will notice that there are many interesting examples for controlling the Immunity Debugger using Python scripts! We are interested in exploring the following:
(1) When are DLL loaded (specifically, why and when is max++.x86.dll loaded?)
(2) Who issues the call to load DLLs?
To answer these two questions, we rely on two features of the IMM Python library: (1) functions that allow us to print out stack frames, so that we could trace back to caller; and (2) event hookers that allow us to log the information when a DLL is loaded. Based on this information we could further explore the issue using breakpoints.
In the following, find the source code of "
TraceDLLLoad.py". You can simply place it in the
root directory of IMM, and start the Python winodw in IMM (second button in the toolbar), and type "
from TraceDLLLoad import *" and then "
hk = TraceDLLLoad(); " and "
hk.add('abc');" to set up the hook.
from immlib import *;
from immutils import *;
HOOK_NAME = "testbplog"
imm = Debugger();
class TraceDLLLoad(LoadDLLHook):
def __init__(self):
LoadDLLHook.__init__(self)
def run(self, regs):
ev = imm.getEvent();
if ev.isLoadDll(): imm.log(" it is LOADDLL!---");
imm.log("!!!!!!!! DLL Loaded: %x" % ev.lpImageName);
baseaddr = ev.lpBaseOfDll;
imm.log("!!!!!!!! DLL image: %x" % ev.lpBaseOfDll);
regs = imm.getRegs();
call_stack = imm.callStack()
for frame in call_stack:
imm.log("Addr= %08x | Stack = %08x | Procedure %s | Frame %08x | Called from %08x" \
%(frame.address, frame.stack, frame.procedure, frame.frame, frame.calledfrom))
The logic of the above script is pretty clear: it overrides the LoadDLLHook class in IMM, and whenenver a DLL is loaded, it prints out the stack contents.
The dump of the log window is shown in Figure1, the image base of every DLL file is shown in Figure 2 (by clicking View -> Executable Modules in IMM).
|
Figure 1. DLL Load Log |
|
Figure 2. DLL Image Base |
Observe Figure 1, you might have some interesting observation. First of all, while loading WSASocket, the system loads many DLLs (most likely by reading the imports of DLLs and load them recursively). The list includes old32.dll, mswsocket.dll etc. What's interesting to us is that
74.117.114.86\max++.x86.dll is loaded at the end (with wshtcpip.dll), while the most astounding discoveries we have so far is: the system actually loads (and then unloads) another module:
\\.\C2CAD9724#4079...\L\max++.00.x86! It is unloaded before max++.x86.dll is loaded! So that we don't have a trace of it in the visible executable module list in Figure 2! (
as we will show you later, actually the new max++.x86.dll is loaded by \\.\C2CAD...\max++.00.x86 and the new DLL takes its address space 0x35670000).
4. First Attempt: Break on LdrLoadDll
We now explore the use of IMM breakpoint to drill into the loading process of DLLs. We use
\\.\C2CAD9724...\L\max++.00.x86 as an example.
Our first attempt would be to simply set a breakpoint at ntdll.LdrLoadDLL. This can be achieved using the following steps. In
IMM -> View -> Executable Modules, find ntdll.dll and right click on it and then select
View->Names. All functions exported from ntdll will be displayed in alphabetical order. Find the
LdrLoadDll and set a breakpoint on it (F2). Go back to the CPU pane and press F9, we would be able to see that LdrLoadDll has been called multiple times. Figure 3 shows one of the stack frames of the call:
|
Figure 3. LdrLoadDll Parameters |
.Doing a simple Google search, we can easily find out the function definition of ntdll.LdrLoadDll. It has four parameters: (1) PathToFile, (2) Flags, (3) ModuleFileName, (4) handle. We are clearly interested in parameter 3
ModuleName, whose type is _Unicode_String. Observe the stack contents in Figure 3, we would easily find out that the
ModuleFileName value is
0x0012CE8C. (where the first highlighted area is the folder of max++, which corresponds to the first parameter).
Right click on
0x112CE8C and select "follow in dump", we have the RAM contents starting from
0x0012CE8C. From the _Unicode_String documentation, we can soon follow that the first word (0x3E0040) indicates the max and the actual length of the unicode string, and the second word
0x7FFDEC00 contains the starting address of the actual string (in wide char). Go to 0x7FFDEC00, we have the contents shown in Figure 4:
c:\windows\system32\mswsock.dll.
|
Figure 4. String Contents of the DLL File Name |
Using the similar approach, we find that the following DLLs are loaded in sequence:
mswsock.dll, \\.\C2CAD....\L\max++.00.x86, hnet.cfg.dll, mswsock.dll (again), wshtcpip.dll, SXS.dll
Notice that the system might generate some random behavior, e.g., sometimes the SXS.dll in the sequence is replaced by wstheme.dll.This could be caused by random behavior of Max++ (but we have not verified in its code).
It is also interesting to know
who calls LdrLoadDLL. Figure 5 shows more contents of the stack when the mswsock.dll is first loaded. It is not hard to see the call chain:
LoadLibraryExA -> LoadLibraryExW->LdrLoadDll, which loads mswsock.dll.
|
Figure 5. More Stack Contents for Call Chain of LdrLoadDll |
If we compare this sequence with the sequence shown in Figure 2, we might notice that there are two DLLs that are not captured using the brekapoint on LdrLoadDll: (1) the
old32.dll, and (3) the
max++.x86.dll! Clearly,
this approach does not work for max++.x86.dll. But it should allow us to find who loads
\\?\C2CAD....\L\max++.00.x86 instead!
Challenge 1. use the above technique to find out who loads max++.00.x86.
5. Second Attempt: Break on ntdll.KiFastSyscall
Our second approach is to break on
KiFastSyscall. This is motivated by the IMM trace log in Figure 2. It seems that IMM is capturing the call of loading DLLs by stopping at KiFastSyscall. In fact, KiFastSyscall is essentially a wrapper of core OS system in Windows. If you look at its implementation, you might notice that it sets up the service code and parameters in registers, and then invokes software interrupt.
By setting a breakpoint at KiFastSyscall, we hope to get a better idea of how DLLs are loaded. Figure 6 below shows the stack contents of one of the KiFastSyscalls we intercepted. By looking at the stack contents, you might immediately infer that this is to perform
zwQueryAttributesFile for mswsock.dll.
|
Figure 6. Syscall for zwQueryAttributeFile |
The breakpoint will be hit multiple times for LdrLoadDll. The sequence of the calls are shown below:
zwFsControlFile, zwQueryAttributesFile, zwOpenFile, zwCreateSection, zwClose, zwMapViewOfSection, zwClose.
Now we might have a good picture of the DLL loading: the system needs to read the DLL file, then
creates a section to map the new DLL into the original address space of the process.
6. Who loads max++.00.x86?
We now use the technique shown in section 4 to check who loads
max++.00.x86. We will show the various delicate attempts that finally accomplish the goal.
6.1 Bottom-up Approach
One natural thought would be to set a breakpoint at
LdrLoadDll and then trace back along the stack frame. The lab configuration is pretty simple: Once reaching
0x3C230F, set a breakpoint at ntdll.LdrLoadDll press
F9, and when we hit LdrLoadDll twice, we are at the call to load
\\.\C2CAD..\max++.00.x86. By using the technique of Section 4, you can observe the memory dump as shown in Figure 7.
On the left of Figure 7, it is the string
\\.\C2CAD..\max++.00.x86, and on the right, you can see that the Unicode string starts at
0x0012C4AC (second highlighted area on the right).
|
Figure 7. Bottom-up Analysis |
Now if we look at the right side of Figure 7 (stack contents), we see a lot of
candidates for return address (note that Figure 7 does not show all the addresses, you need to scroll down in your IMM to see all of them). If we chain them up, we have a chain of
conjecture of return addresses shown in Figure 8. Note that we call them "conjecture" because it's just a guess -- there is no reason that the malware will follow the standard C language calling convention, and thus all addresses (in the range of code segments of various modules) are regarded as
candidates of return addresses. In Figure 8, areas with bold fonts are the ones verified by step-by-step execution in IMM. The ones with
yellow background are actually not return addresses!
Let us observe figure 8 (from the bottom), the logic is pretty clear: lz32 loads ws_32 module for network socket service, which calls load library to load mwsock32.dll. During the loading process, somehow, it calls zwMapViewOfSection, which loads library \\.\C2CAD...\max++.00.x86.
There must be some tricks about why zwMapViewOfSection needs to load \\.\C2CAD..\max++.00.x86? We need to rely on further debugging to figure it out.
----------------------------------------------------------------------------------------------------------
---->
ntdll.7C801BBD (stored at 0x0012C484)
---> 0x0038005C (stored at 0x0012C4B0)
--->
ntdll.7C80AEEC (part of LoadLibraryExW of \\.\C2CAD...\max++.00.x86) (stored at 0x0012C4EC)
-->
0x003800CE (stored at 0x0012C4F0)
-->
0x003800CE (stored at 0x0012C500)
-->
0x0038005C (stored at 0x0012C504 )
--->
0x7C90E437 (
KiDispatchUserAPC, stored at 0x0012C528 )
---> 0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5E0 )
--->
0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
--->
ntdll.0x7C90D50C (stored at 0x0012C804 )
--->
ntdll.0x7C91BD03 (zwMapViewOfSection)(stored at 0x0012C808 )
--->
ntdll.0x7C91624A (stored at 0x0012C8FC )
--->
ntdll.0x7C801BBD (part of ntdll.LoadLdrDll of mwsock32.dll) (stored at 0x0012CE64)
--->
... a lot of ntdll calls
-->
ws2_32.71AB78F1 [this is where ws2_32 calls load library) (stored at 0x0012CEFC)
--> ...
--->
lz32.0x003C2315 (this is the instruction right after 0x003C230F) (stored at 0x0012D314).
Figure 8. Conjecture of Return Addresses in Stack
----------------------------------------------------------------------------------------------------
Let us first verify the actual return addresses in stack. This time, we use step by step execution (F8) instead of
execute until return Ctrl+F9 (because it is unreliable). The following is the sequence of call chains we verified so far. You can verify that some "guessed" return addresses in Figure 8 are fake. Another interesting observation is that: when we get into KiDispatchUserAPC(7c90E437), we are not able to continue because the next instruction is
CALL zwContinue (see Figure 10), which switches threads.
----------------------------------------------------------------------------------------------
LdrLoadDrll
---->
ntdll.7C801BBD (stored at 0x0012C484)
---->
ntdll.7C80AEEC (LoadLibraryExW of max++.00.x86) (stored at 0x0012CE4C)
----->
0x003800CE (stored at 0x0012C500)
-----> ntdll.7C90E437 (
KiDispatchUserAPC) (stored at 0x0012C528)
Figure 9. Stack Frame Verification by Execution
---------------------------------------------------------------------------------------------------
|
Figure 10. Code of KiDispatchUserAPC |
According to MSDN documentation,
KiDispatchUserAPC is to handle a user APC (asynchronous procedure call), which is usually a queued time consuming I/O request. When it finishes the task, it calls
zwContinue to resume the original thread's execution (i.e., it performs a context switch). Clearly, pressing F8 here will not work, because it's not going to hit the next instruction (in KiDispatchUserAPC) any more. Instead, we have to figure out what is the next instruction that the OS will resume. Looking at the documentation of zwContinue (or NtContinue), we found that it takes two parameters: pContenxt which points to a thread context structure and bRaiseAlert. By looking at the stack contents, we can infer that pContext's value is 0x0012C538, then we can easily display all of its contents using WinDbg (simply start a user level WinDbg and attach it to the Max++ process in
non-invasive mode):
0:000> dt _CONTEXT 0x0012C538
ntdll!_CONTEXT
+0x000 ContextFlags : 0x10017
+0x004 Dr0 : 0x4012dc
+0x008 Dr1 : 0
+0x00c Dr2 : 0
+0x010 Dr3 : 0
+0x014 Dr6 : 0xffff0ff0
+0x018 Dr7 : 0x401
+0x01c FloatSave : _FLOATING_SAVE_AREA
+0x08c SegGs : 0
+0x090 SegFs : 0x3b
+0x094 SegEs : 0x23
+0x098 SegDs : 0x23
+0x09c Edi : 0
+0x0a0 Esi : 0
+0x0a4 Ebx : 0
+0x0a8 Edx : 0x7c90e4f4
+0x0ac Ecx : 0x900000
+0x0b0 Eax : 0
+0x0b4 Ebp : 0x12c8f8
+0x0b8 Eip : 0x7c90e4f4
+0x0bc SegCs : 0x1b
+0x0c0 EFlags : 0x296
+0x0c4 Esp : 0x12c804
+0x0c8 SegSs : 0x23
+0x0cc ExtendedRegisters : [512] "???"
You can see that the next EIP is
0x7C90E4F4, and (you can also see Dr0: 0x4012DC, which is a hardware breakpoint which we set before). Now we can set up a breakpoint at 0x7C90E4F4 and continue the step-by-step execution analysis. We can verify that the following return addresses from Figure 8 are visited:
--->
0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
--->
ntdll.0x7C90D50C (stored at 0x0012C804 )
--->
ntdll.0x7C91BD03 (zwMapViewOfSection)(stored at 0x0012C808 )
--->
ntdll.0x7C91624A (stored at 0x0012C8FC )
--->
ntdll.0x7C801BBD (part of
ntdll.LoadLdrDll of ws2help.dll) (stored at 0x0012CE64)
So far we have verified all the return addresses, let's revisit them in Figure 8. Anything suspicious? Look at the following about those in red.
-----------------------------------------------------------------------------------------------------------
---> ntdll.7C80AEEC (part of LoadLibraryExW of \\.\C2CAD...\max++.00.x86) (stored at 0x0012C4EC)
--> 0x003800CE (stored at 0x0012C4F0)
--> 0x003800CE (stored at 0x0012C500)
--> 0x0038005C (stored at 0x0012C504 )
---> 0x7C90E437 (KiDispatchUserAPC, stored at 0x0012C528 )
---> 0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5E0 )
---> 0x7C90E4F4 (ntdll.KiFastSyscallRet) (stored at 0x0012C5F0 )
---> ntdll.0x7C90D50C (stored at 0x0012C804 )
---> ntdll.0x7C91BD03 (zwMapViewOfSection)(stored at 0x0012C808 )
----------------------------------------------------------------------------------------------------------
The first question is:
why does zwMapViewOfSection calls LoadLibraryExW?
The second question is:
what is the address range of 0x003800CE? This looks like the addr range of some kernel. Also, if it is a kernel module, how come we could trace into it?
The two questions are clearly related. We suspect that 0x003800CE is some malicious kernel modules loaded by Max++. In the following, we have to look into the challenge.
6.2 Stealthy Memory Segment at 0x00380000
Figure 11 shows the list of memory segments of the system when we are hitting the breakpoint at LdrLoadDll for \\.\C2CAD...\max++.00.x86.
|
Figure 11. Stealthy Memory Segment at 0x00380000 |
Note that this memory segment is very stealthy! When we hit the LdrLoadDll the first time (for loading mwsock32.dll), it is not there. Later, when we get back to the completion of zwMapViewOfSection (along the call chain in Figure 8), it disappears! From the memory map, we are not able to tell who is the owner of the segment 0x00380000!
If we start a user level WinDbg and attach it to the max++ process, we could run the following command for finding the information of address 0x00380000, which does not yield more information than IMM.
0:000> !address 0x00380000
Failed to map Heaps (error 80004005)
Usage: <unclassified>
Allocation Base: 00380000
Base Address: 00380000
End Address: 00381000
Region Size: 00001000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000040 PAGE_EXECUTE_READWRITE
Our last report would be the kernel model WinDbg. The lab configuration is similar to
Section 2, except that the windows image is started in the
DEBUGGED mode and a
kernel WinDbg is attached from the host machine. Then run to
0x003C203F, we can start our analysis in kernel mode WinDbg (in the host system).
Press
Ctrl+Break in WinDbg to get into the command mode. Our first step is to find out and attack to the process using the kernel mode WinDbg.
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 812ed020 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00039000 ObjectTable: e1000d10 HandleCount: 239.
Image: System
...
PROCESS ff9d9020 SessionId: 0 Cid: 0130 Peb: 7ffde000 ParentCid: 07f8
DirBase: 0999d000 ObjectTable: e1aa7bc0 HandleCount: 40.
Image: Max++ downloader install_2010.exe
kd> .process
80559c20 ...
We find that that the process id of Max++ is ff9d9020. The current process (kernel) is 80559c20. Then we attach to it.
kd> .process ff9d9020
Implicit process is now ff9d9020
WARNING: .cache forcedecodeuser is not enabled
kd> dd 00380000
00380000 ???????? ???????? ???????? ????????
00380010 ???????? ???????? ???????? ????????
00380020 ???????? ???????? ???????? ????????
00380030 ???????? ???????? ???????? ????????
00380040 ???????? ???????? ???????? ????????
00380050 ???????? ???????? ???????? ????????
00380060 ???????? ???????? ???????? ????????
00380070 ???????? ???????? ???????? ????????
As shown by the above, segment 0x00380000 has not been created yet. But we could set a memory breakpoint on the address, when it is being written, and see what we get.
kd> ba w1 0x003800CE
kd> bl
0 e 003800ce w 1 0001 (0001)
Note that 0x003800CE is the return address as shown in Figure 8. We are interested in finding out who sets up the code at that address. here "
ba w1 0x003800CE" means to set a memory breakpoint at 0x003800CE whenever that byte is overwritten. Now type "g" (meaning continue) in GDB and F9 in the IMM, we have the breakpoint hit.
B48DADF8+0x105f:
faea505f f3a5 rep movs dword ptr es:[edi],dword ptr [esi]kd> r edi
edi=003800d0
kd> r esi
esi=faea5408
Clearly, the code is trying to copy from 0xFAF3D408 to 0x003800D0. Now the interesting part: who owns code located at 0xFAF3D05F (the rep moves instruction)? Doing an !address 0x003800CE yields nothing. Looking at the prompt "B48DADF8+0x105f", we know that the code belongs to a module named B48DADF8. This can be confirmed by:
kd> lm
start end module name
804d7000 806ed680 nt (pdb symbols) c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
f7612000 f761b000 HIDCLASS (deferred)
f7682000 f76ac180 kmixer (deferred)
f76b9000 f76bb880 hidusb (deferred)
f76bd000 f76bff80 mouhid (deferred)
faea4000 faea9000 B48DADF8 (no symbols)
faf24000 faf2c000 _ (deferred)
Unloaded modules:
fad9c000 fada5000 HIDCLASS.SYS
fafec000 fafef000 hidusb.sys
...
Clearly B48DADF8 is a kernel module loaded earlier by Max++. But when is it loaded? We leave this as a challenge to you.
Challenge 1. Find out when B48DADF8 is loaded (hint: enable the event handler of WinDbg for watching module loads and combine it with the analysis of stack).
6.3 Conclusion about Max++.00.x86
lz32.dll loads
B48DADF8 as a kernel module and modifies the system service table for handling zwMapViewSection. B4DADF8 then creates the memory segment 0x00380000 in Max++, and resets the kernel handling table for zwMapViewSection. When zwMapViewSection tries to SYSENTER, it jumps to 0x003800CE, which issues a call of LoadLibraryW("max++.00.x86"). This is how max++.00.x86 is loaded. Notice that it is achieved via remote kernel module loading and redirection of system call - this avoids it be discovered by prevalent virus detectors.
However, this approach does have some vulnerability. Notice that SYSENTER is supposed to enter kernel code. The malware author intends to be lazy here and directly calls the user level ntdll.LoadLibrary and ntdll.LdrLoadDll, which actually allows us to trace to unknown/newly created memory region 0x380000, which eventually leads to the discovery of B48DADF8 module. Had the malware author embed all the load library code in kernel, it would be much more difficulty to find it out.
7. Who Loads the Remote http://74.117.114.86\max++.x86.dll?
We now try to analyze who loads
http://74.117.114.86/max++.x86.dll. As shown in Figure 2, the LoadDllHook is not able to capture the loading of a remote DLL, however IMM does see its loading. We need to find another way out. Again, we will resort to the kernel mode WinDbg.
Before we proceed, keep in mind that the
\\.\C2CAD...\max++.00.x86 is loaded in the
0x35670000 range. This is easily visible in IMM -> View -> Executable Modules.
7.1 Lab Instructions
Now start the lab setting following the instructions in
Section 6.2 (kernel mode WinDbg), then run in IMM until you see that IMM shows that it is trying to load http://74.117.114.86/max++.x86.dll in its status bar (as shown in the red boxed area in Figure 12).
Make sure to cut off the internet of the WinXP image (so that the effort to load 74.117.114.86/max++.x86.dll will fail). Now press
Ctrl + Break in the kernel WinDbg to stop the running.
|
Figure 12. Intercepting Load of remote DLL |
7.2 Analysis
Now we are interested in figuring out the status of all related processes in the system. Type
!process 0 0 will give us the list of all existing processes. We now have the process "address" (regard it process ID) of Max++ is 81180228. We could then display all of its details, as shown in the following. It's long, but easy to interpret.
Take a moment to read and guess: there are two threads. Which thread loads 74.117.1147.86/max++.x86.dll?
kd> !process 81180228 7
PROCESS 81180228 SessionId: 0 Cid: 0214 Peb: 7ffd4000 ParentCid: 01ac
DirBase: 01652000 ObjectTable: e1070918 HandleCount: 51.
Image: Max++ downloader install_2010.exe
...
THREAD 811807f0 Cid 0214.01e4 Teb: 7ffdf000 Win32Thread: e10c6118 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
8118098c Semaphore Limit 0x2
...
Stack Init f7d10000 Current f7d0fb68 Base f7d10000 Limit f7d0c000 Call 0
Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 16
ChildEBP RetAddr Args to Child
f7d0fb80 804dc0f7 81180860 811807f0 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f7d0fb8c 804dc143 8118095c 811807f0 81180824 nt!KiSwapThread+0x46 (FPO: [0,0,0])
f7d0fbb4 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f7d0fbcc 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
f7d0fc14 804ecae9 00000000 00000000 00000000 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
f7d0fc2c 804dc143 00000000 811807f0 e1767f58 nt!KiSwapThread+0x64 (FPO: [0,0,0])
f7d0fc54 8058f9db 00000001 00000000 00000001 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f7d0fd10 80587a0f 00185f18 0012c608 0012c610 nt!NtSecureConnectPort+0x662 (FPO: [Non-Fpo])
f7d0fd3c 804de7ec 00185f18 0012c608 0012c610 nt!NtConnectPort+0x24 (FPO: [Non-Fpo])
f7d0fd3c 7c90e4f4 00185f18 0012c608 0012c610 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f7d0fd64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012c6f8 00000000 00000000 00000000 00000000 0x7c90e4f4
THREAD 81228da8 Cid 0214.07c0 Teb: 7ffde000 Win32Thread: e171a6f0 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
...
Win32 Start Address 0x35671d82
Start Address 0x7c8106e9
Stack Init f76e4640 Current f76e40f0 Base f76e5000 Limit f76e1000 Call f76e464c
Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 0
ChildEBP RetAddr Args to Child
f76e4108 804dc0f7 81228e18 81228da8 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f76e4114 804dc143 81228f14 81228da8 81228ddc nt!KiSwapThread+0x46 (FPO: [0,0,0])
f76e413c 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f76e4154 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
f76e419c 806f4c0e 00000000 00000000 f76e41b4 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
f76e41c0 804dcce5 badb0d00 f76e4238 00000000 0x806f4c0e
f76e4230 8065b4d0 ffffff00 f76e424c 8065b4da nt!ZwFlushInstructionCache+0x11 (FPO: [3,0,0])
f76e423c 8065b4da 00000000 00000000 f76e42fc nt!DbgkpSendApiMessage+0x50 (FPO: [Non-Fpo])
f76e424c 80610d25 f76e4268 00000001 81180228 nt!DbgkpSendApiMessage+0x5a (FPO: [Non-Fpo])
f76e42fc 8057d8f2 e1d43d10 5ad70000 00000000 nt!DbgkMapViewOfSection+0xba (FPO: [Non-Fpo])
f76e4374 804de7ec 000000c8 ffffffff 009ff318 nt!NtMapViewOfSection+0x31a (FPO: [Non-Fpo])
f76e4374 7c90e4f4 000000c8 ffffffff 009ff318 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76e43a4)
009ff334 00000000 00000000 00000000 00000000 0x7c90e4f4
Although the first thread is doing some network connections, the second thread (
81228da8) is actually the one which loads the remote DLL. Let's take a deeper look in WinDbg. We need to first properly set up the context (specify the current process and thread being inspected to be 81180228 and 81228da8, thus, when we speak of a virtual address, it is for the process we want). Then we display the stack contents of thread 81228da8 using command "
kv", as shown in the following.
kd> .process 81180228
Implicit process is now 81180228
WARNING: .cache forcedecodeuser is not enabled
kd> .thread 81228da8
Implicit thread is now 81228da8
kd> kv
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
f76e4108 804dc0f7 81228e18 81228da8 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f76e4114 804dc143 81228f14 81228da8 81228ddc nt!KiSwapThread+0x46 (FPO: [0,0,0])
f76e413c 804f8ca9 00000000 00000005 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
f76e4154 804f1718 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
f76e419c 806f4c0e 00000000 00000000 f76e41b4 nt!KiDeliverApc+0x124 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
f76e41c0 804dcce5 badb0d00 f76e4238 00000000 0x806f4c0e
f76e4230 8065b4d0 ffffff00 f76e424c 8065b4da nt!ZwFlushInstructionCache+0x11 (FPO: [3,0,0])
f76e423c 8065b4da 00000000 00000000 f76e42fc nt!DbgkpSendApiMessage+0x50 (FPO: [Non-Fpo])
f76e424c 80610d25 f76e4268 00000001 81180228 nt!DbgkpSendApiMessage+0x5a (FPO: [Non-Fpo])
f76e42fc 8057d8f2 e161b338 5ad70000 00000000 nt!DbgkMapViewOfSection+0xba (FPO: [Non-Fpo])
f76e4374 804de7ec 000000c8 ffffffff 009ff318 nt!NtMapViewOfSection+0x31a (FPO: [Non-Fpo])
f76e4374 7c90e4f4 000000c8 ffffffff 009ff318 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76e43a4)
009ff334 7c91624a 00189ee0 009ff3c0 009ff8e8 0x7c90e4f4
009ff5f4 7c9164b3 00000000 00189ee0 009ff8e8 0x7c91624a
009ff89c 7c801bbd 00189ee0 009ff8e8 009ff8c8 0x7c9164b3
009ff904 7e428055 009ff968 00000000 00000008 0x7c801bbd
009ff930 7c90e453 009ff940 00000068 00000068 0x7e428055
009ffe48 7e42e442 00000000 0000c038 009ffee0 0x7c90e453
009ffef4 7e42d0d6 00000000 0000c038 009ffee0 0x7e42e442
009fff30 77512f04 00000000 0000c038 774ed2a4 0x7e42d0d6
009fff7c 774ff041 00000000 00000002 7c97b2b0 0x77512f04
009fff9c 77502a62 00181358 00000002 009fffec 0x774ff041
009fffac 35671d93 00000000 7c80b713 00000000 0x77502a62
009fffec 00000000 35671d82 00000000 00000000 0x35671d93
Now, we could have the flow of funtion calls, first it's
35671D93 --> 77502A62 ---> ... 7C91624A ---> NtMapViewOfSection ---> DbgkMapViewOfSection ---> DbgkpSendApiMessage.
Note the first
35671D93 (this is a part of
max++.00.x86!). Again, look back at Figure 12,
35671D8D is actually the reall caller and 35671D93 is the return address. At 35671D8D, it is calling
ole32.CoInitialize, which initializes the compartment environment for OLE32 objects (which reorganizes the address space and loads a new DLL named "uwtheme.dll"). However, during the loading process, the remote DLL is loaded. What happened?
Let's try to delve more into the call sequence above. It is clear that CoInitialize loads DLL (called LdrLoadDll on "uwtheme.dll" and then maps it into the current processes's address space using NtMapViewOfSection). NtMapViewOfSection instead calls DbgkMapViewOfSection, which is the "debugged version" of NtMapViewOfSection, because it finds that there is a debugger attached and actually lets the debugger to load the DLL (so IMM should have the ability to monitor the loading of all DLLs!). The mission is accomplished by sending a debug API command via the debug port, via calling
DbgkpSendApiMessage. Then a software trap is generated, the IMM debugger should be responsible for handling that ApiMessage (i.e., to load the DLL).
The natural step next is to inspect the status of IMM debugger. Similarly, using "!process 0 0" we can find the process id (address) of IMM and inspect its threads and stack dump as below.
kd> !process 81181da0 7
PROCESS 81181da0 SessionId: 0 Cid: 01ac Peb: 7ffdf000 ParentCid: 05f8
DirBase: 08b8e000 ObjectTable: e1c38850 HandleCount: 139.
Image: ImmunityDebugger.exe
...
THREAD 8117e748 Cid 01ac.075c Teb: 7ffde000 Win32Thread: e1023598 WAIT: (UserRequest) KernelMode Non-Alertable
8119f7a4 SynchronizationEvent
IRP List:
ffbdff68: (0006,0094) Flags: 00000884 Mdl: 00000000
Not impersonating
...
ChildEBP RetAddr Args to Child
f7d5f868 804dc0f7 8117e7b8 8117e748 804dc143 nt!KiSwapContext+0x2e (FPO: [Uses EBP] [0,0,4])
f7d5f874 804dc143 00000000 8119f778 8119f7a4 nt!KiSwapThread+0x46 (FPO: [0,0,0])
f7d5f89c fa96392e 00000000 00000006 00000000 nt!KeWaitForSingleObject+0x1c2 (FPO: [Non-Fpo])
WARNING: Frame IP not in any known module. Following frames may be wrong.
f7d5f998 80564b0d 00000080 c00000be f7d5fa04 0xfa96392e
f7d5fa04 804e37f7 812c4b28 ffbdff68 ffbdff68 nt!SepPrivilegeCheck+0x8d (FPO: [Non-Fpo])
f7d5faf4 80563fec 812c4b28 00000000 ffb776a8 nt!IopfCallDriver+0x31 (FPO: [0,0,0])
f7d5fb7c 805684da 00000000 f7d5fbbc 00000040 nt!ObpLookupObjectName+0x56a (FPO: [Non-Fpo])
f7d5fbd0 805745a3 00000000 00000000 bf823d01 nt!ObOpenObjectByName+0xeb (FPO: [Non-Fpo])
f7d5fd54 804de7ec 0022e278 0022e250 0022e2a4 nt!NtQueryAttributesFile+0xf1 (FPO: [Non-Fpo])
f7d5fd54 7c90e4f4 0022e278 0022e250 0022e2a4 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f7d5fd64)
0022e2a4 7c916f13 0022e2b4 00000001 003c003a 0x7c90e4f4
0022e2bc 7c9172f3 7c97bec8 00000001 0022e5cc 0x7c916f13
0022e2dc 7c916023 002d5330 7c97bec8 00000000 0x7c9172f3
0022e5a4 7c916866 002d5330 0022e5cc 0022e600 0x7c916023
0022e620 7c916698 00000001 002d5330 00000000 0x7c916866
0022e63c 7c801d23 002d5330 00000000 0022e668 0x7c916698
0022e6a4 77c013bd 002e4758 00000000 00000002 0x7c801d23
0022e6f4 77c01a28 002e4758 0022e854 015374e0 0x77c013bd
0022e718 0049715a 015374e0 0022e854 01536960 0x77c01a28
0022e878 004977a3 015374e0 01537894 00000014 0x49715a
0022f548 00453e01 0022fe44 00000000 00000000 0x4977a3
0022ffa8 80581c9f 7c90dc9c 7c817064 fffffffe 0x453e01
00230018 00000000 00000014 00000001 00000006 nt!CcPfBeginAppLaunch+0x19f (FPO: [Non-Fpo])
The thread
with id 8117e748 shown in the above dump is the thread responsible for handling the DbgApiMsg (and thus needs to load the remote DLL). We can verify this by looking at the parameters of the function calls in the call stack. As shown by the
MSDN documentation, the function prototype of NtQueryAttributesFile is:
NTSTATUS NtQueryAttributesFile(
__in POBJECT_ATTRIBUTES ObjectAttributes,
__out PFILE_BASIC_INFORMATION FileInformation
);
We are interested in figuring out its first parameter, as shown in the following:
kd> dt _OBJECT_ATTRIBUTES 0022e278
nt!_OBJECT_ATTRIBUTES
+0x000 Length : 0x18
+0x004 RootDirectory : (null)
+0x008 ObjectName : 0x0022e29c _UNICODE_STRING "\??\UNC\74.117.114.86\max++.x86.dll"
+0x00c Attributes : 0x40
+0x010 SecurityDescriptor : (null)
+0x014 SecurityQualityOfService : (null)
Bingo, now the IMM is tricked into loading the remote DLL located at 74.117.114.86. But the question is:
Challenge 2. Since ole32.CoInitialize originally intends to load wstheme.dll, why is 74.117.114.86\max.x86.dll loaded?
(hints: search for string "max++.x86.dll" in "\\.\C2CAD...\max++.00.x86" and set data breakpoints for it and find out how it is used)