简单linux 下 x64任意地址的inlinehook

article/2024/3/2 12:08:00

背景

最近工作需要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的功能,顾暂时不需要了

进行跳转:

这里网上资料主要都是使用了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 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;}*/}//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");
}

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


http://www.ngui.cc/article/show-1007591.html

相关文章

谈谈计算机的本质

依托于我现在浅显的认知&#xff0c;我觉得计算机其实就是在处理两大问题&#xff1a;输入输出&#xff08;IO&#xff09;和计算。 输入输出&#xff08;IO&#xff09;包括硬件的IO以及网络IO。 计算包括各种算法甚至是现在大火的人工智能。 操作系统是一个超大的基础软件…

@Transactional和synchronized同时使用时的一些问题以及解决

Transactional和synchronized同时使用并不能保证事务一致性背景任何事情都有一个发生背景有个需求【一个业务里面包含多个事务,而且还需要避免其他线程的影响,所幸的是该服务只需要启动单实例,不然还要考虑分布式的影响】我的思路就是用Transactional 和 synchronized来保证事务…

记一次前端cookie冲突,导致同一个浏览器其他系统被踢下线问题分享

背景: 首先我在是公司的一个职能部门&#xff0c;所做的软件主要是服务于公司内部员工使用&#xff0c;员工可以通过工号来进行登录&#xff0c;也可以通过其他方式登录&#xff0c;所以整个公司提供了一个统一身份管理平台来员工身份认证、权限进行集中式的管理&#xff0c;实…

三、Trino406系列 之 客户端

文章目录前言客户端命令行要求条件客户端安装Running the CLITLS/HTTPSJDBC驱动需求条件安装Registering and configuring the driverConnectingConnection parametersParameter reference前言 https://trino.io/docs/current/client/cli.html 客户端是向trino server提交sql查…

华为OD机试题【字符匹配】用 Java 解 | 含解题说明

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:字符匹配 题目 给你一个字符串…

Golang流媒体实战之一:体验开源项目lal

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 关于《Golang流媒体实战》 因为工作需要&#xff0c;开始了流媒体开发学习&#xff0c;于是打算选择一个Go版本的开源流媒体服务器作为学习方向lal是个不错的…

OpenWrt开启IPV6设置教程

OpenWrt配置OpenWrt->网络->接口->全局网络选项->清空!WAN 接口配置1.网络->接口->WAN->高级设置2.不勾选 “使用内置的 IPv6 管理”3.“Obtain IPv6-Address”设置为 自动。LAN 接口设置网络->接口->LAN->高级设置不勾选 “使用内置的 IPv6 管理…

TENER: Adapting Transformer Encoder for Named Entity Recognition 笔记

TENER: Adapting Transformer Encoder for Named Entity RecognitionAbstract&#xff08;摘要&#xff09;1 Introduction&#xff08;介绍&#xff09;2 Related Work&#xff08;相关工作&#xff09;2.1 Neural Architecture for NER&#xff08;NER 的神经网络架构&#x…

【实战】16.Vue Router 入门

简介 vue-router 是 Vue.js 官方提供的路由管理器,用于实现单页应用(Single Page Application, SPA)中的视图切换和页面导航。 vue-router 基于 Vue.js 的组件化思想,将路由信息抽象成组件,可以通过声明式的方式定义路由,将路由与组件映射起来,同时还支持路由导航守卫、…

实验十 图着色问题

《算法设计与分析》实验报告 所在院系 计算机与信息工程学院 学生学号 学生姓名 年级专业 2020级计算机科学与技术 授课教师 彭绪富 学 期 2022-2023学年第一学期 提交时间 2022年11月16日 目录 实验十-1&#xff1a;图着色问题 一、实验目的…