背景
逆向工程相对于正向的开发,可能关注的没有那么高,尤其是相比于安卓或者其他平台,苹果的安全机制更严格,逆向的流程也会更繁琐,除了有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的一些软件
使用如上的三方软件可以很快的下载对应的ipa包,但是由于上述市场都是镜像自AppStore的内容,并且自己重签名,所以更新的及时性可能没有那么快,也没有那么全,而且因为是被第三方进行了修改重签,所以内容也不一定保证和官方的一致。如果不在乎这些的话还是可以采取这类的方式下载。
方式2:Apple Configuration
可以直接从Mac 上的Apple Store上下载,官方出品,原本是给手机上安装app的。用此方式其实是利用了该App的App下载机制来进行ipa导出的
选择添加App,然后在弹出的弹窗中选择App并且下载
这个时候如果你手机上没有安装该App,则直接会安装成功,此时我们再点击安装下载,然后就会收到设备上已经存在相同的App,是否覆盖安装的提示
的弹窗,此时我们 不要理会 这个弹窗。
然后到如下路径就可以取到对应的ipa
~/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更换加密方式,也不影响动态砸壳的过程。
动态砸壳的方式和工具有很多,现在基本已经流水线化了,可以使用以下方式和工具来进行处理,前提是要有一个越狱的手机。
成果检验
砸壳后需要检查是否砸壳成功,找到对应砸壳后的的ipa,点进去找到mach-o文件,执行如下命令,然后在输出查看cryptid
字段如果为0
就说明砸壳成功。XXX = mach-o名字
otool -l XXXXX |grep cry
复制代码
头文件导出
砸壳后的的第一步就是将ipa文件的头.h文件导出,然后根据 头文件的方法和属性进行逆向分析,在找到对应的hook点。通常我们使用class-dump,可以去他的官网下载对应的文件,然后将文件拷贝到对应的目录下。
sudo cp class-dump /usr/local/bin
复制代码
这一步没什么问题,拷贝完成重启终端就可以调用class-dump的方法了.
导出
执行下面的命令,导出头文件,需要注意的是:导出后会有上万个个文件,所以目标目录最好不要选Desktop或者其他的根目录
class-dump -S -s -H XXXXX -o /path/to/headers/
复制代码
有的时候会收到这样的错误
Error:Cannot find offset for address 0xd80000000101534a in stringAtAddress:
复制代码
这是因为项目使用了Oc和Swift的混编,需要赋予class-dump文件权限即可
sudo chmod 777 /usr/local/bin/class-dump
复制代码
之后就可以导出成功了。
MonkeyDev
这是一个为越狱和非越狱开发人员准备的工具,主要包括四个模块:
-
Logos Tweak
-
使用theos提供的
logify.pl
工具将*.xm
文件转成*.mm
文件进行编译,集成了CydiaSubstrate
,可以使用MSHookMessageEx
和MSHookFunction
来Hook
OC函数和指定地址。
-
-
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
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos
复制代码
安装Monkeydev
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-install)"
复制代码
卸载Monkeydev
sudo /bin/sh -c "$(curl -fsSL https://raw.githubusercontent.com/AloneMonkey/MonkeyDev/master/bin/md-uninstall)"
复制代码
更新Monkeydev
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
文件
File /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Package Types.xcspec not found
复制代码
这个是因为最新的Xcode14中 这个路径已经改变,所以原路径无法找到,不过如果大家需要逆向的事iOS的App到这一步可以不用关心,这个是MacOS相关的模板文件。此时打开Xcode如果有以下模版文件,并能成功创建工程即可。
编译报错
通过上一步的模板文件创建好工程后,直接真机编译运行,这个时候会提示编译错误
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后动态库注入成功,控制台会有如下输出
🎉!!!congratulations!!!🎉\n👍----------------insert dylib success----------------👍
复制代码
但是如果是和我一样的运行环境,你是大概率看不到的,因为会注入失败。这里尝试了两种方式
- insert_dylib 同样注入失败,
- optool 注入成功
下面说下optool使用
-
下载编译optool
git clone https://github.com/alexzielenski/optool.git cd optool git submodule update --init --recursive 复制代码
-
找到编译产物
-
把编译产物拷贝到
/opt/MonkeyDev/bin
下 -
修改
/opt/MonkeyDev/Tools/pack.sh
顶部插入 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,但是要注意是在 DemoLib 的trarget中添加,因为我们的pod是打入动态库的,然后由动态库带入App
# 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的函数所在的类,比如
CHDeclareClass(MYViewController) 复制代码
-
CHOptimizedMethod1
hook实例方法,你会发现后面跟了数字1~10,代表被hook 的函数的参数的个数,比如我将要hook的函数只有一个参数 那么就使用CHOptimizedMethod1参数含义为
-
第一个参数,一般传self
-
第二个参数,传返回值类型,没有返回值就是void
-
第三个参数,函数所在的类名
-
第四个参数,方法名
-
第五个参数,函数参数的类型
-
第六个参数,函数参数的变量
其中第五第六个参数在CHOptimizedMethod1
10 中会重复110次CHOptimizedMethod1(self, void, MYViewController, appMethod, id, para) { NSLog(@"appMethod被Hook = %@", para); } 复制代码
-
-
CHOptimizedClassMethod3
hook类方法,所有函数定义同上
-
CHConstructor结构
用来注册刚才的hook操作
CHConstructor{ // 注册将要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出的头文件中的一个函数名试下。
[XXXXXXXX listenerDownloadLyricWithSongId:resultBlock:]
复制代码
可以看到中间的部分显示出来函数所对应的汇编代码
然后按快捷键Option+enter
即可转为伪OC代码,虽然包含一些的寄存器信息,但是也足以分析了。同时双击可以跳转到对应的函数内部。
最后
以上就是目前的逆向调研过程,这里先记录下,后面还会深入研究,有新的发现会同步更新此文章。