iOS逆向學習之六(Theos實戰演練)
Theos
如果要去掉某個介面上的某個UIView,我們可以通過Cycript獲取到UIView對應的記憶體地址,通過執行[#記憶體地址 removeFromSuperview]命令就可以去掉UIView,但是這種方式僅僅是去掉了記憶體中的UIView,下一次再次進入此頁面的時候,又會重新載入UIView。顯然,這不是我們想要的結果,那麼怎麼永久的去掉UIView呢?這就要使用到了hook,通過hook UIView的指定方法改變成我們自己寫的方法,然後將我們自己的程式碼注入到App中,就能實現永久去除UIView的效果。
如何進入hook操作呢?這就要使用到Theos,通過Thoes就可以建立tweak專案,新增需要hook的類以及方法,然後將tweak專案進行編譯、打包成deb外掛,最終安裝到iPhone上。
簽名工具ldid安裝
- ldid需要通過brew安裝,如果mac上沒有安裝brew,可以通過以下命令安裝HomeBrew
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
複製程式碼
- 安裝完brew之後,通過brew安裝ldid
brew install ldid
複製程式碼
修改環境變數
Theos官方建議我們將Theos克隆到~/theos目錄下,而且~/theos路徑之後需要多次使用,所以將~/theos配置為環境變數
- 首先,進入cd ~進入根目錄,在根目錄下有訪問.bash_profile檔案
vim ~/.bash_profile
複製程式碼
- 在.bash_profile檔案最後加入下面兩行
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
複製程式碼
此處將~/theos/bin配置進PTAH中,PATH通過:來配置多個路徑,配置好Path之後,在終端輸入某一個指令時,會到PATH裡的所有目錄下去尋找可執行檔案。注意:此處的:$PATH一定不能遺漏,否則PATH會被THEOS的目錄覆蓋。
- 配置完成之後,在終端執行source命令,使.bash_profile檔案的配置立即生效
source ~/bash_profile
複製程式碼
- 此時我們通過$PATH來打印出PATH資訊,就可以看到THEOS目錄已經新增到了PATH中
下載Theos
通過以下命令將Theos下載到上面配置的$THEOS目錄中
git clone --recursive https://github.com/theos/theos.git $THEOS
複製程式碼
--recursive表示遞迴下載,因為Theos會依賴其它庫,使用此命令可以遞迴的將所有依賴的庫都下載下來
tweak專案
建立tweak專案
- cd到存放專案的目錄,執行nic.pl指令,選擇序號10,對應iphone/tweak
cd ~/Desktop/tweak
nic.pl
複製程式碼
- 填寫專案所需要的資訊
#專案名稱
Project Name
#專案ID,按規則填寫即可
Package Name
#作者名,直接按回車使用預設名
Author/Maintainer Name
#需要hook的App的Bundle Identifier,可以使用Cycript檢視
[iphone/tweak] MobileSubstrate Bundle filter
#直接回車,使用預設配置就可以
[iphone/tweak] List of applications to terminate upon installation
複製程式碼
至此,tweak專案建立完畢
tweak專案配置
- 編輯Makefile檔案,需要在Makefile檔案中增加以下環境變數,指明通過哪個IP和埠號訪問手機,由於之前使用的是USB的方式訪問,所以IP寫本機地址即可
export THEOS_DEVICE_IP=127.0.0.1 #本機IP地址
export THEOS_DEVICE_PORT=10088 #本機埠號
INSTALL_TARGET_PROCESSES = SpringBoard
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = test_tweak
test_tweak_FILES = Tweak.x
test_tweak_CFLAGS = -fobjc-arc
include $(THEOS_MAKE_PATH)/tweak.mk
複製程式碼
- 上述方式配置的環境變數只能在當前的tweak專案中生效,以後如果有新的tweak專案,就需要重新配置。如果不希望每個專案都要重新編寫IP和埠號的環境變數,可以將這兩個變數配置到.bash_profile中
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
export THEOS_DEVICE_IP=127.0.0.1
export THEOS_DEVICE_PORT=10088
複製程式碼
配置完成後,使用source讓配置生效
source ~/.bash_profile
複製程式碼
tweak專案編寫程式碼
程式碼編寫
tweak專案的攔截程式碼編寫在Tweak.x檔案中,程式碼如下
%hook ClassName
// Hooking a class method
+ (id)sharedInstance {
return %orig;
}
// Hooking an instance method with an argument.
- (void)messageName:(int)argument {
%log; // Write a message about this call,including its class,name and arguments,to the system log.
%orig; // Call through to the original function with its original arguments.
%orig(nil); // Call through to the original function with a custom argument.
// If you use %orig(),you MUST supply all arguments (except for self and _cmd,the automatically generated ones.)
}
// Hooking an instance method with no arguments.
- (id)noArguments {
%log;
id awesome = %orig;
[awesome doSomethingElse];
return awesome;
}
%end
複製程式碼
語法簡介
- tweak使用的Logos語法簡介,可以點選Logos官網檢視完整的語法介紹。
%hook、%end :#表示hook一個類的開始和結束
%log :#列印方法呼叫詳情,可以通過Xcode -> Window -> Devices and Simulators
HBDebugLog : #和NSLog類似
%new :#新增一個新的方法
%c(className) : #生成一個Class物件,比如%c(NSObject),類似於NSStringFromClass()、objc_getClass()
%orig :#呼叫原先的方法邏輯
%ctor :#載入動態庫的時候呼叫
%dtor :#在程式退出時呼叫
複製程式碼
- logify.pl
此工具可以將一個頭檔案快速轉換成已經包含列印資訊的x檔案,這可以為我們節省很多事,比如我們要監聽一個頁面所有方法的呼叫,使用此工具就能自動將所有的OC方法轉換成Logos方法,並且在方法中為我們加上對應的log方法和返回值。具體轉換指令如下:
logify.pl xx.h > xx.xm
複製程式碼
但是使用這種自動轉換的方法轉換之後的程式碼在編譯時會遇到很多問題。解決方法如下
- 刪除__weak
- 刪除inout
- 刪除掉協議,或者使用@protocol XXTestDelegate宣告一下協議
- 刪除掉- (void).cxx_destruct { %log; %orig; }方法
- 刪除HBLogDebug(@" = 0x%x",(unsigned int)r)
- 替換類名為void,例如將XLPerson * 替換成void *,獲取在頭部宣告一下類名@class Person
- 資原始檔
如果有額外的資原始檔,例如圖片等等,放在專案的layout資料夾中,對應手機的根目錄 /,通常開發中可以將圖片存放在以下目錄中/Library/PreferenceLoader/preferences/和/Library/Caches下,具體的做法是在tweak專案的layout資料夾中依次建立Library和Caches目錄,將圖片存放到此目錄下。載入圖片的時候使用全路徑即可
編譯->打包->安裝
#對專案進行編譯
make
#打包成deb檔案
make package
#安裝,安裝過程中會預設重啟SpringBoard
make install
#以上命令也可寫成如下格式
make && make package && make install
複製程式碼
如果之前已經編譯過,需要再次重新編譯的話,執行make clean即可
去除hook程式
- 如果我們不想再hook別人的程式了,可以通過iFunBox,在~/Library/MobileSubstrate/DynamicLibraries/目錄下找到我們的deb外掛生成的.dylib和.plist檔案,一般是以我們的tweak專案名命名的。刪除這兩個檔案,然後重啟SpringBoard就可以去除我們的hook外掛。
- 第二種方式是通過Cydia來解除安裝,開啟Cydia,在已安裝外掛中找到我們剛剛安裝好的外掛,點選解除安裝,完成之後重啟SpringBoard即可
tweak執行原理
- 首先,當我們建立好tweak專案,並且編寫好hook程式碼後,執行make操作,在tweak專案目錄下會生成一個隱藏的資料夾.theos,在.theos/obj/debug/目錄下會生成動態庫.dylib檔案
- 執行make package時,專案目錄下的plist檔案和第一步生成的.dylib檔案打包成對應的deb檔案,存放在packages目錄下
- 執行make install時,首先會根據之前配置的THEOS_DEVICE_IP和THEOS_DEVICE_PORT兩個變數去遠端連線iPhone,然後通過iPhone上的Cydia來安裝對應的deb外掛
- 在App啟動之後,會同時將dylib載入到記憶體中,App中如果訪問被我們hook的類中的方法,會直接執行dylib中的方法。
make package預設是debug模式,如果要釋出release版本的包,執行make package debug=0即可
tweak多檔案開發
在tweak專案開發中,如果我們需要Hook的方法過多,所有的hook方法都寫在同一個類裡,明顯不是一種好的方式。這個時候就需要多檔案開發。tweak支援多檔案開發
- 首先在專案目錄下增加src資料夾,然後將Tweak.x檔案移到src目錄下,這個時候執行make指令會提示
==> Error: File Tweak.x does not exist.
make[1]: *** [before-test_wechat-all] Error 1
make: *** [test_wechat.all.tweak.variables] Error 2
複製程式碼
- tweak在編譯時,會到Makefile中尋找可編譯的檔案路徑,因為我們改變的Tweak.x的路徑,相應的就要修改Makefile檔案下的test_wechat_FILES,如下
INSTALL_TARGET_PROCESSES = SpringBoard
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = test_wechat
test_wechat_FILES = src/Tweak.x
test_wechat_CFLAGS = -fobjc-arc
include $(THEOS_MAKE_PATH)/tweak.mk
複製程式碼
- tweak支援OC類,想tweak專案中的src目錄下匯入Person類,在Tweak.x中#import "Person.h",然後執行make編譯,會出現以下錯誤
ndefined symbols for architecture armv7:
"_OBJC_CLASS_$_Person",referenced from:
objc-class-ref in Tweak.x.01c0bdf0.o
ld: symbol(s) not found for architecture armv7
複製程式碼
- 修改Makefile中的test_wechat_FILES,增加Person.m的路徑
INSTALL_TARGET_PROCESSES = SpringBoard
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = test_wechat
test_wechat_FILES = src/Tweak.x src/*.m
test_wechat_CFLAGS = -fobjc-arc
include $(THEOS_MAKE_PATH)/tweak.mk
複製程式碼
注意兩個路徑之間需要用空格分隔,Tweak支援萬用字元,可以使用*.m指定目錄下的所有.m檔案
實戰一、去掉喜馬拉雅音訊播放介面的廣告
- 如果iPhone上的喜馬拉雅App被加密的話,首先需要通過Clutch或者dumpdecrypted工具進行解密。得到解密後的可執行檔案。
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/14C4F899-BD7B-41A4-BC1A-61892E7B943B/ting.app/ting
複製程式碼
- 使用class-dump匯出喜馬拉雅可執行檔案中所有標頭檔案備用
class-dump -H ~/Desktop/ting -o Headers
複製程式碼
- 使用Cycript獲取喜馬拉雅App的bundle Identifier,後續建立tweak專案時需要用到
cy# [NSBundle mainBundle].bundleIdentifier
@"com.gemd.iting"
cy#
複製程式碼
- 使用Reveal定位到廣告檢視的具體位置
- 獲取到廣告頁面的名稱為XMSoundPatchImageView和XMAdAnimationViewThree,檢視標頭檔案,找到XMSoundPatchImageView.h和XMAdAnimationViewThree.h,以下為部分程式碼
@interface XMSoundPatchImageView : UIView <CAAnimationDelegate,XMSoundPatchImageViewProtocol>
- (void)adImageShadowCreateIfNeeded;
- (void)defaultAnimationShowImage:(_Bool)arg1 withAnimation:(_Bool)arg2 andReoportAdShow:(_Bool)arg3 andReportOnce:(_Bool)arg4;
- (void)showImage:(_Bool)arg1 withAnimation:(_Bool)arg2 andReoportAdShow:(_Bool)arg3 andReportOnce:(_Bool)arg4;
- (void)showImage:(_Bool)arg1 withAnimation:(_Bool)arg2 andReoportAdShow:(_Bool)arg3;
- (void)initUI;
- (void)cleanWithAnimation:(_Bool)arg1;
- (void)clean;
- (void)dealloc;
- (id)initWithFrame:(struct CGRect)arg1;
@end
複製程式碼
XMAdAnimationViewThree的初始化方法和XMSoundPatchImageView方法相同,喜馬拉雅App存在多種廣告View,這裡只列舉了其中兩種。
可以看到整個View是通過initWithFrame方法進行初始化的。 6. 建立tweak專案,在Tweak.x中編寫攔截程式碼,攔截XMSoundPatchImageView的initWithFrame方法,返回nil
%hook XMSoundPatchImageView
- (id)initWithFrame:(struct CGRect)arg1{
return nil;
}
%end
%hook XMAdAnimationViewThree
- (id)initWithFrame:(struct CGRect)arg1{
return nil;
}
%end
複製程式碼
- 編譯,打包以及安裝
make && make package && make install
複製程式碼
- 執行以上命令之後,iPhone桌面會重啟,再次開啟喜馬拉雅App,點選進入播放介面,就會發現不會再彈出廣告。至此,去除廣告的功能就完成了。
實戰二、去掉iPhone桌面上所有角標提示
其實iPhone的桌面也是一個應用,叫做SpringBoard。那麼怎麼驗證它是一個應用呢?通過Cycript指令
ps -A | grep SpringBpard
複製程式碼
508SC:~ root# ps -A | grep SpringBoard
1365 ?? 0:26.56 /System/Library/CoreServices/SpringBoard.app/SpringBoard
1425 ttys000 0:00.01 grep SpringBoard
508SC:~ root#
複製程式碼
通過以上結果我們可以看出,SpringBoard其實就是一個App,安裝路徑為/System/Library/CoreServices/,既然知道SpringBoard是本質是一個App,那麼,我們就可以為SpringBoard建立一個tweak應用
- 首先,通過iFunBox,找到iPhone的/System/Library/CoreServices/目錄下的可執行檔案SpringBoard,拖拽到Mac上
- 通過otool或者MachOView檢視Mach-O檔案是否被加密,結果發現SpringBoard可執行檔案中,Load Commands中並沒有LC_ENCRYPTION_INFO,這也就說明SpringBoard沒有被加密。
- 通過class-dump匯出SpringBoard所有標頭檔案備用
- 獲取微信的BundleIdentifier為com.tencent.xin
- 因為Reveal只能除錯安裝在SpringBoard上的應用,不能除錯SpringBoard本身,所以想要查詢SpringBoard的UI介面,就要使用Cycript指令。
- 首先連線iPhone,通過Cycript監聽SpringBoard,找到SpringBoard主介面,通過主介面的記憶體地址打印出主介面的subViews
508SC:~ root# cycript -p SpringBoard
cy# MJRootVc ()
#"<SBHomeScreenViewController: 0x1308cc8d0>"
cy# MJSubviews (#0x1308cc8d0)
`<SBHomeScreenView: 0x1308c0aa0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x1308d6ac0>>
| <SBIconContentView: 0x1309f6150; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x1309a3940>>
| | <SBRootFolderView: 0x130b68910; frame = (0 0; 320 568); layer = <CALayer: 0x130b6b230>>
| | | <SBSearchBlurEffectView: 0x131d885c0; variant: static wallpaper; style: LightTintedBlur; frame = (0 0; 320 568); clipsToBounds = YES; alpha = 0; layer = <CALayer: 0x131b94ac0>>
| | | | <_SBFakeBlurView: 0x1330823c0; style: LightTintedBlur; frame = (0 0; 320 568); animations = { AlignFakeWallpaperToLayer-0x1308d4dc0=<CAMatchMoveAnimation: 0x1333904e0>; }; layer = <CALayer: 0x131a6d300>>
......
複製程式碼
- 通過分析主介面的所有subViews,可以找到SBIconView,通過拿到SBIconView的記憶體地址,通過設定hidden,最終確定介面上每個App圖示都是一個SBIconView。
cy# #0x130ec4ff0.hidden = 1
1
cy# #0x130ec4ff0.hidden = 0
0
cy#
複製程式碼
- 通過檢視SBIconView的subViews可以確定SBIconParallaxBadgeView就是我們要找的角標View
- 檢視SpringBoard所有標頭檔案,找到SBIconParallaxBadgeView.h檔案如下
#import "SBIconBadgeView.h"
#import "_UISettingsKeyObserver.h"
@class NSString,SBFParallaxSettings;
@interface SBIconParallaxBadgeView : SBIconBadgeView <_UISettingsKeyObserver>
{
SBFParallaxSettings *_parallaxSettings;
}
- (void)_applyParallaxSettings;
- (void)settings:(id)arg1 changedValueForKey:(id)arg2;
- (void)dealloc;
- (id)init;
// Remaining properties
@property(readonly,copy) NSString *debugDescription;
@property(readonly,copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;
@end
複製程式碼
- 建立tweak專案,在Tweak.x編寫攔截程式碼,攔截SBIconParallaxBadgeView的init方法,返回nil
%hook SBIconParallaxBadgeView
- (id)init{
return nil;
}
%end
複製程式碼
- 編譯,打包以及安裝,等待SpringBoard重啟之後,就可以看到整個桌面上的所有App,都沒有角標了。
make && make package && make install
複製程式碼
實戰三、給微信的“發現介面”增加自動搶紅包、退出微信兩個功能,僅改變UI,不實現具體功能
- 首先通過ps -A指令找到微信的可執行檔案路徑
/var/mobile/Containers/Bundle/Application/048B71C8-42E4-4EE0-8E50-EF262251DE17/WeChat.app/WeChat
複製程式碼
- 使用Clutch或者dumpdecrypted工具進行解密。得到解密後的可執行檔案。如果已解密,則忽略此步驟
優先選擇dumpdecrypted工具,解密速度快,不易出錯
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/048B71C8-42E4-4EE0-8E50-EF262251DE17/WeChat.app/WeChat
複製程式碼
- 在/var/root目錄下找到解密後的可執行檔案,使用class-dump匯出所有標頭檔案備用
class-dump -H ~/Desktop/WeChat -o Headers
複製程式碼
- 使用Cycript獲取微信App的bundle Identifier,後續建立tweak專案時需要用到
cy# [NSBundle mainBundle].bundleIdentifier
@"com.tencent.xin"
cy#
複製程式碼
- 通過Reveal分析微信UI介面,可以得出微信的發現頁面是一個UITableView,類名為MMMainTableView,我們想要在UITableView上增加cell,必須要知道MMMainTableView的dataSource。通過Cycript可以得到MMMainTableView的dataSource就是FindFriendEntryViewController
cy# #0x13d20ae00.dataSource
#"delegate[0x13dadaa00],class[FindFriendEntryViewController]"
cy#
複製程式碼
- 查詢微信的標頭檔案,在FindFriendEntryViewController中找到我們需要hook的幾個方法,如下
- (void)tableView:(id)arg1 didSelectRowAtIndexPath:(id)arg2;
- (id)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
- (double)tableView:(id)arg1 heightForRowAtIndexPath:(id)arg2;
- (long long)tableView:(id)arg1 numberOfRowsInSection:(long long)arg2;
- (long long)numberOfSectionsInTableView:(id)arg1;
複製程式碼
- 建立tweak專案,在Tweak.x中編寫以下hook程式碼
#define XLUserDefault [NSUserDefaults standardUserDefaults]
#define XLAutoSwitchKey @"xl_auto_switch_key"
#define XLFile(path) @"/Library/PreferenceLoader/Preferences/XLWeChat/" #path
/* 對FindFriendEntryViewController及numberOfSectionsInTableView方法做前向宣告。
* 否則無法呼叫[self numberOfSectionsInTableView:]方法
*/
@interface FindFriendEntryViewController
- (long long)numberOfSectionsInTableView:(id)tableView;
@end
%hook FindFriendEntryViewController
//當前tableView有幾組
- (long long)numberOfSectionsInTableView:(id)tableView
{
//呼叫微信原始的numberOfSectionsInTableView方法,獲取原始section,在此基礎上+1
return %orig + 1;
}
//當前tableView每組對應多少row
- (long long)tableView:(id)tableView numberOfRowsInSection:(long long)section
{
if (section == [self numberOfSectionsInTableView:tableView] - 1){
return 2;
}
return %orig;
}
//返回每個row對應的cell
- (id)tableView:(id)tableView cellForRowAtIndexPath:(id)indexPath
{
//判斷是否是最後一組
if ([indexPath section] == [self numberOfSectionsInTableView:tableView] - 1){
NSString *cellId = [indexPath row] == 0 ? @"autoCellId" : @"exitCellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellId];
cell.backgroundColor = [UIColor whiteColor];
//設定圖片
cell.imageView.image = [UIImage imageWithContentsOfFile:XLFile(icon.png)];
}
if ([indexPath row] == 0) {
cell.textLabel.text = @"自動搶紅包";
UISwitch *switchView = [[UISwitch alloc] init];
switchView.on = [XLUserDefault boolForKey:XLAutoSwitchKey];
[switchView addTarget:self
action:@selector(xl_switchChange:)
forControlEvents:UIControlEventValueChanged];
cell.accessoryView = switchView;
}else if ([indexPath row] == 1){
cell.textLabel.text = @"退出微信";
}
return cell;
}
return %orig;
}
/*
* 特別注意:在%hook和%end內部所寫的方法,預設是覆蓋當前hook的類中的方法,如果在原類中沒有此方法,會崩潰
* 如果想要在原類中增加新的方法,需要在方法前增加 %new 標識
*/
%new
- (void)xl_switchChange:(UISwitch *)switchView{
[XLUserDefault setBool:switchView.isOn forKey:XLAutoSwitchKey];
[XLUserDefault synchronize];
}
//cell的高度
- (double)tableView:(id)tableView heightForRowAtIndexPath:(id)indexPath
{
//此處判斷當前是否是我們新加的一組,如果是,返回cell的高度為44
if ([indexPath section] == [self numberOfSectionsInTableView:tableView] - 1)
{
return 44;
}
//如果不是最後一組,返回微信自己定義的高度
return %orig;
}
//cell點選事件
- (void)tableView:(id)tableView didSelectRowAtIndexPath:(id)indexPath
{
if ([indexPath section] == [self numberOfSectionsInTableView:tableView] - 1){
if ([indexPath row] == 0){
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}else if([indexPath row] == 1){
//呼叫此函式,直接退出App
abort();
}
}else{
%orig;
}
}
%end
複製程式碼
注意:
- 在Theos中,使用%orig來呼叫原先的方法邏輯。所以我們在編寫hook程式碼時,會在原來方法的基礎上進行編寫。
- 在%hook和%end內部所寫的方法,預設是覆蓋當前hook的類中的方法,如果在原類中沒有此方法,會崩潰。如果想要在原類中增加新的方法,需要在方法前增加 %new 標識
- 在巨集定義中,#path代表在path前後拼接上"",得到"path",使用如下呼叫方式XLFile(icon.png)即可
- 在make編譯時可能會出現以下錯誤,原因是self在此處會被認為是id型別,而numberOfSectionsInTableView方法屬於FindFriendEntryViewController的方法,如果不對FindFriendEntryViewController進行前向宣告,是無法呼叫numberOfSectionsInTableView的。具體前向宣告程式碼如上
Tweak.x:37:30: error: receiver type 'FindFriendEntryViewController' for instance
message is a forward declaration
if ([indexPath section] == [self numberOfSectionsInTableView:tab...
^~~~
Tweak.x:34:8: note: forward declaration of class here
@class FindFriendEntryViewController;
複製程式碼
- tweak專案中資原始檔如何配置?
- 首先需要在我們的tweak專案下建立一個layout資料夾。layout目錄對應iPhone上的根目錄,如果我們將圖片存放在layout目錄下,那麼打包tweak專案時,會將圖片存放到iPhone的根目錄下。
- 通常我們開發外掛的資原始檔都存放在iPhone的/Library/PreferenceLoader/Preferences/目錄下,所以我們需要在layout目錄中依次建立Library、PreferenceLoader、Preferences目錄,為防止資源重複,在Preferences目錄下再建立一個XLWeChat目錄,將我們的資原始檔存放在此目錄。
- 在OC中我們一般通過[UIImage imageNamed:]方法載入圖片,但是在tweak專案中,需要使用[UIImage imageWithContentsOfFile:]方法載入圖片,傳入圖片的全路徑,如下
[UIImage imageWithContentsOfFile:@"/Library/PreferenceLoader/Preferences/XLWeChat/icon.png"]
複製程式碼
- 最後通過編譯、打包、安裝,等待SpringBoard重啟,開啟微信就可以看到在發現頁面增加了兩個功能,如下:
實戰四、去除騰訊視訊客戶端視訊播放時的廣告
- 獲取騰訊視訊App的Bundle Identifier為***com.tencent.live4iphone***
- 獲取騰訊視訊客戶端的可執行檔案路徑,通過dumpdecrypted工具進行脫殼
- 找到脫殼的可執行檔案,使用class-dump匯出所有標頭檔案
- 通過Reveal找到播放視訊時的廣告View為***QNBPlayerVideoAdsView***,它所對應的UIViewController為***QNBPlayerVideoAdsViewController***
- 查詢標頭檔案,找到***QNBPlayerVideoAdsViewController.h***,在新版本的騰訊視訊的可執行檔案中,可能找不到***QNBPlayerVideoAdsViewController***對應的標頭檔案,我這邊用的是舊版本的ipa安裝包。
- 找到***QNBPlayerVideoAdsViewController.h***裡的初始化方法
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withPageViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withAddToParenViewControllerNow:(_Bool)arg4
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3
複製程式碼
- 建立tweak專案,編寫tweak程式碼,替換***QNBPlayerVideoAdsViewController.h***的所有初始化方法,返回nil
- 執行編譯安裝後發現還是存在廣告,繼續使用Reveal進行檢視,找到***QNBPlayerImageAdsViewController***和***TADVideoAdController***,繼續hook這兩個類的初始化方法,最後發現廣告沒有了,完整的hook程式碼如下
%hook QNBPlayerVideoAdsViewController
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withPageViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withAddToParenViewControllerNow:(_Bool)arg4{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3{
return nil;
}
%end
%hook QNBPlayerImageAdsViewController
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withPageViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4 withAddToParenViewControllerNow:(_Bool)arg5{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withAddToParenViewControllerNow:(_Bool)arg4{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3 withParentEventViewController:(id)arg4{
return nil;
}
- (id)initWithEventProxy:(id)arg1 withPlayerInfo:(id)arg2 withParentViewController:(id)arg3{
return nil;
}
%end
%hook TADVideoAdController
- (id)init{
return nil;
}
%end
複製程式碼
由於我這裡用的是老版的騰訊視訊ipa,所以可以成功去除廣告,新版的騰訊視訊改變了廣告的邏輯,所以無法對新版的騰訊視訊進行去廣告的操作。