> 文章列表 > 12、HOOK原理下

12、HOOK原理下

12、HOOK原理下

一、去符号和恢复符号

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代码之后,主工程之前.所以要切入这个时机点去做防护
  • 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_setImplementationstruct rebinding setIMP;setIMP.name    = "method_setImplementation";setIMP.replacement = my_setIMP;setIMP.replaced = (void *)&setIMP_P;//method_getImplementationstruct 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
      • 一个强大的框架
  • fishhook
    • 可以HOOK系统的函数,但是无法HOOK自定义的函数
    • 原理:
      • 共享缓存
        • iOS系统有一块特殊的位置,存放公用动态库.动态库共享缓存(dyld shared cache)
      • PIC技术
        • 由于外部的函数调用,在我们编译时刻是没法确定其内存地址的
        • 苹果就采用了PIC技术(位置无关代码).在MachO文件Data段,建立两张表,懒加载和非懒加载表,里面存放执行外部函数的指针.
        • 首次调用懒加载函数,会去找桩执行代码.首次执行会执行binder函数.
    • 通过字符找到懒加载表
      • fishhook利用stringTable -> Symbols -> indirect Symbols --> 懒加载符号表之间的对应关系,通过重绑定修改指针的值达到HOOK的目的.
  • 反HOOK基本防护
    • 利用fishhook修改MethodSwizzle相关函数
    • 防护代码要最新被加载,否则先HOOK就修改完毕了,防护无效
    • 原始工程编写的Framework库会优先于注入库加载.所以适合写防护代码