Logos

Logos作为Theos开发组件的一部分,通过一组特殊的预处理指令,可以让编写函数钩子(hook)代码变得非常简单和清晰。

Logos提供的语法大大的简化了MobileSubstrate拓展程序(tweaks,能够hook系统中其他方法)的开发,这里所说的“Method hooking”是指通过替换或修改的方式改变其他应用中某些类的某些方法。

Logos是随着Theos发布的,你能够在用Theos创建的项目中直接使用Logos的语法。更多关于Theos的信息,请查看这里

使用Logos

例子

下面是由logify.pl(/theos/bin/logify.pl)生成的一个非常简单的Logos tweak的例子

1
2
3
4
5
6
%hook AFHTTPRequestOperation
- (NSHTTPURLResponse *)response { %log; NSHTTPURLResponse * r = %orig; NSLog(@" = %@", r); return r; }
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> * )responseSerializer { %log; %orig; }
- (AFHTTPResponseSerializer <AFURLResponseSerialization> * )responseSerializer { %log; AFHTTPResponseSerializer <AFURLResponseSerialization> * r = %orig; NSLog(@" = 0x%x", (unsigned int)r); return r; }
- (id )responseObject { %log; id r = %orig; NSLog(@" = %@", r); return r; }
%end

你可以使用logify.pl来创建一个头文件的Logos源文件,用来打印该头文件中所有函数:

1
2
export THEOS=/opt/theos
$THEOS/bin/logify.pl ./AFHTTPRequestOperation.h

Logos指令表

%init

1
2
3
%init
%init([<class>=<expr>, …])
%init(Group[, [+|-]<class>=<expr>, …])

用来初始化一个分组(group)或默认分组,如果不传参数会初始化“_ungrouped”分组,传 class=expr 参数会在指定类初始化的时候替换给定的表达式,+号(作为Objective-C中类方法)在对应的类名中能够优先处理来替换元类的表达式,如果没有特别指定,默认标记是-,只是替换当前类。

%hook

1
%hook ClassName

指定要hook的类,以%end结束。

1
2
3
4
5
6
7
%hook SBApplicationController
-(void)uninstallApplication:(SBApplication *)application {
NSLog(@"Hey, we're hooking uninstallApplication:!");
%orig; // Call the original implementation of this method
return;
}
%end

%subclass

1
2
3
4
5
6
7
8
9
%subclass Classname: Superclass <Protocol, Protocol>
%new
- (void)someNewAddedMethod
{
//...
}
%end

用于申明在运行时创建的类,支持方法,但暂时不支持成员变量。如果增加父类中不存在的方法,需要给这些方法指定%new标识。对象实例化时,你需要用到%c标识。

%group

1
2
%group GroupName
%end

开始一个hook分组,通常用于条件执行或代码组织。所有没有特别指定的%hook都被归为“_grouped”分组。

%new

1
2
%new
%new(signature)

为被hook的类或子类增加新的方法,功能与class_addMethod()相同,signature是新方法的类型编码(Objective-C type encoding),如果不指定,会自动生成一个。

%ctor

1
2
3
4
%ctor
{
//...
}

生成一个匿名构造函数(默认的优先级)。如果不指定,Theos会隐式定义:

1
%ctor { %init(_ungrouped); }

作为tweak程序的入口,可以用于做一些其他初始化处理,如:

1
2
3
4
5
6
7
8
9
%ctor
{
%init;
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
%init(HookGroupForIOS7);
} else {
%init(HookGroupForIOS6);
}
}

%end

1
%end

结束%hook、 %subclass、 %group块。

%config

1
%config(X=Y);

设置一个logos配置标记。

配置标记:

  • generator
    • MobileSubstrate
      生成使用 MobileSubstrate来hook的代码。
    • internal
      生成只使用内置的Objective-C运行时函数来hook的代码。
  • warnings
    • none
      忽略所有警告。
    • default
      报告非致命的警告。
    • error
      所有警告当做错误处理。
  • dump
    • yaml
      以YAML格式导出内部解析树。
    • perl
      以perl源码能处理的格式导出内部解析树。

%c

1
%c([+|-]Class)

在运行时获取一个类的定义,作用等同于objc_getClass()+号获取元类,默认为-号,获取当前类。

%orig

1
2
%orig
%orig(arg1,arg2,arg3)

执行被hook函数的原始代码,不要在使用%new申明的方法中使用。奇怪的是能够在subclass中使用,因为MobileSubstrate在hook时会生成一个父调用闭包(supercall closure),如果所hook的函数在当前的类中不存在,就会调用父类的实现。参数会被传递给原始的函数,不用加self_cmd,Logos已经帮你加了。

%log

1
2
%log
%log([(<type>)<expr>, …])

默认将类名、函数名、参数等信息写入syslog,也可以追加其他信息。

1
2
3
4
5
6
7
%hook SpringBoard
- (void)menuButtonDown:(id)down
{
%log((NSString *)@"hello", (NSString *)@"MobileSubstrate");
%orig; //调用原方法
}
%end

Logos代码跨文件访问

默认情况下,Logos的预处理器在Build时只会处理一个 .xm 文件。然而,也可以实现将Logos hook代码分割到多个文件中。首先,主文件(通常为Tweak.xm)需要改为 .xmi 格式;然后可以在它里面使用#include引入其他 .xm 文件。Logos的预处理器会在开始处理之前就将其他文件加到主文件,.xmi 会被优先处理。

Logos拓展名

Extension Process order
.x 先被Logos处理,然后被作为Objective-C预处理和编译
.xm 先被Logos处理,然后被当做Objective-C++预处理和编译
.xi 首先被当做Objective-C预处理,然后由Logos处理结果,最后被编译
.xmi 首先被当做Objective-C++预处理,然后由Logos处理结果,最后被编译

xi 和 xmi 文件能够使用Logos指令作为 #define 宏进行预处理。


参考: