本文介绍Linux环境下正在运行进程的函数替换,不改变该进程的可执行文件内容。通过使用汇编指令JMP完成运行中进程的函数替换。为了更好地理解本文所述内容,我们需要了解以下几个知识点:
1) 了解汇编JMP指令实现无条件跳转,远跳转,近跳转;
-- E9:本文所用
-- EA:
-- EB:
2) 汇编指令JMP与call的区别;为什么不能使用call来实现本文所述功能?
-- call指令跳转前会将下一条语句入栈,与ret指令结合完成函数调用
-- jmp指令直接修改指令寄存器IP的值
3) 如何控制一个正在运行的进程,并修改该进程代码空间的内容?
-- 想想Linux中的GDB;
-- 系统调用ptrace;
4) 补码的表示,负数的补码是多少?正数的补码是多少?
-- 正数的补码与本值相同;
-- 负数的补码是取反再加1,且最高位置1;
5)
如上图所示,将
打完补丁之后,进程运行如下:
mytest进程不间断,while循环中的myprint替换成new_myprint函数,完成功能。下面来看看具体实现~~~
如上图所示,将jmp.c编译成可执行文件jmp(就是打补丁的工具)。
实现原理是:
1) 计算jmp指令的偏移量:记为offset
> jmp的偏移量:目标地址与下一条指令地址的差值
> 下一条指令地址:即是待替换函数地址+jmp跳转指令长度
> offset:占用4字节,表示可跳转空间达4G,如果进程的跳转超过4G,则失败;
2) 构造JMP指令(共5字节):E9 XX XX XX XX
> LINUX有大小端之分,GDB内存查看时要注意;
2) 使用ptrace系统调用控制mytest进程(PTRACE_ATTACH);
3) 使用ptrace系统调用修改进程空间的代码段内容;
> PTRACE_POKEDATA:将E9 XX XX XX XX写入到进程的myprint函数地址处,完成函数替换;
4) 使用ptrace系统调用放弃进程控制(PTRACE_DETACH)
1) 首先,我们要获取myprint函数地址,和new_myprint函数地址
> 本文源码很简单,可直接使用Linux自带的命令objdump就可查看:
myprint函数地址:0x400580(4195712)
new_myprint函数地址:0x400591(4195729)
> 实际使用中,新函数往往是动态库,只能通过dlopen、dlsym等函数查询;
2) 运行mytest程序,获取进程号:pid = 45151
3) gdb调用mytest,查看myprint函数的汇编代码(0x400580)
4) 退出gdb, 且执行jmp进行补丁
5) 再次gdb查看myprint函数的汇编代码
> 0x400580地址处的push %rbp变成了jmpq 0x400591,实现无条件跳转到new_myprint
> 注意,为了不破坏整个函数调用栈桢的顺序,只能在push %rbp进行跳转
6) 函数替换成功