12、HOOK原理(下)--- InlineHook
在上一节11、HOOK原理(上)--- fishHook中我们使用了fishHook
对NSLog
进行了HOOK
,但是在结尾的时候我们也留下了一个疑问,那就是对于静态方法
我们要怎么去HOOK
。
今天我们就要去解决这个问题,这就要引入我们今天的主题InlineHook(内联钩子)
。
InlineHook
:所谓InlinHook
就是直接修改目标函数的头部代码。让它跳转到我们自定义的函数里面执行我们的代码,从而达到HOOK
的目的。这种HOOK
计数一般用在静态语言的HOOK
上面。
Dobby
在进行InlineHook
的时候,有一个很不错的框架:Dobby,这是一个跨平台的框架,所以项目并不是一个Xcode工程,我们要使用cmake
将这个工程编译成Xcode工程。
- 首先我们将代码
clone
下来:
git clone https://github.com/jmpews/Dobby.git --depth=1
// depth用于指定克隆的深度,为1表示只克隆最近一个commit
- 进入
Dobby
目录,创建一个文件夹,然后cmake
编译工程。
$ cd Dobby-master && 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
编译成功之后,文件夹里面是这个样子的:
build_for_ios_arm64
- 接下来编译Xcode工程,生成我们的
Framework
选择自己的开发者账号,编译一下就可以了:
Dobby编译
Dobby的使用
我们新建一个工程开始使用Dobby
。
-
bitcode
问题,这里有的人可能会遇到一个常见的bitcode
问题,解决办法也很简单,有两种:
i
:让我们的编译的Framework
支持bitcode
。
ii
:我们的Demo工程,关闭bitcode
。
bitcode设置
bitcode
是苹果独有的一层中间代码。包含bitcode
配置的程序会在App Store
上被编译和链接。
bitcode
允许苹果在后期重新优化我们程序的二进制文件,也就是说苹果会将这个bitcode
编译为可执行的64位或32位程序。
-
将Dobby的Framework导入到Demo工程
DobbyX.framework
此时如果运行Demo会遇到一个问题:
会发现Demo运行的过程中,dyld
找不到我们刚刚导入的DobbyX.framework
。这是因为,我们手动将DobbyX.framework
拖入工程的时候,Xcode并不会帮我们去拷贝,运行时发现库并没有打包进入APP包,如下:
此时我们需要手动添加拷贝:
-
在Demo中使用Dobby
我们先定义一个简单的静态函数来测试一下是否成功,可以看到我们HOOK
成功了。
到这里我们的Dobby
使用已经没有什么问题了。
DobbyHook
参数解析:int DobbyHook(void *address, void *replace_call, void **origin_call);
address
:需要HOOK
的函数地址,函数名称就是函数地址replace_call
:新函数的地址origin_call
:将原来的函数地址存放到sum_p(我们自己定义的函数指针)
这个函数指针中,因为要给指针赋值,所以取指针的地址,so:二级指针。
但是新的问题就出来了,我们在HOOK
别人的静态函数的时候,拿不到符号名改怎么办,能不能根据地址去HOOK
?当然是可以的!!!
根据函数地址HOOK
要拿到函数的地址,这个时候就要借助工具了,这里我们使用的MachOView
。
-
首先我们要计算出函数的偏移地址
在sum
原始函数处设置断点,然后运行查看汇编代码:
sum断点
sum函数虚拟内存地址
得到sum
的虚拟内存地址为:0x100e75d04
- 通过
lldb
指令image list
获取镜像列表,查看主程序MachO
的首地址0x0000000100e70000
:
主程序MachO的首地址
则sum函数的偏移地址 = sum函数地址 - 主程序首地址
----
-
在
MachOView
中查找当前函数
我们打开MachOView
,可以看到这里就是sum函数
的开始位置,所以我们验证了sum函数
的文件偏移地址就是0x5D04
。那么我们就用这个地址进行接下来的HOOK
。
-
HOOK
前的准备- 准备一个指针
注意:由于要方便我们的计算,这里定义函数指针不要定义成为函数的结构,而是uintptr_t
类型
- 准备一个指针
//定义指针,表示sum函数的偏移地址
//地址前面的10000是 pagezero,用来适配32位系统
static uintptr_t sumP = 0x100005D04;
- 动态获取ASLR
首先导入#import <mach-o/dyld.h>
然后使用函数_dyld_get_image_vmaddr_slide
获取ASLR
:
//获取ASLR,让sumP变成准确的地址
//参数0代表 imagelist 中的主程序(自己)
uintptr_t aslr = _dyld_get_image_vmaddr_slide(0);
sumP += aslr;
//HOOK sum
DobbyHook((void *)sumP, mySum, (void *)&sum_p);
⚠️ 警告:这里有一个细节大家要注意,由于我们刚刚修改了代码,所以
sum函数
的偏移地址可能发生了改变,这个时候我们要从新去获取这个偏移地址。
将新的地址替换就可以了。
输出结果:
HOOK成功了
到这里,我们通过纯地址的HOOK
也完成了。
可是还有一个问题。我们在HOOK
别人代码的时候,并不是在别人的代码中去写我们的代码。回忆一下我们之前的代码注入,是通过Framework
的注入来完成的。既然我们验证了静态函数也是可以HOOK
的,那么不妨我们就用代码注入的形式去HOOK
一下。
通过代码注入的形式HOOK
-
首先创建一个DemoAPP
DemoAPP
创建DemoAPP
,并在DemoAPP
中添加如下代码:
⚠️ 注意:编译完成之后,在这里获取sum函数
的准确地址。记录一下。
模拟实战环节,我们将DemoAPP
的符号脱掉。
脱符号 -
创建HOOK工程
Other Linker Flags
创建HOOK
工程,并创建Framework
。可参考9、应用重签名原理
&10、代码的注入
这个时候我们在将DobbyX.framework
拖入到HOOK工程
的时候(拖入到主工程下),除了上面所要注意的地方之外,在Targets -> JaxHOOK
中还有两个地方需要注意:
1、
Other Linker Flags
需要配置一下
2、
Framework Search Paths
需要配置一下
Framework Search Paths -
接下来我们就可以在
JaxHOOKJaxHOOK
里面去写我们的HOOK
代码了
-
此时我们运行工程,会出现下面的现象,这是因为我们并没有将
测试APP
引入进来。这个时候就需要结合我们之前的知识,对测试APP
进行重签名和代码注入。
在这里先出本次用到的脚本:
# 0 ----- 准备
ASSETS_PATH="${SRCROOT}/APP"
# 1 ----- 拿到APP的路径
# 拿到临时APP的路径
TEMP_APP_PATH=$(set -- "${ASSETS_PATH}/"*.app;echo "$1")
# 打印临时APP的路径
echo "TEMP_APP_PATH路径:$TEMP_APP_PATH"
# 2 ----- 将.app拷贝进工程下
# TARGET_NAME --- target名称
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
# 打印工程生成的APP包的路径
echo "自己的app的路径:$TARGET_APP_PATH"
# 清空路径下的文件夹
rm -rf $TARGET_APP_PATH
# 创建文件夹
mkdir -p $TARGET_APP_PATH
# 将 TEMP_APP_PATH 拷贝到 TARGET_APP_PATH
cp -rf $TEMP_APP_PATH/ $TARGET_APP_PATH
# 3 ----- 删除 extension 和 watchAPP,个人证书无法签名 extension
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文件上执行权限
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
# 7 ----- 注入 (注意,在重签名APP之前,先注释下面的代码,等到重签名完成之后,再去注入代码)
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/JaxHOOK.framework/JaxHOOK"
注意,脚本中最后一行指令,代码注入
在重签名的时候要先注释掉。
- 在做好准备工作之后,我们来梳理一下整个的操作流程。
① :创建HOOK工程
&带注入的代码(JaxHOOK)
,并运行。目的是让手机认证描述文件。
② :创建测试APP
。
③ :在HOOK工程
的目录下,创建APP
文件夹;将yololib
可以执行文件拖入工程目录下;创建appSign.sh
脚本文件。
④ :将DobbyX.framework
,引入工程,并撰写HOOK
代码,此时先注释掉HOOK
代码。
⑤ :在TARGETS -> JaxHOOKDemo -> Buid Phases
中配置脚本信息;运行工程,对APP进行重签名。
⑥ :打开注入命令,打开HOOK
代码;运行工程,进行HOOK
。