Edited at

SECCON 2016 Online CTF Writeup

More than 1 year has passed since last update.

https://score-quals.seccon.jp/question/

チームnicklegrで個人参加。

300点で305位(930チーム中)でした。

今年は難しくてさっぱり。本格的なCTFになってました。

いいことだけど、雑魚としてはプログラミング問題も少しほしかったなぁ。


Vigenere (Crypto 100)

ヴィジュネル暗号 - Wikipedia

k: ????????????

p: SECCON{???????????????????????????????????}
c: LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ

k=key, p=plain, c=cipher, md5(p)=f528a6ab914c1ecf856a1d93103948fe

|ABCDEFGHIJKLMNOPQRSTUVWXYZ{}
-+----------------------------
A|ABCDEFGHIJKLMNOPQRSTUVWXYZ{}
B|BCDEFGHIJKLMNOPQRSTUVWXYZ{}A
C|CDEFGHIJKLMNOPQRSTUVWXYZ{}AB
D|DEFGHIJKLMNOPQRSTUVWXYZ{}ABC
E|EFGHIJKLMNOPQRSTUVWXYZ{}ABCD
F|FGHIJKLMNOPQRSTUVWXYZ{}ABCDE
G|GHIJKLMNOPQRSTUVWXYZ{}ABCDEF
H|HIJKLMNOPQRSTUVWXYZ{}ABCDEFG
I|IJKLMNOPQRSTUVWXYZ{}ABCDEFGH
J|JKLMNOPQRSTUVWXYZ{}ABCDEFGHI
K|KLMNOPQRSTUVWXYZ{}ABCDEFGHIJ
L|LMNOPQRSTUVWXYZ{}ABCDEFGHIJK
M|MNOPQRSTUVWXYZ{}ABCDEFGHIJKL
N|NOPQRSTUVWXYZ{}ABCDEFGHIJKLM
O|OPQRSTUVWXYZ{}ABCDEFGHIJKLMN
P|PQRSTUVWXYZ{}ABCDEFGHIJKLMNO
Q|QRSTUVWXYZ{}ABCDEFGHIJKLMNOP
R|RSTUVWXYZ{}ABCDEFGHIJKLMNOPQ
S|STUVWXYZ{}ABCDEFGHIJKLMNOPQR
T|TUVWXYZ{}ABCDEFGHIJKLMNOPQRS
U|UVWXYZ{}ABCDEFGHIJKLMNOPQRST
V|VWXYZ{}ABCDEFGHIJKLMNOPQRSTU
W|WXYZ{}ABCDEFGHIJKLMNOPQRSTUV
X|XYZ{}ABCDEFGHIJKLMNOPQRSTUVW
Y|YZ{}ABCDEFGHIJKLMNOPQRSTUVWX
Z|Z{}ABCDEFGHIJKLMNOPQRSTUVWXY
{|{}ABCDEFGHIJKLMNOPQRSTUVWXYZ
}|}ABCDEFGHIJKLMNOPQRSTUVWXYZ{

ヴィジュネル暗号は平文と暗号文の各文字が分かれば鍵が求まるので、まずはSECCON{の部分。手作業でテーブルを引いてみた。

k: VIGENER?????

p: SECCON{???????????????????????????????????}
c: LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ

なら次の文字はEでしょう。

k: VIGENERE????

残り4文字なら、 28^4 = 614656 なので総当たりできる。md5(p)で正解か確認できるのがポイント。

require "pp"

require "digest/md5"

CIPHER = "LMIG}RPEDOEEWKJIQIWKJWMNDTSR}TFVUFWYOCBAJBQ"
CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}"

def to_index(c)
if "A".ord <= c.ord && c.ord <= "Z".ord
c.ord - "A".ord
else
return 26 if c == "{"
return 27 if c == "}"
raise
end
end

def make_table
table = Array.new(28) do
Array.new(28)
end

CHARS.each_char do |c|
CHARS.each_char do |k|
i = CHARS.index(c)
i -= to_index(k)
i += 28 if i < 0

raise if !CHARS[i]
table[to_index(k)][to_index(c)] = CHARS[i]
end
end

table
end

def decode(cipher, key, table)
ret = ""
key_index = 0
cipher.each_char do |c|
ret += table[to_index(key[key_index])][to_index(c)]
key_index += 1
key_index %= key.size
end
ret
end

table = make_table()

CHARS.each_char do |c1|
puts c1
CHARS.each_char do |c2|
CHARS.each_char do |c3|
CHARS.each_char do |c4|
key = "VIGENERE" + c1 + c2 + c3 + c4
plain = decode(CIPHER, key, table)
if Digest::MD5.hexdigest(plain) == "f528a6ab914c1ecf856a1d93103948fe"
puts "found!: #{key} #{plain}"
exit
end
end
end
end
end

A

B
C
found!: VIGENERECODE SECCON{ABABABCDEDEFGHIJJKLMNOPQRSTTUVWXYYZ}

鍵はVIGENERECODE。これくらいならエスパーした人もいるかも。


VoIP (Forensics 100)

Wiresharkに投げたらフラグを読み上げてくれた。

Wiresharkのコアな使い方|VoIPパケットを再生して品質を確認

"nine"が聞き取りずらかった。

SECCON{9001IVR}


Anti-Debugging (Binary 100)

Windowsバイナリ。パスワードチェック + アンチデバッグ。

WindowsはOllydbgが使えるので楽。

ご丁寧に、チェックに引っかかったらエラーメッセージを出してくれる。

ASCII "password is wrong."

ASCII "But detected debugger!"
ASCII "But detected Ollydbg."

これらの文字列の参照箇所にチェックルーチンがまとまってた。全部スキップするようにパッチを当てた。

Patches, item 0

Address = 004013A3
Module = bin
Size = 6.
Status = Applied
Modified command = JMP 00401663
Original command = JNE 0040174F
Comments =

実行したらフラグ。なぜかこれだけメッセージボックスで出てきた。

SECCON{check_Ascii85}


PNG over Telegraph (Crypto 300)

解けなかった。

https://youtu.be/Y6voaURtKlM

見た瞬間吹いた。なんだこれw

カメラアングルがほぼ固定なので、各ポーズのリファレンス画像を用意して差分を取ればいける気がするけど、だいぶ泥臭い作業になりそう。

あと文字が21種類しかないので、どうやってデコードするんだろ。Base64とかではなさそう。


他の人のWriteup

SECCON 2016 Online CTFのWrite-upが集まる魔法のスプレッドシート