所谓InlineHook(内联钩⼦),就是直接修改⽬标函数的头部代码。让它跳转到⾃定义函数中执⾏代码,从⽽达到Hook的⽬的。这种Hook技术⼀般用于静态语⾔
Dobby框架
Dobby是一个全平台的InlineHook框架,详情可查看 官方文档
编译
Dobby将代码
clone下来git clone https://github.com/jmpews/Dobby.git --depth=1由于
Dobby是跨平台框架,所以项⽬并不是⼀个Xcode⼯程,需要使⽤cmake将⼯程编译成为Xcode⼯程进⼊
Dobby⽬录,创建⼀个⽂件夹,然后cmake编译⼯程cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64 cmake .. -G Xcode \ -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \ -DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \ -DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \ -DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON编译完成后,会⽣成⼀个
Xcode⼯程
编译
Xcode⼯程,⽣成Framework
导⼊
DobbyX.framework到⼯程,如果遇到Bitcode问题,两种解决方式
- 关闭当前⼯程的
Bitcode- 编译
DobbyX.framework时,开启Bitcode
DobbyX.framework拷⻉问题将
Framework库⾸次拖⼊⼯程,Xcode不会⾃动帮你拷⻉。运⾏时会发现Framework没有打包进⼊App包,造成DYLD加载时找不到库的错误来到
Xcode中的Build Phases,点击+,选择New Copy Files Phase
在
Copy Files中,将Destination选择Frameworks
点击
+,选择DobbyX.framework,点击Add
Dobby的核心的函数int DobbyHook(void *address, void *replace_call, void **origin_call);
address:需要HOOK的函数地址replace_call:新函数地址origin_call:保留原始函数的指针的地址
案例1
Dobby的使用搭建
InlineDemo项目,拖入DobbyX.framework,解决拷⻉问题打开
ViewController.m文件,写入以下代码:定义将要被
HOOK的静态函数int sum(int a,int b){ return a + b; }定义函数指针,⽤于保存被替换函数的地址
static int (*sum_p)(int a,int b);定义新函数,⽤此函数替换将要
HOOK的函数,该函数的返回值及参数必须⼀致int mySum(int a,int b) { NSLog(@"Sum:%d,🍺🍺🍺🍺🍺",sum_p(a,b)); return a - b; }在
viewDidLoad方法中,调用DobbyHook进行函数的Hook- (void)viewDidLoad { [super viewDidLoad]; DobbyHook((void *)sum, mySum, (void *)&sum_p); }在
touchesBegan中,调用sum函数-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"Sum:%d",sum(10, 20)); }真机运行项目,点击屏幕,输入以下内容:
InlineDemo[9140:1691629] Sum:30,🍺🍺🍺🍺🍺 InlineDemo[9140:1691629] Sum:-10
sum函数HOOK成功,先输出原始函数的执行结果,再输出替换函数的执行结果
HOOK原理
案例1:
通过汇编代码,查看
HOOK原理上述案例中,在
touchesBegan方法上设置断点
真机运行项目,点击屏幕,进入
touchesBegan方法
单步调试,向下执行
1步。进入sum函数
- 前三句代码被替换
- 拉伸栈空间的代码没有了
单步调试,向下执行
3步。通过br x17指令,跳转到mySum函数
进入
mySum函数,通过blr x8指令,跳转到指定地址上执行代码
该代码中,通过
br x17指令,回到sum函数
- 这里出现了拉伸栈空间的代码
进入
sum函数,执行原始代码逻辑
- 在
sum函数的结尾,恢复栈平衡当
sum函数执行ret指令,返回mySum函数,执行后续代码
静态函数的
HOOK,并没有在原始函数中增加代码,而是将拉伸栈空间的三句代码进行了替换当调用原始函数,才会拉伸栈平衡。然后在原始函数的代码中,恢复栈平衡
案例2:
mySum函数中,不调用原始函数在
mySum函数中,注释原始函数的调用
真机运行项目,进入
sum函数。代码并没有发生变化
进入
mySum函数,跳转到指定地址的代码没有了
当
mySum函数执行ret指令,直接返回到touchesBegan此时
sum函数的原始代码都不会被执行
这种情况,不会拉伸栈空间,
sum函数的原始代码不会被执行,所以也不会恢复栈平衡
HOOK函数地址
在逆向开发中,三方应用会剥离符号表,我们无法获得符号名称,所以
HOOK的一定是地址应用每次启动时,
ASLR偏移地址都不一样,所以不能直接HOOK地址正确的做法:先找到函数在
MachO中的偏移地址,加上PAGEZERO的0x100000000,再加上本次启动的ASLR偏移地址
案例1
延用上述案例,找到
sum函数的实现地址查看汇编代码,找到
sum函数的调用
- 函数实现地址:
0x1022edd48使用
image list函数,找到主程序的基地址
- 基地址:
0x1022e8000使用
函数实现地址 - 主程序基地址,计算函数在MachO中的偏移地址e -f x -- 0x1022edd48-0x1022e8000 ------------------------- $1 = 0x5d48
- 偏移地址:
0x5d48在
MachO文件中,查看偏移地址
- 对应的正是
sum函数的汇编代码- 和断点时看到的汇编代码有些区别,因为
Dobby在运行时,Hook函数会替换汇编代码
案例2:
对函数地址进行
HOOK打开
ViewController.m文件,写入以下代码:#import "ViewController.h" #import <DobbyX/dobby.h> #import <mach-o/dyld.h> @implementation ViewController int sum(int a,int b){ return a + b; } static uintptr_t sumP = 0x5d48 + 0x100000000; - (void)viewDidLoad { [super viewDidLoad]; sumP += _dyld_get_image_vmaddr_slide(0); DobbyHook((void *)sumP, mySum, (void *)&sum_p); } static int (*sum_p)(int a,int b); int mySum(int a,int b) { NSLog(@"Sum:%d,🍺🍺🍺🍺🍺",sum_p(a,b)); return a - b; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"Sum:%d",sum(10, 20)); } @end这里有一个小问题,因为案例是在自己的项目中
HOOK地址,所以对项目的代码进行了修改,这样会造成sum函数的实现地址发生改变
sum函数的偏移地址,从之前的0x5d48变为0x5d08打开
ViewController.m文件,修改代码:static uintptr_t sumP = 0x5d08 + 0x100000000;真机运行项目,点击屏幕,
HOOK成功InlineDemo[9883:1880364] Sum:30,🍺🍺🍺🍺🍺 InlineDemo[9883:1880364] Sum:-10在代码不修改的情况下,地址不会改变。所以在逆向开发中,分析第三方应用,不会出现这种问题
Dobby注入应用
案例1:
搭建被
HOOK的应用创建
FuncDemo项目打开
ViewController.m文件,写入以下代码:#import "ViewController.h" @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"Sum:%d",sum(10,15)); } int sum(int a,int b){ return a + b; } @end真机运行项目,使用
函数实现地址 - 主程序基地址,计算出sum函数在MachO中的偏移地址:0x5F04为了
HOOK的场景更加真实,剥离除了间接符号之外的全部符号
剥离符号后,验证
sum函数的实现地址是否改变真机运行项目,使用
image list获得主程序基地址通过暂停,进入
lldb。通过主程序基地址 + 0x5F04,得到sum函数地址
对地址设置断点,成功找到函数,说明
sum函数的实现地址没有改变
案例2:
修改重签名脚本,改为支持
.app格式定义变量
# 工程文件所在的目录 TEMP_PATH="${SRCROOT}/Temp" # 资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包 ASSETS_PATH="${SRCROOT}/APP" # 拿到临时的APP的路径 TEMP_APP_PATH=$(set -- "${ASSETS_PATH}/"*.app;echo "$1")将
.app拷贝进入工程TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app" rm -rf $TARGET_APP_PATH mkdir -p $TARGET_APP_PATH cp -rf $TEMP_APP_PATH/ $TARGET_APP_PATH删除
Extention和Watchrm -rf "$TARGET_APP_PATH/PlugIns" rm -rf "$TARGET_APP_PATH/Watch"更新
info.plist文件中的CFBundleIdentifier/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"拿到
MachO文件的路径,给MachO文件上执行权限APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<` chmod +x "$TARGET_APP_PATH/$APP_BINARY"重签名第三方
FrameworksTARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks" if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ]; then for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"* do签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK" done fi注入
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/Hook.framework/Hook"
案例3:
对
FuncDemo.app进行HOOK搭建
HookDemo项目将
yololib、appSign.sh、DobbyX.framework,拷贝到项目根目录在项目根目录,创建
App目录将
FuncDemo.app拷贝至App目录创建
target,添加注入的动态库,命名HOOK在
HOOK动态库中,创建Inject类在
HookDemo主项目中,拖入DobbyX.framework,勾选HookDemo和HOOK
在
HookDemo中,找到Embed Framewords,添加DobbyX.framework
打开
Inject.m文件,写入以下代码:#import "Inject.h" #import <DobbyX/dobby.h> #import <mach-o/dyld.h> @implementation Inject static uintptr_t sumP = 0x5F04 + 0x100000000; +(void)load{ sumP += _dyld_get_image_vmaddr_slide(0); DobbyHook((void *)sumP, mySum, (void *)&sum_p); } static int (*sum_p)(int a,int b); int mySum(int a,int b) { NSLog(@"Sum:%d,🍺🍺🍺🍺🍺",sum_p(a,b)); return a - b; } @end真机运行项目,点击屏幕,
HOOK成功FuncDemo[11452:2162229] Sum:25,🍺🍺🍺🍺🍺 FuncDemo[11452:2162229] Sum:-5
总结
Dobby
Dobby原理:运行时对目标函数的汇编代码替换,修改的是内存中MachO的代码段Dobby替换汇编代码时,对原始函数的调用,会影响栈的拉伸和平衡- 在真实
HOOK场景中,我们拿不到符号名称,只能对地址进行HOOKHOOK地址时,需要加上PAGEZERO和ASLR

























