iOS逆向--Logos语法学习

1,266 阅读7分钟

上一篇文章我们学习了LLDB调试和Monkey的使用,现在我们来学习monkey的另外一种功能,编写Logos代码,对程序的动态调试。

一、hook目标文件的方法

首先我们创建一个Demo,在Demo中写上下面代码,然后编译运行:


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [self zjj_ClickTap:@"点击了屏幕"];
}

-(void)zjj_ClickTap:(NSString *)str{

    NSLog(@"%@",str);
}

然后我们分别将.app文件和MachO文件拿出来

下面我们要对这个可执行文件进行Hook

我们创建一个Monkey工程,我们把刚才拿到的.app文件放到Monkey工程下的TargetApp文件夹里

然后在通过class-dump工具从刚才的machO文件里导出头文件

class-dump -H ZJJTestLogos -o ./headers/

我们发现我们的monkey工程有两个targets,上面那个就是我们的注入工程,下面那个就是我们注入的库,所以我们的代码卸载下面库里,下面库里我们发现有很多工具文件,例如fishHook等。现在我们来熟悉一下Logos:

我们发现Logos里面有两个文件,一个xm,一个mm文件,其中xm文件是给我写Logos代码的,mm文件是编译器将xm文件编译出来的代码,所以我们要把Logos代码写在xm文件里面。

我们在xm文件中写上下面代码

然后编译运行,点击屏幕,我们发现打印出来的是

说明已经hook了点击方法,我们来分析一下语法

%hook 和 %end之间算一个模块,%hook后面跟上类名,模块之间重写要hook的方法,方法我们通过class-dump工具将.h文件导出来获取,这样我们就能简单的通过monkey工程把方法给hook了

关于Logos我们可以看下介绍Logos介绍

二、Logos语法

hook方法组:%group

刚才我们了解了%hook,下面我们来了解下%group。

开发中,假如我们需要系统版本啦判断hook不同的方法,这个时候就需要用到%group了

%group group1
%hook ViewController

-(void)zjj_ClickTap:(NSString *)str{

    NSLog(@"HOOK了第一组");
}
%end
%end

%group group2
%hook ViewController

-(void)zjj_ClickTap:(NSString *)str{

    NSLog(@"HOOK了第二组");
}
%end
%end

%ctor{
    %init(group1)%init(group2)
}

构造方法:%ctor

在Logos里面有一个函数是%ctor,这个是Logos里面的构造函数,我们可以在里面创建group

编译时候系统会先找%ctor构造函数,然后初始化group1和group2,因为group2是后加载的, 所以会覆盖group1的内容

我们可以通过系统版本来hook不同的group

%ctor{

    NSString *v = [UIDevice currentDevice].systemName;
    if(v.doubleValue >= 11.0){
        %init(group1)
    }else{
        %init(group2)
    }
}

我们运行一下,发现跟预料中的一样

但是最开始时候我们并没有去写group,但依然hook了,这是因为在Logos里面有一个隐式的group:_ungrouped,并且默认在构造函数%ctor里面去初始化它

析构函数:%dtor

既然有构造函数,那就有析构函数,在Logos里面析构函数就是%dtor,也就是相当于dealloc方法,也就是在应用挂掉的时候触发的

%dtor{

}

打印日志:%log

我们再来了解一下%log,我们在方法里面写上

%hook ViewController

-(void)zjj_ClickTap:(NSString *)str{
    %log;
    NSLog(@"HOOK了第一组");
}
%end

然后运行打印,

2020-11-20 20:41:56.962367+0800 ZJJTestLogos[9350:2251906] -[<ViewController: 0x105619e40> zjj_ClickTap:点击了屏幕]

我们发现会把方法调用者,方法名,参数值都打印出来了,这就是%log的功能

调回原方法:%orig

平时我们开发过程中在hook完方法后会调回方法,在Logos里面调回原方法就是%orig,假如方法有返回值,直接return %orig就行了

  • 如果不想改变参数的话,直接调用%orig就行了
  • 假如我们想改变返回值时候,这样就行了:%orig(@"123",@"234")

添加方法:%new

有时候我们需要对类进行添加方法,在Logos中,是使用%new:

@interface ViewController ()

@end

%hook ViewController
%new
-(void)addMethod{

    NSLog(@"sdf");
}

-(void)zjj_ClickTap:(NSString *)str{
    %log;
    NSLog(@"HOOK了第一组");
    [self addMethod];
}
%end

在我们调用addMethod方法时候,self需要去找函数,这个时候我们就需要将这个方法声明一下,所以需要在Logos里面加上interface,我们也可以拿到ViewController的头文件,然后在头文件里面添加addMethod方法,再在Logos里面import ViewController一下就行了

上面我们添加的是对象方法,我们再添加一下类方法:

%new
+(void)addClassMethod{

    NSLog(@"addClassMethod");
}

调用时候我们可以用:

    [self.class addClassMethod];

获取类名:%c

不过我们可以使用%c函数来获取类:

[%c(ViewController) addClassMethod];

这样就可可以调用类方法了

三、Monkey的hook本质

在之前iOS逆向-- fishhook原理分析里面有初探防护,也就是哼哦OK了方法交换函数,来避免方法呗hook,现在我们把那个包拿过来放到monkey工程里面,看看能不通过monkey工程hook到

原工程代码:

- (IBAction)btnClick1:(id)sender {
    NSLog(@"按钮1调用了!");
}
- (IBAction)btnClick2:(id)sender {
    NSLog(@"按钮2调用了!");
}

viewController的防护代码


+(void)load
{
    //防护代码!
    struct rebinding bd;
    bd.name = "method_exchangeImplementations";
    bd.replacement = myExchange;
    bd.replaced = (void *)&exchangeP;
    NSLog(@"防护来了!!");
  
    struct rebinding rebs[] = {bd};
    rebind_symbols(rebs, 1);
}

//防护代码
//函数指针变量
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);

void myExchange(Method _Nonnull m1, Method _Nonnull m2){
    NSLog(@"检测到HOOK!!");
}

然后我们在monkey的xm文件里面写上下面代码:

%hook ViewController
- (void)btnClick1:(id)sender {
    %log;
    NSLog(@"HOOK了按钮");
}
%end

运行,点击按钮:

2020-11-20 21:30:28.574244+0800 002--初探防护[9408:2274225] -[<ViewController: 0x143e1c250> btnClick1:<UIButton: 0x143e21010; frame = (94 263; 30 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x282ba5980>>]
2020-11-20 21:30:28.574556+0800 002--初探防护[9408:2274225] HOOK了按钮

我们发现hook到了,说明我们对方法交换method_exchangeImplementations的防护是无效的,我们再就想到是不是对setIMP和getIMP,进行防护试试呢?

我们把防护代码改成下面

+(void)load
{
    //防护代码!
    struct rebinding bd;
    bd.name = "method_exchangeImplementations";
    bd.replacement = myExchange;
    bd.replaced = (void *)&exchangeP;
    NSLog(@"防护来了!!");
    
    struct rebinding bd1;
    bd1.name = "method_getImplementation";
    bd1.replacement = myExchange;
    bd1.replaced = (void *)&getIMP;
    
    
    struct rebinding bd2;
    bd2.name = "method_setImplementation";
    bd2.replacement = myExchange;
    bd2.replaced = (void *)&setIMP;
    
    
    struct rebinding rebs[] = {bd1,bd2};
    rebind_symbols(rebs, 2);   
}

//防护代码
//函数指针变量
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
IMP _Nonnull (*setIMP)(Method _Nonnull m, IMP _Nonnull imp);
IMP _Nonnull (*getIMP)(Method _Nonnull m);

void myExchange(Method _Nonnull m1, Method _Nonnull m2){
    NSLog(@"检测到HOOK!!");
}

再次运行,拿到app包,放到monkey工程中去,然后运行

2020-11-20 21:30:28.574244+0800 002--初探防护[9408:2274225] -[<ViewController: 0x143e1c250> btnClick1:<UIButton: 0x143e21010; frame = (94 263; 30 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x282ba5980>>]
2020-11-20 21:30:28.574556+0800 002--初探防护[9408:2274225] HOOK了按钮

发现还是hook到了,这是为啥呢,考虑到我们在上一篇文章中防护代码要在进攻代码之前执行才行,我们在monkey工程中创建一个类

2020-11-20 22:02:48.858198+0800 002--初探防护[9436:2288652] 进攻代码来了
2020-11-20 22:02:48.779300+0800 002--初探防护[9436:2288652] 防护来了!!

我们发现进攻代码在防护代码之前,所以我们要把防护代码写在原工程的framework里面,因为frameword的执行时机要比类的执行时机早,并且注入的framework都是按照顺序依次排列的,先注入的在前面,后注入的在后面

我们在创建一个framework放到源工程里面,在编译,我们发现无法hook了,说明,monkeyhook方法是通过hook 它的setIMP和getIMP 函数进行hook的