![](/img/trans.png)
[英]Kernel sys_call_table address does not match address specified in system.map
[英]Linux Kernel 4.2.x: Why does the expected system call address not match the actual address when checked?
我目前正在编写一个 linux 内核模块作为一个项目,以更好地理解 linux 内核内部结构。 我以前写过“hello world”类型的模块,但我想超越它,所以我试图用我自己的替换一些常见的系统调用,如open
、 read
、 write
和close
,以便我可以print
一个将更多信息写入系统日志。
我在搜索时发现的一些内容是 2.6 之前的内核,这没有用,因为sys_call_table
符号在内核 2.6.x 上停止导出。 另一方面,我为 2.6.x 或更高版本找到的那些似乎有自己的问题,即使它们当时显然有效。
我在 linux kernel 2.6.18 post 中的sys_call_table上找到的一篇特别的O'Reilly 文章表明我正在尝试做的事情应该有效,但事实并非如此。 (具体参见Intercepting sys_unlink() Using System.map部分。)
我还通读了Linux Kernel: System call hooking example和Kernel sys_call_table address does not match address specified in system.map虽然有些信息量,但对我没有用。
我在 Kubuntu 15.10 x86_64 架构安装上使用 Linux 内核 4.2.0-16-generic。 由于sys_call_table
符号,不再出口,我grep
PED从系统映射文件地址:
# grep 'sys_call_table' < System.map-4.2.0-16-generic
ffffffff818001c0 R sys_call_table
ffffffff81801580 R ia32_sys_call_table
有了这个,我在内核模块中添加了以下行:
static unsigned long *syscall_table = (unsigned long *) 0xffffffff818001c0;
基于此,我期待一个简单的检查实际上会确认我实际上指向了我认为指向的位置,即内核未导出的sys_call_table
的基地址。 所以,我在模块的 init 函数中写了一个像下面这样的简单检查来验证:
if(syscall_table[__NR_close] != (unsigned long *)sys_close)
{
pr_info("sys_close = 0x%p, syscall_table[__NR_close] = 0x%p\n", sys_close, syscall_table[__NR_close]);
return -ENXIO;
}
此检查失败,日志中打印了不同的地址。
我没想到的这个身体if
得到执行的语句,因为我觉得通过返回的地址syscall_table[__NR_close]
将是相同的sys_close
,但它确实进入。
问题 1:到目前为止,我是否遗漏了有关预期的基于地址的比较的内容? 如果是这样,是什么?
如果我删除此检查,似乎我部分成功,因为显然,我至少可以使用以下代码成功替换read
调用:
static asmlinkage ssize_t (*original_read)(unsigned int fd, char __user *buf, size_t count);
// ...
static void systrap_replace_syscalls(void)
{
pr_debug("systrap: replacing system calls\n");
original_read = syscall_table[__NR_read];
original_write = syscall_table[__NR_write];
original_close = syscall_table[__NR_close];
write_cr0(read_cr0() & ~0x10000);
syscall_table[__NR_read] = systrap_read;
syscall_table[__NR_write] = systrap_write;
syscall_table[__NR_close] = systrap_close;
write_cr0(read_cr0() | 0x10000);
pr_debug("systrap: system calls replaced\n");
}
我的替换函数只是打印一条消息并将调用转发到实际的系统调用。 例如,读取替换函数的代码如下:
static asmlinkage ssize_t systrap_read(unsigned int fd, char __user *buf, size_t count)
{
pr_debug("systrap: reading from fd = %u\n", fd);
return original_read(fd, buf, count);
}
当我insmod
和rmmod
模块时,系统日志显示以下输出:
kernel: [23226.797460] systrap: setting up module
kernel: [23226.797462] systrap: replacing system calls
kernel: [23226.797464] systrap: system calls replaced
kernel: [23226.797465] systrap: module setup complete
kernel: [23226.864198] systrap: reading from fd = 4279272912
<similar output ommitted for brevity>
kernel: [23235.560663] systrap: reading from fd = 2835745072
kernel: [23235.564774] systrap: reading from fd = 861079840
kernel: [23235.564986] systrap: cleaning up module
kernel: [23235.564990] systrap: trying to restore system calls
kernel: [23235.564993] systrap: restored sys_read
kernel: [23235.564995] systrap: restored sys_write
kernel: [23235.564997] systrap: restored sys_close
kernel: [23235.565000] systrap: system call restoration attempt complete
kernel: [23235.565002] systrap: module cleanup complete
我可以让它运行很长时间,而且奇怪的是,我从来没有观察到write
和close
函数调用的条目——仅用于read
,这就是为什么我认为我只是部分成功。
Q2:我是否遗漏了有关替换系统调用的内容? 如果是这样,是什么?
rmmod
命令上的意外错误消息尽管该模块似乎运行正常,但当我从内核rmmod
模块时,总是出现以下错误:
rmmod: ERROR: ../libkmod/libkmod.c:506 lookup_builtin_file() could not open builtin file '(null)/modules.builtin.bin'
我的模块清理函数只是调用另一个(下面),它试图通过执行与上面的替换函数相反的操作来恢复函数调用:
// called by the exit function
static void systrap_restore_syscalls(void)
{
pr_debug("systrap: trying to restore system calls\n");
write_cr0(read_cr0() & ~0x10000);
/* make sure no other modules have made changes before restoring */
if(syscall_table[__NR_read] == systrap_read)
{
syscall_table[__NR_read] = original_read;
pr_debug("systrap: restored sys_read\n");
}
else
{
pr_warn("systrap: sys_read not restored; address mismatch\n");
}
// ... ommitted: same stuff for other sys calls
write_cr0(read_cr0() | 0x10000);
pr_debug("systrap: system call restoration attempt complete\n");
}
Q3:不知道是什么原因导致错误信息; 这里有什么想法吗?
sys_open
标记为弃用? 在另一个意外事件中,我发现__NR_open
宏不再默认定义。 为了让我看到定义,我必须在#include
头文件之前#define __ARCH_WANT_SYSCALL_NO_AT
:
/*
* Force __NR_open definition. It seems sys_open has been replaced by sys_openat(?)
* See include/uapi/asm-generic/unistd.h:724-725
*/
#define __ARCH_WANT_SYSCALL_NO_AT
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
// ...
浏览内核源代码(在上面的评论中提到),您会发现以下评论:
/*
* All syscalls below here should go away really,
* these are provided for both review and as a porting
* help for the C library version.
*
* Last chance: are any of these important enough to
* enable by default?
*/
#ifdef __ARCH_WANT_SYSCALL_NO_AT
#define __NR_open 1024
__SYSCALL(__NR_open, sys_open)
// ...
任何人都可以澄清:
Q4:...上面关于为什么__NR_open
默认不可用的评论?,
Q5:...使用#define
做我正在做的事情是否是个好主意?,以及
Q6:...如果我真的不应该尝试使用__NR_open
我应该使用__NR_open
?
我尝试使用__NR_openat
,像以前一样替换该调用:
static asmlinkage long systrap_openat(int dfd, const char __user *filename, int flags, umode_t mode)
{
pr_debug("systrap: opening file dfd = %d, name = % s\n", filename);
return original_openat(dfd, filename, flags, mode);
}
但这只是帮助我毫不客气地使我自己的系统崩溃 😑 通过导致其他进程在尝试打开文件时出现段错误,例如:
kernel: [135489.202693] systrap: opening file dfd = 0, name = P^Q
kernel: [135489.202913] zsh[11806]: segfault at 410 ip 00007f3a380abe60 sp 00007ffd04c5b550 error 4 in libc-2.21.so[7f3a37fe1000+1c0000]
尝试打印参数数据也显示奇数/垃圾信息。
Q7:关于为什么它会突然崩溃以及为什么这些参数看起来像垃圾一样,还有什么建议吗?
我花了几天时间试图解决这个问题,我只是希望我没有错过一些非常愚蠢的事情......
请让我知道如果您在评论中不完全清楚,我会尝试澄清。
如果您能提供一些实际工作的代码片段和/或指向我足够精确的方向,让我了解我做错了什么以及如何快速解决这个问题,我会非常有帮助。
我已经设法完成了这项工作,现在我正在花时间记录我的发现。
问题 1:到目前为止,我是否遗漏了有关预期的基于地址的比较的内容?
这个比较的问题是,在检查/proc/kallsyms
,我看到sys_close
和其他相关符号也不再导出。 我已经知道一些符号的这一点,但我仍然(错误地)认为其他一些符号仍然可用。 所以我使用的检查(下面)评估为真并导致模块未能通过“安全”检查。
if(syscall_table[__NR_close] != (unsigned long *)sys_close)
{
/* ... */
}
简而言之,您只需要相信从System.map-$(uname -r)
文件中检索到的系统调用表地址的假设。 “安全”检查是不必要的,也不会按预期工作。
Q2:我是否遗漏了有关替换系统调用的内容?
这个问题最终被追溯到我包含的以下头文件中的一个或两个(我没有费心去弄清楚是哪一个。):
#include <uapi/asm-generic/unistd.h>
#include <uapi/asm-generic/errno-base.h>
这些导致__NR_*
宏被重新定义,因此被扩展为不正确的值——至少对于 x86_64 架构。 例如,系统调用表中sys_read
和sys_write
的索引应该分别为0
和1
,但它们获得了其他值并最终索引到表中完全意外的位置。
只需删除上面的头文件即可解决问题,而无需更改其他代码。
Q3:不知道是什么原因导致错误信息; 这里有什么想法吗?
错误消息是上一问题的副作用。 显然,系统调用表的索引不正确(参见Q2 )导致内存中的其他位置被修改。
Q4: ...上面关于为什么
__NR_open
默认不可用的评论?
这是我停止使用的 IDE 的错误报告。 __NR_open
宏已经定义; Q2的修复使它更加明显。
Q5: ...使用
#define
做我正在做的事情是否是个好主意?
简短回答:不,不是一个好主意,绝对不需要。 参见上面的Q2 。
Q6:...如果我真的不应该尝试使用
__NR_open
我应该使用__NR_open
根据之前问题的答案,这不是问题。 使用__NR_open
很好并且可以预期。 由于第二季度的头文件,这部分已经搞砸了
Q7:关于为什么它会突然崩溃以及为什么这些参数看起来像垃圾一样,还有什么建议吗?
__NR_openat
的使用和崩溃可能是由宏被扩展为错误值引起的(再次参见Q2 )。 但是,我可以说我没有真正需要使用它。 我应该按照上面的说明使用__NR_open
,但尝试使用__NR_openat
作为解决Q2 中修复的问题的解决方法。
简而言之, Q2的答案有助于解决级联效应中的几个问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.