iOS逆向实战--022:InlineHook

所谓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中的偏移地址,加上PAGEZERO0x100000000,再加上本次启动的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

删除ExtentionWatch

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项目

yololibappSign.shDobbyX.framework,拷贝到项目根目录

在项目根目录,创建App目录

FuncDemo.app拷贝至App目录

创建target,添加注入的动态库,命名HOOK

HOOK动态库中,创建Inject

HookDemo主项目中,拖入DobbyX.framework,勾选HookDemoHOOK

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

推荐阅读更多精彩内容