JaSST'09TOKYOのLTで使ったネタ。

Linuxカーネル2.6系からシステムコールのテーブルを使って任意のシステムコールをフックしづらくなったのでその方法のメモ。
#configのメニューでどこかをいじれば2.4系と同じようにもできるらしいけど。
テスト環境はx86_64マシン。
やってることはmmap()をフックして、エラーを返すだけのサンプル。
これのfind_sys_call_table()は、前にあったセキュリティホールで、システムコールを無効化するパッチが使っていた方法をそのまま利用。

find_sys_call_table()で使っている0xffffffff80200000は、sys_XXX()がある場所を大体で決め打ち。
この辺のアドレスは/proc/kallsymsを参照。
つーか、ちゃんとやるならフックしたい関数のアドレスを/proc/kallsymsで調べて、Makefile内でsedあたりで書き換えれば良い気がする。
find_sys_call_table()が肝なくらいであとは見ればわかると思います。。

#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/current.h>
#include <linux/syscalls.h>
#include <linux/smp_lock.h>
#include <linux/mman.h>

MODULE_AUTHOR("hoge <hoge@hoge>");
MODULE_DESCRIPTION("System call hook module sample");
MODULE_LICENSE("GPL");

static unsigned long orig_sys_mmap_addr;
static unsigned long **syscalls;

static unsigned long **find_sys_call_table(void);
static long call_orig_sys_mmap(unsigned long addr, unsigned long len, 
			       unsigned long prot, unsigned long flags,
			       unsigned long fd, unsigned long off);

/* original sys_mmap's address */
asmlinkage long (*orig_sys_mmap)(unsigned long addr, unsigned long len, 
				 unsigned long prot, unsigned long flags,
				 unsigned long fd, unsigned long off);


/* own sys_mmap */
asmlinkage long syscall_hook_sys_mmap(unsigned long addr, unsigned long len, 
				      unsigned long prot, unsigned long flags,
				      unsigned long fd, unsigned long off);

static int target_pid = -1;
module_param(target_pid, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC( target_pid, "A pid which is test target");

/*
 * Get the system call table address
 */
static unsigned long **find_sys_call_table(void) 
{
	unsigned long **sctable;
	unsigned long ptr;

	sctable = NULL;
	for (ptr = (unsigned long) 0xffffffff80200000;
	     ptr < (unsigned long) &loops_per_jiffy; 
	     ptr += sizeof(void *)) {
		unsigned long *p;
		p = (unsigned long *)ptr;
		if (p[__NR_close] == (unsigned long) sys_close) {
			sctable = (unsigned long **)p;
			return &sctable[0];
		}
	}
	return NULL;
}

static long call_orig_sys_mmap(unsigned long addr, unsigned long len, 
					  unsigned long prot, unsigned long flags,
					  unsigned long fd, unsigned long off)
{
	long ret = -EINVAL;

	if (orig_sys_mmap)
		ret = orig_sys_mmap(addr, len, prot, flags, fd, off);

	return ret;

}

asmlinkage long syscall_hook_sys_mmap(unsigned long addr, unsigned long len, 
					  unsigned long prot, unsigned long flags,
					  unsigned long fd, unsigned long off)
{
	long ret = -EINVAL;

	if (target_pid == -1 || current->pid != target_pid) {
		
		printk("do real sys_mmap\n");

		ret = call_orig_sys_mmap(addr, len, prot, flags, fd, off);
		
	} else {

		if (flags & MAP_ANONYMOUS) {
			printk("return false value %d\n", -EBADF);
			printk("pid=[%i]\n", current->pid);
			ret = -EBADF;
		} else {
			ret = call_orig_sys_mmap(addr, len, prot, flags, fd, off);
		}
	}

	return ret;
};


int __init syscall_hook_init(void)
{	

	syscalls = find_sys_call_table();
	if (!syscalls) {
		printk("Couldn't find a system call table\n");
		return -1;
	}

	/* set our own system call */
	orig_sys_mmap_addr = (unsigned long) syscalls[__NR_mmap];
	syscalls[__NR_mmap] = (void *) &syscall_hook_sys_mmap;

	/* remember original address */
	orig_sys_mmap = (long (*)(unsigned long, unsigned long, unsigned long, 
				      unsigned long, unsigned long, unsigned long)) orig_sys_mmap_addr;

	printk("syscall_hook  kernel module loaded with pid=[%i]\n", current->pid);
	printk("sys_mmap's address is %lx\n", orig_sys_mmap_addr);

	return 0;
}

void __exit syscall_hook_exit(void)
{
	syscalls[__NR_mmap] = (long *) orig_sys_mmap_addr;

	printk("syscall_hook kernel module ended with pid=[%i]\n", current->pid);
	printk("sys_mmap's address is %p\n", syscalls[__NR_mmap]);
}


module_init(syscall_hook_init);
module_exit(syscall_hook_exit);

MODULE_DESCRIPTION( "syscall_hook" );
MODULE_LICENSE( "GPL2" );

Makefile

obj-m   := syscall_hook.o

KDIR    := /lib/modules/$(shell uname -r)/build
PWD     := $(shell pwd)

default: all

all:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
	rm -f *.o *.ko *~ .syscall_* Module.symvers  modules.order syscall_hook.mod.c

テストは下のコードを適当にコンパイルして、バックグラウンドで実行させて、pidを調べておく
$ gcc mmap_test.c -o mmap_test
$ ./mmap_test&
そしたら、カーネルモジュールを引数付きでinsmodすればおk。
# insmod ./syscall_hook.ko target_pid=XXX
そうするとこんな感じでログが残ってる

[ 4007.334971] syscall_hook kernel module ended with pid=[13443]
[ 4007.334975] sys_mmap's address is ffffffff80218c83
[ 4098.555782] syscall_hook  kernel module loaded with pid=[13458]
[ 4098.555790] sys_mmap's address is ffffffff80218c83
[ 4098.965210] do real sys_mmap
[ 4099.637249] return false value -9
[ 4099.637255] pid=[13452]
[ 4099.637394] return false value -9
[ 4099.637397] pid=[13452]
[ 4099.638053] do real sys_mmap
[ 4099.966346] do real sys_mmap

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <assert.h>

void *mmap_fd(void)
{
	void *p;
	int fd = open("./mmap_test.c", O_RDONLY);
	assert(fd >= 0);

	p = mmap(NULL, 1024, PROT_READ, MAP_SHARED, fd, 0);
	close(fd);

	return p;
}

void *mmap_anonymous(void )
{
	return  mmap(NULL, 1024, PROT_READ | PROT_WRITE,
		     MAP_ANONYMOUS | MAP_PRIVATE,
		     -1, 0);
}

int main(int argc, char **argv)
{
	void *p;
	unsigned long i = 0;
	char *msg;

	while (1) {

		if ((i % 2) == 0) {
			p = mmap_anonymous();
			msg = "mmap anonymous";
		} else {
			p = mmap_fd();
			msg = "mmap fd";
		}

		if (p != MAP_FAILED) {
			printf("mmap success %s - %ld\n", msg, i++);
			munmap(p, 1024);
		} else {
			perror("mmap");
			exit(EXIT_FAILURE);
		}
		sleep(1);
	}

	return 0;
	
}