首页
论坛
课程
招聘
[原创] 重载可执行文件实现高效inline-hook 【源码】
2017-6-3 15:21 6590

[原创] 重载可执行文件实现高效inline-hook 【源码】

2017-6-3 15:21
6590

前言

很多情况下的效率优化,基本上都是时间换空间,空间换时间。本方案就是通过多花一点点内存空间,在传统的inline-hook的基础上实现通用的,高效的hook。其核心原理就是可执行模块的重载。


传统的inline-hook

传统的内联hook是先备份目标函数的前n个字节,通过修改目标函数的前n个字节,替换为几条指令,指令中实现跳转到代理的函数。调用原函数则是有两种方式,一是调用前恢复被修改的字节,返回后再次进行hook。二是将原本的字节备份到一段可执行的内存,内存末尾跳转到原函数被修改的代码的下一条正常的指令,直接调用此处内存的代码。方式一的缺点是效率低,而且在并发上要做特别处理。方式二的缺点是,当前n个字节包含有局部跳转指令时,若不对偏移量进行修正,则会导致执行错误。对偏移量的修正比较麻烦且困难,对不同的处理器架构需要单独做处理。


新的hook方案

新的hook方案有以下优点:高效,调用原函数时,不需要做额外的处理;简单通用,不需要修正指令,不需要单独对处理器架构做处理。而缺点就是多占用一些内存。多占用的内存大小取决于被hook的模块的大小,若进行一些优化处理,这个内存占用大小仍有减少空间。新hook方案对环境要求如下:支持mmapmprotect、支持共享内存。对共享内存的要求,不是绝对必要,只要能实现同一段内存映射到不同的内存地址即可。


实现细节

可执行文件通常是map到内存中的。可执行文件一般有若干段节,比如text代码段,data数据段。其中代码段是可读可执行的,数据段是可读可写的。text段中的代码对data段中的数据的寻址是相对寻址。本方案进行hook前,会克隆整个可执行文件的内存镜像到另一块内存。新代码段是普通的可写可执行的代码段,新数据段则有些特别。新数据段是旧数据段的remap。其关系是,新旧数据段虽然处于不同的内存地址,但是通过某些手段使其指向同一段物理内存。直观地说就是,对新旧数据段的读写,实际上呈现的是同样的内容。目的是为了同步全局变量。
hook的时候,只要对旧代码插入内联hook需要的代码即可。当需要调用原函数的时候,只要调用新代码段中的原函数即可。在这个过程中,基本上没有执行速度上的损耗。


代码

本例子通过使用共享内存的方式实现将一段内存映射到两个不同的地址
#define SET_PROXY(c,f) *((uint64_t*)(((uint8_t*)(c))+2)) = (uint64_t)f

void *ih_hook(void *oldFunc, void *newFunc, void **save) {
    unsigned char hook_stub_code_rax[] = {
        0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, //movabsq 0x1122334455667788, %rax  10
        0xff, 0xe0, 	//jmp *%rax 2
        0x90,
    };
    
    unsigned char hook_stub_code_r11[] = {
        0x49, 0xBB, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, //movabsq 0x1122334455667788, %r11  10
        0x41, 0xff, 0xe3, 	//jmp *%11 3
    };
    
    void *image_ptr = NULL;
    size_t image_size = 0;
    void *data_ptr = NULL;
    size_t data_size = 0;
    
// 获取可执行文件的信息   
 if (get_addr_info(oldFunc, &image_ptr, &image_size, &data_ptr, &data_size) != 0){
        return  NULL;
    }
    
    // 创建共享内存对象
    const char *shm_file = "/tmp/hook.shm";
    int fd = shm_open(shm_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0){
        printf("Error: shm_open\n");
        return NULL;
    }
    ftruncate(fd, image_size);
    
    // 创建新镜像内存
    void *new_image_ptr = mmap(NULL, image_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (new_image_ptr == MAP_FAILED){
        shm_unlink(shm_file);
        printf("Error: mmap for new_image_ptr: %s\n", strerror(errno));
        return  NULL;
    }
    
    // 拷贝数据到新内存
    memcpy(new_image_ptr, image_ptr, image_size);
    if (mprotect(new_image_ptr, image_size, PROT_EXEC|PROT_READ) != 0){
        munmap(new_image_ptr, image_size);
        printf("Error: mprotect: %s\n", strerror(errno));
        return NULL;
    }
    
    // 将旧数据段覆盖为共享内存
    void *shm1 = mmap(data_ptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FILE|MAP_FIXED, fd, 0);
    my_memcpy(shm1, (uint8_t*)new_image_ptr + (data_ptr - image_ptr), data_size);
    
    // 将新数据段覆盖为共享内存
    void *shm2 = mmap((uint8_t*)new_image_ptr + (data_ptr - image_ptr), data_size, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FILE|MAP_FIXED, fd, 0);
    my_memcpy(shm2, data_ptr, data_size);
    
    // 做内联hook
    SET_PROXY(hook_stub_code_r11, newFunc);
    write_memory(oldFunc, hook_stub_code_r11, sizeof(hook_stub_code_r11));
    
    // 计算原函数地址,原函数位于新代码段
    *save = (uint8_t*)new_image_ptr + (oldFunc - image_ptr);;
    
//    
//    munmap(new_image_ptr, image_size);
//    //    munmap(shm1, data_size);
//    munmap(shm2, data_size);
//    
//    shm_unlink(shm_file);
    return NULL;
}

int (*org_puts)(const char *s);

int proxy_puts(const char *s) {
    printf("Hook successful\n");
    org_puts(s);
    return 0;
}

int main(int argc, const char * argv[]) {
    
    ih_hook(puts, proxy_puts, (void**)&org_puts);
    
    puts("Normal");
    
    printf("Hello, World!\n");
    return 0;
}

结果:

Hook successful
Normal
Hello, World!

完整源码见附件,目前只做了osx上的版本,linux和win的日后再研究。


[培训] 优秀毕业生寄语:恭喜id咸鱼炒白菜拿到远超3W月薪的offer,《安卓高级研修班》火热招生!!!

上传的附件:
收藏
点赞0
打赏
分享
打赏 + 10.00
打赏次数 1 金额 + 10.00
 
最新回复 (21)
雪    币: 96
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
火勺 活跃值 2017-6-3 15:42
2
0
你这有点复杂啊
雪    币: 133
活跃值: 活跃值 (152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不追浮云的人 活跃值 2017-6-3 16:03
3
0
  学习了
雪    币: 265
活跃值: 活跃值 (836)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 活跃值 1 2017-6-3 16:32
4
0
先m后看!
雪    币: 280
活跃值: 活跃值 (417)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
我是哥布林 活跃值 2017-6-3 17:04
5
0
学习一下
雪    币: 10388
活跃值: 活跃值 (3988)
能力值: ( LV9,RANK:270 )
在线值:
发帖
回帖
粉丝
hzqst 活跃值 3 2017-6-3 18:52
6
0
这不就是传说中的Page  hook+重定向到新模块么
雪    币: 30
活跃值: 活跃值 (128)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 活跃值 2 2017-6-3 19:20
7
0
火勺 你这有点复杂啊
这个算简单了
雪    币: 30
活跃值: 活跃值 (128)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 活跃值 2 2017-6-3 19:20
8
0
hzqst 这不就是传说中的Page hook+重定向到新模块么
正是
雪    币: 62
活跃值: 活跃值 (256)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
值得怀疑 活跃值 2017-6-3 21:44
9
0
只有代码片段  就  申精了?
雪    币: 30
活跃值: 活跃值 (128)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 活跃值 2 2017-6-3 23:37
10
0
值得怀疑 只有代码片段 就 申精了?
本来想移植到linux再发源码的,现在只有osx的版本。既然你这么说了,那就发吧。其实上面就是核心的代码了,懂行的人,一看就明白,其他的代码不过是工具函数罢了。
雪    币: 207
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
srh 活跃值 2017-8-31 10:22
11
0
666大神膜拜呀!
雪    币: 1583
活跃值: 活跃值 (214)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
xia0 活跃值 1 2017-11-9 20:09
12
0
 

make编译运行后在下面这个函数这里

    void *shm1 = mmap(data_ptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FILE|MAP_FIXED, fd, 0);

报错Error: mmap for new_image_ptr: Cannot allocate memory
请问下大佬这个是什么原因导致的?

雪    币: 137
活跃值: 活跃值 (495)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 活跃值 2017-12-1 11:13
13
0
mark
雪    币: 1375
活跃值: 活跃值 (36)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
ixiaohuo 活跃值 1 2017-12-16 12:18
14
0
mark
雪    币: 30
活跃值: 活跃值 (128)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 活跃值 2 2017-12-16 12:46
15
0
chdgyue make编译运行后在下面这个函数这里 ``` void *shm1 = mmap(data_ptr, data_size, PROT_READ | PROT_WRITE, MAP_SHAR ...
可能是基址或size没对齐,不像oom。
雪    币: 39
活跃值: 活跃值 (197)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
killerfive 活跃值 2017-12-19 15:22
16
0
mark
雪    币: 134
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
流哥 活跃值 2017-12-20 15:55
17
0
已放弃osx,装回windows了
雪    币: 131
活跃值: 活跃值 (370)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
jmpews 活跃值 2017-12-20 17:02
18
0
aka:  复制一份  rx  segment,  映射  rw  segment  至同一物理内存  ?  但是这种对畸形  macho  还要需要处理所有的  rw  属性的  segment  感觉有点复杂哇.  而且内存消耗也是问题.  随便搞下就几个  0x4000
雪    币: 137
活跃值: 活跃值 (495)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 活跃值 2017-12-20 17:51
19
0
mark
雪    币: 30
活跃值: 活跃值 (128)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
malokch 活跃值 2 2017-12-20 19:46
20
0
jmpews aka: 复制一份 rx segment, 映射 rw segment 至同一物理内存 ? 但是这种对畸形 macho 还要需要处理所有的 rw 属性的 segment 感觉有点复杂哇. 而且内存消耗 ...
不用处理macho,也不用单独处理segment,整片拷就行了。想省内存的话,rw  segment一般不大,hook一个点4k内存。这点内存还是可以接受的。有时候效果比那点内存更重要
雪    币: 17
活跃值: 活跃值 (19)
能力值: ( LV9,RANK:146 )
在线值:
发帖
回帖
粉丝
wendax 活跃值 2018-2-10 20:40
21
0
救命啊,朕不是皇上
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
LLR_sunshine 活跃值 2021-8-20 19:04
22
0
你好,请问有Linux版的demo吗?
对machO不是特别熟,看了下代码,想确定下,是要把目标函数所在的文件的整个内存段都映射出来?还是只映射目标函数的内存段就可以了?
游客
登录 | 注册 方可回帖
返回