
Эксплойт-концепт для CVE-2020-1349, нуль-кликовое переполнение кучи в разборе MIME Outlook 2019, достигающее контроля EIP через heap spray и feng shui на корейской Windows 10 x64.
Эта уязвимость проявляется в Outlook 2019 (16.0.12624.20424), установленном на Korean Windows 10 1909 x64, кодовая страница 949.

Мне не удалось обойти ASLR с помощью этой уязвимости, но я написал эксплойт для Outlook 2019 32-bit, который открывает калькулятор, используя адрес системной библиотеки, фиксированный во время загрузки. Эта уязвимость является zero-click. Если учётная запись почтового сервиса, отличного от "outlook.com", связана с Outlook 2019 через IMAP, она срабатывает просто при получении почты.
v26 = IsDBCSLeadByte(*v13);
v12 = 0;
if ( v26 )
{
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
}
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
goto LABEL_51;
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
Происходит сбой, и часть, где существует уязвимость, выглядит так. Когда строка разобранного заголовка To или From является многобайтовой, переменная, подсчитывающая оставшиеся для разбора строки, уменьшается на 2, и происходит целочисленное переполнение (integer underflow) при уменьшении переменной на 2, когда её значение равно 1.
if ( v23 != '=' )
{
if ( v23 == 34 )
v15 = 6;
goto LABEL_48;
}
v15 = 1;
}
if ( v15 )
goto LABEL_40;
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
LABEL_40:
v24 = v13[v14];
if ( v24 )
{
if ( v5 == v14 )
goto LABEL_52;
if ( v8 != v14 )
{
v25 = IsDBCSLeadByte(v24);
v12 = 0;
if ( v25 )
++v14;
++v14;
}
}
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
LABEL_51:
if ( v5 <= v14 )
goto EXIT_0;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
if ( v15 )
{
if ( v15 == 1 )
{
v15 = 2;
v21 = v13[v14];
v22 = v14 + 1;
if ( v21 != '?' )
v22 = v14;
if ( v21 != '?' )
v15 = v12;
v14 = v22;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
else if ( v15 > 1 )
{
if ( v15 <= 3 )
{
v18 = v13[v14];
v19 = v14 + 1;
if ( v18 != '?' )
v19 = v14;
v14 = v19;
v20 = v15 + 1;
if ( v18 != '?' )
v20 = v15;
v15 = v20;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
if ( v15 == 4 )
{
if ( v13[v14] == '?' )
v15 = 5;
LABEL_40:
v24 = v13[v14];
if ( v24 )
{
if ( v5 == v14 )
goto LABEL_52;
if ( v8 != v14 )
{
v25 = IsDBCSLeadByte(v24);
v12 = 0;
if ( v25 )
++v14;
++v14;
}
}
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
if ( v15 == 5 )
{
v16 = v14;
v17 = v14 + 1;
v30 = v13[v16];
if ( v30 == '=' )
{
*v11++ = 34;
--v8;
}
memcpy(v11, v13, v17);
v13 += v17;
v11 += v17;
LODWORD(v5) = v5 - v17;
v8 -= v17;
if ( v30 == '=' )
{
*v11++ = 34;
--v8;
}
v12 = 0;
v14 = 0;
v15 = 0;
goto LABEL_51;
}
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
else
{
v23 = v13[v14];
if ( v23 != '=' )
{
if ( v23 == '"' )
v15 = 6;
goto LABEL_48;
}
v15 = 1;
}
if ( v15 )
goto LABEL_40;
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
LABEL_48:
v26 = IsDBCSLeadByte(*v13);
v12 = 0;
if ( v26 )
{
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
}
LODWORD(v5) = v5 - 1;
*v11++ = *v13++;
--v8;
goto LABEL_51;
[OUTLMIME!CloseAllSockets+0x50804 Псевдокод]
Однако при попытке вызвать heap overflow с помощью вышеуказанной ошибки я не смог удовлетворить условие выхода из цикла снова, поэтому не смог выполнять частичную перезапись (partial overwrite) столько, сколько хотелось. Поэтому мне пришлось искать входные данные, удовлетворяющие определённому условию, чтобы выйти из цикла. По той же причине, что и выше, я тщательно проанализировал окружающий код и вместо нахождения входного значения, позволяющего выйти из цикла, обнаружил ошибку, приводящую к бесконечному циклу без увеличения индекса или записи при обработке специальной строки.

После выполнения heap spray и heap feng shui удалось перехватить EIP, перезаписав vftable запущенного объекта в другом потоке, который не попал в бесконечный цикл из-за вышеуказанной ошибки.