注册

iOS面试题目——hook block(1)

// 1、实现下面的函数,将 block 的实现修改为 NSLog(@"Hello world");
//也就是说,在调用完这个函数后调用用block()时,并不调用原始实现,而是打 "Hello world"

void HookBlockToPrintHelloWorld(id block){

}


分析:题目的意思很明白,就是要实现一个函数,将作为参数传入的block的实现修改为一句log。有研究过block的结构的都知道,block实际上就是一个接口体。而它的实现部分,是作为函数指针被保存在结构体的`invoke`中(即第16个字节处):

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    uintptr_t invoke; // 此处保存的是实现代码的起始地址
    struct Block_descriptor_1 *descriptor;
    // imported variables
};


所以这题的解法也很明朗:就是将block结构体中的invoke函数指针地址替换为我们的函数:

// 定义一个函数
void block1_replace(void){
NSLog(@"Hello world");
};
void HookBlockToPrintHelloWorld(id block){
    // 解析 block 为 struct Block_layout 结构体
struct Block_layout *layout = (__bridge struct Block_layout *)block;

    //此处不能直接修改,因为该处地址属性为不可写状态,强行替换,会导致程序崩溃
    //layout->invoke = (uintptr_t)block1_replace;

    //修改内存属性
    vm_address_t invoke_addr = (vm_address_t)&layout->invoke;
    vm_size_t vmsize = 0;
    mach_port_t object = 0;
    vm_region_basic_info_data_64_t info;
    mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT_64;
    kern_return_t ret = vm_region_64(mach_task_self(), &invoke_addr, &vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &infoCnt, &object);
    if (ret != KERN_SUCCESS) {
        NSLog(@"获取失败");
        return;
    }
    vm_prot_t protection = info.protection;
    // 判断内存是否可写
    if ((protection&VM_PROT_WRITE) == 0) {
        // 修改内存属性 ===> 可写
ret = vm_protect(mach_task_self(), invoke_addr, sizeof(invoke_addr), false, protection|VM_PROT_WRITE);
        if (ret != KERN_SUCCESS) {
NSLog(@"修改失败");
            return;
}
}
layout->invoke = (uintptr_t)block1_replace;
}


0 个评论

要回复文章请先登录注册