自定义打印方法参数的LLDB命令

说在前面

  • iOS逆向开发中,当我们命中方法断点后,需要打印方法调用者、方法名、参数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Process 42106 resuming
    Process 42106 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001008f7bfc OCTest` __hidden#17_ at __hidden#40_:126
    Target 0: (OCTest) stopped.
    (lldb) po $x0 // 打印方法调用者
    <ViewController: 0x101e1d000>
    (lldb) po (char *)$x1 // 打印方法名
    "performLoginWithUserName:password:encryptType:completion:"
    (lldb) po $x2 // 打印第一个参数
    lyy2020@163.com
    (lldb) po $x3 // 打印第二个参数
    L163.2020
    (lldb) po $x4 // 打印第三个参数
    1
    (lldb) po $x5 // 打印第四个参数
    1
  • 每次都做相同的操作显得特别啰嗦,浪费时间又浪费精力,有没有比较简单的方法呢,答案是有的。
  • 我们可以自定义脚本,断点命中后,执行一个自定义命令自动打印以上参数,大大提高逆向的效率,何其快哉。

简单介绍

  • Python的强大毋庸置疑,LLDB提供了Python脚本扩展支持,那么LLDB与Python配合无限强大
  • 进入LLDB模式,并获得帮助
    • (lldb) script help(lldb) 获取模块信息
    • (lldb) script help(lldb.SBFrame) 获得lldb.SBFrame类的帮助
    • (lldb) script help(lldb.process) 获得lldb.process对象帮助
  • 官网可知,使用Python函数创建一个新的LLDB命令,要求的格式如下:
    1
    2
    def command_function(debugger, command, exe_ctx, result, internal_dict):
    pass
    • debugger:lldb.SBDebugger类型,表示当前调试器对象
    • command:包含执行命令所有参数的Python字符串。
    • exe_ctx:lldb.SBExecutionContext类型,表示执行上下文对象
    • result:lldb.SBCommandReturnObject类型,一个包含成功或者失败信息的返回对象
    • internal_dict: 包含所有变量和函数的Python字典对象
  • 以上是Python函数,所以使用Pycharm编写代码再合适不过了,原因如下
    • 断点调式功能异常强大
    • 代码提示功能无与伦比
    • …更多…

搭建环境

  • 执行(lldb) script help(lldb),找到LLDB模块的位置,在Pycharm中进行导入
    1
    2
    3
    ...省略很多... 
    FILE
    /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3/lldb/__init__.py
  • 安装官网的做法,并不能成功使用lldb模块,所以不采用这种方式
    1
    2
    3
    import sys
    sys.path.append('/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3/lldb')
    import lldb
  • 首先进行如下操作,然后在Pycharm中直接import lldb,发现没有报错,证明可以导入LLDB模块。
    1
    2
    3
    4
    拷贝
    /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/Python3/lldb 文件夹

    /Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages目录下,

实战

目的

  • 当命中断点后,执行objargs命令直接打印所有参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Process 42161 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001000cfbfc OCTest` __hidden#18_ at __hidden#43_:128
    Target 0: (OCTest) stopped.
    (lldb) objargs
    方法调用者:<ViewController: 0x1004173d0>
    方法名: performLoginWithUserName:password:encryptType:completion:
    第1个参数:【类型:__NSCFConstantString】【值:lyy2020@163.com】
    第2个参数:【类型:__NSCFConstantString】【值:L163.2020】
    第3个参数:【类型:unsigned long】【值:3】
    第4个参数:【类型:__NSGlobalBlock__】【签名:"v8@?0"】
    (lldb)
  • 根据:计算参数个数
  • 参数类型
    • OC对象区分是不是Block类型,分别打印
    • 基本数据类型,直接打印类型
  • Block签名:
    • 可以访问Apple开发者官网,了解各个字符的意思
    • 可以执行po [NSMethodSignature signatureWithObjCTypes:"\"v8@?0\""]打印查看各个参数的意思

编写函数

  • 使用Pycharm新建Debug.py,内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    import lldb
    import re

    # 格式化输出
    def __format_output(return_obj):
    output = return_obj.GetOutput()
    return None if not output else output.replace('\n', '').replace('\r', '')

    # 打印各个参数
    def __print_arg(interpreter, return_obj, sequence):
    """
    打印函数的第几个参数,传入1打印第一个,以此类推
    """
    register = '$x%s' % str(sequence + 1)
    # 参数值
    interpreter.HandleCommand('po %s' % register, return_obj)
    arg_value = __format_output(return_obj)
    # 参数类型
    interpreter.HandleCommand('po [%s class]' % register, return_obj)
    arg_type: str = __format_output(return_obj)
    # 不是oc对象类型,是基本数据类型
    if not arg_type:
    interpreter.HandleCommand('p %s' % register, return_obj)
    p1 = re.compile(r'[(](.*?)[)]', re.S) # 提取括号里面的参数类型
    arr = re.findall(p1, __format_output(return_obj))
    arg_type = arr[0]
    print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
    return
    # 是oc对象类型,判断是不是block类型
    interpreter.HandleCommand('po (BOOL)[%s isKindOfClass: [NSBlock class]]' % register, return_obj)
    is_block = __format_output(return_obj)
    if not is_block == 'YES': # 如果不是block
    print("第%s个参数:【类型:%s】【值:%s】" % (sequence, arg_type, arg_value))
    return
    # block的处理方式
    interpreter.HandleCommand('x/4xg %s' % register, return_obj)
    descriptor_address = __format_output(return_obj).split(' ')[-1]
    offset = 3 if 'Global' in arg_type else 5
    interpreter.HandleCommand('x/%sxg %s' % (str(offset),descriptor_address), return_obj)
    sign_address = __format_output(return_obj).split(' ')[-1]
    interpreter.HandleCommand('x/s %s' % sign_address, return_obj)
    sign = __format_output(return_obj).split(' ')[1]
    print("第%s个参数:【类型:%s】【签名:%s】" % (sequence, arg_type, sign))


    # 定义打印参数的函数
    def objargs_func(debugger, command, exe_ctx, result, internal_dict):
    """
    人性化的方式打印objc_msgSend的各个参数
    """
    interpreter = debugger.GetCommandInterpreter()
    # 保存结果
    return_obj = lldb.SBCommandReturnObject()
    # 处理命令
    interpreter.HandleCommand('po $x0', return_obj)
    print("方法调用者:%s" % __format_output(return_obj))
    interpreter.HandleCommand('x/s $x1', return_obj)
    method_name: str = __format_output(return_obj).split(' ')[1][1:-1]
    print("方法名:\t %s" % method_name)
    args = method_name.split(':')[:-1]
    if not len(args): # 如果没有参数
    return
    for index, arg in enumerate(args):
    sequence = index + 1
    __print_arg(interpreter, return_obj, sequence)

导出命令

  • 在Debug.py中填入如下内容。__lldb_init_module是脚本的入口,添加后可以提供给LLDB交互界面使用。
    1
    2
    def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f Debug.objargs_func objargs')
    • Debug.objargs_func为执行objargs时自动映射的函数
    • objargs为LLDB交互中使用的命令
  • 将Python脚本加载到LLDB中
    • 若是自动加载,需要vim ~/.lldbinit,然后里面就输入以下命令
      1
      command script import ~/Exercise/Debug.py
    • 若是临时加载,只需在LLDB模式下输入command script import ~/Exercise/Debug.py

pdb调试

简单介绍

  • 有时需要在LLDB中对Python脚本断点调试,这时需要用到pdb。pdb是python程序提供了一种交互的源代码调试功能,主要特性包括设置断点、单步调试、进入函数调试、查看当前代码、查看栈片段、动态改变变量的值等。
  • pdb提供了一些常用的调试命令,如下
    • break(缩写为b) 设置断点
    • continue(缩写为c) 跳过断点,继续执行
    • list(缩写为l) 查看当前的代码段所在的行
    • step(缩写为s) 单步进入函数
    • return(缩写为r) 执行代码直到函数返回
    • exit(或q) 退出当前的调式模式
    • next(缩写为n) 跳过当前行
    • pp 打印变量的值
    • help 帮助

基本调试

  • objargs_func函数开头设置断点,依次打印五个参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    Process 42161 resuming
    Process 42161 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001000cfbfc OCTest` __hidden#18_ at __hidden#43_:128
    Target 0: (OCTest) stopped.
    (lldb) objargs
    > /Users/drag/OCTest/OCTest/Debug.py(55)objargs_func()
    -> interpreter = debugger.GetCommandInterpreter()
    (Pdb) l
    50 def objargs_func(debugger, command, exe_ctx, result, internal_dict):
    51 """
    52 人性化的方式打印objc_msgSend的各个参数
    53 """
    54 pdb.set_trace()
    55 -> interpreter = debugger.GetCommandInterpreter()
    56 # 保存结果
    57 return_obj = lldb.SBCommandReturnObject()
    58 # 处理命令
    59 interpreter.HandleCommand('po $x0', return_obj)
    60 print("方法调用者:%s" % __format_output(return_obj))
    (Pdb) pp debugger
    <lldb.SBDebugger; proxy of <Swig Object of type 'lldb::SBDebugger *' at 0x112553300> >
    (Pdb) pp command
    ''
    (Pdb) pp exe_ctx
    <lldb.SBExecutionContext; proxy of <Swig Object of type 'lldb::SBExecutionContext *' at 0x112469f00> >
    (Pdb) pp result
    <lldb.SBCommandReturnObject; proxy of <Swig Object of type 'lldb::SBCommandReturnObject *' at 0x112469a20> >
    (Pdb) pp internal_dict
    (Pdb) c
    方法调用者:<ViewController: 0x1004173d0>
    方法名: performLoginWithUserName:password:encryptType:completion:
    第1个参数:【类型:__NSCFConstantString】【值:lyy2020@163.com】
    第2个参数:【类型:__NSCFConstantString】【值:L163.2020】
    第3个参数:【类型:unsigned long】【值:3】
    第4个参数:【类型:__NSGlobalBlock__】【签名:"v8@?0"】
    • 在debug的时候不一定能记住当前的代码块,如要要查看具体的代码块,则可以通过l命令显示,会用箭头->指向当前debug的语句
    • 如果需要在调试过程中打印变量的值,可以直接使用pp加上变量名。需要注意的是打印仅仅在当前的语句已经被执行了之后才能看到具体的值,否则会报错。
  • 对函数进行断点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    (lldb) objargs
    方法调用者:<ViewController: 0x1004173d0>
    方法名: performLoginWithUserName:password:encryptType:completion:
    > /Users/drag/OCTest/OCTest/Debug.py(21)__print_arg()
    -> arg_value = __format_output(return_obj)
    (Pdb) l
    19 interpreter.HandleCommand('po %s' % register, return_obj)
    20 pdb.set_trace()
    21 -> arg_value = __format_output(return_obj)
    22 # 参数类型
    23 interpreter.HandleCommand('po [%s class]' % register, return_obj)
    24 arg_type: str = __format_output(return_obj)
    (Pdb) s
    --Call--
    > /Users/drag/OCTest/OCTest/Debug.py(7)__format_output()
    -> def __format_output(return_obj):
    (Pdb) n
    > /Users/drag/OCTest/OCTest/Debug.py(8)__format_output()
    -> output = return_obj.GetOutput()
    (Pdb) n
    > /Users/drag/OCTest/OCTest/Debug.py(9)__format_output()
    -> return None if not output else output.replace('\n', '').replace('\r', '')
    (Pdb) l
    6 # 格式化输出
    7 def __format_output(return_obj):
    8 output = return_obj.GetOutput()
    9 -> return None if not output else output.replace('\n', '').replace('\r', '')
    10
    (Pdb) pp output
    'lyy2020@163.com\n\n'
    (Pdb) r
    --Return--
    > /Users/drag/OCTest/OCTest/Debug.py(9)__format_output()->'lyy2020@163.com'
    -> return None if not output else output.replace('\n', '').replace('\r', '')
    (Pdb) r
    > /Users/drag/OCTest/OCTest/Debug.py(23)__print_arg()
    -> interpreter.HandleCommand('po [%s class]' % register, return_obj)
    (Pdb)
    • 直接使用s进入函数内部
    • 直接使用r退出当前子函数
    • 直接使用n按顺序执行代码,遇到子函数不进入。