Block的Hook方法

作者: 耿杰 | 来源:发表于2019-01-25 11:37 被阅读40次

ABI Block底层实现

利用RunTime动态交换老生常谈的问题了,现在接下来,通过两个例子,在运行时动态替换Block里面的函数

Block内存结构

image.png

一、编写一个Block,调用了printHelloWorld函数之后,再执行Block,打印出Hello World

1、编写Block函数,并且编译成C++文件
1、在main.m文件中, 编写一个简单Block,并且调用它,并有两个int类型的参数
int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        void(^block)(int, int) = ^(int a, int b) {
            NSLog(@"block函数");
        };
        
        block(1, 2);
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
2、在项目目录根路径,利用Clang命令,把main.m文件转换main.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
3、查看main.m文件,刚刚编写的Block是由一个结构体__main_block_impl_0实现的,快速介绍下几个重点参数
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6x_r5wnz2v529j0mmdsb9y1ht440000gn_T_main_d6346c_mi_0);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 1、__main_block_impl_0,有两个参数,加一个构造函数
  • 2、__block_impl第一个参数是一个指针, 第二个参数是Flags,第四个参数是个指针,指向着Block回调函数的地址, 指向是__main_block_func_0的地址。
  • 3、__main_block_desc_0第一个参数是保留字段,没有用,第二个参数是__main_block_impl_0占用内存的大小。
  • 4、__main_block_func_0Block调用要执行的函数
2、查看在main.cppmain函数是如何调用Block函数,也是基本C语法调用
// 创建一个指向函数的指针`block`,指向`__main_block_impl_0`,观察`__main_block_impl_0 `构造函数,发现把`__main_block_func_0`函数的指针赋值给了`impl.FuncPtr`
void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 利用`block`这个指针取出`FuncPtr`的地址值,并调用,并且传了三个参数
// `Block`的本身、1、2。
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
3、如果我们把__main_block_impl_0中的implFuncPtr地址值给修改掉,就可以达到替换Block要执行函数的实现
  • 1、在main函数中声明一个block函数,在调用了printHelloWorld函数之后,调用block函数,打印出Hello World\n
void printHelloWord() {
    printf("Hello World\n");
}

typedef struct WT_Block_Desc {
    size_t reserved;
    size_t Block_size;
} WT_Block_Desc;

struct WT_Block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *funPtr;
    WT_Block_Desc *desc;
};



void printHelloWorld(id block) {
    struct WT_Block_impl *blockImpl = (__bridge struct WT_Block_impl *)block;
    blockImpl->funPtr = &printHelloWord;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        void(^block)(int a, NSString *b) = ^(int a, NSString *b) {
            NSLog(@"block invoke");
        };
        printHelloWorld(block);
        
        block(1, @"3");
    }
    return 0;
}

二、编写一个Block,调用printArguments函数之后,调用Block的时候,什么先打印出Block的参数,并且实现Block函数原有的实现

void(^block)(NSString *,int, double, CGSize) = ^(NSString *a, int b, double c, CGSize size){
    NSLog(@"原始方法实现");
};
        
hookBlock(block);
        
block(@"1", 2, 3.1, CGSizeMake(20, 20));

结果

1,2,3.100000,CGSize:{20, 20}
方法实现
1、实现逻辑

利用libffi能够动态创建方法、根据函数地址,调用任意C函数

  • 1、取出原Block的参数,给每个参数申请内存空间,按ffi要求把参数数据组装成ffi_type **
  • 2、利用参数个数、返回值类型、参数数组组装成ffi_cif对象
  • 3、利用ffi_closure_alloc创建一个ffi_closure的指针, 并且把要替换函数的指针赋值
  • 4、利用ffi_closure的指针、ffi_cif对象、(void (*)(ffi_cif *, void *, void **, void *))、替换函数的指针, 来动态定义一个函数
  • 5、交换原始函数和替换方法的指针,并利用g_origin_funcPtr保存原始函数的指针
  • 6、在替换函数forwardInvation中,从第3个参数中取出Block的参数并打印
  • 7、把cif函数原型、原始函数指针,返回值内存指针,参数数据传入ffi_call调用原始函数实现

方法实现

//
//  main.m
//  Block的Hook
//
//  Created by 无头骑士 GJ on 2019/1/24.
//  Copyright © 2019 无头骑士 GJ. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "WTPerson.h"
#import "ffi.h"

int cout;           // block的参数个数
ffi_cif g_cif;
ffi_closure *g_closure;
void *g_replace_funcPtr;    // 被替换方法指针
void *g_origin_funcPtr;     // 原始方法指针
NSMutableArray *argTypes;
NSMutableDictionary *registeredStruct;
NSMutableArray *argStructTypes;

enum {
    // Set to true on blocks that have captures (and thus are not true
    // global blocks) but are known not to escape for various other
    // reasons. For backward compatiblity with old runtimes, whenever
    // BLOCK_IS_NOESCAPE is set, BLOCK_IS_GLOBAL is set too. Copying a
    // non-escaping block returns the original block and releasing such a
    // block is a no-op, which is exactly how global blocks are handled.
    BLOCK_IS_NOESCAPE      =  (1 << 23),
    
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};

ffi_type *ffiTypeWithEncodingChar(const char *c)
{
    switch (c[0]) {
        case 'v':
            
            return &ffi_type_void;
        case 'c':
            return &ffi_type_schar;
        case 'C':
            return &ffi_type_uchar;
        case 's':
            return &ffi_type_sshort;
        case 'S':
            return &ffi_type_ushort;
        case 'i':
            return &ffi_type_sint;
        case 'I':
            return &ffi_type_uint;
        case 'l':
            return &ffi_type_slong;
        case 'L':
            return &ffi_type_ulong;
        case 'q':
            return &ffi_type_sint64;
        case 'Q':
            return &ffi_type_uint64;
        case 'f':
            return &ffi_type_float;
        case 'd':
            return &ffi_type_double;
        case 'F':
#if CGFLOAT_IS_DOUBLE
            return &ffi_type_double;
#else
            return &ffi_type_float;
#endif
        case 'B':
            return &ffi_type_uint8;
        case '^':
            return &ffi_type_pointer;
        case '@':
            return &ffi_type_pointer;
        case '#':
            return &ffi_type_pointer;
        case '{':
        {
            NSString *typeStr = [NSString stringWithCString: c encoding: NSASCIIStringEncoding];
            NSUInteger end = [typeStr rangeOfString:@"}"].location;
            if (end != NSNotFound) {
                NSString *structName = [typeStr substringWithRange:NSMakeRange(1, end - 1)];
                NSUInteger eqEnd = [typeStr rangeOfString:@"="].location;
                if (end != NSNotFound)
                {
                    structName = [structName substringWithRange: NSMakeRange(0, eqEnd - 1)];
                }
                ffi_type *type = malloc(sizeof(ffi_type));
                type->alignment = 0;
                type->size = 0;
                type->type = FFI_TYPE_STRUCT;
                NSDictionary *structDefine = [registeredStruct objectForKey: structName];
                NSUInteger subTypeCount = [structDefine[@"keys"] count];
                NSString *subTypes = structDefine[@"types"];
                ffi_type **sub_types = malloc(sizeof(ffi_type *) * (subTypeCount + 1));
                for (NSUInteger i=0; i<subTypeCount; i++) {
                    sub_types[i] = ffiTypeWithEncodingChar([subTypes cStringUsingEncoding:NSASCIIStringEncoding]);
                    type->size += sub_types[i]->size;
                }
                sub_types[subTypeCount] = NULL;
                type->elements = sub_types;
                
                if (structName) [argStructTypes addObject: structName];
                return type;
            }
        }
    }
    return NULL;
}

struct WT_block_Desc {
    size_t reserved;
    size_t block_size;
    void *pointer[1];
};

typedef struct WT__block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct WT_block_Desc *desc;
} WT__block_impl;

const char * size_and_alignment(const char *str, NSUInteger *sizep, NSUInteger *alignp, long *len) {
    const char *arg = NSGetSizeAndAlignment(str, sizep, alignp);
    if (len) {
        *len = arg - str;
    }
    while (isdigit(*arg))
    {
        arg++;
    }
    return arg;
}

void * allocate(size_t howmuch) {
    NSMutableData *data = [NSMutableData dataWithLength:howmuch];
    //    [g_allocations addObject: data];
    return [data mutableBytes];
}

static int arg_count(const char *str) {
    int arg_count = -1;
    while (str && *str) {
        str = size_and_alignment(str, NULL, NULL, NULL);
        arg_count ++;
    }
    return arg_count;
}

ffi_type ** args_with_encode_string(const char *signature, int *out_count)
{
    // 1、获取参数的个数
    int count = arg_count(signature);
    // 2、创建参数类型数组
    ffi_type **arg_types = allocate(sizeof(*arg_types) * count);
    
    // 3、第一个参数是返回值
    int i = -1;
    while(signature && *signature) {
        
        // 4、把方法编码 v20@0i4i4 中取出 i i这两个参数编码
        const char *next = size_and_alignment(signature, NULL, NULL, NULL);
        if (i >= 0)
        {
            arg_types[i] = ffiTypeWithEncodingChar(signature);
            NSString *argType = [NSString stringWithFormat:@"%c", signature[0]];
            [argTypes addObject: argType];
        }
        i++;
        signature = next;
    }
    
    *out_count = count;
    return arg_types;
    
}

static void forwardInvation(ffi_cif *cif, void *ret, void **args, void *user_data)
{
    if (argTypes.count > 1)
    {
        __block NSUInteger structIndex = 0;
        [argTypes enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            if (idx > 0)
            {
                const char *type = [obj UTF8String];
                
                switch (*type) {
                    case 'i':
                    {
                        int result = *((int *)args[idx]);
                        NSLog(@"int:%d\n", result);
                        break;
                    }
                    case '@': // id
                    {
                        id result = (__bridge id)(*((void **)args[idx]));
                        NSLog(@"id:%@\n", result);
                        break;
                    }
                    case 'B': // BOOL bool
                    {
                        bool result = *((bool *)args[idx]);
                        NSLog(@"bool:%d\n", result);
                        break;
                    }
                    case 'd': // double
                    {
                        double result = *((double *)args[idx]);
                        NSLog(@"double:%f\n", result);
                        break;
                    }
                    case 'f': // float
                    {
                        float result = *((float *)args[idx]);
                        NSLog(@"float:%f\n", result);
                        break;
                    }
                    case '#': // Class
                    {
                        Class result = *((Class *)args[idx]);
                        NSLog(@"Class:%@\n", NSStringFromClass(result));
                        break;
                    }
                    case '{':
                    {
                        NSString *structName = [argStructTypes objectAtIndex: structIndex];
                        if ([structName isEqualToString: @"CGSize"])
                        {
                            CGSize size = *((CGSize *)args[idx]);
                            NSLog(@"CGSize:%@\n", NSStringFromCGSize(size));
                        }
                        
                        structIndex++;
                        break;
                    }
                    default:
                        break;
                }
            }
        }];
    }
    else
    {
        NSLog(@"没有参数");
    }
    
    ffi_call(&g_cif, g_origin_funcPtr, ret, args);
}

void initGlobalMember()
{
    argTypes = [NSMutableArray array];
    
    registeredStruct = [NSMutableDictionary dictionary];
    registeredStruct[@"CGSize"] = @{@"name": @"CGSize", @"types": @"dd", @"keys": @[@"width", @"height"]};
    
    argStructTypes = [NSMutableArray array];
}

const char * getBlockSignature(WT__block_impl *blockImpl)
{
    struct WT_block_Desc *desc = blockImpl->desc;
    
    int index = 0;
    if (blockImpl->Flags & BLOCK_HAS_COPY_DISPOSE)
    {
        index = 2;
    }
    return desc->pointer[index];
}

void initG_cif(const char *signature)
{
    int args_count;
    ffi_type **arg_types = args_with_encode_string(signature, &args_count);
    
    ffi_status status = ffi_prep_cif(&g_cif, FFI_DEFAULT_ABI, args_count, ffiTypeWithEncodingChar(signature), arg_types);
    
    if (status != FFI_OK)
    {
        printf("ffi_prep_cif ffi_status:%ld", (long)status);
        abort();
    }
}

void initG_closure(WT__block_impl *blockImpl) {
    g_closure = ffi_closure_alloc(sizeof(ffi_closure), &g_replace_funcPtr);
    ffi_status status = ffi_prep_closure_loc(g_closure, &g_cif, forwardInvation, NULL, g_replace_funcPtr);
    
    if (status != FFI_OK)
    {
        printf("ffi_prep_closure_loc ffi_status:%ld", (long)status);
        abort();
    }
    
    g_origin_funcPtr = blockImpl->FuncPtr;
    blockImpl->FuncPtr = g_replace_funcPtr;
}

void hookBlock(id block) {
    // 初始化全局变量
    initGlobalMember();
    // 转换成block结构体
    struct WT__block_impl *blockImpl = (__bridge WT__block_impl *)block;
    // 获取Block方法参数编码
    const char *signature = getBlockSignature(blockImpl);
    // 初始化方法原型
    initG_cif(signature);
    // 根据原型动态创建一个方法
    initG_closure(blockImpl);
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        void(^block)(NSString *,int, double, CGSize) = ^(NSString *a, int b, double c, CGSize size){
            NSLog(@"原始方法实现");
        };
        
        hookBlock(block);
        
        block(@"1", 2, 3.1, CGSizeMake(20, 20));
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
    
}

参考:

http://iosre.com/t/block/6779
https://github.com/mikeash
http://blog.cnbang.net/tech/3332/

相关文章

  • 运行时Hook所有Block方法调用的技术实现

    运行时Hook所有Block方法调用的技术实现 运行时Hook所有Block方法调用的技术实现

  • Hook Objective-C中的block

    前言 iOS的方法交换能为我们 hook 实例方法,也能为我们 hook 类方法,但是对于 Block 却无能为力...

  • Block的Hook方法

    ABI Block底层实现 利用RunTime动态交换老生常谈的问题了,现在接下来,通过两个例子,在运行时动态替换...

  • 开源库 Block Tracker 学习

    修改 selector IMP 映射来 hook 方法在开发中很常见,但是 hook 一个 block 实现以及使...

  • Aspects 库学习笔记

    1, 两个接口的差别 2, hook方法的时机 Adds a block of code before/inst...

  • Block经典问题循环引用&解决

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 1.循环引用怎么产生的...

  • Block探索

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 程序占用内存分类 栈区...

  • Block底层分析

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 1. 研究工具:cla...

  • Block底层hook

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 前言 如何反编译出微信...

  • HOOK block

    block分为三种: globalblock stackblock mallocblock

网友评论

    本文标题:Block的Hook方法

    本文链接:https://www.haomeiwen.com/subject/lytujqtx.html