之前介绍了怎么获取 App 的所有类的结构信息, 这个有什么用呢? 用处大了, 比如以这一步为基础, 下一步通过注入来做更多研究工作.
注入的最小单位是函数, 实际上, 编译执行的程序在编译后, 类就不复存在了, 留下来的只是二进制代码(指令或数据都是一样的二进制代码). 所幸的是, 跟我们打交道的, 并不是二进制代码(那会困难很多), 而是函数, 而且是某个类的函数.
那么, 在用 classdump 拿到成千上万个类与函数后, 哪个函数才是我们关心的呢? 怎么锁定它们呢?
本文介绍锁定目标类与函数的可行的办法.
基本上小程研究的目标 App 都有丰富的界面, 而小程关心的场景基本都是由特定的界面触发, 所以, 从界面入手是个不错的选择.
有没有办法找出某个界面对应哪一个类呢? 这样就可以在 classdump 拿到的众多的类中仔细研究这个界面类拥有的函数与成员变量.
小程觉得目前最好的办法就是使用 Reveal 工具.
(一)使用 Reveal
先下载一个 Reveal(有破解版本), 比如 1.6 版本或 1.5 版本, 或 2.0 版本.
(1)拷贝 libReveal.dylib 到手机
Reveal 最大的一个作用是把手机上的某个 App 的界面同步显示到电脑上, 要做到这个效果, Reveal 既要在电脑上运行, 同时也要把一个 "内鬼" 打入到手机. 这个打入手机的 "内鬼" 就是 libReveal.dylib.
在电脑上运行 Reveal 后, 点击菜单 Help, 选择 Show Reveal Library in Finder, 再点击 iOS Library, 就可以找到 libReveal.dylib.
拷贝到 DynamicLibrary 目录下:
scp libReveal.dylib root@192.168.2.57:/Library/MobileSubstrate/DynamicLibraries
基本上, 手机上的 App 在启动运行后, 都可以加载 DynamicLibraries 里面的动态库(以 dylib 为后缀), 至于加载哪个动态库, 则由 plist 文件决定. 这个知识点很重要, 这意味着你可以写一个 plist 文件, 让某个 App 在启动时加载你写的动态库, 这是注入的前提.
/Library/MobileSubstrate/DynamicLibraries, 这个目录, 在手机成功越狱后就会存在.
对于 Reveal2.0 版本, 也以上面的办法定位动态库文件, 但这个文件名叫 "RevealServer", 可以拷贝过去后命名为 "libReveal.dylib" 即可.
(2)拷贝 libReveal.plist 到手机
找一个 plist 文件来修改, 或者直接写一个 plist 文件, 命名为 libReveal.plist.
libReveal.plist 文件要指定让哪个 App 启动时加载 Reveal.dylib, 比如:
<?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>Filter</key>
- <dict>
- <key>Bundles</key>
- <array>
- <string>com.tencent.QQKSong</string>
- </array>
- </dict>
- </dict>
- </plist>
上面的 plist 内容, 让 "全民 k 歌" 启动时, 加载 Reveal.dylib.
com.tencent.QQKSong, 是 "全民 k 歌" 的 BoudleID. 至于目标 App 的 BoundleID 是多少, 有很多办法可以查到, 比如找到它的 plist 文件来查看, 比如 ps 看进程信息, 比如动态调试等等.
小白: 如果不指定这个 Filter 呢, 是不是所有的 App 启动时都加载?
小程: iOS8 之前的版本是这样的, 但之后的版本都需要指定 App. 所以, 不管 3724, 加上这个 Filter 总是不会错的.
然后, 拷贝 libReveal.plist 到手机:
scp libReveal.plist root@192.168.2.57:/Library/MobileSubstrate/DynamicLibraries
之后, 在电脑上再次启动 Reveal 就可以连接目标 App, 来分析界面类了.
比如, 在手机上重启全民 k 歌, 在电脑上重启 Reveal 并选择菜单项, 连接全民 k 歌. 可以看到, 全民 k 歌的一个页面是这样(右下角的类名是重点):
(二)让全民 k 歌自动切换至歌手页面
为了 "感性" 一点, 小程做一个演示, 通过 Reveal 定位到全民 k 歌的目标类, 并让全民 k 歌启动后自动切换至歌手页面.
首先通过 Reveal, 定位到底部导航条的所在的 viewcontroller 类是 KSRootTabBarController.
然后, 通过查看 classdump 翻译到的类结构中, 找到这个类. 可以看到, 点击 "我要唱" 按钮, 实际就是触发 KSRootTabBarController::onClickTabBarItem 函数.
接着, 就可以 hook 这个类了, 让目标 App 自动跳转. 这一步的具体操作, 小程会在后续详细介绍, 读者只需要 "感性" 地知道这回事就好.
最终, 自动跳转的效果是这样的:
以上讲解了 Reveal 的使用. Reveal 是定位目标类与函数的有效的办法, 除了这个办法, 还有一个办法就是, 观察所有类的类名, 猜测可能有关系的类(比如应该具备某个关键字), 再注入这些类的函数并用 NSLog 输出信息, 或者动态调试观察执行的流程, 最终确定目标类与函数.
小程在这里介绍动态调试目标 App 的办法, 这个办法在 "漫天定位" 的时候可能有用, 实际大多数情况下可能不必用到. 读者可以在需要时再阅读这部分内容.
目标: 在电脑上远程调试手机上的进程.
(一)问题
(1)为什么不用本地调试的方式?
可以在手机上安装 gdb, 再使用 gdb 调试目标进程. 但因为本地用 gdb 来调试可能会遇到很多 gdb 本身的问题(有一些可以解决, 而有一些并不好解决), 而 lldb 被苹果支持, 并用于替代 gdb. 所以, 可考虑使用更好的工具, 即 lldb+debugserver 来远程调试目标进程.
(2)需要什么工具?
电脑上需要 lldb, 如果 Mac 电脑上安装了 xcode 那就会有 lldb.
手机上需要安装 debugserver.
(3)手机如何安装 debugserver?
debugserver 在哪里?
/Applications/Xcode.App/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/xx.xx(目标手机是什么系统就选择什么版本的目录), 双击下面的文件: DeveloperDiskImage.dmg, 在打开的 finder 中, usr/bin 目录中存放了 debugserver.
怎么定制手机上相应的 debugserver?
(aa)
双击打开 DeveloperDiskImage.dmg 后,
- cd /Volumes/DeveloperDiskImage/Library/PrivateFrameworks,
- scp -r ARMDisassembler.framework root@192.168.2.22:/System/Library/PrivateFrameworks/,
即把 ARMDisassembler.framework 拷贝到手机, 之所以这样做, 是据别人的经验, 可以让 lldb 看汇编代码的效果更好, 不妨照做.
(bb)
把 usr/bin 中的 debugserver 拷贝到另一个目录, 为后续的签名作准备.
(cc)
创建一个 entitlement.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>
- (dd)
使用 ldid 来签名:
ldid -Sentitlement.xml debugserver
拷贝到手机:
scp debugserver root@192.168.2.22:/usr/bin/
在手机上测试是否可用(比如 SSH 到手机后输入命令):
debugserver
(4)重签名工具 ldid 怎么安装?
如果有安装 iOSOpenDev, 那在目录 / opt/iOSOpenDev/bin / 下面就有 ldid.
如果没有, 那可以拉代码下来安装:
- Git clone Git://Git.saurik.com/ldid.Git
- cd ldid
- Git submodule update --init
- ./make.sh
- which ldid #查看是否安装成功
(5)怎么使用 debugserver 与 lldb?
在手机上启动服务器 debugserver:
debugserver *:54321 -a "Spotify"
* 表示侦听任意 ip 的连接, 54321 为端口,-a 连接目标进程的名称(可用 ps 取得).
然后, 在电脑上启动 lldb 并连接服务器:
process connect connect://192.168.2.22:54321
如果手机端的 debugserver 提示: Waiting for debugger instructions for process 0, 那说明已经连接上.
之后可以在 lldb 端发送命令, 比如查看当前调试进程的模块信息:
image list -o -f
注意, 第一次 image list -o -f, 可能要等上几分钟. 当有返回后, 再输入 c, 就可以让程序继续运行.
(6)如果觉得 debugserver 运行很慢, 怎么解决?
使用 usbmuxd 来启用数据线来调试.
* 如何安装与使用 usbmuxd?
* 下载: http://7xibfi.com1.z0.glb.clouddn.com/uploads/default/original/2X/a/aa9cecf05b47d08a59324edeaaeea3f17e0608ee.zip
* 具体使用可以参考: http://www.jianshu.com/p/9333a706641a.
小程直接用 lldb 跟 debugserver, 第一次 image list 等了几分钟, 整体还可以接受.
(7)怎么调试?
* debugserver *:54321 -a "Spotify" -- 手机上启动 debugserver 并附加到目标进程
* lldb -- 电脑上启动 lldb
* process connect connect://192.168.2.22:54321 --lldb 连接 debugserver
* image list -o -f -- 查看目标进程的所有模块的信息, 比如输出:
[ 0] 0x000d8000 /var/containers/Bundle/Application/A80AF35B-DAD3-4D7E-B467-6C4230E32556/Spotify.App/Spotify(0x00000000000dc000)
第二个值为程序在内存中的基地址, 记录这个值.
* br s -a (0x000d8000 + 目标代码的偏移地址)
* c -- 让程序继续执行
* (操作程序)让程序触发断点.
* 使用 lldb 命令进行调试.
(二)示例
这里简单演示下怎么调试 Spotify 这个 App.
从 class-dump 得到的信息, 尝试调试类 SPTNowPlayingBarModel 的 pause 函数, 看看在播放 bar 条上暂停播放时会不会断在这个函数上.
class-dump 得到的 pause 的地址是 0x00477ff1, 在 hopper 上跳转到这个地址 (G), 也可以看到 -[SPTNowPlayingBarModel pause] 的信息.
* 运行目标应用
* debugserver *:54321 -a "Spotify" 手机端启动服务器, 之后都在客户端 lldb 中操作.
- * lldb
- * process connect connect://192.168.2.22:54321
* image list -o -f 或 image list -o -f | grep Spotify
这一步可能要等一会, 在 ipod5 的 6.0 系统会很快, 而在 iphone5s 的 10.1.1 系统则很慢.
* 从上面的命令找到基地址为: 0x000e6000 (每次运行不一样)
* 查看一下即将下断点的代码是怎么样的: dis -a '0x000e6000+0x00477ff1' 结果跟用 hopper 看到的一样(0x0047ff1 处).
- * br s -a '0x000e6000+0x00477ff1'
- * c
* 让程序触发到断点, 使用 lldb 的命令查看数据或设置值, 或单步等.
(三)lldb 的常用命令
打印信息:
expr 或 e,po,p 或 print
display 表达式
undisplay 序号
断点:
br l 或 breakpoint list #所有断点
breakpoint set -a 函数地址, 可简写: br s -a xxx #下断点
br s -- 函数关键字(可模糊)
- breakpoint set --shlib foo.dylib --name foo
- br del 1 #删除断点
- br del 2 3 4 #删除几个断点
内存断点
watchpoint set expression 地址
watchpoint set variable 变量名称
条件断点
watchpoint modify -c 表达式
单步:
- s/si #单步进入函数
- n/ni #单步
- f #跳出函数
线程:
thread backtrace, 或 bt, 或 bt all #列出所有线程
模块信息:
- image list [-o -f]
- image lookup -a expression
- image lookup -a $pc
- image lookup -r -n xxx
- image lookup -r -n playPause #用来查看一个函数 (关键字) 的地址
- image lookup -r -s
显示地址处的代码:
disassemble/dis -a 地址
- dis -a $pc
- dis -s 0x0002c000 -c 9 #后面的参数 - c 用来限制显示的代码数.
内存操作:
memory read [起始地址 结束地址]/ 寄存器 -outfile 输出路径
寄存器操作:
register read / 格式
register write 寄存器名称 数值
总结一下, 本文重点在于使用 Reveal, 从界面入手, 定位到关键类与函数, 进而找到目标类与函数, 为分析借鉴或注入提供基础. 对于动态调试, 读者未必需要掌握, 如果它实际帮不到你的话.
posted on 2019-04-03 17:52 广州小程 阅读(...) 评论(...) 编辑 收藏
来源: https://www.cnblogs.com/freeself/p/10650512.html