Show me your IoCtlCodes!

tl;dr: If you want to get a list of valid IoCtlCodes reverse the binaries that talk to the driver from userspace.

Knock, Knock. This is userspace.

In a nutshell, programs that install drivers on your OS (let’s say AV, packet sniffers, etc.) install userland software as well. This software needs to talk to the driver (in kernel space). The glue allowing those two programs, living in different worlds, to talk to each other is implemented via the DeviceIoControl API.

BOOL WINAPI DeviceIoControl(
  _In_         HANDLE hDevice,
  _In_         DWORD dwIoControlCode,
  _In_opt_     LPVOID lpInBuffer,
  _In_         DWORD nInBufferSize,
  _Out_opt_    LPVOID lpOutBuffer,
  _In_         DWORD nOutBufferSize,
  _Out_opt_    LPDWORD lpBytesReturned,
  _Inout_opt_  LPOVERLAPPED lpOverlapped
);

This is pretty easy, just open a handle to the device from userland, call the function with this handle and a couple of buffers: one for the input data, an0ther for the output one.

Sounds easy, doesn’t it? Well, not that fast. There are a couple of issues here…

One of them is the IoControlCode. What the hell is that?

Think about this like a network port, something that allows you to multiplex. So you sent data to a driver, but how does it know what it was intended for? I mean, a driver is a complex piece of software, sure it can do more than one thing with the data in your buffer!

Now, if you checked carefully the DeviceIoControl declaration, you have notice that this number is 32bits long. It goes without saying, is going to be difficult to try random values and hope you got the correct one. It’s necessary to get a list of valid codes somehow.

This is rather easy if you have the binaries of the userland application. Just look for calls to DeviceIoControl and read the second parameter. As you will see sometimes this would be an indirect reference (like for example, “edx”) but anyway is a good way to get an initial list very quickly.

With this in mind, a first version of an IDA Pro script implementing exactly this could be:

# Find the IoControlCodes corresponding to
# calls to DeviceIOControl within a binary

from idaapi import *
from idautils import *
from idc import *

##########################################################
# This class implements a fifo queue
class fqueue(list):
	def __init__(self, n):
		self.n = n

	def push(self, a):
		self.append(a)
		if len(self) > self.n:
			self.pop(0)

##########################################################

gpRegList = ['eax', 'ebx', 'ecx', 'edx']
ioccList = []
callerList = []
pushQueue = fqueue(2)

dioc_ea = LocByName("DeviceIoControl") # EA

print "DeviceIoControl found at 0x%08x" % dioc_ea

for caller in XrefsTo(dioc_ea, True):
	# This is the address (within a function)
	# where the reference was made
	caller_ea = caller.frm

	if caller_ea not in callerList:
		# IDA Pro shifts duplicates, get rid of them
		callerList.append(caller_ea)
		print "xref @ 0x%08x (%s)" % (caller_ea, GetFunctionName(caller_ea))
	else:
		continue

	# The dwIoControlCode must be the second 
	# PUSH xxx before the CALL instruction
	# So we need to keep track of the PUSH instructions
	for ins in FuncItems(caller_ea):
		disasm = GetDisasm(ins)
		if "push" in disasm:
			# Save the PUSH instruction's operand
			pushQueue.push(GetOpnd(ins, 0))
		elif ins == caller_ea:
			# At this moment we hit the corresponding CALL instruction
			# First item in Queue is second "oldest" push
			iocc = pushQueue[0]

			if iocc in gpRegList:
				print "NOTE: IoControlCode was %s at 0x%08x. Check manually" % (iocc, caller_ea)
			else:
				if pushQueue[0] not in ioccList:
					ioccList.append(pushQueue[0])
		else:
			pass

# Print all the gathered IoControlCodes

print "%d IoControlCodes found!" % len(ioccList)

for io in ioccList:
	print "[*]", io

delivering such output:

DeviceIoControl found at 0x650320e4
xref @ 0x6500c19a (AavmDetectionsHaveChanged)
xref @ 0x6500c30b (sub_6500C220)
xref @ 0x6500c62b (sub_6500C220)
xref @ 0x6500c945 (sub_6500C220)
xref @ 0x6500caff (sub_6500CA80)
xref @ 0x6500cc03 (sub_6500CA80)
xref @ 0x6500cc5e (sub_6500CA80)
xref @ 0x6500ccd8 (sub_6500CA80)
xref @ 0x6500cd03 (sub_6500CA80)
xref @ 0x6500cf20 (sub_6500CE90)
xref @ 0x6500cfb1 (sub_6500CE90)
xref @ 0x6500f823 (AavmStart)
xref @ 0x650102bf (AavmStop)
xref @ 0x65016a30 (sub_65016900)
xref @ 0x650274fb (sub_65027480)
xref @ 0x65027792 (sub_65027480)
xref @ 0x6502797d (sub_65027850)
xref @ 0x65027e92 (sub_65027E70)
xref @ 0x65027f67 (sub_65027EA0)
xref @ 0x65029f16 (sub_65029EF0)
NOTE: IoControlCode was edx at 0x65029f16. Check manually
xref @ 0x65029fd1 (sub_65029F40)
xref @ 0x6502a0af (sub_6502A030)
xref @ 0x6502a1d5 (sub_6502A0E0)
xref @ 0x6502a4fb (sub_6502A430)
xref @ 0x6502a6a7 (sub_6502A610)
xref @ 0x6502a7bb (sub_6502A6E0)
xref @ 0x6502fba2 (sub_6502FB70)
xref @ 0x6502fbf3 (sub_6502FBC0)
19 IoControlCodes found!
[*] 0B2C88028h
[*] 0B2D60030h
[*] 0B2D60034h
[*] 0B2D60028h
[*] 0B2D6002Ch
[*] 0B2D60014h
[*] 0B2D6001Ch
[*] 0B2D6000Ch
[*] 0B2D600A4h
[*] 0B2D600B4h
[*] 0B2D600C0h
[*] 0B2D600B8h
[*] 82AC805Ch
[*] 82AC0050h
[*] 82AC0054h
[*] 82AC8120h
[*] 82AC8214h
[*] 82AC8218h
[*] 80002008h

Excellent. Now the following problem arises. Which is the name of the device(s) I need to get a handle for? And in the probable case there are more than one… which IoControlCodes correspond to which devices?

But now is time for breakfast, so this is left for an update, should I find a way to automate it :)

Advertisements

3 thoughts on “Show me your IoCtlCodes!

  1. Here’s a thought: how about opening the driver in IDA and looking for the handlers of those IO control codes instead? That way you’d get exactly which codes are handled, regardless of which ones are actually used by the userspace binary. (In fact, those that aren’t used are likely to be the most interesting!)

    1. You are absolutely right. But automating the search for IoControlCodes within the Ioctl dispatcher could be a bit trickier (sure for me it is). I wanted to start small and enhance the process afterwards but I’ll definitely give it a try when I’m done with this. I guess those unused codes may contain some nasty surprises :) Thanks for that!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s