免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2176 | 回复: 0
打印 上一主题 下一主题

Linux System Call Hook [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-04-23 22:51 |只看该作者 |倒序浏览
关键字:linux系统调用劫持

在linux内核2.4.18之前,sys_call_table符号是导出的。在这种情况下,要添加自己定义的系统调用函数或者修改相应的调用函数都是非常的简单。只要修改一下sys_call_table[__NR_xxxx]就行了。

但是处于安全性的考虑,在linux2.4.18之后,已经不再导出sys_call_table符号了。
在这种情况下,如果要修改系统调用函数,就必须首先要获得sys_call_table这个符号地址。
方法一、
也是最简单的一种方法,但是平台依赖性太强,移植性不好。
通过grep sys_call_table /boot/System.map,从System.map(不同的系统,这个文件的名字有些许的不同)文件中,来获得sys_call_table的地址。
例如:
    [root@zkg ~]# grep sys_call_table /boot/System.map-2.6.11-                                                    1.1369_FC4
    c03c7740 D sys_call_table
    这里的c03c7740就是sys_call_table的地址。有了这个地址后,其它
    System Hook的应用程序就可以利用这个地址来使用sys_call_table符号。
    void** sys_call_table = (void **)0x c03c7740;
方法二、
这是在xfocus上看到的一种方法,通过idtr,idt来获得sys_call_table的地址。
代码如下:
getSysCallTable.c:
#ifndef __SYSCALL_INCLUDE__
# define __SYSCALL_INCLUDE__
#endif
#ifdef MODVERSIONS
#include
#endif
#include
#include
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("export the sys_call_table");
#if !defined(symname)
#error symname not defined
#endif
#define CALLOFF 100
unsigned symname; /* #define */
struct {
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr;
struct {
unsigned short off1;
unsigned short sel;
unsigned char none,
flags;
unsigned short off2;
} __attribute__ ((packed)) * idt;
void set_symbol_addr(unsigned old_value, unsigned new_value)
{
  struct module *mod;
  struct kernel_symbol *s;
  int i;
/*遍历本模块的符号表,把本模块的一个也叫sys_call_table的符号的地址
  设置为系统真正的sys_call_table的实际地址。 */
  for (mod = THIS_MODULE, s = (struct kernel_symbol*)mod->syms,
       i = 0; i num_syms; ++i, ++s)
  if (s->value == old_value)
  {
    printk("set_symbol_addr: old:0x%x, new:0x%                    
           x\n",old_value,new_value);
    s->value = new_value;
    return;
  }
}
char * findoffset(char *start)
{
  char *p;
//搜索system_call函数的后100个字节
  for (p = start; p
//因为call something(,%eax,4)的机器指令码是0xff 0×14 0×85
    if (*(p + 0) == '\xff' && *(p + 1) == '\x14' && *(p + 2)
       == '\x85')
         return p;
  return NULL;
}
static int __init init(void)
{
  unsigned sys_call_off;
  unsigned sct=0;
  char *p;
//读取idtr寄存器的值至idtr结构中。
  asm("sidt %0":"=m"(idtr));
//将0×80描述符存至idt结构中。
  idt = (void *) (idtr.base + 8 * 0x80);
//得到system_call函数的地址。
  sys_call_off = (idt->off2 off1;
//搜索system_call函数的后100字节,来得到call语句对应机器码的地址。
  if ((p = findoffset((char *) sys_call_off)))
  {
//得到sys_call_table的地址
   sct = *(unsigned *) (p + 3);
// 至此已经得到了sys_call_table在内存中的位置,这样在根据系统调用号就能够到
//相应的系统调用对应的地址,修改该地址就可以使用新的系统调函数。
   set_symbol_addr((unsigned) &symname, sct);
  }
  return 0;
}
static void __exit fini(void)
{
}
//导出sys_call_table符号,这样就可以在其他模块上层叠使用新的模块。
EXPORT_SYMBOL(sys_call_table);
module_init(init);
module_exit(fini);
Makefile文件:
obj-m   :=getSysCallTable.o
EXTRA_CFLAGS := -Dsymname=sys_call_table
KDIR   := /lib/modules/$(shell uname -r)/build
PWD   := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
$(RM) -rf .*.cmd *.mod.c *.o *.ko .tmp*
在2.6.11-1.1369_FC4 下测试通过。

现在来解释一下这段程序:
一、首先介绍一下关于IDT和IDTR的相关基本概念,具体可以google。
在Linux中IDT(Interrupt Descriptor Table)是一个有256个入口的线形表,每个中断向量关联了一个中断处理过程。每个IDT的入口是个8字节的描述符,所以整个IDT表的大小为256*8=2048 bytes。IDT被BIOS程序首先初始化,但是当Linux得到控制权后,Linux自己又重新设置了IDT。
IDT有三种不同的描述符或者说是入口,
其中的中断门描述符 Interrupt Gate Descriptor如下描述:
-bits 0 to 15: handler offset low
-bits 16 to 31:segment selector
-bits 32 to 37:reserved
-bits 37 to 39:0
-bits 40 to 47:flags/type
-bits 48 to 63:handler offset high
-flag 组成如下 :
5 bits for the type
interrupt gate:1 1 1 1 0
trap gate:0 1 1 1 0
2 bits for DPL
DPL = descriptor privilege level
1 bit reserved
其中-offset low和offset high组成了处理中断函数的地址。当中断发生时会直接跳到该地址运行。
汇编指令lidt提供了初始化IDTR寄存器—它包含了IDT的大小和IDT的地址。然后setup_idt函数填充了256个IDT入口—使用了同样的中断门(ignore_int)。然后按照需要,安装正确的中断门。
二、在linux中system_call的源代码是这样来实现系统调用的(代码见/arch/i386/kernel/entry.S):
ENTRY(system_call)
pushl %eax     # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp) # 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
从这段代码中可以看出,源代码首先保存相应的寄存器的值,然后判断系统调用号(在eax寄存器中)是否合法等,处理完这些后,,利用call *sys_call_table(,%eax,4) 来转入相应的系统调用进行处理,其中的sys_call_table的地址就是我们需要的地址。于是我们可以利用字符匹配来寻找相应call语句就可以确定sys_call_table的地址,因为call something(,%eax,4)的机器指令码是0xff 0×14 0×85。
为了导出sys_call_table符号,具体的实现过程是自已构建一个内核模块,在这个模块中导出一个sys_call_table。我们在模块里找到int 0x80对sys_call_table的引用,找出sys_call_table的地址,再将我们将要导出的sys_call_table指向这个地址,这样就是使到导出的sys_call_table和真实的sys_call_table没区别。

三、总结
只要在linux内核中还是用int $0x80 指令来进入系统调用的,该方法还是有用的。
但是前些天看了下《Understanding the Linux Kernel, 3rd Edition》中关于系统调用的部分,由于int指令比较慢(因为它要一致性和安全性检查),于是在linux2.6内核中为了安全考虑,同时引入了sysenter指令来实现从用户模式到内核模式的切换,在这种情况下,上述方法可能就不再适用了。

参考文章
https://www.xfocus.net/bbs/index.php?act=ST&f=2&t=52401


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/17669/showart_103949.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP