iOS攻防-實戰(1)-動態注入

前言:之前幾篇講了一些背景知識,這一篇進入正題,來講一下動態注入的基本理論和流程。我們這裏通過動態添加運行庫的方式來實現動態注入。

ipa包結構

還記得我們之前介紹Math-O文件時,提到了Load commands嘛?其中有一部分是加載第三方庫的:

注意其中的:LC_LOAD_DYLIB(AFNetworking)。那麼,能否通過修改Math-O文件來加載我們需要動態注入的庫呢?答案是肯定的。

實際上,我們打包的ipa文件本質上是一個壓縮包,通過修改ipa文件的後綴(ipa->zip),然後解壓縮,就能看到ipa文件中的內容:

Payload就是Hello.ipa解壓縮後的內容,其中只有一個文件Hello.app。右鍵點擊app文件,選擇顯示包內容,就能看到app文件的內容:

裏面的有些內容是不是感覺很熟悉?注意其中名叫Hello的Unix可執行文件,這個就是我們ipa包中的Path-O文件。但有了這些還不夠,我們還需要了解另一個知識:重簽名

重簽名機制

每一個ipa文件在裝到手機中運行前,都需要進行簽名(第二篇中有介紹)。實際上,ipa中加載的每一個庫在打包進ipa中時,都會進行一次簽名。

那麼,我們如果想要通過添加自己的三方庫來實現動態注入,除了要修改Math-O文件,還需要搞定簽名機制。

iOS的重簽名機制其實並不神祕,很多第三方應用分發平臺都會對你的應用進行重籤來進行分發(比如蒲公英)。這方面的資料網上也很多,這裏我們介紹一種比較簡單的方式:xcode+腳本

首先,用xcode新建一個重簽名項目,名字隨意,這裏我取名爲HelloInject。

重籤項目及庫項目

新建一個APP目錄(名字隨意,只要和後面的腳本中的內容對應上),將需要重簽名的ipa文件放到文件夾中

這裏特別提一點,很多介紹重簽名的文章都要求重簽名的ipa是越獄包(關於越獄ipa包是什麼,以及獲取方式,之後有時間給大家講講)。經過我親測,非越獄包也可以重籤,實際上我測試的Hello.ipa就是一個正式簽名ipa(上架包)。但是這裏我遇到一個問題,我在打包ipa時,如果勾選了支持bitcode,重籤就會出問題,原因我也不明白,望有懂的大神能夠指教。

在TARGET中添加一個庫項目,這裏取名爲InjectSDK,在庫項目中建一個類,重寫類的+ (void)load方法:

+ (void)load
{
    NSLog(@"!---------------------------------------------------- Inject Sucess... ----------------------------------------------------!");
}

至於爲什麼要寫在load方法裏,之後再解釋。這個庫項目就是我們要動態注入的動態庫!

重簽名腳本

接下來準備好重簽名的腳本,取名爲AssignApp.sh,放在項目根目錄(腳本內容借鑑自網絡)

# ${SRCROOT} 這個是工程目錄
TEMP_PATH="${SRCROOT}/Temp"
# 資源文件夾,即存放要重籤的ipa文件的路徑
ASSETS_PATH="${SRCROOT}/APP"
# ipa包路徑
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"

#新建Temp文件夾
rm -rf "${TEMP_PATH}"
mkdir -p "${TEMP_PATH}"

# --------------------------------------
# 1. 解壓IPA 到Temp下
# 解壓ipa包到Temp目錄
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解壓的臨時App的路徑
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# 打印一下
echo "TEMP_APP_PATH:$TEMP_APP_PATH"

# -------------------------------------
# 2. 把解壓出來的.app拷貝進去
#BUILT_PRODUCTS_DIR 工程生成的APP包路徑
#TARGET_NAME target名稱
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "TARGET_APP_PATH:$TARGET_APP_PATH"

rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH/"


# -------------------------------------
# 3. 爲了是重簽過程簡化,移走extension和watchAPP. 此外個人免費的證書沒辦法籤extension
echo "Removing AppExtensions"
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"

# -------------------------------------
# 4. 更新 Info.plist 裏的BundleId
# 設置 "Set :KEY Value" "目標文件路徑.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"

# 5.給可執行文件上權限
#添加ipa二進制的執行權限,否則xcode會告知無法運行
#這個操作是要找到第三方app包裏的可執行文件名稱,因爲info.plist的 'Executable file' key對應的是可執行文件的名稱
#我們grep 一下,然後取最後一行, 然後以cut 命令分割,取出想要的關鍵信息。存到APP_BINARY變量裏
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#這個爲二進制文件加上可執行權限 +X
chmod +x "$TARGET_APP_PATH/$APP_BINARY"

# -------------------------------------
# 6. 重籤第三方app Frameworks下已存在的動態庫
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
#遍歷出所有動態庫的路徑
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do
echo "🍺🍺🍺🍺🍺🍺FRAMEWORK : $FRAMEWORK"
#簽名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"


done
fi

yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/InjectSDK.framework/InjectSDK"

仔細觀察腳本配置,腳本的內容其實很簡單:

  1. 解壓ipa
  2. 修改info.plist中的bundle id
  3. 增加可執行文件權限
  4. 遍歷frameworks,進行重簽名
  5. 修改Pach-O文件

這裏重點提一下第2和第5步:

步驟2:需要修改info.plist中的bundle id,這個其實很好理解,因爲你拿來打包的證書不是原始ipa開發團隊的(比如你重簽名微信app,但是你不可能有微信的開發者證書,除非你自己重籤你自己的應用),證書變了,bundle id自然必須改變(蘋果的規則:你無法使用其他團隊已經註冊了的bundle id)。記住這條規則,這一特性爲之後我們講解反重籤和反動態注入埋了一個伏筆。

步驟5:這裏用到了yololib(一個修改Math-O文件的工具:github地址:https://github.com/KJCracks/yololib)。InjectSDK.framework是我們自己的注入framework,那麼前面的文件路徑是怎麼確定的?

我們找到項目Products文件夾中我們生成的HelloInject.app,右鍵點擊包的內容:

HelloInject.app其實就是我們重簽名後的Hello.app,看到Frameworks文件夾的內容應該就能明白:重籤腳本會把我們的framework添加到Frameworks文件夾中,工具yololib將這個路徑下framework中的可執行文件添加到Math-O文件中。

項目配置

修改項目配置,在打包時運行腳本:

 完成後使用真機運行項目,項目顯示的界面是我們想要動態注入目標的(Hello.ipa),仔細觀察輸出的內容:

這裏顯示了我們之前在注入的framework中添加的代碼。說明注入成功!

後記

剛纔留了一個疑問,爲什麼注入的代碼要寫在+ (void)load中?

首先,雖然我們成功在ipa中注入了我們自己的庫,但是我們無法修改項目的源代碼。因此,用什麼方法讓項目運行我們注入的庫中的代碼呢?

瞭解過Class的load方法的同學應該能想到解決方案,load方法的特點是:只要Class加載,就會運行。因此,無需修改源碼去調用我們的代碼,這以特性非常合適作爲我們動態注入的入口。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章