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 *)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 
#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

这里有一个小问题,因为案例是在自己的项目中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 *)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 
#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地址时,需要加上PAGEZEROASLR

你可能感兴趣的:(iOS逆向实战--022:InlineHook)