Linux x64 Hook系统调用
实验环境:
Linux ubuntu 4.4.0-31-generic
#50~14.04.1-Ubuntu
参考文档:
https://blog.csdn.net/zuihaobushi/article/details/72729850 内核访问用户空间
https://www.cnblogs.com/arnoldlu/p/8879800.html 内核访问用户空间文件
https://www.cnblogs.com/bittorrent/p/3804141.html 导出sys_call_table地址
https://blog.csdn.net/daizhongyin/article/details/86567610 内核函数hook
https://blog.csdn.net/T146lLa128XX0x/article/details/79102775 hook系统调用
http://mcns.blog.chinaunix.net/uid-22964557-id-5757996.html __must_check
系统调用劫持:
修改内核符号表,来达到一个劫持的作用。因为系统调用实际上是触发了一个0x80的软中断,然后转到了系统调用处理程序的入口system_call()。system_call()会检查系统调用号来得出到底是调用哪种服务,然后会根据内核符号表跳转到所需要调用的内核函数的入口地址,所以,如果我们这个时候修改了内核符号表,使其跳转到我们自己的函数上,就可以完成劫持。
版本变化:2.6版本内核之前内核符号表直接导出,之后便不再导出
网上教程多为按照一下方式导出sys_call_table地址
- 利用sidt指令获取中断向量描述表的地址
- 找寻第0x80位描述符地址,记录了调用sys_call的地址
- 源码中sys_call调用的100字节内具有实sys_call_table的地址
但是在实验环境下,sys_call_table获取错误。原因待分析
因此,采取另一种获取sys_call_table地址的方法
kallsyms_lookup_name读取法
kallsyms_lookup_name本身也是一个内核符号,如果这个符号被导出了,那么就可以在内核模块中调用kallsyms_lookup_name(“sys_call_table”)来获得sys_call_table的符号地址。
经过实验可成功获取sys_call_table地址。
内核权限修改
注意修改系统调用表时,由于内核中的很多东西,比如这里的系统调用表sys_call_table是只读的,我们需要修改一下权限才能修改。由于控制寄存器CR0的第16位若置位,则表示禁止系统进程写那些只有只读权限的文件,所以我们在修改系统调用表sys_call_table之前先将CR0的第16位清零,在修改完后再恢复置位就好了。如代码里的close_cr()函数,即是将CR0第16位清零,open_cr()函数是将CR0第16位恢复。
unsigned int close_cr(void){
unsigned int cr0 = 0;
unsigned int ret;
asm volatile("movq %%cr0,%%rax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile("movq %%rax,%%cr0"::"a"(cr0));
return ret;
}
void open_cr(unsigned int oldval){
asm volatile("movq %%rax,%%cr0"::"a"(oldval));
}
Hook地址修改
先获得系统调用表的地址,将系统调用表中的原有地址保存下来,再将hooked_open()函数的地址赋到系统调用表中.
((unsigned long * ) (sys_call_table_addr))[__NR_open]= (unsigned long) hooked_open;
Hook函数编写
功能:记录打开文件和时间
在编写hook函数时出现了几个问题:
- 内核访问用户空间
asmlinkage long hooked_open(const char __user * filename, int flags, unsigned short mode)
sys_open函数参数为用户空间的参数,因此内核访问用户空间时需使用专用函数,具体可参考博客https://blog.csdn.net/zuihaobushi/article/details/72729850
否则,一访问系统即会崩溃
- 内核操作文件
内核没有了用户空间的封装函数,所以需采用内核专用函数,如filp_open等,具体可参考博客https://www.cnblogs.com/arnoldlu/p/8879800.html
HOOK结果
挂载驱动
insmod hook.ko
并执行dmesg命令,可观察到驱动加载成功
dmesg
查看日志文件
特殊的问题:
1、__must_check
查看内核代码,copy_from_user 函数前面具有一个__must_check宏定义,
linux-4.2\arch\x86\include\asm\uaccess.h static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n) { …… }
继续跟踪其定义,其指调用函数一定要处理该函数的返回值,否则编译器会给出警告。但在编译时编译器并没有提示错误,但在挂载执行时会导致系统崩溃。
#if GCC_VERSION >= 30400 #define __must_check __attribute__((warn_unused_result)) #endif
因此,对其返回值进行检查,编译后挂载成功运行。
HOOK.C
/*
* This kernel module locates the sys_call_table by kallsyms_lookup_name("sys_call_table");
*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
#include<linux/unistd.h>
#include<linux/sched.h>
#include<linux/syscalls.h>
#include<linux/string.h>
#include<linux/fs.h>
#include<linux/fdtable.h>
#include<linux/uaccess.h>
#include <linux/kallsyms.h>
#include<linux/rtc.h>
#include<linux/vmalloc.h>
#include <linux/slab.h>
/*
** module macros
*/
//MODULE_LICENSE("GPL");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("geeksword");
MODULE_DESCRIPTION("hook sys_open");
/*
** module constructor/destructor
*/
unsigned long sys_call_table_addr = 0;
long g_oldcr0 = 0; //save address of cr0
typedef asmlinkage long (*orig_open)(const char __user *filename, int flags, unsigned short mode);
orig_open g_old_open= NULL;
unsigned int close_cr(void){
unsigned int cr0 = 0;
unsigned int ret;
asm volatile("movq %%cr0,%%rax":"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile("movq %%rax,%%cr0"::"a"(cr0));
return ret;
}
void open_cr(unsigned int oldval){
asm volatile("movq %%rax,%%cr0"::"a"(oldval));
}
asmlinkage long hooked_open(const char __user * filename, int flags, unsigned short mode){
struct file *fp;
mm_segment_t fs;
loff_t pos;
int filename_size;
char * temp_str = NULL;
char * file_str = NULL;
struct timex txc;
struct rtc_time tm;
filename_size = 1024;
temp_str = kmalloc(filename_size , GFP_KERNEL);
if(temp_str == NULL)
{
goto end;
}
file_str = kmalloc(filename_size , GFP_KERNEL);
if(file_str == NULL)
{
goto end;
}
memset(temp_str, 0, filename_size );
memset(file_str, 0, filename_size );
if (NULL != temp_str) {
//copy_from_user(exec_str,filename, strnlen_user(filename,1000));
if(0 != copy_from_user(temp_str,filename, strnlen_user(filename,1000)))
goto end;
}
do_gettimeofday(&(txc.time));
rtc_time_to_tm(txc.time.tv_sec,&tm);
sprintf(file_str,"UTC time :%d-%02d-%02d %02d:%02d:%02d Filename :%s \n",tm.tm_year+1900,tm.tm_mon, tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec,temp_str);
fp = filp_open("/home/log_file",O_RDWR |O_CREAT| O_APPEND ,0777);
if (IS_ERR(fp)){
goto end;
}
pos = 0;
fs = get_fs();
set_fs(KERNEL_DS);
vfs_write(fp,file_str, strlen(file_str),&pos);
filp_close(fp,NULL);
set_fs(fs);
end:
if(temp_str != NULL)
{
kfree(temp_str);
}
if(file_str != NULL)
{
kfree(file_str);
}
return g_old_open(filename,flags,mode);
}
/*******************************************************************
* Name: obtain_sys_call_table_addr
* Description: Obtains the address of the `sys_call_table` in the
* system.
*******************************************************************/
static int obtain_sys_call_table_addr(unsigned long * sys_call_table_addr) {
int ret = 1;
unsigned long temp_sys_call_table_addr;
temp_sys_call_table_addr = kallsyms_lookup_name("sys_call_table");
/* Return error if the symbol doesn't exist */
if (0 == sys_call_table_addr) {
ret = -1;
goto cleanup;
}
printk("Found sys_call_table: %p", (void *) temp_sys_call_table_addr);
*sys_call_table_addr = temp_sys_call_table_addr;
cleanup:
return ret;
}
// initialize the module
static int hooked_init(void) {
printk("+ Loading hook_mkdir module\n");
int ret = -1;
ret = obtain_sys_call_table_addr(&sys_call_table_addr);
if(ret != 1){
printk("- unable to locate sys_call_table\n");
return 0;
}
if(((unsigned long * ) (sys_call_table_addr))[__NR_close]!= (unsigned long)sys_close){
printk("Incorrect sys_call_table address!\n");
return 1;
}
// print out sys_call_table address
// now we can hook syscalls ...such as uname
// first, save the old gate (fptr)
g_old_open = ((unsigned long * ) (sys_call_table_addr))[__NR_open];
// unprotect sys_call_table memory page
g_oldcr0 = close_cr();
printk("+ unprotected kernel memory page containing sys_call_table\n");
// now overwrite the __NR_uname entry with address to our uname
((unsigned long * ) (sys_call_table_addr))[__NR_open]= (unsigned long) hooked_open;
open_cr(g_oldcr0);
printk("+ sys_execve hooked!\n");
return 0;
}
static void hooked_exit(void) {
if(g_old_open != NULL) {
// restore sys_call_table to original state
g_oldcr0 = close_cr();
((unsigned long * ) (sys_call_table_addr))[__NR_open] = (unsigned long) g_old_open;
// reprotect page
open_cr(g_oldcr0);
}
printk("+ Unloading hook_execve module\n");
}
/*
** entry/exit macros
*/
module_init(hooked_init);
module_exit(hooked_exit);
Makefile
obj-m += hook.o
all:
make -C /lib/modules/`uname -r`/build M=$(PWD) modules
clean:
make -C /lib/modules/`uname -r`/build M=$(PWD) clean