17 июня 2010 года мир узнал о Stuxnet - компьютерном черве, который поразил системы управления промышленным оборудованием в Иране. По данным открытых источников, разработка этой вредоносной программы велась как минимум с 2005 года. Спустя 15 лет новый технический анализ одного из компонентов червя - USB-загрузчика ~WTR4141.TMP - показывает, насколько глубокими были инженерные решения его создателей. Речь идёт не просто о вредоносном коде, а об образцовом сочетании обфускации, перехвата системных вызовов и защиты от обнаружения, которое до сих пор изучают специалисты по информационной безопасности.
Загрузчик представлял собой DLL-библиотеку, которая запускалась автоматически при открытии специально созданного файла "Copy of Shortcut to.lnk" на съёмном носителе. Для этого использовалась уязвимость LNK (CVE-2010-2568), позволявшая выполнить код без ведома пользователя. Сам файл загрузчика имел имя с префиксом "~" и расширением .tmp. Это не случайность: если перехват API-функций не срабатывал и файл отображался в проводнике, такой набор символов делал его менее заметным для человека - приём из области социальной инженерии.
Первое, что делал загрузчик после активации, - загружал себя в оперативную память с USB-накопителя. Он избегал стандартного вызова LoadLibraryW, вместо этого формировал имя вида SHELL32.DLL.ASLR.[шестнадцатеричное число] (например, C:\Windows\SHELL32.DLL.ASLR.3adf9b47). Такого файла на диске не существовало - он создавал виртуальное отображение в памяти, куда предварительно помещал свой код. Это позволяло обойти антивирусные проверки, отслеживающие стандартные пути загрузки DLL.
Для сокрытия вредоносных файлов на флеш-накопителе применялся перехват таблицы импорта (IAT hook) - техника, при которой вызовы системных функций перенаправляются на собственный код. Загрузчик перехватывал функции FindFirstFileW, FindNextFileW, FindFirstFileExW из kernel32.dll, а также NtQueryDirectoryFile и ZwQueryDirectoryFile из ntdll.dll. Таким образом он контролировал два уровня - высокоуровневый API Windows и низкоуровневый интерфейс ядра. Файлы отфильтровывались по имени, расширению, размеру и контрольной сумме (модуль 10 должен был равняться нулю). Только те, что проходили проверку, скрывались от глаз пользователя и программ.
Ключевые API-функции, необходимые для работы, были предварительно обфусцированы. Например, строки с именами функций в четных позициях деобфусцировались с помощью операции XOR с магическим числом 0x12. Для модулей "kernel32.dll" и "ntdll.dll" использовалось число 0xAE12. Это серьёзно затрудняло статический анализ и обратную разработку.
Одной из самых интересных находок стало применение двойного отображения виртуальной памяти. Загрузчик вызывал ZwMapViewOfSection дважды для одного и того же физического участка памяти. В результате один и тот же физический адрес был доступен через два разных виртуальных адреса. Например, запись по адресу 0x00F00098 немедленно отражалась на адресе 0x00100098. Такой подход позволял эффективно обмениваться данными между разными компонентами процесса без копирования, а также обходить хуки, установленные на один из виртуальных адресов. После использования загрузчик освобождал память через UnmapViewOfFile.
Перед тем как установить перехватчики, загрузчик выполнял строгую проверку собственной PE-структуры. Он копировал базовый заголовок PE-файла в отдельную область памяти и последовательно проверял магическое число DOS-заголовка "MZ" и сигнатуру PE-заголовка. Затем он подтверждал количество секций и копировал каждую секцию по размеру "Raw Size". После этого вызывалась FlushInstructionCache - очистка кэша инструкций процессора, что говорит о глубоком понимании работы CPU и управления памятью со стороны разработчиков.
Когда проверки завершались, загрузчик переходил к установке перехватчиков на функции ntdll.dll: ZwCreateSection, ZwOpenFile, ZwCloseFile, ZwQueryAttributesFile, ZwQuerySection. Для каждой из них был подготовлен отдельный шелл-код (shellcode A), который с помощью GetProcAddress находил адрес нужной экспортируемой функции и затем передавал управление на основной перехватчик (shellcode B). При вызове перехваченной функции управление уходило в доверенное пространство памяти ntdll.dll, что делало детектирование ещё более сложным.
Особого внимания заслуживает процедура удаления файлов с USB-накопителя. Загрузчик был спроектирован для работы в условиях, когда файл может быть заблокирован антивирусом или другими процессами. Поэтому он использовал "устойчивое удаление": пытался стереть файл до восьми раз, даже если первый вызов DeleteFile завершался неудачей. Такой подход гарантировал уничтожение следов после завершения работы - важнейший элемент для скрытного перемещения между системами.
Эксперты в своём отчёте подробно описала механизмы Stuxnet, но даже спустя годы детальный разбор отдельных модулей вызывает восхищение. Автор нового исследования, используя инструменты PEbear, PEsieve, TinyTracer, ProcessHacker, IDA и x32dbg, подтвердил, что загрузчик применял перехват IAT, двойное отображение памяти, обфускацию строк и многократное удаление файлов. Всё это - только для того, чтобы доставить на целевую систему следующую ступень - настоящую полезную нагрузку ~WTR4132.TMP, которая была ещё сложнее.
Вывод, который напрашивается сам собой: даже спустя 15 лет подходы, использованные в Stuxnet, остаются образцом профессиональной инженерной мысли. Продуманность до мельчайших деталей - от выбора имени файла до очистки кэша процессора - демонстрирует уровень, которого современные киберпреступники достигают далеко не всегда. И пока злоумышленники продолжают развивать свои арсеналы, изучение таких жемчужин, как загрузчик Stuxnet, помогает сообществу ИБ лучше понимать, как строить защиту от самых изощрённых угроз.