荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjksam (UNIX+C+XML+??), 信区: Linux
标  题: 【转载】FreeBSD内核溢出攻击 (一)     gracewind (转寄)[转载]
发信站: 荔园晨风BBS站 (Wed Apr 17 15:11:34 2002), 转信

【 以下文字转载自 jjksam 的信箱 】
【 原文由 jjksam@smth.org 所发表 】
发信人: huxw (米老鸭和唐老鼠), 信区: FreeBSD
标  题: 【转载】FreeBSD内核溢出攻击 (一)
发信站: BBS 水木清华站 (Sun Mar 17 20:42:20 2002)

发信人: scz (小四), 信区: Security
标  题: FreeBSD内核溢出攻击(一)
发信站: 武汉白云黄鹤站 (2001年05月15日20:27:03 星期二), 转信

作者:Esa Etelavuori <eetelavu@CC.HUT.FI>
翻译: warning3 <warning3@nsfocus.com>
时间:2001-5-10

译者:这篇文章是去年年底发布的,它描述了如何利用FreeBSD内核的溢出漏洞进

攻击。想到不久前那个著名的LDT门调用漏洞,我觉得有必要了解一下这种类型的

题,因此就翻译了此文。

-----[ 1. 概述

本文详细讨论分析了FreeBSD内核进程文件系统缓冲区溢出漏洞[7]。这个问题是
FreeBSD/i386特有的,但是这里提到的一些技术对于其他系统也是使用的,也许
会让你从一个新的角度去看缓冲区溢出。

虽然搜索"内核缓冲区溢出"可以发现一些有趣的资料,但是关于这一主题的公开
信息却并不多。Silvio Cesare的kmem patching文章 [1]是一篇很好的基础文章。

FreeBSD内核实现[5, 6]以及IA-32结构[2]的资料也都是相当有用的。查看
FreeBSD
中jail(8)和init(8)的man手册,您可以得到有关jail机制和安全等级的详细资料。


[译者注:jail是FreeBSD提供的一种安全机制,可以将用户的活动限制在一个受限

环境中。这篇文章的目的就是如何利用一个内核的溢出来突破jail限制,这要求攻

击者首先获取jail环境中的root权限。]

-----[ 2. 漏洞分析

当在攻击内核空间的漏洞时,全面理解这个漏洞是基本的要求,因为我们通常只有

一次尝试的机会,一旦出错可能导致系统崩溃。

2.1 理解漏洞

4.4BSD 的procfs实现从一开始就是有问题的,但最后的打击来自jail(2).
当使用一个长主机名(超过255字节)或大的gid设置一个jail,并且从procfs
中读取某个程序状态时,缓冲区溢出就会发生。

Procfs状态信息通常象这样:
# cat /proc/curproc/status
cat 60424 60386 60424 60386 5,0 ctty 972854153,236415 0,0 0,1043\
nochan 0 0 0,0 prisoner

对应的域分别是:
comm pid ppid pgid sid  maj,min ctty,sldr start     user/system time\
wmsg euid ruid rgid,egid,groups[1 .. NGROUPS] jail的主机名

可以用下面的方法使有问题的内核崩溃:
# jail / `perl -e 'print "x" x 250'` 1.2.3.4 /bin/cat
/proc/curproc/status

真正的罪魁祸首在src/sys/miscfs/procfs/procfs_status.c中:

int
procfs_dostatus(curp, p, pfs, uio)
    struct proc *curp;
    struct proc *p;
<snip>
    char *ps;
<snip>
    int xlen;
    int error;
    char psbuf[256];                /* XXX - conservative */
<snip>
    ps = psbuf;
<...snip>
[译者注:如果我们有很多组ID,这里就可能发生溢出]
    for (i = 0; i < cr->cr_ngroups; i++)
        ps += sprintf(ps, ",%lu", (u_long)cr->cr_groups[i]);

[译者注:如果我们有一个较长的主机名,这里可能发生溢出]
    if (p->p_prison)
        ps += sprintf(ps, " %s", p->p_prison->pr_host);
    else
        ps += sprintf(ps, " -");
    ps += sprintf(ps, "\n");

    xlen = ps - psbuf;
    xlen -= uio->uio_offset;
    ps = psbuf + uio->uio_offset;
    xlen = imin(xlen, uio->uio_resid);
    if (xlen <= 0)
        error = 0;
    else
        error = uiomove(ps, xlen, uio);

    return (error);
}

这显然是有问题的,这个jail溢出已在FreeBSD源码树中存在至少18个月了。

psbuf宣称为最后一个本地变量这好像会带来一些麻烦(当然我们是可以克服的),

因为ps会被覆盖。我们需要进一步调查一下如果编译器使用缺省的优化参数(-O)

会产生什么样的代码。

# nm /kernel | grep "T procfs_dostatus"
c0170d64 T procfs_dostatus
# objdump -d /kernel --start-address=0xc0170d64 | less
<snip>
c0170d64 <procfs_dostatus>:
c0170d64:       55                      push   %ebp
c0170d65:       89 e5                   mov    %esp,%ebp
c0170d67:       81 ec 24 01 00 00       sub    $0x124,%esp
c0170d6d:       57                      push   %edi
c0170d6e:       56                      push   %esi
c0170d6f:       53                      push   %ebx
c0170d70:       8b 45 14                mov    0x14(%ebp),%eax
<snip>
        ps += sprintf(ps, "\n");
c017100c:       68 cb 0d 24 c0          push   $0xc0240dcb
c0171011:       56                      push   %esi
c0171012:       e8 21 62 fd ff          call   c0147238 <sprintf>
c0171017:       01 c6                   add    %eax,%esi
        xlen = ps - psbuf;
c0171019:       8d 95 00 ff ff ff       lea    0xffffff00(%ebp),%edx
c017101f:       89 f1                   mov    %esi,%ecx
c0171021:       29 d1                   sub    %edx,%ecx

我们看到,ps被优化后使用寄存器%esi,这样psbuf就放在了栈幀的顶部了
(通过 -256(%ebp)被调用)

我反汇编了一个GENERIC内核,并使用FreeBSD发布的GCC重新编译了一个新内核
(使用了不同的优化设置),对于我们要进行的攻击攻击来说,上面的代码似乎总
是可以利用的。

2.2 控制处理器

当使用gid来进行溢出攻击时,我们所能用的字符受到很大的限制。溢出将以
'\n\0'
结束,所以我们只能访问有限的地址。我们也需要运气来碰到合适的代码。
但我们可以使用单字节幀指针溢出[3,4]来访问当前程序的堆栈,我们也可以用
两字节溢出来访问其他的数据区。我们可以从p->p_md.md_regs来读取当前进程
的内核空间堆栈的栈顶,它位于一个两页用户区的顶部。

我不清楚是否有一个简单的方法使我们的数据填充可访问的区域,我想可以
通过使用伪栈幀来暴力填充用户控制区域,然后执行一些程序,并通过读取kmem
来搜索合适的数据。显然用于拷贝参数的空间是可以访问的,而且对于双字节溢
出来说,这个地址是比较固定而且可以利用的。这也可以用来在其他的BSD系统上

突破安全等级。

[译者注:上面的说法是指如果使用大量的gid来覆盖的情况下如何攻击,实际上使

用长主机名来触发溢出似乎更简单一些,见下面的讨论。]

但是如果内核被编译成不使用栈幀指针怎么办?再看一下源码,我们可以看到结构

针curp和p参数刚刚在保存的返回地址上方,而且在溢出发生后不再被使用。这意

着我们可以使用两个返回地址来填在主机名中,如果没有使用栈幀,那么第二个地

就覆盖了curp,末尾的'\n\0'会破坏p,这样仍然是安全的。

现在我们可以相当确定的说,我们可以控制程序流程了。如何从这里继续攻击有很

方法。所谓"正确"的方法依赖于条件,每个开放源码的内核都可能是不同的。下面

例子只是为了演示在进行内核攻击时需要注意的几个方面,它并不是一个非常优化

攻击程序。

-----[ 3. 产生Payload(执行代码)

[译者注:这里的payload就是指常说的shellcode,我们的可执行代码]

我们的目的是突破jail的限制,并且将安全等级重置为不安全状态。
我们可以通过将我们进程的jail指针清零来逃出jail。进程标记仍然会包含jail

标示,但是这无关紧要,因为主要的检查只是看jail指针的合法性。进程的根目录

可以被设置为系统的根目录,这可以绕过jail(2)使用的chroot(2)。我们通过将一

小于一的数值写到安全等级变量的地址中来重设安全等级。

我们需要得到我们想访问变量的确切地址,然而,即使是在最基本的jail安装下,

/kernel和/dev/{mem,kmem}可能都被链向/dev/null了,因此我们不能使用他们来

取确切地址。然而,FreeBSD内核通过kldsym(2)调用泄漏了所有需要的符号表信息

我们可以通过kmv(3)库来很容易地使用它。

(译者注:因此这个攻击程序编译时需要加"-lkvm"参数)

3.1 执行paload

我们可以通过停止一个伪进程(这样它的状态信息就不会改变)来重定向程序流程,

我们可以用它来计算新主机名(它包含我们的代码)的确切长度,然后设置新的
主机名,然后再次读取状态。(这将触发溢出)

我们可以通过在堆栈顶端到缓冲区中填充NOP指令来到达我们的代码。但我们还可

以得到更精确的地址,方法是利用kmv(3)来从我们的进程结构中得到prison结构
的位置(使用KERN_PROC sysctl(3)调用)。如果我们不在jail环境下,我们可以使

用内核MIB来将数据从用户空间传递到内核空间。


3.2 退出payload

在payload已经被执行之后我们应该做什么?正在运行的程序会被迫中断,这也可

导致一些不可预料的影响,因为这是在内核空间中。这个程序应该保持锁定,其他

资源应该被释放。最安全的方式是恢复执行好像什么也没发生过一样。

问题在于如果我们不能在攻击之前读取内核代码,我们就不能确切地知道应该返回

到哪里。我们可以让payload搜索对procfs_dostatus()的调用,然后实时计算返回

地址。然而,栈幀指针可能也需要调整,我们并不能肯定它会是正确的。

我们可以再次依赖一个常见的例子,但是既然我们已经走到这一步,我们不想现

在就失败。我们可以在payload已经触发之后让程序休眠(sleep).当我们已经逃出

jail环境后,我们可以正确地调整栈幀指针以及返回地址,并发信号给程序,让其

继续它的旅程并安全地返回用户空间。

对于普遍情况,我们可以调整payload,以便被覆盖的栈幀指针被实时设置到一个

确的地址,这可以通过使用堆栈指针以及利用反汇编上一级函数(procfs_rw)来计

得到。
<待续>

--

            也许有一天,他再从海上蓬蓬的雨点中升起,
            飞向西来,再形成一道江流,再冲倒两旁的石壁,
            再来寻夹岸的桃花。然而,我不敢说来生,也不敢信来生......
※ 来源:·武汉白云黄鹤站 bbs.whnet.edu.cn·[FROM: 210.73.87.125]

--

     编译过了,结果为什么不对呢?


※ 来源:·BBS 水木清华站 smth.org·[FROM: 166.111.172.7]
--
※ 转载:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店