Pages

Tuesday, October 23, 2012

Malware Analysis Tutorial 34: Evaluation of Automated Malware Analysis Tools CWSandBox, PeID, and Other Unpacking Tools

Learning Goals:
  1. Understand Design Principles of Automated Malware Analysis Systems
  2. Hands-on Experiences with CWSandBox and Packer Identification Tools
Applicable to:
  1. Operating System Security
1. CWSandBox

We first examine the performance of another automated malware analysis tool CWSandBox (now named GFI Sandbox). The tool is available at http://www.gfi.com/malware-analysis-tool. CWSandBox uses dynamic API hook technique (DLL injection) to monitor system calls. This is similar to the approach taken by the Anubis system. In this article, we will examine the report generated by CWSandBox. We take a similar approach as the previous tutorial, and submit a modified version of Max++. Unfortunately, the server was not able to generate a report for us in 24 hours.

2. VirusTotal
We then submitted the modified version of Max++ to VirusTotal (http://www.virustotal.com). This is an integration site that runs multiple virus search/malware detection engines. We explicitly requested for an onsite start-from-scratch analysis of the modified version. Figure 1 shows the result from VirusTotal (many virus detection tools listed).

Figure 1. Results by Virus Total on Modified Max++
As shown in Figure 1, most signature based tool (including ClamAV) was not able to discover that the modified version of Max++ is a virus (note that the modified version is functionally equivalent to the original Max++. We only inserted two NOP instructions after the INT2D trick.). Among all the virus tools, DrWeb identified it in the virus familty of MaxPlus.6 and most others either identified it as Smiscer or Sirefef.

 Interesting, if we submit the original version of Max++ to virus total, we have the results shown in Figure 1.5. This time ClamAV is able to identify it as "Trojan.Dropper".

Figure 1.5. Results by Virus Total on Original Version of Max++


2. Packer Identification Tools
As introduced in Malware Analysis Tutorial 6- Self-Decoding and Self-Extracting Code Segment , Max++ has self-unpacking behaviors. It is interesting to see that if the tool has used any existing packers such as UPX. We used three packer identification tools: PeID, RDGPacker Detector, and ExeInfoPE to examine the modified version of Max++. All of these tools are freely available on the Internet.

PeID and RDGPacker did not find any known packers used to pack the Max++ code. Figure 2 and Figure 3 shows the running results of these tools.

Figure 2. Results by PeID

Figure 3. Result by RDGPacker Detector


Only ExeInfoPE reports that Max++ has 3 sections packed using similar algorithms like UPX (however, it does not precisely identify it's UPX). Figure 4 shows the report by ExeInfoPE.
Figure 4. Report by ExeInfoPE


The conclusion is that Max++ did not use any existing packers to directly pack its code. Its multiple layer packing algorithm is a customized algorithm (although it's not too complex).


Tuesday, September 18, 2012

Malware Analysis Tutorial 33: Evaluation of Automated Malware Analysis System I (Anubis)

Learning Goals:
  1. Practice Binary Patching
  2. Understand Design Principles of Automated Malware Analysis Systems
  3. Hands-on Experiences with Anubis Malware Analysis System
Applicable to:
  1. Operating System Security
1. Introduction

As we have examined the entire Max++/ZeroAccess binary code, it is now a great time to evaluate a number of automated malware analysis systems based on our knowledge. This tutorial reports our experiences with Anubis (formerly known as TTAnalyze), an automated malware analysis system. A webportal is provided by Anubis designers to submit Windows binary executables (http://anubis.iseclab.org/).

Anubis uses API hook to monitor the system calls placed by a malware (and the processes started by the malware). Equipped with a data structure parser, it is able to extract rich semantic information from the function parameters of system calls (such as registry values and file names). This allows the system to provide a detailed report of malware behaviors.

2. Run Benign Software on Anubis
To verify the functionality and validity of Anubis, we first submitted int2d.exe (used in our malware analysis tutorial 4) to Anubis. int2d.exe is a very simple executable that calls printf() to print two simple strings. As this is the first time that this binary executable is submitted to Anubis, it takes Anubis about 7.5 minutes to generate the report. Figure 1 presents the details of the report.

Figure 1. Anubis Analysis on int2d.exe


 As shown in Figure 1, int2d.exe is identified as a benign file. It does not generate any suspicious file, registry, or network activities.

3. First Submission of Max++.exe
We then proceed with the submission of Max++.exe. This time, it only takes Anubis 10 seconds to generate the report, because it pulls out the report from its repository based on the md5 checksum of the binary executable file. (i.e., Even if we change the file name, it's still the same report). As shown in Figure 2, the first time the binary of Max++ submitted to Anubis is November 2010.


Figure 2. Cached Report of Max++
Figure 3 shows the summary of Max++. It is reported that Max++ has suspicious file, registry, and process creation activities. Specifically, Max++ spawned two processes: dwwin.exe and drwtsn32.exe. This is interesting: because we have not observed such activities during our analysis.It is known that dwwin.exe and dwtsn32.exe are files related to Dr. Watson (a part of the microsoft error report/diagnosis/debugging utility). However, it is unknown in this case if these two files are already there on the Anubis system, or they are fake files downloaded by Max++. We need to drill down deeper into the report.
Figure 3. Summary of Max++



Anubis then reports the detailed activities performed by Max++ and the processes spawned by it. We now briefly examine each:

Runtime DLL Activities: As shown in Figure 4, Anubis successfully discovered that lz32.dll is loaded at run time, however, no further information about the hijack and reloading of DLL contents.

Figure 4. Run-time DLL Activities
Registry Activities: only read values are reported, no write-values.

File Activities. Figure 5 shows the file activities reported by Anubis (note the highlighted area). This is quite different from our own manual analysis. First of all, Anubis did not notice that the file and device drivers are modified by Max++ and thus all operations on the hidden drive (and corresponding file mapping activities) are not discovered by Anubis. Secondly, the reported activities (such as creation of a temporary file and modification of PIPE\lsarpc [used for Local Security Authority]) were not noted in our analysis. We suspect that these activities were made by the drwtsn32.exe.

Figure 5. File Activities


Process Activities. Figure 6 shows the process activities. Again, this is completely different from our manual analysis. Note that Max++ has foreign memory regions written (see highlighted area in Figure 6). Our best guess is that this is similar to the injection of remote thread of an existing process (smss.exe) in our analysis (see Tutorial 15). Because the process is randomly picked, it is possible that the drwtsn.exe is chosen at run time. However, the intended behavior, i.e., to remove the max++_download_2010.exe is not reported by Anubis.
Figure 6. Process Activities

Structured Exceptions. Figure 7 shows Anubis report on the SEH activities. Anubis has  captured one of many Max++'s SEH activities, however, did not perform in-depth analysis of the exception handler after the exception is triggered. For example, the one in Tutorial 11 is not reported.
Figure 7. Structured Exceptions.

Conclusion: it is unclear to us at this moment: if the Dr. Watson service is hijacked by Max++, or it's simply launched because Max++ crashed, as Anubis report also includes a screenshot of the Dr. Watson service screenshot (see Figure 8). To clarify this issue, we have to enforce Anubis to have a new run of Max++ (instead of retrieving the reports based on its MD5 checksum).

Figure 8. Dr. Watson Screenshot


4. Second Trial. Anubis Report on Modified Max++
To enforce the execution of Max++ on Anubis, we have to change the binary executable slighly, so that the Max++ executable does not have the same MD5 (which is already stored in Anubis). Follow the instructions below to modify Max++:

Figure 9. Modify Max++ Binary Executable

(1) Pick one instruction that will never be executed (e.g., the one at 0x00413BD7 in Figure 9).
(2) Right click in code pane -> Assemble -> NOP.
(3) Right click -> Copy to Executable ->All Modified -> Copy All ->  click "close" button on the right top of the dialog and then select "save".

Then upload the modified Max++ to Anubis. It will now take over 8 minutes to generate the report. Interestingly, the report is exactly the same. This seems to confirm that Max++ did not complete in Anubis, and it was stopped early by the drwtson32.exe.




Wednesday, August 15, 2012

Malware Analysis Tutorial 32: Exploration of Botnet Client


Learning Goals:
  1. Practice WinDbg for Inspecting Kernel Data Structure
  2. Use Packet Sniffer to Monitor Malware Network Activities
  3. Understand Frequently Used Network Activities by Malware
  4. Expose Hidden/Unreachable Control Flow of Malware
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction

This tutorial explores the botnet client part of Max++. We assume that you have completed tutorial 31 where our lab setting directly results from its analysis. You should have the following before the analysis, as shown in Figure 1.

Figure 1. Process Network Message

As shown in Figure 1, there is a big loop of calling zwReplyWaitReceivePortEx, which tries to wait for a message from the port. EAX represents the return code of the function, e.g., 0x0 represents OK and 0x102 represents time-out. If time-out, it calls function 0x35671D03. Otherwise, it proceeds to process the message.

Max++ first reads the 2nd word in the message, this is supposed to be an integer between 1 and 3. For case 3, it calls function 0x3567162D. for case 1, it calls function 0x3567204F. Note that all the four parameters of 0x3567204F are from the msg (5th, 7th, 8th and 10th words from the message respectively).

2. Execute Remote Command (#1).
Now let's delve into function 0x3567204F. Again, notice that this function gets executed ONLY WHEN the second word of the remote network message is 0x1. Figure 2 shows its function body.

Figure 2. Mysterious Function 0x3567204F

As shown in Figure 2, one of the input parameters of the function is ESI register (pointing to 0x00181580). This is a mysterious data structure. We suspect that it is the COM interface of the fake JScript COM object (the remote max++.x86.dll). The COM interface wraps a C++ object so that the program (Max++) can call the functions available in the DLL.

Next, the function clears memory region 0x009FFEE8, and copies the first four parameters passed in the stack to this region. Notice that these four parameters are the 5th, 7th, 8th, and 10th words in the remote message. Then it saves 0x009FFFE8 to EBP-10.

Max++ then proceeds to call some function of the remote DLL (see the second highlighted area in Figure 2). It seems to be taking out the virtual function table of the object and it invokes the function located at offset 0x18. This function takes at least 7 parameters, and they are [ESI+18], [ESI+20], 0x356782A4, 0, 1, [EBP-20], and [EBP-10]. Here [ESI+18] and [ESI+20] must be some data attributes of the COM interface object, and [EBP-20] is a pointer to collect result. From the VariantClear call (see the last highlighted area of figure 2), we can infer that [EBP-20] is a pointer to VARIANTARG. Lastly notice that [EBP-10] stores 0x009FFEE8, which contains the four words from the remote message.

Although we have no access of the remote DLL, we can infer that this part of the malware is taking some command/parameters of the remote message, and execute some functions correspondingly. It looks like a bot-net client.

3. Initilization (command #3)
We now look at case #3 (where the 2nd word of the remote message is 3). In this case,  function 0x3567162D is invoked.. To enforce the control flow into this case, we simply change the register values correspondingly. Figure 3 displays the body of function 0x3567162D.

Figure 3. Function Body of 0x3567162D

As shown in Figure 3, function 0x3567162D extracts a file from the hidden drive into a newly allocated heap area. The contents of the file seem to be some special javascript tags and HTTP request headers. The file name is read from an absolute location 0x3567806C. Unless the address is being re-written, it's always reading the same file.

Then Max++ jumps to function 0x356719E4, as shown in Figure 4. Again, it's invoking the COM interface of the remote object which is not available. As shown in Figure 4, it loads the virtual function table of the COM object multiple times and calls several functions of that COM interface.

Figure 4. Calling COM Object

4. Conclusion
Since the remote object is not available, we are not able to drill deeper into Max++. The bottomline via the analysis in this tutorial is that the maxware is able to listen to a port and receive commands from a remote server. The remote command has a fairly simple structure, the second word of the remote message decides the action to take at the max++ client side. #3 seems to be initializing service; and #1 seems to be executing some actions, where four words from the remote message serve as the parameters.

In the next tutorial, we will submit Max++ to various malware analysis engines and match their discovery with our manual analysis.


Saturday, August 4, 2012

Malware Analysis Tutorial 31: Exposing Hidden Control Flow


Learning Goals:
  1. Practice WinDbg for Inspecting Kernel Data Structure
  2. Use Packet Sniffer to Monitor Malware Network Activities
  3. Understand Frequently Used Network Activities by Malware
  4. Expose Hidden/Unreachable Control Flow of Malware
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction

This tutorial analyzes the network activity performed by max++.00.x86 when its efforts to load 147.47.xx.xx\max++.x86.dll fails. We show the use of network sniffer to assist the analysis. We show the use of debugger to expose and analyze the hidden/unreachable control flow of a malware.

2. Lab Configuration

We assume that you have finished Tutorial 30 and max++.00.x86 is already resident on the system. .Now set a breakpoint at 0x35671797 (this is where the malware tries to modify the kernel data structure about library path of max++. Later it will call Ole32.CoInitialize to load the remote). Now at the Ubuntu server, start the Wireshark packet sniffer and listen on the local area network (use ifconfig to find out which adapter to listen to).

Now press F9 until you hit 0x35671797. At this moment, in the Wireshark window, no packets should be intercepted yet. Execute the program step by step until we reach 0x35671D8B. This is right before the call of ole32.CoInitialize.

Figure 1. The Code Which Tires to Load Remote DLL


3. Wireshark Assisted Analysis
Now the intersting part, just one more step in the WinDbg instance, the Ole32.CoInitialize is called. Then you can notice that there is a lot of communication between 169.253.236.201 (our WinDbg instance) and 74.117.114.86. From Figure 2, you can tell that it's using a special HTTP method PROPFIND to retrieve max.x86.dll (note that PROPFIND is a method provided by the WebDav protocol which is an extension of HTTP).

Figure 2. Network Trace of Ole32.CoInitialize
However, interestingly, if we directly F9 from 0x35671797, we got the following in Figure 3. Notice the difference!

Figure 3. A slightly different network trace
 Clearly, the malware is trying to invoke the /install/setup.ppc.php at 108.61.4.52! Now the question is: who is sending this request and why didn't we capture it when we step by step the execution?

Challenge 1. Find a way to trace back to the sender of the packet to 108.61.4.52.

 4. Run Malware without Remote DLL
We are interested in looking at the rest of the malware logic and would like to have a rough idea of Max++.00.x86's behavior what if 74.117.114.86/max++.x86.dll is loaded. This would need us to tweak the control flow a little bit to observe the behavior. We need to perform the following lab configuration:

(1) set a breakpoint at 0x35671D8D and run to it. See Figure 4. This is right before the ole32.CoInitialize() call, which tries to load the remote 74.117.114.86/max++.x86.dll. However, the file is not available any more and the call will fail and terminate the entire process. We need to skip this call so that we could examine the rest of the malware logic.
Figure 4. Breakpoint to Divert Control Flow When Remote DLL Loading Fails


(2) Click the 2nd button on the toolbar (the Python window) and then type
  imm.setReg("EIP", 0x35671D93)
 This is to skip the call of ole32.coInitialize and jump to the next instruction

(3) Now in the register window, change the value of EAX to 0 (to indicate that the call is a success).

After the control flow diverting is successful, max++.00.x86 jumps to function 0x35674737, whose function body is shown in Figure 5.
Figure 5. Function 0x35674737 - Allocate Memory in Heap
Then the malware calls function 0x35671E37 [note at this moment 0x00182130 is the beginning address of allocated heap memory]. As shown in Figure 6, it is constructing some data structure at 0x00182130 (size: 0x24 bytes).

Challenge 2. Use data breakpoints to find out what is the type of the data structure constructed by 0x35671E37.

Figure 6. Function 0x35671E37 constructs some data structure
The control soon flows to 0x35671C4A. This function has several interesting calls, as shown in Figure 7. It seems to be creating a port and listens to it. To figure out the logic, we have to carefully handle the execution of function 0x35671E61 (because it is invoking functions in the remote max++.x86.dll, which is not loaded due to network failure).

Figure 7. Function body of 0x35671C4A
Now let's delve into function 0x35671E61 first,Figure 8 shows its first part. It's a call to ole32.CLSIDFromProgID("JavaScript"). The function locates registry entry based on program ID information. But this call triggers the remote DLL. We'll need to look at the details.
Figure 8. A Call That Triggers remote max++.x86.dll

By tracing into the old32.CLSIDFromProgID("JavaScript") call, we notice that at theole32.CoGetComCatalog call, it is stuck on loading the 74.117.114.86/max++.x86.dll. As shown in figure 9. It seems that CoGetComCatalog visits the loaded module again (and reads the manipulated information of the current module and thus trying to load the remote module. This is similar to the CoInitialize call in discussed in  Tutorial 30).
Figure 9. CLSIDFromProgID Stuck on CoGetComCatalog
Since the remote module name causes the problem, we could try to reset it back. From Tutorial 30, we know that the module name/path information is located at 0x002529c0. We could change it back to the original name "\\.\C2CAD972#4079#4fd3#A68D#AD34CC121074\L\max++.00.x86". (Right click on address 002529c0 in memory dump, select Binary Edit, then enter the path string in the UNICODE box).
Figure 10. Modify the Module Name - Convert it Back

Now let's let's observe the second parameter of CLSIDFromProgID in Figure 8. Via a simple analysis we can identify that the second parameter is located at 0x009FFF48, as shown in Figure 11.

Figure 11. Successful Completion of CLSIDFromProgID

As shown in Figure 11, address 0x009FFF48 stores the class ID. Pay attention to the byte order (you should read the first 4 bytes in the reversed order). For example, for the first 4 bytes (60 C2 14 F4), it should read as 0xf414c260. Searching f414c260 in regedit, we found CLSID {f414c260-6ac0-11cf...}, as shown in Figure 11. You can verify that it matches the highlighted area in the IMM memory dump pane. Reading more details about CLSID {f414c260-6ac0-11cf...}, we can find that the CLSID is mapped to jscript.dll in the system directory, this is as expected (i.e., the CLSIDFromProgID works correctly, given that the broken remote library link did not crash the CoGetComCatalog call in figure 10).

However, notice that, there is a possibility that the remote library when loaded, will re-write the registry entry so that later when JSScript object is used, it is actually referring to the functions of the remote library. As we do not have the 74.117.14.86/max++.x86.dll binary, we have no way to tell.

4.1 Rest of Logic of Function 0x35671E61
We now continue from the call of CLSIDFromProgID.  Again, notice that the CLSID is stored at 0x009FFF48.

Figure 12 shows the rest of the logic of the function 0x35671E61. The major part is a call of CoCreateInstance which constructs a unique instance of the JScript COM object. Note that its second last parameter rrid is the id of the interface that is used to communicate with JScript. However, as the co-initialize function fails, the CoCreateInstance() returns an error code 0x800410F0 (means the COM interface not initialized correctly). In such case, we have to modify the EAX register at 0x35671E90 to force the logic through.

It can be seen that, in Figure 12, three calls related to JScript COM object are placed. However, due to the failed co-initialize, we have no way to know about the details of these three functions. Lastly, function 0x35671E61 returns.

Figure 12. Interacting with COM Object

4.2 Function 0x3567162D
Using the similar technique, we can enforce the logic into function 0x3567162D. Figure 13 shows its function body. As shown in Figure 13, Max++ is readling from \??\C2CAD...6cc2 and allocates 0x15b bytes at 0x003E0000 and extracts the contents fro mthe file into 0x003E0000.

Figure 13. Loading New Malicious Logic

The rest of functio n0x3567162D is shown in Figure 14. It applies 2 layers of decryption to extract the contents at 0x003E0000. As shown in Figure 14, at 0x003E0000 it looks like an XML spec. At this moment, we do not know the meaning of "<jst>" tag. But if you look at the contents, it looks like a URL to download from intensivedive.com and the rest looks like the HTTP request header.



Figure 14. Extraction of Encrypted Contents
Challenge 2. Analyze the logic of function 0x35671ECF. Notice that you have to carefully rewire the logic when it tries to invoke functions in remote DLL.

4.3 Function 0x356713AC. 
At the end of functio n0x3567162D, it calls function 0x356713AC, which is shown below. Its function is pretty similar to 0x3567162D. It reads from another hidden file, resolve the IP of intensivedive.com and constructs request payload.
Figure 15. Function 0x356713AC
At the end of function 0x356713AC, it calls 0x356712D8, whose function body is shown as below. It prepares the necessary resources (UUIDs) for socket communication, in 0x35674237, which calls function 0x3567417c.
Figure 16. Function 0x356712D8 First Half

The function body of 0x3567417C is shown in Figure 17. Note that the first call of ws32_socket will fail. The most interesting part (see highlighted) is the call of BindIoCompletionCallBack. It sets 0x356740D4 as the handler on any IoCompletion on handle of the network communication. Let's set a breakpoint and see if it's getting called. This breakpoint, under the current setting will never get hit because the WSASocket call fails. However, the analysis of its binary code is still possible. We leave it as a homework for readers.

Figure 17. Function Body of 0x3567417C
Challenge 3. Analyze the function of 0x356740D4.

The rest of of 0x356712D8 deals with sending out packets (mainly to intensivedive.com/install.ppc) and there are too many errors as the network initialization of WSASocketW fails. Let's go back to 0x35671C6F and see what's the logic here.

Figure 18. Port Service Open
As shown in Figure 18, clearly, Max++ is opening a port (using zwCreatePort), and then there is a big loop and during each iteration, it is calling ZwReplyWaitReceivePortEx to try to listen to the port.

Challenge 4. Find out the port number that Max++ is using. Notice that since TCP/IP stack service is hijacked by Max++, netstat command won't get you any interesting information!

In the next tutorial, we will tweak the control flow of Max++ to get into each of the switch case of the zwReplyWaitReceivePortEx call and check out if Max++ is serving as a bot-client of a bot-net.

Tuesday, June 12, 2012

Malware Analysis Tutorial 30: Self-Overwriting COM Loading for Remote Loading DLL

Learning Goals:
  1. Practice WinDbg for Inspecting Kernel Data Structure
  2. Trace and Modify Control Flow Using IMM
  3. Understand the techniques employed at Max++ for hiding library loading
  4. Understand image loading notifier, asynchronous procedure call, kernel and normal routines.
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction

This tutorial analyzes the malicious DLL file \\.\C2CAD....\max++.00.x86. We will see a lot of interesting techniques for hiding its malicious behaviors.

2. Lab Configuration

The first step is to take out max++.00.x86 from the hidden drive. You can follow the similar techniques presented in Tutorial 28, The basic idea is to invoke zwCopyFileA after the file is available in the hidden drive.

Then, we have to get into the entry of max++.00.x86, which is located at offset +2DFF. This information is available in IMM, when you load max++.00.x86 as a DLL file in IMM. Then View -> Executable Modules, you can find out the +2DFF DLL entry and its base is 35670000.

Notice that, you do can execute max++.00.x86 step by step in IMM, however, it will not exhibit the "correct" malicious behaviors, because it's not running in the environment where Max++ has pre-hooked certain driver entries and exception handlers as needed. We have to, instead, use WinDbg to get to the DLL entry, following the "proper" execution flow of Max++.

In the following, we show how to step into the entry of max++.00.x86.

The configuration is similar to the setting of the WinDbg instance of Tutorial 20. 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



This is where Max++ is trying to access http://74.117.114.86\max++.x86.dll. Now press F8 in the IMM, you will stop in WinDbg twice on loading images. Now type the following to stop at the entry of max++.00.x86:

> bp 35672dff

Somehow WinDbg is not able to capture the breakpoint, but instead, the INT 3 is captured by the IMM instance as shown in Figure 2:

Figure 2. Entry of max++.00.x86

If you look at the instruction at 35672DFF (in Figure 2), you will notice that there is an 'INT 3' (single-step breakpoint) placed by WinDbg. We have to recover it to the original instruction PUSH EBP (we can learn about this information in the previous session that loads max++.00.x86 as a DLL in user mode IMM). The resetting of "PUSH EBP" has to be done, otherwise, the instruction at 0x35672E05 (MOV EAX, [EBP+C]) would not function correctly, because EBP is not set right. Please follow the instructions below in IMM:

(1) in the code pane, right click on the INT 3 instruction and select assemble, and then type "PUSH EBP".
(2) click the 2nd button (Python) on the tool bar to bring up the Python window and and run the following commands in the python window: imm.setReg("EIP", 0x35672DFF); This is to reset the EIP register. From no on, you can execute max++.00.x86 step by step.

3. Hiding DLL Loading
We now can explain why the remote DLL loading is not captured by IMM earlier in Tutorial 29. Look at figure 3 below.
Figure 3. Hide DLL Loading

The first call (shown in Figure 3), zwTestAlert clears the APC queue, and the second call DisableThreadLibraryCalls detaches DLL_LOAD signal so that the later DLL loading event will not be captured. Then the program plays a trick, it reads out the function addresses stored from 356761E0 to 35676200 one by one, and call each of them (see the high lighted area in the memory dump in Figure 3) one by one. These calls are 3567595c, 35675979, and etc..

Challenge 1. Find out all the functions (and their semantics) being called described in the above.

The logic is roughly described below:
(1) max++ first creates an AVL tree.
(2) then it copied the entry address of several ntdll functions such as ntWriteFile into global memory.
It's basically to prepare the data section (hard code it) in global memory. Then it calls 356752D7 (at 0x35672E5E).
(3) then it saves several dll names such as "kernel32.dll", "kernel32base.dll" into its stack.

4. Manipulation of System Kernel DLLs
Now we start the analysis from 0x356762D7. Figure 4 shows its function body.
Figure 4. Processing Kernel Modules


Function 0x356752D7 processes the related kernel DLLs one by one using a loop. The collection of DLL names is preset by Max++ (see the comments after challenge 1). The function tries to get the handle of each DLL, it proceeds to call 0x3567525C to process the header information of each DLL (only when the handle is successfully retrieved, i.e., the DLL is currently already alive in the system space).

We now look at the details of funciton 0x3657525C, as shown in Figure 5.
Figure 5. Function 0x3567525c

The major part, as shown in Figure 5, is a call of RtlImageDirectoryEntryToData. The undocumented system call (if you search it on ReactOS, you can find the source code) takes four parameters: (1) the image base of the DLL file, (2) whether it is regarded as an image file, (3) the ID of the entry to retrieve, and (4) the entry size. In this case, our ID is "1", reading the MSDN documentation of IMAGE_DIRECTORY of PE header, we can find that ID "1" stands for import table address and size. Now it's your job to find out what is the use of this information, i.e., what does Max++ plan to do with them?

Challenge 2. Find out the use of NT header information retrieved by code at 0x35675277 (hint: use data breakpoints to find out when and where the information is used).

Following function 0x356752D7 there are many technical details related to NT header information retrieval and resetting, whereas the analysis is similar. In the following, we directly jump to the other interesting parts of max++.00.x86.

5. Resetting Exception Handler (SEH)
We now look at an interesting behavior of max++.00.x86 that resets the SEH handler. Before max++.00.x86 does the trick, the current SEH chain is shown in Figure 6 (click View -> SEH chain in IMM).
Figure 6. SEH Chain before max++.00.x86 does the trick

As shown in Figure 6, when an exception occurs, it is first passed to ntdll.7C90E900 (3 times) and then kernel32.7C839AC0 (if 7C90E900 cannot handle it right). In fact, SEH chain is a very simple singly-listed data structure. Each SEH record has two elements as shown below:

  struct SEH_RECORD{
      SEH_RECORD *next;
      void*   handler_entry
 };

 For example, the first SEH record is locatd at 0x0012BF1C (this is usually at the bottom of the current stack frame of the current thread), as shown in Figure 7.

Figure 7. Memory dump of the first SEH record
As shown in Figure 7 (highlighted part), the next pointer points to 0x0012C1C8 (compare it with Figure 6), and the handler part has value 0x7C90E900.

Now, in Figure 8, we present the malicious code in max++.00.x86 which resets SEH. The basic idea is to construct a new SEH record and push it into the stack, and reset the value at FS:[0] (which is part of the kernel data that points to SEH chain). The important instructions are already highlighted.

Figure 8. Manipulates SEH Chain

Challenge 3. Explain in details (based on the instructions given in Figure 8, starting from 0x356754BC): why is 35675510 the new exception handler?

Challenge 4. Explain the logic of handler at 35675510 and investigate when it is called.

5. Loading Remote DLL
The next interesting part is located at 0x35672E6D. Figure 9 shows a call of zwCreateThread and its parameters. The thread's start routine (i.e., the function to execute when the thread is born) is 0x356718D2.
Figure 9. Create a new thread which invokes 0x356718D2

 Let's set a software breakpoint at 0x356718D2 and trace into it, as shown in Figure 10. There are two major functions: 0x35671797 and ole32.CoInitialize.

Figure 10. Thread Start Routine
Figure 11 displays the first part of the function body of 0x35671797.

Figure 11. First Part of 0x0035671797


As shown in Figure 11, The first important call in 0x35671797 is LdrFindEntryForAddress(0x0035670000, 0x009FFFA8). Search the documentation of LdrFindEntryForAddress, we could soon learn that the function finds the module entry (LDR_DATA_TABLE_ENTRY) for address 0x0035670000, and the pointer to LDR_DATA_TABLE_ENTRY is stored in 0x009FFFA8 (which contains value 0x00252A50 after the call. Dump the data structure using WinDbg (start the WinDbg inside the WIN_DEBUG VBox instance and attach the process max++ noninvasively, we have the following. Note that the FullDllName's type is _UNICODE string, while the real char array is stored at 0x002529c0.

:000> dt _LDR_DATA_TABLE_ENTRY 0x00252a50 -r1
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x252b10 - 0x252cd0 ]
      +0x000 Flink            : 0x00252b10 _LIST_ENTRY [ 0x252bd8 - 0x252a50 ]
      +0x004 Blink            : 0x00252cd0 _LIST_ENTRY [ 0x252a50 - 0x252900 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x252b18 - 0x252cd8 ]
      +0x000 Flink            : 0x00252b18 _LIST_ENTRY [ 0x252be0 - 0x252a58 ]
      +0x004 Blink            : 0x00252cd8 _LIST_ENTRY [ 0x252a58 - 0x252908 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x252d48 - 0x252be8 ]
      +0x000 Flink            : 0x00252d48 _LIST_ENTRY [ 0x252e08 - 0x252a60 ]
      +0x004 Blink            : 0x00252be8 _LIST_ENTRY [ 0x252a60 - 0x252b20 ]
   +0x018 DllBase          : 0x35670000 Void
   +0x01c EntryPoint       : 0x35672dff Void
   +0x020 SizeOfImage      : 0xc000
   +0x024 FullDllName      : _UNICODE_STRING "\\.\C2CAD972#4079#4fd3#A68D#AD34CC121074\L\max++.00.x86"
      +0x000 Length           : 0x6e
      +0x002 MaximumLength    : 0x72
      +0x004 Buffer           : 0x002529c0  "\\.\C2CAD972#4079#4fd3#A68D#AD34CC121074\L\max++.00.x86"

   ...

The next call is OLE32.LoadTypeLibrary("\\.\C2CAD...max++.00.x86", REGKIND_NONE, 0x009FFFAC), where a pointer to ITypeLib is stored at 0x009FFFAC. By dumping the RAM, we can find that the ITypeLib pointer is 0x00180F10.

Next comes the interesting part. It reads ECX = [EAX], as shown in Figure 11, then it calls [ECX+18], passing three parameters: 0x00180F10 (the "this" pointer for the ITypeLib interface itself), 0x003567696C, and 0x00356782A0 (and there might be other parameters, i.e., not necessarily 3). None of the 0x003567696c and 0x00356792a0 looks like inside code region. But which function is it calling?

We need to read the documentation of MSDN about ITypeLib carefully. According to MSDN, ITypeLib supports a number of functions such as FindName, GetDocumentation, etc.If we examine the memory contents of 0x00356782A0, before and after the call of [ECX+18], we might notice that its contents is changed. Thus, checking the list of functions available in ITypeLib, the only function that has the second parameter as output is ITypeLib::getTypeInfoOfGUID(int GUID, ITypeLib **).

Challenge 5. Verify if the above conjecture about getTypeInfoOfGUID is correct.


Now let us study the last interesting part of function 0x35671797, as shown in Figure 12.
Figure 12. Overwrite Module Information
As shown in Figure 12, it's a simple memcpy function that copies string "\\74.117.114.86\max++.x86.dll" into address 0x002529C0. Read the discussion after Figure 11 again, address 0x002529C0 stores  the name of the current module in the in-order-load module list!

Now, when the control flow returns from 0x35671797 (look at Figure 10, it calls Ole32.CoInitialize. Because the current module is registered as a type library, and Ole32 modules will try to reload it again somehow, which enforces the system to load the remote DLL \\74.117.114.86\max++.x86.dll.

The file \\74.117.114.86\max++.x86.dll is not available on the Internet anymore. The call of CoInitialize fails. We have to figure out a way to explore the rest of the logic. We will continue our exploration in our next tutorial.



Sunday, June 10, 2012

Malware Analysis Tutorial 29: Stealthy Library Loading II (Using Self-Modifying APC)



Learning Goals:
  1. Practice WinDbg for Intercepting Driver Loading
  2. Trace and Modify Control Flow Using IMM
  3. Understand the techniques employed at Max++ for hiding library loading
  4. Understand image loading notifier, asynchronous procedure call, kernel and normal routines.
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction

This tutorial analyzes the malicious driver B48DADF8.sys. We assume that you have retrieved the driver from the hidden drive, following the instructions of Tutorial 28.

2. Lab Configuration

We need two windows images: one for taking notes and one for actually running the malware. Also a kernel mode WinDbg instance is needed on the host.

To set up the Notes images, you can follow the instructions of Tutorial 20. The basic idea is to start two instances of IMM, using one to debug the other. Then at 0x004E6095 to set a breakpoint and skip the instruction when there will be an illegal memory write. Once B48DADF8.sys is loaded, in the second IMM (as shown in Figure 1), if we check the executable modules (View -> Executable Modules), we can find out the entry of the driver is +1259. Jumping to that address, we can see the driver entry (which makes a bunch of calls on hooking image loading and creation of driver device).

Figure 1. Identify B48DADF8.sys Entry

The set up of the windows image for debugging and WinDbg should follow the instructions of Tutorial 28. We need to stop at the entry of the driver. This could be achieved by first finding out the starting address of the module in WinDbg, and then plus offset 1259.

kd> g
nt!DebugService2+0x10:
80506d3e cc              int     3
kd> g
nt!DebugService2+0x10:
80506d3e cc              int     3
kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
faf0c000 faf11000   B48DADF8   (deferred)            
faf54000 faf5c000   _          (deferred)          

First, we learned that the B48DADF8 module starts at faf0c000. Given that the offset of the entry is 1259, we can set a breakpoint at faf0d259, as shown below.  


kd> bp faf0d259
*** ERROR: Module load completed but symbols could not be loaded for B48DADF8.sys
kd> g
Wed May 30 09:55:24.281 2012 (UTC - 4:00): Breakpoint 0 hit
B48DADF8+0x1259:
faf0d259 55              push    ebp
kd> u
B48DADF8+0x1259:
faf0d259 55              push    ebp
faf0d25a 8bec            mov     ebp,esp
faf0d25c 56              push    esi
faf0d25d 8b7508          mov     esi,dword ptr [ebp+8]
faf0d260 893518f0f0fa    mov     dword ptr [B48DADF8+0x3018 (faf0f018)],esi
faf0d266 33c0            xor     eax,eax
faf0d268 6882d0f0fa      push    offset B48DADF8+0x1082 (faf0d082)
faf0d26d c746384cd1f0fa  mov     dword ptr [esi+38h],offset B48DADF8+0x114c (faf0d14c)

You can verify that the code starts at faf0d259 (shown in the WinDbg dump above) matches the instructions in the IMM window in Figure 1. From now on, we can start the analysis. The basic approach is to execute the driver in the WinDbg instance and annotate the code in the WinNotes image.



3.Hook Up Driver with New Device and Set Image Load Notifier

We now observe the first section of the code at the beginning of the driver loading. Figure 3 shows the annotated code.

Figure 3. First Part of B48DADF8.sys


The first interesting part is that the driver takes itself (DRIVER_OBJECT) and saves to a global variable. It first reads from EBP+8, i.e., the first parameter to a driver (PDRIVER_OBJECT), as shown in the first highlighted part. We will see that the malware later will need this value.

We can verify that the value ffb81268 is really a driver object as shown below. It is clear that the driver object is not fully set up yet, e.g., the DeviceObject is null.

kd> dd esp
f7c88920  faf4d121 ffb81268 00000000 02002000
...
kd> dd _DRIVER_OBJECT ffb81268
Couldn't resolve error at '_DRIVER_OBJECT ffb81268'
kd> dt _DRIVER_OBJECT ffb81268
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : (null)
   +0x008 Flags            : 4
   +0x00c DriverStart      : (null)
   +0x010 DriverSize       : 0
   +0x014 DriverSection    : 0xffb8c8f0 Void
   +0x018 DriverExtension  : 0xffb81310 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\driver\4157114776"
   ...

Next B48DADF8.sys tries to call function psSetLoadImageNotifyRoutine at to +1082. This is clearly an operation that tries to hide the loading of modules.  We are not getting into the details yet, but we can set a breakpoint on it. We can see that the breakpoint will be hit multiple times and DebugService + 2bde is not hit any more.

Then, B48DADF8.sys tries to create an IO device and hooks itself up as the driver for that device. According to MSDN,  IoCreateDevice() has 6 parameters: PDRIVER_OBJECT, DriverExtension, DeviceName, DeviceType, DeviceCharacteristics, Exclusive, PDEVICE_OBJECT.

From the WinDbg dump below, we can soon infer that the name of the new device is \??\EBB02C33..\#...0CFE and the device type is FILE_DEVICE_UNKNOWN. This is confirmed by the following WinDbg dump:


kd> dd esp
f7c888fc  ffb81268 00000000 faed6114 00000022
...
kd> dt _UNICODE_STRING faed6114
nt!_UNICODE_STRING
 "\??\EBB02C33#910D#415d#BB61#FBD3CC1D0CFE"
   +0x000 Length           : 0x50
   +0x002 MaximumLength    : 0x52
   +0x004 Buffer           : 0xfaed60c0  "\??\EBB02C33#910D#415d#BB61#FBD3CC1D0CFE"

4.Hide Driver Module
We now discuss the efforts of B48DADF8.sys to hide itself. This part contains no more than 20 instructions, as shown in Figure 4.
Figure 4. Hide Driver Module B48DADF8.sys

At the beginning of the code, ESI points to the _DRIVER_OBJECT of B48DADF8, and then the code retrieves the word at offset 0x14 of the _DRIVER_OBJECT, and now EDX points to DriverSection (whose data type is _LDR_DATA_TABLE_ENTRY). Using WinDbg, we can easily verify its contents as below. You can see that it's full DLL name is "\??\... C2CAD...B48DADF8.sys".

kd> dt _LDR_DATA_TABLE_ENTRY ffbd61b0 -r1
nt!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x8055b1c0 - 0xffbacea8 ]
      +0x000 Flink            : 0x8055b1c0 _LIST_ENTRY [ 0x8131db20 - 0xffbd61b0 ]
      +0x004 Blink            : 0xffbacea8 _LIST_ENTRY [ 0xffbd61b0 - 0x811974c8 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
      +0x000 Flink            : (null)
      +0x004 Blink            : (null)
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x630069 - 0x0 ]
      +0x000 Flink            : 0x00630069 _LIST_ENTRY
      +0x004 Blink            : (null)
   +0x018 DllBase          : 0xfaedc000 Void
   +0x01c EntryPoint       : 0xfaedd259 Void
   +0x020 SizeOfImage      : 0x5000
   +0x024 FullDllName      : _UNICODE_STRING "\??\C2CAD972#4079#4fd3#A68D#AD34CC121074\B48DADF8.sys"
      +0x000 Length           : 0x6a
      +0x002 MaximumLength    : 0x6a
      +0x004 Buffer           : 0xe1a678c8  "\??\C2CAD972#4079#4fd3#A68D#AD34CC121074\B48DADF8.sys"
   +0x02c BaseDllName      : _UNICODE_STRING "B48DADF8.sys"
      +0x000 Length           : 0x18
      +0x002 MaximumLength    : 0x18
      +0x004 Buffer           : 0xffbd61fc  "B48DADF8.sys"
   +0x034 Flags            : 0x1104000
   +0x038 LoadCount        : 1
   +0x03a TlsIndex         : 0x49
   +0x03c HashLinks        : _LIST_ENTRY [ 0xffffffff - 0x4f51 ]
      +0x000 Flink            : 0xffffffff _LIST_ENTRY
      +0x004 Blink            : 0x00004f51 _LIST_ENTRY
   +0x03c SectionPointer   : 0xffffffff Void
   +0x040 CheckSum         : 0x4f51
   +0x044 TimeDateStamp    : 0xfffffffe
   +0x044 LoadedImports    : 0xfffffffe Void
   +0x048 EntryPointActivationContext : (null)
   +0x04c PatchInformation : 0x00340042 Void


The next couple of instructions (from 0x100012CE to 0x100012D2 in figure 4) clears the FullDLLName. After 0x100012D2, if you display the same DriverSection again, you would notice that the FullDllName is gone, as shown below. However, the BaseDllName is still there, I guess the malware author forgot to clear it as well.

kd> dt _LDR_DATA_TABLE_ENTRY ffbd61b0
nt!_LDR_DATA_TABLE_ENTRY
   ...
   +0x024 FullDllName      : _UNICODE_STRING ""
   +0x02c BaseDllName      : _UNICODE_STRING "B48DADF8.sys"
   ...

Next, B48DADF8.sys tries to remove itself from the module list. As shown in Figure 4, at 100012D6, EAX and ECX now have the FLINK and BLINK of the first module of the InLoadOrderModule list. The next four instructions constitute a typical REMOVE_NODE operation on a doubly linked list, which removes B48DADF8 module from the list.

Challenge 2. Explain the logic of code from 100012D6 to 100012E3 in Figure 4.

4.Hook Up on PCI Device
The next step (function 0x100011D0) is to hook up on the PCI Device by copying from the original PCI driver. This is shown in Figure 5.
Figure 5. Copy from PCI Driver
As shown in Figure 5, the first step of function 0x100011D0 is to retrieve the PCI Driver object by name "driver\PCI". Then it copies several attributes of the PCI driver to the current driver, such as driver_start, driver_init, driver size, and driver name. However, the driver major functions 0xfaed514c (an array that contains the IRP handler entry addresses) is not changed. Here all entries are redirected to 0xfaed514, as shown below (which dumps the contents of the B4DADF8.sys driver object, before and after the call of 0x100011D0.

kd> dt _DRIVER_OBJECT ffb80b90
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n0
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0xffbb4af8 _DEVICE_OBJECT
   +0x008 Flags            : 4
   +0x00c DriverStart      : (null)
   +0x010 DriverSize       : 0
   +0x014 DriverSection    : 0xffb8c8f0 Void
   +0x018 DriverExtension  : 0xffb80c38 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\driver\4157114776"
   +0x024 HardwareDatabase : (null)
   +0x028 FastIoDispatch   : (null)
   +0x02c DriverInit       : 0xfaf572c5     long  +0
   +0x030 DriverStartIo    : (null)
   +0x034 DriverUnload     : (null)
   +0x038 MajorFunction    : [28] 0xfaed514c     long  +0
kd> p
...
kd> dt _DRIVER_OBJECT ffb80b90
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n0
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0xffbb4af8 _DEVICE_OBJECT
   +0x008 Flags            : 4
   +0x00c DriverStart      : 0xfaafc000 Void
   +0x010 DriverSize       : 0x10a80
   +0x014 DriverSection    : 0x8131d8a0 Void
   +0x018 DriverExtension  : 0xffb80c38 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\PCI"
   +0x024 HardwareDatabase : (null)
   +0x028 FastIoDispatch   : (null)
   +0x02c DriverInit       : 0xfab0a004     long  +fffffffffab0a004
   +0x030 DriverStartIo    : (null)
   +0x034 DriverUnload     : (null)
   +0x038 MajorFunction    : [28] 0xfaed514c     long  +0

Challenge 3. Note that at this moment, the PCI device has not been completely hooked up to the new driver. Find a way to find out where the new driver is eventually set as the handling driver for the PCI device.

5.Image Loading
We now go back to study the image loading call at +1082, which is discussed earlier in section 3. Max++ sets +1082 as the call back function whenever NtImageLoad is called. This first, of course, disrupts WinDbg in monitoring image loading. But the code itself is doing a lot of malicious stuff. Let's set a breakpoint at +1082 and watch its behavior. The set up is shown as below:

kd> lm
start    end        module name
804d7000 806ed680   nt         (pdb symbols)          c:\windows\symbols\ntoskrnl.pdb\47A5AC97343A4A7ABF14EFD9E99337722\ntoskrnl.pdb
faee4000 faee9000   B48DADF8   (deferred)            
faf64000 faf6c000   _          (deferred)            

Unloaded modules:
...
kd> bp faee4000 + 1082
*** ERROR: Module load completed but symbols could not be loaded for B48DADF8.sys
kd> g
Mon Jun  4 08:34:46.187 2012 (UTC - 4:00): Breakpoint 0 hit
B48DADF8+0x1082:
faee5082 55              push    ebp

Figure 6 displays the major function of +1082 (also written as 0x10001082).
Figure 6. Function Body of 0x10001082

As shown in Figure 6, the majority part of +1082 is to set up and queue an APC object (Asynchronous Procedure Call). APC is frequently used in I/O operation, it stands for an object that will be executed a while later.

See the highlighted parts in Figure 6, the control flow is very clear: Max++ first tries to call ExAllocatePool to reserve 30 bytes of kernel memory for the APC object, then it calls KeInitializeAPC and KeInsertQueue to queue the APC call. We need to look at the details of KeInitializeAPC. According to ReactOS documentation, the prototype of KeInitializeAPC is shown below:

VOID NAPI KeInitializeAPC(
        IN PKAPC pApc,
        IN PKTHREAD thread, 
        IN KAPC_ENVIRONMENT env,
        IN PKKERNEL_ROUTINE kernelRoutine,
        IN PKROUNDOWN_ROUTINE rundownRoutine,
        IN PKNORMAL_ROUTINE normalRoutine,
        IN KPROCESSOR_MODE mode,
        IN VOID context
)




The dump from WinDbg can be found in the following:
kd> dd esp
f7c88bb4  ffbb2e48 81176320 00000000 faee530c
f7c88bc4  faee52f0 71a50000 00000001 00000000

Here, the kernelRoutine is faee530c (+130c), rundownRoutine is faee52f0 (+12f0), and normalRoutine is 71a50000 (your job: find out which module does it belong to), mode is 1. By MSDN documentation, if mode is 1 and normalRoutine is not 0, this is a user mode APC, which will call the normalRoutine later. However, to be safe, we want to set up breakpoints on all of the routines +130c, +12f0, 71a50000.
Pay special attention, at this moment, the normal routine is 71a50000!

Interestingly, it's the kernel routine 130c which is hit first. The following is the call stack:
kd> kv
ChildEBP RetAddr  Args to Child             
WARNING: Stack unwind information not available. Following frames may be wrong.
f7c88d4c 804de855 00000001 00000000 f7c88d64 B48DADF8+0x130c
f7c88d4c 7c90e4f4 00000001 00000000 f7c88d64 nt!KiServiceExit+0x58 (FPO: [0,0] TrapFrame @ f7c88d64)
0012c8f8 7c91624a 0017c0e0 0012c984 0012ceac 0x7c90e4f4
0012cbb8 7c9164b3 00000000 0017c0e0 0012ceac 0x7c91624a
0012ce60 7c801bbd 0017c0e0 0012ceac 0012ce8c 0x7c9164b3
0012cec8 7c801d72 7ffdfc00 00000000 00000000 0x7c801bbd
0012cedc 7c801da8 0012d15c 00000000 00000000 0x7c801d72
0012cef8 71ab78f1 0012d15c 00155218 0017f410 0x7c801da8
0012d27c 71ab496d 0017f694 0017f420 00000000 0x71ab78f1
0012d29c 71ab49cc 0017f410 c000010e 00000000 0x71ab496d
0012d2b8 71ab40a3 00000002 00000001 00000006 0x71ab49cc
0012d310 003c2315 00000002 00000001 00000006 0x71ab40a3
0012d604 003c24ef 0012d634 7c906786 7c903400 0x3c2315
0012d648 003c2507 00401166 003c0000 fffffffe 0x3c24ef
0012ffd0 8054b6b8 0012ffc8 81176320 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

Challenge 4. Analyze who triggers +130c. (hint: 0x71 range is the mwsock.dll, 0x7c range is ntdll.dll).

Now let's study what the function +130c (0x1000130c in IMM) is doing. Figure 7 shows its function body.

Figure 7. Function Body of +1307
The first part of +130c is pretty interesting. It's a collection of exchange functions, which essentially rotates 6 words on top of the stack. In the following, we show you the contents of the stack before the rotation.

kd> dd esp
f7c88d00  804e60f1 ffb9b638 f7c88d48 f7c88d3c
f7c88d10  f7c88d40 f7c88d44 f7c88d64 0012c834
...

The stack contents after the rotation is shown below. You can find that 0x804e60f1 is shifted to the right (now the 6'th word in the stack).
kd> dd esp
f7c88d00  ffb9b638 f7c88d48 f7c88d3c f7c88d40
f7c88d10  f7c88d44 804e60f1 f7c88d64 0012c834





Why does Max++ have to do this? The reason is that the function copyMaliciousCodeToNewVM (the call located at 10001327 in Figure 7) actually consumes 5 additional words in stack. In the following, we display the stack contents after the call of copyMaliciousCodeToNewVM is completed.
kd> dd esp
f7c88d14  804e60f1 f7c88d64 0012c834 f7c88d64
f7c88d24  ffffffff 804e2490 804f2001 7c90e4f4

 You can notice that 804e60f1 is now at the top of the stack. At this moment, the control flow (at 0x10001332) is going to jump to ntoskrnl.ObfDereferenceObject, which when finishes, will jump to 0x804e60f1 (which is originally the return address of +130c). By manipulating the stack this way, Max++ can successfully confuse the control flow analysis performed by static analysis tools.

Now let's observe the logic of copyMaliciousCodeToNewVM , which is located at +100f. Figure 8 shows its function body

Figure 8. Function Body of +100f

The logic of copyMalicoiusCodeToNewVM (+100f) is very simple, it first lowers the IRQ level and then it allocates a small piece from the memory and copies the contents of +1338 to the target address (0x00380000).Recall the "Stealthy 0x00380000 memory segment" in Tutorial 27,it's now your job to figure out what is copied into the region 0x00380000.

Challenge 5. Figure out what is copied by the copyMaliciousCodeToNewVM.

Then the JMP ntoskrnl.obfDerefrenceObject de-references the new driver object and returns to the system call that triggers the kernel function +130c.

Figure 9 shows the contents of the copyMalicoiusCodeToNewVM (+100f). If you look it its logic, it basically matches the description above. However, there is one thing we'd like you to pay special attention:

Challenge 6. See figure 9, where is the new VM address (allocated by ZwAllocatevirtualMemory, i.e., 0x00380000) stored at?
Figure 9. Function Body of copyMalicousCodeToNewVM (0x1000100f)


If you look at the two highlighed instructions in Figure 9, you might notice that the 3rd parameter of 0x1000100F (copyMaliciousCodeToNewVM) is used to store 0x00380000. But why?

Challenge 7. Figure out what is the motivation for storing 0x00380000 in the 3rd parameter of 0x1000100F.

To solve the above challenge, we need to go back to Figure 8, and we notice that it's f7c88d3cwhich is passed to the call of 0x1000100F (copyMaliciousCodeToNewVM).
Looking at the ReactOS information about kernel routine (search for PKKERNEL_ROUTINE on ReactOS), you will find that kernel routine (130c) accepts 5 parameters: APC, pNormal_Routine, Normal_Context, System_Arg, System_Arg2). So the f7c88d3c is actually the NORMAL_CONTEXT parameter of the KERNEL_ROUTINE.! Similarly, you would find that f7c88d48 (the second parameter) is the NORMAL_ROUTINE.

Now, look the highlighted part in Figure 9 (two yellow underlines and three thicker ones), you will find that copyMalicoiusCodeToNewVM writes value 0x00380000 into both the place holders for NORMAL_CONTEXT and NORMAL_ROUTINE!

Lab Configuration for Analyzing  Code 0x00380000: 
Clearly, the next step we would like to pursue is to debug the code located at 0x00380000 (originally copied from 0x10001338). Interestingly, if you set a hardware BP in WinDbg, the debugger never stops on 0x00380000. We suspect that somehow hardware BP is cleared at some point. Only the software BP works in this scenario ("bp 0x00380000"), and in some scenarios you might find that your IMM actually gets the INT3 (software BP) interrupt and you have to now debug it in IMM. Figure 10 shows you the screen of IMM when the software BP is intercepted. You can see that the "instruction" at 0x00380000 is "INT3".

Figure 10. Interesting Debugging Behavior of WinDbg/IMM
Open Challenge: Figure out why the software BP set by WinDbg is intercepted by Immunity Debugger. (conjecture: the debug port of the operating system is reset by Max++).

Figure 11. Code at 0x00380000

Figure 11 displays the logic of code at 0x00380000 (originally copied from 0x10001338). It mainly consists of three steps: (1) it searches for module "kernel32.dll", (2) it searches for funciton LoadLibraryW in the PE header, (3) it invokes function +1404 (we named it LoadMax++00x86).

The main part of function LoadMax++00x86 (located at +1404) is shown in Figure 12.

Figure 12. Load Max++.00.x86

The major bulk of the funciton + 1404 is the call of LoadLibraryW("\.\C2CAD...max++.00.x86").

Challenge 8. Prove the above statement is true (especially, why is its parameter "\.\C2CAD...max++.00.x86"?).

Summary:
Up to now, combined with Tutorial 27, we have shown you the complete picture of the stealthy remote DLL loading technique. Max++ loads max++.x86.dll via three steps: (1) load B48DADF8.sys; (2) load max++.00.x86; and (3) load max++.x86.dll. During each step, a variety of techniques are employed to hide the trace, e.g., by modifying the kernel data structures of libraries. There are also techniques that we have not completely understand, see the open challenge in this tutorial.