一、去符号和恢复符号
1.1 Stip和 Strip Type解释
- strip在iOS中的作用是 剥掉目标文件中一些符号信息和调试信息,使文件变小。
- dead code strip : 死代码剥离、然后再去链接。
- 那么strip在哪些地方不能起作用呢?
-
- 动态库 不能strip全局符号、因为全局符号要作为导出符号。
- App中 间接符号表中的符号不能strip;那么App中 本地符号、全局符号都可以strip。
- 静态库 = .o文件合集,存在重定位符号表,这个表中的数据也是不能strip的。所以 .o文件中能strip的是调试符号。
- 因此我们在Build Settings中的Strip Style
-
- Debugging Symbols(.o静态库/可执行文件 /动态库)调试符号
- All Symbols 所有符号
- Non-Global Symbols 不是全局符号
- Strip Style:
.o/静态库 __DWARF(静态库没有签名)
-
-
- Mach-O ----> 解析成模型Object ---> 遍历 LoadCommands ----> 找到 ‘Segname == __DWARF’的 ‘Load Command’ --->
- 移除的‘Section’ ----> 从符号表中移除 ‘Symbol’ ---> 将修改后的模型Object重新写入 ----> Mach-O
-
-
- Debugging Symbols(动态库、可执行文件)
-
-
- 遍历符号表 ---> 删除调试符号的n_type包含 N_STAB(0xe0)
-
-
- All Symbols
-
-
- markSymbols ----> 除了间接符号表中引用的符号 ---> 都可以删除。
-
-
- Non-Global Symbols
-
-
- 遍历符号表 ---> 删除符号的n_type != N_EXT
-
- 那么就符号来说:我们的App在使用静态库体积会变小还是使用动态库体积会变小?
-
- 答案是静态库;
- 因为 App在链接静态库的时候,.o文件的合集会把.o中的所有符号、包括重定位的符号,都放到App的符号表中。因此可能变成了全局符号、本地符号、导出符号。那么根据strip的原理、在静态库中所有的符号都可以被剥离。
- 而动态库中所有的符号都被放到App的间接符号表中、那么再去strip的时候、动态库中的死代码将不能被剥离。
1.2 Strip注意事项
- strip脱符号,在Xcode中默认是在Archive的时候才会生效,移除对应符号.
- 查看当前archive后脱去符号的MachO的代码段符号信息
$ objdump --macho -t symbolDemo
symbolDemo:
SYMBOL TABLE:
0000000005614542 d *UND* radr://5614542
0000000100000000 g F __TEXT,__text __mh_execute_header
0000000000000000 *UND* _NSLog
0000000000000000 *UND* _NSStringFromClass
0000000000000000 *UND* _OBJC_CLASS_$_UIResponder
0000000000000000 w *UND* _OBJC_CLASS_$_UISceneConfiguration
0000000000000000 *UND* _OBJC_CLASS_$_UIViewController
0000000000000000 *UND* _OBJC_METACLASS_$_NSObject
0000000000000000 *UND* _OBJC_METACLASS_$_UIResponder
0000000000000000 *UND* _OBJC_METACLASS_$_UIViewController
...
0000000000000000 *UND* _objc_storeStrong
0000000000000000 *UND* dyld_stub_binder
- 而正常运行时,符号信息还包括一些本地符号和全局符号
objdump --macho -t symbolDemo
symbolDemo:
SYMBOL TABLE:
0000000100005d8c l F __TEXT,__text -[ViewController hanktest]
0000000100005da0 l F __TEXT,__text -[ViewController viewDidLoad]
0000000100005df4 l F __TEXT,__text -[ViewController test1]
0000000100005e20 l F __TEXT,__text -[ViewController test]
....
- 当符号被脱去后,想要还原原来的符号,可以通过restore-symbol 这个第三方库恢复
//restore-symbol可执行文件 + 要恢复的可执行文件路径 -0 输出文件名称
$ restore-symbol symbolDemo -o symbolRestores
=========== Start =============
Scan OC method in mach-o-file.
Scan OC method finish.
=========== Finish ============
// 查看代码段符号
$ objdump --macho -t symbolRestores
symbolRestores:
SYMBOL TABLE:
0000000005614542 d *UND* radr://5614542
0000000100000000 g F __TEXT,__text __mh_execute_header
0000000000000000 *UND* _NSLog
0000000000000000 *UND* _NSStringFromClass
0000000000000000 *UND* _OBJC_CLASS_$_UIResponder
0000000000000000 w *UND* _OBJC_CLASS_$_UISceneConfiguration
0000000000000000 *UND* _OBJC_CLASS_$_UIViewController
0000000000000000 *UND* _OBJC_METACLASS_$_NSObject
0000000000000000 *UND* _OBJC_METACLASS_$_UIResponder
....
0000000000000000 *UND* _objc_storeStrong
0000000000000000 *UND* dyld_stub_binder
0000000100006250 l F __TEXT,__text -[ViewController touchesBegan:withEvent:]
000000010000624c l F __TEXT,__text -[ViewController test]
0000000100006230 l F __TEXT,__text -[ViewController test1]
00000001000061f0 l F __TEXT,__text -[ViewController viewDidLoad]
...
000000010000636c l F __TEXT,__text -[SceneDelegate sceneDidEnterBackground:]
0000000100006368 l F __TEXT,__text -[SceneDelegate sceneWillEnterForeground:]
0000000100006364 l F __TEXT,__text -[SceneDelegate sceneWillResignActive:]
0000000100006360 l F __TEXT,__text -[SceneDelegate sceneDidBecomeActive:]
000000010000635c l F __TEXT,__text -[SceneDelegate sceneDidDisconnect:]
二、初探反HOOK防护
-
2.1 如果要防护其他人
- 需要保留自己的HOOK方法;如果要使用fishhook做防护,那么需要在framework里边去实现
- 因为framework里边的load加载会比主工程的代码更快
- 注入的HOOK代码在framework代码之后,主工程之前.所以要切入这个时机点去做防护
- 需要保留自己的HOOK方法;如果要使用fishhook做防护,那么需要在framework里边去实现
-
2.2 反HOOK实战、
- 1.假如我们防护 method_exchangeImplementations 方法、
- 首先创建一个AntiHOOK项目、然后主界面设置两个按钮、接着增加一个HOOKManager的Framework、让主工程依赖它,在Framework中,我们在load方法中实现我们的反Hook逻辑
#import "AntiHOOKCode.h"
#import "fishhook.h"
#import <objc/message.h>
@implementation AntiHOOKCode
+ (void)load {
//基本防护
struct rebinding exchange;
exchange.name = "method_exchangeImplementations";
exchange.replacement = my_exchange;
exchange.replaced = (void *)&exchangeP;
struct rebinding bds[] = {exchange};
rebind_symbols(bds, 1);
}
//指针,暴露给外界自己的工程使用
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
void my_exchange(Method _Nonnull m1, Method _Nonnull m2) {
NSLog(@"检测到了HOOK");
}
-
- 反HOOK部分已经完成、
-
- 接下来、我们模拟HOOK方,对防护的Demo进行HOOK、
- 新建一个HOOKDemo、根目录下创建一个App的文件夹、拿出史诗级脚本appSign.sh放入根目录下
- 创建Payload文件夹、放入AntiHOOK包、APP目录下执行
$zip -ry AntiHOOK.ipa Payload/
adding: Payload/ (stored 0%)
....
adding: Payload/AntiHook.app/embedded.mobileprovision (deflated 36%)
adding: Payload/AntiHook.app/Info.plist (deflated 35%)
adding: Payload/AntiHook.app/PkgInfo (stored 0%)
-
- 压缩完成后形成AntiHOOK.ipa.注释appSign脚本的yololib的执行方法, 运行HOOKDemo、将AntiHOOK.ipa跑进新的Demo中
- 最后,开始注入操作、新建HolothurianHook的Framework、开始使用方法交换,HOOK项目中的btnClick1方法,
- 此时打开yololib的动态注入方法.
#import "InjectHook.h"
#import <objc/message.h>
@implementation InjectHook
+ (void)load {
IMP btnIMP = class_getInstanceMethod(objc_getClass("ViewController"), @selector(btnClick1:));
IMP testIMP = class_getInstanceMethod(self, @selector(test));
method_exchangeImplementations(btnIMP, testIMP);
}
- (void)test {
NSLog(@"HOOK成功");
}
@end
-
- 当运行项目时、我们的反HOOK起了作用.点击按钮时,只输出按钮1被点击的log、
- 现在剩下的问题就是、当我们自己要使用exchange方法时,该怎么做?
- 回到AntiHOOK的工程,在AntiHOOKCode.h文件中,将exchangeP方法暴露出来
CF_EXPORT void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
-
- 并且在HOOKManager.h文件中暴露出头文件
#import <HOOKManager/AntiHookCode.h>
-
- 在ViewController中,实现我们自己的HOOK代码、结果如下
-
- 这样我们就实现了反HOOK,及本工程自身的HOOK.
- 此时我们将AntiHOOK的应用包,进行HOOKDemo的HOOK时,同理,按钮1可以响应,但是按钮2会依然执行本工程的HOOK成功log,这样更加验证了,反HOOK操作是成功的.
2.3 最后的问题
- 1.这个时候我们的反HOOK依然存在问题,因为method_exchangeImplementations方法很容易在Framework的MachO文件中找到,我们从该字符串,可以定位到防护代码.
- 2.执行反HOOK的操作是我们依靠运行时间间隔插入进去的防护,只要注入的动态库的HOOK库运行比反HOOK早,那么该反HOOK就失效了.
三、MonkeyDev
3.1 安装问题
- theos安装的目录默认是在用户同级目录下
- 安装教程、出现常见问题可以在issue里边找到答案.
- 安装成功后可以在新建工程的时候看到逆向的部分
3.2 MonkeyDev应用
3.2.1 安装MonkeyDev后
- 拿出我们的AntiHOOK工程.开启一个MonkeyApp类型工程,创建MonkeyTest的Demo
- 这个时候,放入我们的破解版微信应用的ipa,放在指定目录下
- 运行后随手解开两个报错
-
- 这时候需要我们去 Build Settings中,搜索libstdc++,删掉弱链接即可
- Xcode14.2 经常碰到找不到文件、
-
- 到Drived Data中删除的缓存、重新Run即可. 这个时候我们跑出来了一个微信7.0.8版本的砸壳程序,可正常调试
3.3.2 运行AntiHook程序
- 在MonkeyDev中如何做逆向防护?
- 在MonkeyTestDylib的Logos文件夹下
-
- 选中.xm文件、右侧选择Objective-C++ Preprocessed Source
-
- 这个时候回到.xm文件,就可以看到其中的Logos代码. 删掉其中所有的示例代码、编写我们自己的代码
%hook ViewController - (void)btnClick1:(id)sender { NSLog(@"已HOOK ViewController 中 btnClick1代码~~~"); } %end
-
- 这个时候我们运行Demo、已成功Hook了指定类下的指定方法.
3.3.3 再次防护
- 这个时候、我们怀疑没有使用exchangeImp方法,而是使用了 getImp/setImp方法
- 那么回到AntiHOOK工程、开始编写反HOOK代码
#import "AntiHOOKCode.h"
#import "fishhook.h"
@implementation AntiHOOKCode
+ (void)load {
//基本防护
struct rebinding exchange;
exchange.name = "method_exchangeImplementations";
exchange.replacement = my_exchange;
exchange.replaced = (void *)&exchangeP;
//method_setImplementation
struct rebinding setIMP;
setIMP.name = "method_setImplementation";
setIMP.replacement = my_setIMP;
setIMP.replaced = (void *)&setIMP_P;
//method_getImplementation
struct rebinding getIMP;
getIMP.name = "method_getImplementation";
getIMP.replacement = my_getIMP;
getIMP.replaced = (void *)&getIMP_P;
struct rebinding bds[] = {exchange, setIMP, getIMP};
rebind_symbols(bds, 3);
}
IMP _Nonnull (*setIMP_P)(Method _Nonnull m, IMP _Nonnull imp);
void my_setIMP(Method _Nonnull m, IMP _Nonnull imp) {
NSLog(@"检测到my_setIMP");
}
IMP _Nonnull (*getIMP_P)(Method _Nonnull m);
void my_getIMP(Method _Nonnull m) {
NSLog(@"检测到my_getIMP");
}
//指针,暴露给外界自己的工程使用
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
void my_exchange(Method _Nonnull m1, Method _Nonnull m2) {
NSLog(@"检测到了HOOK");
}
@end
-
- 编译运行、得到程序包,放入MonkeyTest的TargetApp目录下
- 这个时候编译运行MonkeyTest工程、点击按钮1后,输出日志就没有Monkey的HOOK了、因此MonkeyDevHOOK的本质也就是利用 libsubstrate.dylib,HOOK set和get方法
- MonkeyTest的程序包中包含了如下嵌入库.
-
- 回到MonkeyTestDylib.m文件、可以看到实现部分,利用的是get/set
- 回到AntiHOOK工程、我们去掉做的反HOOK防护,也就是恢复原来的系统函数调用方法
IMP method1 = class_getInstanceMethod(self.class, @selector(btnClick2:));
IMP method2 = class_getInstanceMethod(self.class, @selector(testHOOK));
method_exchangeImplementations(method1, method2);
// rebind_symbols(bds, 3);
- 再次替换MonkeyTest中的TargetApp、在Logos文件中,添加一个 %orig; 方法.表示原来的调用,相当于IMP,这个时候执行成功、HOOK到了目标程序方法
四、HOOK原理 总结
- HOOK: 钩子,修改程序的执行流程的一种技术
- MethodSwizzle
- 利用OC的运行时(Runtime)特性修改SEL和IMP(函数指针)对应关系.达到HOOK OC方法的目的
- method_exchangeIMP... 交换两个IMP
- class_replaceMethod替换某个SEL的IMP(如果没有该方法,就添加.相当于替换掉这个方法)
- method_getImplementation、method_setImplementation获取和设置某个方法的IMP(很多三方框架都使用)
- fishhook
- Facebook提供的一个工具,利用MachO文件的加载原理,动态修改懒加载和非懒加载两个符号表
- Cydia Substrate
- 一个强大的框架
- MethodSwizzle
- fishhook
- 可以HOOK系统的函数,但是无法HOOK自定义的函数
- 原理:
- 共享缓存
- iOS系统有一块特殊的位置,存放公用动态库.动态库共享缓存(dyld shared cache)
- PIC技术
- 由于外部的函数调用,在我们编译时刻是没法确定其内存地址的
- 苹果就采用了PIC技术(位置无关代码).在MachO文件Data段,建立两张表,懒加载和非懒加载表,里面存放执行外部函数的指针.
- 首次调用懒加载函数,会去找桩执行代码.首次执行会执行binder函数.
- 共享缓存
- 通过字符找到懒加载表
- fishhook利用stringTable -> Symbols -> indirect Symbols --> 懒加载符号表之间的对应关系,通过重绑定修改指针的值达到HOOK的目的.
- 反HOOK基本防护
- 利用fishhook修改MethodSwizzle相关函数
- 防护代码要最新被加载,否则先HOOK就修改完毕了,防护无效
- 原始工程编写的Framework库会优先于注入库加载.所以适合写防护代码