この記事はLinux Advent Calendar 2014の5日目の記事です。
Linuxカーネルのシステムコールをhookしたい場合にどうやるかという話です。試したカーネルは3.18.0-rc6です。
まずシステムコールですが、これはテーブルで管理されていて、各要素はNR_システムコール名(NR_closeとか)という形でインデックスを指定してアクセスできます。
そしてこのテーブルはsys_call_tableという名前です。System.mapを見ると以下のような感じで見つけられると思います。
masami@kerntest:~$ grep sys_call_table /boot/System.map-3.18.0-rc6-ktest ffffffff81601600 R sys_call_table ffffffff8160cf80 R ia32_sys_call_table
ちなみに、Rが付いているのでリードオンリーです。sys_call_tableのアドレスはSystem.mapを見ることでわかるのですが、R/Oは解除しないとテーブルの更新ができないのでそこを解決してあげる必要があります。
ぐぐったところページの属性変更方法はlinux-syscall-hookerを参考にしました。
やり方はlookup_address()を使ってsys_call_tableが存在するページのpteを取得し、そのpteの属性をR/Wに変えてあげるという方法です。
既存のsys_reboot()をhookしてリブートする代わりにpanic()するようにしてrebootした結果がこれですw
ということで、↓がカーネルモジュールです。
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <asm/uaccess.h> #include <asm/cacheflush.h> #include <linux/syscalls.h> #include <linux/mm.h> MODULE_DESCRIPTION("system call replace test module"); MODULE_AUTHOR("masami256"); MODULE_LICENSE("GPL"); /* following string SYSCALL_TABLE_ADDRESS will be replaced by set_syscall_table_address.sh */ static void **syscall_table = (void *) __SYSCALL_TABLE_ADDRESS__; asmlinkage long (*orig_sys_reboot)(int magic1, int magic2, unsigned int cmd, void __user *arg); asmlinkage long syscall_replace_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user *arg) { panic("call original reboot system call\n"); return orig_sys_reboot(magic1, magic2, cmd, arg); } static void save_original_syscall_address(void) { orig_sys_reboot = syscall_table[__NR_reboot]; } static void change_page_attr_to_rw(pte_t *pte) { set_pte_atomic(pte, pte_mkwrite(*pte)); } static void change_page_attr_to_ro(pte_t *pte) { set_pte_atomic(pte, pte_clear_flags(*pte, _PAGE_RW)); } static void replace_system_call(void *new) { unsigned int level = 0; pte_t *pte; pte = lookup_address((unsigned long) syscall_table, &level); /* Need to set r/w to a page which syscall_table is in. */ change_page_attr_to_rw(pte); syscall_table[__NR_reboot] = new; /* set back to read only */ change_page_attr_to_ro(pte); } static int syscall_replace_init(void) { pr_info("sys_call_table address is 0x%p\n", syscall_table); save_original_syscall_address(); pr_info("original sys_reboot's address is %p\n", orig_sys_reboot); replace_system_call(syscall_replace_sys_reboot); pr_info("system call replaced\n"); return 0; } static void syscall_replace_cleanup(void) { pr_info("cleanup"); if (orig_sys_reboot) replace_system_call(orig_sys_reboot); } module_init(syscall_replace_init); module_exit(syscall_replace_cleanup);
Makefileはこちら。
KERNDIR := /lib/modules/`uname -r`/build BUILD_DIR := $(shell pwd) VERBOSE = 0 obj-m := syscall_replace.o smallmod-objs := syscall_replace.o all: bash set_syscall_table_address.sh make -C $(KERNDIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules clean: rm -f *.o rm -f *.ko rm -f *.mod.c rm -f *~
set_syscall_table_address.shはこれです。
#!/bin/bash system_map="System.map-`uname -r`" address=`grep "R sys_call_table" /boot/${system_map} | cut -f1 -d' '` if [ $? -ne 0 ]; then echo "Cannot fount ${system_map} on your /boot" exit -1 fi sed -i "s/__SYSCALL_TABLE_ADDRESS__/0x${address}/" syscall_replace.c
コードは masami256/syscall_replace · GitHub に置いておきました。
これ一冊で完全理解 Linuxカーネル超入門 (日経BPパソコンベストムック)
- 作者: 日経Linux
- 出版社/メーカー: 日経BP社
- 発売日: 2014/10/16
- メディア: 単行本
- この商品を含むブログ (1件) を見る