Background
Recently, a few reports have come out regarding an interesting rootkit named Uroburos, Snake, or Turla. G-Data first broke the story here. BAE Systems released a follow up report with additional details here. Based on the information in the BAE Systems report, I decided to take a look at one of the samples with the memory forensics toolkit volatility. The sample I looked at was: 626576e5f0f85d77c460a322a92bb267.Hook Analysis
Finding a function hook
In the BAE Systems report, one of the system hooks was IoCreateDevice(). Let's take a look at this function in an infected memory image. A useful volatility plugin to identify the exact memory location of this exported function, is enumfunc. This plugin is not included in the standard download of volatility, but instead must be downloaded from the contrib directory. Let's find the memory location for the IoCreateDevice() function.$ python2 /opt/volatility-2.3.1/vol.py -f uroburos.vmem --profile=WinXPSP3x86 enumfunc -K -E | grep IoCreateDevice
Volatility Foundation Volatility Framework 2.3.1
<KERNEL> Export ntoskrnl.exe 340 0x000000008056aad6 IoCreateDevice
Next, let's fire up volshell to take a look at how this function is hooked:
$ python2 /opt/volatility-2.3.1/vol.py -f uroburos_mod.vmem --profile=WinXPSP3x86 volshell
Volatility Foundation Volatility Framework 2.3.1
Current context: process System, pid=4, ppid=0 DTB=0x334000
Welcome to volshell! Current memory image is:
./uroburos_mod.vmem
To get help, type 'hh()'
>>> dis(0x000000008056aad6)
0x8056aad6 6a01 PUSH 0x1
0x8056aad8 cdc3 INT 0xc3
0x8056aada 90 NOP
0x8056aadb 81ec90000000 SUB ESP, 0x90
0x8056aae1 a140ae5480 MOV EAX, [0x8054ae40]
0x8056aae6 8945fc MOV [EBP-0x4], EAX
Cool, it looks like 0x1 is being pushed onto the stack which is probably
some way to identify which function hook to handle. Next, INT C3
will change the execution to the interrupt handler located at interrupt
0xc3. Let's now see where INT 0xc3 will take us. Let's use volatility
to show us the Interrupt Descriptor Table (IDT):
$ python2 /opt/volatility-2.3.1/vol.py -f uroburos.mem --profile=WinXPSP3x86 idt
Volatility Foundation Volatility Framework 2.3.1
CPU Index Selector Value Module Section
------ ------ ---------- ---------- -------------------- ------------
[snip]
0 BC 0x8 0x8053d0b8 ntoskrnl.exe .text
0 BD 0x8 0x8053d0c2 ntoskrnl.exe .text
0 BE 0x8 0x8053d0cc ntoskrnl.exe .text
0 BF 0x8 0x8053d0d6 ntoskrnl.exe .text
0 C0 0x8 0x8053d0e0 ntoskrnl.exe .text
0 C1 0x8 0x806d1984 hal.dll .text
0 C2 0x8 0x8053d0f4 ntoskrnl.exe .text
0 C3 0x8 0x896a3670 UNKNOWN
0 C4 0x8 0x8053d108 ntoskrnl.exe .text
0 C5 0x8 0x8053d112 ntoskrnl.exe .text
0 C6 0x8 0x8053d11c ntoskrnl.exe .text
0 C7 0x8 0x8053d126 ntoskrnl.exe .text
0 C8 0x8 0x8053d130 ntoskrnl.exe .text
[snip]
Interesting, INT C3 handles the interrupt in an "UNKNOWN" module. Typically, I would have run the volatility plugin apihooks to get this far, but unfortunately, the apihooks plugin currently does not support inline interrupt hooks. Let's change that!
Changing apihooks.py
Within the apihooks.py plugin script, we can find a function named check_inline(). We can see the logic for the typical inline hooks which look for calls outside the current module, unconditional jmps, push/ret, etc. Unforunately, this rootkit does not use any of those methods. After modifying some of the code, I added the following logic to handle inline interrupt hooks:
After making these modifications, I reran the apihooks plugin:
$ python2 /opt/volatility-2.3.1/vol.py -f uroburos.vmem --profile=WinXPSP3x86 apihooks -P
Volatility Foundation Volatility Framework 2.3.1
************************************************************************
Hook mode: Kernelmode
Hook type: Inline/Trampoline
Victim module: ntoskrnl.exe (0x804d7000 - 0x806cf580)
Function: ntoskrnl.exe!IoCreateDevice at 0x8056aad6
Hook address: 0x896a3670
Hooking module: <unknown>
Disassembly(0):
0x8056aad6 6a01 PUSH 0x1
0x8056aad8 cdc3 INT 0xc3
0x8056aada 90 NOP
0x8056aadb 81ec90000000 SUB ESP, 0x90
0x8056aae1 a140ae5480 MOV EAX, [0x8054ae40]
0x8056aae6 8945fc MOV [EBP-0x4], EAX
0x8056aae9 8b4508 MOV EAX, [EBP+0x8]
0x8056aaec 89 DB 0x89
0x8056aaed 45 INC EBP
Disassembly(1):
0x896a3670 90 NOP
0x896a3671 90 NOP
0x896a3672 90 NOP
0x896a3673 90 NOP
0x896a3674 90 NOP
0x896a3675 90 NOP
0x896a3676 90 NOP
0x896a3677 90 NOP
0x896a3678 90 NOP
0x896a3679 90 NOP
0x896a367a 90 NOP
0x896a367b 90 NOP
0x896a367c 90 NOP
0x896a367d 90 NOP
0x896a367e 90 NOP
0x896a367f 90 NOP
0x896a3680 6a08 PUSH 0x8
0x896a3682 6888366a89 PUSH DWORD 0x896a3688
0x896a3687 cb RETF
************************************************************************
Hook mode: Kernelmode
Hook type: Inline/Trampoline
Victim module: ntoskrnl.exe (0x804d7000 - 0x806cf580)
Function: ntoskrnl.exe!IofCallDriver at 0x804ee120
Hook address: 0x896a3670
Hooking module: <unknown>
Disassembly(0):
0x804ee120 6a00 PUSH 0x0
0x804ee122 cdc3 INT 0xc3
0x804ee124 90 NOP
0x804ee125 90 NOP
[snip]
Following the hook
At this point, we can now follow the instructions that are handling these hooks to further analyze this rootkit. Let's jump back into volshell and find the function in the driver that is handling the IoCreateDevice() hook.
>>> dis(0x000000008056aad6, 0xb)
0x8056aad6 6a01 PUSH 0x1
0x8056aad8 cdc3 INT 0xc3
0x8056aada 90 NOP
0x8056aadb 81ec90000000 SUB ESP, 0x90
>>> dis(0x896a3670, 0x18)
0x896a3670 90 NOP
0x896a3671 90 NOP
0x896a3672 90 NOP
0x896a3673 90 NOP
0x896a3674 90 NOP
0x896a3675 90 NOP
0x896a3676 90 NOP
0x896a3677 90 NOP
0x896a3678 90 NOP
0x896a3679 90 NOP
0x896a367a 90 NOP
0x896a367b 90 NOP
0x896a367c 90 NOP
0x896a367d 90 NOP
0x896a367e 90 NOP
0x896a367f 90 NOP
0x896a3680 6a08 PUSH 0x8
0x896a3682 6888366a89 PUSH DWORD 0x896a3688
0x896a3687 cb RETF
>>> dis(0x896a3688, 0x29)
0x896a3688 fb STI
0x896a3689 50 PUSH EAX
0x896a368a 51 PUSH ECX
0x896a368b 0fb6442414 MOVZX EAX, BYTE [ESP+0x14]
0x896a3690 8b4c2418 MOV ECX, [ESP+0x18]
0x896a3694 894c2414 MOV [ESP+0x14], ECX
0x896a3698 8b0d506c6c89 MOV ECX, [0x896c6c50]
0x896a369e 8d04c1 LEA EAX, [ECX+EAX*8]
0x896a36a1 8b4804 MOV ECX, [EAX+0x4]
0x896a36a4 894c2418 MOV [ESP+0x18], ECX
0x896a36a8 59 POP ECX
0x896a36a9 8b00 MOV EAX, [EAX]
0x896a36ab 870424 XCHG [ESP], EAX
0x896a36ae c20c00 RET 0xc
>>> dd(0x896c6c50, 1)
896c6c50 89a2d800
>>> dd(0x89a2d800+1*8, 1)
89a2d808 8963a020
>>> dis(0x8963a020, 0xb)
0x8963a020 55 PUSH EBP
0x8963a021 8bec MOV EBP, ESP
0x8963a023 83ec18 SUB ESP, 0x18
0x8963a026 e875fd0100 CALL 0x89659da0
Now we know the function that is handling the hook. At this point, it looks like we are going to need to dump this driver out of memory to take a look at statically with IDA.
Dumping the Driver
Tracking down the driver in memory
Running the volatility plugin modlist, doesn't seem to show us what we are looking for. No modules appear to be in the memory space we have identified for the rootkit driver. Let's see what else we can do!
The driver seems to take up a fair amount of space in memory. Let's start searching backwards in memory at the lowest address we have identified so far. We are looking for a PE header. It looks like is 0x8963a020 a good place to start. Let's start looking backwards 0x6000 bytes. Yes, I cheated, I already know how far back we should look :).
>>> db(0x8963a020-0x6000, 0x6000)
0x89634020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x89634030 00 00 00 00 00 00 00 00 00 00 00 00 d0 00 00 00 ................
0x89634040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th
0x89634050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is.program.canno
0x89634060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t.be.run.in.DOS.
0x89634070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$.......
0x89634080 b2 4e 55 e7 f6 2f 3b b4 f6 2f 3b b4 f6 2f 3b b4 .NU../;../;../;.
0x89634090 f6 2f 3a b4 26 2f 3b b4 af 0c 28 b4 ff 2f 3b b4 ./:.&/;...(../;.
0x896340a0 d1 e9 46 b4 f4 2f 3b b4 d1 e9 4a b4 74 2f 3b b4 ..F../;...J.t/;.
0x896340b0 d1 e9 41 b4 f7 2f 3b b4 d1 e9 43 b4 f7 2f 3b b4 ..A../;...C../;.
0x896340c0 52 69 63 68 f6 2f 3b b4 00 00 00 00 00 00 00 00 Rich./;.........
0x896340d0 00 00 00 00 4c 01 05 00 e7 eb 14 51 00 00 00 00 ....L......Q....
0x896340e0 00 00 00 00 e0 00 02 21 0b 01 08 00 00 00 07 00 .......!........
0x896340f0 00 72 02 00 00 00 00 00 40 d1 00 00 00 10 00 00 .r......@.......
[snip]
Exciting, we found the DOS header! Now, let's just go back a little further, because it looks like we are missing the 'MZ'.
>>> db(0x89634000, 0x100)
0x89634000 00 00 00 00 03 00 00 00 04 00 00 00 ff ff 00 00 ................
0x89634010 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
0x89634020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x89634030 00 00 00 00 00 00 00 00 00 00 00 00 d0 00 00 00 ................
0x89634040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th
0x89634050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is.program.canno
0x89634060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t.be.run.in.DOS.
0x89634070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$.......
0x89634080 b2 4e 55 e7 f6 2f 3b b4 f6 2f 3b b4 f6 2f 3b b4 .NU../;../;../;.
0x89634090 f6 2f 3a b4 26 2f 3b b4 af 0c 28 b4 ff 2f 3b b4 ./:.&/;...(../;.
0x896340a0 d1 e9 46 b4 f4 2f 3b b4 d1 e9 4a b4 74 2f 3b b4 ..F../;...J.t/;.
0x896340b0 d1 e9 41 b4 f7 2f 3b b4 d1 e9 43 b4 f7 2f 3b b4 ..A../;...C../;.
0x896340c0 52 69 63 68 f6 2f 3b b4 00 00 00 00 00 00 00 00 Rich./;.........
0x896340d0 00 00 00 00 4c 01 05 00 e7 eb 14 51 00 00 00 00 ....L......Q....
0x896340e0 00 00 00 00 e0 00 02 21 0b 01 08 00 00 00 07 00 .......!........
0x896340f0 00 72 02 00 00 00 00 00 40 d1 00 00 00 10 00 00 .r......@.......
Huh? It looks like the 'MZ' and even the 'PE' magic signatures are not there :(. This means that moddump won't want to dump the module for us. Let's fix that!
Patching memory
Volatility has a handy plugin for this sort of situation called patcher. In order to run this plugin, we need to write an xml file to fix up the PE header. Something like this will do:
Essentially, this will search at the beginning of each page boundary for the beginning bytes of the driver we found in memory and insert the magic signature for a properly structured PE header.
$ python2 /opt/volatility-2.3.1/vol.py -f uroburos_mod.vmem --profile=WinXPSP3x86 patcher -w -x patchdriver.xml
Volatility Foundation Volatility Framework 2.3.1
Write support requested. Please type "Yes, I want to enable write support" below precisely (case-sensitive):
Yes, I want to enable write support
Calibrating for speed: Reading patch locations per page
Patching Fix Driver MZ Header at page 9634000
Seems like it worked! Let's check to be sure:
>>> db(0x89634000, 0x100)
0x89634000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 MZ..............
0x89634010 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
0x89634020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x89634030 00 00 00 00 00 00 00 00 00 00 00 00 d0 00 00 00 ................
0x89634040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th
0x89634050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is.program.canno
0x89634060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t.be.run.in.DOS.
0x89634070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$.......
0x89634080 b2 4e 55 e7 f6 2f 3b b4 f6 2f 3b b4 f6 2f 3b b4 .NU../;../;../;.
0x89634090 f6 2f 3a b4 26 2f 3b b4 af 0c 28 b4 ff 2f 3b b4 ./:.&/;...(../;.
0x896340a0 d1 e9 46 b4 f4 2f 3b b4 d1 e9 4a b4 74 2f 3b b4 ..F../;...J.t/;.
0x896340b0 d1 e9 41 b4 f7 2f 3b b4 d1 e9 43 b4 f7 2f 3b b4 ..A../;...C../;.
0x896340c0 52 69 63 68 f6 2f 3b b4 00 00 00 00 00 00 00 00 Rich./;.........
0x896340d0 50 45 00 00 4c 01 05 00 e7 eb 14 51 00 00 00 00 PE..L......Q....
0x896340e0 00 00 00 00 e0 00 02 21 0b 01 08 00 00 00 07 00 .......!........
0x896340f0 00 72 02 00 00 00 00 00 40 d1 00 00 00 10 00 00 .r......@.......
Yeah, that's what we were looking for!
Finally, dumping the driver
Now that the memory is patched with the correct PE header, the driver can now be dumped from memory:
$ python2 /opt/volatility-2.3.1/vol.py -f uroburos_mod.vmem --profile=WinXPSP3x86 moddump -b 0x89634000 -D .
Volatility Foundation Volatility Framework 2.3.1
Module Base Module Name Result
----------- -------------------- ------
0x089634000 UNKNOWN OK: driver.89634000.sys
We are almost done. When we used moddump, it didn't fix up the ImageBase, so when we go to look at it in IDA, all the offsets will be wrong. This can easily be fixed a number of ways, but here's an easy way with the pefile library for python:
>>> import pefile
>>> pe = pefile.PE('driver.89634000.sys')
>>> hex(pe.OPTIONAL_HEADER.ImageBase)
'0x10000'
>>> pe.OPTIONAL_HEADER.ImageBase = 0x89634000
>>> pe.write(filename='driver.89634000_mod.sys')
Alright, now you can load the driver in IDA for a deeper static analysis!
Hope you enjoyed!