荔园在线
荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀
[回到开始]
[上一篇][下一篇]
发信人: 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软件 网络书店