运行中动态添加系统调用

Linux中每次添加一个系统调用都要完成重新编译内核,然后制作initrd等工作,不得不说这是一件繁重的工作,很多人本来已经构思好了自己的一个系统调用,要添加到内核,然后却被这些工作所中断,毫不夸张的说,制作initrd就很麻烦,虽然基于cpio的initrd可以利用几条命令完成,然而只要有一个错误,你就不得不重启系统。

我们都知道,内核模块运行在内核态,可以访问所有的内存空间,那么能不能在系统运行期间,不用重新编译内核,而运用内核模块机制实现添加系统调用呢?答案是肯定的,因为一个基本原理:内核态能读写所有的内存,内存全部在我们手中,没有什么事情做不到, 那么怎么做到呢?

如果我们了解了系统调用执行的过程,那么我们就知道在执行一个系统调用之前,有两个限制,第一就是系统调用数量在编译时定死,系统调用号必须在这个被允许的系统调用号区间内,第二就是系统调用表的地址未导出。 由于系统调用入口也是一种中断/异常,因此我们看一下代码,看个究竟,基于i386的内核代码在arch/i386/kernel/entry.S(基于2.6.8内核,虽然有点老,但能说明问题),我们看一下系统调用的入口

system_call:    ENTRY(system_call)            pushl %eax                      # save orig_eax            SAVE_ALL            GET_THREAD_INFO(%ebp)            cmpl $(nr_syscalls), %eax    #限制系统调用号在允许的范围内            jae syscall_badsys                                            # system call tracing in operation            testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)            jnz syscall_trace_entry    syscall_call:            call *sys_call_table(,%eax,4)  #调用系统调用,其中sys_call_table是系统调用表的地址            movl %eax,EAX(%esp)             # store the return value    ...  

通过以上的代码可以看出,如果我们想添加一个新的系统调用,第一,它的系统调用号不能超过nr_syscalls,第二,它的地址必须在系统调用表地址加上系统调用号*4的位置处, 也就是说,满足这两个条件,内核逻辑就会将执行流路由到我们调用的系统调用服务程序。

如果我们希望在系统运行期动态添加一个系统调用,那么就必须破除上述的两个神话。第一,我们必须修改nr_syscalls的值,第二,我们必须修改sys_call_table的值,并且新的sys_call_table内容值必须保留原有的sys_call_table内容值。 这个需求怎么做到呢?很简单,扫面二进制机器码!记住,内存在我们手中,还要记住,我们的这次行为不是攻击行为,只是为了调试一个新增加的系统调用而不想重新编译内核...!!!

到底怎么做到呢?首先,我们只需要修改掉system_call中的cmpl $(nr_syscalls), %eax语句中的nr_syscalls,我们首先从/proc/kallsym中取得system_call的地址,然后在其下面搜索cmpl $(nr_syscalls), %eax的机器码,搜到后将nr_syscalls修改;其次我们在/boot/Sysmap中取得sys_call_table的地址,然后在system_call的下面搜索这个地址的机器码,取得后我们将其修改为新的系统调用表的地址,需要指出的是,新的系统调用表的前nr_syscalls个字段必须和原始的系统调用表sys_call_table的相同。另外,如何获得sys_call_table的地址,这个技术值得商榷,其实有很多方法,本文使用最直接的方式,那就是从/boot/Sysmap中获取。

有一个细节,那就是如何在system_call附近寻找匹配检测系统调用号范围的机器码呢?最简单的方式莫过于模拟, 那就是写一个程序,调用cmpl $(nr_syscalls), %eax,其中nr_syscalls在2.6.8中为284,那么我们就写下面的代码:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwfxfs.html