Parallels Desktop для Mac является одной из самых популярных программ виртуализации для macOS, но в отношении этого продукта не проводилось много публичных исследований уязвимостей. В ноябре прошлого года Рено Роберт (
Первоначальный анализ
Весь последующий анализ основан на версии 15.1.2. Как было проверено, гостевая виртуальная машина была настроена с параметрами по умолчанию.
Первоначальный отчет был коротким и был найден простым размышлением. Вот соответствующий код из Proof-of-Concept (POC):
По сути, это случайным образом и бесконечно записывает слова в порты ввода / вывода 0x3C4 и 0x3C5. Если вы запустите POC в уязвимой версии Parallels, это приведет к сбою prl_vm_appпроцесса в хост-системе. Каждая виртуальная машина в системе представлена отдельным prl_vm_appпроцессом.
В результате небольшого
Изучение первопричины
Сбой в большой функции называется sub_100185DA0. Связанная часть упрощена и прокомментирована следующим образом.
vga_context Структура выделяется во время инициализации устройства VGA. Он сохраняет статус и переменные для устройства VGA. Эта функция пытается записать vga_context->bufбуфер последовательно с тремя циклами. Общая длина оценивается в vga_context->h * vga_context->w * sizeof(DWORD) байтах. Затем он выполняет запись OOB и падает в циклах из-за неверной длины.
Первым шагом в нашем исследовании является определение источника vga_context->buf содержимого буфера.
Этот довольно большой 64 МБ буфер является экранным буфером, который настраивается через конфигурацию гостевой виртуальной машины (Hardware-> Graphics-> Memory). Похоже, это vga_context->hи vga_context->w есть высота и ширина для разрешения экрана гостевой виртуальной машины.
Далее нам нужно определить источник vga_context->hи vga_context->w содержимое буфера. Мы можем получить этот ответ от нашего отладчика и найти его vga_stateв sub_100184F90.
Но опять же, каков источник vga_state объекта?
Мы находим это общая память. В этом случае он является общим для хоста ring0 и хоста ring3. Он обновляется обработчиком порта ввода / вывода VGA в ring0, и позже рабочий поток видео ring3 (расположенный в sub_100183610) будет использовать его.
В соответствии с приведенным выше псевдокодом порт 0x3C4 действует как селектор для управления тем, что происходит в порту 0x3C5. Одной из особенностей порта 0x3C5 является то, что он может установить произвольное 16-битное значение на vga_state->hи vga_state->w. Когда рабочий поток видео ring3 получает новую высоту и ширину экрана, он пытается обновить весь экранный буфер ( vga_context->buf). Однако он не проверяет новую высоту и ширину, что приводит к переполнению экранного буфера.
Кроме того, длина перелива является контролируемой. Значение переполнения частично контролируется через порт 0x3C9 (см. vga_context->array). В результате мы определили, что это, вероятно, эксплуатируемый.
Оценка патча
После того, как патч был выпущен, я сделал несколько бинарных тестов между версиями 15.1.2 и 15.1.3, чтобы определить, как они решили исправить эту ошибку. Тщательно проверив различия, патч сделал очень маленькое изменение с вызывающим sub_100185DA0.
Одна из веток if изменилась. Патч перенесен flaggg из vga_stateв vga_context.
Что такое flaggg?
Как мы видим sub_100184F90, flagggдолжно быть, TRUEчтобы получить контролируемую высоту и ширину от vga_state. Тем не менее, это flagggдолжно быть FALSE для того, чтобы войти в функцию сбоя. Эти два ограничения противоречат друг другу.
Как мы можем удовлетворить эти ограничения?
Как мы уже vga_state говорили ранее при рассмотрении основной причины, разделяется ли память между ring0 и ring3. flagggФункция может быть сконфигурирован через порт 0x3C5. Следовательно, можно перевернуть flagggи выполнить двойную выборку в рабочем потоке видео ring3 между этими двумя ограничениями.
То , что патч на самом деле сделал это , чтобы перейти flagggот vga_stateк vga_context. Это улучшает ситуацию, поскольку vga_contextявляется распределением кучи в ring3 и не уязвимо для двойной выборки. Следовательно, он никогда не будет инициировать путь к записи OOB.
Вывод
В этом материале представлен хороший пример того, как пройти рабочий процесс и проанализировать причины для виртуального устройства в Parallels Desktop. Несмотря на то, что поставщик перечисляет исправления как «Низкие уровни серьезности», учитывая общие оценки CVSS и возможность перехода от гостя к хосту, следует учитывать, что исправление имеет важное значение по серьезности, и применить его как можно скорее. Мы не видим много ошибок в Parallels Desktop, представленных в программу, но, возможно, этот блог побудит других взглянуть. Если вы все же обнаружите какие-то уязвимости, нам, безусловно, будет интересно их увидеть.
Источник:
Ссылка скрыта от гостей
) сообщил о нескольких ошибках в Parallels в ZDI, одна из которых могла позволить локальному пользователю гостевой ОС повысить привилегии и выполнить код на хосте. Эта ошибка была исправлена в мае с версией
Ссылка скрыта от гостей
и была назначена
Ссылка скрыта от гостей
(ZDI-20-292). В этом блоге более подробно рассматривается эта уязвимость и изменение кода, внесенное Parallels для ее устранения.Первоначальный анализ
Весь последующий анализ основан на версии 15.1.2. Как было проверено, гостевая виртуальная машина была настроена с параметрами по умолчанию.
Первоначальный отчет был коротким и был найден простым размышлением. Вот соответствующий код из Proof-of-Concept (POC):
Код:
while (1) {
port = random_range(0x3C4, 0x3C5+1);
value = random_range(0, 0xFFFF+1);
outw(value, port);
}
По сути, это случайным образом и бесконечно записывает слова в порты ввода / вывода 0x3C4 и 0x3C5. Если вы запустите POC в уязвимой версии Parallels, это приведет к сбою prl_vm_appпроцесса в хост-системе. Каждая виртуальная машина в системе представлена отдельным prl_vm_appпроцессом.
Код:
Process 619 stopped
* thread #31, name = 'QThread', stop reason = EXC_BAD_ACCESS (code=2, address=0x158d28000)
frame #0: 0x0000000108c7a082 prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app + 738
prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app:
-> 0x108c7a082 <+738>: mov dword ptr [rsi], ecx
0x108c7a084 <+740>: cmp r12d, 0x2
0x108c7a088 <+744>: jb 0x108c7a0a0 ; <+768>
0x108c7a08a <+746>: mov dword ptr [rsi + 0x4], ecx
Target 0: (prl_vm_app) stopped.
(lldb) bt
* thread #31, name = 'QThread', stop reason = EXC_BAD_ACCESS (code=2, address=0x158d28000)
* frame #0: 0x0000000108c7a082 prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app + 738
frame #1: 0x0000000108c7ac8b prl_vm_app`___lldb_unnamed_symbol5078$$prl_vm_app + 907
frame #2: 0x0000000108c7dd52 prl_vm_app`___lldb_unnamed_symbol5093$$prl_vm_app + 1442
frame #3: 0x0000000108ce66dc prl_vm_app`___lldb_unnamed_symbol6282$$prl_vm_app + 636
frame #4: 0x0000000108c77bfc prl_vm_app`___lldb_unnamed_symbol5063$$prl_vm_app + 1468
frame #5: 0x0000000108c7762c prl_vm_app`___lldb_unnamed_symbol5062$$prl_vm_app + 28
frame #6: 0x000000010b91c153 QtCore`___lldb_unnamed_symbol228$$QtCore + 323
frame #7: 0x00007fff6879bd76 libsystem_pthread.dylib`_pthread_start + 125
frame #8: 0x00007fff687985d7 libsystem_pthread.dylib`thread_start + 15
(lldb)
В результате небольшого
Ссылка скрыта от гостей
мы обнаружили, что 0x3C4 и 0x3C5 - регистр индекса VGA-секвенсора и регистр данных секвенсора соответственно. На первый взгляд кажется, что в устройстве VGA существует ошибка записи OOB (Out-Of-Bounds). Как уже упоминалось, POC вызывается размытым, и в первоначальном отчете не было подробного анализа. Пришло время идти глубже.Изучение первопричины
Сбой в большой функции называется sub_100185DA0. Связанная часть упрощена и прокомментирована следующим образом.
Код:
char __fastcall sub_100185DA0(__int64 a1, unsigned int a2, unsigned int a3)
{
//...
vga_context = a1;
v12 = 0;
v13 = 0;
//...
while ( 1 )
{
//...
w = (_DWORD *)(vga_context->w);
//...
dst = (unsigned int *)((_QWORD *)(vga_context->buf) + 4LL * v12 * w);
v24 = 0;
do
{
v27 = 8;
do
{
//...
v31 = (_DWORD *)((_DWORD *)(vga_context->array[ 4LL * ((_BYTE *)v29) ]) | 0xFF000000);
*dst = v31; // crash here
++dst;
--v27;
}
while ( v27 );
v24 += 8;
v11 = (_DWORD *)(vga_context->w);
}
while ( v4 * v24 < v11 ); // v4 = 1
//...
}
v12 = v3 * ++v13;
if ( v3 * v13 >= (_DWORD *)(vga_context->h) ) // v3 = 1
break;
...
}
//...
}
vga_context Структура выделяется во время инициализации устройства VGA. Он сохраняет статус и переменные для устройства VGA. Эта функция пытается записать vga_context->bufбуфер последовательно с тремя циклами. Общая длина оценивается в vga_context->h * vga_context->w * sizeof(DWORD) байтах. Затем он выполняет запись OOB и падает в циклах из-за неверной длины.
Первым шагом в нашем исследовании является определение источника vga_context->buf содержимого буфера.
mapped file 00000001539e1000-00000001579e1000 [ 64.0M 47.9M 0K 0K] rw-/rwx SM=ALI
Этот довольно большой 64 МБ буфер является экранным буфером, который настраивается через конфигурацию гостевой виртуальной машины (Hardware-> Graphics-> Memory). Похоже, это vga_context->hи vga_context->w есть высота и ширина для разрешения экрана гостевой виртуальной машины.
Далее нам нужно определить источник vga_context->hи vga_context->w содержимое буфера. Мы можем получить этот ответ от нашего отладчика и найти его vga_stateв sub_100184F90.
Код:
char __usercall sub_100184F90@<al>(int *a1@<rdx>, __int64 a2@<rdi>, _DWORD *a3@<rsi>, unsigned int a4@<r11d>)
{
//...
vga_state = (_QWORD *)(vga_context->vga_state);
v6 = *(_DWORD *)(vga_state->flaggg);
if ( v6 )
{
width = (unsigned __int16 *)(vga_state->w);
height = (unsigned __int16 *)(vga_state->h);
// they will save to vba_context later
//...
}
Но опять же, каков источник vga_state объекта?
shared memory 000000011150e000-0000000111514000 [24K 24K 24K 0K] rw-/rwx SM=SHM
Мы находим это общая память. В этом случае он является общим для хоста ring0 и хоста ring3. Он обновляется обработчиком порта ввода / вывода VGA в ring0, и позже рабочий поток видео ring3 (расположенный в sub_100183610) будет использовать его.
Код:
__int64 __fastcall VgaOutPortFunc(__int16 port, unsigned int cb, unsigned __int64 a3, void *val, void *vga_state, __int64 a6)
{
v11 = *(_DWORD *)val;
v8 = *(_BYTE*)val;
//...
switch ( (unsigned __int16)(port - 0x3B4) )
{
//...
case 0x10u: // 0x3c4
vga_state->sr_index = v8;
return v7;
case 0x11u: // 0x3c5
switch ( vga_state->sr_index + 95 )
{
//...
case 9:
(_WORD *)vga_state->w = v11;
vga_state->sr_index = 0xABu;
return v7;
case 10:
(_WORD *)vga_state->h = v11;
vga_state->sr_index = 0xACu;
return v7;
//...
case 13:
if ( v11 & 1 )
{
(_DWORD *)vga_state->flag8 = 1;
}
else
{
(_DWORD *)vga_state->flag8 = 0;
}
//...
}
//...
case 0x15u: // 0x3c9
LOBYTE(i) = vga_state->i;
vga_state->i = (_BYTE)i + 1;
if ( (_BYTE)i == 2 )
{
v19 = vga_state->index2;
vga_state->array[4 * v19] = 4 * v8;
vga_state->i = 0;
vga_state->index2 = (_BYTE*)(v19 + 1);
}
else if ( (_BYTE)i == 1 )
{
*((_BYTE*)vga_state->array[4 * vga_state->index2 + 1]) = 4 * v8;
}
else if ( (_BYTE)i == 0)
{
*((_BYTE*)vga_state->array[4 * vga_state->index2 + 2]) = 4 * v8;
}
//...
return v7;
//...
}
В соответствии с приведенным выше псевдокодом порт 0x3C4 действует как селектор для управления тем, что происходит в порту 0x3C5. Одной из особенностей порта 0x3C5 является то, что он может установить произвольное 16-битное значение на vga_state->hи vga_state->w. Когда рабочий поток видео ring3 получает новую высоту и ширину экрана, он пытается обновить весь экранный буфер ( vga_context->buf). Однако он не проверяет новую высоту и ширину, что приводит к переполнению экранного буфера.
Кроме того, длина перелива является контролируемой. Значение переполнения частично контролируется через порт 0x3C9 (см. vga_context->array). В результате мы определили, что это, вероятно, эксплуатируемый.
Оценка патча
После того, как патч был выпущен, я сделал несколько бинарных тестов между версиями 15.1.2 и 15.1.3, чтобы определить, как они решили исправить эту ошибку. Тщательно проверив различия, патч сделал очень маленькое изменение с вызывающим sub_100185DA0.
Код:
__int64 __usercall sub_100186900@<rax>(__int64 vga_context@<rdi>, unsigned int a2@<r11d>)
{
//...
sub_100184F90((int *)&v29, vga_context, &v28, a2); // explained above
vga_state = (_QWORD *)vga_context->vga_state);
//...
if ( *(_BYTE *)(vga_context->flaggg) ) // after patch
//if ( (_DWORD *)(vga_state->flaggg) ) // before patch
{
//...
}
else if ( *(_DWORD *)(vga_state + 15828) ) // looks like always 1
{
//...
sub_100185DA0(vga_context, v28, v29); // trigger OOB write
//...
}
//...
}
Одна из веток if изменилась. Патч перенесен flaggg из vga_stateв vga_context.
Что такое flaggg?
Как мы видим sub_100184F90, flagggдолжно быть, TRUEчтобы получить контролируемую высоту и ширину от vga_state. Тем не менее, это flagggдолжно быть FALSE для того, чтобы войти в функцию сбоя. Эти два ограничения противоречат друг другу.
Как мы можем удовлетворить эти ограничения?
Как мы уже vga_state говорили ранее при рассмотрении основной причины, разделяется ли память между ring0 и ring3. flagggФункция может быть сконфигурирован через порт 0x3C5. Следовательно, можно перевернуть flagggи выполнить двойную выборку в рабочем потоке видео ring3 между этими двумя ограничениями.
То , что патч на самом деле сделал это , чтобы перейти flagggот vga_stateк vga_context. Это улучшает ситуацию, поскольку vga_contextявляется распределением кучи в ring3 и не уязвимо для двойной выборки. Следовательно, он никогда не будет инициировать путь к записи OOB.
Вывод
В этом материале представлен хороший пример того, как пройти рабочий процесс и проанализировать причины для виртуального устройства в Parallels Desktop. Несмотря на то, что поставщик перечисляет исправления как «Низкие уровни серьезности», учитывая общие оценки CVSS и возможность перехода от гостя к хосту, следует учитывать, что исправление имеет важное значение по серьезности, и применить его как можно скорее. Мы не видим много ошибок в Parallels Desktop, представленных в программу, но, возможно, этот блог побудит других взглянуть. Если вы все же обнаружите какие-то уязвимости, нам, безусловно, будет интересно их увидеть.
Источник:
Ссылка скрыта от гостей