火曜日, 7月 29, 2025
火曜日, 7月 29, 2025
- Advertisment -
ホームニューステックニュース【CTF】SECCON Beginners 2025 Writeup🚩

【CTF】SECCON Beginners 2025 Writeup🚩


こんにちは。ぽちです。
この度、SECCON Beginners CTF 2025にチーム「THE Students」として参加しましたので、忘備録としてこのWriteupを残しておきます。
主にreversingを担当しました。初心者なので稚拙な部分もあるかと思いますが、一つの解法として参考にしていただければ幸いです。
去年のSECCON for Beginners 2024に続いての参加でしたが、今年はメンバーが変わり、少人数での挑戦となりました。
人数も少ない分、去年より順位も下がりましたが、今回も非常に楽しく学びの多いCTFでした。

Welcome (100pt / 865 solves)

問題

SECCON Beginners CTF 2025へようこそ Flagは ctf4b{W3lc0m3_2_SECCON_Beginners_CTF_2025} です

回答

flag

ctf4b{W3lc0m3_2_SECCON_Beginners_CTF_2025}

seesaw (100pt / 612 solves)

問題

RSA初心者です!pとqはこれでいいよね…?
配布ファイル: seesaw.zip(chall.py, output.txt)

chall.py

import os
from Crypto.Util.number import getPrime

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

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

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

output.txt

n = 362433315617467211669633373003829486226172411166482563442958886158019905839570405964630640284863309204026062750823707471292828663974783556794504696138513859209
c = 104442881094680864129296583260490252400922571545171796349604339308085282733910615781378379107333719109188819881987696111496081779901973854697078360545565962079

解説・解法

この問題はq16bitということがポイントです。
つまり、n = p * qqを総当たりで求めることができます。

与えられた情報

e = 65537
n = 362433315617467211669633373003829486226172411166482563442958886158019905839570405964630640284863309204026062750823707471292828663974783556794504696138513859209
c = 104442881094680864129296583260490252400922571545171796349604339308085282733910615781378379107333719109188819881987696111496081779901973854697078360545565962079

qは16bit → 最大でも2^16(65536)通りなので全探索。
n % q == 0を用いてqを求める。
p = n // qからφ(n)を計算。
d = inverser(e, φ(n))で秘密鍵を計算。
m = pow(c, d, n)で復号。
FLAG = long_to_bytes(m)でflagを獲得。

回答

solver.py

from Crypto.Util.number import long_to_bytes, inverse

n = 362433315617467211669633373003829486226172411166482563442958886158019905839570405964630640284863309204026062750823707471292828663974783556794504696138513859209
c = 104442881094680864129296583260490252400922571545171796349604339308085282733910615781378379107333719109188819881987696111496081779901973854697078360545565962079
e = 65537

for q in range(2, 1  16):
    if n % q == 0:
        p = n // q
        phi = (p - 1) * (q - 1)
        d = inverse(e, phi)
        m = pow(c, d, n)
        try:
            flag = long_to_bytes(m)
            if b'ctf4b' in flag:
                print(f"FLAG = {flag.decode()}")
                break
        except Exception:
            continue

flag

ctf4b{unb4l4nc3d_pr1m35_4r3_b4d}

CrazyLazyProgram1 (100pt / 654 solves)

問題

改行が面倒だったのでワンライナーにしてみました。
配布ファイル: CrazyLazyProgram1.zip(CLP1.cs)

CLP1.cs

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!");}}}}

これらのC#コードを可読性を高めるために改行してみました。

CLP1.cs

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!");
            }
        }
    }
}

解法

CyberChef配列flagの中身を変換します。

flag[]

0x63, 0x74, 0x66, 0x34, 0x62, 0x7b, 0x31, 0x5f, 0x31, 0x69, 0x6e, 0x33, 0x72, 0x35, 0x5f, 0x6d, 0x61, 0x6b, 0x33, 0x5f, 0x50, 0x47, 0x5f, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x32, 0x5f, 0x72, 0x33, 0x61, 0x64, 0x7d

すると、flagが出てきます。

回答

flag

ctf4b{1_1in3r5_mak3_PG_hard_2_r3ad}

CrazyLazyProgram2 (100pt / 468 solves)

問題

コーディングが面倒だったので機械語で作ってみました。
配布ファイル: CrazyLazyProgram2.zip(CLP2.o)

zipファイルを展開すると、CLP2.oというファイルが入っていました。
この.oファイル(オブジェクトファイル)をGhidraに投げて、デコンパイルしたmain関数の中身を見てみます。

main()

void main(void){
char local_38;
...()
undefined4 local_c;

printf("Enter the flag: ");

 __isoc99_scanf(&DAT_001003c6,&local_38);

local_c = 0;

if(local_38 == 'c' && 
  (local_c = 1, cStack_37 == 't') &&
  (local_c = 2, cStack_36 == 'f') &&
  (local_c = 3, cStack_35 == '4') &&
  (local_c = 4, cStack_34 == 'b') &&
  (local_c = 5, cStack_33 == '{') && 
  (local_c = 6, cStack_32 == 'G') &&
  (local_c = 7, cStack_31 == 'O') &&
  (local_c = 8, cStack_30 == 'T') &&
  (local_c = 9, cStack_2f == 'O') &&
  (local_c = 10, cStack_2e == '_') && 
  (local_c = 0xb, cStack_2d == 'G') && 
  (local_c = 0xc, cStack_2c == '0') &&
  (local_c = 0xd, cStack_2b == 'T') && 
  (local_c = 0xe, cStack_2a == '0') && 
  (local_c = 0xf, cStack_29 == '_') &&
  (local_c = 0x10, cStack_28 == '9') &&
  (local_c = 0x11, cStack_27 == '0') && 
  (local_c = 0x12, cStack_26 == 't') &&
  (local_c = 0x13, cStack_25 == '0') &&
  (local_c = 0x14, cStack_24 == '_') && 
  (local_c = 0x15, cStack_23 == 'N') && 
  (local_c = 0x16, cStack_22 == '0') &&
  (local_c = 0x17, cStack_21 == 'm') &&
  (local_c = 0x18, cStack_20 == '0') &&
  (local_c = 0x19, cStack_1f == 'r') &&
  (local_c = 0x1a, cStack_1e == '3') &&
  (local_c = 0x1b, cStack_1d == '_') &&
  (local_c = 0x1c, cStack_1c == '9') &&
  (local_c = 0x1d, cStack_1b == '0') &&
  (local_c = 0x1e, cStack_1a == 't') &&
  (local_c = 0x1f, cStack_19 == '0') &&
  (local_c = 0x20, cStack_18 == '}'))
{
    puts("Flag is correct!");
}
return;

上記のコードは、&&(AND)で繋がれた大量の比較処理をしているif文です。すべての条件が真(true)でないと、puts("Flag is correct!");は実行されません。
したがって、local_38が1文字目、cStack_37が2文字目…というように、各変数と比較されている文字を順番に繋ぎ合わせることでflagが復元できます。

回答

flag

ctf4b{GOTO_G0T0_90t0_N0m0r3_90t0}

D-compile (100pt / 335 solves)

問題

C言語の次はこれ!
※一部環境ではlibgphobos5が必要となります。また必要に応じてecho -nをご利用ください。
配布ファイル: d-compile.zip

解法

  1. ファイル形式の確認
    まず、d-compileのファイル種類をfileコマンドを使用し確認します。
$ file ./d-compile

./d-compile: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=73b95b2e5aea9420c85741c0582cb0945a3a3c69, for GNU/Linux 3.2.0, not stripped

・上記のELF 64-bit LSB executableからLinuxで動作する64ビットの実行ファイルであることが分かります。
not stripped: とてもありがたい情報です。これは、シンボル情報(関数名や変数名など)がバイナリ内に削除されず残っていることを意味します。これにより、逆アセンブルした際に関数名から処理内容を推測しやすくなり、解析が楽になります。
2. 埋め込まれた文字列の調査
次にstringsコマンドを実行してバイナリ内にハードコードされている文字列がないか探します。

$ strings ./d-compile
...()
input flag>
way to go! this is the flag :)
this is wrong
...()
challnege
challnege.d
...()
_Dmain
...()

出力が長いので一部割愛しますが、気になる文字列が見つかりました。

プログラムの挙動に関する文字列

input flag>
way to go! this is the flag :)
this is wrong
これらの文字列から、このプログラムがユーザーにフラグの入力を促し、その正誤を判定するフラグチェッカーであると推測できます。

解析の突破口となりうる文字列

challnege.d: .dという拡張子から、このプログラムがD言語で書かれている可能性が高いとわかります。
_Dmain: not strippedだったおかげでD言語のmain関数にあたるシンボル名がそのまま残っているのを発見できました。恐らくプログラムのメインロジックはここから始まると考えられます。

  1. 逆アセンブル(_Dmain関数の解析)
    ターミナルでobjdumpを使用して解析していきます。
objdump -M intel -d ./d-compile

実行するとえげつない量の出力が出てくるので、頑張って_Dmainの部分を探してください。

000000000000335a _Dmain>:
	335a:   55                      push   rbp
	335b:   48 89 e5                mov    rbp,rsp
	335e:   41 57                   push   r15
	3360:   41 56                   push   r14
	3362:   41 55                   push   r13
	3364:   41 54                   push   r12
	3366:   53                      push   rbx
	3367:   48 83 ec 58             sub    rsp,0x58
	336b:   48 c7 45 90 20 00 00    mov    QWORD PTR [rbp-0x70],0x20
	3372:   00 
	3373:   be 20 00 00 00          mov    esi,0x20
	3378:   48 8b 05 51 4c 00 00    mov    rax,QWORD PTR [rip+0x4c51]        # 7fd0 _D11TypeInfo_Aa6__initZ@Base>
	337f:   48 89 c7                mov    rdi,rax
	3382:   e8 e9 fd ff ff          call   3170 _d_arrayliteralTX@plt>
	3387:   c6 45 b0 63             mov    BYTE PTR [rbp-0x50],0x63
	338b:   c6 45 b1 74             mov    BYTE PTR [rbp-0x4f],0x74
	338f:   c6 45 b2 66             mov    BYTE PTR [rbp-0x4e],0x66
	3393:   c6 45 b3 34             mov    BYTE PTR [rbp-0x4d],0x34
	3397:   c6 45 b4 62             mov    BYTE PTR [rbp-0x4c],0x62
	339b:   c6 45 b5 7b             mov    BYTE PTR [rbp-0x4b],0x7b
	339f:   c6 45 b6 4e             mov    BYTE PTR [rbp-0x4a],0x4e
	33a3:   c6 45 b7 33             mov    BYTE PTR [rbp-0x49],0x33
	33a7:   c6 45 b8 78             mov    BYTE PTR [rbp-0x48],0x78
	33ab:   c6 45 b9 74             mov    BYTE PTR [rbp-0x47],0x74
	33af:   c6 45 ba 5f             mov    BYTE PTR [rbp-0x46],0x5f
	33b3:   c6 45 bb 54             mov    BYTE PTR [rbp-0x45],0x54
	33b7:   c6 45 bc 72             mov    BYTE PTR [rbp-0x44],0x72
	33bb:   c6 45 bd 33             mov    BYTE PTR [rbp-0x43],0x33
	33bf:   c6 45 be 6e             mov    BYTE PTR [rbp-0x42],0x6e
	33c3:   c6 45 bf 64             mov    BYTE PTR [rbp-0x41],0x64
	33c7:   c6 45 c0 5f             mov    BYTE PTR [rbp-0x40],0x5f
	33cb:   c6 45 c1 44             mov    BYTE PTR [rbp-0x3f],0x44
	33cf:   c6 45 c2 5f             mov    BYTE PTR [rbp-0x3e],0x5f
	33d3:   c6 45 c3 31             mov    BYTE PTR [rbp-0x3d],0x31
	33d7:   c6 45 c4 61             mov    BYTE PTR [rbp-0x3c],0x61
	33db:   c6 45 c5 6e             mov    BYTE PTR [rbp-0x3b],0x6e
	33df:   c6 45 c6 39             mov    BYTE PTR [rbp-0x3a],0x39
	33e3:   c6 45 c7 75             mov    BYTE PTR [rbp-0x39],0x75
	33e7:   c6 45 c8 61             mov    BYTE PTR [rbp-0x38],0x61
	33eb:   c6 45 c9 67             mov    BYTE PTR [rbp-0x37],0x67
	33ef:   c6 45 ca 33             mov    BYTE PTR [rbp-0x36],0x33
	33f3:   c6 45 cb 5f             mov    BYTE PTR [rbp-0x35],0x5f
	33f7:   c6 45 cc 31             mov    BYTE PTR [rbp-0x34],0x31
	33fb:   c6 45 cd 30             mov    BYTE PTR [rbp-0x33],0x30
	33ff:   c6 45 ce 31             mov    BYTE PTR [rbp-0x32],0x31
	3403:   c6 45 cf 7d             mov    BYTE PTR [rbp-0x31],0x7d
	3407:   48 8d 55 b0             lea    rdx,[rbp-0x50]
	340b:   48 8b 0a                mov    rcx,QWORD PTR [rdx]
	340e:   48 8b 5a 08             mov    rbx,QWORD PTR [rdx+0x8]
	3412:   48 89 08                mov    QWORD PTR [rax],rcx
	3415:   48 89 58 08             mov    QWORD PTR [rax+0x8],rbx
	3419:   48 8b 4a 10             mov    rcx,QWORD PTR [rdx+0x10]
	341d:   48 8b 5a 18             mov    rbx,QWORD PTR [rdx+0x18]
	3421:   48 89 48 10             mov    QWORD PTR [rax+0x10],rcx
	3425:   48 89 58 18             mov    QWORD PTR [rax+0x18],rbx
	3429:   48 89 45 98             mov    QWORD PTR [rbp-0x68],rax
	342d:   48 c7 45 80 0b 00 00    mov    QWORD PTR [rbp-0x80],0xb
	3434:   00 
	3435:   48 8d 05 cc 2b 00 00    lea    rax,[rip+0x2bcc]        # 6008 _IO_stdin_used+0x8>
	343c:   48 89 45 88             mov    QWORD PTR [rbp-0x78],rax
	3440:   48 8b 5d 80             mov    rbx,QWORD PTR [rbp-0x80]
	3444:   48 8b 75 88             mov    rsi,QWORD PTR [rbp-0x78]
	3448:   48 89 da                mov    rdx,rbx
	344b:   48 89 f0                mov    rax,rsi
	344e:   48 89 d7                mov    rdi,rdx
	3451:   48 89 c6                mov    rsi,rax
	3454:   e8 83 00 00 00          call   34dc _D3std5stdio__T7writelnTAyaZQnFNfQjZv>
	3459:   bf 0a 00 00 00          mov    edi,0xa
	345e:   e8 01 02 00 00          call   3664 _D3std5stdio__T6readlnTAyaZQmFwZQj>
	3463:   48 89 45 a0             mov    QWORD PTR [rbp-0x60],rax
	3467:   48 89 55 a8             mov    QWORD PTR [rbp-0x58],rdx
	346b:   48 8b 45 90             mov    rax,QWORD PTR [rbp-0x70]
	346f:   48 8b 55 98             mov    rdx,QWORD PTR [rbp-0x68]
	3473:   48 8b 7d a0             mov    rdi,QWORD PTR [rbp-0x60]
	3477:   48 8b 75 a8             mov    rsi,QWORD PTR [rbp-0x58]
	347b:   48 89 d1                mov    rcx,rdx
	347e:   48 89 c2                mov    rdx,rax
	3481:   e8 00 02 00 00          call   3686 _D4core8internal5array8equality__T8__equalsTaTaZQoFNaNbNiNeMxAaMxQeZb>
	3486:   84 c0                   test   al,al
	3488:   74 20                   je     34aa _Dmain+0x150>
	348a:   41 be 1e 00 00 00       mov    r14d,0x1e
	3490:   4c 8d 3d 81 2b 00 00    lea    r15,[rip+0x2b81]        # 6018 _IO_stdin_used+0x18>
	3497:   4c 89 f2                mov    rdx,r14
	349a:   4c 89 f8                mov    rax,r15
	349d:   48 89 d7                mov    rdi,rdx
	34a0:   48 89 c6                mov    rsi,rax
	34a3:   e8 34 00 00 00          call   34dc _D3std5stdio__T7writelnTAyaZQnFNfQjZv>
	34a8:   eb 1e                   jmp    34c8 _Dmain+0x16e>
	34aa:   41 bc 0d 00 00 00       mov    r12d,0xd
	34b0:   4c 8d 2d 80 2b 00 00    lea    r13,[rip+0x2b80]        # 6037 _IO_stdin_used+0x37>
	34b7:   4c 89 e2                mov    rdx,r12
	34ba:   4c 89 e8                mov    rax,r13
	34bd:   48 89 d7                mov    rdi,rdx
	34c0:   48 89 c6                mov    rsi,rax
	34c3:   e8 14 00 00 00          call   34dc _D3std5stdio__T7writelnTAyaZQnFNfQjZv>
	34c8:   b8 00 00 00 00          mov    eax,0x0
	34cd:   48 83 c4 58             add    rsp,0x58
	34d1:   5b                      pop    rbx
	34d2:   41 5c                   pop    r12
	34d4:   41 5d                   pop    r13
	34d6:   41 5e                   pop    r14
	34d8:   41 5f                   pop    r15
	34da:   5d                      pop    rbp
	34db:   c3                      ret

中身を要約すると、以下の3ステップで構成されています。

  1. 正解フラグの文字列をメモリ上に構築する。
  2. ユーザーからの入力を受け取る。
  3. 1と2を比較し、結果に応じてメッセージを表示する。

1.正解フラグの構築

_Dmainの前半、0x3387から0x3403のアドレスにかけて、以下のような命令が大量に並んでいます。

3387:   c6 45 b0 63             mov    BYTE PTR [rbp-0x50],0x63
338b:   c6 45 b1 74             mov    BYTE PTR [rbp-0x4f],0x74
338f:   c6 45 b2 66             mov    BYTE PTR [rbp-0x4e],0x66
3393:   c6 45 b3 34             mov    BYTE PTR [rbp-0x4d],0x34
...(略)
3403:   c6 45 cf 7d             mov    BYTE PTR [rbp-0x31],0x7d

これらの処理は、スタック上のメモリ領域([rdp-0x50]から始まる場所)に1バイトずつ即値を書き込んでいる処理です。この即値をASCII文字として解釈すると、正解となるflagが浮かび上がってきます。

なので、これらのASCII文字をCyberChefにかけて復号すると、flagとなります。

回答

flag

ctf4b{N3xt_Tr3nd_D_1an9uag3_101}

wasm_S_exp (100pt / 330 solves)

問題

フラグをチェックしてくれるプログラム
配布ファイル: wasm_S_exp.zip(check_flag.wat)

zipを展開するとcheck_flag.watというWAT(Wasm Text Format)形式のファイルが入っていました。

(module
  (memory (export "memory") 1 )
  (func (export "check_flag") (result i32)
    i32.const 0x7b
    i32.const 38
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x67
    i32.const 20
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x5f
    i32.const 46
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x21
    i32.const 3
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x63
    i32.const 18
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x6e
    i32.const 119
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x5f
    i32.const 51
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x79
    i32.const 59
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x34
    i32.const 9
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x57
    i32.const 4
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x35
    i32.const 37
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x33
    i32.const 12
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x62
    i32.const 111
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x63
    i32.const 45
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x7d
    i32.const 97
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x30
    i32.const 54
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x74
    i32.const 112
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x31
    i32.const 106
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x66
    i32.const 43
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x34
    i32.const 17
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x34
    i32.const 98
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x54
    i32.const 120
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x5f
    i32.const 25
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x6c
    i32.const 127
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 0x41
    i32.const 26
    call $stir
    i32.load8_u
    i32.ne
    if
      i32.const 0
      return
    end

    i32.const 1
    return
  )

  (func $stir (param $x i32) (result i32)
    i32.const 1024
    i32.const 23
    i32.const 37
    local.get $x
    i32.const 0x5a5a
    i32.xor
    i32.mul
    i32.add
    i32.const 101
    i32.rem_u
    i32.add
    return
  )
)

解説・解法

このWasmモジュールには2つの主要な関数があります。
check_flag: flagの検証を行うメインの関数
stir: flagの、どの位置の文字をチェックするかを計算するための関数。一種の難読化。

1.check_flag関数の解析

この関数は同じような構造のブロックが何度も繰り返されています。

    i32.const 0x7b   
    i32.const 38     
    call $stir       
    i32.load8_u      
    i32.ne           
    if               
      i32.const 0    
      return         
    end

このロジックから、stir関数が計算したメモリアドレスにある文字が、指定された文字と一致するかを調べていることがわかります。全てのチェックを通過すれば、最終的に1(成功)を返します。

2.stir関数の解析

次に、メモリアドレスを計算しているstir関数を解読します。

(func $stir (param $x i32) (result i32)
  i32.const 1024
  i32.const 23
  i32.const 37
  local.get $x
  i32.const 0x5a5a
  i32.xor
  i32.mul
  i32.add
  i32.const 101
  i32.rem_u
  i32.add
  return
)

これはスタックマシンなので、下から順に計算します。数式にすると以下のようになります。
address = 1024 + ((37 *(x ^ 0x5a5a) + 23) % 101)
ここで1024はflagが格納されるメモリのベースアドレス、xcheck_flagから渡されるパラメータに対応します。このxを用いて((37 *(x ^ 0x5a5a) + 23) % 101)を計算した結果が、検証対象となる文字のインデックス(何文字目か)となります。

3.flagの復元

あとはcheck_flagにある全てのブロックについて、xの値からインデックスを計算し、対応する文字を当てはめていくだけです。

回答

solver.py

def stir(x):
    index = ((37 * (x ^ 0x5a5a) + 23) % 101)
    return index

def solve():
    checks = [
        (0x7b, 38), (0x67, 20), (0x5f, 46), (0x21, 3), (0x63, 18),
        (0x6e, 119), (0x5f, 51), (0x79, 59), (0x34, 9), (0x57, 4),
        (0x35, 37), (0x33, 12), (0x62, 111), (0x63, 45), (0x7d, 97),
        (0x30, 54), (0x74, 112), (0x31, 106), (0x66, 43), (0x34, 17),
        (0x34, 98), (0x54, 120), (0x5f, 25), (0x6c, 127), (0x41, 26),
    ]

    flag_chars = []
    for char_code, x_val in checks:
        index = stir(x_val)
        character = chr(char_code)
        flag_chars.append((index, character))

    flag_chars.sort()
    final_flag = "".join([char for index, char in flag_chars])

    return final_flag

if __name__ == "__main__":
    flag = solve()
    print(f"Flag: {flag}")

flag

ctf4b{WAT_4n_345y_l0g1c!}

MAFC (339pt / 144 solves)

問題

flagが欲しいかい?ならこのマルウェアを解析してみな。
配布ファイル: MAFC.zip(file.encrypted, MalwareAnalysis-FirstChallenge.exe)

zipを展開すると、flag.encryptedとMalwareAnalysis-FirstChallange.exeが入ってました。
最初からGhidraに投げずに、初期調査を行います。

実行結果(長いです)
$ file MalwareAnalysis-FirstChallenge.exe              

MalwareAnalysis-FirstChallenge.exe: PE32+ executable (console) x86-64, for MS Windows, 6 sections

$ file flag.encrypted                                  

flag.encrypted: data

$ strings MalwareAnalysis-FirstChallenge.exe | head -20

!This program cannot be run in DOS mode.
.text
.rdata
.data
.pdata
.rsrc
.reloc

$ hexdump -C flag.encrypted | head -10                    

00000000  02 69 6f b6 fb 1f 26 4f  97 db 92 09 66 cc b8 a7  |.io...&O....f...|

00000010  d4 a1 e5 0e 9b e8 57 4d  fe 96 11 83 af 06 1f d8  |......WM........|

00000020  c2 22 05 80 d5 69 a5 4e  3b 78 f5 e0 bd 73 02 6c  |."...i.N;x...s.l|

00000030  7b 62 aa 9e 6c bf ad 2c  96 04 87 31 95 da b1 c2  |{b..l..,...1....|

00000040
$ ls -la                                                  

total 28
drwxr-xr-x 2 kali kali  4096 Jul 25 11:33 .
drwxrwxr-x 3 kali kali  4096 Jul 26 03:58 ..
-rw-r--r-- 1 kali kali    64 Jul 25 11:33 flag.encrypted
-rw-r--r-- 1 kali kali 15872 Jul 25 11:33 MalwareAnalysis-FirstChallenge.exe
                                                                                   $ readpe MalwareAnalysis-FirstChallenge.exe               

DOS Header
    Magic number:                    0x5a4d (MZ)
    Bytes in last page:              144
    Pages in file:                   3
    Relocations:                     0
    Size of header in paragraphs:    4
    Minimum extra paragraphs:        0
    Maximum extra paragraphs:        65535
    Initial (relative) SS value:     0
    Initial SP value:                0xb8
    Initial IP value:                0
    Initial (relative) CS value:     0
    Address of relocation table:     0x40
    Overlay number:                  0
    OEM identifier:                  0
    OEM information:                 0
    PE header offset:                0xf8
PE header
    Signature:                       0x00004550 (PE)
COFF/File header
    Machine:                         0x8664 IMAGE_FILE_MACHINE_AMD64
    Number of sections:              6
    Date/time stamp:                 1749952047 (Sun, 15 Jun 2025 01:47:27 UTC)
    Symbol Table offset:             0
    Number of symbols:               0
    Size of optional header:         0xf0
    Characteristics:                 0x22
    Characteristics names
                                         IMAGE_FILE_EXECUTABLE_IMAGE
                                         IMAGE_FILE_LARGE_ADDRESS_AWARE
Optional/Image header
    Magic number:                    0x20b (PE32+)
    Linker major version:            14
    Linker minor version:            44
    Size of .text section:           0x1600
    Size of .data section:           0x2a00
    Size of .bss section:            0
    Entrypoint:                      0x1970
    Address of .text section:        0x1000
    ImageBase:                       0x140000000
    Alignment of sections:           0x1000
    Alignment factor:                0x200
    Major version of required OS:    6
    Minor version of required OS:    0
    Major version of image:          0
    Minor version of image:          0
    Major version of subsystem:      6
    Minor version of subsystem:      0
    Win32 version value:             0
        Overwrite OS major version:      (default)
        Overwrite OS minor version:      (default)
        Overwrite OS build number:       (default)
        Overwrite OS platform id:        (default)
    Size of image:                   0x9000
    Size of headers:                 0x400
    Checksum:                        0
    Subsystem required:              0x3 (IMAGE_SUBSYSTEM_WINDOWS_CUI)
    DLL characteristics:             0x8160
    DLL characteristics names
                                         IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
                                         IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
                                         IMAGE_DLLCHARACTERISTICS_NX_COMPAT
                                       IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
    Size of stack to reserve:        0x100000
    Size of stack to commit:         0x1000
    Size of heap space to reserve:   0x100000
    Size of heap space to commit:    0x1000
    Loader Flags:                    0
    Loader Flags names
Data directories
    Directory
        IMAGE_DIRECTORY_ENTRY_IMPORT:    0x40ac (180 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_RESOURCE:  0x7000 (480 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_EXCEPTION: 0x6000 (540 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_BASERELOC: 0x8000 (88 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_DEBUG:     0x37a0 (112 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: 0x3660 (320 bytes)
    Directory
        IMAGE_DIRECTORY_ENTRY_IAT:       0x3000 (592 bytes)
Imported functions
    Library
        Name:                            KERNEL32.dll
        Functions
            Function
                Hint:                            630
                Name:                            GetFileSize
            Function
                Hint:                            156
                Name:                            CloseHandle
            Function
                Hint:                            306
                Name:                            DeleteFileA
            Function
                Hint:                            221
                Name:                            CreateFileA
            Function
                Hint:                            1631
                Name:                            WriteFile
            Function
                Hint:                            1192
                Name:                            ReadFile
            Function
                Hint:                            1296
                Name:                            RtlLookupFunctionEntry
            Function
                Hint:                            1303
                Name:                            RtlVirtualUnwind
            Function
                Hint:                            1530
                Name:                            UnhandledExceptionFilter
            Function
                Hint:                            676
                Name:                            GetModuleHandleW
            Function
                Hint:                            1463
                Name:                            SetUnhandledExceptionFilter
            Function
                Hint:                            576
                Name:                            GetCurrentProcess
            Function
                Hint:                            1288
                Name:                            RtlCaptureContext
            Function
                Hint:                            1495
                Name:                            TerminateProcess
            Function
                Hint:                            951
                Name:                            IsProcessorFeaturePresent
            Function
                Hint:                            1152
                Name:                            QueryPerformanceCounter
            Function
                Hint:                            577
                Name:                            GetCurrentProcessId
            Function
                Hint:                            581
                Name:                            GetCurrentThreadId
            Function
                Hint:                            793
                Name:                            GetSystemTimeAsFileTime
            Function
                Hint:                            943
                Name:                            IsDebuggerPresent
            Function
                Hint:                            921
                Name:                            InitializeSListHead
    Library
        Name:                            ADVAPI32.dll
        Functions
            Function
                Hint:                            222
                Name:                            CryptSetKeyParam
            Function
                Hint:                            200
                Name:                            CryptDestroyKey
            Function
                Hint:                            194
                Name:                            CryptAcquireContextW
            Function
                Hint:                            203
                Name:                            CryptEncrypt
            Function
                Hint:                            196
                Name:                            CryptCreateHash
            Function
                Hint:                            198
                Name:                            CryptDeriveKey
            Function
                Hint:                            217
                Name:                            CryptHashData
            Function
                Hint:                            220
                Name:                            CryptReleaseContext
            Function
                Hint:                            199
                Name:                            CryptDestroyHash
    Library
        Name:                            VCRUNTIME140.dll
        Functions
            Function
                Hint:                            1
                Name:                            _CxxThrowException
            Function
                Hint:                            28
                Name:                            __current_exception_context
            Function
                Hint:                            33
                Name:                            __std_exception_copy
            Function
                Hint:                            34
                Name:                            __std_exception_destroy
            Function
                Hint:                            8
                Name:                            __C_specific_handler
            Function
                Hint:                            27
                Name:                            __current_exception
            Function
                Hint:                            60
                Name:                            memcpy
            Function
                Hint:                            62
                Name:                            memset
    Library
        Name:                            api-ms-win-crt-runtime-l1-1-0.dll
        Functions
            Function
                Hint:                            60
                Name:                            _register_onexit_function
            Function
                Hint:                            30
                Name:                            _crt_atexit
            Function
                Hint:                            21
                Name:                            _c_exit
            Function
                Hint:                            22
                Name:                            _cexit
            Function
                Hint:                            5
                Name:                            __p___argv
            Function
                Hint:                            55
                Name:                            _initterm_e
            Function
                Hint:                            52
                Name:                            _initialize_onexit_table
            Function
                Hint:                            35
                Name:                            _exit
            Function
                Hint:                            85
                Name:                            exit
            Function
                Hint:                            54
                Name:                            _initterm
            Function
                Hint:                            40
                Name:                            _get_initial_narrow_environment
            Function
                Hint:                            51
                Name:                            _initialize_narrow_environment
            Function
                Hint:                            24
                Name:                            _configure_narrow_argv
            Function
                Hint:                            61
                Name:                            _register_thread_local_exe_atexit_callback
            Function
                Hint:                            66
                Name:                            _set_app_type
            Function
                Hint:                            64
                Name:                            _seh_filter_exe
            Function
                Hint:                            4
                Name:                            __p___argc
            Function
                Hint:                            103
                Name:                            terminate
            Function
                Hint:                            58
                Name:                            _invoke_watson
    Library
        Name:                            api-ms-win-crt-stdio-l1-1-0.dll
        Functions
            Function
                Hint:                            84
                Name:                            _set_fmode
            Function
                Hint:                            1
                Name:                            __p__commode
            Function
                Hint:                            147
                Name:                            puts
    Library
        Name:                            api-ms-win-crt-heap-l1-1-0.dll
        Functions
            Function
                Hint:                            22
                Name:                            _set_new_mode
            Function
                Hint:                            24
                Name:                            free
            Function
                Hint:                            25
                Name:                            malloc
            Function
                Hint:                            8
                Name:                            _callnewh
    Library
        Name:                            api-ms-win-crt-math-l1-1-0.dll
        Functions
            Function
                Hint:                            9
                Name:                            __setusermatherr
    Library
        Name:                            api-ms-win-crt-locale-l1-1-0.dll
        Functions
            Function
                Hint:                            8
                Name:                            _configthreadlocale
Exported functions
Sections
    Section
        Name:                            .text
        Virtual Size:                    0x15bc (5564 bytes)
        Virtual Address:                 0x1000
        Size Of Raw Data:                0x1600 (5632 bytes)
        Pointer To Raw Data:             0x400
        Number Of Relocations:           0
        Characteristics:                 0x60000020
        Characteristic Names
                                             IMAGE_SCN_CNT_CODE
                                             IMAGE_SCN_MEM_EXECUTE
                                             IMAGE_SCN_MEM_READ
    Section
        Name:                            .rdata
        Virtual Size:                    0x196e (6510 bytes)
        Virtual Address:                 0x3000
        Size Of Raw Data:                0x1a00 (6656 bytes)
        Pointer To Raw Data:             0x1a00
        Number Of Relocations:           0
        Characteristics:                 0x40000040
        Characteristic Names
                                             IMAGE_SCN_CNT_INITIALIZED_DATA
                                             IMAGE_SCN_MEM_READ
    Section
        Name:                            .data
        Virtual Size:                    0x720 (1824 bytes)
        Virtual Address:                 0x5000
        Size Of Raw Data:                0x200 (512 bytes)
        Pointer To Raw Data:             0x3400
        Number Of Relocations:           0
        Characteristics:                 0xc0000040
        Characteristic Names
                                             IMAGE_SCN_CNT_INITIALIZED_DATA
                                             IMAGE_SCN_MEM_READ
                                             IMAGE_SCN_MEM_WRITE
    Section
        Name:                            .pdata
        Virtual Size:                    0x21c (540 bytes)
        Virtual Address:                 0x6000
        Size Of Raw Data:                0x400 (1024 bytes)
        Pointer To Raw Data:             0x3600
        Number Of Relocations:           0
        Characteristics:                 0x40000040
        Characteristic Names
                                             IMAGE_SCN_CNT_INITIALIZED_DATA
                                             IMAGE_SCN_MEM_READ
    Section
        Name:                            .rsrc
        Virtual Size:                    0x1e0 (480 bytes)
        Virtual Address:                 0x7000
        Size Of Raw Data:                0x200 (512 bytes)
        Pointer To Raw Data:             0x3a00
        Number Of Relocations:           0
        Characteristics:                 0x40000040
        Characteristic Names
                                             IMAGE_SCN_CNT_INITIALIZED_DATA
                                             IMAGE_SCN_MEM_READ
    Section
        Name:                            .reloc
        Virtual Size:                    0x58 (88 bytes)
        Virtual Address:                 0x8000
        Size Of Raw Data:                0x200 (512 bytes)
        Pointer To Raw Data:             0x3c00
        Number Of Relocations:           0
        Characteristics:                 0x42000040
        Characteristic Names
                                             IMAGE_SCN_CNT_INITIALIZED_DATA
                                             IMAGE_SCN_MEM_DISCARDABLE
                                             IMAGE_SCN_MEM_READ

$ objdump -f MalwareAnalysis-FirstChallenge.exe | grep start

start address 0x0000000140001970

$ strings -a MalwareAnalysis-FirstChallenge.exe | grep -i -E "(key|pass|secret|flag|aes|rc4|crypt|encrypt|decrypt)"

flag.txt
Failed to handle flag.txt
flag.encrypted
Failed to handle flag.encrypted
CryptAcquireContext() Error
CryptCreateHash() Error
ThisIsTheEncryptKey
CryptHashData() Error
CryptDeriveKey() Error
CryptSeKeyParam() Error
CryptSeKeyParam() with IV Error
CryptSetKeyParam() with set MODE Error
CryptEncrypt() Error
CryptDestroyKey() error
CryptDestroyHash() error
CryptReleaseContext() error
CryptReleaseContext
CryptSetKeyParam
CryptDestroyHash
CryptHashData
CryptDeriveKey
CryptCreateHash
CryptEncrypt
CryptAcquireContextW
CryptDestroyKey

$ strings -a MalwareAnalysis-FirstChallenge.exe | grep -E "^[a-zA-Z0-9]{8,16}$"

ReadFile
WriteFile
CreateFileA
DeleteFileA
CloseHandle
GetFileSize
CryptSetKeyParam
CryptDestroyHash
CryptHashData
CryptDeriveKey
CryptCreateHash
CryptEncrypt
CryptDestroyKey
terminate
RtlVirtualUnwind
TerminateProcess
GetModuleHandleW

$ strings -a MalwareAnalysis-FirstChallenge.exe | grep -i "ctf"

上記の結果から以下のことがわかりました。

  1. Windows CryptoAPIの使用: インポートテーブルにCryptCreateHashCryptDeriveKeyといったWindows CryptoAPIの関数群が含まれており、Windows標準の暗号化機能を使用している
  2. 暗号鍵文字列: stringsの出力の中に、暗号鍵らしき文字列ThisIsTheEncryptKeyがある

次に、実行ファイル内のコードをGhidraで解析します。
Symbol Treeで使用されている関数名を確認してみると、main関数が確認できませんね。
これはコンパイル時にシンボル情報が削除されていることを意味します。
ソースコードに記述されているmainWinMainといった関数名は、人間がコードを読みやすくするためのシンボル(目印) です。

関数名はどうなるの?

関数名は以下の手順で新たな関数名になります。

  1. ソースコード段階: int main(){...}のように、mainという名前がはっきりと存在します。
  2. コンパイル段階: ソースコードをコンピュータが実行できる機械語に変換する際、ファイルサイズを小さくしたり、解析を少しでも難しくしたりするために、これらのシンボル情報は通常取り除かれます。
  3. 実行ファイル段階: 最終的に出来上がったexeファイルには、mainという名前は残っておらず、「プログラムのこの部分(例:アドレス0x1400011a0)から処理を始めなさい」という情報だけが記録されます。

Ghidraは、この名前が削除された実行ファイルを解析します。そのため、関数の場所はわかっていても元の名前はわからないので、FUN_ + アドレスの形の名前が自動的に与えられます。

解析に戻ります。
Symbol Treeを確認するとentry()という関数があります。中身を確認してみましょう。

entry();

void entry(void)

{
  FUN_140001d8c();
  FUN_1400017f4();
  return;
}

・これがプログラムの開始点です。
・関数FUN_14001d8c()FUN_140017f4を順番に呼び出しています。
ではこれら2つの関数の中身も追っていきましょう。

FUN_14001d8c();

void FUN_140001d8c(void)

{
  DWORD DVar1;
  _FILETIME local_res8;
  LARGE_INTEGER local_res10;
  _FILETIME local_18 [2];
  
  if (DAT_140005000 == 0x2b992ddfa232) {
    local_res8.dwLowDateTime = 0;
    local_res8.dwHighDateTime = 0;
    GetSystemTimeAsFileTime(&local_res8);
    local_18[0] = local_res8;
    DVar1 = GetCurrentThreadId();
    local_18[0] = (_FILETIME)((ulonglong)local_18[0] ^ (ulonglong)DVar1);
    DVar1 = GetCurrentProcessId();
    local_18[0] = (_FILETIME)((ulonglong)local_18[0] ^ (ulonglong)DVar1);
    QueryPerformanceCounter(&local_res10);
    DAT_140005000 =
         ((ulonglong)local_res10.s.LowPart  0x20 ^
          CONCAT44(local_res10.s.HighPart,local_res10.s.LowPart) ^ (ulonglong)local_18[0] ^
         (ulonglong)local_18) & 0xffffffffffff;
    if (DAT_140005000 == 0x2b992ddfa232) {
      DAT_140005000 = 0x2b992ddfa233;
    }
  }
  DAT_140005040 = ~DAT_140005000;
  return;
}

これはプログラムのセキュリティ機能(セキュリティクッキーなど)を初期化する、コンパイラが自動で生成するコードです。
GetSystemTimeAsFileTimeQueryPerformanceCounterなどを使って予測されにくい「乱数」のようなものを生成しています。
無視して問題ありません。

FUN_1400017f4();

int FUN_1400017f4(void)

{
  undefined8 uVar1;
  bool bVar2;
  char cVar3;
  undefined1 uVar4;
  int iVar5;
  longlong *plVar6;
  undefined8 uVar7;
  undefined8 *puVar8;
  undefined4 *puVar9;
  undefined8 unaff_RBX;
  undefined8 in_R9;
  undefined1 uVar10;
  
  iVar5 = (int)unaff_RBX;
  cVar3 = FUN_140001b88(1);
  if (cVar3 == '\0') {
    FUN_140001ebc(7);
  }
  else {
    bVar2 = false;
    uVar10 = 0;
    uVar4 = __scrt_acquire_startup_lock();
    iVar5 = (int)CONCAT71((int7)((ulonglong)unaff_RBX >> 8),uVar4);
    if (DAT_140005690 != 1) {
      if (DAT_140005690 == 0) {
        DAT_140005690 = 1;
        iVar5 = _initterm_e(&DAT_1400032a0,&DAT_1400032b8);
        if (iVar5 != 0) {
          return 0xff;
        }
        _initterm(&DAT_140003288,&DAT_140003298);
        DAT_140005690 = 2;
      }
      else {
        bVar2 = true;
        uVar10 = 1;
      }
      __scrt_release_startup_lock(uVar4);
      plVar6 = (longlong *)FUN_140001ea0();
      if ((*plVar6 != 0) && (cVar3 = FUN_140001c50(plVar6), cVar3 != '\0')) {
        (*(code *)*plVar6)(0,2,0,in_R9,uVar10);
      }
      plVar6 = (longlong *)FUN_140001ea8();
      if ((*plVar6 != 0) && (cVar3 = FUN_140001c50(plVar6), cVar3 != '\0')) {
        _register_thread_local_exe_atexit_callback(*plVar6);
      }
      uVar7 = _get_initial_narrow_environment();
      puVar8 = (undefined8 *)__p___argv();
      uVar1 = *puVar8;
      puVar9 = (undefined4 *)__p___argc();
      iVar5 = FUN_1400011a0(*puVar9,uVar1,uVar7);
      cVar3 = FUN_140002010();
      if (cVar3 != '\0') {
        if (!bVar2) {
          _cexit();
        }
        __scrt_uninitialize_crt(1,0);
        return iVar5;
      }
      goto LAB_140001960;
    }
  }
  FUN_140001ebc(7);
LAB_140001960:
                    
  exit(iVar5);
}

これはC言語(C++)のプログラムがmain関数を呼び出す前に必ず行われるランタイム初期化処理です。

「じゃあこれ関係なくない?」って思いますよね。
いいえ。あるんです。
プログラムをよく読んでみて下さい。
_initterm_e__p___argcといった名前から、これがmain関数を呼び出すための準備をしていることがわかります。
最も重要なポイント⚠
54行目(下から17行目)を見てください。

FUN_1400017f4();

iVar5 = FUN_1400011a0(*puVar9,uVar1,uVar7);

この関数(FUN_1400017f4)内部で、FUN_140011a0という関数が呼び出されています。
これこそが、プログラムの本体(main関数に相当する部分)です。

では、この関数を解析しましょう。

FUN_140011a0();

void FUN_1400011a0(void)

{
  uint uVar1;
  code *pcVar2;
  longlong lVar3;
  BOOL BVar4;
  DWORD nNumberOfBytesToRead;
  HANDLE hFile;
  HANDLE hFile_00;
  void *pvVar5;
  longlong lVar6;
  BYTE *pbData;
  BYTE *_Memory;
  ulonglong _Size;
  longlong lVar7;
  BYTE *pBVar8;
  undefined1 auStackY_b8 [32];
  HCRYPTKEY local_78;
  HCRYPTPROV local_70;
  BYTE local_68 [8];
  DWORD local_60;
  DWORD local_5c;
  HCRYPTHASH local_58;
  BYTE local_50 [24];
  ulonglong local_38;
  
  local_38 = DAT_140005000 ^ (ulonglong)auStackY_b8;
  pbData = (BYTE *)0x0;
  hFile = CreateFileA("flag.txt",0x80000000,1,(LPSECURITY_ATTRIBUTES)0x0,3,0x80,(HANDLE)0x0);
  if (hFile == (HANDLE)0xffffffffffffffff) {
    puts("Failed to handle flag.txt\n");
  }
  else {
    hFile_00 = CreateFileA("flag.encrypted",0x40000000,0,(LPSECURITY_ATTRIBUTES)0x0,2,0x80,
                           (HANDLE)0x0);
    if (hFile_00 == (HANDLE)0xffffffffffffffff) {
      puts("Failed to handle flag.encrypted\n");
      goto LAB_140001637;
    }
    BVar4 = CryptAcquireContextW
                      (&local_70,(LPCWSTR)0x0,
                       L"Microsoft Enhanced RSA and AES Cryptographic Provider",0x18,0);
    if ((BVar4 == 0) &&
       (BVar4 = CryptAcquireContextW
                          (&local_70,(LPCWSTR)0x0,
                           L"Microsoft Enhanced RSA and AES Cryptographic Provider",0x18,8),
       BVar4 == 0)) {
      puts("CryptAcquireContext() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptCreateHash(local_70,0x800c,0,0,&local_58);
    if (BVar4 == 0) {
      puts("CryptCreateHash() Error\n");
      goto LAB_140001637;
    }
    builtin_memcpy(local_50 + 0x10,"Key",4);
    builtin_memcpy(local_50,"ThisIsTheEncrypt",0x10);
    lVar6 = -1;
    do {
      lVar7 = lVar6 + 1;
      lVar3 = lVar6 + 1;
      lVar6 = lVar7;
    } while (local_50[lVar3] != '\0');
    BVar4 = CryptHashData(local_58,local_50,(DWORD)lVar7,0);
    if (BVar4 == 0) {
      puts("CryptHashData() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptDeriveKey(local_70,0x6610,local_58,0x1000000,&local_78);
    if (BVar4 == 0) {
      puts("CryptDeriveKey() Error\n");
      goto LAB_140001637;
    }
    local_68[0] = '\x01';
    local_68[1] = '\0';
    local_68[2] = '\0';
    local_68[3] = '\0';
    BVar4 = CryptSetKeyParam(local_78,3,local_68,0);
    if (BVar4 == 0) {
      puts("CryptSeKeyParam() Error\n");
      goto LAB_140001637;
    }
    BVar4 = CryptSetKeyParam(local_78,1,(BYTE *)L"IVCanObfuscation",0);
    if (BVar4 == 0) {
      puts("CryptSeKeyParam() with IV Error\n");
      goto LAB_140001637;
    }
    local_68[4] = '\x01';
    local_68[5] = '\0';
    local_68[6] = '\0';
    local_68[7] = '\0';
    BVar4 = CryptSetKeyParam(local_78,4,local_68 + 4,0);
    if (BVar4 == 0) {
      puts("CryptSetKeyParam() with set MODE Error\n");
      goto LAB_140001637;
    }
    nNumberOfBytesToRead = GetFileSize(hFile,(LPDWORD)0x0);
    uVar1 = nNumberOfBytesToRead + 0x10;
    _Size = (ulonglong)uVar1;
    pBVar8 = pbData;
    if (uVar1 != 0) {
      if (_Size  0x1000) {
        pbData = (BYTE *)operator_new(_Size);
      }
      else {
        if ((ulonglong)uVar1 + 0x27  _Size) {
          FUN_140001100();
          pcVar2 = (code *)swi(3);
          (*pcVar2)();
          return;
        }
        pvVar5 = operator_new((ulonglong)uVar1 + 0x27);
        if (pvVar5 == (void *)0x0) goto LAB_140001653;
        pbData = (BYTE *)((longlong)pvVar5 + 0x27U & 0xffffffffffffffe0);
        *(void **)(pbData + -8) = pvVar5;
      }
      pBVar8 = pbData + _Size;
      memset(pbData,0,_Size);
    }
    local_60 = 0;
    BVar4 = ReadFile(hFile,pbData,nNumberOfBytesToRead,&local_60,(LPOVERLAPPED)0x0);
    if (BVar4 == 0) {
      puts("ReadFile() Error\n");
    }
    else {
      lVar6 = -1;
      do {
        lVar6 = lVar6 + 1;
      } while (pbData[lVar6] != '\0');
      local_5c = (int)lVar6 + 1;
      BVar4 = CryptEncrypt(local_78,0,1,0,pbData,&local_5c,0x40);
      if (BVar4 == 0) {
        puts("CryptEncrypt() Error\n");
      }
      else {
        BVar4 = WriteFile(hFile_00,pbData,0x40,(LPDWORD)0x0,(LPOVERLAPPED)0x0);
        if (BVar4 == 0) {
          puts("WriteFile() error\n");
        }
        else {
          CloseHandle(hFile);
          CloseHandle(hFile_00);
          BVar4 = DeleteFileA("flag.txt");
          if (BVar4 == 0) {
            puts("DeleteFileA() error\n");
          }
          else {
            BVar4 = CryptDestroyKey(local_78);
            if (BVar4 == 0) {
              puts("CryptDestroyKey() error\n");
            }
            else {
              BVar4 = CryptDestroyHash(local_58);
              if (BVar4 == 0) {
                puts("CryptDestroyHash() error\n");
              }
              else {
                BVar4 = CryptReleaseContext(local_70,0);
                if (BVar4 == 0) {
                  puts("CryptReleaseContext() error\n");
                }
              }
            }
          }
        }
      }
    }
    if (pbData != (BYTE *)0x0) {
      _Memory = pbData;
      if ((0xfff  (ulonglong)((longlong)pBVar8 - (longlong)pbData)) &&
         (_Memory = *(BYTE **)(pbData + -8), (BYTE *)0x1f  pbData + (-8 - (longlong)_Memory))) {
LAB_140001653:
                    
        _invoke_watson((wchar_t *)0x0,(wchar_t *)0x0,(wchar_t *)0x0,0,0);
      }
      free(_Memory);
    }
  }
LAB_140001637:
  FUN_140001680(local_38 ^ (ulonglong)auStackY_b8);
  return;
}

長いですね。めんどくさいですがもう少しです。
CryptCreateHashCryptDeriveKeyを呼び出している箇所を確認し、それらのAPIに渡されている引数を特定します。
こちらを参照してください。
上記を踏まえて解析を行ったところ、以下全ての暗号化パラメータが判明しました。

  1. ハッシュアルゴリズム: SHA-256
    CryptCreateHash(local_70,0x800c,0,0,&local_58)
    0x800cCALG_SHA_256の定数
  2. 暗号化アルゴリズム: AES-256
    CryptDeriveKey(local_70,0x6610,local_58,0x1000000,&local_78);
    0x6610CALG_AES_256の定数であり、キー長は32バイト
  3. モード: CBC
    CryptSetKeyParam(local_78,4,local_68 + 4,0)
    ・第二引数の4(KP_MODE)、第三引数のデータに値1(CRYPT_MODE_CBC)がセットされている
  4. パスワード: “ThisIsEncryptKey”
    builtin_memcpy(local_50,"ThisIsTheEncrypt",0x10);builtin_memcpy(local_50 + 0x10,"Key",4);
    上記は、メモリ上で"ThisIsEncrypt""Key"を連結して、結果として"ThisIsEncryptKey"という文字列を生成しています。
  5. IV(初期化ベクトル)
    CryptSetKeyParam(local_78, 1, (BYTE *)L"IVCanObfuscation", 0)
    ・IVとして、L"IVObfuscation"というワイド文字列(1文字2バイト) が指定されている
     AESのIVは16バイトなので、プログラムはこの文字列のメモリ上での表現(UTF-16LE)の先頭16バイトをIVとして使用する
    ・バイト列: b'I\x00V\x00C\x00a\x00n\x00O\x00b\x00f\x00'

これらを踏まえてsolverを書きましょう。

回答

solver1(失敗)

solver1.py

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

ENCRYPTED_FILE = "flag.encrypted"
PASSWORD = b"ThisIsTheEncryptKey"
IV = b"IVCanObfuscation"

if __name__ == "__main__":
    try:
        with open(ENCRYPTED_FILE, "rb") as f:
            encrypted_data = f.read()
    except FileNotFoundError:
        print(f"Error: {ENCRYPTED_FILE} Not Found")
        exit()

    h_sha256 = hashlib.sha256()
    h_sha256.update(PASSWORD)

    key = h_sha256.digest()

    print("-" * 80)
    print(f"[*] Password: {PASSWORD.decode()}")
    print(f"[*] IV:         {IV.decode()}")
    print(f"[*] Key:      {key.hex()}")
    print("-" * 80)

    try:
        cipher = AES.new(key, AES.MODE_CBC, IV)
        
        decrypted_data = cipher.decrypt(encrypted_data)
        unpadded_data = unpad(decrypted_data, AES.block_size)
        
        print("\nDecrypted.")
        print(f"\nFLAG: {unpadded_data.decode('utf-8')}\n")

    except Exception as e:
        print(f"\nError: {e}")
$ python3 solver.py
--------------------------------------------------------------------------------
[*] Password: ThisIsTheEncryptKey
[*] IV:     	IVCanObfuscation
[*] Key:  	adafd798c69ffaef2b2bbb44364f0952b988cdd37bb66bb2cb19b5827a8a2465
--------------------------------------------------------------------------------

Decrypted.

FLAG: c"sUO4tb,>/Y(1y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

solver2.py

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

ENCRYPTED_FILE = "flag.encrypted"
PASSWORD = b"ThisIsTheEncryptKey"
IV = b'I\x00V\x00C\x00a\x00n\x00O\x00b\x00f\x00'

if __name__ == "__main__":
	try:
    	with open(ENCRYPTED_FILE, "rb") as f:
        	encrypted_data = f.read()
	except FileNotFoundError:
    	print(f"Error: {ENCRYPTED_FILE} Not Found.")
    	exit()

	h_sha256 = hashlib.sha256()
	h_sha256.update(PASSWORD)
	key = h_sha256.digest()

	print("-" * 80)
	print(f"[*] Password: {PASSWORD.decode()}")
	print(f"[*] IV (raw): {IV.hex()}")
	print(f"[*] Key:  	{key.hex()}")
	print("-" * 80)

	try:
    	cipher = AES.new(key, AES.MODE_CBC, IV)
    	decrypted_data = cipher.decrypt(encrypted_data)
    	unpadded_data = unpad(decrypted_data, AES.block_size)
   	 
    	print("\nDecrypted.")
    	flag_content = unpadded_data.decode('utf-8')
    	print(f"\nF: {flag_content}")

	except Exception as e:
    	print(f"\nError: {e}")
$ python3 solver2.py
--------------------------------------------------------------------------------
[*] Password: ThisIsTheEncryptKey
[*] IV (raw): 49005600430061006e004f0062006600
[*] Key:  	adafd798c69ffaef2b2bbb44364f0952b988cdd37bb66bb2cb19b5827a8a2465
--------------------------------------------------------------------------------

Decrypted.

FLAG: ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

flag

ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

今回、2回目のCTF参加で初めてReversingに挑戦しましたが、非常に面白かったです。バイナリを読んでいるときは頭がおかしくなりそうでしたが、Rev問は6問中5問解くことができ、大きな達成感がありました!(最後の問題でしょうもないミスをして時間を溶かしてしまったのは内緒です…)

Cryptoなど他の分野にも挑戦したかったのですが、こちらはさっぱりでした。もっと幅広い知識を身につけていきたいです。

また、今回は「セキュリティ・キャンプ全国大会 2025」で出会った仲間とチームを組むことができ、非常に有意義な時間を過ごせました。この貴重な経験と繋がりを、今後の学習の糧にしていきたいと思います。

最後までお読みいただきありがとうございました。



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -