一、前言
二、注入思路
三、動態庫注入實現
四、分析實現按鈕監聽
五、實戰修改微信步數
一、前言
在文章《應用簽名-腳本簽名》中介紹了如何在真機上運行破殼應用(抖音、微信、支付寶等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
(工程名隨意):
2、插入簽名腳本、獲取破殼ipa
包,放入app
文件中
-
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
文件
在動態庫文件下創建Insert類:
- 創建動態庫文件,用來做方法替換
在
runtime
中通常會使用+load
方法,該方法會在編譯期被調用,因此在應用運行前,對應用內部方法動動手腳。下面先在load
方法中插入打印代碼,再向ipa
包中插入動態庫,看看是否能夠插入到新包中。
代碼:
@implementation Insert
+ (void)load {
NSLog(@"插入成功");
}
@end
5、引入注入工具yololib
下載 yololib 并編譯(或直接拿到可執行文件),將可執行文件復制到工程中的tool
目錄下,注意給執行權限。
在
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
完整操作如下:
準備工作完成,運行工程:
打印“插入成功”,說明動態庫已成功插入到ipa
中,用 MachOView 打開.app中WeChat通用二進制文件,查看新的布局如下:
在Load commands
中已經有了動態庫的加載指令,接下來就對原登錄按鈕的點擊方法做替換監聽。
四、分析實現按鈕監聽
替換原登錄方法,需要我們知道對應的方法名稱。怎么查找?如下:
- 通過層級關系立刻定位到了登錄方法信息,
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_onFirstViewLogin
被WCAccountLoginControlLogic
的實例調用,因此self
指的是WCAccountLoginControlLogic
的實例
運行工程,點擊登錄按鈕,如下:
第一行打印了“我來了”,說明,我們監聽到了按鈕的點擊事件,但后面出現崩潰,此處很好理解,在原控制器中并沒有找到對應的選擇器。回憶之前的方法替換,我們是在對應類的分類中去替換,方法在編譯時會加入到該類的方法列表中,而此處并不是原類的分類。
那么如何解決呢,這里有三種方法:
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")];
}
- 添加方法并替換方法的
imp
; - 在
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);
}
-
class_getMethodImplementation:
獲取原有方法對應的imp
; -
method_setImplementation:
給原有類的方法設置新的imp
指向; -
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);
}
- 保留原有方法的實現;
- 替換原方法的實現,當觸發按鈕,將調用該函數;
- 在新函數中,執行原方法實現。
以上方法都能完美解決找不到實例方法的問題,點擊登錄,后打印并跳轉到登錄頁面。
注意:方法與實現是兩個不同的概念,方法是SEL
(選擇器),實現是IMP
具體的函數。方法(SEL)指向實現(IMP)。
小結:
OC
是一種動態類型語言,在運行時來確定具體的數據類型,利用runtime
原理,通過消息發送msg_send(id,SEL)
,找到對應的C
函數實現,sel
和imp
相當于一種映射關系,基于這一層的連接,給予我們hook
的機會,通過改變這一對應關系來改變程序的執行順序。
五、實戰修改微信步數
修改微信步數同上,通過替換方法,在微信上傳步數時,監聽方法,替換上傳數據。
首先介紹一個工具Class-dump
:
該方法利用runtime
特性能夠提取MachO
文件中的信息,并產生原應用對應的所有頭文件信息,通過這些信息,能夠快速定位目標類,目標方法。下載地址:http://stevenygard.com/projects/class-dump
先使用該工具獲取.ipa
中對應的頭文件:
class-dump -H WeChat -o apph
目標WeChat
為.app
包中的通用二進制文件,導出頭文件到apph
文件夾中。如下:
以上就是WX
的所有頭文件信息,有屬性,有方法,看到上面標注的就是前面通過頁面找到的類即方法。
這里可以運行查看微信運動頁面,找到相關的方法或屬性,進行修改。這里就不運行查看了(害怕封號??)。直接定位到修改步數的類:
找到類即屬性名稱,這里就直接替換原有的方法,直接返回相應的步數就行。代碼如下:
+ (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
運行,再手機上啟動應用,登錄賬號并來到微信運動公眾賬號,查看自己的步數,如果步數沒有修改,殺死應用重新進入即可。如下: