I've written an IDAPython script that searches for a string in the bytes preceding MmGetSystemRoutineAddress
and FltGetRoutineAddress
calls and then marks that call with the function being imported. It worked on the x64 and x86 drivers I had to hand but I've left my commented out debug prints in just in case.
Here's an example of the disassembly after running the script:
And of the output:
Getting string xrefs
Trying to find MmGetSystemRoutineAddress...
Found MmGetSystemRoutineAddress @ 1C00092E0
Found 7 xrefs to MmGetSystemRoutineAddress
MmGetSystemRoutineAddress called @ 1C000B022 in func sub_1C000B000
1C000B00D lea rdx, aPsgetversion; "PsGetVersion"
Found an Xref to a string in preceding 50 bytes
Searching for use of returned func pointer...
1C000B022 call cs:MmGetSystemRoutineAddress; PsGetVersion - auto added
1C000B028 lea rdx, aWmitracemessag; "WmiTraceMessage"
1C000B02F mov cs:PsGetVersion, rax
Found mov to global data
Found the pointer being stored! Marking it
There's definitely improvements that could be made:
- Make sure that the dynamic importing function xref is an actual call
The function pointer result is often put into a global pointer (i.e. mov dword_444E0, eax
), having this pointer automatically named would be very useful.
The other common use case is seeing it being called straight after (i.e. call eax
) so having this call marked would be nice.
I'll have a look at making these improvements and updating when I can.
#Using Sark would have made the line iteration a bit nicer but I wanted
#something I could use anywhere without any external dependencies
import idc
import idautils
import idaapi
'''
Returns - String xrefs. Dict of xref_location[tuple of (str address, str value)]
'''
def get_string_xrefs():
#Get the strings so we can see what might have been passed in
print "Getting string xrefs"
sc = idautils.Strings(default_setup = False)
# we want C & Unicode strings, and *only* existing strings.
sc.setup(strtypes=(Strings.STR_C | Strings.STR_UNICODE),
ignore_instructions = True,
display_only_existing_strings = True)
#Make a list of all string locations
string_locs = []
for s in sc:
string_locs.append((s.ea, str(s)))
#print "%x: len=%d type=%d -> '%s'" % (s.ea, s.length, s.type, str(s))
#Make a dict of all places strings are Xrefs
string_xrefs = {}
for loc in string_locs:
#print "%08X %s" % (loc[0], loc[1])
for xref in idautils.XrefsTo(loc[0]):
#print "Xref @ %08X" % xref.frm
string_xrefs[xref.frm] = loc
return string_xrefs
'''
dynam_loading_func_name - The function used to dynamically load the functions
string_xrefs - String xrefs. Dict of xref_location[tuple of (str address, str value)]
'''
def markup_dynamically_loaded_funcs(dynam_loading_func_name, string_xrefs, num_search_bytes_string = 50, num_search_bytes_use = 30):
print "Trying to find %s..." % dynam_loading_func_name
getsys_addr = idc.LocByName(dynam_loading_func_name)
print "Found %s @ %08X" % (dynam_loading_func_name, getsys_addr)
num_xrefs = sum(1 for i in idautils.CodeRefsTo(getsys_addr, 0))
print "Found %d xrefs to %s" % (num_xrefs, dynam_loading_func_name)
#Iterate through each Xref to dynamic loading func and see if a string is used in the preceding instructions...
for dynam_xref in idautils.CodeRefsTo(getsys_addr, 0):
print "\n%s called @ %08X in func %s" % (dynam_loading_func_name, dynam_xref, idc.GetFunctionName(dynam_xref))
#Start at line above dynamic loading func call
new_ea = idaapi.get_item_head(dynam_xref - 1)
#Continue until we've gone back 50 bytes or found a string
while new_ea > dynam_xref - num_search_bytes_string:
#print " %08X %s" % (new_ea, idc.GetDisasm(new_ea))
#Go to the line above
new_ea = idaapi.get_item_head(new_ea-1)
#Check if the address is an xref to a string
if new_ea in string_xrefs:
print " %08X %s" % (new_ea, idc.GetDisasm(new_ea))
print " Found an Xref to a string in preceding %d bytes" % num_search_bytes_string
#Make the comment to add
imported_func_name = string_xrefs[new_ea][1]
comment = imported_func_name + " - auto added"
#Add the comment to the dynamic loading func call
idc.MakeComm(dynam_xref, comment)
print " Searching for use of returned func pointer..."
#Start at line past dynamic loading func, go forward one at a time (up to 30 bytes) till we find either:
# - a call to rax or eax
# - a mov of rax or eax into a global pointer
ptr_search_addr = dynam_xref
while ptr_search_addr < dynam_xref + num_search_bytes_use:
print " %08X %s" % (ptr_search_addr, idc.GetDisasm(ptr_search_addr))
ptr_search_addr += idaapi.get_item_size(ptr_search_addr)
#Check for 'call rax' or 'call eax'
if idc.GetMnem(ptr_search_addr) == "call" and idc.GetOpnd(ptr_search_addr,0) in ["rax", "eax"]:
print " %08X %s" % (ptr_search_addr, idc.GetDisasm(ptr_search_addr))
print " Found a call! Marking it"
##Comment it and stop checking for this string
idc.MakeComm(ptr_search_addr, comment)
break
data_sec_start = idaapi.get_segm_by_name(".data").startEA
data_sec_end = idaapi.get_segm_by_name(".data").endEA
#Need to check for mov into global data, avoid situations like:
#call MmGetSystemRoutineAddress
#mov ecx, eax
#mov dword_12345, ecx
#Check for first 'mov' instruction and what it's moving to is in global data
if idc.GetMnem(ptr_search_addr) == "mov" and \
data_sec_start < GetOperandValue(ptr_search_addr, 0) < data_sec_end:
print " Found mov to global data"
#print " %08X" % GetOperandValue(ptr_search_addr, 0)
print " Found the pointer being stored! Marking it"
ptr_addr = GetOperandValue(ptr_search_addr, 0)
#TODO: Validate address is in right section
#Change the name, can't reuse names so keep trying till it works
count = 1
new_name = imported_func_name
while not idc.MakeNameEx(ptr_addr, new_name, SN_NOWARN):
new_name = imported_func_name + "_" + str(count)
print "%s is in use, using %s instead" % (imported_func_name, new_name)
count += 1
break
else:
print "**************************************************"
print "***** *****"
print "***** Didn't find a call or pointer storage! *****"
print "***** *****"
print "**************************************************"
#End the search for the string
break
def main():
string_xrefs = get_string_xrefs()
markup_dynamically_loaded_funcs("MmGetSystemRoutineAddress", string_xrefs)
print "\n\n\n\n\n\n\n\n\n\n\n\n\n"
markup_dynamically_loaded_funcs("FltGetRoutineAddress", string_xrefs)
main()