tags: note - malware-analysis - windows
Советы для исследования .NET малвари
У нас есть малварь, которая декодирует ресурс DE.
С помощью скрипта stego можно расшифровать картинку.
Результат декодирования:
Также этот скрипт способен производить обратную операцию - превращать файл в стеганографическое изображение. Может использоваться в сценарии, когда вы пропатчили вредоносный файл, и хотите обратно закодировать его в картинку, чтобы малварь в процессе своей работы расшифровала ваш пропатченный бинарник. Не забудьте указать ширину и высоту картинки, как в оригинале.
Дамп пейлодов
- Часто .net малварь выполняет несколько стадий распаковки, задействуя несколько пейлодов. Чтобы просмотреть вредоносные пейлоды в памяти можно воспользоваться инстурментом ProcessHacker:
-
Дампнуть отдельные .NET Assembly из памяти можно тулзой ExtremeDumper. hollows_hunter может не видеть все пейлоды в памяти.
-
Также можно сделать дамп, поставив брейкпоинт на загрузку модулей в dnSpy. Способ показан в видео. Нам надо отобразить окно Модули.
Затем поставить “*” в имена отслеживаемых модулей.
Запускаем малварь и мы увидим все загружаемые модули.
Вызов отдельных методов
Способ первый
Малварь может содержать функции по расшифровки/декодированию строк или другие интересные функции, которые хочется вызвать отдельно (только их). Например, малварь содержит функцию по декодированию строки CausalitySource
Аргументы, которая она принимает:
Существует два удобных способа вызвать эту функцию с нужными аргументами. Первый - с помощью LinqPad
Linqpad - это легковесный исполнитель dotnet кода, которым удобно пользоваться в виртуалке. Можно скачивать 5 версию, так как .NET framework по умолчанию стоит в Windows 7. Теперь мы можем просто скопировать из образца код, вставить его в редактор и исполнить.
Необязательно писать полноценную программу с Main() и т.д., можно использовать просто куски кода, для этого в выпадающем меню выбираем Expression
или Statement
.
Например, исполнение отдельных строк кода:
Способ второй
Часто образец малвари в своих методах использует собственные константы, другие методы и т.д. и переносить кучу кода/переменные в Linqpad нерелевантно. Поэтому можно воспользоваться втором способом - powershell. Ничего устанавливать не нужно, powershell по умолчанию есть в Windows 7. Запускаем нужный экземпляр powershell, чтобы его разрядность совпадала с разрядностью образца. Первым делом нам необходимо подгрузить наш вредоносный Assembly командой:
$malware = [Reflection.Assembly]::LoadFile("path")
Также это можно сделать с помощью cmdlet (более современный способ). В этом случае файл должен иметь расширение .dll
:
Add-Type -Path foo.dll
Убедиться что операция завершена успешна поможет команда просмотра списка всех загруженных Assembly:
[appdomain]::currentdomain.GetAssemblies()
Чтобы вызвать конкретный, статический метод мы используем следующую конструкцию:
[имя_namespace.имя_класса]::имя_функции("аргументы")
Например:
Если метод НЕ статический, то в начале инстанцируем класс:
$instance = New-Object имя_namespace.имя_класса
$result = $instance.имя_функции()
echo $result
Пример вызова нестатического метода:
Полезные powershell команды
*ассембли (Assembly) - любой .NET EXE или DLL
Стандартный размер шрифта powershell очень мал. Увеличить его можно кликнув правой кнопкой на иконку - Свойства.
Очистка окна консоли:
Clear-Host
Если для каких-либо операций нам необходимо подгрузить зависимость:
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
Например, подгрузив зависимость Drawing
мы можем использовать класс Bitmap
:
$bmp = New-Object System.Drawing.Bitmap("D:\\path")
Вывод всех типов внутри бинарника:
$malware.gettypes()
Поиск по выводу какой-либо команды:
| Select-String -Pattern "строка_для_поиска"
Вывод только публичных классов:
$assembly.GetTypes() | ? {$_.IsClass -and $_.IsPublic}
Вывод всех текущих загруженных Assembly в отсортированном виде (также эта команда может пригодится или нет при форензике):
[appdomain]::currentdomain.getassemblies() | sort -property fullname | format-table fullname
Дамп захардкоженных переменных ассембли. Допустим у нас есть малварь, которая содержит некий статический байтовый блоб.
Если вы хотите дампнуть его, это можно сделать двумя способами. Динамически - запустить ассембли в дебаггере и дампнуть значение в рантайме. Статически - дампнуть, используя powershell. Чтобы дампнуть используя powershell мы подгружаем ассембли и сохраняем в файл желаемую переменную.
$malware = [Reflection.Assembly]::LoadFrom("path") # подгружаем наш вредоносный ассембли
[io.file]::WriteAllBytes('path_to_save_var', [namespace.class]::var_name) # сохраняем в файл
Пример:
Если требуемая переменная НЕстатическая, то просто создайте инстанс соответствующего класса.
Если вам лень постоянно вводить команды для дампа, то можно написать скрипт. Чтобы скрипт работал, не забудьте включить исполнение скриптов на системе:
set-executionpolicy remotesigned
Лайфхаки
- AgentTesla содержит пейлоды в виде ресурсов. Один из пейлодов является стеганографической картинкой. Если мы хотим пропатчить пейлод, который распаковывается на какой-либо стадии, нам необходимо:
- Преобразовать ресурс из картинки в файл
- Пропатчить
- Преобразовать файл обратно в картинку
- Заменить ресурс картинку В этом может помочь скрипт stego.
- Например, мы хотим написать YARA правило на кусок .NET кода.
- LinqPad поддерживает работу с файлами
- Иногда, .NET малварь может содержать GUID. Это поле идентифицрует конкретный проект на компе разработчика. Соответственно, если у нескольких отдельных образцов один и тот же GUID, то с уверенностью можно говорить, что они были разработаны на одном компе и скорей всего одним и тем же разработчиком.
- Если нам попался .NET Bundle, то дампнуть файлы бандла можно с помощью AsmResolver и powershell:
[Reflection.Assembly]::LoadFrom("C:\AsmResolver\AsmResolver.DotNet.dll") | Out-Null
$extractionPath = "C:\Extracted\"
$manifest = [AsmResolver.DotNet.Bundles.BundleManifest]::FromFile("C:\RiotClientServices.exe")
foreach($file in $manifest.Files)
{
$fileInfo = [IO.FileInfo]::new($extractionPath + $file.RelativePath)
$fileInfo.Directory.Create()
[IO.File]::WriteAllBytes($fileInfo.FullName, $file.getData($true))
}
- Библиотека AsmResolver, с помощью которой можно модифицировать .net бинарники. Например, удалять ненужные методы:
[Reflection.Assembly]::LoadFrom("C:\AsmResolver\AsmResolver.DotNet.dll") | Out-Null
$obfuscated = "C:\RiotClientServices.dll"
$moduleDef = [AsmResolver.DotNet.ModuleDefinition]::FromFile($obfuscated)
# Removing junk methods
foreach($type in $moduleDef.GetAllTypes())
{
foreach($method in [array]$type.Methods.Where{$_.HasMethodBody})
{
if(($method.MethodBody.Instructions.Where{$_.Opcode.Mnemonic -like "call" -and
$_.Operand.FullName -like "*System.Console::WriteLine*"}).count -eq 5)
{
$type.Methods.Remove($method) | Out-Null
}
}
}
-
Если надо отдебажить библиотеку .NET, то можно воспользоваться SharpDllLoader
-
Образцы можно группировать по TypeRef hash. Вычислить его поможет скрипт в гитлаб. TypeRef hash лучше использовать отсортированный и включая сущности, которые ссылаются сами на себя (sorted, include self-referenced entries).
-
Если функция расшифровки строк малвари вызывается много раз, можно написать следующий скрипт (пример):
$malware = [Reflection.Assembly]::LoadFile("C:\my\f.dll")
[jKcEiEe]::TFkR(980214767)| Out-File -FilePath c:\my\output.txt
[jKcEiEe]::TFkR(980214689) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214350) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214730) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214391) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214713) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214747) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214670) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214364) | Add-Content C:\my\output.txt
[jKcEiEe]::TFkR(980214543) | Add-Content C:\my\output.txt
- Иногда малварь для сокрытия пейлода использует Fody/Costura. Costura предназначена для добавления зависимостей/ресурсов в единный бинарник. Полезные ссылки по таким кейсам:
example-analysis-of-multi-component-malware
play-ransomware-volume-shadow-copy
- Получение данных о X509 сертификате кодом в LINQPAD. Также можно проверить валидность файла:
void Main()
{
string fileName = "c:\\my\\OUTLOOK.CFG";
string text2 = "test-908@testgmail-398504.iam.gserviceaccount.com";
X509Certificate2 x509 = new X509Certificate2(fileName, "notasecret", X509KeyStorageFlags.Exportable);
Console.WriteLine("{0}Subject: {1}{0}", Environment.NewLine, x509.Subject);
Console.WriteLine("{0}Issuer: {1}{0}", Environment.NewLine, x509.Issuer);
Console.WriteLine("{0}Version: {1}{0}", Environment.NewLine, x509.Version);
Console.WriteLine("{0}Valid Date: {1}{0}", Environment.NewLine, x509.NotBefore);
Console.WriteLine("{0}Expiry Date: {1}{0}", Environment.NewLine, x509.NotAfter);
Console.WriteLine("{0}Thumbprint: {1}{0}", Environment.NewLine, x509.Thumbprint);
Console.WriteLine("{0}Serial Number: {1}{0}", Environment.NewLine, x509.SerialNumber);
Console.WriteLine("{0}Friendly Name: {1}{0}", Environment.NewLine, x509.PublicKey.Oid.FriendlyName);
Console.WriteLine("{0}Public Key Format: {1}{0}", Environment.NewLine, x509.PublicKey.EncodedKeyValue.Format(true));
Console.WriteLine("{0}Raw Data Length: {1}{0}", Environment.NewLine, x509.RawData.Length);
Console.WriteLine("{0}Certificate to string: {1}{0}", Environment.NewLine, x509.ToString(true));
Console.WriteLine("{0}Certificate to XML String: {1}{0}", Environment.NewLine, x509.PublicKey.Key.ToXmlString(false));
}
// Define other methods and classes here
Быстрая разработка на .NET
Иногда необходимо по-быстрому полностью реализовать какой-нибудь алгоритм/написать код. Например, повторить кусок кода из малвари, который работает с файлами, открывает их, сохраняет и т.д..
- Скачиваем VSCode
- Устанавливаем два расширения: C# Dev Kit и C#
- Скачиваем dotnet sdk
- Чтобы убедиться, что dotnet установлен и работает корректно в командной строке выполняем команду
dotnet
- Открываем
VSCode
черезDeveloper Command Prompt
командойcode
- Создаем новый проект типа Console командой:
dotnet new console
- Если не хватает какой-либо зависимости:
dotnet add package System.Drawing.Common --version 7.0.0
- Собрать проект
dotnet clear
dotnet build
- Чтобы получить бинарник пригодный для запуска на другом компе (не забудьте указать нужную архитектуру):
dotnet publish -r win7-x64
Ссылки по этой теме:
https://learn.microsoft.com/en-us/dotnet/core/rid-catalog
https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build
https://learn.microsoft.com/en-us/dotnet/core/deploying/
Publishing your app as self-contained produces an application that includes the .NET runtime and libraries, and your application and its dependencies. Users of the application can run it on a machine that doesn’t have the .NET runtime installed.
- Чтобы бинарник работал на другой компе или в виртуалке, надо скопировать ВСЮ папку
bin\x64\Debug\net7.0\win7-x64\publish