typeanalysisfamilysilverfoxconfidencehighcreated2026-05-29updated2026-05-29pecompilerobfuscationevasiondefense-evasionmitre-attckmalware-familyloader
SHA-256: ed1a00479fe2ea2555882c67719abc86e98b512f122aea79adacf37355cab996

silverfox: ed1a0047 — Rust x64 dropper with LZSS payload extraction and ntdll unhooking

Executive Summary

A 150 KB Rust-compiled x64 PE dropper masquerading as a Chinese-language PDF. The binary embeds multiple encrypted/compressed payloads in PE .rsrc, extracts them with a custom stream cipher seeded by runtime image hash, decompresses via an LZSS-like algorithm, then injects the decrypted payload into a suspended clone of itself using process hollowing. Prior to injection it restores ntdll .text from disk to evade user-mode EDR hooks. No CAPE detonation available (no x64 Windows guest); all behavior inferred from static reversing.

What It Is

Attribute Value
SHA-256 ed1a00479fe2ea2555882c67719abc86e98b512f122aea79adacf37355cab996
File on disk 2026年 第一季度违纪违规人员名单pdf.exe
Type PE32+ executable (GUI) x86-64^[file.txt]
Size 150 528 bytes
Compiler Rust (rustc) — evidenced by .buildid section and standard library linkage^[pefile.txt:117]
Linker MSVC 14.0 (LinkerVersion: 14.0)^[pefile.txt:45]
Stripped Yes (external PDB, debug stripped)^[pefile.txt:39]
Signed No, unsigned^[rabin2-info.txt:27]
Timestamp Fri 2026-05-22 01:10:24 UTC (fabricated or recent)^[pefile.txt:34]
VersionInfo Randomized: JUjflFmsWFlu.exe, CompanyName=JECIHgAuilWg^[pefile.txt:321]

The OpenCTI labels on this artifact include both silverfox and valleyrat^[triage.json:11] — these may be overlapping campaigns or dual-track attribution. The codebase is Rust with heavy use of unsafe NT APIs via ntdll and hand-rolled crypto; the build is stripped and uses randomized version strings.^[pefile.txt:321]

How It Works

1. Entry point decoy & real start

entry() calls Sleep(0) and immediately exits.^[ghidra:entry] The true initializer is FUN_140001030() (the Rust main or a pre-main hook) which builds obfuscated strings on the stack, resolves APIs dynamically, and begins the payload chain.

2. Anti-analysis: library & process check

The binary builds a DLL name on the stack via FUN_140004ee0() and loads it.^[ghidra:FUN_140001030:90] If the library is absent or a queried export returns 0, it triggers ShellExecuteExW() followed by ExitProcess()^[ghidra:FUN_140001030:95], suggesting an environment-gate that either detects the wrong sandbox or attempts to elevate/re-launch.

3. Process enumeration & target selection

CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS) walks processes looking for a specific module name.^[ghidra:FUN_140001030:120] If found, the target PID is harvested for later injection.

4. Resource extraction (encrypted payload)

Embedded resources in .rsrc are accessed by numeric IDs (200, 51, 103, 107, 129, 67, 198, 150, 27)^[pefile.txt:438] — no FindResourceA/LoadResourceA imports are visible in the static IAT because the binary maps its own PE sections directly via CreateFileMappingW + MapViewOfFile and parses the resource directory manually.^[ghidra:FUN_140001030:1800]

5. Decryption stream cipher

A custom stream cipher is seeded with a key derived from bVar4 ^ 0xcaaafe23, runs a non-linear transformation over each byte using constants 0x3d57aa23, 0x44d9bb23, and 0x9e37cb23^[ghidra:FUN_140001030:1250], then XORs the payload in place. The same routine feeds a decompression pass.

6. Decompression (LZSS-like)

FUN_140004000() implements an LZSS decompressor^[ghidra:FUN_140004000]: bit-packed literal/copy flags, 12-bit back-reference length/offset fields, and sliding-window copy loops using FUN_1400065c0(). The compressed payload appears to sit inside .rsrc resources immediately following an initial length byte.

7. EDR evasion: restore ntdll .text from disk

Before injection, the binary:

  1. Resolves its own ntdll base via GetModuleHandleW("ntdll").^[ghidra:FUN_140001030:1520]
  2. Opens System32\ntdll.dll read-only via CreateFileW.^[ghidra:FUN_140001030:1530]
  3. Maps it with CreateFileMappingW + MapViewOfFile.^[ghidra:FUN_140001030:1540]
  4. Walks the in-memory PE header and matches each section by name hash against the disk-mapped copy.
  5. For any matching .text section, it VirtualProtects the in-memory page to PAGE_EXECUTE_READWRITE (0x40), copies the on-disk bytes over it, then reverts protection.^[ghidra:FUN_140001030:1580]

This is a direct unhooking of EDR user-mode patches in ntdll.

8. Injection — process hollowing into self

If the earlier target PID lookup succeeded:

  • NtOpenProcess with access mask 0x478 (PROCESS_VM_OPERATION | READ | WRITE | QUERY_INFORMATION).^[ghidra:FUN_140001030:1650]
  • NtAllocateVirtualMemory in the remote process for decrypted payload.
  • NtWriteVirtualMemory to deposit the PE.
  • If the process handle can be duplicated and a named pipe handle discovered via NtQuerySystemInformation(SystemHandleInformation, 0x10), the binary uses DuplicateHandle and either calls NtAlertThread or ZwSetIoCompletion to trigger execution.^[ghidra:FUN_140001030:1720]

If no suitable target exists, it falls back to hollowing itself:

  • GetModuleFileNameW to obtain its own path.^[ghidra:FUN_140001030:1850]
  • CreateProcessW with CREATE_SUSPENDED (0x8000000) and no new window.^[ghidra:FUN_140001030:1860]
  • Writes the decrypted PE into the suspended child and exits the parent.

9. Self-deletion

On failure of all injection paths, it self-destructs using DeleteFileW, MoveFileW, and MoveFileExW with MOVEFILE_DELAY_UNTIL_REBOOT.^[ghidra:FUN_140001030:1900]

Decompiled Behavior

Entry point (entry): decoy — Sleep(0), RtlExitUserProcess.^[ghidra:entry]

Notable functions (called from FUN_140001030):

  • FUN_140006280 — optimized memset (byte then qword loop).^[ghidra:FUN_140006280]
  • FUN_140004000 — LZSS decompressor with sliding window and length/offset bit parsing.^[ghidra:FUN_140004000]
  • FUN_140004ee0 / FUN_140004ae0 / FUN_140004530 — stack-based string builders / decoders.
  • FUN_1400038c0 — enables SeDebugPrivilege via AdjustTokenPrivileges.^[ghidra:FUN_1400038c0]
  • FUN_140003f80 — mirrors FUN_1400038c0; appears to be a duplicate privilege-escalation stub.

Control flow patterns:

  • Heavy do { ... } while loops for bitstream parsing in decompression.
  • Repeated Sleep(1000) retry loops (10 iterations) when a target process handle cannot be obtained.^[ghidra:FUN_140001030:1100]

C2 Infrastructure

  • No hardcoded IPs, domains, or URLs found in strings or .rsrc.^[strings.txt]
  • Communication appears to be runtime-resolved via named pipe discovered through handle enumeration (NtQuerySystemInformation + NtQueryObject(ObjectNameInformation)).^[ghidra:FUN_140001030:1700]
  • The pipe name is compared via lstrcmpiW against a dynamically built string, so the pipe name itself is not static.
  • No PCAP available (CAPE skipped).

Mutex / named objects: None found statically.

Interesting Tidbits

  • Rust .buildid section is present (28 bytes, entropy 0.1) with a truncated compiler-generated hash 5db6a0e5.^[pefile.txt:117] This is a reliable Rust-PE detection heuristic.
  • VersionInfo randomization uses 11-character gibberish strings (JECIHgAuilWg) — likely auto-generated per build.^[pefile.txt:321]
  • No TLS callbacks are visible (.tls size 0x10 and zeroed).^[pefile.txt:177]
  • High-entropy .rsrc (7.66) with 10 anonymous numeric resource IDs but no RT_STRING or RT_DIALOG — consistent with encrypted blob storage.^[pefile.txt:198]
  • Import table is tiny — only 47 imports across 5 DLLs — the rest resolved at runtime via GetProcAddress on stack-decrypted names.^[pefile.txt:341]
  • Padding artifact: PADDINGXXPADDING... string at offset ~0xA8C0 into .text suggests the PE was padded or post-processed after linking.^[strings.txt:168]

How To Mess With It (Homelab Replication)

Goal: replicate a dropper that extracts an embedded resource, decrypts it with a custom stream cipher, decompresses LZSS, and hollows itself.

  1. Toolchain: Rust stable (x86_64-pc-windows-msvc), cargo build --release, add #![windows_subsystem = "windows"].
  2. Embed payload: use include_bytes!() or a custom build script to AES/RC4 encrypt a DLL and store it as a .rsrc blob.
  3. LZSS: implement a simple LZ77 with 12-bit length/offset fields (matching FUN_140004000 structure).
  4. Privilege: call OpenProcessToken + LookupPrivilegeValueW("SeDebugPrivilege") + AdjustTokenPrivileges.
  5. Injection: CreateProcessW(self_path, CREATE_SUSPENDED)NtUnmapViewOfSection (or VirtualAllocEx) → WriteProcessMemoryNtResumeThread.
  6. ntdll unhooking: read C:\Windows\System32\ntdll.dll, walk sections, VirtualProtect + overwrite .text from disk.

Verification: run under x64dbg with ScyllaHide disabled; confirm suspended child process creation and memory write to child at offset matching PE ImageBase.

Deployable Signatures

YARA rule

rule SilverFox_Rust_x64_Build {
    meta:
        author = "PacketPursuit"
        date = "2026-05-29"
        description = "SilverFox Rust x64 dropper with .buildid section and resource extraction"
        sha256 = "ed1a00479fe2ea2555882c67719abc86e98b512f122aea79adacf37355cab996"
    strings:
        // .buildid section header in PE section table
        $buildid = ".buildid"
        // Custom crypto constants observed in decrypted payload routine
        $k1 = { 23 FE AA CA }
        $k2 = { 23 AA 57 3D }
        $k3 = { 23 BB D9 44 }
        $k4 = { 23 CB 37 9E }
        // Padding artifact
        $pad = "PADDINGXXPADDING"
    condition:
        uint16(0) == 0x5A4D and
        filesize < 500KB and
        uint16(uint32(0x3c)+0x14) == 0xF0 and      // PE32+
        $buildid and
        2 of ($k1, $k2, $k3, $k4) and
        $pad
}

Behavioral hunt query (Splunk/EQL)

(process where
  (event.type:"start" or event.action:"Process Create") and
  process.parent.executable == process.executable and
  process.command_line_length == 0 and
  process.startup_info.dwFlags : "1"
)
or
(file where
  file.path : "C:\\Windows\\System32\\ntdll.dll" and
  event.action:"Open" and
  process.name : "*.exe"
)

IOC list

Type Indicator Note
Hash ed1a00479fe2ea2555882c67719abc86e98b512f122aea79adacf37355cab996 SHA-256
Hash 9eac33d20fe68f7700ff55e8dd63b032f64621fe6e8d3e4b94dd7e737c6c5272 .text section MD5
File name 2026年 第一季度违纪违规人员名单pdf.exe Original lure
Version string JUjflFmsWFlu.exe Randomized per build
Constants 0xcaaafe23, 0x3d57aa23, 0x44d9bb23, 0x9e37cb23 Stream cipher seed + round constants
Imports NtAllocateVirtualMemory, NtWriteVirtualMemory, NtQuerySystemInformation, NtQueryObject, ZwSetIoCompletion Native API set
Registry None observed
Mutex/Pipe Runtime-resolved Discovered via NtQuerySystemInformation(SystemHandleInformation)

Behavioral fingerprint

This executable is a stripped x64 Rust PE with a .buildid section and randomized VersionInfo strings. On launch it resolves ntdll and kernel32 APIs dynamically from stack-decrypted names, enables SeDebugPrivilege, then enumerates processes via CreateToolhelp32Snapshot. It extracts an encrypted payload from its own PE resources, decrypts it with a stream cipher seeded by image-derived entropy, decompresses via LZSS, and either injects the resulting PE into a discovered target process using NtAllocateVirtualMemory/NtWriteVirtualMemory or hollows a suspended clone of itself. Prior to injection, it restores ntdll .text from disk to remove EDR user-mode hooks.

Detection Signatures

MITRE ATT&CK ID Technique Evidence
T1055 Process Injection NtAllocateVirtualMemory, NtWriteVirtualMemory, suspended child process^[ghidra:FUN_140001030:1650]
T1055.012 Process Hollowing Self-CreateProcessW with CREATE_SUSPENDED, memory write, no command line^[ghidra:FUN_140001030:1850]
T1562.001 Impair Defenses: Disable or Modify Tools ntdll .text restoration from disk via VirtualProtect + copy^[ghidra:FUN_140001030:1580]
T1497.001 Virtualization/Sandbox Evasion: System Checks Initial library load + export call gate that exits if 0^[ghidra:FUN_140001030:90]
T1070.004 File Deletion MoveFileExW with MOVEFILE_DELAY_UNTIL_REBOOT for self-deletion^[ghidra:FUN_140001030:1900]
T1027.002 Obfuscated Files or Information: Software Packing LZSS-like decompression + custom stream cipher^[ghidra:FUN_140004000]
T1106 Native API Direct Nt* syscall wrappers used throughout; no VirtualAllocEx for remote allocation^[ghidra:FUN_140001030:1650]

References

  • Artifact ID: f8697ba9-64a6-4eb1-864c-42558acdb22b (OpenCTI)
  • MalwareBazaar entry: search ed1a00479fe2ea2555882c67719abc86e98b512f122aea79adacf37355cab996
  • OpenCTI labels: silverfox, valleyrat, trojan/silverfox.bg[qtsc]
  • Related wiki: silverfox

Provenance

Source Tool Version
file.txt file 5.45
exiftool.json exiftool 12.76
pefile.txt pefile (Python) 2024.8.26
strings.txt strings binutils 2.42
rabin2-info.txt radare2 5.9.8
capa.txt capa — (signature path error; skipped)
binwalk.txt binwalk 2.4.2
Decompilation Ghidra + r2/pdc Ghidra 11.2.1, radare2 5.9.8
dynamic-analysis.md CAPE skipped (no x64 guest)