逆向中 Block 探究

1,620 阅读3分钟

逆向中少不了各种 Hook:Hook C 函数、Hook OC 方法,以及 Hook Block。C 函数在乎的是 C 函数的符号(Symbol)标识、OC 方法在乎的是 SEL 中的各个段,Block 就不一样的了、在乎的是有多少个参数以及具体的参数的类型。不装了、我们继续吧...
对于本次研究的主题,请看图片:

代码也很简单,也贴一下:
正向代码

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 调用方法
    [self beta_blockWithName:@"CoderHG" block:^(NSDictionary * dict, int age) {
        NSLog(@"beta_blockWithName -: %@, %d", dict, age);
    }];
}

// 自定义方法
- (void)beta_blockWithName:(NSString*)name
                     block:(void (^)(NSDictionary*, int age))block {
    block(@{@"KEY":@"VALUE"}, 18);
}


@end

逆向代码:

%hook ViewController

// hook
- (void)beta_blockWithName:(NSString*)name block:(id)block {
    NSLog(@"Hook && beta_blockWithName = %@, %@", name, block);
    
    %orig;
}

%end

假装我们没有看到过 正向代码,那么问题来了,如果在 逆向 中解析出 block 的参数个数以及各自的参数类型呢?
其实这个在 17 年 的时候就有大神出过文章:<逆向中获取 Block 的参数和返回值>(文章末尾有连接),这篇文章很棒,在其中收获很多,但是我按照这篇文章的步骤操作,最终还差那么一点点就成功了。
比如在操作的过程中,这个指令就困惑了我不少的时间:

(lldb) p (char *)0x0000000100dfec18

后来在 <iOS 使用NSMethodSignature和 NSInvocation进行 method 或 block的调用>, 因为这篇文章提到了 Aspects。我滴个神呐,一下子就豁然开朗了。在中看到了这一句代码:

const char *signature = (*(const char **)desc);

将上面的指令换成这样的:

(lldb) p (*(const char **)0x0000000100dfec18)

厉害了,尽然 成功 了。
后来经过一番的折腾,在逆向代码中添加这些代码:

// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock {
    __unused Class isa;
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

哦豁、不装了,直接上图吧。

此图含金量还算不错。在试验中还发现,一旦使用了 AspectBlockRef 之后, 所有的内存打印格式,才与 <逆向中获取 Block 的参数和返回值> 中介绍的同步,这个可能是设备的问题,也有可能是 lldb 有调整(后期再慢慢研究具体的原因)。
最后还发现一个问题,这种 lldb 指令在现在的 Xcode 中已经不能使用了:

po [NSMethodSignature signatureWithObjCTypes:"v28@?0q8@"NSDictionary"16B24"]

执行直接报错,其实这个直接 copy 到源代码中运行就可以看到具体的类型了。
看到这里应该会很懵,因为跳过了很多的细节,强烈建议看看下面这两篇文章。

感谢这两篇文章才让我彻底的弄明白、大家也去阅读,相信当你看完之后也能收获多多:
逆向中获取 Block 的参数和返回值
iOS 使用NSMethodSignature和 NSInvocation进行 method 或 block的调用


题外话、题外话、题外话,这是我初期的一个小实验,以供各位大神欣赏:


图中有一个亮点是在 hook 代码中,我故意将 block 的参数多弄了一个,然后尽然能正常运行,但是会随机的 crash。这也间接的说明一个问题,如果这个 block 实际的参数都是 id 类型的话,可以一次性弄他个 N 多个,运行起来几乎都能猜中具体的参数个数以及参数类型了。在试验的过程中还发现,hook 中的参数少于实际的参数也是可以的。但是如果有类型对应不上的话肯定会 crash。