Logos 是 Theos 开发套件的一个组件,他允许使用一组特殊的预处理指令(宏)来轻松、清晰地编写代码。
Logos 提供的语法极大地简化了 MobileSubstrate 扩展(tweaks)的开发,可以在整个操作系统 hook 其他方法。此处,“method hooking”是指用于替换或修改在手机上应用程序中找到的类的方法的技术。
Logos 随 Theos 一起发布,你可以在任意 Theos 构建的项目中使用 Logos 语法,而无需任何额外的设置。有关 Theos 的更多信息,请访问该页面。
在该类别中的指令,打开一个代码块必须使用 %end 指令来关闭(如下所示)。而这些指令不应写在函数和方法内存中。
%group Groupname使用一个__组名__开始一个 hook 组(用于条件初始化或组织代码)。所有未分组的 hook 代码都在隐式的,名为“_ungrouped”的组内。
%group 块不能嵌套使用。
分组可用于管理与旧代码的向后兼容性。
%group iOS8
%hook IOS8_SPECIFIC_CLASS
// your code here
%end // end hook
%end // end group ios8
%group iOS9
%hook IOS9_SPECIFIC_CLASS
// your code here
%end // end hook
%end // end group ios9
%ctor {
if (kCFCoreFoundationVersionNumber > 1200) {
%init(iOS9);
} else {
%init(iOS8);
}
}%hook Classname开启名为 Classname 的类的 hook 块。
可以在 %group 块中使用。
下面是一个简单的例子:
%hook SBApplicationController
-(void)uninstallApplication:(SBApplication *)application {
NSLog(@"Hey, we're hooking uninstallApplication:!");
%orig; // Call the original implementation of this method
return;
}
%end%new
%new(signature)通过给在法定义上方添加此指令,可以将新方法添加到 hook 类或其子类。signature 是 Objective-C 类名,如果省略(omitted),将新生成一个。
必须在 %hook 块中使用。
%subclass Classname: Superclass <Protocol list>子类块:在运行时创建类并使用方法填充。尚不支持 ivars(使用关联对象)。在其父类中不存在该方法时,就需要 %new 说明符。要实例化一个新类的对象,可以使用 %c 运算符。
可以在 %group 块中使用。
下面给出一个例子:
%subclass MyObject : NSObject
- (id)init {
self = %orig;
[self setSomeValue:@"value"];
return self;
}
//the following two new methods act as `@property (nonatomic, retain) id someValue;`
%new
- (id)someValue {
return objc_getAssociatedObject(self, @selector(someValue));
}
%new
- (void)setSomeValue:(id)value {
objc_setAssociatedObject(self, @selector(someValue), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
%end
%ctor {
MyObject *myObject = [[%c(MyObject) alloc] init];
NSLog(@"myObject: %@", [myObject someValue]);
}%property (nonatomic|assign|retain|copy|weak|strong|getter|setter) Type name;将属性添加到 %subclass 就像是使用 @property 给普通 Objective-C 子类添加属性一样,同样可以在 %hook 现有类中添加新属性。
必须在 %subclass 或 %hook 块中使用。
%end关闭 group/hook/subclass 块。
该类别中的指令不应写在于 group/hook/subclass 块中。
%config(Key=Value);设置 logos 配置标记。
generatorMobileSubstrate- 生成使用 MobileSubstrate 进行 hook 的代码
internal- 生成只使用内部 Objective-C runtime 方法进行 hook 的代码
warningsnone- 无警告
default- 仅保留非致命警告
error- 让所有警告致命
dumpyaml- 适用 YAML 格式 dump 内部解析树
%hookf(rtype, symbolName, args...) { … }hook 名为 symbolName 的函数。如果传递的是文字字符串,则动态查找该函数。
// Given the function prototype
FILE *fopen(const char *path, const char *mode);
// The hook is thus made
%hookf(FILE *, fopen, const char *path, const char *mode) {
NSLog(@"Hey, we're hooking fopen to deny relative paths!");
if (path[0] != '/') {
return NULL;
}
return %orig; // Call the original implementation of this function
}为适配 MGGetBoolAnswer 的 Ignition hook,你以前必须这样做:
CFBooleanRef (*orig_MGGetBoolAnswer)(CFStringRef);
CFBooleanRef fixed_MGGetBoolAnswer(CFStringRef string)
{
if (CFEqual(string, CFSTR("StarkCapability"))) {
return kCFBooleanTrue;
}
return orig_MGGetBoolAnswer(string);
}
%ctor {
MSHookFunction(((void *)MSFindSymbol(NULL, "_MGGetBoolAnswer")), (void *)fixed_MGGetBoolAnswer, (void **)&orig_MGGetBoolAnswer);
...
}现在你可以这样做:
%hookf(CFBooleanRef, "_MGGetBoolAnswer", CFStringRef string)
{
if (CFEqual(string, CFSTR("StarkCapability"))) {
return kCFBooleanTrue;
}
return %orig;
}%ctor { … }创建匿名构造函数(默认优先级)。
%dtor { … }创建匿名析构函数(默认优先级)。
该类别中的指令仅能用于函数块中。
%init;
%init([<class>=<expr>, …]);
%init(Group[, [+|-]<class>=<expr>, …]);初始化一个组(或默认组)。不传递组名则会初始化“_ungrouped”的组,并且在传递 class=expr 参数时,在初始化时会使用给定表达式替换这些类。+ 号(Objective-C 类方法)可以添加到类名前面,以通过表达式替换元类(metaclass)。如果没有指定,默认是用 - 号,来替换该类自身的方法。若果没有指定,该元类(metaclass)会由该类派生。类名替换尤其适用于其类名包含不能用于 %hook 指令的字符,例如空格和点。
用法:
%hook SomeClass
-(id)init {
return %orig;
}
%end
%ctor {
%init(SomeClass=objc_getClass("class with spaces in the name"));
}%class Class;
%class指定已被启用,请不要在新代码中使用。
前向声明一个类。%c 过时,但仍然可用。创建 $Class 变量,然后用“_ungrouped”组初始化。
%c([+|-]Class)Evaluates to Class at runtime. If the + sigil is specified, it evaluates to MetaClass instead of Class. If not specified, the sigil defaults to -, evaluating to Class.
在运行时求 Class 的值。如果指定了 + 号,则结果将是 MetaClass 而不是 Class。如果没有指定,默认使用 - 号,结果为 Class。
%orig
%orig(arg1, …)调用原始的 hook 方法。在 %new 创建的方法中不起作用。但在子类中是可行的,奇怪得很,因为 MobileSubstrate 会在在 hook 是生成超类调用闭包。(如果我们 hook 的类不存在已经被 hooked 的方法,它会创建一个只调用父类实现的存根(stub)。)参数传递给原始函数,不包括 self 和 _cmd 参数,Logos 会帮你传递这些。
%log;
%log([(<type>)<expr>, …]);Dump the method arguments to syslog. Typed arguments included in %log will be logged as well.
将方法参数 dump 到系统日志中。还将记录 %log 包含的类型的参数。
.x- 将由 Logos 处理,然后进行预处理,并编译为 Objective-C。
.xm- 将由 Logos 处理,然后进行预处理,并编译为 Objective-C++。
.xi- 首先作为 Objective-C 进行预处理,然后 Logos 处理结果,然后进行编译。
.xmi- 首先作为 Objective-C++ 进行预处理,然后 Logos 处理结果,然后进行编译。
xi 或 xmi 文件可以在 #define 宏中使用 Logos 指令。
默认,Logos 预处理器仅在构建式处理一个 .xm 文件。但是,可以将 Logos hook 代码拆分为多个文件。
首先,必须将主文件重命名为 .xmi 文件。然后,可以使用 #include 指令将其他 .xm 文件包含其中。Logos 预处理器会在处理之前将这些文件添加到主文件中。
你可以使用 logify.pl,来从头文件创建 Logos 源文件,该 Logos 源文件将记录头文件的所有函数。以下是 logify.pl 生成的非常简单的 Logos tweak。
给定一个头文件:
@interface SSDownloadAsset : NSObject
- (NSString *)finalizedPath;
- (NSString *)downloadPath;
- (NSString *)downloadFileName;
+ (id)assetWithURL:(id)url type:(int)type;
- (id)initWithURLRequest:(id)urlrequest type:(int)type;
- (id)initWithURLRequest:(id)urlrequest;
- (id)_initWithDownloadMetadata:(id)downloadMetadata type:(id)type;
@end你可以在 $THEOS/bin/logify.pl 找到 logify.pl,使用如下:
$THEOS/bin/logify.pl ./SSDownloadAsset.h输出结果应该是:
%hook SSDownloadAsset
- (NSString *)finalizedPath { %log; NSString * r = %orig; NSLog(@" = %@", r); return r; }
- (NSString *)downloadPath { %log; NSString * r = %orig; NSLog(@" = %@", r); return r; }
- (NSString *)downloadFileName { %log; NSString * r = %orig; NSLog(@" = %@", r); return r; }
+ (id)assetWithURL:(id)url type:(int)type { %log; id r = %orig; NSLog(@" = %@", r); return r; }
- (id)initWithURLRequest:(id)urlrequest type:(int)type { %log; id r = %orig; NSLog(@" = %@", r); return r; }
- (id)initWithURLRequest:(id)urlrequest { %log; id r = %orig; NSLog(@" = %@", r); return r; }
- (id)_initWithDownloadMetadata:(id)downloadMetadata type:(id)type { %log; id r = %orig; NSLog(@" = %@", r); return r; }
%end