# 背景
逆向工程相对于正向的开发,可能关注的没有那么高,尤其是相比于安卓或者其他平台,苹果的安全机制更严格,逆向的流程也会更繁琐,除了有 ASLR(地址空间布局随机化),还有 FairPlay DRM 的 iPA 加密方式,也就是我们俗称的壳。这个给逆向工作带来了更多的挑战。但是 更好更安全的加密方式也只是增加破解的成本,并不是绝对的安全
,这也是逆向的前提。
最近也正在做一些调研的工作,需要从技术层面去分析其他 App 的一些底层逻辑,要用到 iOS 的逆向相关的技术,但是由于笔者做这些工作的时候正处于 MacOS、iOS、Xcode 三个系统的大版本更新期间,一些系统的运行方式和逻辑发生变化,所以导致网上能找到的资料基本都失效了,所以写文档记录下。
# 前置工作
# 环境
- Mac 架构: Intel 架构
- MacOS: 13.0.1 (22A400)
- Xcode 版本:Version 14.1 (14B47b)
- iOS 系统版本:iOS 16.0
# 相关工具
- MachOView
- 用来查看 Mach-o 的文件结构,以及各个部分的信息
- class-dump
- class-dump,顾名思义,就是用来 dump 目标对象 的 class 信息的工具。它利用 Objective-C 语言的 runtime 特性,将存储在 Mach-O 文件中的头文件信息提取出 来,并生成对应的.h 文件。
- MonkeyDev
- 非越狱开发插件,可以进行动态库注入,hook 相关操作
- Hopper Disassembler
- Hopper Disassembler 是 Mac 上的一款二进制反汇编器,基本上满足了工作上的反汇编的需要,包括伪代码以及控制流图 (Control Flow Graph),支持 ARM 指令集并针对 Objective-C 的做了优化。
# iPa 下载
iOS App 的逆向的所有操作都是基于 iPa 的操作,所以大前提是要有目标 iPa,这里提供三种方式来进行 iPa 下载,大家可以选择适合自己的方式下载。
# 方式 1:三方应用市场
现在这样的应用市场比较多,多是平替 iTunes 的一些软件
- 爱思助手
- iTools
使用如上的三方软件可以很快的下载对应的 ipa 包,但是由于上述市场都是镜像自 AppStore 的内容,并且自己重签名,所以更新的及时性可能没有那么快,也没有那么全,而且因为是被第三方进行了修改重签,所以内容也不一定保证和官方的一致。如果不在乎这些的话还是可以采取这类的方式下载。
# 方式 2:Apple Configuration
可以直接从 Mac 上的 Apple Store 上下载,官方出品,原本是给手机上安装 app 的。用此方式其实是利用了该 App 的 App 下载机制来进行 ipa 导出的
选择添加 App,然后在弹出的弹窗中选择 App 并且下载
这个时候如果你手机上没有安装该 App,则直接会安装成功,此时我们再点击安装下载,然后就会收到 设备上已经存在相同的App,是否覆盖安装的提示
的弹窗,此时我们 <font color='red'> 不要理会 </font > 这个弹窗。
然后到如下路径就可以取到对应的 ipa
1 | ~/Library/Group Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps/ |
# 方式 3 DumpApp
是一个第三的网站,同在线砸壳 + ipa 下载的服务,因为我们最终想要的就是一个砸壳之后的 ipa,所以这个网站直接帮我们做好了,只不过是收费的,每个 app 是 9 元,但是有多个境外的 App 市场,比较全面。
# iPa 砸壳
如果 iPa 的获取方式选择方式 3,则可以略过砸壳步骤
app 上传到 AppStore 后 苹果使用 fairplay DRM 来加密,就是我们所说的壳 DRM 全称 Digital Rights Management,即数字版权保护。苹果为了保护 App Store 分发的音乐 / 视频 / 书籍 / App 免于盗版,开发了 Fairplay DRM 技术。
所有逆向都是建立在砸壳的前提下,砸壳的方式有两种:
# 静态砸壳
就是不依赖程序运行,直接用 ipa 包就可以进行砸壳解密,比如说我已经知道了他的加密算法,或者我通过暴力破解了他的加密算法,然后对 ipa 进行解密,但是这样的方法难度较大,而且如果人家一旦换了加密方式或者有其他的改动,那解密方式就不生效了,常见的静态砸壳工具有以下
- [fouldecrypt](NyaMisty/fouldecrypt: A lightweight and simpling iOS binary decryptor (github.com))
- Iridium
# 动态砸壳
与静态相反,动态砸壳就是依赖运行时的原理来进行解密,不过与其说是解密,倒不如说是内存提取,因为无论 ipa 包用什么加密方式,最终都是解密后运行到内存里面的,所以我们可以认为 一个ipa在内存上的数据是未加密的
,所以此时我们只要把内存上的数据提取出来即可,整个过程也不涉及到解密操作,及时后面 Apple 更换加密方式,也不影响动态砸壳的过程。
动态砸壳的方式和工具有很多,现在基本已经流水线化了,可以使用以下方式和工具来进行处理,前提是要有一个越狱的手机。
dumpdecrypted
Clutch
# 成果检验
砸壳后需要检查是否砸壳成功,找到对应砸壳后的的 ipa,点进去找到 mach-o 文件,执行如下命令,然后在输出查看 cryptid
字段如果为 0
就说明砸壳成功。XXX = mach-o 名字
1 | otool -l XXXXX |grep cry |
# 头文件导出
砸壳后的的第一步就是将 ipa 文件的头.h 文件导出,然后根据 头文件的方法和属性进行逆向分析,在找到对应的 hook 点。通常我们使用 class-dump,可以去他的官网下载对应的文件,然后将文件拷贝到对应的目录下。
1 | sudo cp class-dump /usr/local/bin |
这一步没什么问题,拷贝完成重启终端就可以调用 class-dump 的方法了.
# 导出
执行下面的命令,导出头文件,需要注意的是:导出后会有上万个个文件,所以目标目录最好不要选 Desktop 或者其他的根目录
1 | class-dump -S -s -H XXXXX -o /path/to/headers/ |
有的时候会收到这样的错误
1 | Error:Cannot find offset for address 0xd80000000101534a in stringAtAddress: |
这是因为项目使用了 Oc 和 Swift 的混编,需要赋予 class-dump 文件权限即可
1 | sudo chmod 777 /usr/local/bin/class-dump |
之后就可以导出成功了。
# MonkeyDev
这是一个为越狱和非越狱开发人员准备的工具,主要包括四个模块:
Logos Tweak
- 使用 theos 提供的
logify.pl
工具将*.xm
文件转成*.mm
文件进行编译,集成了CydiaSubstrate
,可以使用MSHookMessageEx
和MSHookFunction
来Hook
OC 函数和指定地址。
- 使用 theos 提供的
CaptainHook Tweak
- 使用 CaptainHook 提供的头文件进行 OC 函数的 Hook 以及属性的获取。
Command-line Tool
- 可以直接创建运行于越狱设备的命令行工具
MonkeyApp
- 这是自动给第三方应用集成 Reveal、Cycript 和注入 dylib 的模块,支持调试 dylib 和第三方应用,支持 Pod 给第三放应用集成 SDK,只需要准备一个砸壳后的 ipa 或者 app 文件即可。
# 安装
Monkeydev 依赖 Theos.Theos 是一个越狱开发工具包,由 iOS 越狱界知名人士 Dustin Howett 开发并分享到 GitHub 上。Theos 与其他越狱开发工具相比,最大特点就是简单:下载安装简单、Logos 语法简单、编译发布简单,可以让使用者将精力都放在开发工作上去。
# 安装 Thoes
1 | sudo git clone --recursive https://github.com/theos/theos.git /opt/theos |
# 安装 Monkeydev
1 | sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-install)" |
# 卸载 Monkeydev
1 | sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-uninstall)" |
# 更新 Monkeydev
1 | sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-update)" |
# 安装问题
在安装过程中,修改用户 profile
文件时,找不到 MacOSX Package Types.xcspec
和 MacOSX Product Types.xcspec
文件
1 | File /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Package Types.xcspec not found |
这个是因为最新的 Xcode14 中 这个路径已经改变,所以原路径无法找到,不过如果大家需要逆向的事 iOS 的 App 到这一步可以不用关心,这个是 MacOS 相关的模板文件。此时打开 Xcode 如果有以下模版文件,并能成功创建工程即可。
# 编译报错
通过上一步的模板文件创建好工程后,直接真机编译运行,这个时候会提示编译错误
1 | iOS file not found: /usr/lib/libstdc++.dylib |
这是因为 Xcode 10
之后删除的 libstdc++
库。可以参考此解决方案。之后就可以编译成功了,并且手机上可以跑起来。
第二个错误是 Fishhook 中的错误,这个是是由于 Fishhook 用的是比较老的版本,本身存在 bug,只要去 github 官网找到 fishhook 最新代码 copy 过来即可。
# 文件结构
文件结构如下如图
这是一个标准的 MonkeyDemo 的结构
TargetApp
:放目标ipa
的文件,将需要逆向的破壳ipa
放在此处Logos
:编写相关hook
的文件,所有hook
操作在此处,但是因为该文件下要用了 logos 语句,有一定的学习成本,所以后面的 hook 函数可以直接写在上面的 MonkeyDeomDyLib.m 中fishhook
:用来hook
系统函数的库
上方的 MonkeyDeomDyLib 就是我们即将注入进去的动态库。
# 动态库注入
运行 demo 后动态库注入成功,控制台会有如下输出
1 | 🎉!!!congratulations!!!🎉\n👍----------------insert dylib success----------------👍 |
但是如果是和我一样的运行环境,你是大概率看不到的,因为会注入失败。这里尝试了两种方式
- insert_dylib 同样注入失败,
- optool 注入成功
下面说下 optool 使用
下载编译 optool
1
2
3git clone https://github.com/alexzielenski/optool.git
cd optool
git submodule update --init --recursive找到编译产物
把编译产物拷贝到
/opt/MonkeyDev/bin
下修改
/opt/MonkeyDev/Tools/pack.sh
1
2
3
4
5
6顶部插入
OPTOOL="${MONKEYDEV_PATH}/bin/optool"
同上面一样
修改插入动态库工具代码
"$OPTOOL" install -c load -p "@executable_path/Frameworks/lib""${TARGET_NAME}""Dylib.dylib" -t "${BUILD_APP_PATH}/${APP_BINARY}"然后保存重新运行即可注入成功
# pod 使用
在调试 App 时候我们会用到类似 lookIn 或者 FLEX 的等工具来看 App 的层级结构和 沙盒文件,同样需要 pod 来接入。
像平时创建 podfile 文件一样 进入到工程目录
pod init
在生成的 podfile 中添加 pod,但是要注意是在 <font color='red'> DemoLib </font > 的 trarget 中添加,因为我们的 pod 是打入动态库的,然后由动态库带入 App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'Demo' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
end
target 'DemoDylib' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
pod 'FLEX'
pod 'LookinServer'
end然后
pod install
即可看到效果
# 代码 Hook
通过 Lookin 我们可以找到入手点和对应的类名,然后通过之前导出的头文件可以查看类名对应的函数,接下来就是要看下函数里面做了哪些事情,就要用到 Hook 手段,MonkeyDev 给我们封装好了 Hook 相关的方法,包括 OC 和 C 的 Hook 函数
CHDeclareClass
注册类名。也就是注册要被 hook 的函数所在的类,比如
1
CHDeclareClass(MYViewController)
CHOptimizedMethod1
hook 实例方法,你会发现后面跟了数字 1~10,代表被 hook 的函数的参数的个数,比如我将要 hook 的函数只有一个参数 那么就使用 CHOptimizedMethod1 参数含义为
第一个参数,一般传 self
第二个参数,传返回值类型,没有返回值就是 void
第三个参数,函数所在的类名
第四个参数,方法名
第五个参数,函数参数的类型
第六个参数,函数参数的变量
其中第五第六个参数在 CHOptimizedMethod1 ~10 中会重复 1~10 次
1
2
3CHOptimizedMethod1(self, void, MYViewController, appMethod, id, para) {
NSLog(@"appMethod被Hook = %@", para);
}
CHOptimizedClassMethod3
hook 类方法,所有函数定义同上
CHConstructor 结构
用来注册刚才的 hook 操作
1
2
3
4
5
6
7CHConstructor{
// 注册将要hook的类
CHLoadLateClass(MYViewController);
// 注册将要hook 的方法
CHHook1(MYViewController, appMethod);
}上面流程执行完成后就可以看到函数被 Hook 了
# Hopper Disassembler
上面的步骤讲了如何通过 lookin 或者 reveal 等工具来定位类名,然后通过类名在头文件中找到函数名,然后通过 hook 手段来改变函数的一些表现,但是在如何没有拿到.m 文件的前提下看到某个函数的实现呢?比如一个函数中都做了哪些操作,调用了哪些其他函数,以及调用链是怎样的?
这个时候就需要用到 Hopper Disassembler
或者 IDA Pro
这样的工具了,不过目前遇到的困难是在笔者的系统环境下,这两个软件的破解版无法安装,而且 IDA Pro
的官方试用版还不支持 Arm 的汇编,所以只能使用 Hopper Disassembler 来举例子。打开软件,将 对应App 的Mach-o
文件拖入 Hopper 中等待它分析完成
处理完后的界面左边会显示方法名,支持搜索查询,中间区域显示的是汇编代码,我们搜索一个在之前 dump 出的头文件中的一个函数名试下。
1 | [XXXXXXXX listenerDownloadLyricWithSongId:resultBlock:] |
可以看到中间的部分显示出来函数所对应的汇编代码
然后按快捷键 Option+enter
即可转为伪 OC 代码,虽然包含一些的寄存器信息,但是也足以分析了。同时双击可以跳转到对应的函数内部。
# 最后
以上就是目前的逆向调研过程,这里先记录下,后面还会深入研究,有新的发现会同步更新此文章。