Chapter 6. BOF(Buffer OverFlow) II
1. BOF(Buffer OverFlow)
- Program 내의 Buffer 영역이 Overflow되어 Program이 종료 직전 반환 주소이 정상적인 값이 아닌 값으로 바뀌어 Program이 비정상적으로 종료되는 현상이다.
- strcpy, scanf, gets 등 문자열 관련 함수가 문자열 길이를 확인하지 않아서 발생한다.
- 문자열 관련 함수가 할당한 Prologue에서 할당한 Buffer보다 더 큰 크기의 입력을 받게 되면 Buffer를 넘어 RET의 값을 다른 주소로 덮어씌우게 되어 현재의 Process가 아닌 Code를 실행하게 된다.
- BOF 현상의 예
· Release Mode Setting
◦ Program : Visual C++ 6.0
◦ Program을 Release File로 만들기 위해 설정을 다음과 같이 변경한다.
※ Release File로 변경하는 이유
› Debug File로 생성 시 Assembly Language로 Debug 명령이 생성되어 Shell Code를 생성하는데 불필요한 Code가 생성되기 때문에 Release File로 생성해야한다.
◦ 변경내용 : Active Configuration
▹ Build → Set Active Configuration
▹ Project Configurations : test - Win32 Release
◦ 변경내용 : Setting
▹ Project →Settings
▹ Optimization : Disable [Debug]
· BOF 예시
◦ Buffer 영역이 10Byte가 필요한 Program으로 실제로 이 Program의 Buffer 영역은 4Byte씩 3개의 Memory 영역이 할당된다.
◦ 마지막 문자가 NULL('\0')로 끝나야하기 때문에 문자는 1Byte 씩 11개를 입력할 수 있다.
◦ 하지만 이 영역이 초과되면 BOF 현상이 발생한다.
◦ BOF 현상 발생 시
▹ 이와 같은 오류 Message가 출력된다.
※ 실제로 Program을 실행 후 12자리까지의 숫자를 입력했을 시 문제가 발생하지 않는 이유
› ASCII Code 값으로 Null은 '00'으로 SFP의 처음 주소 또한 '00'으로 시작하기 때문에 Program에 영향을 주지는 못한다.
▹ Memory 영역
2. BOF Attack : RET
- Buffer Overflow 현상을 이용한 공격으로 RET의 값을 변경해 해당 Program의 Process 영역이 아닌 다른 Memory 영역의 주소를 RET 값으로 설정해 해당 Program의 목적과는 다른 Program을 실행하는 Hacking 방법이다.
- 관리자가 아닌 일반 사용자나 익명사용자(Anonymous)로 실행할 수 있는 Program으로 BOF 공격을 실행해 관리자의 Shell이나 관리자의 권한을 획득할 수 있다.
- 현재도 수많은 취약한 Program에서 발견되고 있으며 여러 가지 공격 방법과 방어 방법이 존재한다.
- RET의 가장 기본적으로 필요한 능력
· Assembly Language 추출
· Assembly Language를 이용한 16진수 Code 추출
· RET Address 조작
- RET 실습
· 실습 순서
◦ 일정 Program을 실행하는 Shell Code 만들기
▹ 해당 Program의 Code를 Assembly Language로 변경
▹ Assembly Code 수정
▹ Assembly Code 실행 확인
▹ Byte Code를 이용한 Program 생성해 Code 확인
※ Shell Code를 만들기 위해서는 Release Mode가 아닌 Debug Mode로 설정해야 한다.
◦ 만들어낸 Shell Code를 이용해 취약한 Program을 공격하기 위한 Exploit 만들기
◦ Exploit을 이용한 Program 공격
· 일정 Program을 실행하는 Shell Code 만들기
◦ 명령 Prompt(Command-Line : cmd)를 실행하는 Shell code 만들기
▹ Code
▹ 해당 Program의 Code를 Assembly Language로 변경
▸ 'F9' 단축 Button으로 'Break Point'를 설정 후 'F5' 단축 Button으로 'Debug'를 시작한다.
▸ Debug Mode에서 Menu의 'Go To Disassembly'를 선택한다.
▸ 'Go To Disassembly'를 선택한 화면이며 작성한 Program에 맞는 Code를 모두 복사한다.
▸ 각 Line 별로 Assembly Language가 표시되며 Main 함수의 시작과 끝 부분에는 각각 Prologue, Epilogue의 Assembly Code가 자동으로 입력된다.
▹ Assembly Code 수정
▸ 파랑색 : 남겨둘 부분
▸ 보라색 : Debug Code이기 때문에 삭제할 부분
▸ 주황색 : 수정이 필요한 부분
▫ WinExec 함수가 포함된 Library File의 Memory 주소 삽입
▫ ExitProcess 함수가 포함된 Library File의 Memory 주소 삽입
▫ 주소 계산 방법
▪ WinExec
▭ WinExec는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'WinExec'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ WinExec 함수의 Memory 주소 : 0x7C800000 + 0x000623AD = 0x7C8623AD
▪ ExitProcess
▭ ExitProcess는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'ExitProcess'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ ExitProcess 함수의 Memory 주소 : 0x7C800000 + 0x0001CAFA = 0x7C81CAFA
▸ 빨강색 : 수정이 필요한 부분
▫ Program에 Shell Code를 넣어 실행하는 Program의 경우 Shell Code에 NULL 문자(0)가 포함되어 있을 경우 문제가 발생한다.
▫ 문자열은 시작주소부터 NULL 문자까지 인식하기 때문에 'push 0'의 경우 Byte Code로 나타냈을 때 '0x00' 즉, NULL 문자로 인식해 Shell Code가 동작하는 도중 종료하게 되기 때문에 'push 0'의 0을 1로 수정해야한다.
▸ 주황색 영역을 위와 같이 함수의 Memory 주소 값을 대입한 명령으로 수정
▫ MOV 명령을 이용해 EAX에 'WinExec'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▫ MOV 명령을 이용해 EAX에 'ExitProcess'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▹ Assembly Code 실행 확인
▸ '__asm' 함수를 이용해 이전에 만들어낸 Assembly Code를 실행시켜 본다.
▹ Byte Code를 이용한 Program 생성해 Code 확인
▸ Debug Mode에서 'Go To Disassembly' 선택 후 'Code Byte'를 선택해 Byte Code를 표시한다.
▸ Assembly Code로 입력된 부본의 Byte Code를 모두 복사한다.
▸ Byte Code를 제외한 나머지 Code 모두를 지운다.
▸ 각 16진수 Byte Code의 앞부분에 '\x'를 삽입하고 각각의 줄의 처음과 끝부분에 큰따옴표(")를 삽입한다.
▸ 만들어낸 Byte Code를 위 Program의 문자열에 삽입해 Byte Code를 잘 만들어냈는지 확인해 본다.
◦ 명령 Prompt(Command-Line : cmd)를 실행하는 Shell code 만들기
▹ Code
▹ 해당 Program의 Code를 Assembly Language로 변경
▸ 'F9' 단축 Button으로 'Break Point'를 설정 후 'F5' 단축 Button으로 'Debug'를 시작한다.
▸ Debug Mode에서 Menu의 'Go To Disassembly'를 선택한다.
▸ 'Go To Disassembly'를 선택한 화면이며 작성한 Program에 맞는 Code를 모두 복사한다.
▸ 각 Line 별로 Assembly Language가 표시되며 Main 함수의 시작과 끝 부분에는 각각 Prologue, Epilogue의 Assembly Code가 자동으로 입력된다.
▹ Assembly Code 수정
▸ 파랑색 : 남겨둘 부분
▸ 보라색 : Debug Code이기 때문에 삭제할 부분
▸ 주황색 : 수정이 필요한 부분
▫ WinExec 함수가 포함된 Library File의 Memory 주소 삽입
▫ ExitProcess 함수가 포함된 Library File의 Memory 주소 삽입
▫ 주소 계산 방법
▪ WinExec
▭ WinExec는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'WinExec'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ WinExec 함수의 Memory 주소 : 0x7C800000 + 0x000623AD = 0x7C8623AD
▪ ExitProcess
▭ ExitProcess는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'ExitProcess'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ ExitProcess 함수의 Memory 주소 : 0x7C800000 + 0x0001CAFA = 0x7C81CAFA
▸ 빨강색 : 수정이 필요한 부분
▫ Program에 Shell Code를 넣어 실행하는 Program의 경우 Shell Code에 NULL 문자(0)가 포함되어 있을 경우 문제가 발생한다.
▫ 문자열은 시작주소부터 NULL 문자까지 인식하기 때문에 'push 0'의 경우 Byte Code로 나타냈을 때 '0x00' 즉, NULL 문자로 인식해 Shell Code가 동작하는 도중 종료하게 되기 때문에 'push 0'의 0을 1로 수정해야한다.
▸ 주황색 영역을 위와 같이 함수의 Memory 주소 값을 대입한 명령으로 수정
▫ MOV 명령을 이용해 EAX에 'WinExec'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▫ MOV 명령을 이용해 EAX에 'ExitProcess'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▹ Assembly Code 실행 확인
▸ '__asm' 함수를 이용해 이전에 만들어낸 Assembly Code를 실행시켜 본다.
▹ Byte Code를 이용한 Program 생성해 Code 확인
▸ Debug Mode에서 'Go To Disassembly' 선택 후 'Code Byte'를 선택해 Byte Code를 표시한다.
▸ Assembly Code로 입력된 부본의 Byte Code를 모두 복사한다.
▸ Byte Code를 제외한 나머지 Code 모두를 지운다.
▸ 각 16진수 Byte Code의 앞부분에 '\x'를 삽입하고 각각의 줄의 처음과 끝부분에 큰따옴표(")를 삽입한다.
▸ 만들어낸 Byte Code를 위 Program의 문자열에 삽입해 Byte Code를 잘 만들어냈는지 확인해 본다.
◦ MessageBox를 실행하는 Shell code 만들기
▹ Code
▹ 해당 Program의 Code를 Assembly Language로 변경
▸ 'F9' 단축 Button으로 'Break Point'를 설정 후 'F5' 단축 Button으로 'Debug'를 시작한다.
▸ Debug Mode에서 Menu의 'Go To Disassembly'를 선택한다.
▸ 'Go To Disassembly'를 선택한 화면이며 작성한 Program에 맞는 Code를 모두 복사한다.
▸ 각 Line 별로 Assembly Language가 표시되며 Main 함수의 시작과 끝 부분에는 각각 Prologue, Epilogue의 Assembly Code가 자동으로 입력된다.
▹ Assembly Code 수정
▸ 파랑색 : 남겨둘 부분
▸ 보라색 : Debug Code이기 때문에 삭제할 부분
▸ 주황색 : 수정이 필요한 부분
▫ LoadLibraryA 함수가 포함된 Library File의 Memory 주소 삽입
▫ MessageBoxA 함수가 포함된 Library File의 Memory 주소 삽입
▫ ExitProcess 함수가 포함된 Library File의 Memory 주소 삽입
▫ 주소 계산 방법
▪ LoadLibrary
▭ 여기서 LoadLibrary 함수는 'LoadLibraryA' 함수를 의미한다.
▭ LoadLibraryA는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'LoadLibraryA'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ LoadLibraryA 함수의 Memory 주소 : 0x7C800000 + 0x00001D7B = 0x7C801D7B
▪ MessageBox
▭ 여기서 MessageBox 함수는 'MessageBoxA' 함수를 의미한다.
▭ MessageBoxA는 'USER32.DLL' File에 포함되어 있다.
▭ 따라서 'USER32.DLL File'의 Memory 주소인 'Preffered Base'의 주소와 'MessageBoxA'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ MessageBoxA 함수의 Memory 주소 : 0x77CF0000 + 0x00001D7B = 0x77CF1D7B
▪ ExitProcess
▭ ExitProcess는 'KERNEL32.DLL' File에 포함되어 있다.
▭ 따라서 'KERNUL32.DLL' File의 Memory 주소인 'Preffered Base'의 주소와 'ExitProcess'의 Memory 주소인 'Entry Pointer'의 주소를 더해주면 된다.
▭ ExitProcess 함수의 Memory 주소 : 0x7C800000 + 0x0001CAFA = 0x7C81CAFA
▸ 빨강색 : 수정이 필요한 부분
▫ Program에 Shell Code를 넣어 실행하는 Program의 경우 Shell Code에 NULL 문자(0)가 포함되어 있을 경우 문제가 발생한다.
▫ 문자열은 시작주소부터 NULL 문자까지 인식하기 때문에 'push 0'의 경우 Byte Code로 나타냈을 때 '0x00' 즉, NULL 문자로 인식해 Shell Code가 동작하는 도중 종료하게 되기 때문에 'push 0'의 0을 1로 수정해야 하지만 이 Exploit의 경우 함수 내의 문자열을 구분해줘야 하기 때문에 바꿔주지 않는다.
▸ 주황색 영역을 위와 같이 함수의 Memory 주소 값을 대입한 명령으로 수정
▫ MOV 명령을 이용해 EAX에 'LoadLibraryA'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▫ MOV 명령을 이용해 EAX에 'MessageBoxA'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▫ MOV 명령을 이용해 EAX에 'ExitProcess'의 Memory 주소 복사 후 CALL 명령어로 해당 주소 호출
▹ Assembly Code 실행 확인
▸ '__asm' 함수를 이용해 이전에 만들어낸 Assembly Code를 실행시켜 본다.
▹ Byte Code를 이용한 Program 생성해 Code 확인
▸ Debug Mode에서 'Go To Disassembly' 선택 후 'Code Byte'를 선택해 Byte Code를 표시한다.
▸ Assembly Code로 입력된 부본의 Byte Code를 모두 복사한다.
▸ Byte Code를 제외한 나머지 Code 모두를 지운다.
▸ 각 16진수 Byte Code의 앞부분에 '\x'를 삽입하고 각각의 줄의 처음과 끝부분에 큰따옴표(")를 삽입한다.
▸ 만들어낸 Byte Code를 위 Program의 문자열에 삽입해 Byte Code를 잘 만들어냈는지 확인해 본다.
· 만들어낸 Byte Code를 이용해 취약한 Program을 공격하기 위한 Exploit 만들기
◦ Unicode를 이용하는 방법
▹ Code
▹ 문제점 : Unicode Problem
▸ 앞서 만들었던 'cmd'의 Code는 Program 내부적으로 처리될 경우에는 문제가 발생하지 않지만 다른 Program의 Argument로 사용될 경우 Windows OS가 인자로 입력된 문자열을 Unicode로 인식하게 된다.
▸ Unicode 2Byte에서 1Byte의 1Bit와 뒤에 1Byte의 1Bit를 XOR한 값이 0이 아닐 경우 입력된 값을 '3F(쓰레기 값)'로 변경되는 경우가 대부분이다.
▸ 따라서 기존에 만들었던 Assembly Code가 아닌 Unicode에 맞게 만들어줘야 한다.
▹ Unicode 적용 Code
▸ 위 Code를 이용해 Byte Code로 변환 후 문자열처리('\x', 큰따옴표)를 해준다.
▹ Unicode 적용 Code의 Byte
▸ 기존에 만든 'Vuln' Code의 Shellcode 영역 Unicode를 적용해 생성한 Byte Code로 수정한다.
▹ Shellcode 영역을 Unicode로 적용시킨 Code로 수정한 Code
▹ 생성한 Program으로 취약한 Program 공격
▸ 기존에 만들었던 'Exploit_cmd_uni.exe'와 'Vuln.exe'를 같은 위치로 복사한다.
▸ 'Vuln.exe 100' 명령어를 이용하면 다시 한 번 Command-line이 실행되는 것을 확인할 수 있다.
◦ 'NOP'를 이용하는 방법
▹ 공격할 Program의 Buffer가 작아 ShellCode를 Buffer에 넣을 수 없는 경우에 사용하는 방법으로'\x90'(NOP : No Operate)를 이용하는 방법이다.
▹ 취약 Program이 Buffer가 작은 경우가 대부분이며 Argument를 통해 값을 입력 받는다면 RET를 Argument의 주소에 있는 ShellCode를 Pointing(JMP ESP)해 ShellCode를 실행할 수 있다.
▹ 실제 공격할 때 공격자가 Buffer가 할당된 Memory를 이용하려면 추측을 통해 찾아낼 수밖에 없기 때문에 '\x90'(NOP) Code를 넣어 성공 가능성을 높인다.
▹ '\x90'(NOP) Code 어딘가로 RET가 위치하게 되면 '\x90'(NOP)을 계속 따라가다가 작동시킬 ShellCode를 만나게 된다.
▹ 실행 순서
▸ Memory 상에서 'JMP ESP'(\xFF\xE4)가 있는 주소를 찾기
▸ RET에 'JMP ESP' 주소 넣기
▸ 해당 Program의 바로 밑에 ShellCode를 구성
▸ 실행 시 RETN 명령어가 실행된 후 JMP ESP 명령이 실행
▸ ShellCode가 실행된다.
※ 'JMP ESP'를 찾는 이유
› 'JMP ESP'는 현재 지점의 Code로 Pointing 하는 Code로 실행하면 현재 지점부터 Code를 실행한다.
› 즉 'JMP ESP'를 사용하면 'JMP ESP'이후에 있는 Code를 모두 실행한다.
▹ 사용 Program
▸ Visual C++ 6.0
▸ OnlyDbg 1.1
▸ HxD
▹ Code
▸ Build 시 Release Program으로 생성한다.
▹ Memory 상에서 'JMP ESP'(\xFF\xE4)가 있는 주소를 찾기
▸ 해당 Program을 OnlyDbg로 실행 후 'Memory map'에서 'Search'를 선택한다.
▸ 찾을 내용인 'FF E4'를 입력 후 검색을 시작한다.
▸ 실제 Program의 Code가 저장되는 영역인 Code 영역에 존재하는 'JMP ESP'의 주소를 찾아야 하기 때문에 Library File내의 'JMP ESP'를 찾아야 한다.
▸ 찾아보면 3개의 'JMP ESP'를 찾을 수 있는데 Stack 영역에서 2개(002F5028, 00355028), Code 영역에서 1개(7C86467B)가 찾아지며 실제로 사용할 주소는 '7C86467B'가 된다.
▸ Stack 영역의 'JMP ESP' 또한 사용할 수 있지만 BOF 공격을 하면 해당 Stack 영역의 'JMP ESP'는 다른 내용으로 덮어 씌워질 것이기 때문에 사용하지 않는다.
▹ 실행 Code 작성 : HxD
▸ Buffer의 크기만큼 문자를 채우고 'JMP ESP'의 주소를 삽입하고 바로 뒤에 실행하고자하는 Code를 넣어준 후 저장한다.
▸ Buffer : Buffer 영역을 모두 채울 문자
▸ SFP : Saved Frame Pointer에 채울 문자
▸ RET : Return 함수에 채울 'JMP ESP'의 주소
▸ Shell Code : 앞서 Unicode에 맞게 생성했던 Command-Line 실행 Byte Code 삽입
▹ 취약 Program 공격
▸ 앞서 생성한 취약 Program(gets.exe)의 인자를 통해 'Shellcode.txt'를 입력 Buffer에 넣어 주면 공격 Code에 의해 Command-Line이 실행되는 것을 확인할 수 있다.
▸ 사용 명령어 : gets.exe > shellcode.txt
'System Security > Theory' 카테고리의 다른 글
Chapter 9. 취약점 분석 (0) | 2015.08.18 |
---|---|
Chapter 7. BOF(Buffer OverFlow) III (0) | 2015.08.18 |
Chapter 5. BOF(Buffer OverFlow) I (0) | 2015.08.18 |
Chapter 4. Malware II (0) | 2015.08.18 |
Chapter 3. Malware I (0) | 2015.08.18 |