LLDB是Xcode自带的调试器,作为一个iOS应用开发程序员,平时我在开发应用时会使用LLDB来调试代码。在逆向应用时,也会用到LLDB来跟踪应用的执行过程。LLDB还内置了一个Python解析器,使用Python脚本可以方便LLDB的调试,比如自动化执行一些命令,或者自动化处理数据之类的,具体说明可以参考官方的文档:LLDB Python Reference。 以下就以一个具体的例子来演示一个Python脚本的编写过程: 一、获取方法的偏移地址
运行系统自带的计算器Calculator.app:
可以看到计算器的菜单里有一个“关于计算器”的选项: 如果我们要在点击“关于计算器”事件断下来的话,就要给这个方法设置一个断点。
先找到计算器的可执行文件的路径,路径为:/Applications/Calculator.app/Contents/MacOS/Calculator。
在左侧的搜索框输入”showabout”,会搜索到一个结果: 点击这个结果,会跳转到该方法的汇编代码处: -[CalculatorController showAbout:]: 00000001000093dd push rbp ; Objective C Implementation defined at 0x1000188d0 (instance) 00000001000093de mov rbp, rsp 00000001000093e1 mov rdi, qword [ds:objc_cls_ref_NSDictionary] ; objc_cls_ref_NSDictionary, argument "instance" for method imp___got__objc_msgSend 00000001000093e8 mov rsi, qword [ds:0x10001b6f0] ; @selector(dictionaryWithObject:forKey:), argument "selector" for method imp___got__objc_msgSend 00000001000093ef lea rdx, qword [ds:cfstring_2000] ; @"2000" 00000001000093f6 lea rcx, qword [ds:cfstring_CopyrightStartYear] ; @"CopyrightStartYear" 00000001000093fd call qword [ds:imp___got__objc_msgSend] 0000000100009403 mov rdi, rax 0000000100009406 pop rbp 0000000100009407 jmp imp___stubs__NSShowSystemInfoPanel ; endp 由汇编代码可知[CalculatorController showAbout:]方法在文件中的偏移地址为0x00000001000093dd。 二、使用LLDB调试器设置断点接下来运行终端,执行以下命令,让LLDB调试器依附计算器的进程: Jobs: ~$ ps aux | grep Calculator Jobs 79888 0.0 0.1 2781792 11620 ?? S 6:21下午 0:01.07 /Applications/Calculator.app/Contents/MacOS/Calculator Jobs 80204 0.0 0.0 2432772 568 s002 R+ 6:26下午 0:00.00 grep Calculator Jobs: ~$ lldb -p 79888 (lldb) process attach --pid 79888 Process 79888 stopped * thread #1: tid = 0x6030af, 0x00007fff912d34de libsystem_kernel.dylib`mach_msg_trap + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff912d34de libsystem_kernel.dylib`mach_msg_trap + 10 libsystem_kernel.dylib`mach_msg_trap: -> 0x7fff912d34de <+10>: retq 0x7fff912d34df <+11>: nop libsystem_kernel.dylib`mach_msg_overwrite_trap: 0x7fff912d34e0 <+0>: movq %rcx, %r10 0x7fff912d34e3 <+3>: movl $0x1000020, %eax Executable module set to "/Applications/Calculator.app/Contents/MacOS/Calculator". Architecture set to: x86_64h-apple-macosx. (lldb) continue Process 79888 resuming (lldb) 接着执行以下命令获取应用的ASLR偏移地址: (lldb) image list -o [ 0] 0x000000000cafa000 [ 1] 0x00007fff93d51000 [ 2] 0x000000010cb1e000 ...... (lldb) 第一行数据的16进制地址0x000000000cafa000就是应用的ASLR偏移地址,之前我们已经找到了[CalculatorController showAbout:]方法的偏移地址是0x00000001000093dd,那么该方法在内存中的地址就是0x000000000cafa000 + 0x00000001000093dd。 所以我们可以执行下面的命令来给这个方法设置断点,注意加号两边不要有空格: (lldb) br set -a "0x000000000cafa000+0x00000001000093dd" Breakpoint 1: where = Calculator`___lldb_unnamed_function161$$Calculator, address = 0x000000010cb033dd (lldb) 这时点击菜单的“关于计算器”选项,程序就会断下来: Process 79888 stopped * thread #1: tid = 0x6709de, 0x000000010cb033dd Calculator`___lldb_unnamed_function161$$Calculator, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 frame #0: 0x000000010cb033dd Calculator`___lldb_unnamed_function161$$Calculator Calculator`___lldb_unnamed_function161$$Calculator: -> 0x10cb033dd <+0>: pushq %rbp 0x10cb033de <+1>: movq %rsp, %rbp 0x10cb033e1 <+4>: movq 0x12b98(%rip), %rdi ; (void *)0x00007fff78684488: NSDictionary 0x10cb033e8 <+11>: movq 0x12301(%rip), %rsi ; "dictionaryWithObject:forKey:" (lldb) 三、编写Python脚本那么问题来了,我们每次调试应用都要先手动获取ASLR偏移地址,如果要给多个地址打断点的话,就要写很多遍”ASLR偏移地址+方法偏移地址”,操作比较繁琐。如果这些重复性的操作可以自动完成的话,在平时的使用过程中应该能节省不少时间。 用Python就可以很方便地实现这个功能,接下来我们就来写一个简单的脚本,以简化设置断点的操作。脚本的主要功能是自动获取应用的ASLR地址,用户设置断点时只需输入方法在文件中的的偏移地址,脚本会自动把ASLR偏移地址和方法偏移地址相加,再设置断点。 新建一个Python文件,保存至~/sbr.py,代码如下: #!/usr/bin/python #coding:utf-8 import lldb import commands import optparse import shlex import re # 获取ASLR偏移地址 def get_ASLR(): # 获取'image list -o'命令的返回结果 interpreter = lldb.debugger.GetCommandInterpreter() returnObject = lldb.SBCommandReturnObject() interpreter.HandleCommand('image list -o', returnObject) output = returnObject.GetOutput(); # 正则匹配出第一个0x开头的16进制地址 match = re.match(r'.+(0x[0-9a-fA-F]+)', output) if match: return match.group(1) else: return None # Super breakpoint def sbr(debugger, command, result, internal_dict): #用户是否输入了地址参数 if not command: print >>result, 'Please input the address!' return ASLR = get_ASLR() if ASLR: #如果找到了ASLR偏移,就设置断点 debugger.HandleCommand('br set -a "%s+%s"' % (ASLR, command)) else: print >>result, 'ASLR not found!' # And the initialization code to add your commands def __lldb_init_module(debugger, internal_dict): # 'command script add sbr' : 给lldb增加一个'sbr'命令 # '-f sbr.sbr' : 该命令调用了sbr文件的sbr函数 debugger.HandleCommand('command script add sbr -f sbr.sbr') print 'The "sbr" python command has been installed and is ready for use.' 然后在LLDB中执行下面的语句就可以把脚本导入到LLDB: (lldb) command script import ~/sbr.py The "sbr" python command has been installed and is ready for use. (lldb)
输出结果表示名为sbr的Python命令已经装载好并可以使用了。 (lldb) br delete About to delete all breakpoints, do you want to do that?: [Y/n] y All breakpoints removed. (1 breakpoint) (lldb) sbr 0x00000001000093dd Breakpoint 2: where = Calculator`___lldb_unnamed_function161$$Calculator, address = 0x000000010cb033dd (lldb) 这时点击菜单的“关于计算器”选项,方法也成功断下来了。 四、启动LLDB时自动加载Python脚本
对于经常要使用到的脚本,可以在LLDB的初始化文件里添加命令加载脚本,这样LLDB启动后就能使用自定义的命令了。 command script import ~/sbr.py 重新进入LLDB,可以看到脚本已经自动加载了: Jobs: ~$ lldb The "sbr" python command has been installed and is ready for use. (lldb) command source -s 1 '/Users/Jobs/./.lldbinit' The "sbr" python command has been installed and is ready for use. (lldb) (责任编辑:好模板) |