
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)에 구성된 데이터를 씁니다: