
CVE-2017-8570 Exp и анализ образца эксплуатации
Причина: при открытии документа Office, FLTLDR.EXE используется для отображения встроенного EPS-файла, содержащего уязвимость. Этот файл написан на языке PostScript и может быть использован атакующим с помощью операции "save-restore"; по сути, это уязвимость типа Use-After-Free (UAF). Уязвимость может быть использована, когда пользователь открывает файл, содержащий некорректно сформированное графическое изображение, или когда пользователь вставляет такое изображение в файл Office.
Затрагиваемые версии: Microsoft Office 2010 Service Pack 2, Microsoft Office 2013 Service Pack 1, Microsoft Office 2016
POC: kcufId's Github
Долгое время искал в сети, но не нашёл пакета Office, содержащего EPSIMP32.FLT. К счастью, товарищ kcufId предоставил LoadEps.exe для загрузки EPS-файлов. Спасибо товарищу kcufId.
LoadEps.exe сначала загружает EPSIMP32.FLT:

Затем вызывает ImportGr для начала загрузки EPS-файла:

Здесь нажимаем F7 для входа, и успешно останавливаемся на точке останова, установленной внутри EPSIMP32.FLT.
Прежде чем перейти к основной теме, опишем структуру объекта PostScript.
// PostScript Object
struct PostScript object
{
dword type;
dword attr;
dword value1;
dword value2; // если массив, указывает на userdict, где хранится объект массива
}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 хранит адрес соответствующего элемента этой структуры в списке индексов (т.е. 0x01DB5E94 с изображения 4), поле 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 восстанавливает предыдущее состояние, в результате чего память, выделенная для l63 после оператора /l62 save def, освобождается и становится висящим указателем.

Переменные l95-l99 определяют дальнейший ход процесса, их значение равно 0 (т.е. 32 бита):

Второе срабатывание уязвимости: сначала выделяется память размером 0x27 (фактически будет занято 0x28) для хранения l63:

Затем l62 restore восстанавливает предыдущее состояние, что приводит к освобождению памяти, выделенной для l63, и она становится висящим указателем; далее выполняется l100, и память, ранее занимаемая l63, используется для хранения структуры размером 0x28 строки l102 (т.е. l136) (это объясняет, почему 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: