版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://technica.blogbus.com/logs/18945123.html
这两天在做系统软件大作业,写一个kernel module,添加自己的syscall,实现通过访问内核全局数据获得关于所有进程和CPU等信息。作业本身不难,但是被kernel的各个版本搞死了,书上网上很多东西都不对的或者过时的,这里记录下我所做大致过程,以备不时之需,也为其他人提供参考。
添加syscall有两种办法,一种静态的,需要修改和重新编译内核,另一种就是动态加载。动态加载有的hack的味道,实际上就是劫持篡改内存里的syscall_table。图省事,不高兴每次修改syscall都重新编译内核,我就用动态加载syscall来实现添加syscall。当然kernel module是通过insmod动态加载的。在module初始化时,获取syscall_table并修改相应的offset(offset为__NR_newhandler * 4)处的值为新的handler的函数地址,就完成了添加syscall。具体如何获得syscall_table的地址网上有很多文章,但是这些文章的代码都是抄来抄去,其中都有一个相同的小错误,即搜索call (,%eax,4)的机器码时,应该是p[i]而不是p[0],我一开始没仔细看,结果深受其害,莫名了半天。执行insmod,模块初始化时就完成了syscall的添加。接下来就是如何调用这个新syscall的问题。通常syscall都是通过调用glibc中的库函数间接调用的,但我们自己写的syscall glibc当然是不认识也不向用户提供相应接口的。在老版本kernel中(2.6.15之前),可以通过syscall0(...), syscall1(...)这种来调用syscall,syscallx(...)实际上是定义在<asm/unistd.h>中的一个内嵌asm的macro,x86中就是通过int 0x80直接进入内核态执行系统调用(如下面的代码所示)。我一开始按照这种方法来做,但是发现编译器不认syscall0(...),察看asm/unistd.h发现里面根本没有这个macro,后来URSA告诉我2.6.15之后的版本要用syscall(__NR_xxx, param1,param2,...)这样来直接调syscall,参数可以有也可以没有,看具体的syscall而定。这样就实现了添加syscall以及在用户程序里直接调用syscall。整个做的过程还是碰到了不少零碎地麻烦,kernel的版本比较乱,也没有一个authority来统一说说哪个版本有些什么变化。网上查到的方法大多数都是针对老的版本的,在2.6.22-14下几乎都不能用,当然这个过程也还是学到了不少东西,比如一些gcc扩展 asmlikage,__attribute__((packed)) ,kernel的一些乱七八糟的macro, gcc内嵌汇编,AT&T汇编写法。最后记录下大致过程,也怕时间一长自己忘记。
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
}
1. 做一个kernel module,在这个kernel module的init中,hijack sys_call_table
这一步网上有现成的代码,但是几乎所有的代码都是转载的,都犯了一个小错误。我一开始没注意,加载模块时一直segment fault。下面是hijack sys_call_table的代码。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
MODULE_LICENSE("Dual BSD/GPL");
#define __NR_newhandler 223
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;
unsigned int* syscall_table;
unsigned int old_handler;
unsigned int get_sys_call_table(void)
{
unsigned int sys_call_off;
unsigned int sys_call_table;
char* p;
int i;
// 获取中断描述符表寄存器的地址
asm("sidt %0":"=m"(idtr));
printk("addr of idtr: %x\n", &idtr);
// 获取0x80中断处理程序的地址
memcpy(&idt, idtr.base+8*0x80, sizeof(idt));
sys_call_off=((idt.off2<<16)|idt.off1);
printk("addr of idt 0x80: %x\n", sys_call_off);
// 从0x80中断服务例程中搜索sys_call_table的地址
p=sys_call_off;
for (i=0; i<100; i++)
{
if (p[i]=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85')
{
sys_call_table=*(unsigned int*)(p+i+3);
printk("addr of sys_call_table: %x\n", sys_call_table);
return sys_call_table;
}
}
}
static int mymod_init(void) {
syscall_table = (unsigned int*)get_sys_call_table();
old_handler = syscall_table[__NR_newhandler];
//篡改内存中的syscall_table中ofset为__NR_newhandler处的内容,也就是编号
//为 __NR_newhandler的syscall的处理程序的入口
syscall_table[__NR_newhandler] = (unsigned long)sys_newhandler;
return 0;
}
把makefile也贴出来。
# Makefile2.6
MODNM := syshj1
ifneq ($(KERNELRELEASE),)
#kbuild syntax. dependency relationshsip of files and target modules are listed here.
#mymodule-objs := file1.o file2.o
obj-m += $(MODNM).o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf .*.cmd *.o *.mod.c .tmp_versions *~ *.symvers
install:
insmod $(MODNM).ko
remove:
rmmod $(MODNM)
endif
2. 在kernel module中实现syscall
// syscall的实现,这里的代码是sys要做的事情,可以替换为需要的内容
asmlinkage long sys_newhandler(void) {
printk(KERN_ALERT "Inside new_handler().\n");
return 0;
}
3. 在用户程序中调用syscall
在需要调用的地方用syscall(__NR_xxx); 其实也是一个macro,可惜我在一堆代码里没找到 :(
最后推荐一个好东西 http://lxr.linux.no/ 可以在线察看各个版本的kernel source,很方便。
ps.之所有文章命名为《linux 2.6.22-14 动态添加syscall》是因为觉得linux kernel各个版本太搞了,可能我写的这些在2.6.22-14中可用,也不一定适合别的版本。//~~~
评论
现在大学里如果都这么布置作业就强了。
还是说楼主格外的牛人?
或者我坐井观天以前把大学计算机教育看扁了?
我在ubuntu 8.04 (kernel 2.6.24) 测试成功
我开始正式动手了
版本的确是个大问题,之前许多22.14下的东西都和查到的资料不符合
革新得厉害啊
现在统一都用20.15做东西了
aktoon加油,继续老乱下去
最近都没心思写了,等有空把做病毒的东西也整理一篇出来