使用 IDA 自动注释驱动程序中动态导入的函数

逆向工程 艾达 视窗 蟒蛇 司机
2021-06-16 00:48:52

我有一个 x64 Windows 驱动程序,其中使用MmGetSystemRoutineAddress. 该函数将 aUNICODE_STRING作为参数来告诉它要查找的函数,您经常可以看到它在调用之前被初始化,这就是我检查它正在导入的内容的方式。

无论如何,是否可以自动标记此函数正在导入的内容,而不必手动转到每个调用并检查?我想使用 IDC 或 IDAPython 将是最好的解决方案。

1个回答

我编写了一个 IDAPython 脚本,它在前面的字节中搜索一个字符串MmGetSystemRoutineAddressFltGetRoutineAddress调用,然后用要导入的函数标记该调用。它适用于我不得不手头的 x64 和 x86 驱动程序,但为了以防万一,我已将注释掉的调试打印留下。

这是运行脚本后反汇编的示例: 输出

以及输出:

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

肯定有可以改进的地方:

  • 确保动态导入函数外部参照是实际调用
  • 函数指针的结果通常被放入一个全局指针(即mov dword_444E0, eax),让这个指针自动命名将非常有用。
  • 另一个常见的用例是看到它在 (ie call eax)之后被直接调用,所以标记这个调用会很好。

我会尽可能地进行这些改进和更新。

#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()