[포너블] Exploit Tech: Format String Bug

Chris Kim·2024년 10월 23일

시스템해킹

목록 보기
20/33

출처 드림핵 강의

0. 서론

포맷 스트링 버그는 포맷 스트링을 사용하는 모든 함수에서 발생할 수 있다. 리눅스 라이브러리 함수에서는 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");
    }
  }
}

1. 분석

1.1 보호기법

PIE, NX를 포함한 보호 기법이 적용되어 있다. 카나리는 발견되지 않았다.

1.2 코드 분석

get_string 함수를 통해 buf에 32 바이트 입력을 받는다. 그리고 bufprintf 함수의 인자로 직접 사용하는 취약점이 존재한다.

1.3 익스플로잇 설계

먼저 changeme의 주소는 PIE 보호 기법으로 인해 실행마다 변경된다. 따라서 PIE 베이스 주소를 먼저 구하고, 그 주소를 기준으로 changeme 주소를 계산해야 한다.
그리고나서 get_string으로 changeme의 주소를 스택에 저장한다. printf 함수에서 %n으로 changeme의 값을 조작할 수 있다.

2. 익스플로잇

2.1 changeme 주소 구하기

main 함수를 통해 printf 함수 호출 오프셋을 찾는다.

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

rsp를 다음과 같이 출력하면 rsp+0x480x555555555293가 있다. 해당 값은 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 베이스 주소간의 오프셋은 다음과 같다.

2.2 changeme 덮어쓰기


페이로드 구성에 대한 자세한 내용은 관련 QnA 참고

3. 정리

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

profile
회계+IT=???

0개의 댓글