在利用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 }
%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)
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字段指定工程包含的源文件,如果工程中需要用到多个源文件则用空格将各个文件名分开,可以按需更改;
- 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插件的卸载
- 直接从
/Library/MobileSubstrate/DynamicLibraries
文件夹删除插件对应的Plist文件和dylib文件 - 这种方式卸载不是很干净
方法二
- 通过Cydia卸载,在cydia的已安装模块去查找对应的插件 然后在插件详情页将其卸载。
- 推荐这种方式, 卸载比较彻底