在利用theos開發一些插件時,咱們常常會用到如下幾個指令:php
%hook 指定須要hook的類名,以%end結尾html
//hook的是SpringBoard這個類裏面的方法 %hook SpringBoard -(void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button"); %orig; //call the original _menuButtonDown } %end
%orig 執行被hook函數的原始代碼,相似於super.method功能ios
%hook ClassName - (void) _menuButtonDown: (id)down { NSlog(@"ss"); //若是去掉%orig,那麼原始函數不會獲得執行。 %orig; } @end
%new 該指令用來給現有的class添加一個新的函數。與Runtime中的class_addMethod相同。spring
%hook SpringBoard //hook內部的代碼 默認都是替換被hook類中函數的實現,因此若是不加%new,theos默認是去類中找namespaceNewMethod這個方法,替換它的方法實現。因此若是咱們是新增的函數而不是更改原函數的內部實現則須要加%new這個指令 %new -(void) namespaceNewMethod { NSlog(@"你好"); } @end
%log 用來打印log的,將信息輸入到syslog中,能夠以%log([(<type>)<expr>,...])
的格式追加其打印信息,以下:架構
%hook SpringBoard -(void) _menuButtonDown :(id)down { %log((NSString * )@"IOSER",(NSString *)@"Debug"); %orig; } @end
%group 該指令用於將%hook
分組,便於代碼管理及按條件初始化分組,必須以%end
結尾:一個%group
能夠包含多個%hook
,全部不屬於某個自定義group的%hook
會被隱式歸類到%group _ungrounped
中,%gruop
的用法以下:app
1 //這段代碼的含義爲在%group iOS7hook中勾住了iOS7Class的iOS7Method,同理在iOS8Class的iOS8Method。而後在%group _ungrouped中勾住SpringBoard類的powerDown函數。 2 3 %group iOS7Hook 4 %hook iOS7Class 5 -(id) iOS7Method 6 { 7 8 id result = %orig; 9 NSlog(@"This class & method only exist in ios 8."); 10 return result; 11 } 12 @end 13 @end // iOS7Hook 14 15 16 %group iOS8Hook 17 %hook iOS8Class 18 -(id) iOS8Method 19 { 20 21 id result = %orig; 22 NSlog(@"This class & method only exist in ios 8."); 23 return result; 24 } 25 @end 26 @end // iOS8Hook 27 28 29 //全部不屬於某個自定義group的%hook會被隱式歸類到%group _ungrounped 因此其實下面powerDown函數實際上是屬於group_ungrounped 組的 30 %hook SpringBoard 31 -(void) powerDown 32 { 33 %orig; 34 } 35 @end 36 37 //%ctor: tweak 的constructor ,完成初始化工做;若是不是顯示定義,theos會自動生成一個一個%ctor並在其中調用%init(_ungrouped)。默認只會自動初始化_ungrouped,不會初始化自定義的group 38 %ctor { 39 //只有調用了%init,對應的%group才能起做用、 40 %init(iOS7Hook); 41 %init(iOS8Hook); 42 43 //默認組 44 %init(_ungrouped); 45 }
%init 該指令用於初始化某個%group
,必須在%hook
或%ctor
內調用,若是帶參數,則初始化指定的group,若是不帶參數,則初始化_ungrouped框架
只有調用了%init
,對應的%group
才能起做用ssh
%ctor { //帶參數的話是初始化自定義的group %init(iOS8); //不帶參數的話是初始化默認的_ungrouped %init; }
%ctor tweak的構造器,用來初始化。若是開發者沒有重寫這個方法,theos會自動生成%ctor
並在其中調用%init(_ungrouped)。
iphone%ctor
通常能夠用來初始化%group
,以及進行MSHookFunction
等操做。
注意:%ctor
不須要以%end
結尾。ide
1 %hook SpringBoard 2 3 -(void) reboot 4 { 5 NSlog(@"你好"); 6 %orig; 7 } 8 %end 9 10 //若是開發者不去重寫這個方法,theos默認是實現了這個方法,並在這個方法裏初始化了_ungrouped 因此默認hook都會生效,可是若是用戶實現了這個方法但卻沒作初始化操做那就回致使hook失效 11 %ctor 12 { 13 // need to call %init explicitly! 14 } 15 //這裏 %hook沒法生效,由於這裏顯示定義了%ctor,卻沒有顯示的調用%init,所以%group(_ungrouped)不起做用。
%c 該指令用來獲取一個類的名稱,相似於objc_getClass。%c([+|-]Class)
%dtor 在程序退出是調用。
tweak工程文件
咱們建立完tweak項目後,會在文件夾內看到如下幾個文件:
control文件:該文件記錄了工程的基本信息,會被打包進deb包中,字段內容以下:
Package: com.leegof.reversedemo Name: ReverseDemo Depends: mobilesubstrate Version: 0.0.1 Architecture: iphoneos-arm Description: An awesome MobileSubstrate tweak! Maintainer: LeeGof Author: LeeGof Section: Tweaks
- Package字段:用於描述這個deb包的名字,採用的命名方式和bundle identifier相似,能夠按需更改;
- Name字段:用於描述這個工程的名字,能夠按需更改;
- Depends字段:用於描述這個deb包的「依賴」。「依賴」指的是這個程序運行的基本條件,能夠填寫固件版本或其餘程序,若是當前iOS不知足「依賴」中所定義的條件,則此tweak沒法正常工做,能夠按需更改。例如:
//表示當前iOS版本必須在6.0以上,且必須安裝MobileSubstrate,才能正常運行這個tweak。 Depends: mobilesubstrate, firmware (>=6.0)
- Version字段:用於描述這個deb包的版本號,能夠按需更改;
- Architecture字段:用於描述deb包安裝的目標設備架構,不要更改;
- Description字段:deb包的簡單介紹,能夠按需更改;
- Maintainer字段:用於描述deb包的維護人,即deb包的製做者而非tweak的做者,能夠按需更改;
- Author字段:用於描述tweak的做者,能夠按需更改;
- Section字段:用於描述deb包所屬的程序類別,不要更改。
control文件中能夠自定義的字段還有不少,通常上面的信息就已經足夠了。更全面的能夠查看官方網站。
值得注意的是:Theos在打包deb時會對control文件作進一步處理。好比更改Version字段爲:0.0.1-2,標識Theos的打包次數,方便管理;增長Installed-Size字段,用於描述deb包安裝後的估算大小,與實際大小可能有誤差,不要更改。
Makefile:該文件用來指定工程編譯和連接要用到的文件、框架、庫等信息,將整個過程自動化,自動生成的字段內容以下:
include $(THEOS)/makefiles/common.mk TWEAK_NAME = ReverseDemo ReverseDemo_FILES = Tweak.xm include $(THEOS_MAKE_PATH)/tweak.mk after-install:: install.exec "killall -9 SpringBoard"
- 第一行的include字段指定了工程的common.mk文件,固定寫法,不要修改;
- TWEAK_NAME字段填入的是創建工程時命令行輸入的Project Name,與control文件中的「Name」字段對應,不要更改;
- ReverseDemo_FILES:指定工程中參與編譯的源文件,若是工程中須要用到多個源文件則用空格將各個文件名分開,也能夠用通配符,可是須要制定具體的路徑,好比scr文件夾內有二十個xm文件,若是一個個輸入太繁瑣,因此能夠寫成ReverseDemo_FILES = Tweak.xm scr/*.m 能夠按需修改。
- include字段指定工程的mk文件,這裏新建的是tweak工程,因此填入的是tweak.mk文件,還能夠根據需求填入application.mk以及tool.mk文件;
- 最後一行after-install字段指定安裝程序後須要執行的操做,這裏須要注入SpringBoard進程並執行本身的代碼,所以須要重啓SpringBoard進程,好讓MobileSubstrate加載對應的dylib。
Makefile文件除了自動生成的這些字段外,還能夠根據功能手動添加其餘字段:
- ARCHS字段能夠用來指定處理器架構,通常狀況下填寫「ARCHS = armv7 arm64」便可;
- TARGET字段用來指定SDK版本,例如:TARGET = iphone:7.0
- THEOS_DEVICE_IP =192.168.1.100 指定安裝的手機ip(ssh) THEOS_DEVICE_PORT = 10010 指定端口
- framework字段能夠指定要導入的框架,好比這裏的測試demo中填寫的是「ReverseDemo_FRAMEWORKS = UIKit」,UIKit爲後續測試代碼須要用到的框架,另外一方面,還能夠經過ReverseDemo_PRIVATE_FRAMEWORKS字段指定要導入的私有庫,格式不變。例如:
ReverseDemo_FRAMEWORKS = UIKit CoreTelephony CoreAudio ReverseDemo_PRIVATE_FRAMEWORKS = AppSupport ChatKit
ReverseDemo.plist:記錄工程的配置信息,描述了tweak的做用範圍,內容以下:
Filter下是一系列Array,能夠分爲三類:
Bundle:指定若干bundle爲tweak的做用對象。如:com.apple.springboard
Classes:指定若干class爲tweak的做用對象。如:NSString
Executables:指定若干可執行文件爲tweak的做用對象。如:callservicesd
這三類Array能夠混合使用,但當Filter下有不一樣類的Array時,須要添加一個「Mode: Any」鍵值對。當Filter下的Array只有一類時,不須要添加。
Tweak.xm:該文件是實現具體功能的關鍵所在,是實現具體功能的源文件,這個文件支持Logos和C、C++語法。文件內容以下:
%hook ClassName // 要替換方法的實現 - (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.) } %end
Tweak插件的圖片資源
iOS中經常使用的兩種加載圖片資源的方式:
+ (nullable UIImage *)imageNamed:(NSString *)name; // load from main bundle - (nullable instancetype)initWithContentsOfFile:(NSString *)path; //load image by path
+imageNamed:方式從程序的main bundle加載圖片,因爲咱們本身單獨開發的插架,資源是須要單獨管理的,沒法從宿主app的MainBundle中讀取圖片。因此咱們須要使用第二種方式,給定圖片的路徑去加載圖片。那麼圖片的存放的路徑應該存放在手機的那個目錄下呢?
在tweak項目中,能夠建立名稱爲layout文件夾,將全部圖片等資源文件存放在這裏。在打包安裝插架到手機中時,layout中的資源會打包到手機的根目錄下。也就是說layout就對應手機的根目錄/
圖片資源放到建立的layout文件夾中至關放到了設備的根路徑下,這時候咱們能夠在插件中引用該圖片資源。可是放到根路徑下顯然是不合適的,由於若是開發的插件圖片資源較多的話,根路徑會很亂。因此咱們須要將插件中用到的圖片放到特定的文件夾內,也就是這個文件夾內是設備上全部插件的圖片資源庫,即下面這個preference文件夾。固然這裏只是小的建議,並非強制要求,直接放根路徑或任何路徑下均可以引用。
咱們還能夠爲開發的插件在preference文件夾下新建一個新的文件夾,好比新建一個TweakWechatImage的文件夾,裏面存放的都是咱們開發的wechat插件的圖片。因此咱們須要在layout中創建一個Library/preferenceLoader/Preferences/TweakWechatImage的文件夾路徑,這樣插件打包安裝時會自動將圖片資源放到設備上的這個路徑中。
這個時候,咱們在調用圖片時,只須要填寫全路徑便可:
[UIImage imageWithContentsOfFile:@"/Library/PreferenceLoader/Preferences/TweakWechatImage/test.png"];
固然,若是多處地方用到圖片資源的話,咱們能夠寫一個宏定義,不用每次都寫這麼一大串:
//這是宏定義的語法,一個是@「a」空格@"b」含義就是: a/b 第二個是# :字符串操做符,用於將參數序列化成一個字符串;即#abc 含義爲@"abc" #define IMAGE_PATH(IMG_NAME) @"/Library/PreferenceLoader/Preferences/TweakWechatImage" #IMG_NAME
咱們下次再調用圖片時就很簡潔了:
[UIImage imageWithContentsOfFile:IMAGE_PATH(test.png)];
Tweak插件的實現原理
咱們經過Theos開發的插件其實只是改變app中某些方法在內存中的調用實現,並無修改app的執行文件。在點擊app圖標運行時,crype中的Substrate插件(Substrate負責管理Device/Library/MobileSubstrate文件夾中的內容,這個插件是越獄後自動裝好的)會去這個路徑查看各個plist(plist中規定了對應插件的應用範圍)文件的內容。查看是否存在該app的插件,有的話則在調用插件中的方法時修改其內部實現。
實現過程爲:
因此,theos的Tweak插件不會對原來的可執行文件進行修改,僅僅是修改了內存中的代碼邏輯。
未脫殼的App是否支持tweak? 支持,由於tweak是在內存中實現的,並無修改.app包中的可執行文件 tweak效果是否永久性? 視狀況而定,若是更新了App,新的版本沒有了這個類,或者不使用這個方法了,那麼咱們將沒法hook到這個方法,tweak將會失效 未越獄的手機是否支持tweak? 不能夠,未越獄的手機就沒有Cydia,也就沒有Substrate。因此就不能去查找插件了。 能不能對Swift/C函數進行tweak ? 原理是能夠,可是方法跟oc不同 能不能對遊戲項目進行tweak? 能夠,但遊戲不少是用C#、c++代碼編寫的,並且通常都有混淆的,因此很難。
tweak插件的卸載
- 直接從
/Library/MobileSubstrate/DynamicLibraries
文件夾刪除插件對應的Plist文件和dylib文件 - 這種方式卸載不是很乾淨
方法二
- 經過Cydia卸載,在cydia的已安裝模塊去查找對應的插件 而後在插件詳情頁將其卸載。
- 推薦這種方式, 卸載比較完全
注意點:
一、咱們經過make package打包,默認是打debug包。若是想打release的包,須要將make package指令換成make package debug=0 debug通常用在開發測試,release通常用在正式環境,通常況下release包會比debug包小一點。
二、編寫插件並不必定只能在Tweak.x文件中,能夠建立多文件來開發,這樣目錄清晰,並且文件的格式並不必定限於.x文件,也能夠是.h .m .xm等等。只是注意要在makefile中加入參與編譯的文件,文件中引入其餘文件時要寫全路徑。