Chinaunix首页 | 论坛 | 博客
  • 博客访问: 601758
  • 博文数量: 353
  • 博客积分: 1104
  • 博客等级: 少尉
  • 技术积分: 1457
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-23 23:02
个人简介

1、刚工作时做Linux 流控;后来做安全操作系统;再后来做操作系统加固;现在做TCP 加速。唉!没离开过类Unix!!!但是水平有限。。

文章存档

2015年(80)

2013年(4)

2012年(90)

2011年(177)

2010年(1)

2009年(1)

分类:

2011-12-08 10:49:37

本文欢迎自由转载,但请标明出处和本文链接,并保持本文的完整性。

CU Godbach
     Blog
http://Godbach.cublog.cn

Dec 2, 2009


二、实现原理分析

       实现的方法就是通过中断向量表,找到系统调用的中断向量,再通过系统调用时执行的指令,最终找到系统调用表的地址, 进行系统调用的替换。


(一)中断向量表地址的获取

       中断向量表(IDT)的入口地址是通过IDTR寄存器来确定的。IDTR寄存器的内容如下图所示。


       这就是上面代码中定义结构体

struct idtr {

    unsigned short limit;

    unsigned int base;

} __attribute__ ((packed))

的原因。

idtr寄存器的内容可以通过汇编指令sidt取出,然后就可以IDT的入口地址idtr.base,即高32bit


(二)系统调用中断向量地址的获取

下一步就通过IDT找到系统调用中断即(0x80)的地址。下图即一个中断向量的组成。由此可以见,一个中断向量占用8个字节。因此,系统调用中断的地址可以表示为:

idt  = idtr.base + 8 * 0x80

 

(三)系统调用处理例程地址的获取

获取到系统调用中断的地址后,同样需要一个数据结构,将中断向量描述符的相关内容保存。数据结构的定义如下:

struct idt {

    unsigned short off1;

    unsigned short sel;

    unsigned char none, flags;

    unsigned short off2;

} __attribute__ ((packed));

       通过以上数据结构就可以得到系统调用中断发生时的中断处理例程的地址,即函数system_call()函数的地址:

sys_call_off = idt.off2 << 16 | idt.off1

该函数是用汇编实现的,位于arch/i386/kernel/entry.S,下面截取该函数的部分实现:

    # system call handler stub

ENTRY(system_call)

    RING0_INT_FRAME         # can't unwind into user space anyway

    pushl %eax          # save orig_eax

    CFI_ADJUST_CFA_OFFSET 4

    SAVE_ALL

    GET_THREAD_INFO(%ebp)

    testl $TF_MASK,EFLAGS(%esp)

    jz no_singlestep

    orl $_TIF_SINGLESTEP,TI_flags(%ebp)

no_singlestep:

                    # system call tracing in operation / emulation

    /* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */

    testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)

    jnz syscall_trace_entry

    cmpl $(nr_syscalls), %eax

    jae syscall_badsys

syscall_call:

    call *sys_call_table(,%eax,4)

movl %eax,EAX(%esp)     # store the return value


(四)系统调用表地址的获取

从上面代码中,我们可以看到,系统调用表的入口地址就是上面倒数第二行中sys_call_table。那么如果获取到sys_call_table的地址呢。由于这行代码执行了函数调用的指令call,该指令对应的指令码为\xff\x14\x85。因此,我们只要在system_call函数体内搜索的前三个字节为\xff\x14\x85的内存地址,然后将该地址加3,即可获取到sys_call_table的地址。同时,还有一个问题需要确定,就是call *sys_call_table(,%eax,4)这条指令相对于system_call函数体的偏移是多少,这样我们可以确定内存搜索的范围。下面是entry.o函数反汇编的部分代码:

000000d0 :

  d0:   50                      push   %eax

  d1:   fc                      cld   

  d2:   06                      push   %es 

  d3:   1e                      push   %ds 

  d4:   50                      push   %eax

  d5:   55                      push   %ebp

  d6:   57                      push   %edi

  d7:   56                      push   %esi

  d8:   52                      push   %edx

  d9:   51                      push   %ecx

  da:   53                      push   %ebx

  db:   ba 7b 00 00 00          mov    $0x7b,%edx

  e0:   8e da                   movl   %edx,%ds

  e2:   8e c2                   movl   %edx,%es

  e4:   bd 00 f0 ff ff          mov    $0xfffff000,%ebp

  e9:   21 e5                   and    %esp,%ebp

  eb:   f7 44 24 30 00 01 00    testl  $0x100,0x30(%esp)

  f2:   00  

  f3:   74 04                   je     f9

  f5:   83 4d 08 10             orl    $0x10,0x8(%ebp)

 

000000f9 :

  f9:   66 f7 45 08 c1 01       testw  $0x1c1,0x8(%ebp)

  ff:   0f 85 bf 00 00 00       jne    1c4

 105:   3d 3e 01 00 00          cmp    $0x13e,%eax

 10a:   0f 83 27 01 00 00       jae    237

 

00000110 :

 110:   ff 14 85 00 00 00 00    call   *0x0(,%eax,4)

 117:   89 44 24 18             mov    %eax,0x18(%esp)

通过以上反汇编代码的倒数第二行可以看到,该行即执行call *sys_call_table(,%eax,4) 的代码。那么该执行相对于函数体的偏移为0x110-0xd0 = 0x40。我们实际的代码中使用偏移值100作为搜索的最大范围。

至于反汇编出来为什么只是call *0x0(,%eax,4),个人理解应该是该模块尚未与其他模块进行链接的原因。当生成内核镜像vmlinux之后,反汇编vmlinux,然后找到system_call函数,就可以看到指令call *0x0(,%eax,4)0x0被替换为有效的地址。本人也已经在2.6.18.3vmlinux上验证过了,实际的代码如下:

c1003d04 :

c1003d04:   ff 14 85 e0 14 1f c1    call   *0xc11f14e0(,%eax,4)

c1003d0b:   89 44 24 18             mov    %eax,0x18(%esp)

因此可以看出,我当前系统的sys_call_table的地址为0xc11f14e0


(五)系统调用的替换

一旦我们获取到了系统调用表的地址,需要需要替换那些系统调用,只需要将系统调用表的某个系统调用指向自己实现的系统调用即可。

#define REPLACE(x) old_##x = my_table[__NR_##x];\

    my_table[__NR_##x] = new_##x

    REPLACE(open);

另外,需要注意的是,在替换系统调用的时候,要先清CR0的第20位并记录原始值,不然在替换sys_call_table的时候会报错。在替换完毕之后,再将CR0的原始值恢复,代码如下:

orig_cr0 = clear_and_return_cr0();  

setback_cr0(orig_cr0);

以上为Linux劫持系统调用的总结。欢迎多多交流,如果不妥之处,请大家指正。


参考链接:

1.

3.

阅读(779) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~