本人逆向学习之路总结,涉及到iOS和macOS平台。
工具
- insert_dylib(动态库注入)
- restore-symbol(符号表恢复)
- class-dump(Dump头文件)
- fishhook(静态函数交换)
- AntiAntiDebug(反反调试)
- MonkeyDev(AloneMonkey提供的一条龙重签名服务)
- substrate 或者 substrate (AloneMonkey 提供)(静态函数、方法hook)
- chisel(LLDB快捷调试脚本)
- MachOView(macho文件查看)
- codesige(/usr/bin/codesign)(签名)
- Hopper Disassembler(功能强大的逆向分析工具)
逆向步骤
iOS
- 安装MonkeyDev
- 脱壳(AppStore下载的ipa文件是被加密过的):PP助手下载越狱版本的ipa文件或者IPADownload(最简便的方式)
- 瘦身、符号表恢复:我们下载的包可以用于ARMv7、ARM64平台,可以使用
lipo macho -thin arm64 -output macho_arm64_thin
进行瘦身,再使用./restore-symbol macho_arm64_thin -o macho_arm64_thin_restore
恢复符号表 - 运行:MonkeyDev
macOS
- 可以直接使用MonkeyDev进行调试
- 或者我写的Reverse_xctemplatesMac调试模板
知识必备
ASLR(Address space layout randomization)(随机地址偏移)
我们在Hopper中看到的地址为模块偏移前基地址,需要在加上随机地址偏移(在macOS调试的时候发现,在Debug模式下,始终为0),才是我们得到当前运行程序在内存中实际的地址。获取地址方式有两种,但是有一个细微的差别(自己也曾在这里停留了半天,就是一个细微的差别导致的):
获取的地址可以直接在Hopper中修改基地址,这样在查找跳转中直接使用运行过程中内存地址就可以找到了。
这里获取的地址需要自己手动加,然后在从Hopper进行查找。
寄存器
iOS
macOS
OC下的寄存器:
rax:返回值
rdi:self
rsi:_cmd
rdx、rcx、r8~r9:参数
rbp:基址指针
rsp:栈指针
工具详解
insert_dylib
原理就是在load command中添加要加载的库,这样我们程序在启动的时候就可以加载我们编写的动态库实现方法交换。
在hook过程中,使用了libsubstitute.dylib
,由于编写的动态库需要引用到,这就涉及到该库应该放到什么地方的问题。
先看看生成的动态库是怎么引用这个库的
可执行文件下面?这里指的可执行的文件并不是动态库的mach-o文件,而是要hook的app的mach-o文件下面。这个问题困扰了好久才发现。(猜测项目中引用的话会修改这个加载库为@rpath/libsubstitute.dylib
并将其放入Frameworks中)
class-dump
dump出头文件(但是nygard/class-dump的对swift支持并不是很好,很多时候还是会出问题,所以用了BlueCocoa/class-dump的版本,至少不崩溃了)
fishhook
我们可能不仅仅需要hook相关的OC消息发送,可能还会涉及到静态方法的hook。
动态修改 C 语言函数的实现仔细的分析了fishhook
比如我们在hopper中看到的sub_
开头的方法没有名字,lldb中显示不是通过___lldb_unnamed_symbol
都objc_msgSend
进行派发。
1 | /// Hook c/c++/swift等编译期方法 |
ptrace
程序使用ptrace来进行动态调试保护,使得执行lldb的时候出现Process xxxx exited with status = 45 (0x0000002d)
错误。
使用Hopper修改指令
- 使用Hopper直接修改ptrace,如Surge(2.4.6)
入口就是:EntryPoint
直接暴力修改成如下形式:
使用代码进行修改,使用fishhook替换ptrace函数。
1 |
|
Swift && Objective-C混编
如果改项目是混编项目,难度要高很多,比如Reveal,Swift是类似于C++,在很多东西在编译期就知道,而且Swift在生成的代码中还进行的“混淆”,这使得在hook方法的时候变得不那么容易。
1 | @interface _TtC6Reveal20LicenseBindingsModel : NSObject |
如果直接hook _TtC6Reveal20LicenseBindingsModel
类,是找不到这个类对应的方法的,我们需要找到这类正确的名字。
我的做法是先下断点b 0x00000001001ff200
停止后打印值,看到真正的类名其实是Reveal.LicenseBindingsModel
,之后我们就可以正常的hook了。
1 | @implementation NSObject (XWJACK_LicenseBindingsModel) |
该类由于需要被OC使用,所以继承为NSObject,如果是纯Swift写hook可以参考Refrences的内容。
macOS调试配置
这里指定了当前生成的库会Attach到哪个程序。
实例
macOS
Surge
+[NSA load]
1 | void +[NSA load](void * self, void * _cmd) { |
1 | void sub_1000a860f() { |
1 | - (id)hook_KD_JSONObject { |
policy:对deviceID、expiresOnDate等数据base64得到
sign:对policy的签名再base64
- deviceID:设备ID
- expiresOnDate:过期时间
- issueDate:好像是用来判断下次请求的时间
- type:类型(trial)没有trial意味着是已经激活的
- enterprise(sub_1000a6e27)
….
1 | int sub_1000a6e27() { |
那怎么得到这个东西呢?
sub_1000a6e7a
发送网络请求https://www.surge-activation.com/mac/v3/init/
得到- 本地获取
真正核心部分是sub_1000f5bcc
1 | int sub_1000f5bcc(int arg0, int arg1) { |
由于里面签名过于复杂,就不一一hook了,直接hook这个方法进行返回。
1 | int sub_1000f5f84(int arg0, int arg1) { |
1 |
|
其中无意间发现好像有一个公钥和私钥都在本地写死的。
Reveal
This copy of Reveal is damaged
Reveal会校验签名,如果签名不对,会弹出提示。
- 从资源文件
Localizable.strings
中我们找到:
“IBAApplicationIsDamagedAlert.messageText” = “This copy of Reveal is damaged”;
sub_1001ff4a0()
sub_1001ff7f0()1
2
3
4
5
6void sub_1001ff7f0() {
r15 = sub_1001ff4a0();
/// 弹出模态框
rax = [r15 runModal];
*0x10051b730 = *0x10051b730 + 0x2;
if (rax != 0x3e8) goto loc_1001ff936;
sub_1001ff950()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31int sub_1001ff950() {
rbx = r12;
*0x100522518 = *0x100522518 + 0x1;
rax = sub_1003a4c76();
rax = $S10ObjectiveC22_convertObjCBoolToBoolySbAA0cD0VF(LOBYTE(rax) & 0xff);
/// 从这里我们确定rax的值需要为0x1才不会走else方法。
if ((LOBYTE(rax) & 0x1) != 0x0) {
r12 = rbx;
}
else {
*0x100522520 = *0x100522520 + 0x1;
r14 = [[swift_getInitializedObjCClass(@class(IBAAnalytics)) sharedInstance] retain];
rbx = $SSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF(0x8000000000000000 | "Code Signing Verification Failed", 0x20);
rax = [r14 recordEvent:rbx count:0x1];
rax = [rbx release];
rax = [r14 release];
rax = sub_1001ff7f0();
if (*0x100513078 == 0x0) {
*0x100513078 = sub_10003d530();
}
LODWORD(rdx) = 0x0;
LODWORD(rcx) = 0x0;
rax = swift_allocError(0x1004779f0);
r12 = rax;
}
rbx = stack[2045];
r14 = stack[2046];
rsp = rsp + 0x18;
rbp = stack[2047];
return rax;
}
hopper修改
sub_1003a4c76()mov rax, 0x1
其它全部nop掉
代码
1 | static int (*ori_sub_1003a4c76)(void); |
Reveal.ActivationWindowController: DMActivationController
激活控制器(_stepMap中包含了所有的弹窗)
1 | Printing description of self->_stepsMap: |
先打印一下DMActivationController方法(Hopper中无法看到)
1 | (lldb) pmethods -n DMActivationController |
showActivationWindowIfNeeds
1 | /// 直接hook掉 |
theos基本语法
1 | %hook ClassName |
xctemplate
为了简化步骤,所以写了一个Reverse_xctemplates