Недавно мне попался файл svchost.pyc. Сначала название не вызвало особого интереса — мало ли файлов с похожими именами встречается в системе. Но при ближайшем рассмотрении стало понятно, что к обычному svchost.exe этот файл отношения не имеет.
Передо мной оказался скомпилированный Python-код, внутри которого было довольно много интересного: работа с реестром, COM Hijacking, изменение настроек Windows Defender, отключение AMSI и ETW, внедрение кода в другие процессы и даже попытка сделать собственный процесс критическим для системы. В общем, обычным Python-скриптом это уже не выглядело.
Я решил разобрать файл подробнее и посмотреть, что именно он делает после запуска и зачем ему понадобилось столько способов закрепиться в системе.
Сначала посмотрим, что это вообще за файл
svchost.pyc — это скомпилированный Python bytecode. Исходного .py рядом нет, поэтому при анализе приходится работать непосредственно с .pyc. Внутри сохранилась интересная строка:
E:\code2\AVE\VA\no3\appi\payload.py
То есть изначально файл был собран из payload.py.
Самое интересное начинается дальше. Основной код частично спрятан. В файле находятся данные, которые сначала декодируются через Base85, затем проходят XOR и LZMA-декомпрессию. После этого полученный Python-код передаётся в exec(). Получается примерно такая цепочка:
данные ↓ Base85 ↓ XOR ↓ LZMA ↓ Python-код ↓ exec()
Сделано это достаточно просто, но для статического анализа неудобно: часть логики нельзя увидеть сразу, пока не восстановишь содержимое. Но упаковка здесь далеко не самое интересное.
COM Hijacking
В коде я нашёл функцию setupcom_hijacking. Она работает с веткой:
HKCU\Software\Classes\CLSID\
и создаёт собственный CLSID:
{F56F6FDD-AA9D-4618-A949-C1B91AF43B1A}
Дальше появляется:
InprocServer32
а в качестве обработчика указывается:
C:\Windows\System32\scrobj.dll
Также задаётся:
ThreadingModel = Apartment
и параметр ScriptletURL.
Внутри самого файла есть ещё и JScript, который создаёт:
new ActiveXObject("WScript.Shell")
После этого выполняется:
tasklist /FI "IMAGENAME eq pythonw.exe"
То есть файл проверяет, запущен ли pythonw.exe, и в зависимости от результата может выполнить его запуск. Здесь уже становится понятно, что COM Hijacking используется как один из способов сохранить запуск вредоносного кода в системе. Но это только один из них.
Дальше начинается работа с Windows Defender
Следующее, что бросилось в глаза, — функция adddefender_exclusion. В ней встречаются:
HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Processes
А ещё:
PowerShell Add-MpPreference
То есть файл пытается добавить свои файлы и процессы в исключения Microsoft Defender. В коде есть и непосредственная работа с реестром:
HKEY_LOCAL_MACHINE KEY_SET_VALUE KEY_WOW64_64KEY REG_DWORD
Получается вполне понятная последовательность: сначала программа закрепляется в системе, а затем пытается сделать так, чтобы защитник меньше обращал внимание на используемые ею файлы и процессы.
Патчинг ETW
После этого я дошёл до функции patchetw. Она находит:
ntdll!EtwEventWrite
и меняет код этой функции прямо в памяти процесса. В качестве патча используется:
ret
То есть при вызове EtwEventWrite функция фактически сразу завершается.
Для чего это нужно — понятно из самого названия. Программа пытается отключить ETW для своего процесса и тем самым уменьшить количество событий, которые могут попасть в средства мониторинга. Это уже не просто настройка Windows через реестр. Здесь программа сама изменяет код системной DLL в памяти.
AMSI получает такой же патч
С AMSI используется примерно тот же подход. Есть функция patchamsi, которая работает с:
amsi.dll AmsiScanBuffer
Адрес функции получается через:
LoadLibrary GetProcAddress
Затем используется:
VirtualProtect
чтобы разрешить изменение памяти, после чего туда записывается собственный код.
Один из вариантов патча:
mov eax, 0x80070057 ret
То есть AmsiScanBuffer больше не выполняет обычную проверку, а сразу возвращает ошибку. Для скриптового вредоносного кода это особенно полезно: AMSI является одним из механизмов, через которые Windows и защитные продукты могут проверять выполняемый скриптовый код.
Попытка притвориться svchost.exe
Ещё интереснее выглядит функция hideprocess_info.
Она работает с:
PROCESS_BASIC_INFORMATION NtQueryInformationProcess ReadProcessMemory
и получает адрес PEB процесса. В PEB хранятся данные, которые можно использовать для определения параметров процесса. В коде отдельно обрабатываются:
CommandLine ImagePathName
А среди строк находится:
C:\Windows\System32\svchost.exe -k netsvcs
Здесь уже становится понятно, зачем файлу имя svchost.pyc. Программа пытается изменить информацию о собственном процессе так, чтобы при определённых способах проверки вместо настоящего пути и командной строки можно было увидеть что-то похожее на обычный системный svchost.exe. Сам процесс от этого, конечно, настоящим svchost.exe не становится. Меняется информация, которую можно получить из PEB.
Получение SeDebugPrivilege
Дальше программа проверяет, запущена ли она с правами администратора:
IsUserAnAdmin
После этого получает токен процесса через:
OpenProcessToken
и включает:
SeDebugPrivilege
Это уже необходимо для работы с другими процессами. И практически сразу после этого в коде находится то, ради чего такая привилегия действительно пригодится.
Внедрение shellcode
Функция injectto_system_process получает список процессов через:
tasklist
После выбора процесса используются стандартные Windows API:
OpenProcess VirtualAllocEx WriteProcessMemory CreateRemoteThread
Сначала открывается другой процесс, затем в нём выделяется память:
VirtualAllocEx
после чего туда записывается shellcode:
WriteProcessMemory
и создаётся поток:
CreateRemoteThread
В результате получается классическая последовательность:
OpenProcess ↓ VirtualAllocEx ↓ WriteProcessMemory ↓ CreateRemoteThread
То есть Python здесь используется уже не просто для выполнения скрипта. Через ctypes он напрямую вызывает Windows API и работает с памятью другого процесса.
А что будет, если процесс попытаться завершить?
На этот случай здесь тоже предусмотрена защита. Функция protectprocess использует:
NtSetInformationProcess
с параметром:
ProcessBreakOnTermination
Таким образом процесс помечается как критический. В самом коде даже есть сообщение:
[+] process protected (critical)
Смысл довольно простой: автор хочет сделать завершение процесса максимально неприятным для системы. Если такой процесс принудительно завершить, Windows может получить критическую ошибку. И это уже хорошо показывает общий подход написания программы злоумышленником: программа не только пытается запуститься, но и активно мешает себя остановить.
На этом способов восстановления не заканчивается
В коде я нашёл ещё функцию setupwatchdog_tasks.
Она создаёт Scheduled Task через:
schtasks
В XML-задании присутствует:
<LogonTrigger> <Enabled>true</Enabled> </LogonTrigger>
а также:
<RestartOnFailure> <Interval>PT1M</Interval> <Count>999</Count> </RestartOnFailure>
То есть при входе пользователя в систему задача запускается, а при падении процесса Windows может попробовать запустить его снова. Но и этого автору оказалось мало. Есть ещё один watchdog, который работает через WMI.
WMI следит за процессом
Функция setupwmi_watchdog работает с:
root\subscription
и создаёт:
__EventFilter CommandLineEventConsumer __FilterToConsumerBinding
Событие связано с удалением процесса:
__InstanceDeletionEvent
и классом:
Win32_Process
В частности, отслеживается pythonw.exe. Если процесс исчезает, WMI Consumer может выполнить команду повторного запуска. Получается интересная ситуация: за одним процессом одновременно могут следить Scheduled Task и WMI. Если один механизм убрать, второй всё ещё может вернуть процесс.
Обычный Run тоже присутствует
Ну и совсем без классики не обошлось. В коде есть writestartup, которая работает с:
Software\Microsoft\Windows\CurrentVersion\Run
и создаёт запись автозапуска через:
CreateKey SetValueEx HKEY_CURRENT_USER REG_SZ
В результате у файла сразу несколько вариантов закрепления:
COM Hijacking Registry Run Scheduled Task WMI
Для обычного приложения это выглядело бы очень странно. Здесь же всё складывается в одну картину.
Индикаторы компрометации (IOC)
MD5 |
10356274f35fc919623e297872ee7ebb |
SHA-1 |
53ec5c3666844034733b58b13eda8bfc9a6467f2 |
SHA-256 |
c4e8547c04a8c395af60d7f005c6702482d897e02d2d71b64387466b6474bb0f |
SSDEEP |
98304:zvOesLOioeEVvLca87Hsc5jr4z/McrfL/lMHIF2wvjQmp6xnKJnJbLCrxE/q:CXOis2X4zDfL/lMe2a90CV2rxv |
Что в итоге получилось
Когда я начал разбирать svchost.pyc, сначала казалось, что передо мной просто скомпилированный Python-файл с каким-то загрузчиком. Но чем дальше шёл анализ, тем интереснее он становился.
Файл умеет закрепляться в системе сразу несколькими способами, добавлять свои компоненты в исключения Defender, отключать AMSI и ETW прямо в памяти процесса, менять информацию о собственном процессе, получать SeDebugPrivilege, внедрять shellcode в другие процессы и защищать себя от завершения. При этом для восстановления используются сразу несколько независимых механизмов — реестр, COM Hijacking, Scheduled Task и WMI. Поэтому удаление одного файла или одной записи из автозагрузки здесь ещё не означает, что программа исчезнет из системы.
Самое интересное в этом образце даже не отдельные техники, а их сочетание. Злоумышленник фактически собрал вокруг Python-процесса небольшую систему выживания: один механизм отвечает за запуск, другой — за восстановление, третий — за обход защиты, а четвёртый — за защиту самого процесса.
И именно поэтому файл с безобидным на первый взгляд именем svchost.pyc оказался совсем не тем, чем кажется.
HemulGM
Да само название файла уже вызывает подозрение, если честно)