AI智能
改变未来

IOS逆向学习-动态调试原理、LLDB

logify、Xcode运行原理

  • 1. 动态调试Xcode
  • 1.1 Xcode的动态调用原理
  • 1.2 动态调试任何APP
  • 1.2.1 原理图和条件
  • 1.2.2 debugserver的权限问题
  • 1.2.3 让debugserver附加到某个APP进程
  • 1.2.4 通过debugserver启动App
  • 1.2.5 在Mac上启动LLDB,远程连接iPhone上的debugserver服务
  • 1.2.5 可能遇到的问题(手机环境:ios 12.5.1)
  • 2. LLDB
    • 2.1 LLDB命令格式
    • 2.2 help
    • 2.3 expression
    • 2.3 thread xxx
    • 2.4 breakpoint
    • 2.5 内存断点
    • 2.6 image(模块)
    • 2.7 小技巧

    1. 动态调试Xcode

    • 什么叫动态调试?将程序运行起来,通过下面断点,打印等方式,查看参数、返回值、函数调用流程等

    1.1 Xcode的动态调用原理

    • 关于GCC、LLVM、GDB、LLDB

      Xcode的编译器发展:GCC -> LLVM

    • Xcode的调试器发展:GDB -> LLDB
  • debugserver

    一开始存放在Mac的Xcode里面

      /Applications/Xcode12.3.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver

  • 当Xcode识别手机设备时,Xcode会自动将

    debugserver

    安装到iPhone上,如果是从来没有真机测试的iPhone上是没有

    debugserver

    的:

  • Xcode调试的局限性:一般情况下,只能通过Xcode安装的APP

  • Xcode调试APP的原理图:

  • 当你真机运行的时候,Xcode会在手机上安装

    debugserver

    的,

    debugserver

    监听着

    LLDB

    发送的一些指令,首先

    LLDB

    将这些指令传送给

    debugserver

    , 然后

    debugserver

    接收到这个指令时,在执行到

    app

    上,

    app

    执行指令后把结果反馈给

    debugserver

    ,然后再由

    debugserver

    把结果返回给

    LLDB

    ,在由

    LLDB

    把信息打印在

    Xcode

    上,这也是Xcode可以动态调试app的原因:

  • p

    : 直接地址

  • po

    :打印对象

  • LLDB 是通过数据线来传递数据的

  • 1.2 动态调试任何APP

    1.2.1 原理图和条件

    动态调试任何App示意图:

    • 首先手机上必须要有
      debugserver

      ,其次我们可以通过终端 输入

      lldb

      进入

      lldb

      的调试环境:

    1.2.2 debugserver的权限问题

    • 默认情况下,
      /Developer/usr/bin/debugserver

      缺少一定的权限,只能通过Xcode安装的APP,无法连接其他APP(比如来自App Store的App)

    • 如果希望调试其他APP,需要对
      debuserver

      重新签名,签上两个调试相关的权限

      get-task-allow Boolean 类型 值为YES
    • task_for_pid-allow Boolean 类型 值为YES
  • 如何给
    debugserver

    签上权限

      iPhone上的

      /Developer

      目录是只读,无法直接对

      /Developer/usr/bin/debugserver

      文件直接签名,需要先把

      debugserver

      赋值到Mac上

    • 通过
      ldid

      导出以前文件以前的签名权限:

      ldid -e debugserver > debugserver.entitlements
    • debugserver.entitlements

      文件加上

      get-task-allow、t20000ask_for_pid-allow

      权限:

    • 在通过
      ldid

      命令重新签名:

      did -Sdebugserver.entitlements debugserver
    • 将已经签好权限的debugserver方法到手机的
      /usr/bin

      目录,便于找到debugserver指令

    • 关于权限也可以使用
      codesign
    # 查看权限信息codesign -d --entitlements - debugserver# 签名权限codesign -f -s - --entitlements debugserver.entitlements debugserver# 或则缩写codesign -fs- --entitlements debugserver.entitlements debugserver

    1.2.3 让debugserver附加到某个APP进程

    • 我们可以再终端查看

      debugserver

      的指令:

    • debugserver *:端口号 -a 进程

      *:端口号

      :使用iphone的某个端口启动debugserver服务(只要不是保留端口号就行,就和我们使用SSH登录把iphone的22端口映射到本机的10010一样)

    • 进程:输入APP的进程信息(进程ID和进程名称)

    1.2.4 通过debugserver启动App

    debugserver -x auto *:端口号 App的可执行文件路径

    1.2.5 在Mac上启动LLDB,远程连接iPhone上的debugserver服务

    • 启动LLDB:

      lldb

    • 连接debugserver服务:

      process connect connect://手机IP地址:debugserver服务端端口号

    • 使用LLDB的

      c

      命令让程序先继续运行:

      c

    • 手机端:

    • 电脑端:
      刚开始是卡主的,我们输入

      c

      指令App才可以正常运行

    1.2.5 可能遇到的问题(手机环境:ios 12.5.1)

    • failed to attach to process named: “”

      : 找不到该进程的名称,确认进程名称重新输入

    • Failed to get connection from a remote gdb process

    • 解决方法:

      使用的端口可能被占用, 换一个端口试试

    • debugserver

      的权限文件中包含以下权限(删除该权限即可):

  • rejecting incoming connection from ::ffff:127.0.0.1

  • 出现这种情况指令:
    手机端:

    debugserver *:10012 -a neteasemusic

    电脑端:

    process connect connect://localhost:10012

    或则

    process connect connect://127.0.0.1:10012

    解决方案:
    手机端:

    debugserver 127.0.0.1:10012 -a neteasemusic

    电脑端:

    process connect connect://127.0.0.1:10012

    暂时还没有找到其它的解决方案

    2. LLDB

    2.1 LLDB命令格式

    <command> [<subcommand> [<subcommand>...]] <action> [-options [option-Value]] [argument [arguments...]]
    • <command>

      :命令

    • <subcommand>

      :子命令

    • <action>

      :命令操作

    • <options>

      :命令选项

    • <argument>

      : 命令参数

    • 比如给test函数设置断点:
      breakpoint set -n test

      :

      breakpoint

      <command>
    • set

      <action>
    • -n

      <options>
    • test

      <agrument>

    通过上图,当我们过掉

    touchesBegan:withEvent:

    方法中断点时,会进入到

    test

    函数中的断点(通过LLDB指令添加的断点)

    2.2 help

    • help<command>

      : 查看指令的用法,比如

      help breakpoint、help breakpoint set

    2.3 expression

    • expression<cmd-options>--<expr>

      : 执行一个表达式

      <cmd-options>

      :命令选项

    • --

      :命令选项结束符,表示所有命令选项已经设置完毕,如果没有命令选项,

      --

      可以省略

    • <expr>

      :需要执行的表达式

      expression self.view.backgroundColor = [UIColor redColor]

    有时候我们在通过打断点形式调试程序的时候,特别想动态的让这个程序动态的执行某段代码,以前我们是先把程序退掉,然后把要执行的代码加上,在重新运行程序,那么

    expression

    指令帮们我们实现这个目的:

    由上图可知,我们可以直达直接访问view的

    backgroundColor

    的会报错,但是我们知道每一个view都有一个CALayer,我们可以直接访问这个属性,所以最终需改背景色的表达式:

    expression self.view.layer.backgroundColor = [UIColor redColor].CGColor

    • expression、expression --

      和指令

      print、p、call

      的效果一样

    • expression -O --

      和指令

      po

      效果一样

    • p

      是直接打印对象的地址,但是

      po

      相当于

      NSLog

      一样的效果:

    2.3 thread xxx

    • thread backtrace

      : 打印线程的堆栈信息,和指令

      bt

      的效果一样,示例:比如我们想知道test函数是谁调用的:

    • thread return [<expr>]

      ;让函数直接返回某个值,不会执行断点后面的代码

    • frame variable [<variable-name>]

      :打印当前栈帧的变量,(

      frame(栈帧)

      一个函数调用就是一帧)

    • thread continue、continue、c

      :程序继续运行

    • thread step-over、next、n

      :单补运行,把子函数当做整体一步执行

    • thread step-in、step、s

      :单步运行,遇到子函数会进入函数

    • thread step-out、finish

      :直接执行完当前函数的所有代码,返回到上一个函数

    上述四个指令对应着Xcode工具的四个指令:
    注意:在一行代码一行代码的情况下 这两个指令效果相同, 如果是执行函数的时候,

    setp over

    是直接过掉整个函数 而

    setp into

    会进入函数内部一步一步的走;如果进入函数内部了, 执行

    setup out

    瞬间执行完函数代码

    ,并且回到函数调用的时候, 相当于函数已经调用完了

    • thread step-inst-over 、nexti 、ni

      :汇编指令级别的单步运行,把一个汇编函数当做一个整体一步执行

    • thread step-inst 、stepi、si

      :汇编指令级别的单步运行,遇到子函数会进入子函数,一行汇编指令的执行

    • si、ni

      s、n

      类似:前者是汇编指令级别,后者是源码级别

    2.4 breakpoint

    • breakpoint set

      :设置断点

      breakpoint set -a 函数地址
    • breakpoint set -n 函数名
      breakpoint set -n test
    • breakpoint set -n touchesBegan:withEvent:
    • breakpoint set -n "-[ViewController touchesBegan:withEvent:]"
  • breakpoint set -r 正则表达式
  • breakpoint set -s 动态库 -n 函数名
  • breakpoint list

    :列出所有的断点(每个断点都有自己的编号)

  • breakpoint disable 断点编号

    :禁用断点

  • breakpoint enable 断点编号

    :启用断点

  • breakpoint delete 断点编号

    :删除断点

    • breakpoint command add 断点编号

      :给断点预先设置需要执行的命令,到出发断点时,就回按照顺序执行

    • breakpoint command list 断点编号

      :查看某个断点设置的命令

    • breakpoint command delete 断点编号

      :删除某个断点设置的命令

    首先查看所有断点, 然后给第二个断点添加需要执行命令, 在出发第二个断点时,就回按照顺序执行预设置的命令

    注意: 在给断点设置完预设置命令时,需要输入DONE来结束输入命令。

    2.5 内存断点

    • watchpoint set variable 变量

      watchpoint set variable self->age
    • watchpoint set expression 地址

      watchpoint set expression &(self->_age)
    • watchpoint list
    • watchpoint disable 断点编号
    • watchpoint enable 断点编号
    • watchpoint delete 断点编号
    • watchpoint command add 断点编号
    • watchpoint command list 断点编号
    • watchpoint command delete 断点编号

    我们给

    _count

    打了一个断点,当我们修改

    _count

    的值时候,就会进入断点

    2.6 image(模块)

    • image lookup -t 类型

      :查找某个类型的信息,不用进入头文件,可以快速的查看一些类的信息

    • image lookup -a 地址

      :根据内存地址查找在模块中的位置,对于崩溃信息的定位有比较好的作用

    • image lookup -n 符号或者函数名

      :查找某个符号或则函数的位置

    • image list

      列出锁加载的模块的信息

    • image list -o -f

      :打印出模块的偏移地址、全路径

    可以运行app当前的一些模块 ,我们自己的app本身也是一个模块,app依赖的动态库也是一个模块

    2.7 小技巧

    • Enter

      ,会自动执行上次的命令

    • 绝大部分指令都可以使用缩写
    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » IOS逆向学习-动态调试原理、LLDB