Home > Articles
A Journey to the Center of the Rustock.B Rootkit
Last Update: 20th January 2007
"You try to look innocent, but what's behind the curtain?
Whatever you hide or pretend will be detected - this is certain!"
On 27th December 2006 I found a sample of the Rustock.B Rootkit
at www.offensivecomputing.net, which was only sparsely analyzed
at this time. I was keen to study its behaviour, as I’ve heard
a lot of stories about this infamous Rootkit. Rustock included several
techniques to obfuscate the driver which could be stumbling blocks
for the researcher. Analyzing the binary was quite fun. Recalling
the work I’ve done over the last few days, it is clear that
Rustock is quite different from most other Rootkits I’ve seen
in the past. It is not much because Rustock uses new techniques,
but rather because it combines dozens of known tricks from other
malware which makes it very effective.
This paper is divided into two main parts. In the first part I
wanted to extract the native Rootkit driver code but without the
use of kernel debuggers or other ring0 tools. The second part covers
the extraction over the last three stages but much faster and with
lesser efforts using the SoftICE debugger. Each part shows various
possibilities for solving the different problems facing the researcher
when analyzing Rustock. The techniques can also be useful in future
reversing sessions. All the tools I’ve used can be found in
the references. Some of them are free and others again are commercial,
like IDA Pro. Further all the binary dumps and IDA .idb files from
each stage are included in the package with this paper. Use caution
when reproducing the work described here. Consider employing a virtual
machine like VMware or Virtual PC and perform the analysis on an
isolated network to avoid the damage that could be caused by the
Rootkit. Use at your own risk!
3 Stage 1 - Drop from the Mother ship
First thing we have to do is to browse into the directory stage1
and unzip the file Rustock-Rootkit.B-Password-infected.zip. The
zipfile password is “infected”. Now we are ready to
Load the unpacked file rustock.exe into Ollydbg.
Right click and select:
Search for ---> All referenced text strings
The limited result may indicate that the binary is packed or obfuscated
in some way. The best idea in this case is often to employ a tool
like PEID or Protection-ID. Unfortunately, this time both tools
cannot determine the Compiler/Packer/Protector. It could be that
a proprietary obfuscation technique has been used. One of the indicators
that a file is packed or
obfuscated often is some unrecognized data, thus we start scrolling
down from the entry point at 0x401000 and strike a bonanza at address
0x401b82 (Figure 1). Place the cursor at this position and right
Find references to ---> Selected address (or just CTRL+R)
The references window (Figure 2) should show three hits now. We
choose the second one at address 0x40198d, because it’s the
most likely reference that jumps directly to the unrecognized data
(push 0x401b82/retn is the same as call 0x401b82). A good chance
that this is the end of the obfuscation code.
So why not setting a breakpoint (F2 at cursor position) here and
see what happens after we Run (F9) the code? (Figure 3).
After running the code, a breakpoint occurred at our address as
expected. Now press Step into (F7) two times and we should be located
at address 0x401b82 (Figure 4).
Hm, still doesn’t look like valid code, right?
No problem, right click
Analysis ---> Analyse code (or just CTRL+A)
and the result should look much better.
Use Step Over (F8) until you passed call 0x402092 at 0x401bac, which
does some API importing stuff). Ollydbg now should be able to show
you the API names to the relative addresses, e.g. CALL DWORD PTR:SS[EBP+8c3]
After reading some code, we notice that a file called “lzx32.sys”
is created at 0x401c7c (Figure 5). It is fairly telltale that this
is the kernel mode driver. So let us set another breakpoint at 0x401c7b
(F2) and Run (F9) the code again.
After the breakpoint occurred select EDI in the Registers window,
then right click Follow in Dump (Figure 6)
The register EDI points to the following string:
Confused of the “:” instead of “\” in the
Is it a mistake? Surely not, the driver just is created as Alternative
Data Stream (ADS). A nice method to hide the driver from easy detection,
because neither Windows Explorer nor cmd.exe will show you ADS streams.
This is only possible with special tools like Sysinternals streams.exe.
To simplify our analysis it’s a good idea to let the code
create a normal file. Therefore select the “: = 0x3a”
in the memory map (Figure 7) and patch it to “\ = 0x5c”
using right click
Binary ---> Edit (or just CTRL+E)
Lastly Step Over (F8) until the file was written (_lwrite) and closed
(_lclose) at address 0x401cc7.
As the aim of this paper is to describe how to deobfuscate/unpack
the driver, Ollydbg can be closed now and the first stage was mastered.
As a goody here is a short description for the folks, who are interested
what else is going on in the dropper code of Rustock.
1. If API Import fails, connect to: http://188.8.131.52/index.php?page=main
Delete lzx32.sys and Exit
2. Try to open the service control manager (if it fails go to 5)
3. Create Service PE386 (if it fails go to 5)
4. Start Service PE386 (if it fails go to 5 – if ok go to
5. Create service registry entries by hand as lzx32
6. Invoke ZwLoadDriver
7. Inject Rustock.exe into the Explorer process, create a remote
thread and Exit
4 Stage 2 – Kernel code vs PE-Tools
Welcome to Stage 2! After we have successfully detached the driver
we load it into IDA and see what is going on there. You can find
my detached driver code and .idb file in the directory “stage1”.
Hm, after running down some pages, it seems that there is more code
obfuscation fun waiting for us. ;)
When we try to load this binary into Ollydbg now, a Message box
pops up and tells us something like this:
File “original-dropped-lzx32_sys.sys” is a DLL. Windows
can’t execute DLLs directly. Launch LOADDLL.EXE?
Usually after clicking ‘yes’ the DLL gets loaded by
LOADDLL and stops at its entry point. But after selecting Run (F9)
the next Message box appears and informs us about the following:
Entry Point Alert
Module “ntoskrnl” has entry point outside the code (as
specified in the PE-Header). Maybe this file is selfextracting or
self-modifying. Please keep it in mind when setting breakpoints!
And the same Message for HAL.DLL. No problem, but after clicking
LOADDLL terminates with exit code 1001 and that’s it. :(
In these cases the best choice is to “fix” the PE-Files
So fire up PE-Tools and make the following modifications:
• Tools-->PE-Editor-->Select original-dropped lzx32_sys.sys
• Select “File Header”--->Characteristics--->unmark
the “dll” bit---> click OK (Figure 8)
• Leave “Image File Header Editor”
Select “Optional Header” and change the “Subsystem”
value to “2” (Windows GUI) ---> click OK (Figure
Select “Directories”--->”Import Directory”
and set its “RVA” and “Size” to “00000000”--->click
Save and leave PE-Tools (Remember the old values 0x00010000 and
0x000001cb. We have to reset these later in order to have a working
file!) (Figure 10)
So what have we done so far?
Before the settings were:
• A DLL
• A Native Executable
• Had Imports from Kernel Libraries NTOSKRNL.EXE and HAL.DLL
Now the settings are:
• No DLL
• A Windows GUI Application
• No Imports
Why can we do this?
The answer is easy. As long as the obfuscation code does not expect
any special data returned by imported kernel library functions,
it does not matter how we declare the PE-FILE. ;)
Therefore, after patching the PE-File behaviour we load up original
dropped lzx32_sys.sys again and - Eureka!
After running down some pages again we notice some unrecognized
data at address 0x116a4 again (Figure 11). Are the bells ringing?
Yep, we saw this stuff in the dropper code before. Why do not trying
the same trick again? (Figure 12)
We select address 0x116a4, right click
Find references to--->Selected address (Figure 13)
As the first hit in the references looks best (push 0x116a4/retn)
we choose this one (Figure 13), set a breakpoint using F2 (Figure
14) at address 0x110ce and Run (F9) the code.
When the breakpoint is reached press Step into (F7) two times. We
should be arrived at address 0x116a4 (Figure 15).
Use hotkey CTRL+A to display some human readable code.
As the code looks clearly less obfuscated now (Figure 16), it’s
time to dump the current state.
Click Plugins--->OllyDump--->Dump debugged process (Figure
Unmark “Rebuild Import” and click “Dump”
Cool, after saving the dump it is important to reset the old PE-File
• Setting the “DLL” bit in the “Characteristics”
• Setting the “Subsystem” to “Native”
in the “Optional Header” area
• Setting the “RVA” and “Size” of
the Import Directory field in the “Directories” area
to 0x00010000 and 0x000001cb
Ok, that is it for the second stage.
5 Stage 3 – Naked looks best
For the last stage, load your dumped file from stage 2 into IDA
or just use mine: stage2/lzx32-unobfuscated.idb
As you can see in Figure 19, you can find some comments within the
.idb file. This allows a better understanding about the code.
Before I start explaining what the code between 0x116a4 and 0x11abc
basically does, let’s have a short look at Figure 20.
Again, we have unrecognized data beginning at 0x11afc.
Unfortunately, there are no direct references in the code to this
area, like in the two stages before. :(
Now we need a strategy.
We should start reading some code, to get a clue how to solve our
Here’s a short description:
• Import some APIs from NTOSKRNL (ExAllocatePool, ExFreePool,
• Query all running System modules using ZwQuerySystemInformation/Subfunction
• Allocate Kernel memory
• Unpacking routine at 0x11788 ---> call sub_117d3 unpacks
code to new allocated Kernel memory
• Move unpacked code over packed code area, grab imports from
ntoskrnl.exe and hal.dll, destroy PE-Header (MZ, PE, e_lfanew) and
rebase API calls
• Free the kernel memory, that is no longer used
• JMP EAX at address 0x117c8 ---> execute the real naked
So, how can we grab the real driver without any kernel debugging
Why not just ripping the unpacking code at address 0x117d3 and then
dumping the whole data as a file?
A good idea especially before the PE-Header gets destroyed and the
driver code rebased. ;)
My small C Program called lzx32-laststage-unpacker in directory
stage3 exactly does this job (Figure 21).
Voila, last but not least we have a clean native driver that can
be analyzed very easily now. As a whole analysis of the complete
rootkit would go beyond the scope of this paper, here’s just
a link to an analysis of Rustock.B:
Basically the paper may end here, but I thought it might be also
of interest, how to do the same action like in all 3 stages with
a kernel debugger, but faster.
6 Speeddumping with SoftICE+ICEEXT
To fully understand what’s going on in the preparation, you
need to know that a special function in NTOSKNRL.EXE called IopLoadDriver
isn’t exported by default (next to others). If exported, this
function could be a very useful breakpoint for us.
To solve this problem, we need the proper .pdb file of NTOSKRNL.EXE
from the Microsoft server. Further, the downloaded .pdb file need
conversion to the proprietary SoftICE format .nms. Normally not
a big task, as SoftICE has its own Symbol retriever. The bad news
is that this tool always sucks for me. :(
But why despair, if there’s another way!
The first thing we have to do is to leech the “Windows Debugging
Tools” from the Microsoft website (Link can found in the references)
and installing them.
Based on the fact that you have installed Driverstudio/SoftICE as
well, edit the file %systemroot%\system32\drivers\winice.dat now.
Change value SYM=2048 (default is 512)
If you are not familiar with SoftICE you should read my paper:
The big SoftICE howto (see references)
Or if you are a WinDBG freak, use this one.
Do the following next steps:
cd %ProgramFiles%\Debugging Tools for Windows
symchk.exe %systemroot%\system32\ntoskrnl.exe /s
net start iceext
So, what have we done so far?
• Created a symbol directory
• Switched to the Windows Debugging Tools directory
• Retrieved the proper NTOSKRNL.PDB from the Windows website
• Copied the .pdb from the symbol directory into the Windows
• Switched to the SoftICE directory
• Started the SoftICE extension ICEEXT and thus SoftICE too
(which is quite tautological)
• Converted the .pdb file to a .nms file
Ok, we are now prepared to start the debugging session now.
6.2 Debugging and Dumping
Before we fire up the Rustock.exe we need to adjust two settings
in the SoftICE window first. So enter SoftICE using CTRL+D and set
Leave the debugger using x and execute Rustock.exe
The debugger window should have been popped up now at the entry
point of IopLoadDriver (Figure 23)
Next switch to the code window using F6 and scroll down until you
code like this (Figure 24)
CMP EAX, EBX
MOV [EBP-54], EAX
PUSH DWORD PTR [EBP-70]
On my machine IopLoadDriver+1c1 (Windows XP SP2 German) and address
Leave the code windows with F6 and set a breakpoint at the address
were the following instruction is found and run the code using x.
PUSH DWORD PTR [EBP-70]
When the breakpoint occurred at PUSH DWORD PTR [EBP-70] enter:
As you can see in Figure 25, ebp-70 has a pointer to the start of
the image to the Rootkit driver.
Let us grab some PE section info about the driver.
So what do we know about the image now?
ImageStart ---> 0xf60ee000
Size: 0x110ce ---> (reloc_addr+size) - ImageStart
EntryPoint ---> ImageStart + (*(e_lfanew)+28h) = 0xf60e1000
As we know from the former debugging session with Ollydbg the end
address of the first deobfuscation was at EntryPoint+0xce. So, it’s
a good idea to set our next breakpoint at this address (Figure 26)
and run the code again.
After the breakpoint is reached, use the trace option 2 times using
t and you should see some well-known code (Figure 27).
The last breakpoint that is left, is the one behind the unpacking
routine, we discussed in stage 3. And if you scroll down a little
bit in the SoftICE code window you should find the unpacker at offset
0x1788, thus we set the breakpoint right behind it, on my machine
at 0xf60e178d and run code the again.
So, after the breakpoint occurred and unpacking was done let’s
see what we find at EDI now (Figure 28).
Yep, it points to the unpacked and untouched image we want to dump.
Therefore the last thing what’s left is using the dumping
tool of IceExt.
!dump \??\c:\lzx32-native.sys 81967004 8880
That’s it folks! To clean your drive from Rustock.B again,
just use the fine Rootkit-Detection-Tool called RkUnhooker (see
After studying this paper the reader now should have a better
understanding what different approaches can lead to success when
analyzing an obfuscated/packed driver. You may rest assured that
reverse engineering Malware is getting harder in future. Therefore,
being prepared with some armory and tricks is essential. I hope
you enjoyed this paper a little and I would be glad about some constructive
IDA Pro 184.108.40.2069
Windows Debugging Tools v220.127.116.11
The big SoftICE howto v1.1
Big thanks go to Val Smith, Marc Schönefeld, FX and Olli Koen
for reviewing this paper!
Reproduced with kind permission of Frank Boldewin