我演示了Linux系统中return probe的原理并给出了一个相当简单的示例程序:
https://blog.csdn.net/dog250/article/details/106547963
在文章最后,我提到全文描述的方法竟然是不优雅的,并且给出了优雅的机制应该是:
我无意去做冗长且毫无意义的Linux内核源码分析,这真的毫无意义,如果你面对别的操作系统怎么办?但如果你理解了上面的图示,任何操作系统下,你都能得到一样的效果,至少基于x86_64/ARM体系结构的所有操作系统,你都能搞得定。
回头有时间我来演示一下Windows系统关于return probe的内核玩法。
好了,既然上一篇文章仅仅演示了原理,方法不甚优雅,那么本文我来演示一个稍微更加优雅点的示例:
// ndebug.c
#include <stdio.h>
#include <sys/mman.h>
#include <signal.h>
#define PC_OFFSET 192
#define SP_OFFSET 184
#define I_BRK 0xcc
unsigned long *orig;
void trap(int unused);
extern unsigned long asm_stub;
void retprobe(unsigned long addr)
{
unsigned char *page;
signal(SIGTRAP, trap);
page = (unsigned char *)((unsigned long)addr & 0xffffffffffff1000);
mprotect((void *)page, 4096, PROT_WRITE|PROT_READ|PROT_EXEC);
orig = (unsigned long *)*(unsigned long *)addr;
*(unsigned char *)(addr) = I_BRK;
}
// 刚刚进入函数时int3陷入,发送SIGTRAP,trap为信号处理函数。
// (对于内核态处理,则要register一个notifier block)
void trap(int unused)
{
unsigned long *p;
p = (unsigned long *)((unsigned char *)&p + PC_OFFSET);
*p = *p - 1;
*(unsigned long *)*p = (unsigned long)orig;
orig = (unsigned long *)*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET);
*(unsigned long *)*(unsigned long *)((unsigned char *)&p + SP_OFFSET) = (unsigned long)&asm_stub;
}
// ret_handler 为return probe的handler,参数regs布局参见stub.s的push顺序。
void ret_handler(void *regs)
{
// 显然,regs的第一个字段就是RAX寄存器。
printf("ZheJiang WenZhou skinshoe wet, rain flooding water not fat! :%d\n", *(int *)regs);
// 改掉eax返回值!
*(int *)regs = 404;
}
// 输入什么,返回什么,即echo函数。
int test_function(int ret)
{
printf("[test function]\n");
return ret;
}
int main(int argc, char **argv)
{
int ret = 0;
// 准备probe test_function函数,因此要在该函数最开始打断点。
retprobe((unsigned long)&test_function);
ret = atoi(argv[1]);
printf("before call: %d\n", ret);
ret = test_function(ret);
printf("after call: %d\n", ret);
}
嗯,看代码中写了,有个asm_stub是extern的,对于我这种不会编程的,所有函数都写在一个文件中的,只有一种情况会extern,那就是asm_stub不是C写的。
asm_stub是汇编写的,下面是代码stub.s:
; stub.s
section .text
global asm_stub
extern ret_handler
extern orig
asm_stub:
push rbx ; 保存必要的寄存器上下文
push rbp
push rsp
push r13
push r12
push rsi
push rdi
push rax
mov rdi, rsp ; 准备ret_handler的参数:以上save的所有寄存器
call ret_handler
pop rax ; 恢复寄存器上下文
pop rdi
pop rsi
pop r12
pop r13
pop rsp
pop rbp
pop rbx
mov r11, [orig] ; 恢复原始执行流
push r11
ret
最后给出Makefile:
default: ndebug.o stub.o
gcc -o ndebug ndebug.o stub.o
ndebug.o: ndebug.c
gcc -c ndebug.c -O0
stub.o: stub.s
nasm -f elf64 stub.s -o stub.o
看效果:
[root@localhost probe]# make
gcc -c ndebug.c -O0
nasm -f elf64 stub.s -o stub.o
gcc -o ndebug ndebug.o stub.o
[root@localhost probe]# ./ndebug 12345
before call: 12345
[test function]
ZheJiang WenZhou skinshoe wet, rain flooding water not fat! :12345
after call: 404
有没有意义不要管,有意思就行。
浙江温州皮鞋湿,下雨进水不会胖。