Results 1 to 12 of 12

Thread: Win32.Sinowal MBR rootkit

  1. #1

    Win32.Sinowal MBR rootkit


    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
    Attached Files Attached Files
    Last edited by Silkut; May 11th, 2010 at 14:15. Reason: doesn't comply with malware forum rules

  2. #2


    Next time please stick to all the rules edicted here:

    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.
    Please consider donating to help staying online (here is why).
    Any amount greatly appreciated. Thank you.

  3. #3
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    Quote Originally Posted by sapu View Post
    The driver seems quite obfuscated, so no luck with disassembling... any suggestion about how to analyze / de-obfuscate it ?

    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:

    :00010DF1 77 03            ja      short near ptr loc_10DF5+1
    :00010DF3 32 20            xor     ah, [eax]
    :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:

    :00010DF1 77 03             ja      short loc_10DF6
    :00010DF1                   ; ---------------------
    :00010DF3 32                db  32h ; 2
    :00010DF4 20                db  20h
    :00010DF5 A3                db 0A3h ; 
    :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,

  4. #4
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    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.

    :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       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
    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?

  5. #5
    Quote Originally Posted by Kayaker View Post
    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...

  6. #6
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    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

    :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.

  7. #7
    Musician member evaluator's Avatar
    Join Date
    Sep 2001
    Blog Entries
    also before JA are trash-instructions, which guarantee JA

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

  8. #8
    Quote Originally Posted by evaluator View Post
    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...
    Zip password: virus
    Attached Files Attached Files
    Last edited by sapu; May 14th, 2010 at 12:13. Reason: forgot zip password

  9. #9
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    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: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: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.

  10. #10
    Quote Originally Posted by Kayaker View Post
    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 !!!

    Zip password: virus
    Attached Files Attached Files

  11. #11
    Teach, Not Flame Kayaker's Avatar
    Join Date
    Oct 2000
    Blog Entries
    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:

    :00010708   push    0
    :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 loc_10655:                 ; CODE XREF: SystemThread+129
    :00010655   and     [ebp+EntryPoint], 0
    :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...


    Your computer is now stoned (...again!). The rise of MBR rootkits

  12. #12
    Musician member evaluator's Avatar
    Join Date
    Sep 2001
    Blog Entries
    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!

Similar Threads

  1. Replies: 1
    Last Post: August 11th, 2013, 21:56
  2. o0 user mode rootkit for the blind o0
    By BanMe in forum Off Topic
    Replies: 8
    Last Post: January 12th, 2011, 18:36
  3. obscure rootkit(?) offer fr infected user, kernel detective (long post)
    By quirkly in forum Malware Analysis and Unpacking Forum
    Replies: 52
    Last Post: February 11th, 2010, 14:33
  4. Downloader.Win32.Small or Win32/PolyCrypt Reversing
    By evilcry in forum Blogs Forum
    Replies: 0
    Last Post: May 16th, 2008, 09:12
  5. possibble rootkit kdjfq.exe
    By blabberer in forum Malware Analysis and Unpacking Forum
    Replies: 3
    Last Post: June 9th, 2007, 00:49


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts