[하루한줄] 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 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.