출처 드림핵 강의
포맷 스트링 버그는 포맷 스트링을 사용하는 모든 함수에서 발생할 수 있다. 리눅스 라이브러리 함수에서는 printf, fprintf, sprintf와 같은 함수들을 사용할 때 유의해야한다.
// 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");
}
}
}
PIE, NX를 포함한 보호 기법이 적용되어 있다. 카나리는 발견되지 않았다.
get_string 함수를 통해 buf에 32 바이트 입력을 받는다. 그리고 buf를 printf 함수의 인자로 직접 사용하는 취약점이 존재한다.
먼저 changeme의 주소는 PIE 보호 기법으로 인해 실행마다 변경된다. 따라서 PIE 베이스 주소를 먼저 구하고, 그 주소를 기준으로 changeme 주소를 계산해야 한다.
그리고나서 get_string으로 changeme의 주소를 스택에 저장한다. printf 함수에서 %n으로 changeme의 값을 조작할 수 있다.
main 함수를 통해 printf 함수 호출 오프셋을 찾는다.

printf 함수 실행 직전에 중단점을 설정하고 실행하자.

rsp를 다음과 같이 출력하면 rsp+0x48 에 0x555555555293가 있다. 해당 값은 fsb_overwrite 바이너리가 매핑된 영역에 포함되는 주소다. 즉 이 주소를 사용하면 PIE 베이스 주소를 구할 수 있다.(vmmap로 확인 가능)
pwndbg> x/32gx $rsp
0x7fffffffe2d0: 0x0000006161616161 0x0000000000000000
0x7fffffffe2e0: 0x0000000000000000 0x0000000000000000
0x7fffffffe2f0: 0x0000000000000000 0x77c8b8abdc839b00
0x7fffffffe300: 0x0000000000000001 0x00007ffff7dabd90
0x7fffffffe310: 0x0000000000000000 0x0000555555555293
0x7fffffffe320: 0x0000000100000000 0x00007fffffffe418
0x7fffffffe330: 0x0000000000000000 0x1e535541fc844cd1
0x7fffffffe340: 0x00007fffffffe418 0x0000555555555293
0x7fffffffe350: 0x0000555555557d90 0x00007ffff7ffd040
0x7fffffffe360: 0xe1acaabe3aa64cd1 0xe1acbaf4860e4cd1
0x7fffffffe370: 0x00007fff00000000 0x0000000000000000
0x7fffffffe380: 0x0000000000000000 0x0000000000000000
0x7fffffffe390: 0x0000000000000000 0x77c8b8abdc839b00
0x7fffffffe3a0: 0x0000000000000000 0x00007ffff7dabe40
0x7fffffffe3b0: 0x00007fffffffe428 0x0000555555557d90
0x7fffffffe3c0: 0x00007ffff7ffe2e0 0x0000000000000000
pwndbg> Quit
pwndbg> Quit
pwndbg> Quit
pwndbg> x/gx $rsp+0x48
0x7fffffffe318: 0x0000555555555293
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/dremahack/fsb_overwrite
0x555555555000 0x555555556000 r-xp 1000 1000 /home/dremahack/fsb_overwrite
0x555555556000 0x555555557000 r--p 1000 2000 /home/dremahack/fsb_overwrite
0x555555557000 0x555555558000 r--p 1000 2000 /home/dremahack/fsb_overwrite
0x555555558000 0x555555559000 rw-p 1000 3000 /home/dremahack/fsb_overwrite
[생략]

RSP+0x48은 15번째 인자이므로, %15$p를 입력해서 출력한 주소 값에서 0x1293을 빼면 PIE 베이스 주소가 된다. 그리고 changeme의 오프셋을 더하면 changeme의 주소를 구할 수 있다.
$ readelf -s fsb_overwrite | grep changeme
40: 000000000000401c 4 OBJECT GLOBAL DEFAULT 26 changeme
즉 RSP+0x48에 저장된 주소와 PIE 베이스 주소간의 오프셋은 다음과 같다.


페이로드 구성에 대한 자세한 내용은 관련 QnA 참고
위에서 PIE 베이스를 구하는 과정에서 rsp+0x48을 참고하는 것은, 해당 스택에 저장된 값(주소)가 바이너리가 매핑된 영역의 주소 내에 있기 때문이다. 스택 내 다른 위치(rsp+0x~~)에 바이너리가 매핑된 영역의 주소가 있다면 그 값을 통해 PIE 베이스를 구할 수 있다.