在上一節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
編譯成功之后,文件夾里面是這個樣子的:
- 接下來編譯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
別人代碼的時候,并不是在別人的代碼中去寫我們的代碼。回憶一下我們之前的代碼注入,是通過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
。