This is a walkthrough of the Lab 11-2 from the book Practical Malware Analysis. The sample under analysis, Lab11-02.dll
, is a user-mode rootkit that performs inline hooking. The analysis of hooking mechanism is very interesting.
The samples for this lab can be downloaded from here.
Let’s start!
I’m going to perform some basic static analysis first.
Lab11-02.dll
MD5 | be4f4b9e88f2e1b1c38e0a0858eb3dd9 |
SHA1 | 79787427773dcce211e8e65e1156bd60535494ec |
SHA256 | df899256c4a9fc0e550c62b84ab9cb8acd8d18683f0a41c98ba83f0487d4766e |
Some interesting imports that the program uses are:
ADVAPI32.DLL
, RegOpenKeyEx
and RegSetValueEx
KERNEL32.DLL
, CopyFile
, CreateToolhelp32Snapshot
, OpenThread
, ResumeThread
, SuspendThread
, VirtualProtect
, LoadLibrary
, GetProcAddress
.The functions RegOpenKeyEx
and RegSetValueEx
suggest that this malware manipulates registry keys.
The function CopyFile
may indicate that the malware will make a copy of some file (maybe itself?).
The function CreateToolhelp32Snapshot
returns a snapshot of current processes, heaps, threads and modules; malware often uses this function to iterate through running processes or threads. The functions OpenThread
, ResumeThread
, SuspendThread
suggest that indeed the malware is manipulating threads. The function VirtualProtect
is used to modify the protection settings of a memory region; if this is done on a running thread, then the thread shall be suspended first in order to avoid malfunctions.
The functions LoadLibrary
, GetProcAddress
are an indication that the malware is using other functions from other DLLs, loading them at runtime.
This DLL exports a function named installer
. This may be some installation routing. I can install the malware executing:
rundll32.exe Lab11-02.dll,installer
These are some interesting strings:
THEBAT.EXE
OUTLOOK.EXE
MSIMN.EXE
RCPT TO: <
send
wsock32.dll
SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
AppInit_DLLs
\spoolvxx32.dll
\Lab11-02.ini
The string AppInit_DLLs
may indicate that this malware achieve persistence by using AppInit_DLLs: the DLLs listed in the value AppInit_DLLs
found in this registry key SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
, are loaded into every process that uses User32.dll
. (See Microsoft documentation on AppInit_DLLs for more information.)
The strings wsock32.dll
and send
suggest that this malware may perform network activity. Since there are no imports of networking functions, the malware would load networking DLLs via LoadLibrary
and GetProcAddress
(which indeed are present in the imports).
The string RCPT TO: <
may indicate that the malware uses the SMTP protocol somehow. It’s also worth noting that there are strings referring to the names of some email clients: The Bat, Outlook and Outlook Express.
We also see the name of a DLL spoolvxx32.dll
(maybe the malware hides itself under this name?) and a .ini file (maybe a configuration file?).
Let’s dig in the code with IDA Pro and OllyDbg.
installer
exported functionThis function is used to install the malware and make it persistent. Persistence is achieved by using AppInit_DLLs
. The program opens the registry key SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
and set the value AppInit_DLLs
to %SystemRoot%\System32\spoolvxx32.dll
.
Then the malicious DLL copies itself to %SystemRoot%\System32\spoolvxx32.dll
. The name of the source file to copy is stored in the global variable ExistingFileName
which was set by DLLMain
to the full path of the current module.
DLLMain
functionThe DLLMain
function reads from a file named Lab11-02.ini
located in the SystemDirectory (%SystemRoot%\System32\Lab11-02.ini
). The content of this file seems to be encrypted.
sub_100010B3
function (Sub_Decoder
)Since the bytes read from this file are passed to the function sub_100010B3
(at 0x100016CA
), I expect that this function contains some decryption routine - indeed the function contains a loop and bytes-manipulating instructions (like XOR
). Let’s analyze this function’s behavior in OllyDbg.
I will place a breakpoint at 0x100016CA
, when the function is called. After step-following the execution in the debugger I obtain the decrypted value read from the .ini file: billy@malwareanalysisbook.com
. This value is placed in byte_100034A0
, so I will rename this location to Email_Address
. Also I will rename the function sub_100010B3
to Sub_Decoder
.
sub_100014B6
function (Sub_InstallHook
)After the .ini file is decrypted, the DLLMain
calls the function sub_100014B6
(at 0x100016E2
) before terminating.
This function first calls a subroutine to get the full path of the current module (I renamed this subroutine to Sub_GetModuleFileNameWrapper
); then (at 0x100014D6
) it calls a subroutine to extract the executable name from the full path; next it places that name in var_4
and converts it to uppercase (at 0x100014F0
).
A series of string comparisions follows: the malware is comparing the name of the current executable (that it just obtained from the previous instructions) to those of three email clients: THEBAT.EXE
, OUTLOOK.EXE
and MSIMN.EXE
(the last one is Outlook Express).
Let’s keep in mind that this malicious DLL is loaded via AppInit_DLLs
: that means it is loaded into every process that loads User32.dll
. The Bat, Outlook and Outlook Express will surely do, but they are not the only processes that loads User32.dll
: the malware is checking the name of the executable it was loaded into to decide what to do next.
So when the malicious DLL is loaded by one of those email clients (and only by those), it will continue on with its malicious behavior.
Note: if I try to execute the DLL within OllyDbg I will most probably see LOADDLL.EXE as the executable name: this is used by OllyDbg to load and execute a DLL.
If the malware was loaded by any of those email clients, the program jumps to to 0x10001561
:
10001561 loc_10001561:
10001561 call sub_100013BD
10001566 push offset dword_10003484 ; int
1000156B push offset sub_1000113D ; int
10001570 push offset aSend ; "send"
10001575 push offset aWsock32_dll ; "wsock32.dll"
1000157A call sub_100012A3
1000157F add esp, 10h
10001582 call sub_10001499
This code block contains four functions to analyze!
sub_100013BD
function (Sub_SuspendThreads
)The function sub_100013BD
gets the CurrentProcessId
and passes it as a parameter to the function sub_100012FE
.
The function sub_100012FE
starts by getting the address of the OpenThread
function in kernel32.dll
, and saves it in var_4
(renamed to Address_OpenThread
). The subroutine sub_10001000
is a wrapper to LoadLibrary
and GetProcAddress
, so I will rename it to Sub_GetProcAddressWrapper
.
Next the program gets the CurrentThreadId
, saving it in var_28
(renamed to Var_CurrentThreadId
), and then calls CreateToolhelp32Snapshot
(at 0x10001333
) to get a snapshot of the running threads. It then cycles through the running threads: if the thread Id is not that of the current thread (comparision is made on the CurrentThreadId
at 0x10001360
) then it accesses the thread by calling OpenThread
at 0x10001375
, and suspends it by calling SuspendThread
at 0x10001389
.
So the function sub_100012FE
suspends all but the current thread. I will rename it to Sub_SuspendThreads_Inner
, and rename sub_100013BD
to Sub_SuspendThreads
.
sub_10001499
function (Sub_ResumeThreads
)The function sub_10001499
at the end of the code block, and its inner function sub_100013DA
, are the same as sub_100013BD
and its inner sub_100012FE
, but with one difference: their purpose is to resume the threads that were previously suspended. So I will rename them to (guess?) Sub_ResumeThreads
and Sub_ResumeThreads_Inner
.
So the malware is suspending the running threads and calling the function sub_100012A3
.
sub_100012A3
function (Sub_PlaceHook_Outer
)This function takes four arguments (those pushed on the stack in the code block above):
sub_100012A3("wsock32.dll", "send", sub_1000113D, dword_10003484)
The program here gets the address of the send
function in the wsock32.dll
module (using LoadLibrary
and GetProcAddress
); then at 0x100012F2
it calls sub_10001203
passing in three parameters:
sub_10001203( <Address of "send">, sub_1000113D, dword_10003484)
Let’s follow sub_10001203
down the rabbit hole: as we will see this function performs inline hooking of the send
function - so I will rename it to Sub_PlaceHook
. And I will rename the function sub_100012A3
to Sub_PlaceHook_Outer
.
sub_10001203
function (Sub_PlaceHook
)After the prologue, this function calculates the relative offset between the sub_1000113D
function, ie the “hooking” function performing the malicious behavior (as we’ll see shortly) - and thus renamed to Sub_Hooking
, and the function that gets hooked, ie send
; then it further subtracts 5 bytes, and places the result into var_4
.
10001209 mov eax, [ebp+Address_Sub_Hooking]
1000120C sub eax, [ebp+Address_send]
1000120F sub eax, 5
10001212 mov [ebp+var_4], eax
What’s happening here?
In order to hook the send
function, the malware needs to place a JMP instruction at the beginning of the send
function to jump to the hooking function that performs the malicious behavior. To place this JMP, it needs to calculate the relative offset, or displacement, for the jump.
When the CPU prepares to execute a relative JMP instruction, the new Instruction Pointer is calculated as follows:
IP(new) = IP(current) + 5 + displacement
Why 5? It is the length of the unconditional JMP instruction: the E9
opcode (1 byte) plus the 32-bit address (4 bytes displacement).
Since we are jumping from send
to Sub_Hooking
, the displacement of the relative jump is:
displacement = Address_Sub_Hooking - Address_send - 5
So now var_4
contains the displacement of the JMP instruction to jump from the send
to the hooking function. I will rename var_4
to Var_DisplaceToHooking
.
Let’s move on:
In order to place a hook a the beginning of the send
function, the malware first needs to change the protection settings of that memory region - (it also needs to suspend the running threads before modifying the memory!). The hook is 5 bytes long, so the malware needs to change the protection settings of the first 5 bytes starting from the address of send
.
To do this the malware calls VirtualProtect
:
10001215 lea ecx, [ebp+flOldProtect]
10001218 push ecx ; lpflOldProtect
10001219 push PAGE_EXECUTE_READWRITE ; flNewProtect
1000121B push 5 ; dwSize
1000121D mov edx, [ebp+Address_send]
10001220 push edx ; lpAddress
10001221 call ds:VirtualProtect
VirtualProtect
takes four parameters:
VirtualProtect(lpAddress, size, NewProtect, lpOldProtect)
The new protection is PAGE_EXECUTE_READWRITE
because write permission is needed. The old protection value is saved into flOldProtect
to be restored later.
Next, the program allocates 255 bytes of memory:
10001227 push 0FFh ; size_t
1000122C call malloc
10001231 add esp, 4
10001234 mov [ebp+var_8], eax
The address of the newly allocated memory is saved into var_8
.
What’s this memory region for?
This is the memory region that the malware uses to create a trampoline. The purpose of the trampoline is to jump back into the hooked function bypassing the hook itself! At the beginning of the trampoline the malware must also save the first 5 bytes of the send
function that were overwritten by the hook (the “stolen bytes”).
So I will rename var_8
to Address_Trampoline
, because it is the address of a memory region the serves the purpose of a trampoline.
In the next five instructions, the malware saves the address of the send
function in the first 4 bytes of the trampoline region, then it writes the value 5
into the next byte. I don’t know why… and I won’t care!
10001237 mov eax, [ebp+Address_Trampoline]
1000123A mov ecx, [ebp+Address_send]
1000123D mov [eax], ecx
1000123F mov edx, [ebp+Address_Trampoline]
10001242 mov byte ptr [edx+4], 5
Then the malware copies (memcpy
) the first 5 bytes of the send
function into the trampoline region pointed to by (Address_Trampoline
+ 5 bytes):
10001246 push 5 ; size_t
10001248 mov eax, [ebp+Address_send] ; source
1000124B push eax ; void *
1000124C mov ecx, [ebp+Address_Trampoline] ; destination
1000124F add ecx, 5
10001252 push ecx ; void *
10001253 call memcpy
10001258 add esp, 0Ch
Here the malware is assuming that the send
function’s first instructions align exactly on 5 bytes, which might not always be the case.
Immediately following in the trampoline region, the malware writes the byte 0xE9
:
1000125B mov edx, [ebp+Address_Trampoline]
1000125E mov byte ptr [edx+0Ah], 0E9h
What is this? E9
is the opcode of the unconditional JMP instruction; the 4 bytes that follow must be the displacement of the relative jump. Since we are jumping from the trampoline back into the send
function, the displacement is calculated as (the values shown here come from OllyDbg):
displacement = Address_send - Address_Trampoline - 0x0A
= 0x71AB4C27 - 0x003625A0 - 0x0A
= 0x7175267D
0x0A
is the sum of the length of the JMP instruction (5 bytes) and 5 more bytes to jump into the send
function in order to bypass the hook!
The 4-byte displacement is written immediately after the E9
opcode (starting at offset 0x0B
)>
10001262 mov eax, [ebp+Address_send]
10001265 sub eax, [ebp+Address_Trampoline]
10001268 sub eax, 0Ah
1000126B mov ecx, [ebp+Address_Trampoline]
1000126E mov [ecx+0Bh], eax
This is what gets written in the trampoline region:
003625A0 27 4C AB 71 05 8B FF 55
003625A8 8B EC E9 7D 26 75 71
003625B0
When the Instruction Pointer points at 0x003625AA
, i.e. at opcode E9
, the JMP will be executed; the new instruction pointer is then calculated as follows:
IP(new) = IP(current) + 5 + displacement
= 0x003625AA + 5 + 0x7175267D
= 0x71AB4C2C
This is exactly 5 bytes into the send function, so bypassing the hook!
We can see the same thing by disassembling the dump in OllyDbg:
003625AA -E9 7D267571 JMP WS2_32.71AB4C2C
Next the JMP opcode E9
is copied into the beginning of the send
function, followed by the displacement Var_DisplaceToHooking
to jump from the send
function to the hooking function:
10001271 mov edx, [ebp+Address_send]
10001274 mov byte ptr [edx], 0E9h
10001277 mov eax, [ebp+Address_send]
1000127A mov ecx, [ebp+Var_DisplaceToHooking]
1000127D mov [eax+1], ecx
Now a new call to VirtualProtect
restores the original protection settings on the memory region of the send
function.
Lastly, Address_Dword_10003484
is modified to point to 5 bytes into the trampoline, i.e. where the stolen bytes of the send
function were saved. I renamed Address_Dword_10003484
to Address_ToTrampoline
.
10001294 mov edx, [ebp+Address_Trampoline]
10001297 add edx, 5 ; 5 bytes into the trampoline
1000129A mov eax, [ebp+Address_ToTrampoline]
1000129D mov [eax], edx
So to wrap up so far, the function sub_100014B6
installs a hook at the beginning of the send
function - I will rename it to Sub_InstallHook
. The hook jumps to the hooking function which I’ll be analyzing in a moment. The hook installation routing also create a trampoline to jump back into the send
function bypassing the hook.
The last function left for analysis is the hooking function that actually performs the malicious behavior.
sub_1000113D
function (Sub_Hooking
)The function sub_1000113D
is the hooking function that performs the malicious behavior. It takes the same arguments as the hooked function send
(it can be redefined in IDA Pro by using Set Function Type):
send(SOCKET s, char * buf, int len, int flags)
The Sub_Hooking
function searches for the substring RCPT TO:
in the buffer associated to the socket.
If none is found then it jumps to location 0x100011E4
and calls the original send
function using the Address_ToTrampoline
(at 0x100011F4
). If that string is found, then the program builds the string RCPT TO: <billy@malwareanalysisbook.com>\r\n
and places it into the buffer.
In other words, the hooking function adds a new recipient to any email that is sent out using one of those email clients.
Then it calls Address_ToTrampoline
to jump back into the hooked function.
This is a diagram to show how the hooking works:
Let’s try running the malware and observing its behavior!
I will run Outlook Express and send an email to a fake SMTP server running on my REMnux host. I’m going to use ApateDNS to resolve every DNS name query to my REMnux host and INetSim as the fake SMTP server.
To install the malware I will manually place the .ini
file under %SystemRoot%\System32
and then run this command:
rundll32.exe Lab11-02.dll,installer
If I check in the registry, now the key SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
has AppInit_DLLs
set to %SystemRoot%\System32\spoolvxx32.dll
: that’s the location where the malware placed a copy of itself.
Next I start Outlook Express: looking at the loaded DLLs with Process Explorer, I can spot the malicious spoolvxx32.dll
.
Then I compose a new email and send it to johnny@malware.lab
. Looking at the SMTP server log, we can see that the message has two recipients indeed: the second one billy@malwareanalysisbook.com
was added by the malware.
connect
send: 220 mail.inetsim.org INetSim Mail Service ready.
recv: HELO remwindowsxp
send: 250 mail.inetsim.org
recv: MAIL FROM: <johnny@malware.lab>
send: 250 2.1.0 Ok
recv: RCPT TO: <billy@malwareanalysisbook.com>
send: 250 2.1.5 Ok
recv: RCPT TO: <buddy@somewhere.net>
send: 250 2.1.5 Ok
recv: DATA
send: 354 End data with <CR><LF>.<CR><LF>
recv: <(MESSAGE)> (505 bytes)
recv: .
info: Message id: <CD43BED5-ed51bfbe05aa0559b04dfd109370f10c1be1ba67@mail.inetsim.org>
If we attach to the Outlook Express process msimn.exe
with OllyDbg, we can see the hooked function send
in WS2_32.dll
. Notice the JMP
instruction right at the beginning to jump to the hooking function (0x1000113D
) in the malicious spoolvxx
DLL:
And this is the trampoline: the malware saved here the 5 bytes stolen from the hooked function (8B FF 55 8B EC
which indeed disassemble to those instructions) followed by a jump back into the hooked function to bypass the hook (to 0x71AB4C2C
):
That’s all for this lab!