Theos

在利用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 }
View Code

%init 該指令用於初始化某個%group,必須在%hook%ctor內調用,若是帶參數,則初始化指定的group,若是不帶參數,則初始化_ungrouped框架

只有調用了%init,對應的%group才能起做用ssh

%ctor { //帶參數的話是初始化自定義的group 
    %init(iOS8); //不帶參數的話是初始化默認的_ungrouped
    %init; }

%ctor tweak的構造器,用來初始化。若是開發者沒有重寫這個方法,theos會自動生成%ctor並在其中調用%init(_ungrouped)。%ctor通常能夠用來初始化%group,以及進行MSHookFunction等操做。iphone

注意:%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中加入參與編譯的文件,文件中引入其餘文件時要寫全路徑。

相關文章
相關標籤/搜索