12、HOOK原理(下)--- InlineHook

在上一节11、HOOK原理(上)--- fishHook中我们使用了fishHookNSLog进行了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
    然后使用函数_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工程
    创建HOOK工程,并创建Framework。可参考9、应用重签名原理
    &10、代码的注入
    这个时候我们在将DobbyX.framework拖入到HOOK工程的时候(拖入到主工程下),除了上面所要注意的地方之外,在Targets -> JaxHOOK中还有两个地方需要注意:
    1、 Other Linker Flags需要配置一下

    Other Linker Flags

    2、 Framework Search Paths需要配置一下
    Framework Search Paths

  • 接下来我们就可以在JaxHOOK里面去写我们的HOOK代码了

    JaxHOOK

  • 此时我们运行工程,会出现下面的现象,这是因为我们并没有将测试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

Demo链接

你可能感兴趣的:(12、HOOK原理(下)--- InlineHook)