若是要去掉某個界面上的某個UIView,咱們能夠經過Cycript獲取到UIView對應的內存地址,經過執行[#內存地址 removeFromSuperview]命令就能夠去掉UIView,可是這種方式僅僅是去掉了內存中的UIView,下一次再次進入此頁面的時候,又會從新加載UIView。顯然,這不是咱們想要的結果,那麼怎麼永久的去掉UIView呢?這就要使用到了hook,經過hook UIView的指定方法改變成咱們本身寫的方法,而後將咱們本身的代碼注入到App中,就能實現永久去除UIView的效果。php
如何進入hook操做呢?這就要使用到Theos,經過Thoes就能夠建立tweak項目,添加須要hook的類以及方法,而後將tweak項目進行編譯、打包成deb插件,最終安裝到iPhone上。git
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
複製代碼
brew install ldid
複製代碼
Theos官方建議咱們將Theos克隆到~/theos目錄下,並且~/theos路徑以後須要屢次使用,因此將~/theos配置爲環境變量github
vim ~/.bash_profile
複製代碼
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
複製代碼
此處將~/theos/bin配置進PTAH中,PATH經過:來配置多個路徑,配置好Path以後,在終端輸入某一個指令時,會到PATH裏的全部目錄下去尋找可執行文件。注意:此處的:$PATH必定不能遺漏,不然PATH會被THEOS的目錄覆蓋。vim
source ~/bash_profile
複製代碼
經過如下命令將Theos下載到上面配置的$THEOS目錄中ruby
git clone --recursive https://github.com/theos/theos.git $THEOS
複製代碼
--recursive表示遞歸下載,由於Theos會依賴其它庫,使用此命令能夠遞歸的將全部依賴的庫都下載下來bash
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項目建立完畢微信
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
複製代碼
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
export THEOS_DEVICE_IP=127.0.0.1
export THEOS_DEVICE_PORT=10088
複製代碼
配置完成後,使用source讓配置生效app
source ~/.bash_profile
複製代碼
tweak項目的攔截代碼編寫在Tweak.x文件中,代碼以下curl
%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
複製代碼
%hook、%end :#表示hook一個類的開始和結束
%log :#打印方法調用詳情,能夠經過Xcode -> Window -> Devices and Simulators
HBDebugLog : #和NSLog相似
%new :#添加一個新的方法
%c(className) : #生成一個Class對象,好比%c(NSObject),相似於NSStringFromClass()、objc_getClass()
%orig :#調用原先的方法邏輯
%ctor :#加載動態庫的時候調用
%dtor :#在程序退出時調用
複製代碼
此工具能夠將一個頭文件快速轉換成已經包含打印信息的x文件,這能夠爲咱們節省不少事,好比咱們要監聽一個頁面全部方法的調用,使用此工具就能自動將全部的OC方法轉換成Logos方法,而且在方法中爲咱們加上對應的log方法和返回值。具體轉換指令以下:iphone
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便可
make package默認是debug模式,若是要發佈release版本的包,執行make package debug=0便可
在tweak項目開發中,若是咱們須要Hook的方法過多,全部的hook方法都寫在同一個類裏,明顯不是一種好的方式。這個時候就須要多文件開發。tweak支持多文件開發
==> Error: File Tweak.x does not exist.
make[1]: *** [before-test_wechat-all] Error 1
make: *** [test_wechat.all.tweak.variables] Error 2
複製代碼
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
複製代碼
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
複製代碼
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文件
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/14C4F899-BD7B-41A4-BC1A-61892E7B943B/ting.app/ting
複製代碼
class-dump -H ~/Desktop/ting -o Headers
複製代碼
cy# [NSBundle mainBundle].bundleIdentifier
@"com.gemd.iting"
cy#
複製代碼
@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的桌面也是一個應用,叫作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應用
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>>
......
複製代碼
cy# #0x130ec4ff0.hidden = 1
1
cy# #0x130ec4ff0.hidden = 0
0
cy#
複製代碼
#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
複製代碼
%hook SBIconParallaxBadgeView
- (id)init{
return nil;
}
%end
複製代碼
make && make package && make install
複製代碼
/var/mobile/Containers/Bundle/Application/048B71C8-42E4-4EE0-8E50-EF262251DE17/WeChat.app/WeChat
複製代碼
優先選擇dumpdecrypted工具,解密速度快,不易出錯
DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/048B71C8-42E4-4EE0-8E50-EF262251DE17/WeChat.app/WeChat
複製代碼
class-dump -H ~/Desktop/WeChat -o Headers
複製代碼
cy# [NSBundle mainBundle].bundleIdentifier
@"com.tencent.xin"
cy#
複製代碼
cy# #0x13d20ae00.dataSource
#"delegate[0x13dadaa00], class[FindFriendEntryViewController]"
cy#
複製代碼
- (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;
複製代碼
#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;
複製代碼
[UIImage imageWithContentsOfFile:@"/Library/PreferenceLoader/Preferences/XLWeChat/icon.png"]
複製代碼
- (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
複製代碼
%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,因此能夠成功去除廣告,新版的騰訊視頻改變了廣告的邏輯,因此沒法對新版的騰訊視頻進行去廣告的操做。