美文网首页Apple高级调试与逆向工程
(十三)SB示例 升级版的lookup

(十三)SB示例 升级版的lookup

作者: 收纳箱 | 来源:发表于2020-03-10 15:01 被阅读0次

    1. 升级版的lookup

    1.1 创建自动化脚本

    这个项目的starter目录中包含了两个Python脚本,这将使您在创建LLDB脚本内容时的生活更轻松。具体如下:

    • generate_new_script.py:接受一个名字,然后帮我们搭建一个脚本的基础骨架。
    • lldbinit.py:此脚本将遍历位于同一目录中的所有脚本(以.py结尾的文件),并尝试将它们加载到LLDB中。此外,如果存在扩展名为txt的文件,LLDB将尝试通过命令导入来加载这些文件的内容。

    把这两个文件都放在本章的starter文件夹中,并将它们粘贴到~/lldb/目录中。进入到~/.lldbinit文件并添加以下代码行:

    command script import ~/lldb/lldbinit.py
    
    创建lookup命令
     ~> lldb
    (lldb) reload_script
    

    如果没有错误的话,我们就可以开始了。

    (lldb) __generate_script lookup
    Opening "/Users/ycpeng/lldb/lookup.py"...
    (lldb) reload_script
    (lldb) lookup
    Hello! the lookup command is working!
    
    实现lookup命令

    前面我们看到lookup命令背后的基础相当简单。“秘密”就是使用SBTargetfindGlobalFunctions。在那之后,只需要进行格式化输出。

    我们将继续使用Allocator项目。打开项目,在iPhone模拟器上构建并运行。运行之后,暂停应用程序并启动LLDB。

    这个FindGlobalFunctions需要哪些参数?

    (lldb) script help(lldb.SBTarget.FindGlobalFunctions)
    Help on function FindGlobalFunctions in module lldb:
    
    FindGlobalFunctions(self, name, max_matches, matchtype)
        FindGlobalFunctions(SBTarget self, str const * name, uint32_t max_matches, lldb::MatchType matchtype) -> SBSymbolContextList
    

    因为它是一个Python类,所以可以忽略第一个自参数。名为name的str参数将是我们查询字符串。最大匹配将指定您想要的最大匹配数量。如果使用0,它将返回所有可用的匹配项。matchType参数是一个lldb Python枚举,可以指定执行不同类型的搜索,例如regexnon-regex

    因为regex搜索实际上是唯一的方法,所以我们将使用LLDB枚举值lldb.eMatchTypeRegex

    其他枚举值可以在这里找到:https://lldb.llvm.org/python_reference/_lldb%27-module.html#eMatchTypeRegex

    是时候在lookup.py脚本中实现它了。打开~/lldb/lookup.py。在handle_command末尾找到以下代码:

    # Uncomment if you are expecting at least one argument
    # clean_command = shlex.split(args[0])[0]
    result.AppendMessage('Hello! the lookup command is working!')
    

    替换为

    #1
    clean_command = shlex.split(args[0])[0]
    #2
    target = debugger.GetSelectedTarget()
    #3
    contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
    #4
    result.AppendMessage(str(contextlist))
    
    1. 获取传递给脚本的命令的干净版本。
    2. 通过SBDebugger获取SBTarget的实例。
    3. 使用FindGlobalFunctions API,传入clean_command。提供的0表示结果数没有上限,并使用eMatchTypeRegex匹配类型进行正则表达式搜索。
    4. contextlist转换为Python str,然后将其附加到SBCommandReturnObject
    (lldb) lookup DSObjectiveCObject
         Module: file = "/Users/ycpeng/Library/Developer/Xcode/DerivedData/Allocator-fnbctbueesgtzzfgksqcimloegwm/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
    CompileUnit: id = {0x00000000}, file = "/Users/ycpeng/Desktop/Books/Apple_Debugging_and_Reverse_Engineering_v3.0/25-Script Bridging with SBValue & Language Contexts/starter/Allocator/Allocator/DSObjectiveCObject.m", language = "unknown"
       Function: id = {0x100000268}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100002150-0x0000000100002181)
       FuncType: id = {0x100000268}, byte-size = 0, decl = DSObjectiveCObject.h:36, compiler_type = "void (NSString *)"
         Symbol: id = {0x000000db}, range = [0x0000000100002150-0x0000000100002190), name="-[DSObjectiveCObject setLastName:]"
    ...
    

    得到的输出比image lookup -rn DSObjectiveCObject更糟糕。我们用script研究一下这个执行结果。

    (lldb) script k = lldb.target.FindGlobalFunctions('DSObjectiveCObject', 0, lldb.eMatchTypeRegex)
    

    我们可以去查看SBSymbolContextList的文档,或直接查看它的所有方法。

    (lldb) gdocumentation SBSymbolContextList
    (lldb) script dir(lldb.SBSymbolContextList)
    ['Append', 'Clear', 'GetContextAtIndex', 'GetDescription', 'GetSize', 'IsValid', '__bool__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'blocks', 'compile_units', 'functions', 'get_block_array', 'get_compile_unit_array', 'get_function_array', 'get_line_entry_array', 'get_module_array', 'get_symbol_array', 'line_entries', 'modules', 'symbols']
    

    我们可以发现两个重要的方法__iter____getitem__,说明我们可以遍历和使用索引访问它。

    //等效于script k.__getitem__(0)
    (lldb) script k[0]
    <lldb.SBSymbolContext; proxy of <Swig Object of type 'lldb::SBSymbolContext *' at 0x10898b6c0> >
    
    (lldb) script print(k[0])
         Module: file = "/Users/ycpeng/Library/Developer/Xcode/DerivedData/Allocator-fnbctbueesgtzzfgksqcimloegwm/Build/Products/Debug-iphonesimulator/Allocator.app/Allocator", arch = "x86_64"
    CompileUnit: id = {0x00000000}, file = "/Users/ycpeng/Desktop/Books/Apple_Debugging_and_Reverse_Engineering_v3.0/25-Script Bridging with SBValue & Language Contexts/starter/Allocator/Allocator/DSObjectiveCObject.m", language = "objective-c"
       Function: id = {0x100000268}, name = "-[DSObjectiveCObject setLastName:]", range = [0x0000000100002150-0x0000000100002181)
       FuncType: id = {0x100000268}, byte-size = 0, decl = DSObjectiveCObject.h:36, compiler_type = "void (NSString *)"
         Symbol: id = {0x000000db}, range = [0x0000000100002150-0x0000000100002190), name="-[DSObjectiveCObject setLastName:]"
    

    我们关心的是方法名,如何快速拿到它呢?

    (lldb) script print(k[0].symbol.name)
    -[DSObjectiveCObject setLastName:]
    (lldb) script print(k[0].function.name)
    -[DSObjectiveCObject setLastName:]
    

    下面我们将

    #3
    contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
    #4
    result.AppendMessage(str(contextlist))
    

    替换为

    contextlist = target.FindGlobalFunctions(clean_command, 0, lldb.eMatchTypeRegex)
    output = ''
    for context in contextlist:
        output += context.symbol.name + '\n\n'
    result.AppendMessage(output)
    

    并在LLDB中实验一下

    (lldb) reload_script
    (lldb) lookup DSObjectiveCObject
    -[DSObjectiveCObject setLastName:]
    
    -[DSObjectiveCObject .cxx_destruct]
    
    -[DSObjectiveCObject setFirstName:]
    
    -[DSObjectiveCObject eyeColor]
    
    -[DSObjectiveCObject init]
    
    -[DSObjectiveCObject lastName]
    
    -[DSObjectiveCObject setEyeColor:]
    
    -[DSObjectiveCObject firstName]
    

    输出更加干净了。

    现在我想看看这些函数在进程中的位置。我想将一个特定的模块所有函数打印出来,最上面是模块名和命中数量和分割的*

    返回lookup.py文件,创建两个新函数。

    • generateFunctionDictionary获取SBBreakpointContextList并生成一个Python字典列表。这个dict将包含每个模块的键。对于dict中的值,将为每个命中的SBSymbolContext提供一个Python列表。

    • generateOutput解析创建的字典,并从OptionParser实例接收到的选项。此方法将返回要打印回控制台的字符串。

    首先在lookup.py脚本中的handle_command函数下面实现generateModuleDictionary函数:

    def generateModuleDictionary(contextlist):
        mdict = {}
        for context in contextlist: 
            #1
            key = context.module.file.fullpath
            #2
            if not key in mdict:
                mdict[key] = []
            #3
            mdict[key].append(context)
        return mdict
    
    1. SBSymbolContext中,通过SBModuleSBFileSpecfullPath获取Python字符串,并将其分配给一个名为key的变量。获取完整路径(而不是SBFileSpecbasename属性)很重要,因为可能有多个具有相同basename的模块。
    2. mdict变量将保存找到的所有符号的列表,并按模块拆分。此字典中的键将是模块名称,值将是在该模块中找到的符号数组。检查字典是否已经包含此模块的列表。如果没有,将为此模块键添加一个空白列表。
    3. SBSymbolContext实例添加到此模块的相应列表中。

    这个函数下面继续完成generateOutput

    def generateOutput(mdict, options, target):
        #1
        output = ''
        separator = '*' * 60 + '\n'
        #2
        for key in mdict:
            #3
            count = len(mdict[key])
            firstItem = mdict[key][0]
            #4
            moduleName = firstItem.module.file.basename
            output += '{0}{1} hits in {2}\n{0}'.format(separator, count, moduleName)
            #5
            for context in mdict[key]:
                query = ''
                query += context.symbol.name
                query += '\n\n'
                output += query
        return output
    
    1. 输出变量将是包含最终传递给SBCommandReturnObject的所有内容的返回字符串。
    2. 遍历mdict字典中找到的所有键。
    3. 这将获取命中数量和第一个对象(下一步从里面取出模块名)。
    4. 获取模块名。
    5. 遍历Python列表中的所有SBSymbolContext项,并将名称添加到输出变量中。

    扩展handle_command函数中的代码,以便可以使用刚刚创建的两个新方法。找到:

    output = ''
    for context in contextlist:
        output += context.symbol.name + '\n\n'
    

    替换为:

    mdict = generateModuleDictionary(contextlist)
    output = generateOutput(mdict, options, target)
    

    试一下:

    (lldb) reload_script
    (lldb) lookup DSObjectiveCObject
    ************************************************************
    8 hits in Allocator
    ************************************************************
    -[DSObjectiveCObject setLastName:]
    
    -[DSObjectiveCObject .cxx_destruct]
    
    -[DSObjectiveCObject setFirstName:]
    
    -[DSObjectiveCObject eyeColor]
    
    -[DSObjectiveCObject init]
    
    -[DSObjectiveCObject lastName]
    
    -[DSObjectiveCObject setEyeColor:]
    
    -[DSObjectiveCObject firstName]
    

    我们现在可以查看OC中有两个参数的所有方法:

    (lldb) lookup initWith(\\w+\:){2,2}\]
    

    1.4 为lookup添加选项

    • 向每个查询添加加载地址,展示实际函数在内存中的位置。
    • 仅提供模块摘要。不生成函数名,只列出每个模块的命中数。

    配置generateOptionParser

    def generateOptionParser():
        usage = "usage: %prog [options] code_to_query"
        parser = optparse.OptionParser(usage=usage, prog="lookup")
        parser.add_option("-l", "--load_address",
                          action="store_true",
                          default=False,
                          dest="load_address",
                          help="Show the load addresses for a particular hit")
        parser.add_option("-s", "--module_summary",
                          action="store_true",
                          default=False,
                          dest="module_summary",
                          help="Only show the amount of queries in the module")
        return parser
    

    您将首先实现--load_address。在generateOutput函数中,找到在SBSymbolContext上迭代的for循环,并替换为:

    for context in mdict[key]:
                query = ''
                
                #1
                if options.load_address:
                    #2
                    start = context.symbol.addr.GetLoadAddress(target)
                    end = context.symbol.end_addr.GetLoadAddress(target)
                    #3
                    startHex = '0x' + format(start, '012x')
                    endHex = '0x' + format(end, '012x')
                    query += '[{}-{}]\n'.format(startHex, endHex)
    
                query += context.symbol.name
                query += '\n\n'
                output += query
    
    1. 判断是否需要加载地址信息。
    2. 符号的addrend_addr中通过GetLoadAddress获取到加载地址。
    3. 使用Python format函数对使用Python long表示开始和结束地址进行格式化。

    实验一下:

    (lldb) reload_script
    (lldb) lookup -l DSObjectiveCObject
    ************************************************************
    8 hits in Allocator
    ************************************************************
    [0x0001056de150-0x0001056de190]
    -[DSObjectiveCObject setLastName:]
    
    [0x0001056de190-0x0001056de1e9]
    -[DSObjectiveCObject .cxx_destruct]
    
    [0x0001056de0f0-0x0001056de130]
    -[DSObjectiveCObject setFirstName:]
    
    [0x0001056de070-0x0001056de090]
    -[DSObjectiveCObject eyeColor]
    
    [0x0001056ddf60-0x0001056de070]
    -[DSObjectiveCObject init]
    
    [0x0001056de130-0x0001056de150]
    -[DSObjectiveCObject lastName]
    
    [0x0001056de090-0x0001056de0d0]
    -[DSObjectiveCObject setEyeColor:]
    
    [0x0001056de0d0-0x0001056de0f0]
    -[DSObjectiveCObject firstName]
    

    然后在generateOutputmoduleName = firstItem.module.file.basename后面添加:

    if options.module_summary:
        output += '{} hits in {}\n'.format(count, moduleName)
        continue
    

    实验一下:

    (lldb) reload_script
    (lldb) lookup -s viewWillAppear
    2 hits in Allocator
    1 hits in DocumentManager
    54 hits in UIKitCore
    4 hits in ShareSheet
    6 hits in AVKit
    2 hits in WebKit
    1 hits in PDFKit
    1 hits in GLKit
    10 hits in MapKit
    27 hits in ContactsUI
    5 hits in OnBoardingKit
    2 hits in AccessibilityUIUtilities
    20 hits in Preferences
    

    相关文章

      网友评论

        本文标题:(十三)SB示例 升级版的lookup

        本文链接:https://www.haomeiwen.com/subject/wekvdhtx.html