I've got an x64 Windows driver where a lot of functions are dynamically imported using MmGetSystemRoutineAddress. The function takes a UNICODE_STRING as a parameter to tell it what function to find and you can often see this being initialised just before the call which is how I'm checking what it's importing.

Is there anyway to automatically mark what this function is importing rather than having to manually go to each call and check? I imagine using IDC or IDAPython would be the best solution.


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: output

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)

                    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
                    print "**************************************************"
                    print "*****                                        *****"
                    print "***** Didn't find a call or pointer storage! *****"
                    print "*****                                        *****"
                    print "**************************************************"

                #End the search for the string

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)
  • This was my first IDAPython script and my Python isn't strong so feel free to make comments/suggestions for improvement. – Peanut Oct 5 '16 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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