Malware Analysis Tutorial 18: Infecting Driver Files (Part II: Simple Infection)

Learning Goals:

  1. Understand the frequently used tricks by malware to infect driver files
  2. Understand the role of registry in Windows operating systems
  3. Practice analyzing function calls
  4. Practice reverse engineering sophisticated data structures in memory

Applicable to:

  1. Operating Systems
  2. Assembly Language
  3. Operating System Security

1. Introduction
This tutorial continues the analysis of Tutorial 17. As shown in Tutorial 17, Max++ randomly picks up a system module which satisfies two criteria: (1) has export table and (2) file size > 0x4c10. This tutorial shows how Max++ infects the randomly picked driver and run it.

We will analyze the code from 0x3C1E73  in this tutorial.

2. Lab Configuration
(0) Start WinXP image in DEBUGGED mode. Now in your host system, start a windows command window and CD to “c:Program FilesDebugging Tools for Windows (x86)” (where WinDBG is installed). Type “windbg -b -k com:pipe,port=\.pipecom_12” (check the com port number in your VBox instance set up). When WinDbg initiates, types “g” (go) twice to let it continue.

(1) Now launch IMM in the WinXP instance, clear all breakpoints and hardware breakpoints in IMM (see View->Breakpoints and View->Hardware Breakpoints).

(2) Go to 0x4012DC and set a hardware breakpoint there. (why not software bp? Because that region will be self-extracted and overwritten and the software BP will be lost). Pay special attention that once you go to 0x4012DC, directly right click on the line to set hardware BP (currently it’s gibberish code).

(3) PressF9 several times run to 0x4012DC. You will encounter several breakpoints before 0x4012DC. If you pay attention, they are actually caused by the int 2d tricks (explained in Tutorial 3 and 4, and 5). Simply ignore them and continue (using F9) until you hit 0x4012DC.

Figure 1 shows the code that you should be able to see. As you can see, this is right before the call of RtlAddVectoredException, where hardware BP is set to break the LdrLoadDll call (see Tutorial 11 for details).

Figure 1: code at 0x4012DC

(4) Now scroll down about 2 pages and set a SOFTWARE BREAKPOINT at 0x401417. This is right after the call of LdrLoadDll(“lz32.dll”), where Max++ finishes the loading of lz32.dll. Then hit SHIFT+F9 several times until you reach 0x401417 (you will hit 0x7C90D500 twice, this is somwhere inside ntdll.zwMapViewSection which is being called by LdrLoadDll).

Figure 2: code at 0x401407

(6) Now we will set a breakpoint at 0x3C1E73  .  Goto 0x3C1E73  and set a SOFTWARE BREAKPOINT. SHIFT+F9 there. Press SHIFT+F9 to run to 0x3C1E73  . (You may see a warning that this is out range of the code segment, simply ignore the warning).

(Figure 3 shows the code that you should be able to see at 0x3C1E73  . This is right after the loop for randomly picking up a system module to infect.

Figure 3. Code Starts at 0x3C1E73 (Double Check of System Functions)

Section 3. Warm-Up before Infection
As shown in Figure 3, right after the random pick loop (at 0x3C1E73), EDI points to the name of the system module to infect (in our case, it’s “mouclass.sys” – the name will be different every time you run Max++). Starting from 0x3C1E73 to 0x3C1F83, Max++ performs some warm-up activities to check of all important system functions are working right. Details are shown in Figure 3. It checks the following functions:

(1) zwCreateSection – this function allows to create a section (a shared memory object between processes), it can also be used to map between a file and a memory region.
(2) zwMapViewOfSection – map a section object into the address space of a process
(3) zwReadFile – read file contents into a memory region.

If all checks are good, it will continue to the infection.

Challenge 1. As shown in Figure 3, analyze the sprintf call at 0x3C1E84. What are its parameters? What’s returned?
Challenge 2. As shown in Figure 3, analyze the zwCreateSection call at 0x3C1EEE. What are its parameters? What’s returned?
Challenge 3. As shown in Figure 3, analyze the zwMapViewOfSection call at 0x3C1F20. What are its parameters? What’s returned?
Challenge 4. As shown in Figure 3, analyze the zwReadFile call at 0x3C1F73. What are its parameters? What’s returned?

Section 4. Mods on Registry
At 0x3C1FAD, Max++ calls function 0x3C196A. This is the function that performs the real malicious infection operation.

The first series of actions Max++ performs is to create a registry entry and set its values, as shown in Figure 4. Assume that the file to infect is mouclass.sys, Max++ creates a registry key named “registryMACHINESYSTEMCurrentControlSetServices.mouclass“. Then it creates several value pair, such as the “start” and the “ImagePath”. Note that in the “CurrentControlSetServices” there is originally a registry named “mouclass” (just without the .dot). The original version of the registry and the new version are displayed in Figure 5(a) and Figure 5(b) respectively, using regedit.

Figure 4. Registry Keys

Look at Figure 5 and compare the values for “Start” and “ImagePath“. There are some interesting observations: In the old Mouclass entry, the “Start” type is “0” (meaning load at boot time) and the new “.Mouclass” start type is “3” (meaning load on demand). The ImagePath of the old Mouclass entry is the absolute path to the driver, while the ImagePath of the new Mouclass is “*“. The meaning of these parameters can be found in MSDN documentation [1]. As far as we know, the “*” is not a legal value for ImagePath. There must be some special handling for this once Max++ infects the file/disk driver. The meaning of “*” is currently an open question to us.

Figure 5. Registry “mouclass” (old) and “.mouclass” (new)

Section 5. Disable File Protection Service (Function 0x003C15AB)
Next, Max++ disables a file protection service called  sfc_os.dll. Here sfc stands for “Microsoft Windows File Protection Service“, it monitors the file operations on system files (including drivers). Max++ needs to disable sfc_os.dll to overwrite the system driver file for infection. To do this, Max++ calls function 0x003C15AB at 0x003C1A16 (shown at the bottom of Figure 3). Function 0x003C15AB kills the kernel process/thread related to sfc_os.dll one by one to achieve the goal. We now delve into the details of Function 0x003C15AB.

Figure 6. Search for “Winlogon.exe”

Shown in Figure 6 is our old friend zwQuerySystemInformation. This times, Max++ uses it to retrieve the information of live processes in the system (last time in Tutorial 16 and 17, it retrieves system modules/drivers). It then enumerates the information of each process and compares its name with “Winlogin.exe” until it finds the Winlogon.exe process.

Challenge 1. How do you tell that the function call at 0x3C1612 (RtlEqualUnicodeString) is to compare with Winlogin.exe? Show the parameters of the RtlEqualUnicodeString call. (hint: use WinDbg to display UNICODE_STRING).

Figure 7. Query Debugging Information

Max++ then does a query on the debugging information of Winlogon.exe process (as shown in Figure 7). We leave the analysis details to you and you can refer to [2] for details of RtlqueryProcessDebugInfo. Potentially, Max++ could take advantage of the information to tell if the system is being debugged or not, but it didn’t perform any detection/anti-debugging actions here and directly jumps to function 0x003C14D7 (at 0x003c1651, see Figure 7). Function 0x003C14D7 disables the file protection service.For now, we just keep in mind that the _DEBUG_BUFFER is located at 0x00900000, which might be useful later. Note that the _DEBUG_BUFFER at 0x00900000 stores the information of Winlogon.exe!

Challenge 2. Trace to the RtlQueryProcessDebugInfo, and show the contents of the _DEBUG_MODULE_INFO.

5.1 Kill the File Protection Service
We now trace into function 0x003C14D7. Figure 8 shows its function body. As usual, the first thing you should look at is the input parameter of the function. By examining the first copy of instructions you would soon notice that the function is reading from two registers:
(1) EAX – it contains value 0x00900000 (note: this is the _DEBUG_BUFFER for winlogon.exe)
(2) ECX – it contains the _SYSTEM_MODULE_INFORMION of winlogon.exe.

We leave the  details of inferring the semantics of EAX, ECX parameters to you:
Challenge 2. How do you infer the data type of the EAX, ECX (as input parameters) of function 0x003C14D7?

Figure 8. Function body (Part I) of Function 0x3C14D7

The first part of the function is a loop (from 0x3C14EC to 0x3C150E, see Figure 8). Max++ reads the _DEBUG_MODULE_INFORMATION one by one, and compare its name with “sfc_os.dll” (see the function call at 0x3C14FB, and the stack contents in Figure 8). It jumps out of the loop once it finds the _DEBUG_MODULE_INFORMATION for “sfc_os.dll”.

The tricky part of the analysis of this part of the code is the lack of documentation for kernel data structures such as _DEBUG_BUFFER and _DEBUG_MODULE_INFORMATION.

Challenge 3. ***** Observe the instruction at 0x003C14E3, how would you infer that EDI now contains the number of modules, assuming that EAX has the PVOID ModuleInformation of _DEBUG_BUFFER? To simply search “typedef _DEBUG_BUFFER” gives you no more information than a PVOID type. You have to read the source code of RtlQueryProcessDebugInformation (at [3]) and LdrQueryProcessModuleInformationEx (at [4]) to figure it out.

Figure 9. Kill Threads Related to sfc_os.dll

Now, since Max++ identifies the DEBUG_MODULE_INFORMATION of sfc_os.dll, it is able to know the ImageBase address of sfc_os.dll. The next immediate action is a loop which searches all threads of winlogon.exe and kill those that are related to sfc_os.dll. The details are shown in Figure 9.

The basic control flow is:
  for each thread in winlogon.exe
        open thread
        get the base address of the thread
        if base address of the threadimageAddress of sfc_os  <= imageSize of sfc_os
                  suspend the thread

In summary: if the base address of a thread is inside the image range of sfc_os, it is killed.

We now list some challenges to help you understand the program logic in Figure 9.
Challenge 4. Observe the instruction at 0x3C1519 of Figure 9: MOV EAX, [ESI+8]. How would you infer that EAX will then contain the base of sfc_os.dll?

Challenge 5. Observe the instruction at 0x3C1562 of Figure 9: MOV EAX, [EBP-8]. How would you infer that EAX will then contain the base address of the thread?

Up to now, the sfc_os.dll module should be completely disabled and the OS is not performing security check on the driver files located on disk.

Section 6. The Infection
We now discuss the infection. This part of the code starts from 0x3C1A1B (right after it returns from the call of 0x3C15AB – to disable sfc_os.dll).

Figure 10 shows the first action taken by Max++. It makes sure that the randomly picked driver (e.g., mouclass.sys – note the name is changing everytime you run it) is not compressed. This is achieved by the call of zwFsControlFile.

Challenge 6. Observe the instruction at 0x003C1A63 (call of zwFsControlFile). Analyze the semantics of its function parameters.

Figure 10. Change File Property

Figure 11 displays the next actions of Max++: It creates a section object and then a view. Section object is used by Windows to share memory between processes. It can also be used to map a file to memory. Max++ uses the second case: it creates a section object that maps the contents of the driver file to infect (let it be mouclass.sys) to memory and then creates a view of the section object. You need to analyze the highlighted function calls to get more information, e.g., what is the starting address of the memory region of the file mapping and what is the size?

Figure 11. Create Memory Mapping of File
Figure 12. New Section Object

Challenge 7: Analyze the highlighted functions in Figure 11 and answer the following questions: (1) where is the mapped memory region of mouclass.sys (the file to infect), i.e., where it starts and where it ends? (2) how do you tell that the call zwCreateSection is not to create a shared memory region between two processes, but to map a file? (hint: you could click View->View Memory, and you will notice the starting address of the new section object created, see Figure 12)

Figure 13. Decoding and Copy to Mapped File

Next Max++ establishes a decoding table (see Figure 13), and copies some contents from its own stack (0x12XXXX range) to 0x00380000 (the mapped region for file mouclass.sys). Notice that the value 0x00380000 could vary in a different section. After a number of decoding rounds, we finally have the contents starting from 0x00380000 (see Figure 13, the memory dump section). You will notice the 0x4D5A, which is the magic word for “DOS header”.

Conclusion: Max++ performs a very simple infection approach. It just brutally overwrites the beginning of the driver file to infect and does not do any “weaving” of the LONG JUMP instructions to maintain the original logic of the infected file. Once the infected file is overwritten, it loses its original functions! 

The final action Max++ takes is to set up the checksum of the PE header and then writes the contents of the memory region to the file (by calling zwFlushVirtualMemory, see Figure 14).

Challenge 8. Analyze the highlighted area 2 of Figure 14, explain why the instruction is setting the PE checksum?

Figure 14. Set up Checksum and Write Contents Back

As shown in Figure 14 (highlighted area 4), Max++ immediately call zwLoadDriver() to load the newly updated file. If we want to delve into the logic of the new driver file, we would not be able to, because the driver will run in privileged mode while IMM is a ring3 debugger. In this case, we need kernel debugger WinDbg. We will move on to the analysis of the infected driver file in the next tutorial.

1. Microsoft, “CurrentControlSetServices subentry keys“, Availabe at
2.Unknown, “RtlQueryProcessDebugInformation as Anti-Dbg Trick”, Available at
3. Unofficial source code of RtlQueryProcessDebugInformation (
4. Unofficial source code of LdrQueryProcessModuleInformationEx (

By admin