16-Hook原理(三)InlineHook

前言

一、InlineHook概述

inlineHook(内联钩子):所谓InlineHook就是直接修改目标函数头部代码,让它跳转到我们自定义的函数里面执行我们的代码,从而达到Hook的目的。这种Hook技术一般用在静态语言HOOK上面。

Inline Hook 就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。大体分为三步

  1. 原函数的前 N 个字节搬运Hook 函数的前 N 个字节;
  2. 然后将原函数的前 N 个字节填充跳转到 Hook 函数的指令;
  3. Hook 函数末尾几个字节填充跳转回原函数 +N 的跳转指令;

之前的文章14-Hook原理(一)fishHook中也有介绍InlineHook

二、Dobby框架

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

2.1 Dobby框架搭建

要学习Dobby,当然是先大框架

  1. 首先clone工程
#depth用于指定克隆深度,为1即表示只克隆最近一次commit.
git clone https://github.com/jmpews/Dobby.git --depth=1 
  1. 由于Dobby是跨平台的,所以项目并不是一个Xcode工程,要使用cmake将这个工程编译成为Xcode工程。相关指令如下
// 进入`Dobby目录`,创建一个文件夹`build_for_ios_arm64`
cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64

// 然后`cmake`编译生成XCode工程
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

命令执行完成后,工程应该是这样

  1. 编译Xcode工程

我们生成动态库DobbyX.framework

2.2 项目演示

Dobby的使用

  1. 新建一个工程DoddyDemo,导入DobbyX.framework

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

  • 关闭当前⼯程DoddyDemoBitcode
  • 编译DobbyX.framework时,开启Bitcode

Bitcode的设置

  1. 打开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;
}
  1. 调用DobbyHook进行函数的Hook
int DobbyHook(void *address, void *replace_call, void **origin_call);

Dobby的核心的函数

参数名 释义
address 需要HOOK的函数地址
replace_call 新函数地址
origin_call 保留原始函数的指针的地址

viewDidLoad中进行Hook

- (void)viewDidLoad {
   [super viewDidLoad];

   DobbyHook((void *)sum, mySum, (void *)&sum_p);    
}
  1. touchesBegan中,调用sum函数
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Sum:%d",sum(10, 20));
}
  1. 真机运行项目,点击屏幕

sum函数HOOK成功!

三、Dobby Hook原理

接下来,我们来研究下Dobby Hook的原理。

  1. 在上述案例中的touchesBegan方法上设置断点
  1. 打开汇编,真机运行项目,点击屏幕,进入touchesBegan方法

step into,进入sum函数

我们发现

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

看到第一句代码,就是拉伸栈空间的代码。然后br x17,会回到sum函数

接着就是执行完sum函数的逻辑,最后在结尾恢复栈平衡

  1. sum函数执行ret指令,返回mySum函数,执行后续代码

综上所述

  • 静态函数的HOOK,并没有在原始函数中增加代码,而是将拉伸栈空间的三句代码进行了替换
  • 当调用原始函数,才会拉伸栈平衡。然后在原始函数的代码中,恢复栈平衡

案例修改

我们再看修改下案例 mySum函数中,不调用原始函数

int mySum(int a,int b) {
//   NSLog(@"Sum:%d,",sum_p(a,b));
   return a - b;
}

真机运行项目,查看sum函数的汇编

上图中,代码并没有变化。

接着进入mySum函数

跳转到指定地址的代码blr x18没有了。

mySum函数执行ret指令,直接返回到touchesBegan

此时sum函数的原始代码都不会被执行。这种情况

不会拉伸栈空间,sum函数的原始代码不会被执行,所以也不会恢复栈平衡

2.1 Hook函数地址

在逆向开发中,一般的应用App会剥离符号表,所以我们无法获得符号名称,那么久没法Hook方法名称,智能Hook地址
但是,应用每次启动时,ASLR偏移地址都不一样,所以不能直接Hook固定的地址。正确的做法

先找到函数在MachO中的偏移地址,加上PAGEZERO0x100000000,再加上本次启动的ASLR偏移地址

案例演示

继续使用上述案例,找到sum函数的地址

函数实现地址是 0x104221bcc
然后使用image list,找到主程序的基地址

基地址是 0x10421c000
那么函数在MachO中的偏移地址 函数实现地址 - 主程序基地址

0x104221bcc - 0x10421c000 = 0x5BCC

偏移地址是 0x5BCC。在MachO文件中,查看该偏移地址

上图显而易见,对应的正是sum函数的汇编代码。

函数地址进行Hook

  1. 打开ViewController.m文件,写入以下代码
#import "ViewController.h"
#import 
#import 

@implementation ViewController

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

static uintptr_t sumP = 0x5BCC + 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

⚠️注意:因为修改了代码,需要重新编译,所以,sum函数在Mach-O中的偏移地址会发生改变

  1. 重新编译,查看Mach-O中的sum函数偏移地址

偏移地址0x5B34,那么修改代码

static uintptr_t sumP = 0x5B34 + 0x100000000;
  1. 真机运行项目,点击屏幕

果然,Hook成功!

2.2 Dobby注入

最后,我们来看看使用Dobby,如何代码注入?

  1. 创建FuncDemo项目,打开ViewController.m文件,写入以下代码
#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中的偏移地址

计算出偏移地址 0x5D68

⚠️注意:因为真实场景中会剥离符号,所以需要再build setting中设置

剥离符号后真机运行,通过暂停

然后lldb中输入image list获取基地址

计算出sum函数的地址 0x1000dc000 + 0x5D68 = 0x1000E1D68

  1. 对地址0x1000E1D68设置断点

能断点成功,说明sum函数的实现地址没有改变。

FuncDemo.app进行代码注入

  1. 搭建HookDemo项目,将yololib、appSign.sh、DobbyX.framework,拷贝到项目根目录

其中,脚本appSign.sh

# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包
ASSETS_PATH="${SRCROOT}/APP"
# 拿到临时的APP的路径
TEMP_APP_PATH=$(set -- "${ASSETS_PATH}/"*.app;echo "$1")

#目标ipa包路径
#TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"

#TEMP_APP_PATH=$(set -- "${ASSETS_PATH}/"*.app;echo "$1")
#TEMP_APP_PATH="${ASSETS_PATH}/*.app"
#清空Temp文件夹
#rm -rf "${SRCROOT}/Temp"
#mkdir -p "${SRCROOT}/Temp"



#----------------------------------------
# 1. 解压IPA到Temp下
#unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
#TEMP_APP_PATH=$(set -- "${ASSETS_PATH}/"*.app;echo "$1")
#TEMP_APP_PATH=$(echo "${ASSETS_PATH}/"*.app)
echo "TEMP_APP_PATH路径:$TEMP_APP_PATH"
#echo "Tempapp路径:$TEMP_APP_PATH/"

# echo "路径是:$TEMP_APP_PATH"


#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"

rm -rf $TARGET_APP_PATH
mkdir -p $TARGET_APP_PATH
cp -rf $TEMP_APP_PATH/ $TARGET_APP_PATH



#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"



#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
#  设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"


#----------------------------------------
# 5. 给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"



#----------------------------------------
# 6. 重签名第三方 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"
  1. 在项目根目录,创建App目录,将FuncDemo.app拷贝至App目录
  1. HookDemo主工程中添加target,就是注入的动态库,命名HOOK ,在HOOK动态库中,创建Inject
  1. HookDemo主工程中,拖入DobbyX.framework,勾选HookDemo
  1. HookDemo中,找到Embed Framewords,添加DobbyX.framework

然后在Hook.framework Target配置

  1. 打开Inject.m文件,写入以下代码
#import "Inject.h"
#import 
#import 

@implementation Inject

static uintptr_t sumP = 0x5D68 + 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. 真机运行项目,点击屏幕
FuncDemo[11452:2162229] Sum:25,
FuncDemo[11452:2162229] Sum:-5

总结

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

你可能感兴趣的:(16-Hook原理(三)InlineHook)