Conti Ransomware malware leak WITH LOCKER
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

1380 lines
106 KiB

БЫСТРЫЙ СТАРТ ПО ТЕХНИКАМ СКРЫТИЯ, ВНЕДРЕНИЯ И ЗАЩИТЫ В ОС WINDOWS
ВВЕДЕНИЕ
Следует помнить, что ни одна из рассматриваемых техник защиты или атаки не является абсолютной.
Для каждой есть меры противодействия.
Некоторые техники используются несмотря на устаревание, т.к.:
- гарантируют запуск на старых ОС, коих всегда будет много;
- ничего не изобрели вместо.
Параллельно с этой методичкой рекомендуется к прочтению цикл статей "Malware development" в блоге https://0xpat.github.io
I. ТЕХНИКИ СКРЫТИЯ И ВНЕДРЕНИЯ
Windows позволяет манипулировать процессами в очень широких пределах,
вследствие наличия набора системных вызовов CreateRemoteThread(), ReadProcessMemory(), WriteProcessMemory().
Назначение этих вызовов - обеспечение работы отладчиков (из этого логически следует, что подобные
механики доступны во всех ОС, позволяющих отлаживать сторонний процесс.
В Linux они тоже есть: см.ptrace,
https://habr.com/post/430302/
https://github.com/gaffe23/linux-inject
https://www.evilsocket.net/2015/05/01/dynamically-inject-a-shared-library-into-a-running-process-on-androidarm/ ).
Интересная техника инъекции без ptrace: https://github.com/DavidBuchanan314/dlinject
Простенький инжектор под Линукс: https://stackoverflow.com/questions/24355344/inject-shared-library-into-a-process
Поэтому внедрение в другие процессы и манипуляция ими (в пределах прав доступа) - дело техники.
1. ИНЪЕКЦИЯ В ПРОЦЕСС
Инъекция в процесс нужна для выполнения своего кода в чужом процессе того же пользователя, в ситуации, когда уже есть доступ в систему.
Классическая цель - броузеры (перехват и замена трафика для реализации атаки MITM), мессенджеры, почтовые клиенты итд.
Последовательность вызовов примерно такая:
OpenProcess - открываем чужой процесс
VirtualAlloc - аллоцируем в нем память
VirtualProtect - разрешаем выполнение этой памяти
WriteProcessMemory - пишем свой шелл-код в эту память
CreateRemoteThread - запускаем шелл-код в виде нового потока
Если шелл-код короткий и написан с относительной адресацией, этого как правило и достаточно.
Но это редкий случай, такой код пишется как правило на ассемблере.
Более часто нужно выполнить большие куски логики, оформленные в виде dll.
Для запуска dll в чужом процессе эту dll нужно скопировать в чужую память,
и настроить ее - прописать весь импорт, настроить адреса ф-й итд.
В обычной жизни этим занимается функция LoadLibrary.
Но это слишком приметная функция, которая кроме того обращается к диску.
Полностью бездисковую инъекцию из памяти реализуют две библиотеки:
https://github.com/stephenfewer/ReflectiveDLLInjection
https://github.com/dismantl/ImprovedReflectiveDLLInjection
Вторая библиотека - просто улучшение первой:
- работает кросс-разрядная инъекция (32->64, 64->32, 32->32, 64->64)
Принципиально, что в реализации есть инъектируемый загрузчик, использующий только относительную адресацию.
Инъекция проходит так:
- в один кусок памяти записывается загрузчик
- в другой кусок памяти записывается dll
- управление передается загрузчику
- загрузчик ищет в таблице импорта функции LoadLibrary и GetProcAddress по хешам их имен
- загрузчик ищет в памяти dll и настраивает её
- после настройки загрузчик передает управление на DllMain
Эту технику можно улучшить, если переделать инъектируемый загрузчик во внешний:
- он будет выполняться из атакующего процесса, а не изнутри атакуемого
- в нем нужно заменить все функции работы с памятью на WriteProcessMemory/ReadProcessMemory.
Исходников готовой реализации пока в сети не встречалось, но в природе такая реализация есть.
Рефлективная загрузка dll в собственный процесс: https://github.com/fancycode/MemoryModule
Рефлективная загрузка 80-го левела: https://github.com/bats3c/DarkLoadLibrary/blob/master/DarkLoadLibrary/src/ldrutils.c
https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/
Заявлен обход ядерных триггеров на загрузку образа в память, хотя чем это отличается от рефлективной загрузки не очень ясно
(UPD: сообщают что запись о модуле не попадает в PEB)
Дополнительно, выполнение произвольного шелл-кода в контексте текущего процесса:
https://github.com/DimopoulosElias/SimpleShellcodeInjector/blob/master/SimpleShellcodeInjector.c
2. ПЕРЕХВАТ ФУНКЦИЙ (ХУКИ)
Известная не один десяток лет техника:
- в прологе большинства системных функций есть несколько nop (до 5-и), предусмотренных
специально для этой цели
- пролог функции заменяется jmp на наш обработчик
- обработчик вызывает оригинальную функцию по смещению пролога после nop (или не вызывает - зависит от логики)
Чтобы все отработало без ошибок, сигнатура нашей функции-обработчика должна полностью совпадать по типам данных,
возврата и соглашению о вызовах.
Если специального пролога нет, хук все равно можно поставить - не всегда. Нужно смотреть на содержимое пролога.
Просто затираемые jmp инструкции нужно скопировать и перенести в тело нового обработчика - но там творческий подход,
и универсальных рецептов нет.
Компилятор Microsoft имеет переключатель: свойства проекта -> C/C++ -> Создание кода -> Создать образ с обновлением
(patchable image) специально для цели генерации пустых прологов функций.
Такой хук еще называют трамплином, detour или inline-хуком.
В статье "Trampolines In x64" https://www.ragestorm.net/blogs/?p=107 можно посмотреть вариации трамплинов.
В данной статье https://www.malwaretech.com/2013/10/ring3-ring0-rootkit-hook-detection-22.html
расписано больше видов хуков (в т.ч. в ring0) - IAT hooks, inline hooks, SSDT hooks, SYSENTER_EIP Hooks, IRP Major Function Hook
3. PROCESS HOLLOWING
Process Hollowing (aka RunPE) - это Windows-реализация аналога пары сисвызовов fork()/exec() из Unix. То есть, запуск процесса,
и замещение его текста другим исполняемым модулем, c настройкой его в памяти процесса, и дальнейший запуск с точки входа.
В Windows вместо fork()/exec() используется CreateProcess(), делающий сразу и fork() и exec().
Т.е. происходит замещение текста процесса, которым управлять мы не можем.
Потому реализацию замены текста, настройки импорта и релокаций нужно делать самому, что достаточно сложно
и по сути дублирует системный загрузчик процессов ОС Windows.
Преимущества такого трюка в том, что:
- наш код выглядит как другой процесс
- другой процесс может быть доверенным (trusted) процессом ОС (explorer, svchost),
добавленный в исключения UAC, файрволла и антивирусов.
Реализация с подробным описанием доступна по адресу
https://github.com/m0n0ph1/Process-Hollowing
Антивирусы распознают process hollowing сравнением текста процесса в памяти и на диске.
Реализация на VBScript, для использования в макросах-дропперах:
https://github.com/itm4n/VBA-RunPE
4. PROCESS DOPPELGäNGING
Назначение техники то же самое - скрыть название настоящего запускаемого процесса.
Суть исполнения иная.
- открывается NTFS-транзакция на запись в какой-нибудь исполняемый файл. Как обычно, это будет
кто-нибудь из доверенных процессов - svchost, explorer итд.
- тело .exe заменяется нашим текстом
- процесс запускается
- транзакция откатывается. Физическая запись в тело файла не происходит.
Для всех остальных процессов выглядит так, как будто бы запускается оригинальный файл.
Но открывший транзакцию поток видит новый текст в запускаемом файле.
Техника успела устареть, т.к. начиная с какой-то версии Windows 10 транзакции NTFS вроде бы отменили совсем.
Реализация доступна по адресу
https://github.com/hasherezade/process_doppelganging/blob/master/main.cpp
Описание на английском:
https://hshrzd.wordpress.com/2017/12/18/process-doppelganging-a-new-way-to-impersonate-a-process/
Есть комбо из двух техник:
Process Doppelgänging combo Process Hollowing
https://blog.malwarebytes.com/threat-analysis/2018/08/process-doppelganging-meets-process-hollowing_osiris/
Transacted hollowing
https://github.com/hasherezade/transacted_hollowing
4.1. PROCESS HERPADERPING
https://jxy-s.github.io/herpaderping/
https://github.com/jxy-s/herpaderping.git
Техника похожа на Process doppelganging:
- настоящий текст программы пишется в файл и запускается CreateProcess
- до запуска реального потока текст затирается шумом (например, текстом программы-хоста)
- используется race condition в Windows Defender, чтобы подсунуть ему левый текст программы
5. ЗАМЕНА PEB
Легковесная техника для скрытия процесса постфактум.
PEB - это Process Environment Block, структура с основной информацией о процессе, присутствует в каждом процессе.
В ней в частности есть имя процесса и его командная строка. Их можно заменить как изнутри процесса, так и извне
(приостановив потоки процесса с помощью SuspendThread(), заменив данные WriteProcessMemory() и возобновив потоки
с помощью ResumeThread()). После этого в списке процессов видны измененные имя процесса и командная строка.
Еще один интересный прием - затирание таблицы экспорта .dll после загрузки в память.
После того, как получены адреса всех необходимых адресов функций, можно затереть весь экспорт и таким образом
осложнить идентификацию процесса по известным/уникальным именам функций.
Дополнительно, можно затереть секцию с ресурсами, если они не используются (либо после того как они были подгружены).
Словом, вариации с подменой и затиранием служебных таблиц/областей ограничена лишь фантазией.
6. SHELL CODE
Название идет с былинных времен, когда инъектируемый код был призван получить remote shell на атакуемой машине.
Потому код должен был удовлетворять требованиям:
- быть кратким насколько возможно (чтобы затереть чем поменьше в атакуемом процессе)
- быть позиционно-независимым (только относительная адресация)
Ныне, shell code обозначает любую вставку машинного кода вне зависимости от контекста применения.
Однако типичный контекст по-прежнему - инъекция в процесс, например грубо в кусок исполняемого потока (чтобы обойти DEP).
Так что указанные выше требования в силе.
Типичный shell code для процессов Windows делает bootstrapping (в том числе для обхода ASLR):
- определяет важные вехи в памяти - указатели на TEB и PEB
- из PEB получает адрес таблицы импорта
- из таблицы импорта получает адрес kernel32.dll
- из kernel32.dll получает адреса LoadLibrary, GetProcAddress (обычно по хешу имени, а не по самому имени)
- когда есть LoadLibrary и GetProcAddress, можно делать все что угодно.
https://habr.com/ru/post/522966/
http://www.hick.org/code/skape/papers/win32-shellcode.pdf
(У этого чувака http://www.hick.org/~mmiller/ кстати есть куча интересного, хотя и устаревшего)
https://www.corelan.be/index.php/2010/02/25/exploit-writing-tutorial-part-9-introduction-to-win32-shellcoding/
Этот же код можно написать на Си, не используя ассемблер.
Пример - функция ReflectiveLoader() https://github.com/dismantl/ImprovedReflectiveDLLInjection/blob/master/dll/src/ReflectiveLoader.c
Hell's Gate: разбор поиска системных вызовов в позиционно-независимом коде на Си
https://vxug.fakedoma.in/papers/VXUG/Exclusive/HellsGate.pdf
Конвертер PE->shell
https://github.com/hasherezade/pe_to_shellcode
7. INJECT PE / INFECT PE
Внедрение нагрузки в существующий PE-файл:
https://github.com/secrary/InfectPE/
https://github.com/JonDoNym/peinjector
В файл добавляется код распаковки (загрузчик) и нагрузка, переправляется оригинальная точка входа на загрузчик.
Так можно к легальному софту цеплять свои нагрузки.
Цифровые подписи разумеется слетают.
II. ЗАЩИТНЫЕ ТЕХНИКИ
КАК РАБОТАЮТ АНТИВИРУСЫ
АВ получают инфу о том что происходит в системе одним из двух путей:
- подписываясь на события ядра с помощью Event Trace for Windows (ETW) https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-
- подключая свой драйвер-минифильтр в режиме ядра (ring0), и подписываясь таким образом на активность файловой системы и подобные события (Windows Defender, Eset, WebRoot, McAfee)
- проставляя свои хуки в режиме пользователя (ring3) непосредственно в исполняемые процессы (Avast, BitDefender, Symantec, TrendMicro) на функции из ntdll.dll, user32.dll, kernel32.dll итд.
В первом случае ничего поделать нельзя, во втором случае хуки можно снять.
УСТРОЙСТВО WINDOWS DEFENDER
Windows Defender использует драйвер типа minifilter под названием WdFilter для подписки на события в системе.
Перехватываются:
- создание и запуск процесса
- загрузка образа процесса
- запуск потоков
- манипуляции с неродным процессом - запись в память и запуск удаленного потока
- верифицируются загружаемые драйвера
- перехватываются операции с реестром.
Из всего полезного что удалось найти в разборе - список условий, при которых можно избежать такого внимания:
- белый список процессов (в него входят в т.ч. svchost, werfault и процессы самого WinDefender)
- список hardened ключей реестра
https://n4r1b.com/posts/2020/01/dissecting-the-windows-defender-driver-wdfilter-part-1/
https://n4r1b.com/posts/2020/02/dissecting-the-windows-defender-driver-wdfilter-part-2/
https://n4r1b.com/posts/2020/03/dissecting-the-windows-defender-driver-wdfilter-part-3/
https://n4r1b.com/posts/2020/04/dissecting-the-windows-defender-driver-wdfilter-part-4/
УСТРОЙСТВО ЭМУЛЯТОРА
Эмулятор АВ предназначен для дополнительного анализа возможного поведения программы при статическом анализе бинарника:
https://findpatent.ru/patent/251/2514141.html
1) эмулятор не выполняет все системные вызовы, делая вызов "заглушек". Особо нужные для функционирования программы может выполнять (типа аллокации памяти),
но в общем случае - нет;
2) эмулятор больше интересуют последовательности системных вызовов, чем их результат;
3) в некоторых АВ эмулятор выполняет все ветвления в коде вне зависимости от выполнения условий (по некоторым свидетельствам VBA32).
Для того чтобы заглянуть во все ветки кода.
4) эмулятор ограничен по времени работы (порядка десятков секунд, либо по количеству выполненных системных вызовов).
В некоторых АВ эмулятор начинается с внедренных в ваш код хуков.
Детект эмулятора Avast:
// Противодействие Avast CyberCapture.
BYTE* A = (BYTE*)GET_API(SendMessageA); // Любая функция, на которую Avast ставит ловушку
if (A[0] == 0xe9) // Если первая команда - JMP
{
// В песочнице Avast ставит ловушки с переходом на адрес, по которому записана последовательность байтов FF25 00000000 (еще один JMP)
// т.е. характерным признаком эмулятора Аваст является два последовательных JMP, E9 и FF 25 в прологе вашей функции
// В нормальном режиме Аваст также ставит хуки, но их меньше, и они ведут на адреса насильно внедренной в процесс aswhook.dll
SIZE_T W = (SIZE_T)(A + 5) + (SIZE_T)(*(INT32*)(A + 1));
if (*(WORD*)W == 0x25FF && *(DWORD*)(W + 2) == 0)
{
debug_printfA(ORANGE, "Avast CyberCapture (sandbox) detected\n");
GET_API(ExitProcess)(-1);
}
}
Устройство ЧАСТИ обнаруживающего shell-код эмулятора BitDefender описано тут https://habr.com/ru/company/skillfactory/blog/527512/
Исходники эмулятора доступны https://github.com/bitdefender/bddisasm
Триггеры эмулятора:
- определение shell-кода в памяти:
- доступ к регистру указателя инструкций EIP/RIP
- доступ к структурам TEB/PEB
- поиск системных функций типа GetProcAddress
- самомодификация (расшифровка на месте)
- расшифровка строк на стеке
- выполнение стека
- использование SYSCALL вместо точек входа в библиотечных .dll
- в режиме ядра
- специфические инструкции типа SWAPGS
- доступ к регистру MSR
- доступ к KPCR (Область управления процессором ядра)
ПРОТИВОДЕЙСТВИЕ АНТИВИРУСАМ
Есть два вида детектов антивирусов (далее АВ):
- статический (по сигнатуре)
- динамический (по поведению).
С первым бороться легко, со вторым трудно.
Вдобавок, АВ используют нечеткое хеширование (fuzzy hashing (ssdeep, imphash)), нейросети и байесовские фильтры, чтобы обнаруживать неизвестную ранее малварь
по степени подобия текста или поведения уже известным образцам:
https://xakep.ru/2020/09/28/clear-hash/
АВ в обязательном порядке ставят детекты на известные строки и импорт функций.
Необходимые (но недостаточные) меры по защите от АВ:
- обфускация строк
- обфускация импорта (т.е. импортируемых из сторонних .dll функций)
- внесение шума в код
- шифрование кода с расшифровкой перед выполнением.
Дополнительные меры по противодействию АВ:
- отключение АВ (при достаточных правах)
- снятие хуков (только для АВ, работающих в userland)
Известна техника KHOBE (Kernel HOoks Bypassing Engine). В паблике отсутствует код, известны лишь общие сведения.
Нужно использовать race condition (чей?), предъявляя для анализа благополучный код в момент и быстро переключить контекст сразу после анализа АВ.
Подробнее см.раздел "Чистка от антивирусных детектов".
ПОИСК СИГНАТУР В ИЗВЕСТНЫХ БД
Декодер правил Windows Defender:
https://github.com/hfiref0x/WDExtract
Самый простой способ - поищите уже известные сигнатуры в базе сигнатур YARA.
Yara - это свободное средство для сигнатурного анализа малвари
https://virustotal.github.io/yara/
Но сами правила от него не выложены централизованно.
Есть коммерческие сборники правил, например
https://www.nextron-systems.com/2018/12/21/yara-rule-sets-and-rule-feed/
Есть бесплатные низкого качества https://github.com/Neo23x0/signature-base/
Большой сборник баз, среди которых есть и шлак, и актуальные: https://github.com/InQuest/awesome-yara
Синтаксис и возможности правил: https://habr.com/ru/company/varonis/blog/584618/
Можно конвертировать БД ClamAV в человеко-понятный формат Yara и далее искать нужные детекты:
https://resources.infosecinstitute.com/topic/yara-simple-effective-way-dissecting-malware/
> YARA with ClamAV Rules
>
> YARA can be integrated with ClamAv rule database. Perform the below steps to integrate ClamAv rules with YARA:
>
> Download the ClamAV to YARA Python script here: https://code.google.com/p/malwarecookbook/source/browse/trunk/3/3/clamav_to_yara.py
> Download and unpack the ClamAV db: http://database.clamav.net/main.cvd
> Run the ClamAV to YARA Python script:
> python clamav_to_yara.py –f main.cvd –o testing_clamav.yara
> Now test the converted rules with YARA like below:
> yara –r testing_clamav.yara /directory/to/check"
ОТКЛЮЧЕНИЕ WINDOWS DEFENDER
Иногда АВ можно просто отключить, в частности можно добавить себя в исключения Windows Defender (не всегда) здесь
HKLM|HKCU\MACHINE\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths
HKLM|HKCU\MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender\Exclusions\Paths
И примерно в том же месте вовсе отключить АВ.
Заметьте, что это делается через политики!
ОБФУСКАЦИЯ СИСТЕМНЫХ ВЫЗОВОВ
Простейшая техника - получение адреса функции с помощью GetProcAddress(LoadLibrary(decode("lib.dll"), decode("funcname"))
Однако здесь светятся вызовы GetProcAddress и LoadLibrary.
Старая, но до сих пор действенная техника GetApi была применена в троянце Carberp:
https://github.com/hzeroo/Carberp/blob/master/source%20-%20absource/pro/all%20source/RemoteCtl/DrClient/GetApi.h
Суть ее в поиске функций в таблице импорта по хешу их имени. Это сбивает сигнатурные детекты, хотя и доступно автоматизированному анализу
в дизассемблерах. Метод хеширования можно периодически менять.
Другой подход - cистемные вызовы inline - суем весь boilerplate-код подготовки сисвызова в ассемблер
https://github.com/JustasMasiulis/inline_syscall
Библиотека заменителей некоторым вызовам WinAPI:
https://github.com/vxunderground/WinAPI-Tricks
В конечном итоге, системные вызовы все равно ловятся АВ, работающих в режиме ядра через подписки на перехват вызовов.
ОБФУСКАЦИЯ КОДА
Обычно код в виде .dll сжимают, шифруют и пакуют в массив (см.ниже Скрытие данных в сегменте кода),
во время выполнения аллоцируют память, извлекают и распаковывают код, далее настраивают .dll в памяти
(релокации, импорт и все такое). См.ниже про крипторы и упаковщики - это оно и есть.
Этот трюк известен АВ и от проактивной защиты такое не спасает.
Потому мысль продвинулась дальше:
1. Распаковка каждой функции непосредственно перед выполнением.
В каждой функции определяется пролог и эпилог, который расшифровывает и шифрует тело функции соответственно.
Для поиска границы функции используются сигнатуры.
Для изначального шифрования функций в файле требуется внешний кодировщик.
Возможны и другие варианты декодирования на лету, но все они сложны в реализации.
2. собственные JIT-интерпретаторы.
Например, https://github.com/jacob-baines/jit_obfuscation_poc
Идея ясна из названия - транслируем один код в другой код, который неизвестен антивирусам и недоступен
автоматическому реверсу и анализу.
VMProtect-2 - обфусцирующая виртуальная машина
https://back.engineering/tags/vmprotect-2/
Есть интересный проект обфусцирующего компилятора на основе LLVM:
https://github.com/obfuscator-llvm/obfuscator
и пояснения по его алгоритмам
https://0xpat.github.io/Malware_development_part_6/
Еще один коммерческий:
http://www.star-force.ru/products/starforce-obfuscator/
Интересный подход к расшифровке кода - получение ключа расшифровки с сервера.
Это дает определенный эффект против эмуляторов АВ.
ОБФУСКАЦИЯ СТРОК
До появления constexpr обфускацию строк делали сторонней утилитой и двухпроходной сборкой:
- строки в коде помечались специальными маркерами, как правило в глобальной таблице строк
- обращение к строкам происходило через функцию дешифровки, по ее индексу в таблице
- утилита проходила по готовому .exe и замещала их шифротекстом.
Такой подход очень усложнял отладку и требовал дополнительных этапов сборки,
делая код нечитаемым.
constexpr позволяет сделать шифрование строки на этапе сборки, решая все перечисленные проблемы.
Однако это работает в Visual Studio не ниже версии 2015, требуя C++ стандарта 14.
Готовая библиотека шифрования Andrivet ADVObfuscator:
https://github.com/andrivet/ADVobfuscator
Также есть
https://github.com/Snowapril/String-Obfuscator-In-Compile-Time (основан на Andrivet)
https://github.com/adamyaxley/Obfuscate
https://github.com/fritzone/obfy
https://github.com/revsic/cpp-obfuscator
Обфускация работает на максимальных настройках оптимизации в Visual Studio:
- C/C++/ Оптимизация: Полная оптимизация
- C/C++/ Оптимизация / Оптимизация всей программы: Вкл
- C/C++/ Оптимизация / Разворачивать inline-функции: отключить
- C/C++/ Создание кода / Включить объединение строк: Да
Однако этот параметр надо пробовать, влияет когда как, по-разному на x64/x86
- другие настройки оптимизации тоже нужно пробовать, могут влиять.
Комментарий разработчика насчет того, что обфускатор не работает на полной оптимизации для компиляции х86:
"...почему обфускатор строк не всегда работает.
Дело в методе decrypt, получается когда включена оптимизация разворачивать подставляемые функции,
компилятор по месту вызова этого метода вставляет его тело, а так как в этом теле используются выражения, которые он может просчитать во время компиляции,
то и расшифровывает строку он получается во время компиляции.
Получается он зашифровал и расшифровал строку во время компиляции.
Исправляется отключением оптимизации разворачивать подставляемые функции."
Из известных недостатков инлайн-обфускаторов - ограничение на длину строк.
Каждый дополнительный символ в строке - это дополнительная рекурсия компилятора при вычислениях на этапе компиляции.
Стек у MSVC2015 кончается на длинах примерно в 100 символов.
Есть также простой трюк, используемый при отсутствии С++ (в чистом Си)
char str[] = { 'H', 'E', 'L', 'L', 'O' };
В такой инициализации строка заносится в массив на стеке mov'ами в во время выполнения, и как строка не попадет в сегмент .data
(т.е. превратится в набор ассемблерных инструкций в .text такого вида:
mov edx, 8EB5h
push edx
mov edx, 6C6CD488h
push edx
итд
кстати, расшифровка строки по месту выглядит примерно так:
mov edx, 8EB5h
xor edx, 8EDAh
push edx
mov edx, 6C6CD488h
xor edx, 0B1C0h
push edx
mov esi, esp
sub esp, 20h
mov edx, 0F478h
xor edx, 0F459h
push edx
mov edx, 74690CD7h
xor edx, 2CB2h
push edx
Чистая же строка выглядит в дизассемблере так:
.code:004010A5 aTest001 db 'Test001',0
.code:004010AD aLoremIpsumDolo db 'Lorem ipsum dolor sit amet',0
)
char str[] = "HELLO"; заполнится во время компиляции как строка и попадет в .data.
ОБФУСКАЦИЯ ТОЧКИ ВХОДА
Эта мера используется для противодействия АВ-эмуляторам и ручному анализу.
Реальная точка входа отличается от декларируемой в PE/ELF-заголовках.
К примеру, в .dll есть фальшивый безобидный экспорт (какой-нибудь DllMain, DoTheWork итд), выполняющий какие-то действия для отвода глаз.
Для запуска же реальной нагрузки нужно дернуть не проэкспортированную функцию по известному лишь запускающему контексту адресу.
Другой вариант - использование DOS-заглушки. Смена сигнатуры MZ на любую другую в двоичном файле PE приведет к запуску бинарника в режиме DOS.
Как следствие, АВ проигнорирует истинную точку входа.
В 16-битном же режиме эмуляторы не работают; через функцию 4B прерывания DOS можно запустить сторонний бинарь.
Это можно использовать в технике "разрыв цепочки".
СКРЫТИЕ ДАННЫХ В СЕГМЕНТЕ КОДА
АВ чутко реагируют на необычно большую секцию данных (.data, .rdata) - это признак сокрытия в ней зашифрованного кода-нагрузки.
Данные можно скрывать в секции текста. Компилятор Microsoft C++ позволяет сделать это с помощью такого трюка:
#pragma code_seg(push, ".text")
#pragma code_seg(pop)
unsigned char __declspec(allocate(".text")) hiddencode[]={ ... };
По подобному принципу можно складывать нагрузку в другие секции, прагмами/declspec data_seg, const_seg.
Правда, линкер может плодить секции с одинаковыми именами и разными правами доступа, потому есть еще такой вариант:
#pragma comment(linker,"/SECTION:.data,EW")
unsigned char PayloadName0[]={}
#pragma comment(linker,"/SECTION:.rdata,R")
unsigned char PayloadName2[]={}
Противодействие этой мере со стороны АВ - частотный анализ секции кода. У секции кода низкая энтропия,
т.к. число опкодов ограничено, и статистическое распределение символов в коде имеет четко выраженную структуру.
Потому скрытие шифрованных и/или упакованных массивов достаточно четко отслеживается.
Этому в свою очередь можно противопоставить слабо меняющие энтропию методики шифрования - к примеру,
xor 1 байт (разумеется, если в таком массиве скрыт код в виде .dll. Если там другие данные, это не поможет).
Про энтропию и вообще как выглядит ваш PE-файл для АВ:
https://0xpat.github.io/Malware_development_part_4/
Померять энтропию можно DIE (Detect It Easy).
КРИПТОРЫ И УПАКОВЩИКИ
Надежные реализации упаковщиков известны как минимум с середины 90-х годов.
Идея проста - один .exe упаковывается внутрь другого .exe, и при выполнении выполняет трюк барона Мюнгхаузена
по вытаскиванию и запуску нагрузки из себя.
Разумеется, это отличное средство для скрытия кода.
К упаковке добавляется также и шифрование.
Упаковка может быть многослойной, для затруднения анализа.
Пик популярности упаковщиков пришелся на 0-е годы.
Для определения типа упаковщика применялась программа PEId (прекращена в 2011-м году).
Сейчас применяются намного более умные крипторы.
Криптор вдобавок берет на себя функции обхода эмуляции АВ, детекта песочниц,
иногда даже обхода UAC и повышения привилегий (вследствие особенностей запуска нагрузки эти функции
бывает уместно возложить именно на криптор).
Также, кроме банального вытаскивания нагрузки из шифрованного массива внутри себя,
хороший криптор генерирует правдоподобные таблицы импорта, правдоподобный код, сбивающий с толку АВ,
разбавляет энтропию нагрузки, рассовывает нагрузку случайным образом по разным секциям,
генерирует настоящие ресурсы (строки в локализации), словом делает вид что он настоящая программа.
Подобный подход описан в https://xss.is/threads/39006/
Словом, это защитная оболочка, прячущая ваш код.
Конечно крипторы не всесильны, и детекты по поведению они не снимут.
Интересный подход к криптостроению: ключ расшифровки нагрузки находится отдельно от крипта и передается через командную строку (или еще как-то):
https://habr.com/ru/company/solarsecurity/blog/519994/
Используем комбо из публичного кода в пакере с нуля
https://iwantmore.pizza/posts/PEzor.html
в том числе используя
упаковщик Donut https://github.com/TheWover/donut
морфер Shikata Ga Nai https://github.com/EgeBalci/sgn
Криптор с открытым кодом:
https://github.com/oddcod3/Phantom-Evasion
Еще один:
https://github.com/ximerus/Kryptonite
Протектор с "наномитами":
https://www.apriorit.com/white-papers/293-nanomite-technology
Применена блокировка отладки процесса своим собственным отладчиком;
замена в дочернем потоке ВСЕХ инструкций переходов опкодами INT 3 (отладочное прерывание);
и формирование адреса перехода в процессе-отладчике.
СНЯТИЕ ХУКОВ
Для снятия чужих хуков можно использовать сравнение пролога функции в памяти процесса
с прологом в файле соответствующей .dll. Если они различаются, это признак того, что на функцию проставлен чужой хук.
Борьба соответствующая: прочесть тело функции из файла и заменить тело функции в памяти.
Достаточно первых 10 байт.
Обзор и сравнение разных техник:
https://www.first.org/resources/papers/telaviv2019/Ensilo-Omri-Misgav-Udi-Yavo-Analyzing-Malware-Evasion-Trend-Bypassing-User-Mode-Hooks.pdf
Демо: https://github.com/apriorit/antirootkit-anti-splicer
Еще: https://github.com/CylanceVulnResearch/ReflectiveDLLRefresher
Детект хуков: https://github.com/asaurusrex/Probatorum-EDR-Userland-Hook-Checker
Сравнение userland-хуков разных EDR; прямая работа с сисвызовами: https://github.com/Mr-Un1k0d3r/EDRs
Имейте в виду, что хуки на ваш процесс могут быть восстановлены после того, как вы их сняли.
ПЕРЕХВАТ ЧУЖИХ ПОТОКОВ (ЗАЩИТА ОТ ИНЪЕКЦИИ)
Перехватить чужую инъекцию в процесс можно, поставив свой обработчик на функцию BaseThreadInitThunk().
Создание нового потока начинается с нее (в том числе, инициированного извне процесса).
В этом обработчике можно принимать решение, разрешить или заблокировать запуск потока, по определенным признакам.
Самый простой подход - запустить сразу все свои потоки и далее блокировать все остальное.
Если это неприемлемо, можно смотреть на адрес и свойства страницы памяти, откуда запускается код.
У инъектируемого потока это как правило куча (heap). У потока здорового человека это секция текста (.text).
В частности, таким образом реализована защита от инъекций у броузера Mozilla Firefox.
Этой технике можно успешно противодействовать - извне процесса можно убрать хук на BaseThreadInitThunk
по описанной выше методике снятия хуков, после чего инъекция возможна.
Еще один способ - сразу после старта процесса выкрутить на максимум все митигации (см.ниже) в частности по DEP
и подписи кода.
https://ethicalchaos.dev/2020/06/14/lets-create-an-edr-and-bypass-it-part-2/
Здесь описана защита процесса с помощью:
- "невинной" разработки кода
- снятия хуков
- работы сисвызовами напрямую
- митигаций
- SharpBlock - еще одна техника, использующая перехват старта дочернего потока с помощью событий отладки, и патчинг его точки входа для запутывания EDR.
ЗАЩИТА ПРОЦЕССА ОТ ЗАВЕРШЕНИЯ
1. Запрет доступа через дискреционный список контроля доступа (DACL). DACL пустой => процесс сможет убить только админ:
https://stackoverflow.com/questions/6185975/prevent-user-process-from-being-killed-with-end-process-from-process-explorer
2. Пометка процесса как критически важного (RtlSetProcessIsCritical, NtSetInformationProcess).
Любая попытка остановить такой процесс будет приводить к BSOD;
при попытке убить такой процесс через Диспетчер Задач будет выведено предупреждение о том, что процесс критически важный
и его снятие может привести к краху системы.
Требует права администратора и наличие привилегии SeDebugPrivelege:
RtlSetProcessIsCritical:
https://www.codeproject.com/Articles/43405/Protecting-Your-Process-with-RtlSetProcessIsCriti
NtSetInformationProcess со значением параметра ProcessInformationClass = BreakOnTermination:
http://www.rohitab.com/discuss/topic/40275-set-a-process-as-critical-process-using-ntsetinformationprocess-function/
Использование этих вызовов может приводить к падениям.
Эмпирически установлено, что NtSetInformationProcess с параметром BreakOnTermination стабильно работает на 32-битных ОС,
а RtlSetProcessIsCritical - на 64-битных.
3. Если есть приватный ключ от цифровой подписи кода Microsoft (лол))), то начиная с Windows Vista
можно сделать любой процесс защищенным от любых изменений извне.
Также, защищенный родительский процесс может порождать защищенный дочерний процесс,
используя вызов функции CreateProcess с флагом CREATE_PROTECTED_PROCESS.
Этот механизм был улучшен в Windows 8.1, но он не идеален и не исключает возможности сделать любой процесс защищенным
или же снять защиту с системных процессов, имеющих цифровую подпись.
Пример создания защищенного дочернего процесса есть в описании функции UpdateProcThreadAttribute на MSDN:
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
Статья о защищенных процессах от Alex Ionescu:
https://www.crowdstrike.com/blog/evolution-protected-processes-part-1-pass-hash-mitigations-windows-81
https://www.crowdstrike.com/blog/evolution-protected-processes-part-2-exploitjailbreak-mitigations-unkillable-processes-and
Презентация от Alex Ionescu:
http://www.nosuchcon.org/talks/2014/D3_05_Alex_ionescu_Breaking_protected_processes.pdf
Пример эксплойта для уязвимости в драйвере Capcom, позволяющий сделать любой процесс защищенным:
https://www.unknowncheats.me/forum/anti-cheat-bypass/271789-pplib-ppl-processes.html
https://github.com/notscimmy/pplib
Статья с примерами как сделать процесс защищенным и поднять его привилегии, пропатчив память процесса:
https://www.blackhat.com/docs/asia-17/materials/asia-17-Braeken-Hack-Microsoft-Using-Microsoft-Signed-Binaries-wp.pdf
Исходники драйверов, снимающих защиту цифровой подписи:
https://github.com/Mattiwatti/PPLKiller
https://github.com/katlogic/WindowsD
4. Прочие не самые эффективные способы защиты процессов описаны здесь:
https://security.stackexchange.com/questions/30985/create-a-unterminable-process-in-windows
ЗАЩИТА ПРОЦЕССА ОТ СНЯТИЯ ПРИ ОСТАНОВЕ СИСТЕМЫ (SHUTDOWN)
1. Можно установить обработчик событий консоли через вызов SetConsoleCtrlHandler,
в котором возвращать 0 на события CTRL_LOGOFF_EVENT и CTRL_SHUTDOWN_EVENT.
- Работает для консольных программ, для которых не выполняются другие обработчики событий консоли.
- Начиная с Windows 7 обработка событий CTRL_LOGOFF_EVENT и CTRL_SHUTDOWN_EVENT не работает
для программ, использующих функции библиотек user32.dll и gdi32.dll.
Пример на MSDN:
https://docs.microsoft.com/en-us/windows/console/registering-a-control-handler-function
2. Можно вызывать функцию AbortSystemShutdown в бесконечном цикле.
- требует права администратора и привилегии SeShutdownPrivilege
- не успевает отработать если выполнить в консоли команду shutdown c ключом /t со значением 0 (таймаут 0 секунд)
- не спасает от выполнения в консоли команды shutdown с ключом /f
- на Windows 10 похоже не работает.
3. Можно создать невидимое окно и возвращать 0 в обработчике событий окна на события WM_QUERYENDSESSION и WM_ENDSESSION.
- Начиная с Windows Vista требуется вызывать функцию ShutdownBlockReasonCreate на событии WM_QUERYENDSESSION,
либо делать скрытие окна вызовом функции ShowWindow со значением второго параметра FALSE (хотя окно и так создается невидимым).
- от нажатия кнопки принудительного shutdown это никак не спасает.
- не спасает от выполнения в консоли команды shutdown с ключом /f
- не работает для консольных программ, в частности бесполезно использовать эту технику внутри dll, запущенной через rundll32
Подробнее описано на MSDN:
https://docs.microsoft.com/en-us/windows/win32/shutdown/shutdown-changes-for-windows-vista
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms700677(v=vs.85)
МИТИГАЦИИ
см. SetProcessMitigationPolicy()/UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY)
Позволяет включить у процесса DEP, ASLR, запретить динамическую генерацию кода,
доп.проверки на проверки подписи, валидности дескрипторов, SEHOP исключений, и много чего еще.
Этим пользуются броузеры и АВ, выкручивая митигации на максималки с целью затруднить инъекции в них или сковырнуть процесс.
На Windows 10 это действительно эффективно.
Хорошая статья http://www.sekoia.fr/blog/microsoft-edge-binary-injection-mitigation-overview/
и код к ней
https://github.com/SekoiaLab/BinaryInjectionMitigation/
демонстрируют защиту кода митигацией по проверке подписи кода.
В этой статье https://habr.com/ru/post/494000/ дан обзор политик митигаций, включая теневой стек, цитата:
"Code Integrity Guard (CIG) предъявляет обязательное требование к подписи загружаемых двоичных файлов.
Arbitrary Code Guard (ACG) гарантирует, что подписанные страницы неизменны,
а динамический код не может быть сгенерирован, что гарантирует целостность загружаемых двоичных файлов.
С введением CIG и ACG злоумышленники все чаще прибегают к перехвату управления с помощью косвенных вызовов и возвратов,
известных как Call/Jump Oriented Programming (COP/JOP) и Return Oriented Programming (ROP)."
СЕТЕВАЯ АСИММЕТРИЯ
Результаты работы антивируса зависят от страны.
Большинство производителей АВ решений - страны коллективного Запада.
Эффективность АВ зависит в основном от проверки нейросетками в "облаке".
С началом кибер-противостояния РФ-США, последние поставили приоритет всему трафику из своей страны, в ущерб другим странам (в том числе Западной Европы),
с целью усилить собственную безопасность. По-видимому проверочные мощности не безразмерны.
Поэтому стала нормальной ситуация, когда одна и та же нагрузка не работает в US и работает в других странах.
ЧИСТКА ОТ АНТИВИРУСНЫХ ДЕТЕКТОВ
Перед чисткой нужно первым делом убедиться, что антивирус не сливает образцы:
- virustotal ВСЕГДА сливает образцы в прямом эфире
- dyncheck сливает образцы при динамических проверках по поведению. При статических проверках - вроде бы нет
- у Windows Defender нужно отключить опцию "Отправка образцов"
- у остальных АВ нужно найти опцию отправки образцов и облачной защиты и отключить их.
Общая методика чистки такая:
1. Находим конкретные строки кода, на которые взводится детект;
2. Заменяем его.
Пункт 1 долгий и нудный, делается так:
- отключаем комментом или ifdef'ом ВЕСЬ код программы, собираем
- АВ затыкается
- раскомментируем половину кода
- АВ молчит
- еще половину половины
- АВ молчит
- половину половины половины
- АВ орет -> нашли участок!
Далее такой же дихотомией доходим до конкретных строчек:
- строчку раскомментируем - орет, закомментируем - молчит.
Учитываем оптимизирующий компилятор: оптимизатор может выкинуть огромный блок кода,
если не видит влияния этого кода на общее поведение.
К примеру, если в рамках поиска детекта поставить return посреди тестируемой функции,
оптимизатор может выкинуть из конечного бинарника и хвост ф-ии, и ее начало,
т.к. посчитает, что в оставшейся части ф-ии ничего полезного и влияющего на общее выполнение не происходит.
По такому же принципу оптимизатор выкидывает неожиданные участки кода, что здорово путает карты.
Как правило, антивирусные детекты выставляются на:
- имя бинарного файла (раз засвеченное имя будет давать детекты)
- Microsoft Visual C++ добавляет строку с именем проекта в бинарник: Свойства проекта -> Общие -> Целевое имя объекта (там по умолчанию стоит $(ProjectName))
нужно его рандомизировать, или просто затирать нулями/пробелами в пост-событии сборки прямо в бинарнике
- отдельные системные вызовы (CreateRemoteThread, VirtualProtect, CreateFile, CreateProcess, OpenProcess, работа с реестром, и прочее)
- последовательности системных вызовов (на отдельные сисвызовы молчит, на последовательность - орёт)
- открытые строки
- характерные алгоритмы (генераторы случайных чисел, (де)шифрование, (де)компрессия)
- высокая энтропия бинарного файла (шифрованые/архивированные массивы, в т.ч. в секции кода)
Дополнительно, детект на чистый файл может быть выставлен:
- При скачивании его с сайта с низкой репутацией (на который уже были комплейны о наличии подозрительных файлов)
- Как вариант, при скачивании файла из другой страны (влияет как адрес клиента, так и адрес сайта)
- При отсутствии хэша файла в базе данных АВ (запускаемых файлов в мире ограниченное количество)
Под Linux, не забываем убрать символы и лишние строки утилитой strip (а еще лучше sstrip).
Системные вызовы либо обфусцируются GetApi.h (можно брать напрямую из carberp/GetApi.h
https://github.com/hzeroo/Carberp/blob/master/source%20-%20absource/pro/all%20source/RemoteCtl/DrClient/GetApi.h)
либо, если в GetApi нужный вызов отсутствует, следующей последовательностью:
HANDLE h = LoadLibrary(_OBFUSCATED("dll.dll"));
void* f = GetProcAddress(h, _OBFUSCATED("funcname"));
здесь обе строки обфусцированы.
Последовательности сисвызовов обфусцируются таким же образом, либо сисвызовы творчески заменяются на аналоги.
Как обфусцировать строки см.выше.
Характерные алгоритмы мы разбавляем шумовым кодом:
- добавляем/убираем volatile к локальным переменным
- меняем место определения локальной переменной в коде (было в прологе ф-ии - ставим ближе к месту использования, и наоборот)
- добавляем шумовой код между строк (инкремент мусорной volatile переменной, сложения, вычитания, другие операции)
- такой шумовой код можно оформить в inline-ф-ю или макрос: в отладочной сборке тело ф-ии отключено, в боевой - constexpr'ами
создаются случайные паттерны кода.
- энтропию убираем, распихивая куски массива по разным секциям и дополняя массивы неиспользуемыми константными байтами,
собирая массив перед использованием из кусков.
Еще одна тактика снятия детектов - рандомизация адресов функций в итоговом .exe-файле.
Этого можно добиться простым перемешиванием списка объектных файлов в командной строке компоновщика:
link.exe /out:file.exe foo.obj bar.obj --> детект
link.exe /out:file.exe bar.obj foo.obj --> нет детекта
Есть утилита для поиска сигнатур в PE-файлах:
https://github.com/vxlabinfo/SignFinder
основана на статьях
https://vxlab.info/%d1%87%d0%b8%d1%81%d1%82%d0%ba%d0%b0-pe32-%d1%87%d0%b0%d1%81%d1%82%d1%8c-1/
https://vxlab.info/%d1%87%d0%b8%d1%81%d1%82%d0%ba%d0%b0-pe32-%d1%87%d0%b0%d1%81%d1%82%d1%8c-2/
сайт уже протух, но остались копии, например здесь
https://ru-sfera.org/threads/chistka-ot-signaturnogo-detekta-antivirusov.2870/
а также гуглить чистка-pe32-часть-1 чистка-pe32-часть-2
РЕПУТАЦИЯ ФАЙЛА
Файл с новым никому не известным хэшем будет блокироваться АВ просто на всякий случай.
Это называется "отсутствие репутации".
Потому обычно файлу накручивают репутацию на подконтрольных машинах: запускают, и при блокировке АВ,
разблокируют его вручную, добавляют в исключение и заставляют работать.
Таким же образом работает штатный SmartScreen, и АВ-экран Chrome.
Описание механизма репутации в Mozilla Firefox:
https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc
РАЗРЫВ ЦЕПОЧКИ
При обходе АВ в случае многоступенчатого запуска нагрузки, почти единственный способ избежать обнаружения - это разрыв цепочки запуска.
Так что родительским процессом у последующих ступеней является не предыдущая ступень, а легитимный файл ОС.
К примеру, мы хотим из лоадера скачать и запустить вторую ступень нагрузки.
Если мы сделаем это напрямую, АВ обнаружит связь между лоадером и нагрузкой.
Если мы добавим в цепочку промежуточное звено (например запуск нагрузки командой AT), то цепочка будет разорвана.
Одна добрая душа сделала каталог таких системных утилит и критериев для их использования https://lolbas-project.github.io/
https://github.com/api0cradle/LOLBAS
ПОДМЕНА РОДИТЕЛЬСКОГО ПРОЦЕССА
Разновидность разрыва цепочки, разрывающая связь с порождаемым процессом.
В CreateProcessA передается lpStartupInfo, в lpAttributesList которого указыватся дескриптор желаемого родительского процесса.
Кстати, таким образом можно повышать привилегии, наследуя контекст безопасности процесса.
Детали в https://blog.f-secure.com/detecting-parent-pid-spoofing/
ZERG RUSH
Запускаем 100500 разных хэшей крипта одной и той же нагрузки, перегружая тем самым АВ.
Если повезет, АВ выпилит только 10499:
https://habr.com/ru/company/solarsecurity/blog/519994/
ОБХОД AMSI
AMSI - это Antimalware Scan Interface, антивирусный модуль для анализа кода скриптовых языков Windows.
Он обрабатывает код на PowerShell, C#, VBScript, JavaScript, Windows Script Host (wscript.exe and cscript.exe),
макросов Office VBA, и UAC.
Краткая суть: анализируем исходники (если надо декомпилируем), только в статике, ставим детекты на строки - имена переменных, строки,
похожие паттерны, использование мостов для WinAPI.
Чистим соответственно.
Чувак по имени S3cur3Th1sSh1t проделал отличную работу, чтобы систематизировать все методы обхода AMSI.
Две статьи описывают принципы работы AMSI и принципы его обхода:
https://s3cur3th1ssh1t.github.io/Bypass_AMSI_by_manual_modification/
https://s3cur3th1ssh1t.github.io/Bypass-AMSI-by-manual-modification-part-II/
TL;DR: AMSI ставит детекты на строки, поэтому активно переименовываем идентификаторы в скриптах,
клеим строки на лету, используем левые кодировки для хранения скриптов.
Сканер детектов (находит строки, на которые выставлен детект):
https://github.com/RythmStick/AMSITrigger
Анти-AMSI обфускатор:
https://amsi.fail/
Техники обхода собраны здесь:
https://github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell
Обфускатор PowerShell-скриптов:
https://reconshell.com/chimera-powershell-obfuscation-script-for-bypass-amsi-and-antivirus/
Документы чистятся так:
- первым делом меняем разводку (хэш картинок, расположение элементов, текстовки итд)
- обфусцируем VBA код
- ставим таймауты перед распаковкой дроппера и перед запуском самого файла
FAT BINARY
Непроработанная, но давно известная и перспективная техника, связанная с необходимостью доставки универсальной 32/64-разрядной нагрузки:
https://en.wikipedia.org/wiki/Fat_binary
https://habr.com/ru/company/macloud/blog/545278/
За счет махинаций с заголовками исполняемых файлов, стартовый пролог является общим,
который далее выбирает нужную нагрузку с нужной точкой входа.
III. ОБНАРУЖЕНИЕ ПЕСОЧНИЦ И ОТЛАДЧИКОВ
ОБНАРУЖЕНИЕ ПЕСОЧНИЦ
Обнаружение песочниц нужно для того, чтобы не выполниться в ней. Не загружать и не светить основную нагрузку.
Песочницы в основном делают из виртуальных машин, но сам по себе этот критерий недостаточный, т.к. на ВМ вполне может крутиться легитимный терминальный сервер.
Много методов систематизировано здесь:
Al-Khaser: https://github.com/LordNoteworthy/al-khaser
PaFish: https://github.com/a0rtega/pafish
Хорошая статья по обнаружению эмуляторов и песочниц: https://0xpat.github.io/Malware_development_part_2/
Ниже очень краткая и неполная выжимка стратегий:
1. По имени машины (https://www.blackhat.com/docs/us-17/thursday/us-17-Kotler-The-Adventures-Of-Av-And-The-Leaky-Sandbox.pdf):
*ESET: REYNAPC, MALVAPC, ELEANOREPC, WRIGHTPC, BRIAPC, JORIPC, GABBIPC, HELSAPC, MAMEPC, SHARAIPC, ARACHONPC, FLORIANPC, EDITHPC
*Various: WIN7-PC, ROGER-PC, DAVID-PC, ADMIN-PC, APIARY7-PC, ANTONY-PC, LUSER-PC, PERRY-PC, KLONE_X64-PC, 0M9P60J5W-PC, MIKI-PC
*Avira: C02TT22, C02TT26, C02TT36, C02TT18, C06TT43
*Comodo: spurtive, circumstellar
*Others: ZGXTIQTG8837952 (Comodo), ABC-WIN7, PC, WIN-U9ELNVPPAD0, PC-4A095E27CB, WIN-LRG949QLD21
2. По серийным номерам и названию оборудования - MAC-адреса сетевой карты, имя тома жесткого диска
(vbox, qemu, vmware, virtual hd)
3. По выполнению в виртуальной машине.
4. По времени выполнения инструкции CPUID
4.1. По разнице GetTickCount() перед и после Sleep();
5. По отстутствию активности в интерактивной сессии (мышь, клавиатура)
6. Резолв заведомо несуществующего домена (NotPetya killswitch)
Несколько примеров реализации детекта песочниц:
- https://habr.com/ru/company/solarsecurity/blog/473086/
- Обзор дешманских методов от Positive Technologies: https://habr.com/ru/company/pt/blog/507912/
- Комбайн с многоступенчатым обнаружением песочниц:
https://blog.talosintelligence.com/2020/05/astaroth-analysis.html
читать с раздела "Anti-analysis/Anti-sandbox mechanisms"
ОБХОД ЭМУЛЯТОРОВ
Эмулятор как правило является частью антивируса, и ему нужно очень быстро определить, разрешить ли работу данного кода или нет.
Из-за этого, проверка в эмуляторе как правило не занимает много времени.
На этом строится основная стратегия обхода эмуляторов - задержка выполнения.
Простой Sleep() уже давно не работает, т.к. перехватывается эмулятором, реальной задержки не происходит.
Поэтому как правило вместо задержки используется цикл вычислений (например расчет числа Пи с большой точностью).
Много интересных и простых техник по обходу эмуляторов:
https://wikileaks.org/ciav7p1/cms/files/BypassAVDynamics.pdf
Подход, основанный на несовершенстве эмуляции WinAPI - анализ регистров ECX EDX после возврата из вызова:
https://winternl.com/fuzzing-the-windows-api-for-av-evasion/
https://github.com/jackullrich/Windows-API-Fuzzer
Более ранняя работа
https://github.com/SPTHvx/SPTH/blob/master/articles/files/dynamic_anti_emulation.txt
Маскировка истинной последовательности системных вызовов в большом количестве шумовых вызовов:
https://habr.com/ru/company/pt/blog/551954/
ОБНАРУЖЕНИЕ ОТЛАДЧИКОВ, ЗАЩИТА ОТ ОТЛАДКИ
1. IsDebuggerPresent() - ненадежно, функцию патчат и она возвращает "нас не дебажат"
2. Поиск процессов по имени (windbg, idapro итд)
3. Засечки времени прохождения характерных кусков кода
Интересные методы защиты от отладки у криптера OnionCrypter: https://decoded.avast.io/jakubkaloc/onion-crypter/
- применяют сигнатуры от известных пакеров (UPX), чтобы зациклить автоанализ и сбить с толку реверсеров попроще.
Разумеется нагрузка накрыта не тем пакером, следы которого оставлены.
- после обнаружения отладчика выбрасывается исключение
- три разных ф-ии для аллокации памяти - HeapAlloc GlobalAlloc VirtualAlloc. Много ложных аллокаций затрудняют ручной анализ,
делает бесполезным точку останова и хук на эти ф-ии.
- запуск нагрузки через callback системной ф-ии. Т.е. не "передаем управление на такой-то адрес",
а "вызываем EnumWhateverA и в качестве callback передаем этой ф-ии точку входа нагрузки".
IV. ТЕХНИКИ ЗАКРЕПЛЕНИЯ
На Windows, классические способы закрепления следующие:
- автозагрузка [HKLM|HKCU]\Software\Microsoft\Windows\CurrentVersion\Run
- задача по расписанию CoCreateInstance(CLSID_TaskScheduler, ...)
Из достоинств - не требуются права админа, из недостатков - настолько очевидны, что дают немедленный детект по поведению.
- установка себя как службы (без прав не обойтись)
- BITS - не столь часто используется, но не напрягает АВ
- ... (техник много)
Тут https://habr.com/ru/post/425177/ хороший обзор техник.
Тут http://www.hexacorn.com/blog/2017/01/28/beyond-good-ol-run-key-all-parts/ огромное количество стандартных
и нестандартных точек расширения в Windows. В частности обыгрываются идеи запуска по нестандартным триггерам - на аппаратные события,
точки расширения популярных программ итд итп.
Также смотри выше "Разрыв цепочки".
V. ОБРАТНЫЙ КАНАЛ И СВЯЗЬ
C&C-сервера всегда скрыты: либо за прокладками (обратными прокси), либо за тор-доменом.
Прокладки выстраиваются в каскады, их делается много, так что вывод из строя одной не приводит к краху всей сети.
При реализации канала связи с C&C следует помнить, что в ОС может быть настроен свой системный прокси.
Для начального поиска C&C можно использовать алгоритм генерации домена (DGA - Domain Generation Algorithm)
http://www.marc-blanchard.com/BotInvaders/index.php
Смысл его в том, чтобы сгенерировать псевдослучайные доменные имена
- которых не слишком много (до 10к), чтобы перебрать за разумное время;
- их достаточно много, чтобы их нельзя было засквоттить или еще как-то забанить/заспуфить;
- список доменов для одного месяца отличается от списка доменов для другого месяца;
- трудно сделать регулярку, чтобы вырезать их на dns-серверах.
Обычно для связи с C&C используется протокол HTTPS, но не всегда нужный порт или протокол открыт.
Если DPI-фильтр режет HTTP(s)-трафик, используются другие протоколы:
- можно делать специфические DNS-запросы на _нужный_ (свой) DNS-сервер, и прятать информацию в доменных именах
- можно делать специфические ICMP-посылки ***
- используются почтовые протоколы SMTP/IMAP/POP3
https://habr.com/ru/company/kaspersky/blog/522128/
Словом, можно использовать разные варианты модуляции полезного сигнала поверх несущей,
которая гарантировано проходит через файрволл.
Трафик перехватывается и анализируется системами наподобие Suricata https://suricata-ids.org/
распознающими аномалии и отыскивающими паттерны в трафике.
Библиотека PyWhat для автоматического парсинга трафика
https://habr.com/ru/company/dcmiran/news/t/563206/
https://github.com/bee-san/pyWhat
Существуют черные списки доменов, адресов, SSL-сертификатов, профилей трафика, например:
https://sslbl.abuse.ch
https://urlhaus.abuse.ch
https://feodotracker.abuse.ch
Используются такие техники как JA3 client fingerprinting/JA3S server fingerprinting/JARM (нечеткое хеширование):
https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967
https://habr.com/ru/company/acribia/blog/560168/
Суть их в том, что TLS-рукопожатие прогнозируемо для связки клиент+сервер, т.к. в рукопожатии фигурирует большое количество комбинаций шифров,
с учетом их взаимного расположения. Рукопожатие одного и того же клиента с одним и тем же сервером всегда одинаково.
С этого рукопожатия снимают отпечаток, клея версию TLS, принимаемые шифры, список расширений, эллиптические кривые и форматы эллиптических кривых, и накрывая MD5.
Средство для снятия отпечатка:
https://github.com/salesforce/ja3
https://ja3er.com/form
Средство борьбы с этим - рандомизация TLS-стека (Cipher-Stunting) и на клиенте, и на сервере (рандомизация настройки SSLCipherSuite и подобных):
https://www.bc-security.org/post/ja3-s-signatures-and-how-to-avoid-them
https://www.vectra.ai/blogpost/c2-evasion-techniques
TL;DR: командлет Enable-TlsCipherSuite позволяет сменить комбинацию шифров клиента - но - это общесистемная настройка.
BCryptAddContextFunction
https://docs.microsoft.com/en-us/windows/win32/secauthn/prioritizing-schannel-cipher-suites
Нужно предусмотреть постоянную актуальность адреса C&C-сервера в теле программы.
Поэтому вместо использования обычных доменных имен и DNS-инфраструктуры,
можно использовать публичную инфраструктуру, которую нельзя отозвать:
- emercoin-домены и DNS (можно делать как специфические запросы по протоколу Emercoin,
так и обычные DNS-запросы к серверам OpenNIC)
- записи в блокчейнах криптовалют (модуляция инфы в суммах, адресах или служебных записях)
- TOR-домены (не везде TOR открыт, и требуется специфичный клиентский код для работы поверх TOR)
- твиттеры и прочие публичные соцсети (реже, т.к. учетка может быть отозвана)
например https://safe.cnews.ru/news/top/2020-09-08_hakerynaemniki_shest_let
Обзор техник связи любезно предоставлен Positive Technologies: https://habr.com/ru/company/pt/blog/497608/
DNS-туннелирование: https://habr.com/ru/company/varonis/blog/513160/
Для борьбы с детектами на сетевой обмен используются подходы с маскировкой трафика.
Для работы в промышленных масштабах используются конструкторы вроде C2 Malleable Profile для Cobalt Strike
https://www.cobaltstrike.com/help-malleable-c2
https://github.com/threatexpress/random_c2_profile
или конструктора шлюзов C3 https://github.com/FSecureLABS/C3
Принцип в том, чтобы вынести транспортный уровень софта из него самого, сделать его гибко настраиваемым и маскируемым,
сделать возможность быстрого расширения и гибкой реакции на детекты за счет модульности (основной функционал софта - главный модуль,
сетевой функционал - выносной модуль-плагин).
Если полностью закрыты все каналы изнутри сети, то инфу все равно можно отправить.
Один из вариантов - почта (SMTP):
- Ищем почтового клиента на локальной машине и кладем письмо в Outbox, не забывая подчистить за собой
- ищем SMTP-сервер в локалке (хотя они теперь всегда требуют аутентификацию)
- также на локальной машине может быть доступна веб-почта (например OWA = Outlook Web Access) и закрыт при этом инет.
Другой вариант - так называемая "ракета":
https://www.blackhat.com/docs/us-17/thursday/us-17-Kotler-The-Adventures-Of-Av-And-The-Leaky-Sandbox.pdf
состоящая, как следует из названия, из двух ступеней.
Первая ступень должна быть Fully Undetectable.
Она собирает информацию с системы, формирует сообщение, и прошивает ее во вторую ступень.
Вторая ступень представляет из себя бинарник со следующими свойствами:
- он раздражает АВ (есть детекты по сигнатуре)
- он умеет связаться с C&C-сервером и отправить ему прошитую носителем посылку.
Дальше события развиваются так:
- корпоративный АВ отправляет вторую ступень в "облако" на выполнение в песочнице
- вторая ступень попадает в песочницу и запускается
- песочница находится в "облаке", снаружи обороняемого периметра, и оттуда есть связь с интернетом
- задача песочницы - исследовать поведение образца, потому его активность не глушится, хотя и регистрируется
- вторая ступень отправляет данные на C&C, и дальше ей уже все равно
- PROFIT
VI. ПОВЫШЕНИЕ ПРИВИЛЕГИЙ
ОБХОД UAC
Это первое что приходится делать: https://github.com/hfiref0x/UACME
Методы примерно до 20-го устарели и не работают; до 40-го через один.
В двух словах, идея обхода следующая:
1. маскируем текущий процесс под легитимный, для которого Windows никогда не спрашивает о необходимости elevation (путем подмены имени процесса в PEB)
2. дергаем другой процесс, у которого выставлена авто-элевация, чтобы он запустил нужный нам .exe (аналог suid root в Unix)
Для второго пункта есть туева хуча способов.
Ни в коем случае не запускать на личной машине! МОЖНО ПРИВЕСТИ ОС В НЕГОДНОСТЬ!
ПОДЪЕМ ПРИВИЛЕГИЙ (LPE)
По повышению привилегий, обзор общих стратегий тут: https://habr.com/ru/post/428602/
Конкретные эксплойты быстро устаревают, потому здесь не приводим.
Общая идея LPE в Windows - получение и использование чужого токена безопасности https://habr.com/ru/company/pt/blog/563436/
Теория и практика LPE: https://habr.com/ru/company/otus/blog/530596/
Общие приемы эксплуатации уязвимостей в ядре: https://habr.com/ru/company/pt/blog/566698/
(патчинг HalDispatchTable, кража токена)
VII. ИССЛЕДОВАНИЯ
Как понять, что происходит внутри черного ящика, будь то накрытого упаковщиком .exe, или неведомой системы вообще?
ПЕРЕХВАТ СИСТЕМНЫХ ВЫЗОВОВ
В Windows есть API Monitor (из комплекта rohitab), а в Linux - strace.
Подслушиваем системные вызовы интересующего нас исполняемого файла, отфильтровываем по нужным критериям, понимаем картину происходящего.
ПЕРЕХВАТ ТРАФИКА
Wireshark, можно узнать адреса, порты, протоколы. Если повезет, можно даже посмотреть подробности.
Из-за повсеместного SSL стало непросто, но можно подсунуть свой корневой сертификат в систему, поднять прокси с его использованием,
и ловить трафик уже на прокси.
https://mitmproxy.org/ прокси для HTTPS, также полезен для ловли проблем на стыках подсистем
PATCH DIFFING / BINARY DIFFING
Способ поиска уязвимости по её исправлению.
Берем старый исполняемый файл, берем новый исполняемый файл (с патчем уязвимости), смотрим разницу,
по разнице вычисляем подробности эксплуатации.
https://habr.com/ru/company/dsec/blog/479972/
https://wumb0.in/extracting-and-diffing-ms-patches-in-2020.html
FUZZING
Кормим системе на вход лютый бред, генерируемый случайно (но по правилам), и смотрим, когда она сломается (ловим крэши).
Далее, соотнося данные на входе и крэши, находим устойчивую картину.
Далее по ней уже можно верстать шелл-код для эксплуатации.
Для этого есть масса инструментов, вручную так никто не делает, все автоматизированно.
Описание фаззинга белого ящика (с известными исходниками).
https://habr.com/ru/company/dsec/blog/517596/
Это скорее для автотестировщиков, контроля качества, и генерации тестов на лету, а не для исследований/реверса.
И все же общие понятия о методиках и инструментах статья дает.
Популярный фаззер AFL (American Fuzzy Lop), основанный на генетических алгоритмах (морфирование корректного входного сэмпла)
https://github.com/google/AFL
он же под винду
https://github.com/googleprojectzero/winafl
Обоснование подходов к фаззингу с разбором теории:
https://habr.com/ru/company/bizone/blog/570312/
https://habr.com/ru/company/bizone/blog/570534/
https://wcventure.github.io/FuzzingPaper/
Фаззить просто черный ящик бесперспективно. Нужно
- реверсить/анализировать код
- искать входные проверки
- начинять фаззер ими, чтобы тот мог корректно мутировать ввод и прорываться за проверки
VIII. ВСПОМОГАТЕЛЬНЫЕ СЛУЖЕБНЫЕ ТЕХНИКИ
ИСКЛЮЧЕНИЯ И ПОСМЕРТНЫЙ СТЕК
Ловля багов и отладка в промышленных масштабах - задача в общем-то достаточно банальная, но решения для неё известны не всем и не всегда тривиальны.
Основной способ узнать, что пошло не так - это снять посмертный стек вызовов и отправить его телеметрией на сервер.
Теория и практика по обработке исключений в Windows: https://habr.com/ru/post/536990/
Для этого надо этот посмертный стек снять.
Для этого надо словить аварийный вылет программы (a.k.a. крэш) и снять нужную инфу, перед тем как дать ей погибнуть.
Для этого есть два основных способа:
* VEH - Vectored Exception Handling
* SEH - Structured Exception Handling
На Linux/*nix есть сигналы (SIGBUS, SIGSTOP, SIGILL итд), man signal
И еще раз:
- VEH - это AddVectoredExceptionHandler() и статья https://docs.microsoft.com/ru-ru/windows/win32/debug/using-a-vectored-exception-handler
- SEH - это __try ... __except и статья https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=vs-2019
Если используется SEH, то обернуть в try/except нужно все основные потоки.
Если используется VEH, то достаточно установки одного общего обработчика в прологе программы.
Из минусов SEH - Ошибка C2712 Cannot use __try in functions that require object unwinding, и решение описано тут
https://stackoverflow.com/questions/51701426/cannot-use-try-in-functions-that-require-object-unwinding-fix
Свойства / C/C++ / Создание кода / Включить С++ исключения: Нет
Главный недостаток любого подхода - с process hollowed процессами ни один метод не даст номеров строк и имен функций,
т.к. не сработает загрузка символов. Будут только голые адреса.
В обработчике исключения мы должны снять стек (код ниже).
Если мы хотим при этом номера строк кода, нам нужны символы (.pdb), а проект должен быть собран с опциями
- C/C++ / Общие / Формат отладочной информации: База данных программы (/Zi)
- Компоновщик / Отладка / Создавать отладочную информацию: Оптимизировать для отладки (/DEBUG)
- Компоновщик / Отладка / Создать полный файл базы данных программы: Да
и .pdb должен лежать РЯДОМ с умирающим .exe или .dll.
Для боевых сборок это не подойдет, но для отладки на внутренних ресурсах можно так. Для боевых сборок в стеке будут просто адреса, что тоже немало.
Код снятия стека достаточно мал, приведем его здесь:
#include <windows.h>
#include <Psapi.h>
// Some versions of imagehlp.dll lack the proper packing directives themselves
// so we need to do it.
#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
#pragma pack( pop, before_imagehlp )
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dbghelp.lib")
__declspec(noinline) DWORD DumpStackTrace() {
unsigned int i;
void * stack[100];
unsigned short frames;
SYMBOL_INFO * symbol;
HANDLE process;
debug_printf("PROGRAM CRASHED, STACK TRACE FOLLOWS:\r\n");
process = GetCurrentProcess();
if (!SymInitialize(process, NULL, TRUE))
return 0;
DWORD symOptions = SymGetOptions();
symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_LOAD_ANYTHING | SYMOPT_CASE_INSENSITIVE;
SymSetOptions(symOptions);
frames = CaptureStackBackTrace(0, 100, stack, NULL);
symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
DWORD offset_from_symbol = 0;
#ifdef _WIN64
IMAGEHLP_LINE64* line = (IMAGEHLP_LINE64*)calloc(sizeof(IMAGEHLP_LINE64), 1);
line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
#else
IMAGEHLP_LINE* line = (IMAGEHLP_LINE*)calloc(sizeof(IMAGEHLP_LINE), 1);
line->SizeOfStruct = sizeof(IMAGEHLP_LINE);
#endif
for (i = 0; i < frames; i++)
{
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
SymGetLineFromAddr(process, (DWORD64)(stack[i]), &offset_from_symbol, line);
debug_printf( "%i: %s (%s:%i) - 0x%0X\n", frames - i - 1, symbol->Name,
line->FileName, line->LineNumber, symbol->Address);
symbol->Name[0] = 0;
symbol->Address = 0;
if(line->FileName)
line->FileName[0] = 0;
line->LineNumber = 0;
}
free(symbol);
free(line);
return 1;
}
ФАЙЛЫ КАРТЫ (.MAP)
Включаются в компоновщике:
Visual Studio / Свойства проекта / Компоновщик / Отладка / Создавать файл сопоставления: ДА
Если у упавшей программы отсутствуют символы в .pdb, но известен адрес падения, то по карте можно найти адрес функции, как описано здесь:
https://www.codeproject.com/articles/3472/finding-crash-information-using-the-map-file
УМЕНЬШЕНИЕ РАЗМЕРА КОДА
ОТКАЗ ОТ CRT (C RUNTIME LIBRARY)
Пример программы, компилирующейся в .exe размером 3к:
hello.cpp:
#include <windows.h>
const char *str="Message";
int MyMain()
{
MessageBoxA(NULL,str,str,MB_OK);
ExitProcess(0);
return 0;
}
build.bat:
set PATH=c:\LLVM9\bin
clang++.exe -DUNICODE -c -D_UNICODE -m32 -std=c++14 -Wall -Os -mno-sse -fms-extensions -fms-compatibility -fno-exceptions -fno-rtti -fomit-frame-pointer -ffunction-sections -fdata-sections -Wno-c++11-narrowing -Wc++11-compat-deprecated-writable-strings *.cpp -I"c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include"
lld-link.exe /subsystem:windows /nodefaultlib /entry:MyMain /libpath:"c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib" /libpath:"c:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib" *.o kernel32.lib user32.lib
По умолчанию точка входа - WinMainCRTStartup в CRT. Когда своя точка входа, CRT не нужен.
Вдобавок здесь указан отключающий CRT ключ /nodefaultlib.
Но функции strcpy придется писать самому, и исключения не получится использовать.
Но strcpy уже есть в shlwapi.lib, потому делаем в коде
#include <Shlwapi.h>
#pragma comment(lib,"Shlwapi.lib")
ОТКЛЮЧЕНИЕ ПРОВЕРОК БЕЗОПАСНОСТИ
Компилятор Microsoft сует много дополнительного защитного кода в итоговый код - канарейки стека, проверки выхода за массивы,
зануление переменных на входе в функцию. Все это дает лишние килобайты и в боевом коде не нужно.
- Свойства / С/С++ / Создание кода / Основные проверки времени выполнения (/RTC) - по умолчанию (тут не очень понятно что ставить на самом деле)
- Свойства / С/С++ / Создание кода / Защита потока управления - Нет
- Свойства / С/С++ / Создание кода / Создать образ с обновлением - Нет (только если вы не будете ставить хуки на свои же функции)
- Свойства / С/С++ / Язык / Удалить код и данные, на которые не указывает ссылка - Да /Zc:inline)
- Свойства / С/С++ / Язык / Включить информацию о типах времени выполнения - Нет
ОПТИМИЗАЦИИ
- Отключить кадры стека (Omit frame pointers) - по умолчанию при входе в функцию текущая вершина стека в регистре BSP.
Таким образом можно отграничить личный стек текущей функции от стека вышестоящих функций.
Если отключить сохранение кадров стека, то высвободится еще один регистр, и уменьшится число ассемблерных инструкций,
как за счет отказа от записи в него, так и за счет того, что больше переменных можно хранить и передавать через регистры.
MICROSOFT RICH HEADER
http://ntcore.com/files/richsign.htm
http://bytepointer.com/articles/the_microsoft_rich_header.htm
Нештатная секция в PE-заголовке, которая вставляется компоновщиками Microsoft с 1998 года и версии Microsoft Visual Studio 6.0.
В нее записывается статистика о тулчейне, собравшем данный бинарник, как-то - кол-во объектных файлов C,
кол-во объектных файлов C++, кол-во объектных файлов ASM, версия компоновщика, версия компилятора ресурсов,
кол-во функций в импорте, и всякое такое.
Сделано это было скорее всего для отладочных целей (чтобы отладить тулчейн сборки).
Однако нужно понимать, что этот заголовок может быть использован для криминалистики как отпечаток.
КРОСС-БИТНЫЙ КОД
Возможно выполнять как 32-битный код в 64-разрядном режиме, так и наоборот.
В ядре Windows для выполнения системных вызовов из 32-разрядного режима предусмотрены шлюзы, такие как Heaven's Gate:
https://medium.com/@fsx30/hooking-heavens-gate-a-wow64-hooking-technique-5235e1aeed73
Еще пример:
http://blog.rewolf.pl/blog/?p=102
https://github.com/rwfpl/rewolf-wow64ext
ГЕНЕРАЦИЯ ПСЕВДОСЛУЧАЙНЫХ ЧИСЕЛ
Код и разбор простых алгоритмов ГПСЧ здесь https://habr.com/ru/post/499490/
Теоретический обзор некриптостойких и криптостойких ГПСЧ: https://habr.com/ru/post/531750/
Следует помнить, что качество ГСЧ (именно случайных, без буквы П в аббревиатуре) - это важнейшее звено криптографии.
Хороший криптоалгоритм сводится на нет использованием в нем плохого ГСЧ (например для генерации гаммы, вектора IV итд).
ЛИТЕРАТУРА
1. М. Руссинович, Д. Соломон - Внутреннее устройство Microsoft Windows, 6-е издание (Часть 1) [2013, PDF, RUS] - https://rutracker.org/forum/viewtopic.php?t=4469765
2. М. Руссинович, Д. Соломон, А. Ионеску - Внутреннее устройство Microsoft Windows. Основные подсистемы ОС. 6-е издание (Часть 2) [2014, PDF, RUS] - https://rutracker.org/forum/viewtopic.php?t=4727796
3. Библиотека разработчика - Лав Р. - Ядро Linux: описание процесса разработки, 3-е изд. [2013, PDF, RUS] - https://rutracker.org/forum/viewtopic.php?t=5169029
4. Библиотека программиста - Элджер Дж. - C++ [2008, PDF, RUS] - https://rutracker.org/forum/viewtopic.php?t=694260
5. Крис Касперски. Сборник из 507 статей - 2017.03.01 [PDF, DOC] - https://rutracker.org/forum/viewtopic.php?t=5375505
6. Understanding Windows Shellcode - [email protected] - http://www.hick.org/code/skape/papers/win32-shellcode.pdf
7. https://0xpat.github.io/ 0xPat blog Red/purple teamer - цикл статей "Malware development"