一、前言
二、注入思路
三、動態庫注入實現
四、分析實現按鈕監聽
五、實戰修改微信步數
一、前言
在文章《應用簽名-腳本簽名》中介紹瞭如何在真機上運行破殼應用(抖音、微信、支付寶等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
包不建議導入工程,放在文件中即可(可能要加入其他應用包)- 具體腳本在《應用簽名-腳本簽名》中查看
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)。
五、實戰修改微信步數
修改微信步數同上,通過替換方法,在微信上傳步數時,監聽方法,替換上傳數據。
首先介紹一個工具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
運行,再手機上啓動應用,登錄賬號並來到微信運動公衆賬號,查看自己的步數,如果步數沒有修改,殺死應用重新進入即可。如下:
完整工程:https://gitee.com/yahibo/InsertCode.git