前言
本篇文章给大家演示一下,如何在越狱机
上进行第三方App
的调试。期间会利用一些很常用的动态调试的工具,例如Reveal
、Cycript
、lldb
等。
一、Reveal
首先给大家介绍Reveal
,它是一款UI调试
神器,对iOS逆向
开发非常有帮助。这里使用Version 4(8796)
版本。
Reveal官网直接下载安装,可以用试用版。
1.1安装
1.1.1 Mac端
下载完成后,打开,输入邮箱,接收试用key👇🏻
去到邮箱,接收并输入试用key👇🏻
主界面👇🏻
1.1.2 手机端
打开Cydia
,安装Reveal Loader
插件👇🏻
1.2 使用
导入dylib
文件
- 在手机上,进入/Library,创建RHRevealLoader目录
mkdir RHRevealLoader
- 在Mac电脑上,打开Reveal👇🏻
找到RevealServer
路径
- 打开终端,将
RevealServer
拷贝到手机的/Library/RHRevealLoader
目录下,重命名为libReveal.dylib
scp -P 12345 ./RevealServer root@localhost:/Library/RHRevealLoader/libReveal.dylib
开启允许调试的应用
- 打开设置,找到
Reveal
选项
- 开启允许调试的应用,例如
WeChat
使用Reveal进行UI调式
-
在Mac电脑上,打开Reveal软件。手机上重新启动
WeChat
。 -
在电脑的Reveal中,出现两个WeChat,分别是WiFi连接和USB连接。
- 点击USB连接的WeChat,可进行UI调式,并且不会阻塞WeChat的进程
至此,我们就能在上面很直接的看到微信的UI层级了,使用起来XCode
的Debug View
相似度很高。
二、debugserver
2.1 lldb附加
接下来,我们看看在越狱环境
中,是如何使用Xcode
利用lldb
进行进程附加
的。
- 打开
Xcode
,随意打开一个项目,空工程
也可以 - 选择
真机
调试,在Debug
菜单中,选择Attach to Process
,选择WeChat
进程
显示Running
,表示附加成功👇🏻
- 使用
lldb
将应用暂停
👇🏻
- 使用
Debug View
进行UI调试
2.2 lldb原理
上面之所以能通过lldb
进行进程附加
,调试手机中的应用,是因为手机中的debugserver
开启了相关服务。
-
Xcode
中有lldb
给手机中的debugserver
发送指令。 - 手机中的
debugserver
会附加App
,读App
中的内容做一系列操作。 -
debugserver
将读取到的结果给到lldb
显示。
在越狱环境中,我们只需要开启debugserver
服务,就可以利用lldb
远程调试三方应用了。
2.3 探索debugserver
Mac端
- 找到Mac电脑中的
debugserver
,进入以下目录👇🏻
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport
- 可以找到不同iOS系统版本,所对应的镜像文件👇🏻
- 进入设备对应的系统目录,找到
dmg
文件(我的设备是12.1.2
版本)👇🏻
- 打开
dmg
文件,进入usr/bin
目录可以看到debugserver
。这就是Xcode
安装到真机
中的文件👇🏻
手机端
在手机系统中,也存在一个debugserver
。当Xcode
第一次连接手机,就会将对应版本的debugserver
安装到手机系统中。
进入手机的/Developer/usr/bin
目录下👇🏻
2.4 copy debugserver
- 将
手机
中的debugserver
拷贝到Mac电脑
中
scp -P 12345 root@localhost:/Developer/usr/bin/debugserver ./
- 将拷贝后的
debugserver
生成md5值
md5 debugserver
- 找到Mac电脑中的
debugserver
cd /Volumes/DeveloperDiskImage/usr/bin
- 将Mac电脑中的
debugserver
生成md5值
md5 debugserver
上图可见,手机端和Mac端的debugserver
文件的Hash一致,说明手机中的debugserver
就是Mac电脑中指定系统目录下的debugserver
。
2.5 USB启动debugserver
2.5.1 iPhone中开启debugserver服务
Mac
电脑中的lldb
连接手机上的debugserver
,需要配置IP和端口号
。
在手机中,查看debugserver
命令
./debugserver
-------------------------
debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87
for arm64.
Usage:
debugserver host:port [program-name program-arg1 program-arg2 ...]
debugserver /path/file [program-name program-arg1 program-arg2 ...]
debugserver host:port --attach=<pid>
debugserver /path/file --attach=<pid>
debugserver host:port --attach=<process_name>
debugserver /path/file --attach=<process_name>
debugserver 主机地址:端口号 –a 应用进程
- 由于主机地址是当前手机,可以使用
localhost
代替 - 端口号 👉🏻 启动
server
服务开放端口,让远程的lldb
通过sever
调试进程
2.5.2 附加WeChat应用
接下来,我们来使用手机上的debugserver
,进行附加WeChat
应用。
- 找到
WeChat
进程
- 使用
debugserver
附加WeChat
应用
遇到错误👇🏻
Failed to get connection from a remote gdb process.
解决方法 👉🏻 使用
ldid
对debugserver
配置权限
- 进入手机中
debugserver
拷贝到Mac电脑的目录(上面执行过) - 导出
debugserver
的权限
ldid -e debugserver > debugserver.entitlements
我导出来的
entitlements
中,有2个plist,因为在iOS 12
之后debugserver
包含两个架构arm64
和arm64e
。
我们可以拆分
架构生成重签对应架构的debugserver(当然不拆也没有问题,不拆plist中两份配置都要改,我选择的不拆)👇🏻
lipo -thin arm64 debugserver -output debugserver_arm64
- 删除
三项
权限👇🏻- seatbelt-profiles
- com.apple.security.network.server
- com.apple.security.network.client
- 添加
四项
权限👇🏻
<key>task_for_pid-allow</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>platform-application</key>
<true/>
<key>run-unsigned-code</key>
<true/>
那么,修改后的debugserver.entitlements
文件👇🏻
<?xml version="1.0" encoding="UTF-8"?>
<!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>com.apple.backboardd.launchapplications</key>
<true/>
<key>com.apple.backboardd.debugapplications</key>
<true/>
<key>com.apple.frontboard.launchapplications</key>
<true/>
<key>com.apple.frontboard.debugapplications</key>
<true/>
<key>com.apple.diagnosticd.diagnostic</key>
<true/>
<key>com.apple.private.memorystatus</key>
<true/>
<key>com.apple.private.cs.debugger</key>
<true/>
<key>platform-application</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>com.apple.system-task-ports</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!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>com.apple.backboardd.launchapplications</key>
<true/>
<key>com.apple.backboardd.debugapplications</key>
<true/>
<key>com.apple.frontboard.launchapplications</key>
<true/>
<key>com.apple.frontboard.debugapplications</key>
<true/>
<key>com.apple.diagnosticd.diagnostic</key>
<true/>
<key>com.apple.private.memorystatus</key>
<true/>
<key>com.apple.private.cs.debugger</key>
<true/>
<key>platform-application</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>com.apple.system-task-ports</key>
<true/>
</dict>
</plist>
- 导入权限文件到
debugserver
ldid -Sdebugserver.entitlements debugserver
回到之前的附加流程👇🏻
- 接下来就是将
debugserver
拷贝回手机
⚠️注意:手机中的
/Developer/usr/bin
目录,有权限问题
,不能直接拷贝。
那么,就拷贝到手机的/usr/bin
目录,拷贝后可全局使用
👇🏻
scp -P 12345 ./debugserver root@localhost:/usr/bin/debugserver
- 接着,在手机端上找到
WeChat
进程👇🏻
ps -A | grep WeChat
- 最后,使用
debugserver
,再次附加WeChat
应用👇🏻
debugserver localhost:12346 -a 11656
-------------------------
debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87
for arm64.
Attaching to process 11656...
Listening to port 12346 for a connection from localhost...
- 使用
lldb
连接debugserver
- 在Mac电脑上,进入lldb环境
lldb
- 连接
debugserver
process connect connect://10.165.45.19:12346
错误 👉🏻 error: Failed to connect port
我们使用USB
端口映射,修改usbConnect.sh
脚本,增加12346
的端口映射
python /Users/aronm1/python-client/tcprelay.py -t 22:12345 12346:12346
使用USB连接
- 手机上使用
debugserver
,附加WeChat
应用
./debugserver localhost:12346 -a 11733
再一次,Mac电脑上,进入lldb
环境
lldb
使用lldb连接debugserver
process connect connect://localhost:12346
-------------------------
Process 11733 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00000001a3e740f4 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
-> 0x1a3e740f4 <+8>: ret
libsystem_kernel.dylib`mach_msg_overwrite_trap:
0x1a3e740f8 <+0>: mov x16, #-0x20
0x1a3e740fc <+4>: svc #0x80
0x1a3e74100 <+8>: ret
Target 0: (WeChat) stopped.
连接成功,输入c
,继续运行
c
-------------------------
Process 11733 resuming
输入process interrupt,暂停
process interrupt
使用command + w
,停止
WeChat附加,但不杀掉应用
。
三、class-dump
class-dump
之前文章就使用过了,它是一个命令行工具,最高版本为class-dump 3.5 (64 bit)
,已经停止更新
。
查看class-dump
的路径👇🏻
which class-dump
上图可见,来自MonkeyDev
框架。
3.1 class-dump的使用
在MonkeyDev中,class-dump如何使用?
- 搭建MonkeyDev项目
- 在
Build Settings
中,将MONKEYDEV_CLASS_DUMP
默认的NO
修改为YES
- 编译项目,主工程下生成
Headers
目录,自动导出头文件(以微信
为例)
⚠️注意:工程目录下不要包含
中文
,否则Headers
目录以及头文件无法生成。
四、命令行工具
接下来,给大家演示一下 👉🏻 搭建自定义的命令行工具。
- 创建App项目,命名
FuncDemo
- 打开
main.m
文件,写入以下代码👇🏻
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
for (int intIndex=0; intIndex<argc; intIndex++) {
printf("参数%i:%s\n",intIndex, argv[intIndex]);
}
return 0;
}
- 编译项目,将
MachO
文件拷贝到手机上
需要将
MachO
文件copy
出来
scp -P 12345 ./FuncDemo root@localhost:~/
- USB连接手机设备
usb-iphone8.sh
- 使用自定义命令行工具
./FuncDemo -v
-------------------------
参数0:./FuncDemo
参数1:-v
上图可见,参数0
为默认,显示当前MachO
。
五、lldb手动砸壳
砸壳
之前讲过,有很多方式可以进行。逆向分析一个应用,第一步就是对应用砸壳
。
例如,查看WeChat
的crypt
信息👇🏻
otool -l WeChat | grep crypt
-------------------------
cryptoff 16384
cryptsize 187236352
cryptid 1
-
cryptid
👉🏻 为0
表示应用已砸壳 -
cryptoff
👉🏻 表示开始加密的偏移位置 -
cryptsize
👉🏻 表示加密长度
将应用
砸壳
后,才能使用class-dump
导出头文件。
5.1 查看手机中的MachO
-
USB
连接手机设备,找到WeChat
的沙盒路径
ps -A | grep WeChat
-------------------------
11783 ?? 0:52.19 /var/containers/Bundle/Application/B9046860-DDDA-44B4-AFF5-AF20FFA6FC9D/WeChat.app/WeChat
- 将
WeChat
拷贝到Mac电脑
scp -P 12345 root@localhost:/var/containers/Bundle/Application/B9046860-DDDA-44B4-AFF5-AF20FFA6FC9D/WeChat.app/WeChat ./
- 查看
MachO
文件中的crypt
信息
otool -l WeChat | grep crypt
5.2 方式一:修改cryptid砸壳
-
使用
MachOView
打开WeChat
-
在
Load Commands
中,找到LC_ENCRYPTION_INFO_64
,修改Crypt ID
为0
- 使用
class-dump
导出头文件
class-dump -H WeChat -o ./header
⚠️注意:建议千万别试,会不停的循环输出
乱码
!
所以,仅修改cryptid,无法导出头文件。因此砸壳的关键,并不是cryptid
,而是将加密的代码段
进行解密
。
5.3 方式二:lldb手动砸壳
接下来,我们尝试lldb
手动砸壳,我们知道 👇🏻
砸壳的逻辑,是从内存中读取
cryptoff位置
到cryptsize长度
的数据,然后将其覆盖
原始MachO文件。
- 使用
Xcode
打开工程,选择真机设备
,附加WeChat进程
-
image list
获取MachO的首地址
(lldb) image list
[ 0] EB606691-98E6-384F-BABB-F46E7BC265F9 0x0000000102378000 /var/containers/Bundle/Application/B9046860-DDDA-44B4-AFF5-AF20FFA6FC9D/WeChat.app/WeChat (0x0000000102378000)
首地址是 👉🏻 0x0000000102378000
- 从内存中,将加密部分的代码段,导出到
WeChat.bin
文件。因为已读取到内存中,相当于已解密。
memory read --force --outfile ~/Downloads/WeChat.bin --binary --count 187236352 0x0000000102378000+16384
代码段加密的开始位置 👉🏻
MachO首地址 + 加密偏移地址
- 将
WeChat.bin
文件,复制到WeChat
MachO文件相同的目录,然后将其写入到MachO
文件中相同位置,相当于用解密后的数据,覆盖原始的加密数据
dd seek=16384 bs=1 conv=notrunc if=./WeChat.bin of=WeChat
-
seek
👉🏻 从输出文件开头跳过x
个块后再开始复制 -
bs
👉🏻 同时设置读入/输出
的块大小为x
个字节 -
conv=notrunc
👉🏻 不截断输出文件 -
if
👉🏻输入
文件名,默认为标准输入。即指定源文件
-
o
f 👉🏻输出
文件名,默认为标准输出。即指定目的文件
耗时比较久,共计500多秒。接着对WeChat
MachO文件的cryptid
修改为0,可以成功导出头文件👇🏻
六、Tweak屏蔽Badge红点气泡
接下来,我们创建Tweak插件,来屏蔽应用的红点气泡👇🏻
实现该功能的前提是需要附加系统的桌面程序
SpringBoard
。
- USB连接手机,找到
SpringBoard
进程
- 将
SpringBoard
拷贝到Mac电脑
scp -P 12345 root@127.0.0.1:/System/Library/CoreServices/SpringBoard.app/SpringBoard ./
- 查看
SpringBoard
的MachO
文件中的crypt
信息
上图可见,MachO
中没有加密信息,说明SpringBoard
原本就没有加壳
。
- 既然没有加壳,我们尝试使用
class-dump
导出头文件
class-dump -H SpringBoard -o ./header
果然,可以dump出头文件,所以SpringBoard
是没有加壳
的。
- 动态调试,定位找出红点气泡相关的类。
现在我们已知的动态调试有3种方式👇🏻
-
Reveal
👉🏻SpringBoard
无法显示在Mac端的Reveal
客户端中,不可用! -
Cycript
👉🏻 采用附加SpringBoard
进程的方式,然后通过cy指令
查找定位红点UI,需要手动搜索,很不直观,不建议使用! -
lldb
👉🏻 使用lldb附加SpringBoard
进程,然后通过Debug View
找到红点对象,很直接,建议使用!
我们定位到红点对象的类 👉🏻 SBIconParallaxBadgeView
。
- 验证是否为
SBIconParallaxBadgeView
- usb连接手机,进入cy环境
- 导入自定义
cy
脚本
- 打印当前vc视图层级
cy# currentVC()
#"<SBHomeScreenViewController: 0x10329c780>"
cy# #0x10329c780.view.recursiveDescription().toString()
- 在结果中搜索
SBIconParallaxBadgeView
结果只有1处,因为当前页面只有一处红点气泡。
<SBIconParallaxBadgeView: 0x112192f20; frame = (45 -11; 26 26); animations = { <UIInterpolatingMotionEffect: 0x283402300>=<CABasicAnimation: 0x281749280>; <UIInterpolatingMotionEffect: 0x2834065d0>=<CABasicAnimation: 0x28174aca0>; }; layer = <CALayer: 0x2816ba7e0>>\n
- 将其设置为隐藏
#0x112192f20.hidden=YES
然后手机上的红点气泡没了👇🏻
- 接下来我们通过
Hook
的方式,实现隐藏气泡
。
- 在导出的头文件中,找到
SBIconParallaxBadgeView.h
文件
- 对
SBIconParallaxBadgeView
的init
方法进行HOOK
,破坏它,即可隐藏红点气泡
七、Tweak插件实现隐藏气泡
要实现对SBIconParallaxBadgeView
的init
方法进行HOOK
,需要搭建Tweak
插件。
- 使用
nic.pl→15
,创建Tweak插件
- 在
Makefile
文件中,增加IP和端口
小技巧:配置到环境变量,一劳永逸。
vim ~/.zshrc
export THEOS_DEVICE_IP=localhost
export THEOS_DEVICE_PORT=12345
source ~/.zshrc
- 打开
Tweak.x
文件,写入以下代码👇🏻
%hook SBIconParallaxBadgeView
- (id)init {
return nil;
}
%end
- 编译、打包、安装插件
cd badgedemo
make
make package;make install
⚠️注意:安装这一步需要USB连接手机设备!
安装完成后,手机会重启桌面,再次进入桌面时,气泡全部消失。同时,在Cydia
中会显示已安装的BadgeDemo
插件👇🏻
八、MonkeyDev搭建Tweak插件
最后,我们使用MonkeyDev
插件,来创建Tweak插件
。
- 新建
Logos Tweak
项目
项目名称 👉🏻 BadgeMonkeyDemo
- 项目结构
-
BadgeMonkeyDemo.xm
👉🏻 代码 -
control
👉🏻 配置信息,版本号、作者名称等 -
BadgeMonkeyDemo.plist
👉🏻 附加应用的包名称等信息
- 在
Build Settings
中,搜索Monkey
,找到Tweak
的设置👇🏻
有以下自定义的配置项👇🏻
-
MonkeyDevBuildPackageOnAnyBuild
👉🏻 每次编译时打包 -
MonkeyDevClearUiCacheOnInstall
👉🏻 安装时清除缓存 -
MonkeyDevCopyOnBuild
👉🏻 编译时拷贝包到目录 -
MonkeyDevDeviceIP
👉🏻 设备IP
-
MonkeyDevDevicePassword
👉🏻 设备密码 -
MonkeyDevDevicePort
👉🏻 设备端口 -
MonkeyDevInstallOnAnyBuild
👉🏻 每次编译时安装 -
MonkeyDevkillProcessOnInstall
👉🏻 安装成功后杀掉的进程
- 和写Tweak插件时一样,设置IP和端口,同样将这两项配置在环境变量中👇🏻
export MonkeyDevDeviceIP=localhost
export MonkeyDevDevicePort=12345
- 同样,打开BadgeTweakDemo.xm文件,写入以下代码👇🏻
#import <UIKit/UIKit.h>
%hook SBIconParallaxBadgeView
- (id)init {
return nil;
}
%end
- 在
Build Settings
中,搜索signing
设置签名
Code Signing Identity
设置为iOS Developer
👇🏻
- 编译项目
如果报错👇🏻
解决 👉🏻 按目录找到CydiaSubstrate.tbd
文件,删除里面的i386和x86_64
👇🏻
再次run项目,成功安装Tweak
插件,红点气泡全部隐藏。
总结
-
Reveal
-
iOS
安装插件 -
Mac
安装客户端App - 将动态库导入
iPhone
-
-
USB
启动debugserver
-
终端附加
◦ 手机,使用debugserver 主机名称:端口 -a 进程id
◦Mac
电脑,启动lldb,使用process connect connect://主机名称:端口
◦USB
端口映射 -
Xcode
附加
◦ 打开工程
◦ 选择设备
◦ 附加进程(菜单栏Debug->Attach to process ->选择进程
)
-
-
debugserver
权限问题- 导出权限文件,查看文件
◦ldid -e debugserver > debugserver.entitlements
- 删除权限
◦seatbelt-profiles
◦com.apple.security.network.server
◦com.apple.security.network.client
- 添加权限
◦task_for_pid-allow
设置为YES
◦get-task-allow
设置为YES
◦platform-application
设置为YES
◦run-unsigned-code
设置为YES - 导入权限文件
◦ldid -Sdebugserver.entitlements debugserver
- 导出权限文件,查看文件
-
class-dump
class-dump -H MachO文件路径 -o 头文件路径
-
MonkeyDev
中,可以快速使用class-dump
-
命令行工具
-
argc
:参数个数 -
argv
:参数数组
-
-
lldb手动砸壳
- memory read命令
◦ 通过--outfile
参数,导出文件
◦ 通过--count
参数,指定导出的大小 -
dd
命令
◦ 写入源文件
◦seek
指定偏移,也就是跳过多少开始写入
◦conv
保留没有替换的部分
- memory read命令
-
Tweak修改系统行为
-
Reveal
无法使用,在手机设置页的Reveal
选项中,没有SpringBoard
应用 -
Cycript
可以使用,但定位UI
不直观 -
lldb
可以使用,最简单的方式
-
-
MonkeyDev
搭建Tweak
插件- 在
Build Settings
中,配置参数 - 设置签名
- 编译项目并安装插件
- 在
网友评论