Tuesday, May 29, 2012

Malware Analysis Tutorial 28: Break Max++ Rootkit Hidden Drive Protection

Learning Goals:
  1. Practice WinDbg for Intercepting Driver Loading
  2. Practice IMM for Modifying Binary Code
  3. Trace and Modify Control Flow Using IMM
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction

One typical feature of Max++ is its ability to hide malicious files in a hidden drive. In this tutorial, we show you how to modify the malware itself to break its hidden drive protection. Our goal is to dig out the B48DADFD.sys from the hidden drive. Recall that B48DADFD.sys is used to load a series of malicious DLL files, introduced in Tutorial 27,

2. Preliminaries: Who Has Loaded B8DADFD.sys?
We need to figure out who loads B48DADFD.sys first. Simply follow the instructions (1) to (4) of Tutorial 19 to set up the lab experiment. Once the WinDbg is started, click Debug->Event Filter to enable the "Load Module" event (as shown in Figure 1). Then, after you hit the BP at 0x401417 in IMM, press SHIFT+F9 to let it go. The first module you capture will be "_" (the one which hacks lz32.dll) and the second hit will get you B48DADFD.sys.

Figure 1. Enable Event Filter on Module Loading

As shown in the following, when WinDbg hit DebugService2+0x10, it's the handler for module load event. If you list all current modules using "lm", you will notice that B48DADF8 is the module being loaded. We can further dump the stack contents to find out the caller.

kd> g
80506d3e cc              int     3
kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
fadfc000 fae01000   B48DADF8   (deferred)            
faed4000 faedc000   _          (deferred)            

Unloaded modules:
f77c4000 f77ef000   kmixer.sys
f7b6e000 f7b99000   kmixer.sys
fb206000 fb207000   drmkaud.sys
f7b99000 f7bbc000   aec.sys
f7c89000 f7c96000   DMusic.sys
facec000 facfa000   swmidi.sys
fb0f0000 fb0f2000   splitter.sys
fad6c000 fad77000   imapi.sys
fad5c000 fad6b000   redbook.sys
fae6c000 fae71000   Cdaudio.SYS
fb050000 fb053000   Sfloppy.SYS
fae64000 fae69000   Flpydisk.SYS
fae5c000 fae63000   Fdc.SYS
kd> kv
ChildEBP RetAddr  Args to Child             
f76a251c 80506d80 f76a25b4 f76a2530 00000003 nt!DebugService2+0x10 (FPO: [Non-Fpo])
f76a2540 805a3113 f76a25b4 fadfc000 ffffffff nt!DbgLoadImageSymbols+0x42 (FPO: [Non-Fpo])
f76a26e4 805a7c9d f76a2800 00000000 00000000 nt!MmLoadSystemImage+0xa80 (FPO: [Non-Fpo])
f76a2894 804de7ec 00000036 f76a2974 0000001c nt!NtSetSystemInformation+0x8a9 (FPO: [Non-Fpo])
f76a2894 804dd98d 00000036 f76a2974 0000001c nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76a28a8)
*** ERROR: Module load completed but symbols could not be loaded for *
f76a2918 faed50c7 00000036 f76a2974 0000001c nt!ZwSetSystemInformation+0x11 (FPO: [3,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
f76a299c faed6b93 ffbd2478 ffb23550 003f005c _+0x10c7
f76a2a34 faed6bf8 ffbd2478 ffb23550 ffb97840 _+0x2b93
f76a2a4c 804e37f7 ffa337b0 ffbd2468 ffbd2468 _+0x2bf8
f76a2a5c 8056c712 ffa33798 ffaad86c f76a2c04 nt!IopfCallDriver+0x31 (FPO: [0,0,0])
f76a2b3c 80563fec ffa337b0 00000000 ffaad7c8 nt!IopParseDevice+0xa12 (FPO: [Non-Fpo])
f76a2bc4 805684da 00000000 f76a2c04 00000040 nt!ObpLookupObjectName+0x56a (FPO: [Non-Fpo])
f76a2c18 8056cbeb 00000000 00000000 160de801 nt!ObOpenObjectByName+0xeb (FPO: [Non-Fpo])
f76a2c94 8056ccba 0012d624 00100000 003d3150 nt!IopCreateFile+0x407 (FPO: [Non-Fpo])
f76a2cf0 8056cdf0 0012d624 00100000 003d3150 nt!IoCreateFile+0x8e (FPO: [Non-Fpo])
f76a2d30 804de7ec 0012d624 00100000 003d3150 nt!NtCreateFile+0x30 (FPO: [Non-Fpo])
f76a2d30 7c90e4f4 0012d624 00100000 003d3150 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ f76a2d64)
0012d648 003c2507 00401166 003c0000 fffffffe 0x7c90e4f4
0012ffd0 8054b6b8 0012ffc8 81148da8 ffffffff 0x3c2507
00413a40 ec81ec8b 0000030c d98b5653 f4b58d57 nt!ExFreePoolWithTag+0x676 (FPO: [Non-Fpo])
00413a4c f4b58d57 8bfffffd f45589c3 0000c0e8 0xec81ec8b
00413a50 8bfffffd f45589c3 0000c0e8 10c38300 0xf4b58d57
00413a54 f45589c3 0000c0e8 10c38300 0cf85d89 0x8bfffffd
00413a58 00000000 10c38300 0cf85d89 f3b58dff 0xf45589c3

As shown by the stack dump, it's the lz32.dll code (one instruction before 0x3C2507) that eventually triggers the load of B48DADFD.sys. The chain of calls are:

one instruction before 0x3C2507 (lz32.dll) --> ... --> IoCreateFile --> ... --> IopfCallDriver ... ---> load B484DADFD.sys

Challenge 1. Use the analysis techniques presented in Tutorial 27 and analyze how B48DADFD is loaded.

3. Lab Configuration
Given that B48DADFD is loaded by one instruction before 3C2507, we can start our lab configuration to dig out the malicious driver file. The lab configuration is shown below:

First, follow the instructions (1) to (4) of  Tutorial 19.  When the breakpoint at 0x004017 is hit, jump to 0X3C2502 in the CPU pane and set a software breakpoint on it (by right clicking in CPU pane -> go to expression). You should see the binary code as shown in Figure 2 below.
Figure 2. Breakpoint Right Before Loading B48DADFD.sys

At this moment, we are now ready to take the B48DADFD out.

4. Surgery on Hidden Drive
As the hidden drive is not visible in Windows explorer, we have to call some functions available in the windows system library to get B48DADFD.sys out of the hidden drive. This is possible because Max++ hooks up the file system driver to handle special requests on the hidden drive. Our plan is to overwrite the instructions right after the instruction of 0x003C2502, and try to copy the file out.

Browsing the kernel32.dll (View -> Executable Modules -> ViewNames) gives us one good candidate, as shown in Figure 3.

Figure 3. kernel32.dll CopyFileA

By MSDN,the prototype of CopyFileA is defined as below: (where "A" stands for ASCII, i.e., how file name is encoded. You could see there are several variations in Figure 3, such as copyFileW).

  _In_  LPCTSTR lpExistingFileName,
  _In_  LPCTSTR lpNewFileName,
  _In_  BOOL bFailIfExists

So the following is our plan, starting at 0x003C250C, we can use IMM to modify the binary code and insert a instruction CALL 7C8286D6. Meanwhile, we will modify the stack contents so that the three parameters are arranged properly. We can first modify the instructions, as shown in Figure 4. Simply right click in CPU pane --> assemble (or press space bar), and type "CALL 7C8286D6") to modify the binary code.

Figure 4. Modify Binary Code in IMM

We now need to create the parameters for the CopyFileA call. We need to insert two strings in the data dump window, the general approach is shown in Figure 5. Right click -> Binary -> Edit. Make sure that only insert data when EIP is 0x003c2507 to avoid destructing the data for previous calls of Max++.

Figure 5. Modify Binary Code

The inserted data is shown in Figure 6. The file string "\\.\C2CAD...\B48DADF8.sys" is retrieved from the earlier analysis of the ntLoadImage in the stack dump in section 2. As shown in Figure 6, we inserted two pieces of strings, one is the "\\.\C2CAD...\B48DADF8.sys" and the other is "c:\bad.sys". Also the parameter setting is shown in the stack once we have entered the data in the memory dump pane. The third parameter (FailfExists) can be set to "false" (0).

Figure 6. The Input Parameters of CopyFileA

Execute one step in the CPU pane and we have successfully pulled out the malicious driver file out. See Figure 7.

Challenge 2. List all files in the hidden drive and take all of them out.

Figure 7. Success

Friday, May 25, 2012

Malware Analysis Tutorial 27: Stealthy Loading of Malicious DLL

Learning Goals:
  1. Practice the use of WinDbg for intercepting DLL loading
  2. Use IMM Python scripts to log DLL loading sequence
  3. Understand the stealthy DLL loading/hickjacking technique by malware
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. 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\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):

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

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

faea505f f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
kd> r edi
kd> r esi

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\max++.x86.dll?

We now try to analyze who loads 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 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 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 
   +0x000 Length           : 0x18
   +0x004 RootDirectory    : (null) 
   +0x008 ObjectName       : 0x0022e29c _UNICODE_STRING "\??\UNC\\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 But the question is:

Challenge 2. Since ole32.CoInitialize originally intends to load wstheme.dll, why is\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)