Log in

View Full Version : Win32.Sinowal MBR rootkit


sapu
May 11th, 2010, 06:02
MALWARE/BIOHAZARD

For anyone who could be interested into MBR rootkits:
The zip does not contains the infected MBR (it was dumped from disk after removal), but the bootloader code and the injected driver are still here, ready to be analyzed (see code_* and driver_*.bin).
The driver seems quite obfuscated, so no luck with disassembling... any suggestion about how to analyze / de-obfuscate it ?

Zip password: virus

Silkut
May 11th, 2010, 14:18
Next time please stick to all the rules edicted here: http://www.woodmann.com/forum/showthread.php?9907-Malware-Forum-RULES

It is important to:
1/ Clearly state (big red letters) the content may be harmful
2/ Password protect the archive (you did it so it's okay)
3/ Rename the file extension to a non-clickable one to avoid infection and spreading.

For the record, topics missing one of the fore-mentionned rule will be deleted.

Thanks for posting it though.

Kayaker
May 11th, 2010, 22:05
Quote:
[Originally Posted by sapu;86456]The driver seems quite obfuscated, so no luck with disassembling... any suggestion about how to analyze / de-obfuscate it ?


Hi

Nice dump of the files. I've looked at the driver a little bit, there seems to be one particular "style" of obfuscation seen throughout that causes a particular problem in the IDA analysis, but can be fixed individually fairly easily.

You want as clean a disassembly as possible before analyzing anything, which means you want to be able to right click on each function call and tell IDA to Create Function and have it accurately define all the parameters and local variables. The obfuscation hinders that.


You'll see stuff like this
add eax, 74503002h
interspersed with redundant push/pops and other useless instructions. They can basically be ignored, keep going until you reach a branch instruction.

There are groups of 3 bytes all over the place which either don't disassemble, or do disassemble incorrectly, but usually corrupt the jump xrefs:

Code:

:00010DF1 77 03 ja short near ptr loc_10DF5+1
:00010DF3 32 20 xor ah, [eax]
:00010DF5
:00010DF5 loc_10DF5: ; CODE XREF: :00010DF1
:00010DF5 A3 E8 65 FD FF mov ds:0FFFD65E8h, eax


So instead, U(ndefine) the problem instructions, skip the 3 garbage bytes and type C(ode) to reassemble again beginning at the correct offset. This will fix the jump xref:

Code:

:00010DF1 77 03 ja short loc_10DF6
:00010DF1 ; ---------------------
:00010DF3 32 db 32h ; 2
:00010DF4 20 db 20h
:00010DF5 A3 db 0A3h ; ú
:00010DF6 ; ---------------------
:00010DF6
:00010DF6 loc_10DF6: ; CODE XREF: :00010DF1
:00010DF6 E8 65 FD FF FF call loc_10B60



Next, you should select Edit/Patch Program/Change Byte and convert those 3 bytes to 90h 90h 90h and reassemble them as NOPS. If you don't, IDA may complain they are undefined bytes when next analysed.

When you fix up ALL those 3 byte sequences within a function, and anything else that doesn't look right, then you can go back and use Create Function and IDA should resolve it properly. Name the function to something and go onto the next one.


To begin with, define the entry point Start function as a regular DriverEntry function, pDriverObject is an important landmark.
int __stdcall DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath)


The PsCreateSystemThread thread seems like it might decrypt and call a function in the data section, passing two values from the StartContext parameter and a pDriverObject. This is probably where the real action takes place.

Not sure if the decryption can be figured out statically, it might be necessary to load the driver and step through it, breaking on both DriverEntry and the PsCreateSystemThread thread. Some finagling with Softice/remote WinDbg and a driver loader should allow live tracing and really suss this thing out.

Thanks for the post,
Kayaker

Kayaker
May 12th, 2010, 22:12
Well this is interesting. While this looks like a relatively normal driver, there's something peculiar about how it must be loaded and what the starting parameters are.

I had said to define the Start routine as a normal DriverEntry, which would usually be logical. However I wasn't *really* comfortable making that statement because it didn't look like the code handling the pDriverObject parameter made any sense with that assumption.

To confirm I live traced the driver in Softice (fix PE Checksum first), loading it as a normal driver.

There is a function right at the start, (which is also used in the SystemThread, as well as another function which references ntoskrnl.exe and hal.dll), which takes the parameter which would "normally" be pDriverObject in a regular loaded driver, and checks if it's a PE file image.

Huh? That does not compute. If that param is not the start of a PE image, driver loading fails.

Code:

:00010B7B mov eax, [ebp+DriverObject] // can't really be DriverObject
:00010B7E movzx eax, [eax+DRIVER_OBJECT.Type]
..
:00010BA3 cmp eax, 5A4Dh // MZ
:00010BA8 jz OK
:00010BAA xor al, al
:00010BAC jmp return
:00010BAE ; ------------------------------------
:00010BAE
:00010BAE OK: ; CODE XREF: CheckForPE+48
:00010BAE mov eax, [ebp+DriverObject]
:00010BB1 mov ecx, [ebp+pDriverObject]
:00010BB4 add ecx, [eax+3Ch]

:00010BBD cmp dword ptr [eax], 4550h // PE
:00010BC3 jz short OK2
:00010BC5 xor al, al
:00010BC7 jmp return


Well this obviously doesn't make any sense, so the first parameter passed to DriverEntry *cannot* be a DRIVER_OBJECT structure, so therefore it's not loaded in a normal way (nt!_IoplLoadDriver, SystemLoadAndCallImage, etc.), or at least isn't designed as a standard kernel driver with the usual expected starting parameters.


I'll go out on a limb here and say that this driver is probably executed by directly calling it's entry point from some other kernel code. The description of the Stealth MBR rootkit at
http://www2.gmer.net/mbr/
seems to confirm that method.

It's unknown what the parameters on the stack at driver loading might be, other than the first param must be a PE image in this particular case.

Might the loader code be part of the other dump files?

sapu
May 13th, 2010, 02:55
Quote:
[Originally Posted by Kayaker;86498]Might the loader code be part of the other dump files?


The loader code is not complete since it was dumped after virus removal, but there is still something interesting in the file 'code_*.bin'.
It seems composed by 2 sections:
- 16-bit code (from offset 0000 to 0181) that copy itself from offset 003F to address 7D80:0000 and hooks something to that address.
- 32-bit code from 0182 to 0623, that is probably going to replace part of ntldr (at offset 007F the code looks for pattern 'C4 02 E9 00 00 E9 FD FF', that is found inside ntldr)
Interesting how it could create the string "\??\PhisicalDrive0" by pushes at offset 047A...

Kayaker
May 14th, 2010, 01:11
A little further info in case anyone is interested.

The obfuscation in the driver that prevents a good disassembly can be fixed with a small idc script. I ended up doing most of it manually before figuring out it was consistent enough for a script.

Every JA instruction is followed by 3 garbage bytes which generally don't resolve into real instructions and end up corrupting the jump xrefs and prevents accurate function definition. The following should fix it.

Scan code section for bytes 0F 87 xx xx xx xx
Change next 3 bytes to 90h
Reanalyze

Code:

:00010C57 0F 87 BB 00 00 00 ja loc_10D18
:00010C5D 90 nop
:00010C5E 90 nop
:00010C5F 90 nop


As for the parameters at DriverEntry...

The first parameter is a PE image as I mentioned, at the moment I'm going to assume it's the base address of the driver itself. The second parameter - I have a feeling it's PLIST_ENTRY PsLoadedModuleList.

I'm going to reload the driver in Softice and replace in memory the usual DriverEntry parameters with pointers to these new values. It should be enough to get the code executing, and if valid, to get the SystemThread running and hopefully a dump of the encrypted driver code.

evaluator
May 14th, 2010, 07:09
also before JA are trash-instructions, which guarantee JA

push esi
push ebx
mov ebx,ebp
nop
pop ebx
xor esi,esi
push esi
add esi,esi
or esi,edi
pop esi
add esi,03E1F8A5E
push ebx
inc ebx
dec ebx
nop
pop ebx
sub esi,0002F16DB
pop esi
ja .0000109F5

sapu
May 14th, 2010, 12:13
Quote:
[Originally Posted by evaluator;86512]also before JA are trash-instructions, which guarantee JA

This should be the result after de-obfuscation, removal of dead code and re-join of 'ja' code pieces... almost ready to be analyzed.
I already set-up an user-mode debug environment... the code crashes when scanning for imported modules ('hal.dll' and 'ntoskrnl.exe'), but with some debugger step-out it's still possible to trace the remaining code...

Don't know if i'll need to put the 'big red letter' statement again, but just in case...
MALWARE/BIOHAZARD
Zip password: virus

Kayaker
May 14th, 2010, 17:18
That's an interesting rip/loader sapu

You might be able to run the decryption routine as an independant function as well in usermode. (ref. 17298 / 3E830 / 1729C), and at least step into the decrypted code.


If we tentatively define DriverEntry as
DriverEntry(ULONG BaseAddress, PLIST_ENTRY PsLoadedModuleList)
or as you have it in usermode as
DriverEntry(void *arg1, void *arg2);
where arg1 = GetModuleHandle(NULL)

The StartContext parameter of PsCreateSystemThread, which is passed to the new thread, is an allocated buffer pointer containing the values of BaseAddress (arg1) and PsLoadedModuleList (dummy arg2)

Code:

.code:000104D8 mov eax, [ebp+StartContext]
.code:000104DB mov ecx, [ebp+BaseAddress]
.code:000104DE mov [eax], ecx
.code:000104E0 mov eax, [ebp+StartContext]
.code:000104E3 mov ecx, [ebp+PsLoadedModuleList]
.code:000104E6 mov [eax+4], ecx

.code:000104E9 push [ebp+StartContext] ; StartContext
.code:000104EC push offset SystemThread ; StartRoutine
...
.code:000104FD call ds:PsCreateSystemThread


After the decryption routine in the SystemThread it looks like the new code is run in a loop here

Code:

.code:00010665 push [ebp+BaseAddress]
.code:00010668 push [ebp+PsLoadedModuleList]
.code:0001066B push [ebp+var_8] // ?
.code:0001066E call [ebp+var_10] // Decrypted code


I'm really curious as to what this encrypted code is now.

sapu
May 15th, 2010, 10:55
Quote:
[Originally Posted by Kayaker;86515]I'm really curious as to what this encrypted code is now.

Kayaker, thank you for the info.
It worked exactly as you said... second parameter was the loaded module list.
Here attached is the updated test program (only the modified .c file, all the others remains unchanged from previous .zip) and ...
... the decrypted driver code (dumped from memory just before relocation), ready to be disassembled !!!

MALWARE/BIOHAZARD
Zip password: virus

Kayaker
May 15th, 2010, 23:21
Nicely done sapu. Quite cool actually, a nice example of running moderately non-specific driver code in usermode. You even recreated your own ExAllocatePoolWithTag as a wrapper of VirtualAlloc to give the encrypted driver a place to decrypt

I see you also got the embedded usermode dll in the dump (extract 5000 bytes beginning at 87400 and save to new file). Presumably it's injected up some poor processes behind at some point...


It took me several tries to get to the right spot in kernelmode tracing to get a clean dump (identical to yours). If you allow the relocation code in the original MBR driver to execute it corrupts several instruction addresses when running it as a regular driver. I was thinking that zeroing the .reloc table might get around that without having to prevent the code itself from running.
The relocations may have something to do with it originally running with respect to the NTLDR base, and certain fixups need to be made?


I've now got a VMWare snapshot of SoftIce poised to step into the EntryPoint of the decrypted NDIS driver. The final instructions can now be defined:

Code:

:00010708 push 0
...
:00010635
:00010635 loc_10635: ; CODE XREF: SystemThread+20C
:00010635 push eax ; Optional_Header
:00010636 push 0
:00010638 push [ebp+BaseAddress_Ndis_Driver]
:0001063B call CheckForPE
:00010640 movzx eax, al
:00010643 test eax, eax
:00010645 jz short loc_10655 ; no jump
:00010647 mov eax, [ebp+Optional_Header]
:0001064A mov ecx, [ebp+BaseAddress_Ndis_Driver]
:0001064D add ecx, [eax+IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint]
:00010650 mov [ebp+EntryPoint], ecx
:00010653 jmp short loc_10659
:00010655 ; -------------------------------------------------------------
:00010655
:00010655 loc_10655: ; CODE XREF: SystemThread+129
:00010655 and [ebp+EntryPoint], 0
:00010659
:00010659 loc_10659: ; CODE XREF: SystemThread+137
:00010659 mov eax, [ebp+EntryPoint]
:0001065C mov [ebp+NDIS_DriverEntry], eax
:0001065F cmp [ebp+NDIS_DriverEntry], 0
:00010663 jz short locret_10674

:00010665 push [ebp+BaseAddress_Mbr]
:00010668 push [ebp+PsLoadedModuleList]
:0001066B push [ebp+BaseAddress_Ndis]
:0001066E call [ebp+NDIS_DriverEntry]



From this we can get the DriverEntry definition for the NDIS driver

DriverEntry(PVOID BaseAddress_Ndis, PLIST_ENTRY PsLoadedModuleList, PVOID BaseAddress_Mbr)

I see there's another PsCreateSystemThread in this new NDIS driver, hmmm...


PS,

Your computer is now stoned (...again!). The rise of MBR rootkits
http://www.f-secure.com/weblog/archives/Kasslin-Florio-VB2008.pdf

evaluator
May 16th, 2010, 06:28
TIP: once i want to load driver as Exe in user-space.
then i created copies of NTOSKRNL & HAL, then removed their EIP & IT from PE-headers.
that's all Folks!