所谓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
> 真机运行项目,点击屏幕,输入以下内容:
>
InlineDemo[9140:1691629] Sum:30,🍺🍺🍺🍺🍺 InlineDemo[9140:1691629] Sum:-10
> `sum`函数`HOOK`成功,先输出原始函数的执行结果,再输出替换函数的执行结果
#####HOOK原理
> 案例1:
> 通过汇编代码,查看`HOOK`原理
> 上述案例中,在`touchesBegan`方法上设置断点<br />

> 真机运行项目,点击屏幕,进入`touchesBegan`方法<br />

> 单步调试,向下执行`1`步。进入`sum`函数<br />

> - 前三句代码被替换
> - 拉伸栈空间的代码没有了
> 单步调试,向下执行`3`步。通过`br x17`指令,跳转到`mySum`函数<br />

> 进入`mySum`函数,通过`blr x8`指令,跳转到指定地址上执行代码<br />

> 该代码中,通过`br x17`指令,回到`sum`函数<br />

> - 这里出现了拉伸栈空间的代码
> 进入`sum`函数,执行原始代码逻辑<br />

> - 在`sum`函数的结尾,恢复栈平衡
> 当`sum`函数执行`ret`指令,返回`mySum`函数,执行后续代码<br />

> 静态函数的`HOOK`,并没有在原始函数中增加代码,而是将拉伸栈空间的三句代码进行了替换
> 当调用原始函数,才会拉伸栈平衡。然后在原始函数的代码中,恢复栈平衡
> 案例2:
> `mySum`函数中,不调用原始函数
> 在`mySum`函数中,注释原始函数的调用<br />

> 真机运行项目,进入`sum`函数。代码并没有发生变化<br />

> 进入`mySum`函数,跳转到指定地址的代码没有了<br />

> 当`mySum`函数执行`ret`指令,直接返回到`touchesBegan`
> 此时`sum`函数的原始代码都不会被执行<br />

> 这种情况,不会拉伸栈空间,`sum`函数的原始代码不会被执行,所以也不会恢复栈平衡
#####HOOK函数地址
> 在逆向开发中,三方应用会剥离符号表,我们无法获得符号名称,所以`HOOK`的一定是地址
> 应用每次启动时,`ASLR`偏移地址都不一样,所以不能直接`HOOK`地址
> 正确的做法:先找到函数在`MachO`中的偏移地址,加上`PAGEZERO`的`0x100000000`,再加上本次启动的`ASLR`偏移地址
> 案例1
> 延用上述案例,找到`sum`函数的实现地址
> 查看汇编代码,找到`sum`函数的调用<br />

> - 函数实现地址:`0x1022edd48`
> 使用`image list`函数,找到主程序的基地址<br />

> - 基地址:`0x1022e8000`
> 使用`函数实现地址 - 主程序基地址`,计算函数在`MachO`中的偏移地址
>
e -f x — 0x1022edd48-0x1022e8000
$1 = 0x5d48
> - 偏移地址:`0x5d48`
> 在`MachO`文件中,查看偏移地址<br />

> - 对应的正是`sum`函数的汇编代码
> - 和断点时看到的汇编代码有些区别,因为`Dobby`在运行时,`Hook`函数会替换汇编代码
> 案例2:
> 对函数地址进行`HOOK`
> 打开`ViewController.m`文件,写入以下代码:
>
import “ViewController.h”
import
import
@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
@end
> 这里有一个小问题,因为案例是在自己的项目中`HOOK`地址,所以对项目的代码进行了修改,这样会造成`sum`函数的实现地址发生改变<br />

> - `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
int sum(int a,int b){ return a + b; }
@end
> 真机运行项目,使用`函数实现地址 - 主程序基地址`,计算出`sum`函数在`MachO`中的偏移地址:`0x5F04`
> 为了`HOOK`的场景更加真实,剥离除了间接符号之外的全部符号<br />

> 剥离符号后,验证`sum`函数的实现地址是否改变
> 真机运行项目,使用`image list`获得主程序基地址
> 通过暂停,进入`lldb`。通过`主程序基地址 + 0x5F04`,得到`sum`函数地址<br />

> 对地址设置断点,成功找到函数,说明`sum`函数的实现地址没有改变<br />

> 案例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`和`Watch`
>
rm -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”
> 重签名第三方`Frameworks`
>
TARGET_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`<br />

> 在`HookDemo`中,找到`Embed Framewords`,添加`DobbyX.framework`<br />

> 打开`Inject.m`文件,写入以下代码:
>
import “Inject.h”
import
import
@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
场景中,我们拿不到符号名称,只能对地址进行HOOK
HOOK
地址时,需要加上PAGEZERO
和ASLR