简单linux 下 x64任意地址的inlinehook

背景

最近工作需要hook函数,然后实现自己的逻辑,之前都是使用的frida来直接hook,但是这里发现,挂在frida之后对性能影响较大,可能是数十倍的影响,之前一直都没发现。所以这里必须自己实现一个hook

参考

这里参照了下面几个文章

https://bbs.kanxue.com/thread-272797-1.htm

https://www.cnblogs.com/pwl999/p/15535002.html

https://www.cnblogs.com/iBinary/p/11334793.html

https://toutiao.io/posts/mwpeytt/preview

过程

先是参照第一篇的,直接全盘复制,例子没通。。。我是使用的LD_PRELOAD的方式来加载so的,不会调试,有会的大佬指点一下呀,然后就准备重写了。

所有的hook,大致流程如下:

取出当前hook点的汇编temp

修改为跳转汇编,跳转到一个跳板指令段

在跳板指令中保存现场

跳转到自定义函数

自定义函数执行完后恢复现场

执行temp指令

跳转到原始指令的下一条

针对我的应用场景,不普适,只求快

1.

首先我们需要开辟一个空间,用于自己hook函数后跳转到的自定义函数的存放。

这里有两个方式,frida和常规的都是在自己so中存放这段函数,但是这里有个问题,通常64位的内存分布中,so和原始elf文件的内存空间间隔很远,都要使用一个长跳跳过去。而我这里主要考虑效率问题(我猜会不会跳的短一点有可能对性能影响小),以及后续期望能够通过5字节的一个jmp指令,减少对原指令的破坏,所以需要在原始的elf中找一段不用的函数,来填写我们的自定义函数。这里参考了frida的实现,我们打开一个elf文件,看其中的段分区,可以看到代码段到堆栈分区中间是有一段空白区的,我们只要在这一段分配一块空间供跳板地址存储。

这里通过mmap的方式,在指定的地址分配一块内存:

void  *init_addr   =   (void  *)0xc62000;
char* init_addr = (char*)mmap(NULL, 4096, PROT_WRITE|PROT_EXEC|PROT_READ, MAP_ANON|MAP_PRIVATE, -1, 0);

  1. 如果这里的点不是五个字节的完整汇编呢,即正好一个2+4等情况,需要我们保存超过5个字节的汇编。这里可以使用反汇编工具,读取当前地址的指令,看保存几个合适,这里我就是参照了第一篇文章的使用了capstone,和他的区别就是我这里后续只需要5个字节,对代码的破坏性更小。

//获取目标函数前几条指令的长度,跳转指令是5个字节,所以取大于等于5的长度
int get_asm_len(intptr_t target)
{
    csh handle;
    cs_insn* insn;
    size_t count;
    char code[30] = {0};
    int rv;
 
    memcpy((void*)code, (void*)target, 30);
    if(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
    {
        printf("Error: cs_open\n");
        return -1;
    }
 
    count = cs_disasm(handle, code, 30, 0, 0, &insn);
    if(count)
    {
        for(size_t i = 0; i < count; i++)
        {
        //
            if(!strcmp("call", insn[i].mnemonic))
                return 0;
            if (insn[i].address >= 5)
            {
                rv = insn[i].address;
                break;
            }
        }
        cs_free(insn, count);
    }
    else
    {
        printf("Error: cs_disasm\n");
        return -1;
    }
    cs_close(&handle);
    return rv;
}

  1. 这里的汇编中包含call,或者jmp 到相对地址的指令,这里都需要进行特殊处理,我这里是求简单了,当前应用的只需要处理call的,通过之前的get_asm_len如果返回为0来判断需要修改。

//根据当前temp地址重新填写相对地址
int change_asm_code(intptr_t target ,intptr_t temp)
{
    csh handle;
    cs_insn* insn;
    size_t count;
    char code[30] = {0};
    int rv;
 
    memcpy((void*)code, (void*)target, 30);
    if(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
    {
        printf("Error: cs_open\n");
        return -1;
    }
 
    count = cs_disasm(handle, code, 30, 0, 0, &insn);
    if(count)
    {
        for(size_t i = 0; i < count; i++)
        {
        //
            if(!strcmp("call", insn[i].mnemonic))
            {    
                int at = insn[i].address;
                uint32_t originAddr = code[at + 4]<<24|code[at + 3]<<16|code[at + 2]|code[at + 1];
                uint32_t targetAddr = target + at + originAddr + 5;
                uint32_t relativeAddr = temp - targetAddr - 5;
                uint8_t jumpCode[5] = {0xe8,0x00,0x00,0x00,0x00};
                memcpy(jumpCode + 1; &relativeAddr, sizeof(uint32_t));
                change_bytes(targetAddr,(const char*) jumpCode, 5);    
            }
            if (insn[i].address >= 5)
            {
                rv = insn[i].address;
                break;
            }
        }
        cs_free(insn, count);
    }
    else
    {
        printf("Error: cs_disasm\n");
        return -1;
    }
    cs_close(&handle);
    return rv;
}
 

保存现场:

这里可以自定义一个pusha和popa,因为本身64位的汇编不支持pusha了,我这里只保存了rdi,rsi,rdx,rcx,rax五个寄存器,前四个通常是调用的前4个参数,第五个是函数返回。如果需要的话,第五六个参数是r8和r9寄存器。本来也准备保存rsp寄存器的,后来发现编译器给我加上了保存rsp的功能,顾暂时不需要了

修改:后来发现老崩溃,这里发现少保存了一个rbp寄存器,相当于缺少对环境的恢复了

进行跳转:

这里网上资料主要都是使用了14个字节的跳转方式,这里我用在一级跳板往自定义代码中以及一级跳板跳回原代码,因为这里不需要考虑对源代码的破坏,长一点不要紧,简单最好。。:

68 XX XX XX XX                            push LowAddress
C7 44 24 04 XX XX XX XX              mov qword ptr ss:[rsp + 4],HighAddress
C3

但是我们为了更小的破坏代码,在修改原地址时只使用了五个字节:

e9 XX XX XX XX                      jmp    相对地址

这里相对地址的计算公式是:相对地址 = 目的 - 源 - 5(指令长度)

所以我们的一级跳板主要包括四个部分:

13个字节保存自定义函数返回地址到rsp中;

14个字节绝对跳转到自定义函数地址;

n个字节保存原始地址的指令;

14个字节跳回原始地址+n的地址;

自定义函数部分走到的坑:

汇编语言风格:

我这里之前主要是通过frida获取寄存器的值来进行其他操作,所以这里需要取寄存器的值,开始使用

__asm("pop rax")的操作时,总是提示我找不到rax,这里是因为汇编风格的问题,我这不知道为什么都是at&t的风格,需要在rax前面加上%即可。其他很多相应的问题都是asm的汇编语言风格问题。

获取寄存器:

不知道什么原因,在网上搜的很多的在汇编和c++之间共享变量的方式都无法使用,报各种奇怪的错,所以我这里寻求帮助后使用了另外一种方式:

register int *r12 asm ("r12"); 

这里保存寄存器r12当前的值到变量r12中,后续不会随着寄存器的变化继续变化了。

最终代码:

#include<stdio.h>
#include<stdlib.h>
#include<capstone/capstone.h>
#include<unistd.h>
#include<sys/types.h>
#include<dlfcn.h>
#include<sys/mman.h>
#include<string.h>
#include<stdint.h>
 
 //使用mmap来分配一个内存,用于后续的一级跳板
void  *tempAddr = (void  *)0xc62000;
char* temp_func;

 
//获取目标函数前几条指令的长度,跳转指令是5个字节,所以取大于等于5的长度
int get_asm_len(intptr_t target)
{
    csh handle;
    cs_insn* insn;
    size_t count;
    char code[30] = {0};
    int rv;
 
    memcpy((void*)code, (void*)target, 30);
    if(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
    {
        printf("Error: cs_open\n");
        return -1;
    }
 
    count = cs_disasm(handle, code, 30, 0, 0, &insn);
    if(count)
    {
        for(size_t i = 0; i < count; i++)
        {
        //
            if(!strcmp("call", insn[i].mnemonic))
                return 0;
            if (insn[i].address >= 5)
            {
                rv = insn[i].address;
                break;
            }
        }
        cs_free(insn, count);
    }
    else
    {
        printf("Error: cs_disasm\n");
        return -1;
    }
    cs_close(&handle);
    return rv;
}
 

//根据当前temp地址重新填写相对地址
int change_asm_code(intptr_t target ,intptr_t temp)
{
    csh handle;
    cs_insn* insn;
    size_t count;
    char code[30] = {0};
    int rv;
 
    memcpy((void*)code, (void*)target, 30);
    if(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
    {
        printf("Error: cs_open\n");
        return -1;
    }
 
    count = cs_disasm(handle, code, 30, 0, 0, &insn);
    if(count)
    {
        for(size_t i = 0; i < count; i++)
        {
        //
            if(!strcmp("call", insn[i].mnemonic))
            {    
                int at = insn[i].address;
                uint32_t originAddr = code[at + 4]<<24|code[at + 3]<<16|code[at + 2]|code[at + 1];
                uint32_t targetAddr = target + at + originAddr + 5;
                uint32_t relativeAddr = temp - targetAddr - 5;
                uint8_t jumpCode[5] = {0xe8,0x00,0x00,0x00,0x00};
                memcpy(jumpCode + 1, &relativeAddr, sizeof(uint32_t));
                change_bytes(targetAddr,(const char*) jumpCode, 5);    
            }
            if (insn[i].address >= 5)
            {
                rv = insn[i].address;
                break;
            }
        }
        cs_free(insn, count);
    }
    else
    {
        printf("Error: cs_disasm\n");
        return -1;
    }
    cs_close(&handle);
    return rv;
}
 
 
 
//替换目标函数的前len个字节,使之跳转到hook函数
void change_bytes(intptr_t addr, const char code[], int len)
{
    memcpy((void*)addr, code, len);
}
 
 
void func_hook(intptr_t target_addr, void* hook_func)
{
 
    //根据目标函数的地址确定目标函数所在的页,并将该页的权限改为可读可写可执行
    intptr_t page_start = target_addr & 0xfffffffff000;
    mprotect((void*)page_start, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC);
 
 
    int asm_len = get_asm_len(target_addr);
    if(asm_len == 0)
    {
        change_asm_code(target_addr, temp_func + 13 + 14);
    }
    if(asm_len == -1)
    {
        printf("Error: get_asm_code\n");
        exit(-1);
    }
    
    
    //保存跳板中的地址到rsp中,这样自定义函数返回时可以返回跳板函数继续执行原函数
    intptr_t x = *temp_func + 27;
    char ret_code[13] = {0x68,x&0xff,(x&0xff00)>>8,(x&0xff0000)>>16,(x&0xff000000)>>24,
        0xC7,0x44,0x24,0x04,(x&0xff00000000)>>32,(x&0xff0000000000)>>40,(x&0xff000000000000)>>48,
        (x&0xff00000000000000)>>56};
    memcpy((void*)temp_func, (void*)ret_code, asm_len);   
    
    
    //跳板地址跳到自定义函数
    intptr_t y = hook_func;
    char self_code[14] = {0x68,y&0xff,(y&0xff00)>>8,(y&0xff0000)>>16,(y&0xff000000)>>24,
        0xC7,0x44,0x24,0x04,(y&0xff00000000)>>32,(y&0xff0000000000)>>40,(y&0xff000000000000)>>48,
        (y&0xff00000000000000)>>56,0xC3};
    memcpy((void*)temp_func + 13, (void*)self_code, asm_len);   
    
    
    //复制原始指令
    memcpy((void*)temp_func+13+14, (void*)target_addr, asm_len);
    
    //跳转指令,跳回原始地址+asmlen处
    intptr_t z = (intptr_t)target_addr + asm_len;
    //构造push&ret跳转,填入目标地址
    char jmp_code[14] = {0x68,z&0xff,(z&0xff00)>>8,(z&0xff0000)>>16,(z&0xff000000)>>24,
        0xC7,0x44,0x24,0x04,(z&0xff00000000)>>32,(z&0xff0000000000)>>40,(z&0xff000000000000)>>48,
        (z&0xff00000000000000)>>56,0xC3};    
    memcpy((void*)(temp_func+asm_len + 13 + 14), (void*)jmp_code, 14);
 
 
    
        
    //目标地址改为跳到跳板地址
    uint32_t relativeAddr = target_addr - (uint32_t)temp_func - 5;
    uint8_t jumpCode[5] = {0xe9,0x00,0x00,0x00,0x00};
    memcpy(jumpCode + 1, &relativeAddr, sizeof(uint32_t));
    change_bytes(target_addr,(const char*) jumpCode, 5);       
    
     //用于后续的hook函数使用
    temp_func = temp_func + 60;
    
     
    
}
 
 
 
//
void test_hook()
{
    __asm("push %rdi");
    /*__asm{
        push rbp;
        push rdi;
        push rsi;
        push rdx;
        push rcx;
        push rax;
    }*/
    printf("everythint is ok\n");
    register int *rax asm ("rax"); 
    printf("rax is %lx\n",*rax);
    
    
    
    /*__asm{
        pop rax;
        pop rcx;
        pop rdx;
        pop rsi;
        pop rdi;
        pop rbp;
    }*/
    
}

 
//so被加载后会首先执行这里的代码
__attribute__((constructor))
void load()
{
  temp_func = (char*)mmap(tempAddr, 4096, PROT_WRITE|PROT_EXEC|PROT_READ, MAP_ANON|MAP_PRIVATE, -1, 0);
    func_hook(0x666666, (void*)test_hook);
 
    printf("inject suceessfully\n");
}

这里就是粗略的实现了,反正能用。。而且是从内网往回敲的,可能有点错误还,反正就是这么个思路,好多不是很优美,等后续有时间了不断完善可以

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
x64进程远程hookx64_远程调用函数,源码更新V1.8.2:2021/4/12 源码为下方连接帖子后续更新内容: 浅谈64位进程远程hook技术: https://bbs.125.la/forum.php?mod=viewthreadtid=14666356extra= 不管您是转载还是使用请保留版权,源码在精益论坛免费发布本人未获利,请不要用于非法途径。 --------------------------------------------------------------- 2021/4/12 模块源码 v1.8.2更新 1:修复 x64_远调用函数()在 易语言 主线程调用时造成消息无法回调,导致易语言主线程窗口卡死的问题。      感谢楼下易友发现的BUG,已经第一时间更新 2021/4/12 模块源码 v1.8.1更新 1:修复 hook全部卸载时的流程写法的一个错误,由于句柄的提前关闭导致多个hook点卸载不干净的问题 2:改写了消息回调时线程传参的代码优化,优化了其他一些小问题 3:  鉴于很多朋友需要,改写了模块自带实列,对TCP,UDP的两组封包函数做了hook实列写法 4:列子中同样增加对x64_远调用函数()的应用写了几个列子,如使用套接字取得本地或远端IP端口API调用的的应用实列 5:本hook模块不支持非模块内存区hook,如申请的动态分配页等,不是不能支持,只是觉得没有任何意义,对这方面有需求的,自行改写模块源码使用 提醒:hook回调函数中尽量减少耗时代码,时间越长返回越慢,回调中谨慎操作控件,如必须要用到可参考源码中实列写法采用线程操作 历史更新 --------------------------------------------------------------- 2021/3/1   模块源码v1.6更新: 1:修复  x64_远程调用函数()命令,在没有提供 寄存器 参数时,没有返回值的BUG。 --------------------------------------------------------------- 2021/2/28 模块源码v1.5更新: 一:修复win7 64位系统下枚举模块 出现部分模块长度出现负数的问题,从而导致部分win7用户不能使用 二:强化 远程hook64指令_安装 的稳定性:        1,穿插代码中增加对标志位的保护,避免hook位置长度下一条指令为跳转时产生跳转错乱的问题,强化了hook任意位置的定位        2,因为穿插代码中会调用API函数,而64位汇编必须遵守栈指针16字节对齐,故对穿插代码进行栈指针16字节对齐,增强稳定性        3,hook指令安装支持长度由6-127字节 变动 为 6-119字节,原因么没必要说了,代码优化造成的,稍微少了一点无所谓了        4,对模块回调进行了适当优化处理,增强稳定性 三:应支持的朋友需要故增加 x64_远程调用函数()命令,易语言可以直接远call64进程,且无需写汇编代码或机器码指令,支持15个参数,支持返回值,支持16个通用寄存器全部取得返回值       该功能调用即16字节栈对齐,不要用户管堆栈,代码内部构成,远线程执行,你只需要知道call有几个参数,需要什么寄存器,对应提供即可。 四:有朋友说原模块x64英文看了烦,那好吧就给改成了中文标识,弄得我自己也不习惯 五:源码内列子改了改,可以自己看,需要注意的是模块注释的很详细,使用前最好看一看,尤其是hook回调接口的写法和安装的写法最好按照模块列子中的写法来,除非你能把64hook模块组看懂一遍,对于一些对本模块一知半解的朋友请不要乱改乱发,这个模块我会继续增强的,只是工作原因时间有限,只能一点一点来
x64 Inline Hook(内联钩子)是一种在x64架构下实现的钩子技术,用于在程序运行时对函数进行修改或者监控。钩子技术可以用于实现一些高级功能,如函数拦截、行为修改和调试等。 Inline Hook的主要原理是通过修改函数的机器码,将目标函数的执行流程改变到一个特定的钩子函数,从而实现我们所需的功能。这个钩子函数可以进行一系列的操作,如记录参数、修改参数、替换返回值等。 x64 Inline Hook实现相对复杂,因为x64架构下的指令集更加复杂,并且x64架构引入了新的寄存器和指令,如RAX、R10、R11,还有新的调用惯例等。因此,在实现x64 Inline Hook前,我们需要对x64汇编指令和调用惯例有深入的了解。 具体实现Inline Hook主要包括以下几个步骤: 1. 定位到目标函数的地址。可以通过符号表、导入表或者动态调试等方式获取目标函数的地址。 2. 备份目标函数的原始字节码。为了在后续操作中恢复目标函数的完整执行流程,我们需要保留原始字节码。 3. 修改目标函数的字节码。通过修改目标函数的机器码,将执行流程转移到我们的钩子函数。 4. 编写钩子函数。钩子函数的参数和返回值需要与目标函数保持一致,并实现所需的功能。 5. 恢复目标函数的原始字节码。在钩子函数执行完毕后,需要将目标函数的字节码恢复到原始状态,以确保程序正常运行。 6. 跳回目标函数。在钩子函数执行完成后,我们需要将执行流程跳转回原始的目标函数。 需要注意的是,Inline Hook的实现需要考虑到多线程的情况,并且要保证对内存的修改是线程安全的,以及在恢复原始字节码时要避免潜在的问题。 总的来说,x64 Inline Hook是一种强大的技术,可以用于实现程序的函数修改和监控等高级功能。但它的实现相对复杂,需要对x64架构和汇编指令有深入的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值