CydiaSubstrate
绝大部分tweak正常工作的基础, 它由MobileHooker MobileLoader Safe mode 组成.
MobileHooker
替换系统函数. 也就是所谓的hook, 它主要包含以下两个函数:
(1) 其中MSHookMessageEx作用于OC函数, 通过调用method_setImplementation函数将[class selector] 的实现改为replacement, 达到hook的目的. 如 向一个NSString对象发送hasSuffix消息, 而做出hasPrefix的操作, 相当于把函数实现换了.
(2) MSHookFunction : 作用于C和C++函数, 通过编写汇编指令, 在进程执行到function时转而执行replacement, 同时保持function的指令及其返回地址, 使得用户可以选择性地执行function, 并保证进程能够在执行完replacement后继续正常运行. 而且MSHookFunction对function的指令总长度是有要求的, function所有指令加起来长度不能太短.(8字节)
MSHookFunction三个参数作用分别是: 替换的原函数 替换函数 被MobileHooker保存的原函数.这个体系写法如下:
MobileLoader
作用是加载第三方dylib. iOS 启动时, 会由launchd将MobileLoader载入内存, 然后MobileLoader会根据dylib的同名plist文件指定的作用范围, 有选择地在不同进程里通过dlopen函数打开目录/Library/MobileSubstrate/DynamicLibraries/ 下的所有dylib.
Safe mode
由于tweak本质是dylib, 寄生在别的进程里, 一旦出错可能会导致进程崩溃, 而如果是SpringBoard等系统进程, 则会造成iOS 系统瘫痪, 所以CydiaSubstrate引入Safe mode, 他会捕获SIGTRAP SIGABRT SIGILL SIGBUS SIGSEGV SIGSYS这六种信号, 然后进入安全模式. 安全模式里所有基于CydiaSubstrate的第三方均会被禁用. 而且这个插件要自己去安装, 越狱之后是不会自动安装的, 所以本人之前的手机就白苹果了, 如果有这个插件可以避免一些问题的出现, 不过白苹果也是可以解决的, 通过iTunes刷机就好了, 还是可以恢复正常的, 但系统就是最新的了.
Cycript
首先ssh到手机的进程, 然后 cycript, 出现cy# 的提示符就说明已成功启动cycript.
按下control + D , 先退出 Cycript. 如果要测试NSStrign类的 length函数功能, 则可注入任意连接了Foundation库的进程.
通过进程注入方式调用Cycript测试函数的步骤很简单, 以SpringBoard为例, 首先找到进程名或PID如下:
SpringBoard的进程PID是4634, 接下来输入 cycript-p 4634 或者 cycript-p SpringBoard, 把Cycript注入到SpringBoard, 这时Cycript已经运行到SpringBoard进程里了.
如果知道对象的内存地址, 还可以通过# 操作符来获取这个对象.
如果知道对象的内存地址, 还可以通过# 操作符来获取这个对象.
通过choose命令, 可以获取类对象的地址.
LLDB和debugserver
LLDB
全称 Low Level Debugger 苹果出品, 内置Xcode中的动态调试工具, 运行在Mac中. LLDB功能可以概括以下四点:
(1) 在指定的条件下启动程序
(2) 在指定的条件下停止程序
(3) 在程序停止的时候对程序进行改动, 观察程序的执行过程有什么变化.
(4) 在程序停止的时候检查程序内部发生的事.
debugserver
运行在iOS 上, 它作为服务端, 实际执行LLDB(客户端)传过来的命令, 再把执行结果反馈给LLDB显示给用户. 所谓的远程调试. 默认iOS 上没有安装debugserver, 只有设备连接过Xcode, 并运行调试过才会把debugserver安装到iOS 的 /Developer/usr/bin/ 目录. 因为缺少task_for_pid权限,所以只能调试自己的App.
配置debugserver
1.给debugserver减肥
由于本人用的是iPhone5s所以选择arm64的架构.
首先将未处理过的debugserver拷贝到电脑中(~/debugserver)这里可以直接用pp助手.
然后帮助debugserver减肥.
lipo -thin armv7s ~/debugserver -output ~/debugserver
复制代码
2.给debugserver添加task_for_pid权限
下载xml格式文件配置信息 iosre.com/ent.xml 到debugserver同级目录.
然后执行如下命令:
codesign -s - --entitlements ent.xml -f debugserver
复制代码
执行前确保xml文件和debugserver在同一个文件夹内, 而且执行当前命令时要在当前文件的文件夹中.
如图:
3.然后将处理过的debugserver拷贝到手机中(/usr/bin/)
这里没有覆盖之前的文件, 一是因为原版文件是不可写的, 无法覆盖, 二是因为/usr/bin/下的命令无须输入全路径就可以执行, 即在任何路径下运行debugserver都可以启动处理过的debugserver.
最后还要给debugserver赋予执行权限命令如下:
chmod +x /usr/bin/debugserver
复制代码
如果在电脑终端执行必须要ssh到当前手机才可以.
4.用debugserver启动或附加进程
-
启动进程
debugserver -x backboard *:1234 /Applications/MobileSMS.app/MobileSMS 复制代码
debugserver会启动MobileSMS, 并开启1234端口, 等待来自任何IP的LLDB接入.
如果中途出现错误, 那么就有可能是task_for_pid权限没加上.
-
附加进程
debugserver *:1234 -a "MobileSMS" 复制代码
其实附加进程和启动进程区别就是需要手动打开指定的App.
-
错误
如果出现如图:
说明iOS上的/Developer/目录下缺少必要的调试数据.因为没有在Xcode的Window->Devices菜单中添加此设备, 重新添加即可.
LLDB使用说明
在终端中输入lldb即可启动lldb. 如图:
然后执行:
process connect connect://iOSIP:1234
复制代码
记得把iOSIP换成自己手机的IP.
成功后如图:
在这之前debugserver启动过进程或者附加了进程才可以.
所以电脑终端最好开倆个窗口好操作些, 用手机终端则麻烦很多.
这个时候我们就可以开始调试了, 下面看一下常用的LLDB命令.
1. image list
用于列举当前进程中的所有模块, 因为ASLR的关系, 每次进程启动时(就是当你打开一个应用时), 同一进程的所有模块在虚拟内存中的起始地址都会产生随机偏移. 个人理解其实就是App启动时在手机内存中是有一个起始的内存地址的, 而ASLR其实就是让App每次打开时的起始地址随机.
那么怎么获取模块的起始地址呢? 待LLDB链接debugserver后, 先输入如下口令:
image list -o -f
复制代码
- 模块基地址
上图的输出中 第一列 [x] 是模块的序号. 第二列是ASLR产生随机偏移大小. 第三列是模块的全路径, 括号里是偏移之后的起始地址. 模块的起始地址术语叫模块基地址.
偏移后模块基地址 = 偏移前模块基地址 + ASLR偏移
复制代码
如上图MobileSubstrate.dylib的偏移前模块基地址 = 0x0000000104910000 - 0x0000000104910000 = 0
那这个0哪里来的呢? 我们把MobileSubstrate.dylib放到IDA中.
把View-A拉到最上面看到的第一行, 其中0就是我们计算后的偏移前基地址.
-
符号基地址
偏移后符号的基地址 = 模块ASLR偏移 + 符号基地址
如果是偏移前只要减去ASLR偏移即可.
符号偏移前基地址可以在IDA中根据符号来获取
只要知道偏移前基地址从IDA看, ASLR偏移从LLDB看就可以了.
2. breakpoint
和Xcode中的断点一样, 只不过这里不是图形工具而已. 一般逆向工程中用到的:
b function
在函数的起始位置设置断点
br s –a address
br s –a 'ASLROffset+address'
在地址处设置断点
复制代码
以我自己新建Xocde工程项目设置函数[ViewController buttonAction:]断点为例:
(1) 用IDA查看这个项目偏移前的基地址, 把项目二进制文件放入IDA中, 定位到buttonAction:方法可以看到:
第一条指令 SUB SP, SP, #0x30 偏移前的基地址是0x100006728.
复制代码
(2) 通过LLDB查看ASLR偏移 0xa8000
(3) 设置并触发断点:
指令的偏移后基地址 = 0x100006728 + 0xa8000 = 0x1000ae728
在LLDB中设置断点
br s -a 0x1000ae728
复制代码
其中Breakpoint后面的1是断点的序号, 以后会用到.
然后我们点击屏幕上的按钮触发断点.
打印的是一些方法的信息, 当进程停下来后我们可以用c命令继续运行.
还可以通过br dis 和 br en 和 br del 来禁用 启用 删除断点.
如果是禁用所有断点则执行
br dis
复制代码
禁用某个断点在后面加上断点的序号
br dis 1
复制代码
同理启用和删除断点也是一样.
br en
br del
复制代码
另外一个很有用的命令就是在断点触发前执行预先设置的指令, 它的用法如下:
br com add 1
复制代码
然后出现如图:
其中po i 是要执行的指令, 而DONE是退出设置指令. 数字4是断点的序号.
这里设置了一条指令, 然后我们点击按钮触发方法如图:
这个时候i的值是1.
这个命令一般用于自动观察某个断点触发时的上下文变化, 后面会用到.
3. print
LLDB主要功能之一是在程序停止的时候检查程序内部发生的事, 而这个功能是通过print命令完成的, 他可以打印某处的值. 以我本人最近开发的程序 -[HomePageController tableView:didSelectRowAtIndexPath:] 方法为例演示一系列用法.
po $r0 输出r0对应的值
p $r0 输出r0值的类型以及命令结果
p/x $r0 输出r0的十六进制值
x/10 $r0 输出指针r0指向的连续10个字的数据
nexti(ni) 执行下一条机器指令 不会进入函数体
stepi(si) 执行下一条机器指令 会进入函数体
** 进不进入函数体的意思就是你再方法中调用其他方法, 而当断点到这个方法的时候, 如果是上面的指令会跳转的这个调用的方法里面, 而下面的指令不会进入. **
register write r0 1 给寄存器r0赋值为1
复制代码