3.4 使用LKMs来防护你的linux内核
对于Phrack的读者来说,这一节听起来有些耳熟.Route介绍了很多使linux系统更为安全的方法.他使用了很多补丁.我想说明一些方法也可以通过LKMs实现.记住不隐藏这些LKMs有时候也是有用的.(当然隐藏使你必须做的一些事情).因为route的补丁当某人控制系统时几乎不起作用;而且一个没有特权的用户是不能移走我们的LKM的.但是他可以看见他.使用LKMs替代静态的内核补丁的优点:你可以很容易很安全的控制整个系统,可以在正在运行的系统中很容易的进行安装.没有必要每时每刻在一个敏感的系统上安装新的内核.Phrack的补丁也增加了一些我没有实现的纪录功能.但是有很多种方法来做这个.最简单的可以用printk(...)
[注意:我并没有细看route的补丁的每一个方面.也许真正一个好的内核探索者可以通过LKMs来做更多的东西.]
3.4.1 为什么我们必须允许任何一个程序都拥有可执行的权限
下面是像route的内核补丁里面的一个检查执行权限功能的LKM.
#define __KERNEL__
#define MODULE
#include <linux/version.h>
#include <linux/mm.h>
#include <linux/unistd.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <asm/errno.h>
#include <asm/string.h>
#include <linux/fcntl.h>
#include <sys/syscall.h>
#include <linux/module.h>
#include <linux/malloc.h>
#include <linux/kernel.h>
#include <linux/kerneld.h>
/*系统调用的位置*/
int __NR_myexecve = 0;
extern void *sys_call_table[];
int (*orig_execve) (const char *, const char *[], const char *[]);
int (*open)(char *, int, int);
int (*close)(int);
char *strncpy_fromfs(char *dest, const char *src, int n)
{
char *tmp = src;
int compt = 0;
do {
dest[compt++] = __get_user(tmp++, 1);
}
while ((dest[compt - 1] != '\0') && (compt != n));
return dest;
}
int my_execve(const char *filename, const char *argv[], const char
*envp[])
{
long __res;
__asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve),
"b"((long)
(filename)), "c"((long) (argv)), "d"((long) (envp)));
return (int) __res;
}
int hacked_execve(const char *filename, const char *argv[], const char
*envp[])
{
int fd = 0, ret;
struct file *file;
/*我们需要inode结构*/
/*我使用了open来实现,因为在学习过LKM传染者之后,你必须能够理解这个,下面会有一个好一些的实现*/
fd = open(filename, O_RDONLY, 0);
file = current->files->fd[fd];
/*是root的文件吗?*/
/*要记住:在这儿你可以做一些其他的检查(route做了很多的检查),但是这里只是为了演示.你可以看看inode的结构,看看有一些什么东西是要检查的(linux/fs.h)*/
if (file->f_inode->i_uid!=0)
{
printk("<1>Execution denied !\n");
close(fd);
return -1;
}
else
/*否则让用户执行文件*/
{
ret = my_execve(filename, argv, envp);
return ret;
}
}
int init_module(void) /*初始化模块*/
{
printk("<1>INIT \n");
__NR_myexecve = 250;
while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve]
!= 0)
__NR_myexecve--;
orig_execve = sys_call_table[SYS_execve];
if (__NR_myexecve != 0)
{
printk("<1>everything OK\n");
sys_call_table[__NR_myexecve] = orig_execve;
sys_call_table[SYS_execve] = (void *) hacked_execve;
}
open = sys_call_table[__NR_open];
close = sys_call_table[__NR_close];
return 0;
}
void cleanup_module(void) /*卸载模块*/
{
sys_call_table[SYS_execve]=orig_execve;
}
这和route的内核的补丁并不是完全一样。route检查路径,而我们检查的是文件。(检查路径也是可以的,但是依我看来文件的检查会更好一些)。我只检查了文件的UID。一个管理员可以加更多的过滤器。正如我所说的,上面我所用的open/fd实现并不是最简单的方法。我在这里使用他是因为你应该很熟悉这种方法了。(记住,LKM传染者使用过这种方法)。对于我们的这个目的,下面的内核函数也是可以用的(比较简单的方法):
int namei(const char *pathname, struct inode **res_inode);
int lnamei(const char *pathname, struct inode **res_inode);
这些函数用一个路径作为参数,返回相对应的inode结构。两个函数的区别在于对于链接的处理:lnamei不解析链接并且返回那个链接本身的inode。作为一名hacker你也可以改变inode。只不过需要替换sys_execve(...)并且使用namei(...)(这个方法我们也用于执行控制),然后操纵inode(我会在5。3中给你们一个实用的例子)。
3.4.2 链接的补丁
当谈到系统安全时,每一个linux用户都知道链接的错误是常常会引起严重问题的。Andrew Tridgell开发出一个内核的补丁来防止一个进程进行恶意的链接。Solar的设计者也增加了一些代码来阻止用户进行不正确的链接。
我必须承认链接的补丁对于我们LKM来说是一个不是太容易接触到的层次。既没有输出的符号,也没有可以拦截的系统调用。解析链接是VFS做的。可以看看我们在第四部分解决这个问题的方法。(但是我不会使用第四部分的方法来进行系统安全维护)。你也许会奇怪为什么我不使用sys_readlink(...)系统调用来解决这个问题。因为当你运行'ls
-a syslink'时,会进行这个系统调用,然而,当运行'cat symlink'就不会了。
我认为你可以把这个问题留给内核补丁。当然你可以编一个LKM拦截sys_symlink(...)来阻止在/tmp目录下新建链接.看链接的LKM,有相似的模块实现.
OK,链接问题对于LKM来说有点困难.但是Solar设计者的关于限制链接的想法是怎么样的呢?这是可以被LKM实现的。我们只需要截获sys_link(...)。这个系统调用是负责新建所有硬链接的。让我们看看替换的系统调用(这个代码段并不和内核补丁完全一样,因为我们只须检查/tmp目录,不是sticky位。但是这个可以通过看inode的结构实现[见5。1]):
int hacked_link(const char *oldname, const char *newname)
{
char *kernel_newname;
int fd = 0, ret;
struct file *file;
kernel_newname = (char*) kmalloc(256, GFP_KERNEL);
memcpy_fromfs(kernel_newname, newname, 255);
/*是链接到/tmp目录的么?*/
if (strstr(kernel_newname, (char*)&hide ) != NULL)
{
kfree(kernel_newname);
/*我再次使用了open方法*/
fd = open(oldname, O_RDONLY, 0);
file = current->files->fd[fd];
/*检查UID*/
if (file->f_inode->i_uid!=current->uid)
{
printk("<1>Hard Link Creation denied !\n");
close(fd);
return -1;
}
}
else
{
kfree(kernel_newname);
/*一切正常-〉用户可以新建链接了*/
return orig_link(oldname, newname);
}
}
这是你可以控制新建链接的方法。
3.4.3 /proc权限的补丁
我已经演示了一些如何隐藏一些进程的信息的方法。route的隐藏的想法和我们的完全不一样。他想通过改变目录的权限来限制/proc/的存取(存取进程信息要到这个目录)。因此他检查了proc的inode。如果你加载了他,一个普通用户是不可以读取/proc的fs的。如果你卸载了,他就可以了。下面我们看看:
/*非常坏的编程风格(也许我们必须使用一个函数来获得inode),但是他确实能用*/
#define __KERNEL__
#define MODULE
#define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds());
#define END_KMEM set_fs(old_fs);}
#include <linux/version.h>
#include <linux/mm.h>
#include <linux/unistd.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <asm/errno.h>
#include <asm/string.h>
#include <linux/fcntl.h>
#include <sys/syscall.h>
#include <linux/module.h>
#include <linux/malloc.h>
#include <linux/kernel.h>
#include <linux/kerneld.h>
extern void *sys_call_table[];
int (*open)(char *, int, int);
int (*close)(int);
int init_module(void) /*初始化模块*/
{
int fd = 0;
struct file *file;
struct inode *ino;
/*再一次使用open方法*/
open = sys_call_table[SYS_open];
close = sys_call_table[SYS_close];
/*我们必须准备一些内核空间的数据给系统调用*/
BEGIN_KMEM
fd = open("/proc", O_RDONLY, 0);
END_KMEM
printk("%d\n", fd);
file = current->files->fd[fd];
/*proc目录的inode*/
ino= file->f_inode;
/*改变权限*/
ino->i_mode=S_IFDIR | S_IRUSR | S_IXUSR;
close(fd);
return 0;
}
void cleanup_module(void) /*卸载模块*/
{
int fd = 0;
struct file *file;
struct inode *ino;
BEGIN_KMEM
fd = open("/proc", O_RDONLY, 0);
END_KMEM
printk("%d\n", fd);
file = current->files->fd[fd];
/*这里是proc目录的inode*/
ino= file->f_inode;
/*改变权限*/
ino->i_mode=S_IFDIR | S_IRUGO | S_IXUGO;
close(fd);
}
加载这个模块,并尝试ps,top或者其他的,他们不会运行的.每一次/proc的存取都被拒绝了.当然,作为root你还是可以察看每一个进程或者其他的任何事情的。这只不过是一个愚昧你的用户的方法。
[注意:这是一个很实际的在运行中改变inode的例子。你可以见到很多运用这种方法的例子。]
3.4.4 安全级别的补丁
这个补丁的目的:我引用route的话"这个补丁实际上不是一个很象样的补丁.他只不过简单的把安全级别增加.从0增加到1.这会设置文件的不可改变只能增加位.任何人都不可以改变他们.(通过普通的接口)。在打开这个选项以前,你应该可以使得某些关键的文件不能被改变,某些纪录文件只能被增加。尽管还是有可能打开底层的磁盘设备,然而,一般的只会照搬代码的hacker对此会一筹莫展。
OK,这对于一个LKM来说是很容易实现的。我们很幸运,因为关于安全级别的符号是公开的,(见/proc/ksyms),因此我们可以很容易的改变他。我不会给出关于这一位的任何代码。只要引入安全级别,然后在模块初始化的时候设置就可以了。
3.4.5 底层磁盘补丁
我开发了一个简单的工具来防止一些像THC的manipate-data这样的东西。这些工具被hacker用来搜索整个磁盘的他们的原始IP地址或者DNS名字。在找到以后改变或者从硬盘中移出这些项。当然他们只可以在控制了这个系统以后才能这么做。那我们该怎么办呢?我发现下面的方法可以阻止这种进攻[当然了,同时也有很多方法来阻止这种保护
:( ]
启动你的系统
安装一个阻止直接存取你的保存你的纪录的那个分区的LKM
这个方案可行是因为只有当某些(很少)操作时系统(通常)需要直接存取底层磁盘。这个LKM只要拦截sys_open(...)并过滤掉需要的设备文件就可以了。我想在这里没有必要显示如何编他。看看2。4。2。这个方法可以保护任何/dev/*
下面的文件。问题是通过这个方法,当LKM加载时,没有人可以直接访问他们了。
[注意:会有一些函数不能运行,或者会拖跨整个系统,但是一个正常的网络服务器,或者邮件服务器应该都是可以正常运行的。]
摘自《赛迪网》 pragmatic/THC,(版本1.0)/文