教你如何动态调试 iOS App(反编译App)

开篇


通过本文你能了解 iOS 逆向的基本知识,对 iOS App 的安全有一定了解。然后能举一反三,在自家 App 找到危险漏洞加以预防,保证用户数据安全。

在安全领域,攻与防永远存在。哪怕是 iPhone 有着强大的安全防护机制,也挡不住那些极客们一次又一次的好奇,开发了很多强大且便利的工具。本文就是在这些极客们提供的工具的基础上完成的!

准备工具


  • Mac 电脑和越狱 iPhone 手机
  • 查看手机系统目录工具 iFunbox 或 iTools
  • 网络分析工具 Charles
  • 反编译工具 Hopper, IDA Pro
  • 查看头文件工具 class-dump
  • 砸壳工具 dumpdecrypted, Clutch
  • 调试器 lldb 或 gdb
  • 调试工具:Cycript

HTTP(S) 抓包


HTTP 抓包

第一步:获取 MAC IP

按下Option键,同时点击 Mac 菜单栏上的无线网 Icon,能看到当前电脑的 IP 地址。
或在终端输入 ifconfig en0 也可查看。

第二步:设置代理

保证手机和电脑在同一 WIFI 下,在手机上,点击“设置->无线局域网->连接的WiFi”,设置HTTP代理:

服务器:为 Mac 电脑 IP 地址(如192.168.1.122)

端口:8888

第三步:抓包

在电脑端,打开 Charles。使手机发生网络请求,Charles 会弹出一个询问的对话框

点击“Allow”允许,Charles 会出现手机的 HTTP 请求记录列表。

HTTPS 抓包

第一步: 获取证书安装地址

安装 SSL 证书到手机设备。点击 Help -> SSL Proxying -> Install Charles Root Certificate on a Mobile Device

出现弹窗得到地址 chls.pro/ssl

第二步:iPhone 安装证书

在手机 Safari 浏览器输入地址 chls.pro/ssl,出现证书安装页面,点击安装,手机设置有密码的输入密码进行安装

第三步:配置代理 host

Charles 设置 Proxy。选择 Proxy -> SSL Proxying Settings...

勾选 Enable SSL Proxying,点击 Add

Host 设置要抓取的 HTTPS 接口,Port 填写 443。

让手机重新发送 HTTPS 请求,可看到抓包。

注意:不抓包请关闭手机 HTTP 代理,否则断开与电脑连接后会连不上网!

拿到 .h 头文件

从 AppStore 直接下载的 ipa, 苹果公司对其做了 FairPlay DRM 技术进行加密保护,无法直接使用 class-dump 工具获取头文件。但是如果是通过 development 打包出来的话的 App 的话,是可以直接使用 class-dump 查看所有头文件的,此部分介绍就是通过此情况来说明如何获取 .h 文件的。

此处不再介绍 class-dump 工具的安装过程,具体步骤请直接百度。

进入到 appName.ipa 所在目录,修改扩展名为 .zip,然后解压文件,得到 appName.app。

然后执行:

class-dump -H appName.app -o ./headers/

命令执行完成后,会在当前目录下的 headers 目录里看到 app 所有头文件。

如果添加参数 -A -S 会在头文件里标记处类方法和属性的 IMP 地址(模块偏移前基地址)。

class-dump -H -A -S appName.app -o ./headers/

SSH 访问手机文件目录


在你的越狱手机上使用 Cydia 应用市场安装 OpenSSH,并保证 Mac 和 iPhone 处于同一个WIFI下,在 MAC 终端输入:

ssh root@IP ,IP 替换为 iPhone 的 IP 地址

输入默认密码:alpine

即可进入 iPhone 终端。

使用 Clutch 反编译 App


第一步:重新签名 debugserver

取得 debugserver 有两种方式。

第一种是在 Mac 电脑中拿到

进入路径 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/8.3/DeveloperDiskImage.dmg(其中路径里 8.3,代表 iOS 系统版本,需与准备的越狱手机系统版本保持一致)。双击 DeveloperDiskImage.dmg,将目录里的 usr/bin/debugserver 复制到指定文件夹中。

第二种是在越狱手机里拿到

如果手机连接过手机并通过 XCode 调试过 app,会在手机里的 /Developer/usr/bin/ 目录下生成一个 debugserver 文件。通过 iFunbox 导出至 Mac 桌面。或使用 scp 命令 cpoy 出来。

重签名 debugserver

即给 debugserver 添加 task_for_pid 权限

创建 entitlements.plist,添加如下四个 key:

com.apple.springboard.debugapplications

get-task-allow

task_for_pid-allow

run-unsigned-code

key 对应的 value 都设为设为 ture

将 entitlements.plist 和 debugserver 放在同一个目录下,执行以下命令:

codesign -s - --entitlements entitlements.plist -f debugserver

此命令会重新签名 debugserver,将签名后的 debugserver 拷贝至手机系统的 /usr/bin/ 目录下。

注意:不要将 debugserver 拷贝至 /Developer/usr/bin/ 路径下

第二步: 通过 Clutch 拿到反编译后的 App 可执行文件

将下载好的 Clutch 放入手机的 /usr/bin/ 路径下。然后,给 Clutch 赋予权限,通过 SSH 登录到手机,进入 /usr/bin/ 执行 chmod a+x ./Clutch

通过命令 Clutch -i,列出所有的可被 Clutch 的应用。

对指定序号的应用进行脱壳,如企业微信,序号是1,命令是 Clutch -d 1。执行完成后,会得到脱壳后的 ipa。

第三步:使用 class-dump 拿到 .h 头文件

使用上文 【拿到.h头文件】 介绍的方法拿到脱壳后的 App 头文件和并记下要打断点的方法的 IMP 地址。

动态调试 App

本文动态调试用到的调试器是 lldb。

第一步:使 iPhone 进入等待挂载状态

SSH 登录到手机,执行 ps -e 命令得到 App PID 或项目名称。

进入 /usr/bin/ 执行 ./debugserver IP:port -a PID|appProjectName。 其中第一个参数 IP 可以替换为 Mac 电脑 IP地址,或者使用 * 通配符,允许所有 IP 调试;第二个参数 port 随便写一个就行。第四个参数 可以指定要调试 App 的 PID 或项目名称。比如要调试的 PID 为 6019 的搜狗输入法项目名称为SogouInput,则命令即为:

./debugserver *:1234 -a 6019./debugserver *:1234 -a ‘SogouInput’

此命令执行完成后,app会进入等到挂载状态,app会被卡住点击无反应。正常现象!

如果此命令报错,如出现 Segmentation fault: 11 等情况,说明 App 做了反动态调试保护。遇到此种情况,需先确定 App 采用了哪种保护方案,然后进一步找到对应措施,干掉它的反动态调试保护。

第二步:监听进程,进入挂载状态

重新打开一个 Mac 终端执行 lldb 进入 lldb 调试状态。然后输入

process connect connect://iPhoneIP:port

iPhoneIP 替换为 iPhone 的 IP 地址;port 改为刚才指定的端口,即 1234。

待命令执行完成后,App 即进入挂载状态。

第三步:获取 App 的 ASLR 偏移量

ASLR偏移量其实就是虚拟内存的地址相对于模块基地址的偏移量。有两个概念需要熟悉一下:

  • 模块在内存中的起始地址 ---- 模块基地址
  • ASLR偏移 ---- 虚拟内存起始地址与模块基地址的偏移量

在 lldb 调试器模式下,执行 imge list -o -f

模块偏移后的基地址 = ASLR 偏移量 + 模块偏移前基地址(方法的 IMP 地址)

上面这个公式是尤为重要的,因为 Class-dump 中显示的都是“模块偏移前基地址”,而 lldb 要操作的都是“模块偏移后的基地址”。所以从 Class-dump 到 lldb 要做一个地址偏移量的转换。

至此,已得到了 App 的 ASLR 偏移量和方法的 IMP 地址。

第四步:打断点,调试

在 lldb 模式下执行,br s -a 'ASLR 偏移量+ IMP',然后执行 c,使 App 跑起来,触发一个方法调用,就会进入断点模式。输入 po $arg1 打印第一个参数。

然后,配合着抓包工具 Charles(比如分析网络请求加密逻辑) 和 Class-dump(比如修改某个类的方法返回值)等工具,你就可以随意动态调试 App 了,就像在 XCode 里调试一样!

br 命令说明

br dis 1 -- 禁用(disable)编号为1的断点

br en 1 -- 启用(enable)编号为1的断点

br dis -- 禁用所有断点

br en -- 启用所有断点

br del 1 -- 删除(delete)编号为1的断点

br del -- 删除所有断点

br list -- 列出所有断点

使用 dumpdecrypted 破壳 App


dumpdecrypted 脱壳工具的原理是:将应用程序运行起来(iOS 系统会先解密程序再启动),然后将内存中的解密结果 dump 写入文件中,得到一个新的可执行程序。

第一步:生成 .dylib 文件

在终端进入到下载后的目录中,cd dumpdecrypted-master,然后执行 make,即可生成 dumpdecrypted.dylib

第二步:找到 App 的 Documents 文件夹路径

通过 SSH 登录到 iPhone,然后执行 ps -e 查看进程,获取要破壳的进程 PID。然后执行 cycript -p PID 附加到 PID 进程上。最后执行 [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]得到 Documents 文件夹路径。

第三步:开始破壳

将第一步生成的 dumpdecrypted.dylib 拷贝到第二步得到的 .../Documents/ 路径下,命令如下:
scp ~/dumpdecrypted.dylib root@IP:/var/mobile/Containers/Data/Application/2B4C6281-C015-4FF3-A8EC-5E5C7554D447/Documents(将路径里的 UDID 替换为你的要破壳的 App 的 UDID)

进入 Documents 目录下,执行DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/
Application/BFED82A3-3238-4F41-B797-C1CB584CBE05/appProjectName.app/appProjectName
(将路径里的 UDID 替换为你的要破壳的 App 的 UDID;将 appProjectName 替换为要破壳 App 的项目名称)

待命令执行完,会在当前目录生成一个名为 appProject.decrypted 的文件,这个就是破壳后的 App 可执行文件,要的就是它!使用 Class-dump 即可得到头文件。或使用 Hopper 或 IDA Pro 进行反编译。

给你的 App 添加反动态调试机制


ptrace

为了方便应用软件的开发和调试,从Unix的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用 ptrace()。
通过 ptrace 可以对另一个进程实现调试跟踪,同时 ptrace 还提供了一个非常有用的参数那就是 PT_DENY_ATTACH,这个参数用来告诉系统,阻止调试器依附。

所以最常用的反调试方案就是通过调用ptrace来实现反调试。

sysctl

当一个进程被调试的时候,该进程会有一个标记来标记自己正在被调试,所以可以通过 sysctl 去查看当前进程的信息,看有没有这个标记位即可检查当前调试状态。

检测到调试器就退出,或者制造崩溃,或者隐藏工程,当然也可以定时去查看有没有这个标记。

syscall

为从实现从用户态切换到内核态,系统提供了一个系统调用函数 syscall,上面讲到的 ptrace 也是通过系统调用去实现的。

在Kernel Syscalls27这里可以找到 ptrace 对应的编号。

26. ptrace 801e812c T

所以如下的调用等同于调用 ptrace:

syscall(26,31,0,0,0);

arm

syscall 是通过软中断来实现从用户态到内核态,也可以通过汇编 svc 调用来实现。

觉得不错的话,欢迎关注我的公众号哦!