Thursday, March 20, 2014

Uroburos Rootkit Hook Analysis and Driver Extraction

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]

Success!

If you want to download the modified version of apihooks, check it out here.

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!