В мае 2018 года Advanced Threat Response Team of 360 Core Security Division обнаружил атаку APT с использованием 0-day уязвимости и захватила первый в мире вредоносный офисный образец, который использует 0-day уязвимость браузера
В образце была использована уязвимость use-after-free в движке VBScript, которая была исправлена Microsoft как CVE-2018-8174 в обновлении для системы безопасности в мае 2018 года, подробный анализ этого бага может быть найден по адресу:https://blog.360totalsecurity.com/en/analysis-cve-2018-8174-vbscript-0day-apt-actor-related-office-targeted-attack/
После тщательного анализа патча CVE-2018-8174 мы обнаружили, что исправление не было таким полным, и все еще существуют аналогичные проблемы, которые могут быть использованы для обеспечения надежного удаленного выполнения кода в движке VBScript. Мы сразу сообщили о проблемах, и Microsoft обратилась к ним с новым исправлением (CVE-2018-8242) в обновлении для системы безопасности в июле 2018 года.
В этом блоге мы расскажем о новых проблемах с использованием патча CVE-2018-8174, который мы нашли. Мы также обсудим, как мы используем новый баг и получаем надежное удаленное выполнение кода в дальнейшем блоге (часть II).
Исходный 0-day ITW
Прежде чем начать рассказать новый баг, давайте сначала рассмотрим старый баг. PoC для исходного бага выглядит следующим образом:
Вот как работает poc:
1. Он определяет класс VBScript с именем | cla1 |, класс имеет функцию | Class_Terminate | , которая будет вызываться каждый раз, когда экземпляр | cla1 | разрушается.
2. Он определяет vbscript SafeArray | arr | и установите первый элемент массива как экземпляр | cla1 |.
3. Он вызывает «erase» в массиве, который сначала очистит все элементы массива, а затем освободит весь SafeArray, включая его «внутренний буфер».
4. Поскольку первый элемент массива является экземпляром | cla1 |, когда он очищается, обратный вызов| Class_Terminate | будет вызван.
5. Внутри обратного вызова мы снова получаем референс на элемент, выполняя: Set o = arr (0)
6. После возврата из обратного вызова, arr (0) будет освобожден.
7. Сейчас мы имеет референс на освобожденный объект, который дает нам баг use-after-free .
Исправление в мае 2018 года
Исправление Microsoft для этого бага был довольно простым:
В oleaut32! VariantClear, если он очищает вариант VT_DISPATCH (Object), он сначала настраивает тип варианта VT_EMPTY перед запуском | Class_Terminate |, как указано ниже:
И псевдокод патча выглядит так:
if (variant is a dispatch object) {
variant.vt = VT_EMPTY;
Call VBScriptClass::Release which will call the class Terminate function.
}
Сейчас с этим патчем исходный PoC больше не будет работать, потому что когда вы пытаетесь захватить рефренс на элемент | arr (0) | в функции обратного вызова со следующей фразой в poc:
Set o = arr(0)
Вы получите пустой вариант, потому что тип варианта уже установлен на VT_EMPTY, поэтому use after free не произойдет.
Является ли исправление идеальным?
Давайте рассмотрим причину этой уязвимости:
Когда мы стираем SafeArray, он очищает элементы в массиве один за другим. Проблема в том, что мы все равно можем получить доступ к SafeArray, когда элементы очищаются, используя | Class_Terminate |.
Здесь «Доступ» к SafeArray означает, что мы можем:
1. Извлеките элементы из массива (который используется в исходном 0-day poc)
2.Введите элементы в массив.
3. Модифицируйте внутренние данные массива, включая свободный внутренний буфер массива.
Сейчас давайте вернемся к исправлению: патч не позволяет нам прочитать элемент, который очищается в обратном вызове. Легко заметить, что этот патч (частично) предотвращает первый шаблон, но не может адресовать другие 2 шаблона.
Посмотрим, сможем ли мы найти новые проблемы с использованием этих двух шаблонов после патча CVE-2018-8174.
Покажи мне новые 0-day
Новая проблема 1 — SafeArray Double Free
Попробуем шаблон 3, чтобы увидеть, что произойдет, если мы попытаемся удалить внутренний буфер SafeArray внутри обратного вызова | Class_Terminate | с новым poc:
Здесь мы немного изменили исходный poc, попытались стереть массив снова внутри | Class_Terminate |, к нашему удивлению, новый poc разбился немедленно:
(14c8.258): Break instruction exception – code 80000003 (!!! second chance !!!)
eax=00000000 ebx=00000000 ecx=000014c8 edx=00000000 esi=65789d80 edi=00000000
eip=6578cd6f esp=0727ca14 ebp=0727ca38 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
verifier!VerifierStopMessage+0x27f:
6578cd6f 837df800 cmp dword ptr [ebp-8],0 ss:0023:0727ca30=00000000
0:006> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0727ca38 6578ab0c verifier!VerifierStopMessage+0x27f
0727ca9c 6578794d verifier!VerifierDisableFaultInjectionExclusionRange+0x414c
0727cb00 65787aa5 verifier!VerifierDisableFaultInjectionExclusionRange+0xf8d
0727cb24 65787d30 verifier!VerifierDisableFaultInjectionExclusionRange+0x10e5
0727cb40 65789e10 verifier!VerifierDisableFaultInjectionExclusionRange+0x1370
0727cb5c 779616d9 verifier!VerifierDisableFaultInjectionExclusionRange+0x3450
0727cbbc 778b8c0a ntdll!RtlpNtSetValueKey+0x3089
0727ccc0 778b64b8 ntdll!RtlFreeHeap+0x2eaa
0727cd10 7553d83b ntdll!RtlFreeHeap+0x758
0727cd24 753cc819 combase!CoCreateFreeThreadedMarshaler+0x1362b
0727cd40 753cc8a4 OLEAUT32!_SafeArrayFreeData+0x34
0727cd4c 753ccb52 OLEAUT32!_SafeArrayReleaseDescriptor+0x38
0727cd5c 5e02f348 OLEAUT32!SafeArrayReleaseDescriptor+0x12
0727cd68 5e034c1e vbscript!AutoVariantRef::ArrayPinnableData::Unpin+0x21
0727cd6c 5e034c12 vbscript!VbsErase+0xee
0727cda8 5dfec837 vbscript!VbsErase+0xe2
Вы можете видеть, что верификатор обнаружил критическую ошибку при вызове RtlFreeHeap для освобождения блока кучи, что обычно указывает на потенциальное повреждение кучи. Обратите внимание, что проблема запускается с | VbsErase | в callstack.
Давайте проверим освобожденный блок кучи, который вызвал предупреждение верификатора:
address 041b4fd8 found in
_DPH_HEAP_ROOT @ 701000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
40b3f08: 41b4000 2000
65789e42 verifier!VerifierDisableFaultInjectionExclusionRange+0x00003482
779616d9 ntdll!RtlpNtSetValueKey+0x00003089
778b8c0a ntdll!RtlFreeHeap+0x00002eaa
778b64b8 ntdll!RtlFreeHeap+0x00000758
7553d83b combase!CoCreateFreeThreadedMarshaler+0x0001362b
753cc819 OLEAUT32!_SafeArrayFreeData+0x00000034
753cc8a4 OLEAUT32!_SafeArrayReleaseDescriptor+0x00000038
753ccb52 OLEAUT32!SafeArrayReleaseDescriptor+0x00000012
753741f7 OLEAUT32!SafeArrayDestroyDescriptor+0x00000087
5e034be6 vbscript!VbsErase+0x000000b6
Вы можете видеть, что блок кучи передан в | RtlFreeHeap | ,который уже был освобожден ранее в другом вывозе к VbsErase.
Сейчас совершенно ясно, что это двойная свободная уязвимость. В нашем poc, когда первый вызов VbsErase пытается освободить память дескриптора SafeArray, он уже был неожиданно освобожден вторым вызовом VbsErase в нашем обратном вывозе | Class_Terminator |.
Эта двойная свободная уязвимость довольно проста, однако для ее использования требуется некоторый метод. Я буду подробно обсуждать о этой уязвимости, включая подробный анализ и как написать poc-эксплойт для него в следующем блоге. Вы можете попытаться написать poc-эксплоит самостоятельно, если вы заинтересованы в нем.
Новая проблема 2: Объект Use After Free,вызванный рефренсом,привел к переполнению отсчета
Сейчас у нас есть новая двойная свободная уязвимость. Но мы не хотели останавливаться здесь и отдыхать, наш мозговой штурм продолжался.
О шаблонае 2? Можем ли мы сделать некоторые проблемы, написав элементы на SafeArray внутри обратного вывоза| Class_Terminate |?
Ответ — да. Проблема не так очевидна: мы просто пишем некоторые элементы на SafeArray, чья память будет освобождена, мы не напишем границу массива; мы не напишем в уже освобожденной памяти. Кажется, не имеет большого значения?
Однако рассмотрите следующую ситуацию:
1. У нас есть SafeArray с 2 элементами.
2. Мы вызываем erase на SafeArray.
3. Элементы в SafeArray будут очищаться один за другим, сначала очистят arr (0), а затем очистят arr (1) »
4. При очистке arr (1) мы можем запустить одни обратный вывоз | Class_Terminate |. И внутри обратного вызова мы сохраняем объект arr (0) : Set arr (0) = SomeObject. В VBScript сохранение объекта в SafeArray увеличит отсчет рефренсов этого объекта, поэтому мы получим SomeObject.ref_cnt + = 1. Отсчет рефенсов будет уменьшен, когда мы очистим объект от массива, в нормальном дело.
5. Сейчас, когда arr (1) очищен, все элементы в SafeArray очищены. Он не вернется к очистке arr (0), потому что arr (0) уже очищен до очистки arr (1). Затем он сразу освободит память внутреннего буфера SafeArray.
6. Таким образом, SomeObject.ref_cnt не будет уменьшаться. Это вызовет утечку рефренсов на объекте.
7. Отсчет рефренсов объекта VBScript составляет 32 бита. Путем триггера утечки отсчета рефренсов, мы можем, наконец, переполнить 32-битное отсчет рефренсов, и отсчет рефренсов станет очень маленьким. Затем мы можем освободить объект, очистив несколько рефренсов, в то время как у нас все еще есть много рефренсов, указывающих на этом объекте. Это означает, что с этой уязвимостью мы можем создать висячий указатель на уже освобожденный объект vbscript и получить доступ к свободному объекту в любое время, когда мы хотим, что довольно легко использовать.
Полный poc указан ниже:
Вышеуказанный PoC взял ~ 10 минут, чтобы потерпеть крах на моей тестовой машине. Ошибка при попытке доступа к уже освобожденному объекту в oleaut32! VariantClear: (58.1154): Нарушение прав доступа — код c0000005 (!!! второй шанс !!!)
eax=00000000 ebx=071cd010 ecx=0e9d2fc0 edx=04000000 esi=00000000 edi=00000009
eip=7537bbce esp=071ccfc0 ebp=071ccfe0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
OLEAUT32!VariantClear+0xfe:
7537bbce 8b01 mov eax,dword ptr [ecx] ds:0023:0e9d2fc0=????????
address 0e9d2fc0 found in
_DPH_HEAP_ROOT @ 3fb1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
dab3fa4: e9d2000 2000
65789e42 verifier!VerifierDisableFaultInjectionExclusionRange+0x00003482
779616d9 ntdll!RtlpNtSetValueKey+0x00003089
778b8c0a ntdll!RtlFreeHeap+0x00002eaa
778b64b8 ntdll!RtlFreeHeap+0x00000758
752d77c5 msvcrt!free+0x00000065
5f430619 vbscript!VBScriptClass::`scalar deleting destructor’+0x00000019
5f476fca vbscript!VBScriptClass::CheckDelete+0x00000028
7537bbb9 OLEAUT32!VariantClear+0x000000e9
60db19e2 IEShims!IEShims_SetRedirectRegistryForThread+0x00005472
5f43224e vbscript!AssignVar+0x0000013e
Исправление для системы безопасности в июле
Мы сообщили о 2 новых уязвимостях для Microsoft, и они обратились к ним в одном CVE (CVE-2018-8242) в июльском обновлении для системы безопасности.
Быстрый анализ показывает, что на этот раз они исправили новые проблемы следующим образом:
Сейчас для операций VBScript, которые освободят внутренний буфер SafeArray (erase, redim), прежде чем очищать элементы в массиве, сначала установит внутренний дескриптор SafeArray равным null. После этого исправления вы не сможете получить доступ (читать / писать / освободить) к SafeArray внутри | Class_Terminate | снова, потому что дескриптор уже очищен при вызове обратного вызова.
Этот патч прекрасно фиксировал новые проблемы, о которых мы сообщали.
Кстати, это не проблема безопасности, заметили ли вы, что вышеупомянутый патч ввел проблему с утечкой ресурсов (памяти)? Попытайтесь понять это.
Некоторые мысли
Это дело — интересное приключение для меня. Анализ poc / патч — подумать — поиск нового пути к сбою , каждый шаг приносит мне массу удовольствия.
То, что я узнал (снова) из этого дела, что анализ исправлений безопасности очень важен для охотника за ошибками. Я видел много случаев, когда исправление безопасности не помогло устранить все возможные пути уязвимости, и мы все еще можем найти способ запуска этой уязвимости после патча. И я также увидел случаи, когда исправление безопасности может указать новые возможности атаки или новые ошибки непосредственно в программное обеспечение. И я думаю, что разработчики и исследователи безопасности должны потратить больше времени на исправления безопасности.
Подробнее о 360 Total Security