
Beacon Object File für Cobalt Strike, das .NET-Assemblys im Beacon mit Umgehungstechniken ausführt.
Beacon-Objekt-Datei für Cobalt Strike, die .NET-Assemblies im Beacon mit Umgehungstechniken ausführt.
┌──────────────────────────────────────────────────────────────────────────────┐
│ Cobalt Strike Beacon │
│ (Parent Process) │
└──────────────────────────────────┬───────────────────────────────────────────┘
│
│ beacon_inline_execute()
│ - Gepackte Argumente parsen
│ - go() aufrufen
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ BOF Execute-Assembly Entry (go) │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Konfigurationsparsing │ │
│ │ • ProxyMethod (None/Draugr/Timer/RegWait) │ │
│ │ • AmsiEvasion (None/Patch/HWBP) │ │
│ │ • EtwEvasion (None/Patch) │ │
│ │ • PipeName, AppDomainName, Assembly-Bytes, Argumente │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ Framework-Initialisierung │ │
│ │ • InitVxTable() - Syscall-Nummern auflösen │ │
│ │ └─> NtProtectVirtualMemory, NtContinue, NtCreateEvent, │ │
│ │ NtSetEvent, NtWaitForSingleObject, NtClose │ │
│ │ • DraugrInit() - Synthetische Stack-Frames einrichten │ │
│ │ └─> RtlUserThreadStart, BaseThreadInitThunk lokalisieren │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ DLL-Laden (ProxyLoadLibraryA) │ │
│ │ • amsi.dll, OleAut32.dll, mscoree.dll, User32.dll │ │
│ │ │ │
│ │ PROXY_NONE: LoadLibraryA() direkt │ │
│ │ PROXY_DRAUGR: DRAUGR_API(LoadLibraryA) - gespoofter Stack │ │
│ │ PROXY_TIMER: CreateTimerQueue → Timer-Rückruf │ │
│ │ PROXY_REGWAIT: RegisterWaitForSingleObject → Event-Rückruf │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ AMSI-Umgehung einrichten │ │
│ │ │ │
│ │ AMSI_PATCH: AMSI_HWBP: │ │
│ │ ┌─────────────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ 1. 4 Bytes sichern │ │ 1. VEH-Handler hinzufügen │ │ │
│ │ │ 2. NtProtectVirtualMem │ │ 2. RtlCaptureContext │ │ │
│ │ │ (RW) │ │ 3. DR0 = AmsiScanBuffer │ │ │
│ │ │ 3. Schreiben: │ │ 4. DR7-Breakpoint aktivieren │ │ │
│ │ │ 48 31 C0 xor rax,rax│ │ 5. NtContinue (Kontext anw.) │ │ │
│ │ │ C3 ret │ │ │ │ │
│ │ │ 4. NtProtectVirtualMem │ │ Bei Aufruf von AmsiScanBuffer:│ │ │
│ │ │ (wiederherstellen) │ │ → #BP-Exception │ │ │
│ │ └─────────────────────────┘ │ → VEH leitet zu RET um │ │ │
│ │ │ → RAX = 0 │ │ │
│ │ └──────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ ETW-Umgehung (falls aktiviert) │ │
│ │ • NtProtectVirtualMemory(NtTraceEvent, RW) │ │
│ │ • 4 Bytes sichern │ │
│ │ • Schreiben: 48 31 C0 C3 (xor rax,rax; ret) │ │
│ │ • NtProtectVirtualMemory(Schutz wiederherstellen) │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ Ausgabeumleitung einrichten │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. CreateNamedPipeW(\\.\pipe\{CustomName}) → hPipe │ │ │
│ │ │ 2. CreateFileW(Pipe-Pfad) → hFile │ │ │
│ │ │ 3. AllocConsole() + ShowWindow(SW_HIDE) → Versteckte Konsole │ │ │
│ │ │ 4. PEB-Manipulation: │ │ │
│ │ │ • Sichern: hCurrentStdOut = PEB->ProcessParameters->StdOut │ │ │
│ │ │ • Sichern: hCurrentStdErr = PEB->ProcessParameters->StdErr │ │ │
│ │ │ • Umleiten: PEB->StdOut = hFile │ │ │
│ │ │ • Umleiten: PEB->StdErr = hFile │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ CLR-Hosting & Assembly-Ausführung (ExecuteAssembly) │ │
│ │ ┌──────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. CLR-Versionserkennung │ │ │
│ │ │ • Assembly-Bytes auf "v2.0.50727" oder "v4.0.30319" prüfen │ │ │
│ │ │ 2. CLR-Initialisierung │ │ │
│ │ │ • CLRCreateInstance → ICLRMetaHost │ │ │
│ │ │ • GetRuntime(v2/v4) → ICLRRuntimeInfo │ │ │
│ │ │ • GetInterface → ICorRuntimeHost │ │ │
│ │ │ • Start() │ │ │
│ │ │ 3. AppDomain-Verwaltung │ │ │
│ │ │ • GetDefaultDomain() → Standard-AppDomain │ │ │
│ │ │ • CreateDomain(CustomName) → Isolierte AppDomain │ │ │
│ │ │ 4. Assembly laden │ │ │
│ │ │ • SAFEARRAY (VT_UI1) mit Assembly-Bytes erstellen │ │ │
│ │ │ • SafeArrayAccessData → Assembly in Safe Array kopieren │ │ │
│ │ │ • CustomAppDomain->Load_3(safearray) → Im Speicher laden │ │ │
│ │ │ 5. Argumente vorbereiten │ │ │
│ │ │ • Leerzeichen-getrennte Argumente parsen │ │ │
│ │ │ • SAFEARRAY(VT_BSTR) für jedes Argument erstellen │ │ │
│ │ │ • In VARIANT-Struktur verpacken │ │ │
│ │ │ 6. Ausführung │ │ │
│ │ │ • Assembly->EntryPoint() → Main()-MethodInfo holen │ │ │
│ │ │ • MethodInfo->Invoke_3(arguments) → Ausführen │ │ │
│ │ │ └─> Assembly schreibt auf Konsole │ │ │
│ │ │ └─> Umgeleitet zu hFile → Named Pipe │ │ │
│ │ │ 7. Bereinigung │ │ │
│ │ │ • COM-Schnittstellen freigeben (MethodInfo, Assembly, usw.) │ │ │
│ │ │ • UnloadDomain(CustomAppDomain) → Vollständiges Entladen │ │ │
│ │ │ • FreeLibrary(mscoree.dll) │ │ │
│ │ └──────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ Ausgabeerfassung & -anzeige │ │
│ │ • PEB wiederherstellen: StdOut/StdErr = ursprüngliche Handles │ │
│ │ • Puffer zuweisen (0x10000 Bytes) │ │
│ │ • ReadFile(hPipe) → Assembly-Ausgabe erfassen │ │
│ │ • BeaconPrintf(CALLBACK_OUTPUT, output) → Operator anzeigen │ │
│ └────────────────────────────────┬───────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────▼───────────────────────────────────────┐ │
│ │ Bereinigung & Wiederherstellung │ │
│ │ • free(pAssemblyStdOut) │ │
│ │ • NtClose(hFile, hPipe) │ │
│ │ • FreeConsole() │ │
│ │ │ │
│ │ if (AMSI_PATCH): │ │
│ │ • RestoreAmsi() - Originale 4 Bytes zurückschreiben │ │
│ │ │ │
│ │ if (AMSI_HWBP): │ │
│ │ • RemoveHwbp() - Debug-Register löschen │ │
│ │ • RemoveVectoredExceptionHandler(VehHandler) │ │
│ │ │ │
│ │ if (ETW_PATCH): │ │
│ │ • RestoreEtw() - Originale 4 Bytes zurückschreiben │ │
│ │ │ │
│ │ • PEB wiederherstellen: StdOut/StdErr = original │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
│
│ Rückkehr zum Beacon
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Beacon setzt Ausführung fort │
│ (BOF-Speicher bereinigt) │
└──────────────────────────────────────────────────────────────────────────────┘
| Methode | Beschreibung |
|---|---|
None | Direkte API-Aufrufe |
Draugr | Stack-gespoofte API-Aufrufe via Draugr |
| Methode | Beschreibung |
|---|---|
None | Kein AMSI-Bypass |
Patch | Speicher-Patch von AMSI!AmsiScanBuffer (xor rax,rax; ret) |
HWBP | Hardware-Breakpoint-Hook auf AMSI!AmsiScanBuffer via VEH |
| Methode | Beschreibung |
|---|---|
None | Kein ETW-Bypass |
Patch | Speicher-Patch von NTDLL!NtTraceEvent (xor rax,rax; ret) |
| Parameter | Beschreibung | Beispiel |
|---|---|---|
| PipeName | Name der Named Pipe zur Erfassung der Assembly-Ausgabe | P1p3N4m3 |
| AppDomain | Benutzerdefinierter .NET-AppDomain-Name für Assembly-Isolation | Tot4lL3g1t |
LoadLibraryA("amsi.dll") → Direkter Aufruf
DRAUGR_API(LoadLibraryA, "amsi.dll")
│
├─ Synthetischer Stack-Aufbau
├─ Rückadressen-Spoofing
└─ Indirekte Ausführung
CreateTimerQueue() → CreateTimerQueueTimer(
callback = LoadLibraryA,
parameter = "amsi.dll",
dueTime = 100ms
) → Warten → DeleteTimerQueueEx()
CreateEvent() → RegisterWaitForSingleObject(
event,
callback = LoadLibraryA,
context = "amsi.dll"
) → SetEvent() → UnregisterWait()
Vor dem Patch: Nach dem Patch:
AmsiScanBuffer: AmsiScanBuffer:
4C 8B DC mov r11, rsp 48 31 C0 xor rax, rax
49 89 5B 08 mov [r11+8], rbx C3 ret
... ...
Ergebnis: Alle Scans geben S_OK zurück (sauber)
Methode:
xor rax, rax; ret schreibenEinrichtung:
1. AddVectoredExceptionHandler
2. RtlCaptureContext
3. DR0 = AmsiScanBuffer-Adresse setzen
4. DR7-Breakpoint-Flag aktivieren
5. NtContinue (Kontext anwenden)
Ausführungsablauf:
AmsiScanBuffer aufgerufen
│
▼
#BP-Exception (EXCEPTION_SINGLE_STEP)
│
▼
VEH-Handler fängt ab
│
├─ Überprüfen, ob RIP == AmsiScanBuffer
├─ RIP = FindRetInstruction(AmsiScanBuffer) setzen
├─ RAX = 0 (S_OK) setzen
└─ TF (Trap-Flag) setzen
│
▼
Rückkehr mit RAX=0
Vorher: Nachher:
NtTraceEvent: NtTraceEvent:
4C 8B D1 mov r10, rcx 48 31 C0 xor rax, rax
B8 XX XX mov eax, syscall C3 ret
Standard-Assembly (ohne BOF): BOF Execute-Assembly:
Assembly → Console.WriteLine 1. \\.\pipe\{name} erstellen
│ │
▼ ▼
Ausgabe verloren 2. Pipe als Datei-Handle öffnen
│
▼
3. PEB-Handles umleiten:
• StdOut → Pipe
• StdErr → Pipe
│
▼
4. Assembly ausführen
│
▼
5. ReadFile(Pipe)
│
▼
6. BeaconPrintf → Operator
Speicherschutzänderungen:
NtProtectVirtualMemory-Aufrufe werden via EtwTiLogReadWriteVm protokolliert.text-Abschnitt von amsi.dll.text-Abschnitt von ntdll.dllErkennung: Speicherschutzänderungen an geladenen Modulen sind starke Indikatoren.
Named Pipe-Erstellung:
NtCreateFile mit \\.\pipe\*-Pfad für Minifilter-Treiber sichtbarModulladung:
LdrLoadDll-Ereignisse werden von EDR-Kernel-Treibern protokolliertThread-Kontext-Manipulation (HWBP-Methode):
AllocConsole + ShowWindow(SW_HIDE))Cobalt Strike → Script Manager → Laden → BOF_ExecuteAssembly.cna
Menü: Additions postex → Execute-Assembly Config

BOF_ExecuteAssembly --assembly /tmp/Ghostpack-CompiledBinaries/Rubeus.exe --args help

beacon> help BOF_ExecuteAssembl

Mit Dockerfile:
sudo docker build -t ubuntu-gcc-13 .
sudo docker run --rm -it -v "$PWD":/work -w /work ubuntu-gcc-13:latest make
Oder falls auf Ihrem System nasm, make und mingw-w64 (kompatibel mit gcc-13) installiert sind:
make
Ausgabe: Bin/BOF_ExecuteAssembly.o
Regwait |
| Ausführung via RegisterWaitForSingleObject-Rückruf |
Timer | Ausführung via Timer-Queue-Rückruf |
| Technik | Umgeht |
|---|
| Indirekte Syscalls | Userland-API-Hooks (EDR/AV) |
| Draugr-Stack-Spoofing | Call-Stack-Inspektionstools |
| AMSI-Patch/HWBP | .NET-Assembly-Scanning |
| ETW-Patching | Ereignisbasierte Überwachung |
| Proxy-DLL-Laden | LoadLibrary-Stackframe-Überwachung |
| Anpassbare Named Pipe | Pipe-Überwachung |
| Benutzerdefinierte AppDomain | Überwachung der Standard-AppDomain |