我正在做一个拦截内核系统调用的内核模块。拦截,或者更确切地说,只是用普通 C 中的假系统调用地址替换真实的系统调用地址就像 1-2-3 一样简单。但我想知道它在低级别上是如何工作的。
(假设我在 x86 上)
首先,我只是在做一个基本测试:我正在
kalloc
ating一小块可执行内存并用这个操作码填充它:
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
ing到的是真正的系统调用函数的地址。
Edit 2:我认为这很明显,但无论如何我都会解释它以防万一有人不理解我在做什么。我正在分配一个小的可执行内存块,用我正在显示的操作码填充它,然后使给定的系统调用(比方说
__NR_read
)指向该可执行内存块的地址。
工作得非常完美 == 系统保持运行没有问题。这意味着真正的系统调用是从假系统调用
它不起作用 ==系统崩溃,因为假系统调用没有调用真正的系统调用
Syscall 参数首先通过寄存器从用户空间传递到
system_call()
函数,这本质上是一个通用的系统调用调度程序。然而system_call()
然后以通常的方式调用真正的系统调用函数,例如sys_read()
,通过堆栈传递参数。因此,弄乱堆栈会导致崩溃。
另外,请参阅此 SO 答案:https://stackoverflow.com/a/10459713 以及关于 quora 的非常详细的解释:http://www.quora.com/Linux-Kernel/What-does-asmlinkage-mean-在系统调用的定义中#step=6(需要注册)。