Microsoft Windows SMB Protocol Denial of Service Vulnerability (CVE-2017-11781)

본 포스팅은 지난번에 발견했던 SMB 취약점에 대해 분석한 글입니다. SMB 서비스가 실행되고 있을때 어떠한 인증 없이도 원격으로 Microsoft Windows 를 Crash 낼 수 있는 취약점입니다. 이에 대한 김치콘 2017 발표자료와 POC 코드는 아래에 있습니다.

SMB 김치콘 2017 발표 자료

CVE-2017-11781 POC github

# 개요

SMB는 파일과 디렉터리 및 주변 장치들을 공유하는 데 사용되는 메시지 형식으로 윈도우 OS에 기본적으로 탑재되어 있다. 해당 취약점은 어떠한 인증 없이 원격으로 서버에 패킷을 보내 서버를 강제 종료시킬 수 있다. Windows SMB Denial of Service Vulnerability 라는 이름으로 정보 보안 취약점 표준 코드(Common Vulnerabilities and Exposures) 번호인 CVE-2017-11781이 부여되었다.

# 환경

취약한 버전은 다음과 같다.

# 정적 분석

정적 분석은 IDA를 사용해 진행했다. SMB는 파일을 공유하기 위해 다양한 Command와 Data type을 사용한다.

캡처555

SMB 드라이버 중 하나인 srv.sys에서 문제가 발생한다. SMB의 Command 중 하나인 FindFirst2는 서버와 클라이언트 사이에서 파일 정보를 리스트로 공유하기 위해 첫 번째 파일을 찾는 역할을 담당한다. 이 Command는 srv.sys 내부의 SrvFind2Loop 함수에서 수행된다. 파일 정보들을 공유하기 위해서 SMB_GEA_LIST라는 이름의 데이터 타입을 사용한다. 서버 측에서 GeaList를 Windwos에서 사용하는 NT 타입으로 변환하는 과정에서 멤버 변수인 SizeOfListInBytes의 타입을 잘못 변환한다. SizeOfListInBytes 변수가 ULONG으로 크기가 DWORD인데 반해 이를 WORD로 강제 캐스팅되는 부분이 존재한다. 즉, 4바이트인 변수를 2바이트로 잘라서 계산하기 때문에 문제가 된다.

image

잘못된 캐스팅을 한 상태에서 SrvOs2GeaListToNt를 호출한다. 이 때 SrvOs2GeaListToNt 함수의 첫 번째 인자는 GeaList의 포인터이다. SrvOs2GeaListToNt 함수를 호출하기 전 GeaList의 SizeOfListInBytes의 사이즈를 검사하는 루틴이 있다.

image

GeaList의 크기가 검증하는 부분이 존재하기 때문에 정상적인 흐름이라면 비정상적인 크기를 보냈을 때 프로그램이 예외처리와 함께 잘 종료되어야 한다. “*(WORD *) v49>v48” 코드에서 SizeOfListInBytes가 너무 클 경우 함수를 종료하게 된다. 그러나 문제는 이 루틴이 이미 word로 잘못 캐스팅된 후이기 때문에 무의미한 검사가 된다것에 있다. SizeOfListInBytes를 0x10007로 조작해서 보낼 경우 0x0007만 잘라서 검사하기 때문에 SrvOs2GeaListToNt에 진입이 가능하다. SrvOs2GeaListToNt 내부에서는 SrvOs2GeaListSizeToNt를 호출한다. 해당 함수는 Nt format으로 변환하기 위해 Nonpaged pool의 size를 계산하는 함수이다.

SrvOs2GeaListSizeToNt는 반복문을 돌면서 NT format으로 변환하기 위해 Nonpaged pool을 할당받을 크기를 계산한다. 먼저 GeaList의 시작 부분 포인터에 SizeOfListInBytes를 더하여 GeaList의 끝을 가리키는 포인터를 만든다. 그리고 순환 포인터를 하나 두고 끝을 가리키는 포인터보다 작거나 같을 때까지 gea를 순환한다.

캡처2


NT format 헤더의 크기(12)와 GEA의 value값의 크기를 더하면서 다음 GEA로 포인터를 계속 옮긴다. 그러나 SizeOfListInBytes가 비정상적으로 조절되어 있으면 while문을 비정상적으로 크게 반복하여 존재하지 않는 메모리에 접근하려다 메모리 커럽션이 발생한다. 드라이버에서 발생한 취약점이기 때문에 블루스크린이 발생하면서 윈도우 서버가 종료된다.

image

# 동적 분석

동적 분석은 Windbg를 통해 진행했다. 브레이크 포인터를 걸기 위해 “bu srv!srvFind2Loop+0xD65A” 명령어를 실행 후에 SMB 패킷을 재전송하였다. 그러면 브레이크가 걸리고 레지스터 RCX를 확인하면 0x10007을 볼 수 있다. 이것은 임의의 패킷을 생성할 때 GeaList의 SizeOfListInBytes를 설정한 값이다.

1: kd> g
Breakpoint 1 hit  
srv! ?? ::NNGAKEGL::`string'+0x58e2:  
fffff880`0534023a 0fb701          movzx   eax,word ptr [rcx]  
0: kd> dd @rcx  
fffff8a0`1f053124  00010007 00000000 00000000 00000000  
fffff8a0`1f053134  00000000 00000000 00000000 00000000  
fffff8a0`1f053144  00000000 00000000 00000000 00000000  
fffff8a0`1f053154  00000000 00000000 00000000 00000000  
fffff8a0`1f053164  00000000 00000000 00000000 00000000  
fffff8a0`1f053174  00000000 00000000 00000000 00000000  
fffff8a0`1f053184  00000000 00000000 00000000 00000000  
fffff8a0`1f053194  00000000 00000000 00000000 00000000  

한줄을 실행 후 레지스터를 확인하면 WORD로 잘못된 캐스팅이 되어 0x7로 변경된 것을 알 수 있다.

0: kd> p
0: kd> r
rax=0000000000000007 rbx=fffff8a01eebc060 rcx=fffff8a01f053124
rdx=0000000000000204 rsi=fffff8a01f053010 rdi=0000000000000003
rip=fffff8800534023d rsp=fffff880051bb880 rbp=fffff8a01f053000
 r8=0000000000000000  r9=0000000000000000 r10=fffffa8032db4010
r11=0000000000000000 r12=fffff8a01f050200 r13=0000000000000000
r14=fffff8a01f05332a r15=0000000000000000

정상적으로 프로그램이 이 데이터를 처리하려면 DWORD로 인식되어 0x10007의 MAX 값보다 크다고 판단되어 함수가 종료되어야 한다. 그러나 WORD로 변경되기 때문에 cmp ax,6 및 cmp eax,edx를 우회하여 SrvOs2GeaListToNt로 진입이 가능하다.

srv! ?? ::NNGAKEGL::`string'+0x58e5:
fffff880`0534023d 6683f806        cmp     ax,6
fffff880`05340241 0f829b000000    jb      srv! ?? ::NNGAKEGL::`string'+0x599a (fffff880`053402e2)
fffff880`05340247 0fb7c0          movzx   eax,ax
fffff880`0534024a 3bc2            cmp     eax,edx
fffff880`0534024c 0f8790000000    ja      srv! ?? ::NNGAKEGL::`string'+0x599a (fffff880`053402e2)
fffff880`05340252 4c8d8c24a8000000 lea     r9,[rsp+0A8h]
fffff880`0534025a 4c8d842420010000 lea     r8,[rsp+120h]
fffff880`05340262 488d942418010000 lea     rdx,[rsp+118h]
fffff880`0534026a e831cb0000      call    srv!SrvOs2GeaListToNt (fffff880`0534cda0)

디버깅을 진행하기 위해 “bu srv!SrvOs2GeaListSizeToNt+0xa” 명령어를 실행했다. GEAList의 크기인 0x10007의 값에 GEAList의 시작포인터를 구하여 GEAList의 End Point를 구한다.

0: kd> r
rax=0000000000000007 rbx=fffff8a01f053124 rcx=fffff8a01f053124
rdx=fffff8a01f053128 rsi=fffff880051bb9a0 rdi=0000000000000000
rip=fffff8800534ab4a rsp=fffff880051bb828 rbp=fffff880051bb998
 r8=0000000000010007  r9=0000000000000000 r10=fffffa8032db4010
r11=0000000000000000 r12=fffff8a01f050200 r13=fffff880051bb928
r14=fffff8a01f05332a r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
srv!SrvOs2GeaListSizeToNt+0xa:
fffff880`0534ab4a 4c03c1          add     r8,rcx   # ->  end point of GeaList
0: kd> p
0: kd> r
rax=0000000000000007 rbx=fffff8a01f053124 rcx=fffff8a01f053124
rdx=fffff8a01f053128 rsi=fffff880051bb9a0 rdi=0000000000000000
rip=fffff8800534ab4d rsp=fffff880051bb828 rbp=fffff880051bb998
 r8=fffff8a01f06312b  r9=0000000000000000 r10=fffffa8032db4010 #<-- r8
r11=0000000000000000 r12=fffff8a01f050200 r13=fffff880051bb928
r14=fffff8a01f05332a r15=0000000000000000
iopl=0         nv up ei ng nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
srv!SrvOs2GeaListSizeToNt+0xd:
fffff880`0534ab4d eb24            jmp     srv!SrvOs2GeaListSizeToNt+0x33 (fffff880`0534ab73)

그리고 End Point까지 반복문을 돌면서 GEA를 순회하여 NT Size를 구하게 되는데 비정상적인 0x10007 값이 더해졌으므로 접근 권한이 없는 메모리까지 접근하여 커널 패닉이 발생한다.

TRAP_FRAME:  fffff880051bb690 -- (.trap 0xfffff880051bb690)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff8a020981003 rbx=0000000000000000 rcx=fffff8a02097b124
rdx=fffff8a020981001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8800534ab58 rsp=fffff880051bb828 rbp=fffff880051bb998
 r8=fffff8a02098b12b  r9=00000000000172ec r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz ac po cy
srv!SrvOs2GeaListSizeToNt+0x18:
fffff880`0534ab58 440fb612        movzx   r10d,byte ptr [rdx] ds:1020:1001=??

안타깝게도 exploitable 하지는 않은 버그였다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중