697f620e753a0e0bbbedeee062ab7b2d
核心知识篇:Logos 语法

1.概述

LogosTheOS 开发套件中的一个组件,它允许使用一组特殊的预处理器指令轻松而清晰地编写 Hook 代码,极大地简化了 MobileSubstrate 扩展(Tweaks)的开发。

2.基本语法

完整的 Logos 语法,可以查看官网:https://iphonedevwiki.net/index.php/Logos。 从官网的目录可以看到,Logos 语法分为三大类。

2.1 Block level

这一类型的指令会开辟一个代码块,以 %end 结束。

  • %hook:指定需要 hook 的 class,必须以 %end 结尾(可以被 %group 包含)
%hook SBApplicationController
-(void)uninstallApplication:(SBApplication *)application {
    NSLog(@"Hey, we're hooking uninstallApplication:!");
    %orig; // Call the original implementation of this method
    return;
}
%end
  • %group:该指令用于将 %hook 分组,便于代码管理及按条件初始化分组,必须以 %end 结尾。一个 %group 可以包含多个 %hook,所有不属于某个自定义 group 的 %hook 会被隐式归类到 %group_ungrouped 中。
%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);
    }
}
  • %new:在 %hook 内部使用,给一个现有 Class 添加新函数,功能与 class_addMethod 相同。
%hook SBApplicationController
-(void)uninstallApplication:(SBApplication *)application {
    NSLog(@"Hey, we're hooking uninstallApplication:!");
    %orig; // Call the original implementation of this method
    return;
}

%new
- (void)namespaceNewMethod
{
    NSLog(@"We've added a new method to SpringBoard.");
}
%end
  • %subclass:子类块。该类在运行时创建,并用方法填充。现在 IVAR 还没有被支持(可使用关联对象)。对于超类中不存在的方法,需要 %new 进行添加。要实例化类的对象,可以使用 %c。
%subclass GofObject : 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 {
    GofObject *gofObject = [[%c(GofObject) alloc] init];
    NSLog(@"gofObject: %@", [gofObject someValue]);
}
  • %property:将一个属性添加到 %subclass 中。
%property (nonatomic|assign|retain|copy|weak|strong|getter|setter) Type name;
  • %end:关闭组、钩子类块。

2.2 Top level

  • %ctor:Tweak 的构造函数,完成初始化工作。如果不显示定义,Theos 会自动生成一个 %ctor,并在其中调用 %init(_ungrouped)。
  • %dtor:Tweak 的析构函数。如果不显示定义,Theos 会自动生成一个 %dtor。

2.3 Function level

  • %init:该指令用于初始化某个 %group,必须在 %hook 或 %ctor 内调用。如果带参数,则初始化指定的 group,如果不带参数,则初始化 _ungrouped。
%ctor {
    if (kCFCoreFoundationVersionNumber > 1200) %init(iOS9);
     else %init(iOS8);
}

只有调用了 %init,对应的 %group 才能起作用。

  • %c:该指令的作用等同于 objc_getClass 或 NSClassFromString,即动态获取一个类的定义,在 %hook 或 %ctor 内使用 。
%hook SpringBoard
- (void)_menuButtonDown:(id)down
{
   %orig;
   SBScreenShotter *shotter = [%c(SBScreenShotter) sharedInstance];
   [shotter saveScreenshot:YES];
}
%end
  • %log:该指令在 %hook 内部使用,将函数的类名、参数等信息写入 syslog,可以 %log([(),…..]) 的格式追加其他打印信息。这个指令在跟踪某个事件(比如说抢红包),要查看具体执行了哪些方法时非常有用,因为根据这个输出信息,就能跟踪到执行的相关类的相关方法,有效的缩小我们的跟进分析代码范围。
  • %orig:该指令在 %hook 内部使用,执行被 hook 的函数的原始代码;也可以用 %orig 更改原始函数的参数(定义一个变量,拿到 %orig 执行的返回值,进行修改,然后重新返回即可)。

2.4 使用示例

2.4.1 初步尝试

先说一下我们要做的:这里,我们写一个模拟登录的 Demo(工程 A),然后利用 MonkeyDev,新建一个 Hook 工程(工程 B),使用 Logos 语法,来 Hook 我们的登录按钮点击。

  • 第一步:新建工程 A,在 Main.storyboard 中添加两个 UITextField(用户名和密码输入框),一个 UIButton(登录按钮),在 ViewController 类中添加下面的代码:
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextField *tfUserName;
@property (weak, nonatomic) IBOutlet UITextField *tfPwd;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (IBAction)btnLogin:(id)sender {
    NSLog(@"点击了登录按钮");
}

@end
  • 第二步:编译工程 A,得到 .app 文件,使用下面的指令生成 .ipa 包。
zip -ry GofLogosDemo.ipa Payload
  • 第三步:使用模板 MonkeyApp,新建工程 B,将第二步生成的 ipa 包,拷贝到对应的目录下面。然后在 GofHookDemoDylib.xm 文件中添加 Hook 代码。
#import <UIKit/UIKit.h>

%hook ViewController

- (void)btnLogin:(id)sender {
    NSLog(@"Hook 点击了登录按钮");
}

%end
  • 第四步:运行工程 B,点击登录按钮,可以看到,Hook 已经成功。

可以看到,使用 MonkeyApp 创建的工程,可以很快速的添加 Hook 代码,那么它的原理是什么呢?打开工程 B 生成的 app 文件,可以看到 Frameworks 目录下面有一个 libsubstrate.dylib(越狱设备上,直接将这个库导入到了系统),说明 MonkeyApp 是使用前面我们所说的第三种方式(Cydia Substrate)进行 Hook 的,本质上还是基于方法交换 和 fishhook。

2.4.2 使用 Group

修改工程 B 的代码:
```

import

%group groupA

%hook ViewController

  • (void)btnLogin:(id)sender {
    NSLog(@"HookA 点击了登录按钮");
    }

%end

%end

%group groupB

%hook ViewController

  • (void)btnLogin:(id)sender {
    NSLog(@"HookB 点击了登录按钮");
top Created with Sketch.