[하루한줄] CVE-2024-39420: Adobe Acrobat Reader의 TOCTOU로 인한 OOB Write 취약점

URL

https://www.talosintelligence.com/vulnerability_reports/TALOS-2024-2011

Target

  • Acrobat Reader
    • 24.001.30123
    • 24.001.30159
    • 24.002.20964
    • 24.002.20965
    • 24.002.21005
    • 24.003.20054
    • 20.005.30636
    • 20.005.30655

Explain

Adobe Acrobat Reader는 메모, 하이라이트와 같이 다양한 종류의 주석 객체를 지원합니다.

각 주석 객체는 해당 주석이 위치한 페이지 번호를 나타내는 page 속성이 포함되어 있습니다.

취약점은 주석 객체의 page 속성을 처리하는 과정에서 TOCTOU가 발생해 OOB Write 취약점으로 이어집니다.

v9 = *(void (__cdecl **)(_DWORD, void (__noreturn *)()))(dword_688BCDF4 + 8);
v49 = 2;
v9(0, sub_68191F20);
page_annot_ptr = (wchar_t *)ASSureCalloc(8, total_page_num); // [1]
(*(void (__thiscall **)(_DWORD))(dword_688BCDF4 + 12))(*(_DWORD *)(dword_688BCDF4 + 12));
v49 = -1;
word_688BD21C = a3;
gbPreserveAnnotNames = a4;
gbIgnoreFiltration = a5;

PDF 내부에 JavaScript 코드가 포함된 파일을 Acrobat Reader로 열면, AnnotsExportNotes 함수를 시작으로 여러 함수들이 호출됩니다.

[1]번에서 ASSureCalloc 함수를 통해, 8 * total_page_num 크기의 page_annot_ptr 배열을 생성합니다.

page_annot_ptr 배열을 통해 각 페이지별 주석 정보(annot_size, annot_buffer)를 저장할 공간을 확보합니다.

for ( i = *v10; i != v10[1]; ++i )   // [2]
{
  v46 = *i;
  v15 = (*(int (__thiscall **)(int))(*(_DWORD *)v46 + 24))(v46); // [3]
  v14 = (int **)v15;
  if ( v15 >= 0 && v15 < total_page_num )
  {
    v42 = (wchar_t *)*((_DWORD *)dword_688BCE18 + 15);
    v46 = *i;
    v16 = (_DWORD *)(*(int (__thiscall **)(int, __int16 *, int))(*(_DWORD *)v46 + 20))(v46, v38, 1);// calls CPDAnnot::getPopup
    v17 = ((int (__thiscall *)(wchar_t *, _DWORD, _DWORD))v42)(v42, *v16, v16[1]);
    *(_DWORD *)&page_annot_ptr[4 * (_DWORD)v14] += (v17 != 0) + 1; // [4]
  }
  v10 = a2;
}

[2]번 반복문에서는 각 페이지별 주석의 개수(annot_size)를 읽어옵니다.

[3]번에서 CPDAnnot::getPage를 호출해 현재 주석이 속한 페이지 번호를 확인한 뒤, [4]번에서 이 정보를 page_annot_ptr에 저장합니다.

따라서, page_annot_ptr는 페이지별 주석 개수 정보를 담게 됩니다.


total_page_num_1 = total_page_num;
index = 0;
 for ( v46 = 0; index < total_page_num_1; v46 = index )  // [5]
 {
   annot_buffer = (*((int (__cdecl **)(int))dword_688BCE00 + 1))(8 * *(_DWORD *)&page_annot_ptr[4 * index]); // [6]
   j_1 = v46;
   *(_DWORD *)&page_annot_ptr[4 * v46 + 2] = annot_buffer; // [7]
   *(_DWORD *)&page_annot_ptr[4 * j_1] = 0;
   index = j_1 + 1;
 }

[5]번 반복문에서는 파악한 annot_size에 따라, 각 페이지별 주석 관련 정보를 담는 annot_buffer를 할당합니다.

[6]번에서 malloc을 통해 8 * annot_size 크기의 메모리를 할당하고, [7]번에서 해당 메모리 주소를 page_annot_ptr에 기록합니다.


CPDAnnot = a2;                               
  for ( j = *a2; j != CPDAnnot[1]; ++j )   // [8]
 {
   v14 = (int **)*j;
   page_1 = ((int (__thiscall *)(int **))(*v14)[6])(v14); // [9]
   v46 = page_1;                                          // [10]
   if ( page_1 >= 0 && page_1 < total_page_num )
   {
     if ( *(_DWORD *)&page_annot_ptr[4 * page_1 + 2] )
     {
       v14 = (int **)*j;
       annot = (int *)((int (__thiscall *)(int **, __int16 *))(*v14)[4])(v14, v37);// CPDAnnot__getAnnot
       v26 = page_annot_ptr;
       v27 = *annot;
       v14 = (int **)annot[1];
       write_to_it_buffer_1 = *(_DWORD *)&page_annot_ptr[4 * v46 + 2];
       v29 = *(_DWORD *)&page_annot_ptr[4 * v46];
       *(_DWORD *)(write_to_it_buffer_1 + 8 * v29) = v27;
       *(_DWORD *)(write_to_it_buffer_1 + 8 * v29 + 4) = v14;
       ++*(_DWORD *)&v26[4 * v46];
       v14 = (int **)*j;
       ((void (__thiscall *)(int **, wchar_t *, int))(*v14)[5])(v14, v_data_A, 1);
       if ( (*((unsigned __int16 (__cdecl **)(_DWORD, wchar_t *))dword_688BCE18 + 15))(*(_DWORD *)v_data_A, v_data_B_1) )
       {
         v_data_B = v_data_B_1;
         v42 = page_annot_ptr;
         annot_buffer_1 = *(_DWORD *)&page_annot_ptr[4 * v46 + 2]; // [11]
         v32 = *(_DWORD *)&page_annot_ptr[4 * v46];
         *(_DWORD *)(annot_buffer_1 + 8 * v32) = *(_DWORD *)v_data_A; // [12]
         *(_DWORD *)(annot_buffer_1 + 8 * v32 + 4) = v_data_B;
         ++*(_DWORD *)&v42[4 * v46];
       }
     }
   }
   CPDAnnot = a2;
 }

[8]번 반복문에서는 할당된 annot_buffer에 주석 객체의 정보를 담는 작업을 수행합니다.

[9][10]번에서 CPDAnnot::getPage를 호출해 주석 객체가 속한 페이지 번호를 얻은 뒤, [11][12]번에서 페이지 번호를 통해 annot_buffer 배열에 주석 정보를 기록합니다.

취약점은 페이지 번호를 확인하는 시점과 annot_buffer에 기록하는 시점 사이에, CPDAnnot 객체의 페이지 번호가 변경될 수 있습니다.

이로 인해, 실제 주석이 위치한 페이지 번호와 annot_buffer에 기록된 페이지 번호가 불일치하는 TOCTOU가 발생하여 OOB Write 취약점을 트리거할 수 있습니다.

function main() { 

    app.activeDocs[0].layout = "TwoColumnLeft"; 
    app.activeDocs[0].scroll();
    app.activeDocs[0].submitForm({cURL:event.shift,  bAnnotations:true}); 

}

[...]
   function set_annot() {
   
    var square_annot = {page: 0, type: "Square", point: [18,14,3,6]}; 
    app.activeDocs[0].getAnnots()[0].setProps(square_annot);

}

공격자는 위와 같은 PoC 코드를 PDF 문서 내부에 삽입하여, 주석 객체의 페이지 정보를 처리하는 과정에서 페이지 번호를 수정해 OOB Write를 트리거 할 수 있습니다.

Reference



본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.