首页 > 系统相关 >Linux系统下的HOOK

Linux系统下的HOOK

时间:2024-04-10 13:30:07浏览次数:32  
标签:函数 int 系统 pid regs HOOK ptrace Linux

HOOK通过在系统调用或函数调用前以替换的方式改变程序中原有的函数功能,实现更改原有函数的功能。

利用LD_PRELOAD进行HOOK

Linux提供了一个名为LD_PRELOAD的环境变量。这个环境变量允许用户指定一个或多个共享链接库文件的路径。当程序启动时,动态加载器会在加载C语言运行库之前,首先加载LD_PRELOAD所指定的共享链接库。这种加载方式被称为预装载。

预装载机制使得用户可以在程序执行前插入自定义的共享链接库,从而改变或扩展程序的行为。这些自定义的共享链接库可以包含重写的函数定义,当程序尝试调用这些函数时,动态加载器会优先加载并执行预装载的库中的函数定义,而不是默认的库中的定义。结合LD_PRELOAD和预装载机制,我们可以实现对函数的HOOK。

首先我们编写一个目标程序代码,这个程序会等待用户的输入,从而处于阻塞状态。

#include <stdio.h>

int main()
{
  printf("please input a number:\n");
  int val = 0;
  scanf("%d", &val);
  printf("already recv your number!\n");
  return 0;
}

然后编写HOOK函数,该函数重写了scanf函数,打印出一句话,从而使目标程序便能够无需等待用户输入而继续执行。

#include <stdio.h>

int scanf(const char* restrict format, ...)
{
	printf("Hook function\n");
	return 0;
}

通过以下命令分别编译目标程序和用于HOOK的so文件。

gcc ./target.c -o target
gcc --shared hook.c -o hook.so -fPIC

执行下述命令实现HOOK。可以看到scanf函数由原先的等待用户输入,变成了输出一句话。

LD_PRELOAD=./hook.so ./target

图片

利用ptrace进行HOOK

然而上述方法只能对未运行的程序进行HOOK,对于已经运行的程序可以利用ptrace系统调用进行HOOK。

ptrace允许一个进程监控和控制另一个进程的执行,是GDB等调试器实现的基础。利用ptrace进行HOOK的步骤如下所示:

1.HOOK程序利用ptrace附加到已经运行的目标程序,获取目标进程运行的上下文,保存原寄存器数据;
2.查找目标程序的link_map链表的指针,根据函数名称遍历查找函数的真实地址。link_map地址存在于.got.plt区节中,该区节的加载地址可以从DYNAMIC段DT_PLTGOT区域得到。在此我们主要查找dlopen函数地址;
3.通过修改目标程序的寄存器和堆栈使目标程序调用dlopen函数,从而将hook.so加载到目标程序中;
4.将要HOOK的原函数地址,替换为hook.so中重写后的新函数地址。因为hook.so在上一步被dlopen加载到目标内存空间中,所以重写后的新函数地址可以通过步骤2得到;
5.恢复目标程序原寄存器的内容,传入PTRACE_DETACH参数结束对目标程序的附加。

接下来详细介绍具体的实现:

利用ptrace附加目标程序,传入user_regs_struct结构体用于保存目标程序的寄存器信息。

void ptrace_attach(pid_t pid, struct user_regs_struct *regs)
{
    if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0)
    {
        printf("ptrace_attach error\n");
    }
    
    waitpid(pid, NULL, WUNTRACED);
    
    if(ptrace(PTRACE_GETREGS, pid, NULL, regs))
    {
        printf("ptrace_getregs error!\n");
    }
}

这里省略了查找link_map链表指针,该链表可以通过解析ELF文件结构获取到。主要看如何遍历link_map链表查找指定函数的地址。

在find_symbol函数中通过link_map链表指针获取link_map结构体内容,根据link_map中l_name字段判断该动态链接库是否有效。

在find_symbol_in_linkmap函数中,通过解析link_map中l_ld字段,获取动态链接库的符号表等信息,与指定函数名称进行对比,获取该函数的地址。

Elf_Addr find_symbol(int pid, Elf_Addr lm_addr, char *sym_name)
{
    struct link_map lmap;//存储lmap的内容
    unsigned int nlen = 0;

    while (lm_addr)
    {
        // 有了link_map指针后,根据指针获取link_map结构体的内容
        ptrace_getdata(pid, lm_addr, &lmap, sizeof(struct link_map));
    // 获取下一个link_map结构体的指针
        lm_addr = (Elf_Addr)(lmap.l_next);
        // 判断动态链接库是有否有效
        if (0 == lmap.l_name)
        {
            continue;
        }
        Elf_Addr sym_addr = find_symbol_in_linkmap(pid, &lmap, sym_name);
        if (sym_addr)
        {
            return sym_addr;
        }
    }
    return 0;
}

通过上一步的find_symbol函数,可以得到dlopen的函数地址,模拟调用dlopen函数,设置栈空间将要加载的so库绝对路径写入栈地址,调用dlopen加载so库到目标地址中。

int inject_code(pid_t pid, unsigned long dlopen_addr, char *libc_path)
{
    char sbuf1[STRLEN], sbuf2[STRLEN];
    struct user_regs_struct regs, saved_regs;
    int status;

    ptrace_getregs(pid, &regs);//获取所有寄存器值
    ptrace_getdata(pid, regs.rsp + STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_getdata(pid, regs.rsp, sbuf2, sizeof(sbuf2));

    /*用于引发SIGSEGV信号的ret内容*/
    unsigned long ret_addr = 0x666;

    ptrace_setdata(pid, regs.rsp, (char *)&ret_addr, sizeof(ret_addr));
    ptrace_setdata(pid, regs.rsp + STRLEN, libc_path, strlen(libc_path) + 1);

    memcpy(&saved_regs, &regs, sizeof(regs));

    regs.rdi = regs.rsp + STRLEN;
    regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE;
    regs.rip = dlopen_addr+2;

    if (ptrace(PTRACE_SETREGS, pid, NULL, &regs) < 0)
    {
        printf("inject_code:PTRACE_SETREGS 1 failed!");
    }
    if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
    {
        printf("inject_code:PTRACE_CONT failed!");
    }
    waitpid(pid, &status, 0);
    if (ptrace(PTRACE_SETREGS, pid, 0, &saved_regs) < 0)
    {
        printf("inject_code:PTRACE_SETREGS 2 failed!");;
    }
    ptrace_setdata(pid, saved_regs.rsp + STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_setdata(pid, saved_regs.rsp, sbuf2, sizeof(sbuf2));
    return 0;
}

接下来查找目标程序的got表,修改目标函数的got表内容为新函数的地址。

Elf_Addr find_sym_in_rel(int pid, char *sym_name)
{
    Elf_Rel *rel = (Elf_Rel *) malloc(sizeof(Elf_Rel));
    Elf_Sym *sym = (Elf_Sym *) malloc(sizeof(Elf_Sym));
    int i;
    char str[STRLEN] = {0};
    unsigned long ret;
    struct lmap_result *lmret = get_dyn_info(pid);

    for (i = 0; i<lmret->nrelplts; i++)
    {
        ptrace_getdata(pid, lmret->jmprel + i*sizeof(Elf_Rela), rel, sizeof(Elf_Rela));
        ptrace_getdata(pid, lmret->symtab + ELF64_R_SYM(rel->r_info) * sizeof(Elf_Sym), sym, sizeof(Elf_Sym));
        int n = ptrace_getstr(pid, lmret->strtab + sym->st_name, str, STRLEN);
        if (strcmp(str, sym_name) == 0)
            break;
    }
    if (i == lmret->nrelplts)
        ret = 0;
    else
        ret = rel->r_offset;
    free(rel);
    return ret;
}

在这里我们修改一下目标程序,循环10次,每次接收控制台输入,并打印出来。

#include <stdio.h>
#include <time.h>
int main()
{
  int val = 10;
  while (val--)
  {
        sleep(2);
    printf("please input a number:\n");
    int val = 0;
    scanf("%d", &val);
    printf("your val is %d\n", val);
  }

  return 0;
}

在HOOK代码中,自动为val变量赋值。

#include <stdio.h>
#include <stdarg.h>
int num = 1;
int hookscanf(const char *format,...)
{
    va_list ap;
    int retval;
    va_start(ap, format);
    int* pval = va_arg(ap, int*);
  printf("自动输入:%d\n", num);
    *pval = num++;
    return 0;
}

编译运行,效果如下,不需要在控制台进行输入,自动为val赋值。

图片

小结

本文介绍了两种linux系统下常见的HOOK方法,第一种方法比较简单但无法对已经运行的程序进行HOOK,第二种方法相较于第一种会更加复杂,需要对ELF文件格式以及相关结构有更深入的了解。

图片

标签:函数,int,系统,pid,regs,HOOK,ptrace,Linux
From: https://blog.csdn.net/We8__/article/details/137592341

相关文章

  • 基于SpringBoot+MySQL+SSM+Vue.js的宠物商城系统(附论文)
    演示视频基于SpringBoot+MySQL+SSM+Vue.js的宠物商城系统技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Vue/ElementUI后端框架:Spring+SpringMVC+Mybatis+SpringBoot文字描述基于SpringBoot+MySQL+SSM+Vue.js的宠物商城系统(附论文),用......
  • GPS时钟服务器(北斗对时服务器)在地铁自控系统应用方案
    GPS时钟服务器(北斗对时服务器)在地铁自控系统应用方案GPS时钟服务器(北斗对时服务器)在地铁自控系统应用方案京准电子科技官微——ahjzsz一、时钟系统基本描述1、时钟系统概述时钟系统是轨道交通系统的重要组成部份之一,其主要作用是为控制中心调度员、车站值班员、各部门工作人......
  • 创建网络名称空间后的Linux幕后工作解析
    Linux网络名称空间(NetworkNamespace)是一种强大的虚拟化技术......
  • java计算机毕业设计二手车平台交易系统(附源码+springboot+开题+论文+部署)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着经济的不断发展与人们生活水平的提高,汽车已成为人们日常生活中不可或缺的交通工具。然而,二手车市场的快速发展也带来了一系列问题,如信息不对称、......
  • COP4600 文件系统实现细节
    P3:文件系统概述你在蜥蜴军团的掩护被揭穿了,你被揭露为双重间谍并被驱逐出去!是的都很“詹姆斯·邦德”,如果你自己这么说的话,那是多么大胆的地下直升机逃生……但是你感到很幸运能带着你的皮肤逃脱。(从字面上看……他们会用你做一套“人体服”!)现在你又回到了“外部”,你的任务是创建......
  • 基于SSM+MySql+Layui的在线教育视频课程管理系统(附论文)
    演示视频基于SSM+MySql+Layui的在线教育视频课程管理系统(附论文)-源码乐园技术描述开发工具:Idea/Eclipse数据库:mysqlJar包仓库:Maven前段框架:LayUI后端框架:Spring+SpringMVC+Mybatis+MySQL文字描述基于SSM+MySql+Layui的在线教育视频课程管理系统,分为用......
  • 基于SSM+SpringBoot+MySQL+Element+Vue的鲜花销售商城系统(附论文)
    演示视频基于SSM+SpringBoot+MySQL+Element+Vue的鲜花销售商城系统(附论文)-源码乐园技术描述开发工具:Idea/Eclipse数据库:MySQLJar包仓库:Maven前端框架:Element/Vue后端框架:Spring+SpringMVC+Mybatis+Element+Vue文字描述基于SSM+SpringBoot+MySQL+Element......
  • 磁盘管理与文件系统
    一、硬盘的物理结构硬盘的分类:1.机械硬盘:靠磁头转到找数据  慢 便宜2.固态硬盘:靠芯片去找数据 快 贵1.1磁盘的硬件架构扇区:一个扇区512字节,是磁盘的最小单位磁道:同一盘片不同半径的同心圆柱面:不同盘片相同半径构成的圆(柱面和磁道数量相同)公式去算你磁盘的......
  • 基于SSM果蔬购物管理系统
    目录项目介绍:图片展示获取方式项目介绍:基于ssm的果蔬,水果,蔬菜购物管理系统用户划分,用户管理员用户,具有登录,注册,查看首页,蔬菜专区进行购买蔬菜,水果专区进行购买水果,我的账号,可以查看自己的信息,添加收货地址,以及确认收货等加入购物车添加到收藏以及购买支付等......
  • 基于ssm+vue.js的社区团购系统附带文章和源代码设计说明文档ppt
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我成功案例代码参考数据库参考源码获取前言......