動態庫注入

dylib.png

一、前言
二、注入思路
三、動態庫注入實現
四、分析實現按鈕監聽
五、實戰修改微信步數

一、前言

在文章《應用簽名-腳本簽名》中介紹了如何在真機上運行破殼應用(抖音、微信、支付寶等ipa包),來觀察應用視圖的層級結構,方法調用,類名稱等,以便學習參考。應用主要與后臺進行數據交互展示(數據拉取及提交數據),既然能對破殼應用重簽名并在真機上運行,那么能不能修改應用數據和頁面展示呢,如監聽微信登錄按鈕,修改微信步數,下面就這兩個功能拓展一下思路。

二、注入思路

ipa包中,主要包含了應用簽名、資源文件、Frameworks文件、info.plist配置文件及與ipa包同名的可執行通用文件(這里包含了具體的業務執行指令)。在《Mach-O》中我們對Mach-O文件有一個初步了解,主要有Header、Load commands、Data三部分組成。

Header:包含Mach-O文件的基本信息,字節順序、架構類型、加載指令的數量等;
Load commands:包含區域位置、符號表、動態符號表,加載Mach-O文件時使用這里的數據確定內存分布;
Data:數據段segement,包含具體代碼、常量、類、方法等。

如果要向已有應用ipa中加入自己的代碼,必須對Mach-O進行修改重組。根據已有開發經驗有思路兩個:

1、修改功能代碼,向Mach-O中的Data部分增刪改數據段;
2、另一個是添加動態庫,利用runtime對原始代碼進行方法替換,數據、UI的修改。

第一種方案需要分析Mach-O對應的匯編代碼,來修改功能,操作較繁瑣;第二種方案,需要我們向ipa包下的Frameworks中添加寫好的動態庫,并向Load commands添加動態庫加載指令,同時還需要修改Header中對應的基本信息,相對于第一種不用做匯編指令分析了。

以上只是個人的一些思考,這里就不去研究具體的注入過程,直接使用一個第三方庫來完成動態庫的注入,先完成微信登錄按鈕的監聽,以后再對具體的操作進一步學習研究。

第三方動態庫注入工具:yololib

三、動態庫注入實現

破殼ipa獲取:
1、通過越獄手機獲取破殼應用;
2、通過PP助手獲取越獄應用。

《應用簽名-腳本簽名》中實現了對破殼應用的重簽名,并運行在真機上可供調試。

1、創建新工程
常規創建,工程名InsertCode(工程名隨意):

project.png

2、插入簽名腳本、獲取破殼ipa包,放入app文件中

file.png
  • app內的ipa包不建議導入工程,放在文件中即可
  • SignApp.sh腳本放在tool文件下

SignApp.sh腳本如下:

##!/bin/sh
#
##  SignApp.sh
##  inserCode
##
##  Created by hibo on 2019/10/15.
##  Copyright ? 2019 hibo. All rights reserved.
#
##定義目錄路徑 ${SRCROOT}為當前工程的根目錄
#
#目標文件路徑
BUILD_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
#Info文件路徑
TARGET_INFO_PLIST=$BUILD_APP_PATH/Info.plist
#定義臨時目錄變量,存放解.ipa后產生的臨時文件
TEMP_PATH="${SRCROOT}/temp"
#tool路徑
TOOL_PATH="${SRCROOT}/tool"
#定義APP資源目錄變量,存放要重簽名的APP
APP_PATH="${SRCROOT}/app"
#定義ipa包路徑
IPA_PATH="${APP_PATH}/*.ipa"


#移除臨時文件,并重新創建文件夾
rm -rf "${TEMP_PATH}"
mkdir -p "${TEMP_PATH}"



###########1、解壓ipa到指定的文件下###########
unzip -o "$IPA_PATH" -d "$TEMP_PATH"
#獲取臨時app路徑
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
echo ".app文件路徑:$TEMP_APP_PATH"

###########2、創建en.plist權限文件###########
#Xcode中會生成權限文件,注意將生成的目標文件中的權限文件移動到TEMP_APP_PATH中
echo "目標代碼路徑:$BUILD_APP_PATH"
cp -rf "$BUILD_APP_PATH/embedded.mobileprovision" "$TEMP_APP_PATH/"

###########3、修改應用info.plist中的BundleId、displayName###########
#設置 "Set :KEY Value" "Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TEMP_APP_PATH/Info.plist"
#獲取名稱并設置到目標文件中的Info.plist文件中
TARGET_DISPLAY_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleDisplayName" "$TARGET_INFO_PLIST")
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $TARGET_DISPLAY_NAME" "$TEMP_APP_PATH/Info.plist"
echo "plist路徑:$TARGET_INFO_PLIST"
echo "顯示名稱:$TARGET_DISPLAY_NAME"

if [[ "$TARGET_DISPLAY_NAME" != "" ]]; then
    for file in `ls "$TEMP_APP_PATH"`;
    do
        extension="${file#*.}"
        if [[ -d "$TEMP_APP_PATH/$file" ]]; then
            if [[ "$extension" == "lproj" ]]; then
                if [[ -f "$TEMP_APP_PATH/$file/InfoPlist.strings" ]];then
                    /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $TARGET_DISPLAY_NAME" "$TEMP_APP_PATH/$file/InfoPlist.strings"
                fi
            fi
        fi
    done
fi


###########4、刪除擴展應用及插件###########
echo "Removing PlugIns and Watch"
rm -rf "$TEMP_APP_PATH/PlugIns"
rm -rf "$TEMP_APP_PATH/Watch"
#rm -rf "$TEMP_APP_PATH/com.apple.WatchPlaceholder"


###########5、給可執行文件執行權限###########
#獲取可執行文件路徑
APP_BINARY=`plutil -convert xml1 -o - $TEMP_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#給可執行文件權限
chmod +x "$TEMP_APP_PATH/$APP_BINARY"


###########6、重新簽名Frameworks下的所有動態庫###########
#獲取動態庫路徑
APP_FRAMEWORKS_PATH="$TEMP_APP_PATH/Frameworks"
#判斷文件是否存在
if [ -d "$APP_FRAMEWORKS_PATH" ]
then
#遍歷所有動態庫
for FRAMEWORK in "$APP_FRAMEWORKS_PATH/"*
do
echo "framework: $FRAMEWORK"
#對動態庫簽名 $EXPANDED_CODE_SIGN_IDENTITY xcode上的證書
/usr/bin/codesign -fs "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
echo "EXPANDED_CODE_SIGN_IDENTITY:$EXPANDED_CODE_SIGN_IDENTITY  FRAMEWORK:$FRAMEWORK"
done
fi

###########7、將修改后的.app移動到xcode對應的Products下###########
#BUILT_PRODUCTS_DIR xcode生成的.app包路徑
#TARGET_NAME 應用名稱
rm -rf "$BUILD_APP_PATH"
mkdir -p "$BUILD_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$BUILD_APP_PATH/"

###########8、注入到可執行文件中###########
if [ -d "$BUILT_PRODUCTS_DIR/HBHook.framework" ]
then
${SRCROOT}/tool/yololib "$BUILD_APP_PATH/$APP_BINARY" "Frameworks/HBHook.framework/HBHook"
else
echo "沒有該文件"
fi

3、真機運行,執行要查看的ipa包

選擇證書,在真機上運行,ipa會被安裝至手機上,注意這里的ipa必須是破殼的,并具有相同架構配置的ipa否則安裝失敗。

4、創建Frameworks文件

framework.png

在動態庫文件下創建Insert類:

create.png
  • 創建動態庫文件,用來做方法替換

runtime中通常會使用+load方法,該方法會在編譯期被調用,因此在應用運行前,對應用內部方法動動手腳。下面先在load方法中插入打印代碼,再向ipa包中插入動態庫,看看是否能夠插入到新包中。

代碼:

@implementation Insert
+ (void)load {
    NSLog(@"插入成功");
}
@end

5、引入注入工具yololib

下載 yololib 并編譯(或直接拿到可執行文件),將可執行文件復制到工程中的tool目錄下,注意給執行權限。

yololib.png

SignApp.sh腳本最后一行插入注入指令:

#注入
if [ -d "$BUILT_PRODUCTS_DIR/HBHook.framework" ]
then
${SRCROOT}/tool/yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HBHook.framework/HBHook"
else
echo "沒有該文件"
fi

完整操作如下:

yololib.png

準備工作完成,運行工程:

insert.png

打印“插入成功”,說明動態庫已成功插入到ipa中,用 MachOView 打開.app中WeChat通用二進制文件,查看新的布局如下:

MachO.png

Load commands中已經有了動態庫的加載指令,接下來就對原登錄按鈕的點擊方法做替換監聽。

四、分析實現按鈕監聽

替換原登錄方法,需要我們知道對應的方法名稱。怎么查找?如下:

view.png
  • 通過層級關系立刻定位到了登錄方法信息,Action onFirstViewLogin,所屬類WCAccountLoginControlLogic

瞬間感覺微信很友好??!!!

那么開始編寫方法替換代碼:

#import "Insert.h"
#import <objc/message.h>
@implementation Insert
+ (void)load {
    NSLog(@"插入成功");
    Method old_method = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    Method new_method = class_getInstanceMethod(self, sel_registerName("my_onFirstViewLogin"));
    method_exchangeImplementations(old_method, new_method);
}
-(void)my_onFirstViewLogin {
    NSLog(@"我來了");
    [self my_onFirstViewLogin];
}
@end
  • 交換了交換了原始方法的imp指向
  • 通過runtime函數來獲取類及類方法
  • my_onFirstViewLoginWCAccountLoginControlLogic的實例調用,因此self指的是WCAccountLoginControlLogic的實例

運行工程,點擊登錄按鈕,如下:

login.png

第一行打印了“我來了”,說明,我們監聽到了按鈕的點擊事件,但后面出現崩潰,此處很好理解,在原控制器中并沒有找到對應的選擇器。回憶之前的方法替換,我們是在對應類的分類中去替換,方法在編譯時會加入到該類的方法列表中,而此處并不是原類的分類。

那么如何解決呢,這里有三種方法:

1、給原類添加方法

+ (void)load {
    NSLog(@"插入成功");
    Method old_method = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    BOOL result = class_addMethod(objc_getClass("WCAccountLoginControlLogic"),
                    sel_registerName("new_method"), (IMP)new_method, "v@:");//添加新的方法
    NSLog(@"%@",result?@"添加成功":@"添加失敗");
    method_exchangeImplementations(old_method, class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"),sel_registerName("new_method")));
}
void new_method(id self, SEL _cmd){
    NSLog(@"我來了");
    [self performSelector:sel_registerName("new_method")];
}
  1. 添加方法并替換方法的imp
  2. imp實現中通過performSelector來調用原有類添加的方法,從而找到原方法對應的實現。
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
  • cls:為哪個類添加方法
  • name:設置方法名稱
  • imp:設置方法對應的imp,此處imp設置在當前類,以便調用
  • types:定義方法類型

2、設置原方法實現

+ (void)load {
    NSLog(@"插入成功");
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin")), (IMP)new_method);
}
IMP (*old_imp)(id self,SEL _cmd);
void new_method(id self, SEL _cmd){
    NSLog(@"我來了");
     old_imp(self,_cmd);
}
  1. class_getMethodImplementation:獲取原有方法對應的imp
  2. method_setImplementation:給原有類的方法設置新的imp指向;
  3. old_imp(self,_cmd):執行保留原類方法實現繼續執行原有方法內部指令。

3、 替換原方法實現

+ (void)load {
    NSLog(@"插入成功");
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    class_replaceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"), (IMP)new_method, "v@:");
}
IMP (*old_imp)(id self,SEL _cmd);
void new_method(id self, SEL _cmd){
    NSLog(@"我來了");
     old_imp(self,_cmd);
}
  1. 保留原有方法的實現;
  2. 替換原方法的實現,當觸發按鈕,將調用該函數;
  3. 在新函數中,執行原方法實現。

以上方法都能完美解決找不到實例方法的問題,點擊登錄,后打印并跳轉到登錄頁面。

注意:方法與實現是兩個不同的概念,方法是SEL(選擇器),實現是IMP具體的函數。方法(SEL)指向實現(IMP)。

小結:

OC是一種動態類型語言,在運行時來確定具體的數據類型,利用runtime原理,通過消息發送msg_send(id,SEL),找到對應的C函數實現,selimp相當于一種映射關系,基于這一層的連接,給予我們hook的機會,通過改變這一對應關系來改變程序的執行順序。

五、實戰修改微信步數

修改微信步數同上,通過替換方法,在微信上傳步數時,監聽方法,替換上傳數據。

首先介紹一個工具Class-dump:

該方法利用runtime特性能夠提取MachO文件中的信息,并產生原應用對應的所有頭文件信息,通過這些信息,能夠快速定位目標類,目標方法。下載地址:http://stevenygard.com/projects/class-dump

先使用該工具獲取.ipa中對應的頭文件:

class-dump -H WeChat -o apph

目標WeChat.app包中的通用二進制文件,導出頭文件到apph文件夾中。如下:

login.png

以上就是WX的所有頭文件信息,有屬性,有方法,看到上面標注的就是前面通過頁面找到的類即方法。
這里可以運行查看微信運動頁面,找到相關的方法或屬性,進行修改。這里就不運行查看了(害怕封號??)。直接定位到修改步數的類:

modify.png

找到類即屬性名稱,這里就直接替換原有的方法,直接返回相應的步數就行。代碼如下:

+ (void)load {
    NSLog(@"插入成功");
    [self modifyWxStep];
}
+(void)modifyWxStep{
    //修改微信步數
    Class class = objc_getClass("WCDeviceStepObject");
    SEL select = sel_registerName("m7StepCount");
    
    Method method = class_getInstanceMethod(class, select);
    const char *typeEncoding = method_getTypeEncoding(method);
    NSLog(@"typeEncoding:%s",typeEncoding);
    class_replaceMethod(class, select, (IMP)my_m7StepCount, typeEncoding);
}
int my_m7StepCount(id self, SEL _cmd){
    return 56382;
}

安裝后,停止xcode運行,再手機上啟動應用,登錄賬號并來到微信運動公眾賬號,查看自己的步數,如果步數沒有修改,殺死應用重新進入即可。如下:

step.png

demo:https://github.com/yahibo/InsertCode

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容