Linux系统演示更优雅的return probe示例程序

我演示了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

有没有意义不要管,有意思就行。


浙江温州皮鞋湿,下雨进水不会胖。

猜你喜欢

转载自blog.csdn.net/dog250/article/details/106571680