Files
2013-03-25 22:00:37 -04:00

328 lines
14 KiB
Python
Executable File

#!/usr/bin/env python
"""
Immunity Static Driver Analysis for Immunity Debugger
(c) Immunity, Inc. 2004-2006
U{Immunity Inc.<http://www.immunityinc.com>} Debugger Driver Library for python
"""
__VERSION__ = '1.0'
from immutils import *
from immlib import *
import struct
class Driver:
def __init__(self):
# Globals
self.imm = Debugger()
self.IOCTLDispatchFunction = None
self.IOCTLDispatchFunctionAddress = 0x00000000
self.IOCTLCodes = []
self.IOCTLCodesLanding = {}
self.deviceNames = []
self.module = self.imm.getModule( self.imm.getDebuggedName() )
# Do some quick setup
if not self.module.isAnalysed:
self.imm.analyseCode( self.module.getCodebase() )
def getIOCTLCodes( self ):
"""
Useful function to root out IOCTL codes from a driver.
This is also a big part of automating ioctlizer.
@rtype: List
@returns: List of all IOCTL codes that are supported by the driver.
"""
if not self.IOCTLCodes:
if self.IOCTLDispatchFunction is None:
self.getIOCTLDispatch()
bb_list = self.IOCTLDispatchFunction.getBasicBlocks()
# Each IOCTL call has to do some setup first and then make a
# decision on the dwIoctlCode, so get the first basic block
# start disassembling from the end backwards
first_bb = bb_list[0]
instruction_list = first_bb.getInstructions( self.imm )[::-1]
term_jmp_found = False
first_ioctl_code = None
for instruction in instruction_list:
inst_string = instruction.getResult()
# We first confirm that we are terminated by a conditional
# jump so that we can now look for a CMP/SUB with a constant
# that will contain the IOCTL code
if not term_jmp_found and instruction.isConditionalJmp():
term_jmp_found = True
continue
if "CMP" in inst_string or "SUB" in inst_string and term_jmp_found:
ioctl_code = instruction.getImmConst()
self.imm.log("First IOCTL code: 0x%08x" % int(ioctl_code))
first_ioctl_code = int(ioctl_code)
break
# Now we put ourselves into a dissasembling frenzy
# A CMP instruction means we don't modify the IOCTL code
# A SUB/ADD instruction means we have to adjust the IOCTL code before storing it
self.IOCTLCodes.append( first_ioctl_code )
self.IOCTLCodesLanding[ first_ioctl_code ] = bb_list[0].getTrueEdge()
base_register = instruction.getOperandRegister(0)
# We aren't interested in the True edge, that's for the
# already discovered first_ioctl_code
continue_search = True
current_bb = bb_list[0]
# Just key a dict with the bb heads for quick access later
basic_block_head_addresses = {}
for bb in bb_list:
basic_block_head_addresses[bb.getStart()] = bb
modifier = first_ioctl_code
reg_modifier = None
reg_modifier_value = 0
while continue_search:
false_edge = current_bb.getFalseEdge()
if false_edge is None:
break
current_bb = basic_block_head_addresses[ false_edge ]
instruction_list = current_bb.getInstructions( self.imm )
# Now we have the false edge in the list let's
# check for our IOCTL comparisons
for instruction in instruction_list:
# Something is being done TO the base_register
if base_register == instruction.getOperandRegister(0):
# Ok now we know that our base register is being
# either compared or manipulated
inst_string = instruction.getResult()
const = instruction.getImmConst()
const_found = False
# This means we have a valid constant being used
# otherwise we need to track down the register being used.
if "CMP" in inst_string:
self.IOCTLCodes.append( const )
self.IOCTLCodesLanding[ const ] = current_bb.getTrueEdge()
break
if "SUB" in inst_string:
# We have tracked a modifier that's been assigned to
# a register that's being subtracted from our IOCTL code
if reg_modifier is not None:
if instruction.getOperandRegister(1) == reg_modifier and instruction.getOperandRegister(0) == base_register:
const = reg_modifier_value
const_found = True
self.imm.log("Reg modifier check: 0x%08x" % reg_modifier_value)
# Check to make sure we aren't modifying the modifier :)
if instruction.getOperandRegister(1) == reg_modifier and instruction.getImmConst() != 0:
reg_modifier_value = reg_modifier_value - instruction.getImmConst()
const_found = True
self.imm.log("Reg modifier check 2")
if not const and not const_found:
# K now we gotta track down that pesky register
reg_modifier = instruction.getOperandRegister(1)
# Now we disassemble backwards looking for a constant
rev_instruction_list = instruction_list[::-1]
for search_instruction in instruction_list:
mod_constant = search_instruction.getImmConst()
if mod_constant:
reg_modifier_value = mod_constant
const = mod_constant
break
self.imm.log("Address: 0x%08x" % instruction.getAddress(), address = instruction.getAddress() )
modifier = modifier - const
self.IOCTLCodes.append( modifier )
self.IOCTLCodesLanding[ modifier ] = current_bb.getTrueEdge()
break
# Now pretty print them out
for ioctl_code in self.IOCTLCodes:
self.imm.log("IOCTL Code: 0x%08x" % ioctl_code)
return self.IOCTLCodes
def getDeviceNames( self ):
"""
Attempts to discover all registered device symbolic links
which are how usermode talks to the driver.
@rtype: List
@return: List of all possible devices names.
"""
string_list = self.imm.getReferencedStrings( self.module.getCodebase() )
for entry in string_list:
if "\\Device\\" in entry[2]:
self.imm.log("Possible match at address: 0x%08x" % entry[0], address = entry[0] )
self.deviceNames.append( entry[2].split("\"")[1] )
self.imm.log("Possible device names: %s" % self.deviceNames)
return self.deviceNames
def getIOCTLDispatch( self ):
"""
Locates the primary dispatch function for handling IOCTLs from
userland.
@rtype: Function object
@return: Function object.
"""
# The IOCTL dispatch is always located at MOV DWORD PTR [R32+0x70], CONST
search_pattern = "MOV DWORD PTR [R32+70],CONST"
dispatch_address = self.imm.searchCommandsOnModule( self.module.getCodebase(), search_pattern )
# We have to weed out some possible bad matches
for address in dispatch_address:
instruction = self.imm.disasm( address[0] )
if "MOV DWORD PTR" in instruction.getResult():
if "+70" in instruction.getResult():
self.IOCTLDispatchFunctionAddress = instruction.getImmConst()
self.IOCTLDispatchFunction = self.imm.getFunction( self.IOCTLDispatchFunctionAddress )
break
if not self.IOCTLDispatchFunctionAddress and not self.IOCTLDispatchFunction:
# If that first loop fails, then we start walking the driver
# until we freakin' find it, slow but accurate
function_list = self.imm.getAllFunctions( self.module.getCodebase() )
for function in function_list:
bb_list = self.imm.getFunction( function ).getBasicBlocks()
for bb in bb_list:
instruction_list = bb.getInstructions( self.imm )
for instruction in instruction_list:
if "MOV DWORD PTR" in instruction.getResult():
if "+70" in instruction.getResult():
self.IOCTLDispatchFunctionAddress = instruction.getImmConst()
self.IOCTLDispatchFunction = self.imm.getFunction( self.IOCTLDispatchFunctionAddress )
break
if self.IOCTLDispatchFunction:
self.imm.log("Dispatch address: 0x%08x" % self.IOCTLDispatchFunctionAddress, address = self.IOCTLDispatchFunctionAddress )
else:
self.imm.log("Couldn't find an IOCTL dispatch routine. Driver may not support usermode calls in this manner.")
return self.IOCTLDispatchFunction
def printDriverReport( self ):
"""
This simply runs all of the functions and outputs as much information as it
can gather about the driver, spits it all out into the log window and
drops a text file called driver_name_report.txt with all of the information.
"""
# TODO: make this do what i said it's gonna do
self.getIOCTLDispatch()
if self.IOCTLDispatchFunctionAddress:
self.getIOCTLCodes()
self.getDeviceNames()
fd = open("%s.txt" % self.imm.getDebuggedName(), "w")
self.imm.log("=" * 512)
fd.write("=" * 512)
fd.write("\n")
msg = "Driver Report for: %s (Version: %s)" % ( self.imm.getDebuggedName(), self.module.getVersion() )
self.imm.log("%s" % msg)
self.imm.log("")
fd.write( msg + "\n\n")
msg = "Discovered Device Names:"
self.imm.log("%s" % msg)
fd.write( msg + "\n")
for device_name in self.deviceNames:
self.imm.log( "%s" % device_name)
fd.write( device_name + "\n" )
self.imm.log("")
fd.write("\n")
if self.IOCTLDispatchFunctionAddress:
msg = "IOCTL Dispatch located at: 0x%08x (+%08x)" % ( self.IOCTLDispatchFunctionAddress, (self.IOCTLDispatchFunctionAddress - self.module.getBase() ) )
self.imm.log("%s" % msg, address = self.IOCTLDispatchFunctionAddress)
self.imm.log("")
fd.write( msg + "\n\n")
msg = "IOCTL Codes:"
self.imm.log("%s" % msg)
self.imm.log("")
fd.write( msg + "\n\n")
for ioctl_code in self.IOCTLCodes:
msg = "0x%08x" % ioctl_code
self.imm.log("%s" % msg)
fd.write( msg + "\n")
self.imm.log("")
fd.write("\n")
msg = "IOCTL Codes Landing Basic Blocks ( IOCTL CODE => Landing Address ( Relative Offset ) ):"
self.imm.log("%s" % msg)
self.imm.log("")
fd.write( msg + "\n\n")
for ioctl_code in self.IOCTLCodes:
msg = "0x%08x => 0x%08x (+%08x)" % ( ioctl_code, self.IOCTLCodesLanding[ ioctl_code ], ( self.IOCTLCodesLanding[ ioctl_code ] - self.module.getBase() ) )
self.imm.log("%s" % msg, address = self.IOCTLCodesLanding[ ioctl_code ] )
fd.write( msg + "\n")
self.imm.log("")
fd.write("\n")
fd.close()
return