본문 바로가기

System Security/Theory

Chapter 6. BOF(Buffer OverFlow) II

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