- 5分野から、問題を1つ選んで書いた
- Binary Exploitationは、ない・・
- 自分の解いた中で、一番高かったやつ、脳汁でたやつ
- 人生2回目のCTF、楽しかった
問題(要ログイン)
URL: https://2019game.picoctf.com/problems
[General Skills] mus1c, 1_wanna_b3_a_r0ck5tar
歌詞「lyrics.txt」が提供されます。Hintは、「rockstarをマスターできるかい?」
「ロックをマスターする」じゃないのか?とか思いながら検索を進めたとこ、
Rockstarというプログラミング言語があり、そのソースコードだということがわかりました。
以下、解法(実行方法)、要Node.js
です。
git clone https://github.com/RockstarLang/rockstar
cd rockstar/satriani
# ちなみに、rockstarのソースコードの拡張子は「.rock」
node rockstar lyrics.txt
114
114
114
111
99
107
110
114
110
48
49
49
51
114
出力された数値をASCIIにマップすると、rrrocknrn0113r
→ フラグはpicoCTF{rrrocknrn0113r}
次問の1_wanna_b3_a_r0ck5tarもrockstarですが、ユーザ入力を受付け、なおかつ正しい入力結果じゃないと条件分岐して何も出力しません。
なので、入力待ちと、条件分岐部分を削ってしまいます。
Listen to the ~
, If the ~
, Else ~
が該当箇所なので削除して実行すると以下の通り。
node rockstar lyrics2.rock
66
79
78
74
79
86
73
出力された数値をASCIIにマップすると、BONJOVI
→ フラグはpicoCTF{BONJOVI}
[Forensics] WhitePages
真っ白のテキストファイル「whitepage.txt」が与えられます。
hexdump
でダンプしてみると、e2 80 83
と20
がたくさん出てきます。
0x20
=SPACE、0xe2 0x80 0x83
=EM SPACE ですので、2種の空白文字が羅列されていることになります。
2種類なのでバイナリーかな?とあたりをつけて解いてみます。
実際は以下の感じで解きました。
- SPACE=1, EM SPACE=0 として変換し、Hex表記にする。
- URLエンコードとみなし、デコードする。
以下、作成したコードです。(ここでは、EM SPACEを'|'に置換してます)
a = '|||| | ||||| || |||| || | ||||| | || | ||| | | | |||| | | | ||| ||| ||||| | ||||| | ||||| || |||| || | | || | ||| | | ||| | || |||||| | ||||| | | | | |||| || || ||| || || | |||| || |||||| | || || ||| | | |||| | || | | || || ||| ||| | || || ||||||| || ||| |||||| |||| || ||||| | |||| | || | | ||| | | || || || | | | | | || || ||| |||| |||||| | || || ||| | | | ||||| || | | || || | | |||||| | ||||| || |||| || || | | || |||||| |||||| |||||| |||||| ||| || | | || || ||| || || | | || || |||||| ||||| | | || || | || | |||| |||||| | ||||| | || | | ||| | ||| || | ||| || | | | || || || | | ||||| | |||| |||||| | ||||| ||||| || ||||||| ||| || | | || || ||| ||| || || |||| | ||||| || |||| || | ||||| | || | ||| | | | |||| | | | ||| ||| || | | | || | | | ||| | | |||| | | ||| | ||| | | || | ||||| |||| | ||| | || | | || | | | |||| | || || || | | | | ||| | || || || | | |||| | | ||| || | | || ||| | | || | | ||| | | | | |||| | | ||| | || ||| || | || |||| || |||| | || || ||| |||||| || ||| | | || || || |||| | || ||| ||| | |||| | || ||| || ||| || || || || | ||| || | || | || ||| || | | || | ||| || | | ||| | |||| || || ||| || | |||| || || || | | | | |||| | ||||| || |||| || '
b = ''.join(['0' if i == '|' else '1' for i in a])
i = int(b, 2)
h = '' if len(hex(i)) % 2 == 0 else '0' + hex(i)[2:]
u = '%' + '%'.join([h[i:i+2] for i in range(0, len(h), 2)])
import urllib
urllib.parse.unquote(u)
# 実行結果
picoCTF
SEE PUBLIC RECORDS & BACKGROUND REPORT
5000 Forbes Ave, Pittsburgh, PA 15213
picoCTF{not_all_spaces_are_created_equal_178d720252af1af29369e154eca23a95}
上のRockstarを先に解いていたから、最初Whitespaceプログラミングの問題かと思いました。
[Reverse Engineering] vault-door-8
Javaのコードが渡されます。8問目は、改行なし悪魔のワンライナーコードだったのでonline java beautifierにかけましたが、難読化(or デコンパイル後のもの?)されていました。
しかも、scramble
メソッド以下で、ビット単位で値をシャッフルしてて非常にめんどくさいコードになっています。
しかし、扱っている範囲は1 byte分でしかないので、scramble以下を流用し、0~255と変換後の値とのLookup Tableを作成することで、簡単にしてしまいます。
def scramble(c):
c = switchBits(c, 1, 2)
c = switchBits(c, 0, 3)
c = switchBits(c, 5, 6)
c = switchBits(c, 4, 7)
c = switchBits(c, 0, 1)
c = switchBits(c, 3, 4)
c = switchBits(c, 2, 5)
c = switchBits(c, 6, 7)
return c
def switchBits(c, p1, p2):
mask1 = 1 << p1
mask2 = 1 << p2
bit1 = c & mask1
bit2 = c & mask2
rest = c & ~(mask1 | mask2)
shift = p2 - p1
return (bit1 << shift) | (bit2 >> shift) | rest
lookup = {scramble(i): chr(i) for i in range(256)}
''.join([lookup[i] for i in [0xF4,0xC0,0x97,0xF0,0x77,0x97,0xC0,0xE4,0xF0,0x77,0xA4,0xD0,0xC5,0x77,0xF4,0x86,0xD0,0xA5,0x45,0x96,0x27,0xB5,0x77,0xF1,0xC1,0xF0,0x94,0xC1,0xA5,0xC1,0xC2,0xA4]])
# 実行結果
's0m3_m0r3_b1t_sh1fTiNg_743a4f48b'
[Cryptography] AES-ABC
データ部が暗号化されたPPM形式の画像と、その暗号化に利用したコードが与えられます。
暗号化は、AES ECSで暗号化後、先頭にIV追加、順々にブロックを混ぜていくことをしています。
Hintには、AES ECSが抜け目みたいなことが書いてあります。画像だし、よくみるTuxのあれかな・・・
ブロックの混ぜ合わせ部分を戻せれば、解にたどり着きそうなのでそこだけ頑張ります。
ブロックが先頭から $block[0], block[1], ..., block[n]$ と並んでいるとします。($block[0]$は、IVにあたります。)ブロックを混ぜ合わせる式は、
$block[i] = ( block[i] + block[i-1] ) \bmod 256^{16}, \qquad i=1, 2, \cdots, n$
なので、復号化時に $block[i-1]$ が $block[i]$ より大ならば、暗号化時に $\bmod$ されていたことがわかります。よって、
block[i] = \begin{cases}
256^{16} - block[i - 1] + block[i] & (block[i - 1] > block[i]) \\
block[i] - block[i - 1] & (otherwise)
\end{cases}, \qquad i=n, n-1, \cdots, 1
で混ぜ合わす前の値が得られます。暗号化時とは、処理順が逆方向なことに注意。
以下コード。
bsize = 16
maxval = 256 ** bsize
with open('body.enc.ppm', 'rb') as f:
data = f.read()
# header抽出
headers = []
for _ in range(3):
i = data.index(b'\n')
tmp, data = data[:i + 1], data[i + 1:]
headers.append(tmp)
# abcモードを戻す
blocks = [data[i * bsize:(i + 1) * bsize] for i in range(len(data) // bsize)]
blocks_i = [int.from_bytes(b, 'big') for b in blocks]
blocks_i_inv = list(reversed(blocks_i))
blocks_i_inv = [(maxval - a + c) if a > c else (c - a) for a, c in zip(blocks_i_inv[1:], blocks_i_inv)]
iv = blocks_i[0]
blocks_i = list(reversed(blocks_i_inv))
blocks = [i.to_bytes(bsize, 'big') for i in blocks_i]
with open('result.ppm', 'wb') as f:
f.write(b''.join(headers))
f.write(b''.join(blocks))
[Web Exploitation] Java Script Kiddie
Hintに従いサイトのJSを見るに、あるスペース区切りの数列をバイトとして読み込み、デコードしてPNG画像として読み込もうとしています。
デコード内容を見るに、データを16-byte区切りにし、行列(m行16列)として見立て、$i$列目にあたるバイトを、ある数値分だけ列方向にシフトさせるエンコードをしているようです。
先頭16-byteが、PNGのファイルシグネチャ、IHDRチャンク(先頭8-byte)になるシフト量の組合せを探索して、解候補の画像を全出力させて解きました。
arr = [59,120,172,124,140,0,73,158,164,109,61,140,73,175,14,206,200,239,223,243,254,10,26,254,255,34,202,16,0,44,235,218,137,252,155,0,207,0,1,59,78,222,89,190,154,245,147,0,0,80,254,71,96,73,68,69,0,122,90,201,98,251,10,82,164,0,188,114,20,144,88,10,1,231,163,66,110,73,0,108,48,0,0,2,231,194,75,114,84,234,105,13,61,130,68,164,128,16,78,191,74,94,160,65,165,27,174,0,157,72,95,55,36,163,1,1,206,46,28,186,208,68,0,237,164,192,110,209,226,10,0,97,0,164,22,36,248,0,0,143,13,77,60,108,191,61,133,252,13,81,146,110,119,0,156,237,84,174,163,249,80,241,244,86,0,243,159,156,83,120,160,42,191,210,126,63,2,143,33,59,133,242,187,235,1,28,133,231,122,187,222,52,142,26,107,146,26,75,171,52,199,155,79,26,56,119,173,125,191,42,86,159,70,250,48,173,236,22,121,54,144,250,6,62,190,136,243,13,84,120,193,108,210,222,176,249,83,172,106,116,250,145,73,212,241,19,63,241,83,11,117,56,207,28,223,190,249,55,88,72,11,233,243,215,90,227,157,177,249,15,255,2,172,110,63,157,243,19,44,136,153,249,245,245,212,156,1,157,116,7,134,103,94,66,83,154,122,67,210,78,125,255,219,56,241,188,8,8,90,252,155,177,99,32,75,136,98,96,72,114,73,19,250,32,8,92,90,20,13,247,164,137,195,35,221,34,39,195,172,211,201,104,160,35,241,77,5,199,99,254,121,82,19,233,46,102,192,100,106,248,191,217,223,182,252,125,247,72,222,100,39,70,127,33,84,189,134,156,167,106,36,76,13,98,249,250,119,149,205,249,149,145,144,31,135,158,153,152,45,199,118,29,102,226,102,61,31,115,150,33,162,147,168,76,123,238,83,255,235,144,215,121,124,195,174,82,78,20,245,253,173,90,129,115,32,234,239,125,164,234,99,113,77,130,151,31,40,97,199,114,248,62,165,223,146,207,203,160,181,92,147,88,237,183,214,249,158,221,45,131,102,207,231,33,87,99,179,229,10,127,21,0,255,145,214,191,218,30,187,243,137,231,154,167,136,120,227,16,234,65,223,143,210,83,168,172,144,55,151,217,35,211,253,252,219,252,175,240,171,177,82,120,83,199,123,239,243,203,179,123,249,190,241,134,122,230,32,223,225,169,55,254,14,8,53,17,132,157,126,175,215,139,49,247,207,142,251,17,31,87,249,117,190,89,195,42,237,213,163,127,21,209,113,157,95,187,130,110,50,29,207,6,195,147,61,181,57,223,190,236,251,219,235,183,111,56,39,85,30,127,143,83,166,253,127,191,126,51,59,114,174,178,38,127,183,14,103,204,156,227,43,66,47,126,124,255,34,247,206,109,137,146,7,122,219,249,32,245,73,31,126,110,174,40,140,32,72,7,254,184,103,234,45,253,39,227,201,44,127,173,179,255,177,78,32,190,121,251,242,126,255,78,229,63,141,159,234,254,249,131,68,239,199,206,241,63,255,107,62,190,104,126,53,59,191,23,242,194,103,205,96,132,143,84]
import numpy as np
mat = np.array([arr[i*16:(i+1)*16] for i in range(len(arr) // 16)])
png = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52]
buff = []
for i, v in enumerate(png):
buff.append([j for j, _ in filter(lambda x: x[1] == v, enumerate(mat[:, i]))])
def func(i, stack):
if i == 16:
name = '-'.join(map(str, stack))
tmp = []
for k, v in enumerate(stack):
tmp.append(np.roll(mat[:, k], -v))
tmp = b''.join(map(lambda x: int(x).to_bytes(1, 'big'), np.array(tmp).T.flatten()))
with open('js-%s.png' % name, 'wb') as f:
f.write(tmp)
else:
for j in buff[i]:
stack.append(j)
func(i + 1, stack)
stack.pop(-1)
func(0, [])
QRコードの画像が出力されるので、スマホで読み取ってフラグpicoCTF{4c182733af80dd49cc12d13be80d5893}
ゲット。