我正在做一个拦截内核系统调用的内核模块。拦截,或者只是用普通C中的伪系统调用地址替换真实的系统调用地址就像1-2-3一样简单。但是我想知道它在低级别上是如何工作的。
(让我假装我在x86上)
首先,我只做了一个基本的测试:我kalloc
使用一小块可执行内存并用这个操作码填充它:
0xB8, 0x00, 0x00, 0x00, 0x00, //mov eax, &real_syscall_function;
0xFF, 0xE0, //jmp eax;
插入模块并更换系统调用非常完美。
现在,根据this SO answer,参数在寄存器中传递。 我想检查一下,所以我创建了一个可执行的内存块并用这段代码填充它:
0x55, //push ebp;
0x89, 0xE5, //mov ebp, esp;
0x83, 0xEC, 0x20, //sub esp, 32;
0xB8, 0x00, 0x00, 0x00, 0x00, //mov eax, &real_syscall_function;
0xFF, 0xE0, //jmp eax;
0x89, 0xEC, //mov esp, ebp;
0x5D, //pop ebp;
0xC3 //ret;
这也应该有用,因为我没有触及任何寄存器,我只是在玩堆栈,但它不起作用。这让我觉得参数实际上是在堆栈上传递的。但为什么?我理解与错误相关的SO答案吗?调用系统调用时,是否应该将args放在寄存器中?
额外问题:为什么使用jmp eax
有效,但call eax
不起作用? (这适用于第一个和第二个示例代码)。
编辑:对不起,我错过了一些ASM代码中的评论。我jmp
的内容是真实系统调用函数的地址。
编辑2 :我认为这很明显,但无论如何我会解释它,以防万一有人不理解我在做什么。我正在分配一小块可执行内存,用我正在显示的操作码填充它,然后使给定的系统调用(假设__NR_read
)指向该可执行内存块的地址。
工作得很完美 ==系统保持运行没有问题。这意味着真正的系统调用是从伪系统调用
调用的它不起作用 ==系统崩溃,因为假的系统调用没有调用真正的系统调用
答案 0 :(得分:1)
Syscall参数首先从用户空间通过寄存器传递给system_call()函数,该函数本质上是一个常见的系统调度程序。但是,system_call()然后以通常的方式调用实际的系统调用函数,例如sys_read(),通过堆栈传递参数。因此,搞乱堆栈会导致崩溃。 另外,请参阅此SO答案:https://stackoverflow.com/a/10459713以及关于quora的非常详细的解释:http://www.quora.com/Linux-Kernel/What-does-asmlinkage-mean-in-the-definition-of-system-calls#step=6(需要注册)。