Driver packer

The last time we’ve seen a userland packer, this time we’ll see a kernel-land packer (MD5: AD61E65ACF9B0A56C77C02EFE208B470).
The packing scheme is similar to userland one, the main differences are that the opaque predicates and anti-debugging snippets are missing. what is left is a light (but still annoying) control flow obfuscation.

Anyway the first stage consist in decrypting a portion of code, and since this is quite annoying and error-prone to follow this kind of code inside the kernelmode, we can use an old trick: change the subsystem type of the driver from Native to Windows GUI (see CFF Explorer) and run it as a normal executable. We can run the driver in userland until it doesnt start using kernel specific functions or structures.


Now that we’re ready we start stepping until we find out how it works the descrambling loop, something like this (spreaded over hundreds of instruction.. OF COURSE 😀 ):

mov   dh, byte ptr [ecx]
rol   dh, 07h
add   dh, 06Fh
not   dh
ror   dh, 017h
neg   dh
mov   byte ptr [ecx], dh
inc ecx

Once the loop is finished control is passed to the newly descrambled code, that starts using kernelmode structures, to better follow what the driver does we can switch (temporarily) to a kernel debugger like Syser or WinDbg.

The first step is determining the driver base address and saving it

.text:00018864                 call    $+5
.text:00018869                 pop     eax
.text:0001886A                 jmp     short loc_18872
.text:0001886C loc_1886C:
.text:0001886C                 dec     eax
.text:0001886D                 and     eax, 0FFFFF000h
.text:00018872 loc_18872:
.text:00018872                 cmp     word ptr [eax], 5A4Dh
.text:00018C9A                 jnz     loc_1886C
.text:00018CA0                 mov     [ebp-14h], eax  ; save module base

once this is done it allocs some memory for the next steps…

push    36036A24h
push    [ebp+arg_0] ; DriverObject
call    sub_18B59
sub_18B59       proc near
.text:00018B59    push    ebp
.text:00018B5A    mov    ebp, esp
.text:00018FE4    mov    esi, [ebp+8]
.text:00018FE7    mov    esi, [esi+DRIVER_OBJECT.DriverSection]
.text:00018FEA    mov    edi, esi
.text:000188BC    mov    esi, [esi] ;esi = DRIVER_SECTION
.text:000188BE    lea    ebx, [esi+2Ch]
.text:00018A19    mov    ebx, [ebx+4] ; ebx = ModuleName
.text:00018A1C    or    ebx, ebx
.text:00018A1E    jz    loc_191E3
.text:00018A24    xor    eax, eax

It’s conceptually the same function used in usermode to resolve APIs ported to kernelmode. It gets the pointer to DriverSection structure through the DRIVER_OBJECT. The DriverSection structure is not officially described, but it’s well known to reversers and kernel developers and goes under different names: DRIVER_SECTION or MODULE_ENTRY but it’s the same structure.

	+0x000 LIST_ENTRY le_mod
		+0x000 FLink
		+0x004 BLink
	+0x008 unkn[4]
	+0x018 Base
	+0x01c DriverStart
	+0x020 unkn2
	+0x024 UNICODE_STRING DriverPath
		+0x000 Len
		+0x004 Max
		+0x008 Buffer
	+0x02c UNICODE_STRING DriverName
		+0x000 Len
		+0x004 Max
		+0x008 Buffer

what it does is traversing the module list until it finds the kernel image: it calcs a simple hash on the DriverName and compares it to that of the module’s name to find (ntoskrnl.exe in this case). Once it has found it, the kernel base is retrieved and from there it searches for the given function (always by hash on name) in the export table and returns its address.
The first function it looks for is denoted by this hash: 0C3898A9h, to be able to perfom static analysis i developed a simple python script that calculates this hash given the list of function names (generated with dumpbin).

C:\Projects\ddos_sys\utils>python | findstr C3898A9
MmAllocateContiguousMemory / 0C3898A9

The interesting thing about this function is that it gives problems when run under a debugger, there will be a later post on this mystery 😉 for now let’s just say that this can be regarded as an anti-debugging technique.

The whole driver image is copied into the new space and executions resumes there.

.text:0001892D RESUME_TO_NEWMEM:
.text:0001892D                 pop     eax
.text:0001892E                 sub     eax, [ebp-14h] ; DriverBase
.text:00018931                 add     eax, [ebp-1Ch] ; AllocatedMem
.text:00018934                 push    eax
.text:00018935                 retn

After that there is another allocation, the first section of the driver is copied into the allocated memory and decrypted there: this is the packed driver. Once decrypted, as in userland, the current driver code/data is replaced with the decrypted driver, first the header and the section by section. Once the substitution is done control is passed to the entry point.

This new driver simply extracts another driver, writes it to file c:\windows\system32\drivers\nups.sys, install the driver in the registry and loads it.

and that’s it for now 🙂 next time the interesting part..


%d bloggers like this: