iOS 逆向(六)动态调试

1,285 阅读5分钟

这个系列,是很早听 MJ 课程时的整理,现在分享出来。 其中一些参考资料有些有引用,有些可能忘记添加了,如果有引用部分资料,可以联系我。

iOS 逆向(一)环境搭建
iOS 逆向(二)Cycript
iOS 逆向(三)逆向工具
iOS 逆向(四)脱壳
审核中 iOS 逆向(五)Theos工具 iOS 逆向(六)动态调试
iOS 逆向(七)重签名

一、什么是动态调试

将运行起来的程序,通过断点、打印等方式,查看参数、返回值、函数调用流程等。

1.1 Xcode调试APP的原理

Xcode中内置LLDB调试器,通过与iPhone连接,在iPhone安装debugserver的程序,debugserver与LLDB互相通信,发送调试指令和接收结果,debugserver与App之间互相通信,发送指令和接受结果。

  • GCC、LLVM、GDB、LLDB

    • Xcode的编译器发展历程:GCC--->LLVM
    • Xcode的调试器发展历程:GDB--->LLDB
  • debugserver 一开始存放在Mac的Xcode里面

    • /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/De viceSupport/9.1/DeveloperDiskImage.dmg/usr/bin/debugserver
  • 当Xcode识别到手机设备时,Xcode会自动将debugserver安装到iPhone上

    • /Developer/usr/bin/debugserver
  • Xcode调试的局限性

    • 一般情况下,只能调试通过Xcode安装的app
  • LLDB调试,请参考iOS调试(二)LLDB

1.2 动态调试其他APP原理

二、如何动态调试

2.1 debugserver的权限问题

  • 默认情况下,/Developer/usr/bin/debugserver缺少一定的权限,只能调试通过Xcode安装的APP,无法调试其他的APP(比如通过App Store下载的APP)
  • 如果希望调试其他APP,需要对debugserver重新签名,签上两个调试相关的权限:
    • get-task-allow
    • task_for_pid-allow

2.2 给debugserver签上权限

签名使用到的工具ldid,当然也可以使用codesign。此处利用之前已经讲述的命令ldid

2.2.1 拷贝debugserver到Mac

iPhone上的Developer目录是只读的,无法直接对/Developer/usr/bin/debugserver文件签名,需要先把debugserver复制到Mac

2.2.2 导出原来权限

通过ldid命令导出文件以前的签名权限

$ ldid -e debugserver > debugserver.entitlements

2.2.3 添加权限

debugserver.entitlements添加get-task-allow和task_for_pid-allow权限如下:

2.2.4 重签名

$ ldid -Sdebugserver.entitlements debugserver

将已经签好权限的debugserver放到/usr/bin目录,便于直接执行该命令

codesign命令 //查看权限信息 codesigndentitlementsdebugserver//签名codesign -d --entitlements - debugserver //签名 codesign -f -s - --entitlements debugserver.entitlements debugserver // 签名简写为 $ codesign -fs- --entitlements debugserver.entitlements debugserver

2.3 搭建debugserver环境

2.3.1 权限不足

//权限不足
5s:~ root# debugserver
-sh: /usr/bin/debugserver: Permission denied

//添加运行权限
5s:~ root# chmod +x /usr/bin/debugserver

2.3.2 附加到进程

debugserver Usage: debugserver host:port [program-name program-arg1 program-arg2 ...] debugserver /path/file [program-name program-arg1 program-arg2 ...] debugserver host:port --attach= debugserver /path/file --attach= debugserver host:port --attach=<process_name> debugserver /path/file --attach=<process_name>

debugserver *:端口号 -a 进程
  • *:端口号 使用iPhone的某个端口号(不要使用保留端口号)启动debugserver服务
  • -a 进程 app的进程id或者进程名称

此处,用端口号10086,附加到微信进程:

注意:此处端口号,之后要通Mac lldb建立连接。

5s:~ root# debugserver *:10086 -a WeChat
Attaching to process WeChat...
Listening to port 10086 for a connection from *...

如上,连接之后,iPhone 10086端口会监听是否有连接到该端口的程序。

2.3.3 lldb连接debugserver

  • 启动LLDB```bash $ lldb




##### Python 2.7错误排除


​```bash
$ lldb                                                                             
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python/lldb/__init__.py", line 98, in <module>
    import six
ImportError: No module named six
....

出现以上错误,尝试此方案

$ brew reinstall python@2
$ pip install six

假如pip遇到下面错误,尝试方案,我的当前系统10.14,Xcode 10:

Traceback (most recent call last):
  File "/usr/local/bin/pip", line 11, in <module>
  .....
ImportError: No module named zlib

通过下面方案解决:

  1. brew update
  2. 重新下载Command Line Tools for Xcode 10,安装;
  3. brew reinstall python@2

之后,再次启动,仍然遇到错误:

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 52, in <module>
    import weakref
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py", line 14, in <module>
    from _weakref import (
ImportError: cannot import name _remove_dead_weakref

方案在这儿。

再次重启,完美!

  • 连接debugserver服务``` (lldb) process connect connect://手机IP地址:debugserver服务端口号

此处,由于上面开放的端口为10086,连接,需要稍等,输出如下:```bash
(lldb) process connect connect://192.168.1.6:10086
Process 1844 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000198420a40 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
->  0x198420a40 <+8>: ret

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x198420a44 <+0>: mov    x16, #-0x20
    0x198420a48 <+4>: svc    #0x80
    0x198420a4c <+8>: ret
Target 0: (WeChat) stopped.
  • 优化连接debugserver 我们现在是通过WiFi连接,同样也可以通过USB连接。 在我们之前的bfios-usb.sh脚本,脚本地址:```bash

This is a comment too

echo '---------------------------------------------' echo 'SSH login' echo 'mac port 10010 connect to iPhone port 22' echo '---------------------------------------------' echo 'lldb connect debugserver by mac port 9999 to iPhone port 10086' echo '1. In iPhone: debugserver *:10086 -a App name' echo '2. In Mac: enter lldb' echo '3. In Mac: process connect connect://localhost:9999' echo '---------------------------------------------' iproxy 10010 22 &
iproxy 9999 10086


现在,我们连接debugserver的命令修改为:```
(lldb) process connect connect://localhost:9999
  • 继续运行程序``` (lldb) c Process 1844 resumin




现在,可以通过Mac上的lldb直接动态调试App了。


### 2.3.4 总结


1. Mac调用脚本`bfios-usb.sh`通过USB连接手机;
2. Mac调用脚本`bfios-login.sh`登录到手机;
3. 在手机中,debugserver连接到运行中的app;```
5s:~ root# debugserver *:10086 -a WeChat
  1. 在Mac上,lldb连接到debugserver:```bash $ lldb (lldb) process connect connect://localhost:9999 (lldb) c

5. 调试中.....



# 三、ASLR


	Address Space Layout Randomization,地址空间布局随机化。


	ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术。


	iOS4.3开始引入了ASLR技术


一个Mach-O文件整个地址布局分为三种:


- Mach-O 文件地址布局:Mach-O不运行,不会映射到内存,在文件内的地址空间;
- Mach-O 未使用ASLR的内存布局:Mach-O会运行,但映射到内存中的地址空间;
- Mach-O 使用ASLR的内存布局:Mach-O运行,使用ASLR映射到内存中的地址空间;



☞ **关于Mach-O更多的信息**,请参考[Mach-O(一)结构](https://wenghengcong.com/posts/9cb913e/)及[Mach-O(二)内存分布](https://wenghengcong.com/posts/f13a5377/)。


下面实例以微信的破壳头文件WeChat为例,并且以`Segment`来说明:


## 3.1 文件地址布局


文件地址布局,就是Mach-O文件中各个段的布局,其中一个Segment:


- File Offset:段偏移地址
- File Size:段大小



**计算:下一个段地址File Offset = 上一个段Offset + File Size**


我们通过MachOExplore来整理得到下表:

| Segment | File Offset | File Size |
| --- | --- | --- |
| __PAGEZERO | 0x0 | 0x0 |
| __TEXT | 0x0 | 0x3910000 |
| __DATA | 0x3910000 | 0xCA8000 |
| __LINKEDIT | 0X45B8000 | 0x3AB210 |



计算__LINKEDIT Offset = __DATA Offset + __DATA  Size


0X45B8000 =  0x3910000 + 0xCA8000


另外,`Section`之间的地址也是这种规律。


## 3.2 未使用ASLR的内存布局


	文件地址,是读取Segment的 File Offset以及File Size。


	那么内存布局,也就是我们说的虚拟内存布局,我们在开发中说的内存,都是经过系统调度分配的虚拟内存,系统将虚拟内存映射到真实的物理内存,这一步我们不用去管。


	未使用ASLR的内存布局,即是通过Mach-O文件中的,**VM Address**以及**VM Size**来确定其在内存中的位置。


- VM Address:虚拟内存起始地址,
- VM Size:虚拟内存中占据的大小



我们再次列表查看WeChat的内存布局:

| Segment | VM Address | VM Size | File Offset | File Size |
| --- | --- | --- | --- | --- |
| __PAGEZERO | 0X0 | 0x100000000 | 0x0 | 0x0 |
| __TEXT | 0x100000000 | 0x3910000 | 0x0 | 0x3910000 |
| __DATA | 0x103910000 | 0xD48000 | 0x3910000 | 0xCA8000 |
| __LINKEDIT | 0x104658000 | 0x3AC000 | 0X45B8000 | 0x3AB210 |



据此表格,我们绘制出如下图:


![](http://blog-1251606168.file.myqcloud.com/blog/2018-11-01-094257.png#align=left&display=inline&height=1040&margin=%5Bobject%20Object%5D&originHeight=1040&originWidth=1880&status=done&style=none&width=1880)


针对上图,我们还有一些需要说明的点:


- __PAGEZERO大小为0x100000000,是固定值;
   - arm64:0x100000000(8个0)
   - 非arm64:0x4000(3个0)
- __TEXT段在加载内存之后,大小不变,函数代码存放在__TEXT段中;
   - 代码段的内存地址,就是LC_SEGMENT(__TEXT)中的VM Address
- __DATA段在加载到内存中占用空间变大,是由于系统虚拟内存会边界对齐,以提高调度速率;
   - 全局变量等存放在__DATA段中
- 可使用`size -l -m -x WeChat`来查看Mach-O的内存分布
- 可使用`vm_stat`查看进程在运行时的内存分布



## 3.3 使用ASLR的内存布局


	ASLR随机产生的Offset(偏移),此处为:0x3500


![](http://blog-1251606168.file.myqcloud.com/blog/2018-11-01-094435.png#align=left&display=inline&height=1062&margin=%5Bobject%20Object%5D&originHeight=1062&originWidth=1898&status=done&style=none&width=1898)


- 函数的内存地址
函数的内存地址(VM Address) = File Offset + ASLR Offset + __PAGEZERO Size
- Hopper、IDA中的地址都是未使用ASLR的VM Address
- 通过image查看ASLR随机分配的地址空间:```bash
(lldb) image list -f -o