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<UITouch *> *)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 <DobbyX/dobby.h>
#import <mach-o/dyld.h>

@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<UITouch *> *)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<UITouch *> *)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 <DobbyX/dobby.h>
#import <mach-o/dyld.h>

@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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容