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 <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工程
    創建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鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容