越狱开发3-lldb调试和hopper使用

2016-11-14 | 阅读

调试,是越狱分析中,最常用,也是最有效的手段。

使用lldb进行调试

在Xcode使用lldb进行调试, 而越狱环境中,也是可以使用lldb来进行调试,以下是安装和使用步骤:

  1. 下载ldid : http://joedj.net/ldid

    ldid 是一个 修改二进制文件授权的工具, 使用这个工具, 我们可以对应用进行再打包。

  2. 将 ios 中的 /Developer/usr/bin/debugserver拷贝到OSX中,并为其减肥。lipo -thin arm64 debugserver -output debugserver
  3. 创建一个授权文件, 命名为ent.xml , 内容如下 :

     <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     <plist version="1.0">
     <dict>
             <key>com.apple.springboard.debugapplications</key>
             <true/>
             <key>get-task-allow</key>
             <true/>
             <key>task_for_pid-allow</key>
             <true/>
             <key>run-unsigned-code</key>
             <true/>
     </dict>
     </plist>
    
  4. debugserver 签上 entitlementsldid -Sent.xml debugserver
  5. debugserver 拷贝回iOS,放在/usr/bin路径下。
  6. 在iOS上attach依附进程 : debugserver *:1234 -a "xxxAPP" , 1234表示我们指定的端口,他人通过这个端口来访问debugserver以调试程序。
  7. 所以, 我们在OSX 上,使用lldb进行远程调试, 先输入lldb , 再输入 process connect connect://ip:1234 ,一会就可以有输出 :

    Process 12224 stopped
    * thread #1: tid = 0x9707, 0x0000000194320e0c libsystem_kernel.dylib`mach_msg_trap + 8, stop reason = signal SIGSTOP
        frame #0: 0x0000000194320e0c libsystem_kernel.dylib`mach_msg_trap + 8
    libsystem_kernel.dylib`mach_msg_trap:
    ->  0x194320e0c <+8>: ret    
    	
    libsystem_kernel.dylib`mach_msg_overwrite_trap:
        0x194320e10 <+0>: movn   x16, #0x1f
        0x194320e14 <+4>: svc    #0x80
        0x194320e18 <+8>: ret    
    (lldb)  
    

images

调试阶段了,首先,我们需要获取 ASLR的offset :

image list -o -f

输出以下结果 :

这里会列举出所有的image, 第一列[x]image的序号,不需要在意,第二列是 ASLRoffset,也就是对应的image的虚拟内存的slide , 这是最重要的数据,第三列是image的全路径和slide之后的基地址。

lldb命令详解

参考资料

lldb命令的格式如下 :

<noun> <verb> [-options [option-value]] [argument [argument..]]

格式很简单,verb options arguments 都是用空格进行分割,用双引号来包含带有空格的参数。

断点管理

简单地通过文件和行号进行断点 :

breakpoint set --file foo.c --line 12
breakpoint set -f foo.c -l 12

通过函数的名称来设置断点 :

breakpoint set --name foo
breakpoint set -n foo

可以同时使用 -n来设置多个函数的断点:

breakpoint set -n foo -n bar

要为C++函数设置断点,使用method

breakpoint set --method foo
breakpoint set -M foo

设置 OC里面selector的断点 :

breakpoint set --seletor alignLeftEdges:
breakpoint set -S alignLeftEdges:

也可以针对可执行的image设置断点 :

breakpoint set --shlib foo.dylib --name foo
breakpoint set -s foo.dylib -n foo

可以重复使用 --shlib来标记多个公共库。

breakpoint简写为br

breakpoint set -n "-[SKTGraphicView alignLeftEdges:]"
br s -n "-[SKTGraphicView alignLeftEdges:]"

lldb中可以设置自定义的缩写模式, 如 :

breakpoint set --file foo.c --line 12

打印当前的断点列表 , breakpoint list 简写为 br li :

(lldb) br li
Current breakpoints:
4: name = '-[NSArray objectAtIndex:]', locations = 1, resolved = 1, hit count = 0
  4.1: where = CoreFoundation`-[NSArray objectAtIndex:], address = 0x0000000184ef467c, resolved, hit count = 0 

每个断点都会有一个id,用于标记,如上面的 4。使用 breakpoint delete id 删除断点,简写 br del, 使用breakpoint enable idbreakpoint disable id 来启用或禁用断点,

抓取崩溃堆栈

breakpoint set -E Swift

breakpoint set -E objc

breakpoint set -E c++

使用lldb 调试错误, 查看当前线程列表 :

thread list

查看当前线程堆栈信息

thread  backtrace

选择帧

frame select n

使用地址进行断点

使用hopper查看需要打断点的地址 :

首先这个地址得是你所用测试机的架构的地址,即当前我使用5S,需要用arm64架构的地址。

然后由于APP拥有 ASLR 特性,导致真实运行时地址不是可执行文件中的地址,所以我们要去获取image的基地址,也就是用image list -o -f ,得到的地址 :

上图中,左边的0x0000000000058000 是可执行文件中的地址,而括号中的0x0000000100058000才是我们需要的运行地址。

然后我们将地址相加,就可以得到真正需要断点的地址,以地址设置断点:

br s -a 0x00000001000b0000+0x0013482A

成功断点 :

自定义alias

我们可以设置alias

command alias bfl breakpoint set -f %1 -l %2
bfl foo.c 12

用户可以修改~/.lldbinit文件,以存储自己的快捷键。

raw

raw命令, 使用expression声明row命令, 如 :

e count = 42
p count

raw命令中,可能会遇到这种情况 :

e -h + 18

这里-h是一个标识还是一个变量,难以区分, 我们用--来声明输入,

e -- -h + 18 // 表示 负 h + 18 
e -h -- + 18 // 而 这里 -h 表示 --help

而刚才的命令 printe --的简写。

在使用print命令时, 我们用p object输出的是对象的地址,要输出对象本身时, 使用 e -o -- obejct,也就是po命令。

输出时,可以使用 print/<fmt>来输出指定格式:

(lldb) p/x 16 // 输出16进制
0x10
(lldb) p/t (char)16 // 输出二进制
0b00010000

变量

我们可以在expression命令中声明变量,但是声明的变量,需要使用美元符号开头:

(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2

流程控制

Xcode界面上,我们看到4个按钮, continue step over step into step out , 这四个按钮对应了lldb中常用的命令:

process continue 取消暂停,继续执行函数 ,别名是 continue,简写是 c.

thread step-over : 执行当前函数中的一行代码,但是不跳进函数。简写是 next ,n

thread step-in : 跳进函数中, 简写 step,s

thread step-out : 跳出函数,简写是 finish

thread list : 列出线程列表

thread backtrace : 列出线程堆栈。可以通过thread select 2切换线程。 thread backtrace all 输出所有线程堆栈

thread return <RETURN EXPRESSION> : 直接返回当前函数, 可以设置返回值。

其他命令

使用frame info 查看当前的断点的函数信息。

可以通过help命令查看帮助, 如help frame ,help thread , help process

调试的时候,我们经常需要打印界面的层次,使用以下命令:

po [[UIApp keyWindow] recursiveDescription]

recursiveDescription是一个私有函数。会打印出view的所有层次信息。

lldb高级

我们可以随意操作断点中的函数,如我们在函数断点中, 通过命令thread return直接返回,以跳过函数的逻辑。

我们也可以添加Action以在断点时,执行自定义事件。 使用br command add 断点 ,如 :

(lldb) breakpoint set -n isEven
Breakpoint 1: where = DebuggerDance`isEven + 16 at main.m:4, address = 0x00000001083b5d00
(lldb) breakpoint modify -c 'i == 99' 1
(lldb) breakpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> p i
> DONE

breakpoint modify -c 'i == 99' 1 指添加action的触发条件。

使用Action,我们可以在调试时,随意修改函数,进行所需要的测试功能,

初始化应用程序,而不是运行中附着

debugserver -x backboard *:1234 /var/mobile/Containers/Bundle/Application/FCE08BF6-E1AE-40A4-AA5A-594DB3977EE3/AlipayWallet.app/AlipayWallet

初始化程序,目的是从程序入口就开始进行附着,这样我们就可以在一些安全防护代码执行之前,进行破解。 最常用的就是跳过ptrace

(lldb) br set -n ptrace
Breakpoint 2: where = libsystem_kernel.dylib`__ptrace, address = 0x00000001966af2d4
(lldb) br command add 2
Enter your debugger command(s).  Type 'DONE' to end.
> thread return
> c
> DONE

我们在程序开始之前,对ptrace设置断点,就可以跳过ptrace防护。

ASLR 特性

ASLRAddress Space Layout Randomization , 地址空间随机布局,该特性是为了防御对已知地址的攻击, 使程序在运行时加载的地址是随机的。 所以在调试时的断点设置的地址,为程序中的文件偏移加上加载到内存的随机地址。

有ASLR特性的程序在Mach-o文件头上都会有PIE的标识。

Hopper使用

hoppper 是一个著名的跨平台反编译工具,能够反编译 32/64位 的可执行文件,并进行修改。下载使用 Hopper的破解版, 从PYG论坛搬运过来, 插件下载地址 , 插件最新为 4.0.8版本,所有要去下载4.0.8版本的Hopper.

使用文档

hopper中最常用的操作,就是直接修改汇编代码 ,在菜单Modify - Assemble Instruction 进行汇编代码的修改。