
Продвинутая техника обхода в памяти, переключающая защиту памяти шеллкода между RW/NoAccess и RX, а затем шифрующая/дешифрующая его содержимое
PoC-реализация ещё одной техники внутрипроцессного обхода, которая циклически шифрует и расшифровывает содержимое шеллкода, заставляя его «колебаться» между правами памяти RW (или NoAccess) и RX.
Когда наш шеллкод находится на страницах памяти RW или NoAccess, такие сканеры, как Moneta или pe-sieve, не смогут его отследить и выгрузить для дальнейшего анализа.
После публикации ThreadStackSpoofer я получил несколько вопросов о следующем пункте из README:
Измените защиту страниц памяти вашего Beacon на RW (с RX/RWX) и зашифруйте их содержимое перед засыпанием (это может обойти такие сканеры, как Moneta или pe-sieve)
Ранее я полагал, что сообщество уже знает, как шифровать/расшифровывать свои полезные нагрузки и менять права памяти, чтобы просто обходить сканеры памяти, ищущие аномальные исполняемые области. Вопросы показали обратное, поэтому я решил опубликовать этот «невооружённый» PoC, чтобы задокументировать ещё одну стратегию обхода и предложить сообществу пример реализации для работы.
Этот PoC демонстрирует довольно простую технику, уже известную наступательному сообществу (так что здесь я вряд ли привношу что-то новое), в надежде приоткрыть завесу тайны над магией, показываемой некоторыми коммерческими фреймворками, которые демонстрируют свои возможности обхода, нацеленные на оба упомянутых сканера памяти.
Вот сравнение при колебании до RW (другой вариант — колебание до PAGE_NOACCESS — описан ниже):

Эта реализация вместе с моим ThreadStackSpoofer даёт сообществу Offensive Security примеры реализаций, чтобы мы могли не отставать от предложений коммерческих C2-продуктов и быть ничуть не хуже в наших Red Team-инструментах. 💪
Эта программа выполняет самоинъекцию шеллкода (примерно через классические VirtualAlloc + memcpy + CreateThread).
Когда шеллкод запускается (эта реализация специально нацелена на импланты Cobalt Strike Beacon), хукается функция Windows, перехватывающая момент засыпания Beacon — kernel32!Sleep.
Всякий раз, когда вызывается захученная функция MySleep, она определяет границы своего выделения памяти, меняет их защиту на RW и побайтово выполняет xor32 над всеми байтами, хранящимися там.
Выждав ожидаемое количество времени, когда шеллкод вернётся к нашему обработчику MySleep, мы расшифровываем данные шеллкода и возвращаем защиту памяти обратно на RX.
PAGE_READWRITE работает следующим образомkernel32!Sleep, указывая на наш callback.VirtualAlloc + memcpy + CreateThread. В отличие от того, что было в ThreadStackSpoofer, здесь мы не хукаем ничего в ntdll для запуска шеллкода, а переходим на него из собственной функции. Это позволяет избежать оставления в памяти простых IOC, указывающих на изменённую память ntdll.MySleep.RW.kernel32!Sleep, чтобы не оставлять в памяти простой IOC, указывающий на то, что Sleep был трамплинирован (inline-хук).::Sleep, чтобы дать Beacon поспать в ожидании дальнейшей коммуникации.RX и повторно хукаем , чтобы обеспечить перехват последующих засыпаний.PAGE_NOACCESS работает следующим образомkernel32!Sleep, указывая на наш callback.VirtualAlloc + memcpy + CreateThread ...MySleep.PAGE_NOACCESS.kernel32!Sleep, чтобы не оставлять в памяти простой IOC, указывающий на то, что Sleep был трамплинирован (inline-хук).::Sleep, чтобы дать Beacon поспать в ожидании дальнейшей коммуникации.kernel32!Sleep, чтобы обеспечить перехват последующих засыпаний.Техника не новая, и не я её придумал. Это лишь реализация, показывающая концепцию и её практическое применение, чтобы наше сообщество Offensive Security могло не отставать от предложений коммерческих C2-фреймворков.
Вообще-то, с идеей переключения защиты памяти шеллкода я познакомился пару лет назад благодаря работе Джоша Лоспинозо в его потрясающем Gargoyle.
Вот ещё немного дополнительной информации:
Gargoyle развивает концепцию самоосознающего и самофлуктуирующего шеллкода гораздо дальше, используя ROP-последовательность с вызовом VirtualProtect.
Однако, хотя техника впечатляет, её так же сложно применить к Cobalt Strike Beacon без необходимости убивать его поток и постоянно переинициализировать Beacon в памяти.
Это далеко от идеала, но поскольку мы уже действуем с позиций собственного процесса-загрузчика самоинъекции, мы можем делать с окружением, в котором работает шеллкод, всё, что захотим, и скрывать его как угодно. Эта техника (и предыдущая — ThreadStackSpoofer) показывает преимущества запуска наших шеллкодов именно таким способом.
Реализация колебания до PAGE_NOACCESS вдохновлена работой ORCA666, представленной в его https://github.com/ORCA666/0x41 инжекторе.
Он показал, что:
Эта реализация содержит данную идею, доступную с опцией 2 в <fluctuate>.
Обязательно посмотрите и другие его проекты.
Инструмент ShellcodeFluctuation принимает три параметра: первый — путь к шеллкоду, второй — модификатор нашей функциональности.```
Usage: ShellcodeFluctuation.exe
:
-1 - Read shellcode but dont inject it. Run in an infinite loop.
0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything
1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE.
2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.
### Moneta (по-видимому) ложное срабатывание```
C:\> ShellcodeFluctuation.exe beacon64.bin -1
Итак, сначала посмотрим, что сканер Moneta64 думает о процессе, который не делает ничего подозрительного и просто запускает бесконечный цикл:

Как мы видим, здесь есть некоторое ложное срабатывание (по крайней мере, как я это расцениваю), якобы обнаруживающее Mismatching PEB module / Phantom image.
Границы памяти указывают на сам модуль ShellcodeFluctuate.exe и могут свидетельствовать о том, что этот модуль, хотя и имеет тип MEM_IMAGE, не связан с PEB процесса — что необычно и звучит довольно странно.
Причина этого IOC мне неизвестна, и я не пытался разобраться в ней лучше, однако на самом деле это не то, о чём нам стоит беспокоиться.
Если кто-то знает, в чём причина такого обнаружения, мне было бы очень интересно узнать! Пожалуйста, свяжитесь со мной.
C:> ShellcodeFluctuation.exe beacon64.bin 0
Второй вариант использования представляет Memory IOCs для Beacon, работающего внутри нашего процесса, который не использует никаких настроенных `Artifact Kits`, `User-Defined Reflective Loaders` (таких как мой [`ElusiveMice`](https://github.com/mgeeky/ElusiveMice)), а также никаких начальных действий, которые могли бы испортить наши результаты.

Мы можем видеть, что `Moneta64` правильно распознаёт `Abnormal private executable memory`, указывающую на место, где находится наш шеллкод.
Это действительно сильный Memory IOC, раскрывающий наш шеллкод для дампа и анализа автоматическими сканерами. Не круто.
### Зашифрованный Beacon с защитой RW```
C:\> ShellcodeFluctuation.exe beacon64.bin 1
Теперь третий вариант использования, наиболее интересный с точки зрения данной реализации, — флуктуирующий Beacon.

Помимо первого IOC, считающегося в некоторой степени ложным срабатыванием, мы видим новый, указывающий на то, что память kernel32.dll была изменена.
Однако в этот раз IOC Abnormal private executable memory отсутствует. Наша флуктуация (повторное шифрование/дешифрование и переключение защит памяти) активна.
И для протокола, pe-sieve также обнаруживает внедрённый PE при использовании с опцией /data 3 (если эта опция не указана, обнаружения не будет):

Моё текущее предположение состоит в том, что PE-Sieve замечает те же признаки, что и Moneta (описано ниже в разделе Изменённый код в kernel32.dll) — тот факт, что у отображённого PE-модуля непустой рабочий набор, что является очевидным свидетельством некоего внедрения кода. Это помечается как Implanted PE / Implanted. Если это так, вывод аналогичен наблюдению Moneta. Я не думаю, что нам стоит слишком беспокоиться об этом IOC с точки зрения обнаружения.
На данный момент я не придумал ничего лучше для перехвата выполнения шеллкода в середине (теперь речь о Cobalt Strike), чем хукать kernel32!Sleep. Таким образом, мы вынуждены оставлять подобного рода IOC.
Но эй, по-прежнему ни один байт не отличается от того, что лежит в файловой системе (C:\Windows\System32\kernel32.dll), и ни одна функция не перехвачена, в чём подвох? 😉
C:> ShellcodeFluctuation.exe beacon64.bin 2

Это заставит шелл-код фактически переключаться между страницами `RX` и `NA`.
На данный момент я не уверен в преимуществах переключения на `PAGE_NOACCESS` вместо `PAGE_READWRITE`.
### Модифицированный код в kernel32.dll
Так что же насчёт того модифицированного IOC `kernel32`?
Давайте попробуем разобраться в этом IOC и понять, в чём тут дело.
Во-первых, сделаем дамп упомянутой области памяти — секции `.text` (кода) модуля `kernel32.dll`. Для этого воспользуемся `ProcessHacker`, чтобы задействовать общеизвестный и стабильный инструментарий:

Мы создаём дамп секции кода предположительно модифицированного kernel32, а затем делаем то же самое для kernel32, работающего в процессе, который не изменял эту область.
Получив два дампа, мы можем сравнить их побайтово (используя мой [expdevBadChars](https://github.com/mgeeky/expdevBadChars)), чтобы выявить любые несоответствия:

И вот видно, что они совпадают друг с другом. Очевидно, в `kernel32.dll` не изменено ни одного байта, и причина этого в том, что мы снимаем перехват с `kernel32!Sleep` перед его вызовом:
`main.cpp:31:````
HookTrampolineBuffers buffers = { 0 };
buffers.originalBytes = g_hookedSleep.sleepStub;
buffers.originalBytesSize = sizeof(g_hookedSleep.sleepStub);
//
// Unhook kernel32!Sleep to evade hooked Sleep IOC.
// We leverage the fact that the return address left on the stack will make the thread
// get back to our handler anyway.
//
fastTrampoline(false, (BYTE*)::Sleep, &MySleep, &buffers);
// Perform sleep emulating originally hooked functionality.
::Sleep(dwMilliseconds);
Итак, что вызывает срабатывание IOC? Давайте рассмотрим Moneta подробнее:

Заглянув в Ioc.cpp проекта Moneta, примерно в строку 104, где сообщается об IOC MODIFIED_CODE, мы можем немного модифицировать код, чтобы точнее показать момент, когда он анализирует пул kernel32.
Итак:
a = truekernel32 имеется b = 0x1000 приватных байт. С чего бы? Их должно быть 0.a && b), сообщается об IOCКогда загрузчик образов Windows отображает DLL-модуль в адресное пространство процесса, лежащие в основе страницы памяти помечаются как MEM_MAPPED или MEM_IMAGE в зависимости от сценария.
Если мы изменяем хотя бы один байт в выделении MEM_MAPPED/MEM_IMAGE, система отделяет одну страницу памяти (при условии, что мы изменили меньше PAGE_SIZE байт и не пересекли границу страницы), чтобы обозначить фрагмент, который больше не отображается на исходный образ.
Затем это наблюдение используется как IOC — образ не должен содержать MEM_PRIVATE-выделений в пределах своей области памяти, поскольку это указывало бы на то, что какие-то байты когда-то были изменены в этой области. Moneta корректно обнаруживает модификацию кода, даже если на момент сравнения байты совпадали с байтами исходного модуля.
За подробным объяснением того, как работают под капотом Moneta, реализация внедрения процессов и связанные с этим IOC, прочитайте следующие статьи высшего качества от Форреста Орра:
Это поистине выдающееся исследование и документация от Форреста, отличная работа, приятель!
Особенно вторая статья описывает обоснование этого обнаружения; вот чему нас учит Форрест:
В случае, если бы модуль был легитимно загружен и добавлен в PEB, шеллкод-имплант всё равно был бы обнаружен из-за 0x1000 байт (1 страницы) памяти, приватно отображённой в адресное пространство и полученной Moneta через запрос её рабочего набора, что приводит к IOC модифицированного кода, как показано выше.
Подытожим: мы оставляем после себя IOC, но стоит ли нам об этом беспокоиться? Даже если IOC есть, украденные байты не видны, так что нет непосредственной ссылки на наш шеллкод или отличия техники нашего шеллкода от других.
Короче говоря — нам не стоит особо беспокоиться об этом IOC. :-)
Можно сказать, что эта реализация далека от совершенства, потому что она что-то оставляет; IOC всё же есть, а коммерческие продукты демонстрируют, что у них подобных особенностей нет.
Когда этот аргумент оказывается на столе, я должен напомнить, что коммерческие фреймворки имеют полный контроль над исходным кодом своих имплантов и загрузчиков шеллкода и потому могут аккуратно интегрировать их друг с другом, избегая необходимости самим выполнять хуки и обходные манипуляции вокруг своего шеллкода. Здесь же нам нужно хукать kernel32!Sleep, чтобы перехватывать выполнение Beacon от Cobalt Strike прямо перед его засыпанием и запускать нашу служебную работу. Если бы существовал более удачный механизм для нашего вмешательства без хука на sleep — было бы идеально.
Однако в Cobalt Strike существует такое понятие, как Sleep Mask, и ограничения по размеру в несколько сотен байт не позволяют нам полностью перенести эту логику в саму маску (иначе мы могли бы не хукать и Sleep, не оставляя IOC, как это делают коммерческие продукты).
Ещё один аргумент может заключаться в том, что коммерческие фреймворки интегрируют подобную логику в свои Reflective Loader'ы, а мы вместо этого оставляем её в EXE-обвязке. Это правда, но причина такого решения двоякая:
Мне нужно быть очень осторожным с публикацией подобной технологии, чтобы не помочь вооружить реальных преступников реализацией, которая ещё аукнется нам очередной Petya. Поэтому я решил опустить некоторые жуткие детали, которые использую в своём профессиональном инструментарии для проведения коммерческих контрактных упражнений по имитации действий противника (Adversary Simulation). Публикуя это зерно, я надеюсь встретить понимание у профессионалов сообщества, способных развить эту концепцию в собственных инструментах, при условии, что у них будут соответствующие навыки.
Я бы гораздо охотнее перенёс всю эту логику в User-Defined Reflective Loader от Cobalt Strike, что дало бы группам Red Team повышенные шансы на этапе доставки. Но, во-первых, см. пункт (1), а во-вторых, эта технология в настоящее время ограничена размером 5 КБ для их RDLL, что не позволяет мне реализовать её и там. Те из нас, кто создаёт собственные C2 и импланты для внутренних упражнений Adversary Simulation, теперь получили пример реализации, который наверняка поможет им соответствующим образом усовершенствовать свои инструменты.
Посмотрите на код и его реализацию, поймите концепцию и переосмыслите её в рамках собственных загрузчиков шеллкода, которые вы используете для проведения своих Red Team-задач. Это ещё одна техника продвинутого обхода в памяти, которая повышает шансы вашей команды не быть пойманными антивирусами, EDR и аналитиками вредоносного ПО, изучающими ваши импланты.
Разрабатывая свой продвинутый загрузчик шеллкода, вы также можете захотеть реализовать:
BeaconEyeMEM_PRIVATE-выделений памяти, на которые ссылаются эти потоки)Вариант использования:``` Usage: ShellcodeFluctuation.exe : -1 - Read shellcode but dont inject it. Run in an infinite loop. 0 - Inject the shellcode but don't hook kernel32!Sleep and don't encrypt anything 1 - Inject shellcode and start fluctuating its memory with standard PAGE_READWRITE. 2 - Inject shellcode and start fluctuating its memory with ORCA666's PAGE_NOACCESS.
Где:
- `<shellcode>` — путь к файлу shellcode
- `<fluctuate>`, как описано выше, принимает `-1`, `0` или `1`
Пример запуска, который подделывает стек вызовов потока beacon:```
C:\> ShellcodeFluctuation.exe ..\..\tests\beacon64.bin 1
[.] Reading shellcode bytes...
[.] Hooking kernel32!Sleep...
[.] Injecting shellcode...
[+] Shellcode is now running. PID = 9456
[+] Fluctuation initialized.
Shellcode resides at 0x000002210C091000 and occupies 176128 bytes. XOR32 key: 0x1e602f0d
[>] Flipped to RW. Encoding...
===> MySleep(5000)
[.] Decoding...
[>] Flipped to RX.
[>] Flipped to RW. Encoding...
===> MySleep(5000)
Если вы планируете добавить эту функциональность в свои собственные загрузчики шеллкода / инструменты, обязательно ИЗБЕГАЙТЕ снятия хуков с kernel32.dll.
Попытка снять хуки с kernel32 восстановит исходную функциональность Sleep, что предотвратит вызов нашего колбэка.
Если наш колбэк не будет вызван, поток не сможет самостоятельно подделать свой собственный стек вызовов.
Если вам нужно именно это, то вам, возможно, придётся запустить дополнительный сторожевой поток, который будет гарантировать, что поток Beacon будет получать поддельный стек всякий раз, когда он засыпает.
Если вы используете Cobalt Strike и BOF unhook-bof от Raphael's Mudge, обязательно ознакомьтесь с моим Pull Request, который добавляет в BOF необязательный параметр, указывающий библиотеки, с которых не следует снимать хуки.
Таким образом вы сможете сохранить свои хуки в kernel32:``` beacon> unhook kernel32 [*] Running unhook. Will skip these modules: wmp.dll, kernel32.dll [+] host called home, sent: 9475 bytes [+] received output: ntdll.dll <.text> Unhook is done.
[Изменённый `unhook-bof` с опцией игнорирования указанных модулей](https://github.com/mgeeky/unhook-bof)
---
## Заключительное замечание
Этот PoC был разработан для работы с shellcode-кодами Beacon из Cobalt Strike. Известно, что Beacon обращается к `kernel32!Sleep`, чтобы ожидать дальнейших инструкций от своего C2.
Этот загрузчик использует этот факт, перехватывая `Sleep` для выполнения своих служебных задач.
Эта реализация может не работать с другими shellcode-кодами на рынке (такими как _Meterpreter_), если они не используют `Sleep` для приостановки.
Поскольку это всего лишь _Proof of Concept_, демонстрирующий технику, я не собираюсь добавлять поддержку каких-либо других C2-фреймворков.
Когда вы поймёте концепцию, вы, безусловно, сможете перенести её на требования вашего shellcode-кода и адаптировать решение под свои задачи.
Пожалуйста, не открывайте issues на Github, связанные с «этот код не работает с XYZ shellcode», — они будут немедленно закрыты.
---
### ☕ Выразить поддержку ☕
Этот и другие проекты — результат бессонных ночей и **большой упорной работы**. Если вам нравится то, что я делаю, и вы цените, что я всегда отдаю что-то сообществу,
[Подумайте о том, чтобы купить мне кофе](https://github.com/sponsors/mgeeky) _(или лучше пива)_, просто чтобы сказать спасибо! 💪
---
## Автор```
Mariusz Banach / mgeeky, 21
<mb [at] binary-offensive.com>
(https://github.com/mgeeky)
kernel32!SleepRX