Theos


在利用theos開發一些插件時,我們經常會用到以下幾個指令:

%hook 指定需要hook的類名,以%end結尾

//hook的是SpringBoard這個類里面的方法
%hook SpringBoard
 -(void)_menuButtonDown:(id)down
 {
   NSLog(@"You've  pressed home button");
   %orig; //call the original _menuButtonDown
 }
 %end

%orig 執行被hook函數的原始代碼,類似於super.method功能

%hook ClassName
- (void) _menuButtonDown: (id)down
{
   NSlog(@"ss");
    //如果去掉%orig,那么原始函數不會得到執行。
   %orig;
}
@end

%new 該指令用來給現有的class添加一個新的函數。與Runtime中的class_addMethod相同。

%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的用法如下:

 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才能起作用

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

%ctor tweak的構造器,用來初始化。如果開發者沒有重寫這個方法,theos會自動生成%ctor並在其中調用%init(_ungrouped)。%ctor一般可以用來初始化%group,以及進行MSHookFunction等操作。

注意:%ctor不需要以%end結尾

 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的已安裝模塊去查找對應的插件 然后在插件詳情頁將其卸載。
  • 推薦這種方式, 卸載比較徹底

 

注意點:

1、我們通過make package打包,默認是打debug包。如果想打release的包,需要將make package指令換成make package debug=0  debug一般用在開發測試,release一般用在正式環境,一般況下release包會比debug包小一點。

2、編寫插件並不一定只能在Tweak.x文件中,可以創建多文件來開發,這樣目錄清晰,而且文件的格式並不一定限於.x文件,也可以是.h .m .xm等等。只是注意要在makefile中加入參與編譯的文件,文件中引入其他文件時要寫全路徑。

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM