在linux 2.4版本的内核中,使用了导出符号表sys_call_table.因此,很容易通过导出符号表来找到系统调用表的地址,因此给攻击程序提供了方便的策略。因此,为了安全,在2.6内核版本中,系统调用表不再被导出。下面给出在2.6内核代码中获得系统调用表,实现系统劫持的过程。(修改sys_mkdir系统调用为例)。
实现代码:
#include <linux/kernel.h>
#include <linux/init.h> #include <linux/module.h> #include <asm/unistd.h> MODULE_DESCRIPTION("My kernel module"); MODULE_AUTHOR("root (root@10h212. MODULE_LICENSE("$LICENSE$"); struct idt struct idt_gate unsigned int get_idt_base() unsigned int get_sys_call_entry(unsigned int idt_base) unsigned int get_sys_call_table_entry(unsigned int sys_call_entry,char * exp,char exp_len,unsigned int cope)
void setback_cr0(unsigned int val) // new mkdir module_init(syscall_info_init_module); 实现原理: 在linux中使用0x80 异常实现系统调用,因此,主要的实现路径:获得中断向量表->获得系统调用中断处理函数地址->获得系统调用符号表->修改对应变量的偏移值指向新的系统调用程序(后门程序)。
一,中断像量表的获取。
在x86中,idtr寄存器使得中断向量表可以存放在内存的任何位置,idtr寄存器有一个基地址和一个段限组成,高四字节为基地址,低两字节为段限。可以通过sidt指令获得idtr的内容。
struct idt
{ unsigned short limit; unsigned int base; }__attribute__((packed)); |
__asm__ __volatile__("sidt %0":"=m"(idt_table));
上面语句通过sidt将idtr的内容付给idt_table,通过idt_table.base即可得到idt的基地址。
二,系统调用处理函数地址的获取:
IDT基地址存放的是中断门,每个门8个字节,门描述符的格式参考intel开发手册,其中,中断门的最低两个字节和最高两个字节构成了中断处理程序的地址。获得系统调用中断(异常)处理程序的地址
memcpy(&sys_call,idt_base+8*0x80,sizeof(struct idt_gate));
unsigned int sys_call_entry=(sys_call.off2 << 16) | sys_call.off1;
return sys_call_entry;
通过sys_call结构体的off2字段和off1字段来获得系统调用处理程序的地址。
中断门描述符:
struct idt_gate
{
unsigned short off1;
unsigned short sel;
unsigned char nome,flags;
unsigned short off2;
}__attribute__((packed));
三,获得系统调用表。
我们的目的是修改系统调用地址,从而修改系统处理程序地址。sys_call是所有系统调用的处理程序,在进行一些必要的处理后,统一调用
call sys_call_table(,eax,4)来调用系统调用表中的系统调用程序,eax存放的即系统调用号,因此,获取sys_call_table的地址即可以达到目的。
通过反汇编sys_call函数,可以得知,只有在调用系统调用处使用了call指令,x86 call指令的二进制格式为\xff\x14\x85,因此,我们可以从sys_call函数开始进行搜索,当出现\xff\x14\x85指令的时候,即为call的地址,从而能得到存放sys_call_table的地址即当前地址+3,而系统调用表即地址的内容,因此,获取系统调用表地址的实现过程就简单了。
unsigned int get_sys_call_table_entry(unsigned int sys_call_entry,char * exp,char exp_len,unsigned int cope)
{
char * begin=sys_call_entry;
char * end=sys_call_entry+cope;
for(;begin<end;begin++)
{
}
return 0;
}
这是通过简单的搜索的方式来找到call 指令,从而得到sys_call_table的地址的。
四,劫持系统调用
首先,准备了一个替代的系统调用
asmlinkage long my_mkdir(const char *name,int mod)
{
printk(KERN_ALERT"mkdir call is intercepted\n");
}
然后设置cr0寄存器的wp位,然后修改mkdir系统调用地址,设置为my_mkdir的地址。
注:如果不清楚cr0的wp位,则出现段错误,原因是,对相关页实现了写保护策略,因此,。应该通过cr0寄存器wp位置来使得页表保护无效,修改后,再设置为原来的值。
这样,将模块加载到内核后,即可以实现系统调用的劫持。在terminal中输入mkdir命令,即可发现,不能创建文件夹了。