I have started reversing this piece of malware. At some point it creates a service and starts it, then immediately it calls the function DeviceIoControl and the malware went from "paused" to "running" under ollydbg. I've searched a little bit, and I understand that this function serves to communicate with the service it just had created.

But how exactly do I reverse it? How do I know what it does? And how can I continue stepping under ollydbg? Or do I have to move to windbg or some other kernel-mode debugger?

2 Answers 2


You cannot step into kernel mode from Ollydbg. You need a kernel debugger like windbg, as ollydbg is a user mode debugger.

Since you posed the question, I assume you neither have a kernel debugging connection, nor the driver where that control code is sent for analyzing it, as answered by Jonathon.

Usage of proper security measures to deal with malware assumed and emphasized from here onward.

I assume the malware is running already as your query states that you are on DeviceIoControl.

I am going to use ollydbg 2.01.

Attach ollydbg to an unknown process

Press Ctrl+G and start typing ntdll.ntDeviceIo and the list box will show ntdll.ntDeviceIoControl now select it and follow label.

Press F2 to set a breakpoint, and F9 to run the attached process.

INT3 breakpoints, item 0
  Address = 7C90D27E NtDeviceIoControlFile
  Module = ntdll
  Status = Active
  Disassembly = MOV     EAX, 42
  Comment = ntdll.NtDeviceIoControlFile(guessed Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8,Arg9,Arg10)

Ollydbg should break when a control code is sent and stack should look like this:

CPU Stack
    Address   Value      ASCII Comments
    0013EF50  [7C801675  u€|   ; /RETURN from ntdll.NtDeviceIoControlFile to kernel32.DeviceIoControl+4C
    0013EF54  /00000090        ; |Arg1 = 90
    0013EF58  |00000000        ; |Arg2 = 0
    0013EF5C  |00000000        ; |Arg3 = 0
    0013EF60  |00000000        ; |Arg4 = 0
    0013EF64  |0013EF88  ˆï    ; |Arg5 = 13EF88
    0013EF68  |83050024  $ ƒ   ; |Arg6 = 83050024
    0013EF6C  |00000000        ; |Arg7 = 0
    0013EF70  |00000000        ; |Arg8 = 0
    0013EF74  |0013EFEC  ìï    ; |Arg9 = 13EFEC
    0013EF78  |00000004        ; \Arg10 = 4

From the specs of DeviceIoControlCode, the first argument points to handle and the sixth argument is the control code

Having windbg installed can make things easier from here, but we will not use windbg at this moment as it has a steep learning curve.

Open ollydbg handle window and find what does the handle point to (0x90 in the above paste) - it points to a device:

Handles, item 9
  Handle = 00000090
  Type = File (dev)   <-------- it is a _FILE_OBJECT
  Refs =    2.
  Tag =
  Info =
  Translated name = \Device\Dbgv

Now run Process Explorer from SysInternals. Select the unknown process, and press Ctrl+H to make the lower pane show handles.

Select the handle 90, right click and select properties. Process Explorer will show the address of the Device Object as noted by Ollydbg. This device object is a File Object:

enter image description here

ollydbg 2.01 can show memory above 0x7fffffff (kernel mode memory) we will use that feature to find the driver associated with this device

Read about the FILE_OBJECT structure relevant to your OS. In Windows XP you will find the address of device object at +4 from file object.

Select the dump window press Ctrl+G and type in the address process explorer showed viz

863c87f0 and follow expression 

CPU Dump
Address   Hex dump                                         ASCII
863C87F0  05 00 70 00|A8 D3 51 86|00 00 00 00|00 00 00 00|  p ¨ÓQ†

So 8651d3a8 in the paste above points to the device object for this file object.

Read about DEVICE_OBJECT structure. In Windows XP, the address of the driver object is at device object + 8. Following there with Ctrl+G -> follow expression as done earlier

CPU Dump
Address   Hex dump                                         ASCII
8651D3A8  03 00 B8 00|01 00 00 00|48 2C F1 86|00 00 00 00|  ¸    H,ñ†

notice 86f12c48 is driver object. Read about DRIVER_OBJECT structure.

+0xc is Driver Start and +0x10 is Driver Size --> Ctrl+G to follow:

CPU Dump
Address   Hex dump                                         ASCII
86F12C48  04 00 A8 00|A8 D3 51 86|12 00 00 00|00 30 2A A9|  ¨ ¨ÓQ†    0*©
86F12C58  00 3D 00 00|48 9C D8 86|F0 2C F1 86|18 00 18 00|  =  HœØ†ð,ñ† 

a92a3000 is DriverStart and size of driver is 3d00.

Ctrl+G to follow:

CPU Dump
Address   Hex dump                                         ASCII
A92A3000  4D 5A 90 00|03 00 00 00|04 00 00 00|FF FF 00 00| MZ       ÿÿ
A92A3010  B8 00 00 00|00 00 00 00|40 00 00 00|00 00 00 00| ¸       @

The famous IMAGE_DOS_HEADER is noticeable.

Select from a92a3000 to a92a6d00, Right-Click -> Edit -> binary copy and paste the bytes to a new file in any hex editor (HxD for example), and save it as malwaredriver.sys :)

foo:\>dir malwaredriver.sys

26/03/2014  14:04            15,616 malwaredriver.sys

\>set /a 0x3d00
\>file malwaredriver.sys

malwaredriver.sys; PE32 executable for MS Windows (native) Intel 80386 32-bit


You can now analyze the driver statically.

Read about DRIVER_OBJECT structure carefully to find you can readily know the handler for IRP_MJ_DEVICE_IO_CONTROL handler in ollydbg. It would be at +0x70 from Driverobject in XP. For this particular driver, the IRP_MJ_DEVICE_IO_CONTROL_HANDLER is at

CPU Dump
Address   Value      ASCII Comments
86F12CB8   A92A4168  hA*©

You can follow and disassemble this memory in ollydbg. Select cpu dump press ctrl+g to disassemble an unknown driver

CPU Disasm
Address   Command                             Comments
A92A4168  MOV     EDI, EDI                    ; IRP_MJ_DEVICE_IO_CONTROL_HANDLER(pdevob,pirp)
A92A416A  PUSH    EBP
A92A416B  MOV     EBP, ESP
A92A416D  SUB     ESP, 24
A92A4170  PUSH    EBX
A92A4171  PUSH    ESI
A92A4172  MOV     ESI, DWORD PTR SS:[EBP+0C]  ; irp
A92A4175  MOV     EAX, DWORD PTR DS:[ESI+60]  ; nt!_irp -y Tail.overlay.cur->maj
A92A4178  MOV     EDX, DWORD PTR DS:[ESI+0C]  ; nt!_irp Tail.overlay.cur->par.DeviceIo.ty
A92A417B  XOR     EBX, EBX
A92A417D  PUSH    EDI
A92A417E  LEA     EDI, [ESI+18]
A92A4183  MOV     DWORD PTR DS:[ESI+1C], EBX
A92A4189  SUB     ECX, EBX
A92A418B  JE      SHORT A92A41F7              ; case 0 create
A92A418D  DEC     ECX
A92A418E  DEC     ECX
A92A418F  JE      SHORT A92A41CA              ; case 2 close
A92A4191  SUB     ECX, 0C
A92A4194  JNE     A92A42BB                    ; unhandled
A92A419A  MOV     ECX, DWORD PTR DS:[EAX+0C]  ; ioctl
A92A419D  MOV     EBX, ECX
A92A419F  AND     EBX, 00000003
A92A41A2  CMP     BL, 3
A92A41A5  JNE     SHORT A92A41AC              ; buff align
A92A41AA  JMP     SHORT A92A41AE
A92A41AC  MOV     EBX, EDX                    ; inbuff
A92A41AE  PUSH    DWORD PTR SS:[EBP+8]        ; devobj
A92A41B1  PUSH    EDI
A92A41B2  PUSH    ECX
A92A41B3  PUSH    DWORD PTR DS:[EAX+4]        ; bufflen
A92A41B6  PUSH    EBX
A92A41B7  PUSH    DWORD PTR DS:[EAX+8]        ; ioctlcode
A92A41BB  PUSH    1
A92A41BD  PUSH    DWORD PTR DS:[EAX+18]       ; fobj
A92A41C0  CALL    A92A3EEC                    ; actual_handler

Here the ioctl code is handled:

CPU Disasm
Address   Command                                  Comments
A92A3F02  MOV     ECX, DWORD PTR SS:[EBP+20]
A92A3F05  MOV     EAX, 83050020
A92A3F0A  CMP     ECX, EAX
A92A3F0C  JA      A92A40F1  this code will take this path as 24 > 20


CPU Disasm
Address     Hex dump                  Command                Comments
A92A40F1    81F9 24000583             CMP     ECX, 83050024
A92A40F7    74 3F                     JE      SHORT A92A4138 path taken

The control code does this work and sends some 320 on some condition

CPU Disasm
Address   Command                             Comments
A92A4138  PUSH    4
A92A413A  POP     EAX
A92A413E  JB      SHORT A92A4152
A92A4140  MOV     ECX, DWORD PTR SS:[EBP+18]
A92A4143  CMP     ECX, EBX
A92A4145  JE      SHORT A92A4152
A92A4147  MOV     DWORD PTR DS:[ECX], 320      <------------
A92A4150  JMP     SHORT A92A4158
A92A4152  MOV     DWORD PTR DS:[ESI], C000000D
A92A4158  MOV     AL, 1
A92A415A  CALL    A92A46D5
A92A415F  RETN    24

The driver analysed here is the driver loaded by dbgview (dbgv.sys) from SysInternals. This control code checks for an incompatible version of driver loaded in memory. Follow through can be practiced with the specific driver and specific version:

(4.75) build time Thu Aug 07 04:51:27 2008


With windbg inastalled you can do the same with just few commands as posted below

C:\>tlist | grep -i dbgview

3724 Dbgview.exe       DebugView on \\ (local)

C:\>kd -kl -c ".foreach /pS 1 /ps 4 (place  { .shell -ci \"!handle 0 3 0n3724 Fi
le \" grep File } ) {dt nt!_FILE_OBJECT -y Dev->Dri->DriverS place};q" | grep -i
 +0x00c DriverStart       : 0xf7451000 Void  
 +0x00c DriverStart       : 0xf73c5000 Void   
 +0x00c DriverStart       : 0xa87f9000 Void   
 +0x00c DriverStart       : 0xf7451000 Void   
 +0x00c DriverStart       : 0xf7451000 Void   

C:\>kd -kl -c ".reload ; lm a (0xf7451000); lm a (0xf73c5000) ; lm a (0xa87f9000);q" | grep -i defer

 f7451000 f746f880   ftdisk     (deferred)    
 f73c5000 f73db880   KSecDD     (deferred)    
 a87f9000 a87fcd00   Dbgv       (deferred)    

C:\>kd -kl -c ".writemem  malware.sys a87f9000 a87fcfff ;q" | grep -A 1 lkd

 lkd> kd: Reading initial command '.writemem  malware.sys a87f9000 a87fcfff ;q'     
 Writing 4000 bytes........    

C:\>file malware.sys  

 malware.sys; PE32 executable for MS Windows (native) Intel 80386 32-bit   

An explanation for the commands above as follows

kd -kl is local kernel debugging

!handle command has the capability to search for kernel handle  
when you input a Pid 

The explanation for arguments for this commands are 

 0 = all handles 
 3 = default flag provides basic+object information
 0n3724 = pid (can be from task manager
 File = type of Object to search for 

!dt can display memebers of nested structures with ease 
-y with dt takes partial inputs (wild card entries ) 
 and displays  all matching structure members 

and the output is manipulatable with any text / stream handling 
software like grep , sed . awk . find etc
  • Lot's of good tips here!
    – fileoffset
    Mar 27, 2014 at 0:25

More specifically, it sounds like your executable is loading a Device Driver. Userspace executables often communicates with drivers via IOCTLs (or I/O Controls).

DeviceIoControl does just that: sends an IOCTL to the driver. Note the second parameter to this function: DWORD dwIoControlCode. This is the code that identifies which IOCTL the program is requesting the driver to perform. It is just a 32-bit DWORD value.

Now, on the driver side there are a few things you need to know. (Be prepared to swim through a few structures!)

When a driver is first loaded, its DriverEntry function will be called. This is passed a pointer to the driver's DRIVER_OBJECT. In this structure there is an array named MajorFunction, which is a set of function pointers that the kernel will call when userspace tries to do something with the driver (e.g. Open, Close, or send IOCTL). These functions are identified by IRP Major Function Codes.

In the case of ioctls sent by DeviceIoControl, the IRP_MJ_DEVICE_CONTROL function is called. This function is called for any ioctl. Now to determine which dwIoControlCode was passed to DeviceIoControl, the ioctl-handling function will look at Parameters.DeviceIoControl.IoControlCode.

From this value, there is often a switch-statement which selects different behavior depending on the control code.

So first, you want to load up the .sys driver into IDA. The function that IDA marks as DriverEntry is really a wrapper generated by the DDK. The real DriverEntry is usually jmp'd to at the end of this stub.

In the real DriverEntry, you'll want to locate where the MajorFunction entries are populated. It might look something like this:

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.