Linux hook系统调用使你文件无法删除

前言

hook技术在Linux系统安全领域有着广泛的应用,例如通过hook技术可以劫持删除文件的系统调用,从而使用户无法删除特定文件,也可以实现对系统网络,文件,进程等的监控。

一、什么是hook技术

hook技术即钩子函数,钩子的本质是一段用以处理系统消息的程序,。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息(如屏幕取词,监视日志,截获键盘/鼠标输入等),也可以不作处理而继续传递该消息,还可以强制结束消息的传递。也即这项技术就是提供了一个入口,能够针对不同的消息或者API在执行前,先执行你的操作,你的操作也称为[钩子函数]

二、Linux hook种类

三、系统调用表hook

相信大家对某些无法删除的流氓软件都有深刻的印象,下面我们通过一个系统调用表hook来实现该功能。
试验环境:centos,x86,3.10+内核

3.1 查看删除文件用到系统调用

使用strace跟踪文件删除系统调用

strace -o 1.txt rm test

查看输出结果

cat 1.txt

execve("/usr/bin/rm", ["rm", "test"], 0x7ffc8101c548 /* 24 vars */) = 0
brk(NULL)                               = 0xa0f000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0ae12a4000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=87988, ...}) = 0
mmap(NULL, 87988, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0ae128e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156592, ...}) = 0
mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0ae0cb6000
mprotect(0x7f0ae0e7a000, 2093056, PROT_NONE) = 0
mmap(0x7f0ae1079000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f0ae1079000
mmap(0x7f0ae107f000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0ae107f000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0ae128d000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0ae128b000
arch_prctl(ARCH_SET_FS, 0x7f0ae128b740) = 0
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/sysconfig/strcasecmp-nonascii", F_OK) = -1 ENOENT (No such file or directory)
mprotect(0x7f0ae1079000, 16384, PROT_READ) = 0
mprotect(0x60d000, 4096, PROT_READ)     = 0
mprotect(0x7f0ae12a5000, 4096, PROT_READ) = 0
munmap(0x7f0ae128e000, 87988)           = 0
brk(NULL)                               = 0xa0f000
brk(0xa30000)                           = 0xa30000
brk(NULL)                               = 0xa30000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, ...}) = 0
mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0ada774000
close(3)                                = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
newfstatat(AT_FDCWD, "test", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_SYMLINK_NOFOLLOW) = 0
geteuid()                               = 0
unlinkat(AT_FDCWD, "test", 0)           = 0
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
close(0)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

从上述内容中我们不难看出用户使用rm命令删除文件时,最终是调用了unlinkat实现的删除操作。

3.2 获取系统调用函数

打开内核源码在线阅读网站,搜索sys_unlinkat(系统调用函数一般是sys_*的格式,在搜索时需要添加前缀)

include/linux/syscalls.h文件中找到函数的定义(主要是看一下参数列表)。

在这里插入图片描述

下面我们在系统调用表中找到这个函数的地址,获取它的函数指针。

unsigned long (*orig_unlinkat)(int dfd, const char __user * pathname, int flag);

unsigned long *sys_call_table = NULL;
sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");
if(sys_call_table == NULL) {
	printk("%s: can not find sys_call_table address\n", __func__);
	return -1;
}
printk("%s: sys_call_addr = 0x%p\n", __func__, sys_call_table);
orig_unlinkat = sys_call_table[__NR_unlinkat];
printk("%s: _NR_unlinkat = 0x%p\n", __func__, orig_unlinkat);

3.3 编写hook函数

这里我们定义一个hook函数,当文件名称等于"test"返回错误,否则调用unlinkat函数。
这里有2点需要注意:

  • hook函数参数列表必须和原函数完全一致;
  • 在内核空间中无法直接访问用户空间地址的数据,如果要在hook函数中读取参数的值,需要借助copy_from_user先将数据拷贝到内核空间;
asmlinkage long hook_unlinkat(int dfd, const char __user * pathname, int flag)
{
    char *filename;
    long ret;

    // Allocate memory to store the filename
    filename = (char *)kmalloc(PATH_MAX, GFP_KERNEL);
    if (!filename)
        return -ENOMEM;

    // Copy the pathname from userspace to kernelspace
    if (copy_from_user(filename, pathname, PATH_MAX)) {
        kfree(filename);
        return -EFAULT;
    }

    // Check if the filename is the one we want to block
    if (strcmp(filename, TARGET_FILENAME) == 0) {
        printk(KERN_INFO "Attempt to delete %s blocked\n", TARGET_FILENAME);
        kfree(filename);
        return -EPERM; // Permission denied
    }

    // Call the original unlink syscall
    ret = orig_unlinkat(dfd, pathname, flag);

    kfree(filename);

    return ret;
}

3.4 替换hook函数

这里只需要将系统调用表中__NR_unlinkat表项存储的地址替换为我们自己定义的hook函数地址即可。
替换后在用户在使用unlinkat系统调用时会走到我们的hook函数中,经过hook函数处理之后才原来的系统调用函数。
需要注意的是,系统调用表位于内核代码段,在修改系统调用表之前需要先将页表属性修改为可写。

/* make the page writable */
int make_rw(unsigned long address)
{
    unsigned int level;
    pte_t *pte = lookup_address(address, &level); //查找虚拟地址所在的页表地址
    pte->pte |= _PAGE_RW;// 设置页表读写属性
    return 0;
}

/* make the page write protected */
int make_ro(unsigned long address)
{
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    pte->pte &= ~_PAGE_RW; //设置只读属性
    return 0;
}

make_rw((unsigned long)sys_call_table); //修改页属性
sys_call_table[__NR_unlinkat] = (unsigned long *)hook_unlinkat; //设置新的系统调用地址
make_ro((unsigned long)sys_call_table);

3.5 测试

加载编译好的内核模块,测试删除test文件
在这里插入图片描述
可以看到在加载了我们编译的模块之后,已经无法删除test文件,这里只是一个示例,通过修改hook函数我们可以实现更多的功能。

参考资料

  1. hook原理介绍与简单实例
  2. Linux hook 机制
  3. Hooking linux内核函数(一):寻找完美解决方案
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值