日記

日本語の勉強のためのブログ

IERAE CTF 2024 Baby's writeup

チーム名numeron/ユーザ名numeronで参加。5問解いて693点取得し、224人中86位だった。

もともと参加の予定はなかったのであまり時間は取れなかったが、とっつきやすい問題も多く楽しめた。

OMG

戻るボタンを連打したら出てきた。

IERAE{Tr3ndy_4ds.LOL}

assignment

ghidraで解析すると、flag文字列が書いてある部分が見つかったが、文字の順番が入れ替わっている。手作業で並び替えるのも面倒なので、Cでsolver(と言っていいのか?)を書いた。

#include <stdio.h>

int main() {
  char flag[40]; // 適当な大きさ
  flag[28] = 0x33;
  flag[1] = 0x45;
  flag[2] = 0x52;
  flag[20] = 0x72;
  flag[26] = 0x61;
  flag[10] = 0x5f;
  flag[32] = 0x7d;
  flag[9] = 0x65;
  flag[22] = 0x6e;
  flag[17] = 0x5f;
  flag[6] = 0x73;
  flag[7] = 0x30;
  flag[15] = 0x30;
  flag[16] = 0x6d;
  flag[21] = 0x31;
  flag[24] = 0x5f;
  flag[12] = 0x34;
  flag[25] = 0x35;
  flag[31] = 99;
  flag[3] = 0x41;
  flag[0] = 0x49;
  flag[29] = 0x35;
  flag[18] = 0x73;
  flag[19] = 0x74;
  flag[11] = 0x72;
  flag[8] = 0x6d;
  flag[5] = 0x7b;
  flag[4] = 0x45;
  flag[27] = 0x39;
  flag[30] = 0x34;
  flag[23] = 0x67;
  flag[13] = 0x6e;
  flag[14] = 100;
  printf("%s\n", flag);
}

IERAE{s0me_r4nd0m_str1ng_5a9354c}

derangement

15文字の適当な文字列magic_wordを当てる問題。ヒントを聞くと、magic_wordの各文字の配置をランダムにした文字列が手に入る。このヒントは、もとのmagic_wordの配置と全く重ならないように作られている(任意のnについて、ヒントのn文字目はmagic_wordのn文字目と一致しない)ため、これを手がかりにしてmagic_wordを推測していく。

注意点として、配布プログラムのwhile connection_count < 300:にもあるように、ヒント問い合わせと回答送信は合わせて300回までしか行えない。そこで、最初に298回*1ヒントを聞いておき、それらを手がかりに回答するという手順でsolverを作成した。

以下がsolverのコードである。

from pwn import *

LENGTH = 15


def post_answer(io, answer: str):
    io.recvuntil(b"> ")
    io.sendline(b"2")
    io.recvuntil(b"> ")
    io.sendline(answer)
    io.interactive()


def ask_hint(io) -> str:
    io.recvuntil(b"> ")
    io.sendline(b"1")
    io.recvuntil(b"hint: ")
    return io.recv(LENGTH).decode()


def main():
    io = remote("104.199.135.28", 55555)
    hints: list[str] = [ask_hint(io) for i in range(298)]
    char_set = hints[0]

    answer: str = ""
    for i in range(15):
        chars_left: str = char_set
        for h in hints:
            chars_left = chars_left.replace(h[i], "")
            if len(chars_left) == 1:
                answer += chars_left[0]
                break
        print(i, answer)

    print(answer)
    post_answer(io, answer)


if __name__ == "__main__":
    main()

solverの実行結果を以下に示す。

[x] Opening connection to 104.199.135.28 on port 55555
[x] Opening connection to 104.199.135.28 on port 55555: Trying 104.199.135.28
[+] Opening connection to 104.199.135.28 on port 55555: Done
0 (
1 (t
2 (tq
3 (tq1
4 (tq1)
5 (tq1)P
6 (tq1)Pf
7 (tq1)Pf+
8 (tq1)Pf+r
9 (tq1)Pf+rp
10 (tq1)Pf+rp^
11 (tq1)Pf+rp^d
12 (tq1)Pf+rp^dG
13 (tq1)Pf+rp^dG,
14 (tq1)Pf+rp^dG,/
(tq1)Pf+rp^dG,/
[*] Switching to interactive mode
Congrats!
 IERAE{th3r35_n0_5uch_th!ng_45_p3rf3ct_3ncrypt!0n}
Connection limit reached. Exiting...
[*] Got EOF while reading in interactive

IERAE{th3r35_n0_5uch_th!ng_45_p3rf3ct_3ncrypt!0n}

Luz Da Lua

Luaのデコンパイラがあったので使う。 https://luadec.metaworm.site

-- filename: @/mnt/LuzDaLua.lua
-- version: lua54
-- line: [0, 0] id: 0
io.write("Input > ")
input = io.read("*l")
if string.len(input) ~= 28 then
  goto label_301
elseif string.byte(input, 1) ~ 232 ~= 161 then
  goto label_301
elseif string.byte(input, 2) ~ 110 ~= 43 then
  goto label_301
elseif string.byte(input, 3) ~ 178 ~= 224 then
  goto label_301
elseif string.byte(input, 4) ~ 172 ~= 237 then
  goto label_301
elseif string.byte(input, 5) ~ 212 ~= 145 then
  goto label_301
elseif string.byte(input, 6) ~ 25 ~= 98 then
  goto label_301
elseif string.byte(input, 7) ~ 53 ~= 121 then
  goto label_301
elseif string.byte(input, 8) ~ 63 ~= 74 then
  goto label_301
elseif string.byte(input, 9) ~ 135 ~= 230 then
  goto label_301
elseif string.byte(input, 10) ~ 92 ~= 3 then
  goto label_301
elseif string.byte(input, 11) ~ 38 ~= 23 then
  goto label_301
elseif string.byte(input, 12) ~ 250 ~= 137 then
  goto label_301
elseif string.byte(input, 13) ~ 216 ~= 135 then
  goto label_301
elseif string.byte(input, 14) ~ 5 ~= 86 then
  goto label_301
elseif string.byte(input, 15) ~ 69 ~= 117 then
  goto label_301
elseif string.byte(input, 16) ~ 226 ~= 189 then
  goto label_301
elseif string.byte(input, 17) ~ 137 ~= 186 then
  goto label_301
elseif string.byte(input, 18) ~ 148 ~= 240 then
  goto label_301
elseif string.byte(input, 19) ~ 64 ~= 53 then
  goto label_301
elseif string.byte(input, 20) ~ 130 ~= 225 then
  goto label_301
elseif string.byte(input, 21) ~ 241 ~= 197 then
  goto label_301
elseif string.byte(input, 22) ~ 151 ~= 227 then
  goto label_301
elseif string.byte(input, 23) ~ 203 ~= 250 then
  goto label_301
elseif string.byte(input, 24) ~ 179 ~= 220 then
  goto label_301
elseif string.byte(input, 25) ~ 216 ~= 182 then
  goto label_301
elseif string.byte(input, 26) ~ 101 ~= 4 then
  goto label_301
elseif string.byte(input, 27) ~ 238 ~= 130 then
  goto label_301
elseif string.byte(input, 28) ~ 61 ~= 64 then
  goto label_301
else
  print("Correct")
end
-- warn: not visited block [59]
-- block#59:
-- _ENV.print("Wrong")

ここで~はXOR演算、~=は等しくないことを示す比較演算子である。 入力とflagを(XOR演算を挟みつつ)1文字ずつ比較しているので、この逆演算をすればよい。

pythonで適当にsolverを書いた。

chars: list[int] = [232^161, 110^43, 178^224, 172^237, 212^145, 25^98, 53^121, 63^74, 135^230, 92^3, 38^23, 250^137, 216^135, 5^86, 69^117, 226^189, 137^186, 148^240, 64^53, 130^225, 241^197, 151^227, 203^250, 179^220, 216^182, 101^4, 238^130, 61^64]
flag: str = "".join([chr(c) for c in chars])
print(flag)

これを実行するとflagが得られる。

IERAE{Lua_1s_S0_3duc4t1onal}

*1:別に299回以下なら何回でもOKだと思うが念の為回数に余裕をもたせた