BigBoss有一个分类,称之为Tweaks。网上所说得越狱插件,大多可归类与此。至于tweak这个名称的由来以及含义,由于接触得晚,完全不知其所以然。若有朋友对此熟悉,还请不吝赐教。

Tweak

Tweak开发,离不开的是MobielSubstrate。按百科上所讲的 ,MobieSubstrate是一个允许第三方开发者在运行时(run-time)替换扩展iOS 上系统方法的框架,主要包含了三个组件:MobileHooker,MobileLoader以及safe mode。MobielSubstrate由Saurik开发。假如不知道Saurik是哪位大神,开句外国式的玩笑,stop reading this!

MobileHooker

“钩子”这个名称可谓形象。它的作用就是,在运行时替代Objective-C消息(由于动态特性,Objective-C一般都用“消息”来指代“方法”)的实现。简单来讲,就是系统向某个类发送消息(objc_msgSend)时,会被“钩住”,并用自己的实现方法代替(是否执行系统实现方法以及执行时机可由自己决定)。

代码如下:

1
2
3
4
5
6
7
8
9
10
static IMP original_UIView_setFrame_; //声明origin的方法,也就是原有的系统方法
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) {// 原有的系统方法被替换如下
CGRect originalFrame = self.frame;
NSLog("Changing frame of %p from %@ to %@", self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame));
original_UIView_setFrame_(self, _cmd, frame); // 原有的系统方法被调用
}

MSHookMessageEx([UIView class], @selector( setFrame: ), (IMP)replaced_UIView_setFrame_, (IMP*)&original_UIView_setFrame_); // 声明需要“HOOK”的类和消息名

myView.frame = CGRectMake(0, 0, 100, 100); //当UIView的 - setFrame 被调用时,会被替代并输出一句系统log。[/cpp]

当然,最后一句也可以写成

1
original_UIView_setFrame_(self, _cmd, CGRectMake(0, 0, 0, 0));

或者直接注销掉。不过强烈建议不要尝试。只想说的是,替代原有方法绝不是输出一句log那么简单,可以做的事情远超乎想象。

类方法的“钩子”的声明,需要修改如下:

1
MSHookMessageEx(objc_getMetaClass("UIView"), @selector(commitAnimations), replaced_UIView_commitAnimations, (IMP*)&original_UIView_commitAnimations);

MobileLoader

“钩子”需要在运行时被加载,靠的就是MobileLoader的功劳。MobileLoader会在适当的时机加载/Library/MobileSubstrate/DynamicLibraries/目录下的动态库(.dylib,这是tweak的最终产品)。当.dylib被加载时,下面的语句会被最先调用:

1
2
3
__attribute__((constructor))staticvoid initialize() {

}

打开/Library/MobileSubstrate/DynamicLibraries/,可以看到每一个.dylib都有会一个同名的.plist。.plist的作用就是用来限制.dylib的加载范围的,包括iOS版本和被加载对象的bundleID。

safe mode

看了MobileHooker的大概原理,可以猜出这东西是多么不安全。safe mode算是个保险,当SpirngBoard崩溃时会自动进入安全模式,同时仅用所有.dylib的加载。

至于MobilSubstrate的原理,个人觉得和Objective-C的动态特性关系密切。关于这方面,可以参考这里

iOSOpenDev

假如按照上面的语法来开发Tweak,语句实在发杂繁琐。网上由各种各样的模板,像iOSOpenDev,就内置了两种Tweak模板:CaptainHook Tweak和Logos Tweak。CaptainHook Tweak模板使用了大量宏定义来简化格式,而Logos Tweak则走得更远。初期建议用原生的语法或者CaptainHook Tweak之类的模板。在熟悉了相关的原理和语法之后,强烈建议在实际开发中使用Logos Tweak模板。

使用iOSOpenDev新建一个Logos Tweak工程,目录结构如下:

Package目录是deb包的相关东西,稍微了解deb包结构的人应该很熟悉。打开control,有这么一句:Depends:mobilesubstrate。说明Tweak开发是必须依赖mobilesubstrate这个框架的。

Products目录里面的.dylib就是最终产品了,是一个动态库。

.xm文件是我们敲代码的地方(不是.mm)。按照.xm的提示,先链接libsubstrate.dylib,然后按照Logos Tweak模板格式编写tweak,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static id __sb = nil;
%hook SpringBoard //需要hook的类名
-(void)applicationDidFinishLaunching: (id)application //需要hook的消息名
{
  %orig; //执行原方法
  UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Welcome" message:@"Welcome to your iPhone Brandon!" delegate:self cancelButtonTitle:@"Thanks" otherButtonTitles:nil];
  [alert show];
  [alert release];
}
- (id)init
{
__sb = self;
return %orig;
}
- (void)alertView: (UIAlertView *)alertView clickedButtonAtIndex: (NSInteger)buttonIndex
{
  //[__sb relaunchSpringBoard];
}
%new( @@: )
+ (id)sharedBoard
{
  return __sb;
}
%end //结束标志

编译一下,然后打开.mm文件,可以看到Logos Tweak的脚本已经自动将.xm文件中Logos格式的语句转化为我们熟悉的符合mobilesubstrate语法的语句了,使用了类似于CaptainHook的宏定义。每次编译.xm文件都会自动覆盖.mm文件,所以,除了了解一些实现原理外,可基本忽略.mm文件。

而Logos Tweak的这种类似于类定义的语法,看着确实让人舒服,同时也可实实在在地提高开发速度。

%hook Classname:需要“hook”某个类的起始标志

%new:插入新方法到类中

%end:“hook”某个类的结束标志

%orig或者%orig(args):执行系统方法,若有参数可带参数

%c(Class):其实就是objc_getClass()

%log:快速打印类、消息名以及参数到系统log中

%ctor{…}:其实就是

1
__attribute__((constructor))staticvoid initialize(){}

这个入口函数。

还有很多,可以查看这里

前面那段代码其实算是Tweak的HelloWorld吧,其实就是在SpringBoard启动时弹出一个对话框(被注销掉的代码是临时加的,可以尝试去掉看看效果)。将.dylib和.plist拷贝出来复制到/Library/MobileSubstrate/DynamicLibraries/目录下(由于没有打deb包,请确认手机已经安装了MobilSubstrate),重启SpringBoard,假如启动后弹出了一个对话框,说明tweak成功了。Hello world!