这个系列,是很早听 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命令 //查看权限信息 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
通过下面方案解决:
brew update
- 重新下载Command Line Tools for Xcode 10,安装;
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
- 在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