
CVE-2017-8570 Exp 및 악용 샘플 분석
원인: Office 문서를 열 때 FLTLDR.EXE가 해당 취약점을 포함하는 내장 EPS 파일을 렌더링하는 데 사용됩니다. 이 파일은 PostScript 언어로 작성되었으며, 공격자가 "save-restore" 작업을 통해 악용할 수 있습니다. 본질적으로 UAF(Use-After-Free) 취약점입니다. 사용자가 잘못된 형식의 그래픽 이미지가 포함된 파일을 열거나, 잘못된 형식의 그래픽 이미지를 Office 파일에 삽입할 때 이 취약점이 악용될 수 있습니다.
영향받는 버전: Microsoft Office 2010 Service Pack 2, Microsoft Office 2013 Service Pack 1, Microsoft Office 2016
POC: kcufId's Github
필자가 온라인에서 오랫동안 찾아보았지만 EPSIMP32.FLT가 포함된 Office 설치 패키지를 찾지 못했습니다. 다행히 kcufId 님이 EPS 파일을 로드하는 LoadEps.exe를 제공해 주셨습니다. kcufId 님께 감사드립니다.
LoadEps.exe는 먼저 EPSIMP32.FLT를 로드합니다:

그런 다음 ImportGr를 호출하여 EPS 파일 로드를 시작합니다:

여기서 바로 F7로 따라 들어가면 EPSIMP32.FLT 내에 설정한 중단점에 성공적으로 걸릴 수 있습니다.
본론에 들어가기 전에 PostScript 객체 구조에 대해 간단히 설명하겠습니다.
// PostScript Object
struct PostScript object
{
dword type;
dword attr;
dword value1;
dword value2; // if array, point to userdict where store the array object
}ps_obj;
여기서 서로 다른 type에 해당하는 값은 다음과 같습니다:
0x0 nulltype
0x3 integertype
0x5 realtype
0x8 booleantype
0x10 operatortype
0x20 marktype
0x40 savetype
0x300 nametype
0x500 stringtype
0x900 filetype
0x30000 arraytype
0x0B0000 packedarraytype
0x70000 packedarraytype
0x110000 dicttype
0x210000 gstatetype
문자열을 예로 들어 그 저장 구조를 설명합니다. forall 함수에 중단점을 설정하면 문자열을 어떻게 처리하는지 자세히 확인할 수 있습니다 (forall 함수를 찾는 방법은 https://paper.seebug.org/368/ 을 참조하세요).

그림 1은 ps_obj에 해당하며, value2 항목은 인덱스 목록의 해당 항목(그림 2)을 가리킵니다. 인덱스 항목은 크기 0x30의 구조를 가리키고, 이 구조의 0x24 위치에는 크기 0x28의 구조(그림 5)를 가리키는 포인터의 포인터가 저장되며, 0x2C 위치에는 문자열 크기(그림 3)가 저장됩니다. 그림 5에서 구조의 0x4 위치에는 인덱스 목록에서 이 구조에 해당하는 항목의 주소(즉, 그림 4의 0x01DB5E94)가 저장되고, 0x20 위치는 문자열의 최종 저장 위치(그림 6)를 가리키며, 0x24 위치는 실제 점유 메모리 크기 — 문자열 크기 + 1입니다.
크기 0x30 구조:
+0x0 dword
+0x4 dword
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword
+0x24 dword pp_struct //指向大小为0x28结构的指针的指针
+0x28 dword
+0x2c dword size //字符串实际大小
크기 0x28 구조 (배열일 경우 이 구조의 크기는 0x2C이며, 0x28 위치는 배열 요소를 가리키고, 각 요소는 ps_obj입니다):
+0x0 dword
+0x4 dword //存储该结构于索引列表中对应项的地址
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword ptr_object //指向字符串最终存储位置
+0x24 dword size //实际所占内存大小,字符串实际大小+1
취약점 첫 번째 트리거:

먼저 VM 상태를 l62 변수에 저장합니다. 그런 다음 l63 변수의 각 문자에 대해 l61——>>l59——>>l56 처리 과정을 호출합니다. l62 restore는 이전 상태를 복원하므로 /l62 save def 문 뒤에서 l63이 할당한 메모리 공간이 해제되어 댕글링 포인터가 됩니다.

l95-l99 변수는 이후 흐름을 결정하며, 그 값은 모두 0입니다(즉, 32비트):

취약점 두 번째 트리거: 먼저 0x27 크기(실제로는 0x28을 차지)의 메모리 공간을 할당하여 l63을 저장합니다:

그런 다음 l62 restore가 이전 상태를 복원하여 l63이 할당한 메모리 공간이 해제되고 댕글링 포인터가 됩니다. 이어서 l100을 실행하면, 이전에 l63이 차지하던 메모리 공간이 l102(즉 l136) 문자열의 0x28 구조를 저장하는 데 사용됩니다 (이것은 l63이 왜 0x27 크기의 메모리 공간을 할당했는지 설명합니다):

이 구조의 0x4, 0x20, 0x24 위치의 값을 각각 가져옵니다:

마지막으로 l136 문자열의 내용을 수정합니다(그림에는 일부 수정된 부분만 표시됨):

이 수정 내용은 정교하게 구성된 것으로, 세 번째 취약점 트리거 시 사용됩니다.
취약점 세 번째 트리거: 0x37개의 요소를 포함하는 배열을 할당한 후, 루프가 0x34번째 요소에 도달했을 때 l62 restore를 실행합니다:

restore 실행이 완료된 후, 배열의 0x30 구조가 l193 문자열 내용으로 덮어써집니다:

이로써 마지막(0x36) forall 과정에서 실행되는 객체가 위 그림의 0x30 구조가 되며, 그 0x36번째 요소를 가져오면 두 번째 취약점 트리거 시 정교하게 구성된 문자열에 도달하게 됩니다:

그리고 가져온 배열 요소는 크기가 4인 배열이며, 이 배열의 첫 번째 요소는 시작 주소가 0이고 크기가 0x7FFFFFFF인 문자열입니다:

이 배열은 l159 변수에 저장되며, 그 첫 번째 요소 — 시작 주소가 0이고 크기가 0x7FFFFFFF인 문자열 — 는 l201 변수에 저장됩니다. 이후 l201 변수를 통해 임의 주소의 값을 읽을 수 있습니다.
kernel32.dll 베이스 주소 구하기:


이로써 l314 변수에는 EPSIMP32.FLT 베이스 주소가 저장됩니다.

참고: search 명령 구문은 다음과 같습니다:

지정된 gadget 찾기:


file 유형 구조 구성:
l199 l201 get_dword
/l487 exch def
l487 l201 get_dword
/l488 exch def
l488 36 my_add l201 get_dword
/l489 exch def
l489 l201 get_dword
/l490 exch def
l490 32 my_add l201 get_dword
/l491 exch def
l199 l491 l201 put_data_to_array
l199 12 my_sub 2304 l201 put_data_to_array

l492 주소(l491+0x32)에 구성된 데이터를 씁니다:
l492 0 l201 put_data_to_array %% 0x00 0
l492 4 my_add l375 l201 put_data_to_array %% 0x04 Address of <5E C3>
l492 8 my_add l373 l201 put_data_to_array %% 0x08 Address of <94 00 00 00 00 5E C3>
l492 12 my_add l377 l201 put_data_to_array %% 0x0C Address of <C2 0C 00>
l492 16 my_add l370 l201 put_data_to_array %% 0x10 Address of VirtualProtect()
l492 20 my_add 0 l201 put_data_to_array %% 0x14 0
l492 24 my_add 0 l201 put_data_to_array %% 0x18 0
l492 28 my_add 0 l201 put_data_to_array %% 0x1C 0
l492 32 my_add l368 l201 put_data_to_array %% 0x20 Address of Shellcode
l492 36 my_add l368 l201 put_data_to_array %% 0x24 Address of Shellcode——lpAddress
l492 40 my_add l349 l201 put_data_to_array %% 0x28 Size of Shellcode——dwSize
l492 44 my_add 64 l201 put_data_to_array %% 0x2C PAGE_EXECUTE_READWRITE——flNewProtect
l492 48 my_add l493 l201 put_data_to_array %% 0x30 lpflOldProtect
마지막으로 closefile 명령을 실행할 때 Shellcode로 점프합니다:

EPS 악용 스크립트는 \word\media 디렉터리에 있으며, 압축을 풀면 바로 확인할 수 있습니다. 이 취약점 악용 샘플은 Shellcode 부분을 제외하면 기본적으로 동일하므로, Patchword 조직의 특정 샘플을 예로 들어 분석합니다.
파일 이름: Cyber_Secure_Pakistan.docx
MD5: DD89BBB916A2C909630EC78CBB0E13E5
Shellcode로 점프하여 스택을 복원합니다:

메모리 할당:

함수 호출 주소 가져오기:

디버깅 과정에서 환경 문제로 인해 CreateToolhelp32Snapshot 함수 호출 주소를 얻지 못했습니다:

주소를 수동으로 입력하고 Word를 열어 분석을 계속합니다. 프로세스를 열거하여 WINWORD.exe를 찾습니다:

C:\ProgramData\Microsoft\DeviceSync 디렉터리에 MSBuild.exe라는 프로그램을 생성합니다:

파일 내용을 씁니다. 그 내용은 EPS 스크립트의 payload_32 변수에 저장되어 있습니다:


vmtools.dll 파일 생성:

파일 내용을 씁니다. 그 내용은 EPS 스크립트의 payload_32_f2 변수에 저장되어 있습니다:


VMwareCplLauncher.exe 파일 생성:

그 내용은 EPS 스크립트의 payload_32_f1 변수에 저장되어 있습니다:

이 파일은 VMware 서명이 있는 정상(white) 파일입니다:

다음 내용을 explorer.exe에 주입합니다:

그 기능은 VMwareCplLauncher.exe 프로세스를 생성하는 것입니다:

이후 절차는 360의 이 보고서에 언급되어 있으며, 본 문서에서는 해당 분석 부분을 다루지 않습니다:

관심 있는 독자는 해당 보고서를 더 읽어볼 수 있습니다.
참고: 이 취약점의 악용 샘플은 기본적으로 유사하며, 차이점은 마지막 MSBuild.exe 페이로드에 있습니다. 이 페이로드는 EPS 스크립트의 payload_32 변수에 저장되어 있어 바로 덤프할 수 있으며, DOS 파일 헤더를 채운 후 IDA에 넣어 분석할 수 있습니다.