Challenge
題目:train.py
Solution
這題連線後有兩個選項可用:
- your ticket
- use ticket
選擇 Your ticket 會把該次連線所用的公鑰給你 (n,e) 和一個 ticket 。
ticket 是把 date:2019/1/11|session:{session}|secret:{flag}'.encode()
每 16 bytes 作 RSA 加密,再把密文串起來回傳給使用者,且共有 5 段 : iv + cipher*4
session 為 os.urandom(5).hex()
共 10 bytes,flag 的格式固定為 FLAG{ ... }
如此一來我們至少可以知道,被切割後會長下面這樣:
date:2019/1/11|s
ession:XXXXXXXXX
X|secret:FLAG{??
????????????????
而選擇 use ticket 需要傳一個 ticket 給他,他會幫你解密,並做一些檢查
try:
plain = rsa.decrypt(cipher)
date, session, secret = plain.split(b'|')
if date.partition(b':')[2] == b'2019/1/11':
print('Pass')
else:
print('Wrong ticket')
except:
print('Oops, our train has some technical issue')
關於第三段,可以直接爆搜,反正只有 16 * 100 * 100
種組合 ( hex * printable string ** 2)
那麼第四段,我們要先知道他的長度,剛好他的檢查有一行可以用
try:
...
date, session, secret = plain.split(b'|')
...
print('Wrong ticket')
except :
print('Oops, our train has some technical issue')
如果他在明文中找到兩個 |
那他會回傳 Wrong ticket,反之會回傳 Oops, …
不過為了避免前 1 block 解密出現 |
所以我們把第三段當作 iv 讓 server 只解密處理第四段密文
先爆搜前 2 bytes,什麼時候會 Wrong ticket → ||
代表他的長度不到 16 bytes
再試試 |x|
, |xx|
, |xxx|
, … , 都沒用,所以知道他的長度是 14 bytes
再來就是很像 Padding Oracle 的處理方法了,先固定前 2 bytes 為 |x
接著報搜最後一個 bytes ,如果出現 Wrong ticket,可以知道
當時猜的那個 i
讓他解密成, |x?????????????|
,那就可以返推出他的明文
|x?????????????}
然後重複這個動作,就能順利把 FLAG 炸出來了
FLAG{cBCNEVERGetSoLD}
Script : solve.py