所谓InlineHook(内联钩⼦),就是直接修改⽬标函数的头部代码。让它跳转到⾃定义函数中执⾏代码,从⽽达到Hook的⽬的。这种Hook技术⼀般用于静态语⾔

Dobby框架

Dobby是一个全平台的InlineHook框架,详情可查看 官方文档

编译Dobby

将代码clone下来

  1. git clone https://github.com/jmpews/Dobby.git --depth=1

由于Dobby是跨平台框架,所以项⽬并不是⼀个Xcode⼯程,需要使⽤cmake将⼯程编译成为Xcode⼯程

进⼊Dobby⽬录,创建⼀个⽂件夹,然后cmake编译⼯程

  1. cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
  2. cmake .. -G Xcode \
  3. -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
  4. -DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
  5. -DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
  6. -DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON

编译完成后,会⽣成⼀个Xcode⼯程
iOS逆向实战--022:InlineHook - 图1

编译Xcode⼯程,⽣成Framework
iOS逆向实战--022:InlineHook - 图2

导⼊DobbyX.framework到⼯程,如果遇到Bitcode问题,两种解决方式

  • 关闭当前⼯程的Bitcode
  • 编译DobbyX.framework时,开启Bitcode
    iOS逆向实战--022:InlineHook - 图3

DobbyX.framework拷⻉问题

Framework库⾸次拖⼊⼯程,Xcode不会⾃动帮你拷⻉。运⾏时会发现Framework没有打包进⼊App包,造成DYLD加载时找不到库的错误

来到Xcode中的Build Phases,点击+,选择New Copy Files Phase
iOS逆向实战--022:InlineHook - 图4

Copy Files中,将Destination选择Frameworks
iOS逆向实战--022:InlineHook - 图5

点击+,选择DobbyX.framework,点击Add
iOS逆向实战--022:InlineHook - 图6

Dobby的核心的函数

  1. int DobbyHook(void *address, void *replace_call, void **origin_call);
  • address:需要HOOK的函数地址
  • replace_call:新函数地址
  • origin_call:保留原始函数的指针的地址

案例1

Dobby的使用

搭建InlineDemo项目,拖入DobbyX.framework,解决拷⻉问题

打开ViewController.m文件,写入以下代码:

定义将要被HOOK的静态函数

  1. int sum(int a,int b){
  2. return a + b;
  3. }

定义函数指针,⽤于保存被替换函数的地址

  1. static int (*sum_p)(int a,int b);

定义新函数,⽤此函数替换将要HOOK的函数,该函数的返回值及参数必须⼀致

  1. int mySum(int a,int b) {
  2. NSLog(@"Sum:%d,🍺🍺🍺🍺🍺",sum_p(a,b));
  3. return a - b;
  4. }

viewDidLoad方法中,调用DobbyHook进行函数的Hook

```

  • (void)viewDidLoad { [super viewDidLoad];

DobbyHook((void )sum, mySum, (void )&sum_p);
}

  1. > `touchesBegan`中,调用`sum`函数
  2. >

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { NSLog(@”Sum:%d”,sum(10, 20)); }

  1. > 真机运行项目,点击屏幕,输入以下内容:
  2. >

InlineDemo[9140:1691629] Sum:30,🍺🍺🍺🍺🍺 InlineDemo[9140:1691629] Sum:-10

  1. > `sum`函数`HOOK`成功,先输出原始函数的执行结果,再输出替换函数的执行结果
  2. #####HOOK原理
  3. > 案例1
  4. > 通过汇编代码,查看`HOOK`原理
  5. > 上述案例中,在`touchesBegan`方法上设置断点<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-76a81ed3d1850b46.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > 真机运行项目,点击屏幕,进入`touchesBegan`方法<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-e5229de188f9228d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > 单步调试,向下执行`1`步。进入`sum`函数<br />
  10. ![](https://upload-images.jianshu.io/upload_images/9297953-f4e4299f988ce700.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  11. > - 前三句代码被替换
  12. > - 拉伸栈空间的代码没有了
  13. > 单步调试,向下执行`3`步。通过`br x17`指令,跳转到`mySum`函数<br />
  14. ![](https://upload-images.jianshu.io/upload_images/9297953-b4ed4d01026f4b4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  15. > 进入`mySum`函数,通过`blr x8`指令,跳转到指定地址上执行代码<br />
  16. ![](https://upload-images.jianshu.io/upload_images/9297953-0beec308658afad1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  17. > 该代码中,通过`br x17`指令,回到`sum`函数<br />
  18. ![](https://upload-images.jianshu.io/upload_images/9297953-cb72c09a4fd2e89b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  19. > - 这里出现了拉伸栈空间的代码
  20. > 进入`sum`函数,执行原始代码逻辑<br />
  21. ![](https://upload-images.jianshu.io/upload_images/9297953-b08cddc826755bd7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  22. > - `sum`函数的结尾,恢复栈平衡
  23. > `sum`函数执行`ret`指令,返回`mySum`函数,执行后续代码<br />
  24. ![](https://upload-images.jianshu.io/upload_images/9297953-50a3f9c7f18e5e2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  25. > 静态函数的`HOOK`,并没有在原始函数中增加代码,而是将拉伸栈空间的三句代码进行了替换
  26. > 当调用原始函数,才会拉伸栈平衡。然后在原始函数的代码中,恢复栈平衡
  27. > 案例2
  28. > `mySum`函数中,不调用原始函数
  29. > `mySum`函数中,注释原始函数的调用<br />
  30. ![](https://upload-images.jianshu.io/upload_images/9297953-cca87039e97388af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  31. > 真机运行项目,进入`sum`函数。代码并没有发生变化<br />
  32. ![](https://upload-images.jianshu.io/upload_images/9297953-43c2e865d34d0a07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  33. > 进入`mySum`函数,跳转到指定地址的代码没有了<br />
  34. ![](https://upload-images.jianshu.io/upload_images/9297953-70ea6b03c4d1941a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  35. > `mySum`函数执行`ret`指令,直接返回到`touchesBegan`
  36. > 此时`sum`函数的原始代码都不会被执行<br />
  37. ![](https://upload-images.jianshu.io/upload_images/9297953-b08cddc826755bd7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  38. > 这种情况,不会拉伸栈空间,`sum`函数的原始代码不会被执行,所以也不会恢复栈平衡
  39. #####HOOK函数地址
  40. > 在逆向开发中,三方应用会剥离符号表,我们无法获得符号名称,所以`HOOK`的一定是地址
  41. > 应用每次启动时,`ASLR`偏移地址都不一样,所以不能直接`HOOK`地址
  42. > 正确的做法:先找到函数在`MachO`中的偏移地址,加上`PAGEZERO``0x100000000`,再加上本次启动的`ASLR`偏移地址
  43. > 案例1
  44. > 延用上述案例,找到`sum`函数的实现地址
  45. > 查看汇编代码,找到`sum`函数的调用<br />
  46. ![](https://upload-images.jianshu.io/upload_images/9297953-6c9226ad0adc45a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  47. > - 函数实现地址:`0x1022edd48`
  48. > 使用`image list`函数,找到主程序的基地址<br />
  49. ![](https://upload-images.jianshu.io/upload_images/9297953-03d8acd626867e5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  50. > - 基地址:`0x1022e8000`
  51. > 使用`函数实现地址 - 主程序基地址`,计算函数在`MachO`中的偏移地址
  52. >

e -f x — 0x1022edd48-0x1022e8000

$1 = 0x5d48

  1. > - 偏移地址:`0x5d48`
  2. > `MachO`文件中,查看偏移地址<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-848b4d9c0d3593f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. > - 对应的正是`sum`函数的汇编代码
  5. > - 和断点时看到的汇编代码有些区别,因为`Dobby`在运行时,`Hook`函数会替换汇编代码
  6. > 案例2
  7. > 对函数地址进行`HOOK`
  8. > 打开`ViewController.m`文件,写入以下代码:
  9. >

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 )touches withEvent:(UIEvent )event { NSLog(@”Sum:%d”,sum(10, 20)); }

@end

  1. > 这里有一个小问题,因为案例是在自己的项目中`HOOK`地址,所以对项目的代码进行了修改,这样会造成`sum`函数的实现地址发生改变<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-d6c6a061b5d66580.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `sum`函数的偏移地址,从之前的`0x5d48`变为`0x5d08`
  4. > 打开`ViewController.m`文件,修改代码:
  5. >

static uintptr_t sumP = 0x5d08 + 0x100000000;

  1. > 真机运行项目,点击屏幕,`HOOK`成功
  2. >

InlineDemo[9883:1880364] Sum:30,🍺🍺🍺🍺🍺 InlineDemo[9883:1880364] Sum:-10

  1. > 在代码不修改的情况下,地址不会改变。所以在逆向开发中,分析第三方应用,不会出现这种问题
  2. #####Dobby注入应用
  3. > 案例1
  4. > 搭建被`HOOK`的应用
  5. > 创建`FuncDemo`项目
  6. > 打开`ViewController.m`文件,写入以下代码:
  7. >

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { [super viewDidLoad]; }

-(void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event { NSLog(@”Sum:%d”,sum(10,15)); }

int sum(int a,int b){ return a + b; }

@end

  1. > 真机运行项目,使用`函数实现地址 - 主程序基地址`,计算出`sum`函数在`MachO`中的偏移地址:`0x5F04`
  2. > 为了`HOOK`的场景更加真实,剥离除了间接符号之外的全部符号<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-c5f953d09c549d56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. > 剥离符号后,验证`sum`函数的实现地址是否改变
  5. > 真机运行项目,使用`image list`获得主程序基地址
  6. > 通过暂停,进入`lldb`。通过`主程序基地址 + 0x5F04`,得到`sum`函数地址<br />
  7. ![](https://upload-images.jianshu.io/upload_images/9297953-c03eb7f01bf38828.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  8. > 对地址设置断点,成功找到函数,说明`sum`函数的实现地址没有改变<br />
  9. ![](https://upload-images.jianshu.io/upload_images/9297953-1e4543283afbc192.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  10. > 案例2
  11. > 修改重签名脚本,改为支持`.app`格式
  12. > 定义变量
  13. >

工程文件所在的目录

TEMP_PATH=”${SRCROOT}/Temp”

资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包

ASSETS_PATH=”${SRCROOT}/APP”

拿到临时的APP的路径

TEMP_APP_PATH=$(set — “${ASSETS_PATH}/“*.app;echo “$1”)

  1. > `.app`拷贝进入工程
  2. >

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

  1. > 删除`Extention``Watch`
  2. >

rm -rf “$TARGET_APP_PATH/PlugIns” rm -rf “$TARGET_APP_PATH/Watch”

  1. > 更新`info.plist`文件中的`CFBundleIdentifier`
  2. >

/usr/libexec/PlistBuddy -c “Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER” “$TARGET_APP_PATH/Info.plist”

  1. > 拿到`MachO`文件的路径,给`MachO`文件上执行权限
  2. >

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”

  1. > 重签名第三方`Frameworks`
  2. >

TARGET_APP_FRAMEWORKS_PATH=”$TARGET_APP_PATH/Frameworks” if [ -d “$TARGET_APP_FRAMEWORKS_PATH” ]; then for FRAMEWORK in “$TARGET_APP_FRAMEWORKS_PATH/“* do

  1. > 签名
  2. >

/usr/bin/codesign —force —sign “$EXPANDED_CODE_SIGN_IDENTITY” “$FRAMEWORK” done fi

  1. > 注入
  2. >

./yololib “$TARGET_APP_PATH/$APP_BINARY” “Frameworks/Hook.framework/Hook”

  1. > 案例3
  2. > `FuncDemo.app`进行`HOOK`
  3. > 搭建`HookDemo`项目
  4. > `yololib``appSign.sh``DobbyX.framework`,拷贝到项目根目录
  5. > 在项目根目录,创建`App`目录
  6. > `FuncDemo.app`拷贝至`App`目录
  7. > 创建`target`,添加注入的动态库,命名`HOOK`
  8. > `HOOK`动态库中,创建`Inject`
  9. > `HookDemo`主项目中,拖入`DobbyX.framework`,勾选`HookDemo``HOOK`<br />
  10. ![](https://upload-images.jianshu.io/upload_images/9297953-9e3da5aeec09a6ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  11. > `HookDemo`中,找到`Embed Framewords`,添加`DobbyX.framework`<br />
  12. ![](https://upload-images.jianshu.io/upload_images/9297953-68902fd496d40a02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  13. > 打开`Inject.m`文件,写入以下代码:
  14. >

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

  1. > 真机运行项目,点击屏幕,`HOOK`成功
  2. >

FuncDemo[11452:2162229] Sum:25,🍺🍺🍺🍺🍺 FuncDemo[11452:2162229] Sum:-5 ```

总结

Dobby

  • Dobby原理:运行时对目标函数的汇编代码替换,修改的是内存中MachO的代码段
  • Dobby替换汇编代码时,对原始函数的调用,会影响栈的拉伸和平衡
  • 在真实HOOK场景中,我们拿不到符号名称,只能对地址进行HOOK
  • HOOK地址时,需要加上PAGEZEROASLR