typeanalysisfamily54e64econfidencemediumcreated2026-06-02updated2026-06-02pemalware-familyloaderdefense-evasionpackerupxevasion
SHA-256: c8db13c15ad99cc002dda644384e730497972a9995510918f5fc7e2c071b9a0f

54e64e: c8db13c1 — UPX-packed x64 Amadey dropper, compressed payload, standard unpacker defeated

Executive Summary

A 3.1 MB PE32+ x64 executable packed with a modified UPX loader that resists standard decompression. Only four KERNEL32 imports survive the packer (LoadLibraryA, GetProcAddress, VirtualProtect, ExitProcess); the real payload decompresses into the UPX0 section at runtime and is invisible to static string extraction. Co-labeled dropped-by-amadey by OpenCTI. No CAPE detonation available (no Windows guest). This is a mechanically distinct UPX-packed sibling of the 54e64e family, unrelated to the 5.2 MB null-padded "certpert" dropper (3b13b28c).

What It Is

  • File: PE32+ executable (GUI) x86-64, 3 sections, 3 282 432 bytes ^[file.txt]
  • Compiler: Unknown — compiled timestamp zeroed by UPX ^[pefile.txt]
  • Packing: UPX (three sections: UPX0, UPX1, UPX2) ^[pefile.txt] ^[yara.txt]
  • UPX compression type: Unconfirmed — standard UPX 4.2.2 reports CantUnpackException: file is possibly modified/hacked/protected ^[terminal]
  • Import table: Stripped to four KERNEL32.DLL functions via UPX import rebuild ^[pefile.txt:198-213]
    • LoadLibraryA, GetProcAddress, VirtualProtect, ExitProcess
  • Signing: Unsigned (rabin2 reports signed: false) ^[rabin2-info.txt]
  • Family: 54e64e (OpenCTI label); also tagged dropped-by-amadey ^[metadata.json]
  • Relation to prior sample: NOT the same build as 3b13b28c (5.2 MB MSVC C++ "certpert" with null padding and plaintext strings). This sample uses UPX compression instead of null-padding and has no visible plaintext indicators.

How It Works

UPX Packing — Modified/Hacked

The binary follows the standard UPX PE layout:

Section VirtualSize RawSize Entropy Purpose
UPX0 0x2627000 0x0 0.00 Uninitialized BSS — decompressed payload lands here at runtime
UPX1 0x322000 0x321200 7.92 Compressed payload + UPX decompressor stub
UPX2 0x1000 0x200 1.46 Rebuilt import table (4 entries)

^[pefile.txt:88-145]

The entry point (0x1402948f40) points into UPX1 and is a standard UPX decompression stub. Disassembly shows the characteristic NRV/LZMA bitstream decoder: call-based jump table, add ebx,ebx/adc bit extraction, and a repz ret idiom typical of UPX-generated epilogues. ^[r2:entry0]

The stub performs four major phases:

  1. Decompression: Reads compressed data from UPX1 and writes the uncompressed image into UPX0 (virtual address 0x140001000). ^[r2:0x142948f40]
  2. Import rebuilding: Once decompressed, it walks the embedded names and uses LoadLibraryA + GetProcAddress to repopulate the IAT entries in UPX2. ^[r2:0x1429490be]
  3. Reloc fixups: A relocation pass adds the image base to addresses stored in the decompressed image. ^[r2:0x14294911a]
  4. OEP transfer: After calling VirtualProtect to restore the correct page permissions, the stub adjusts the stack alignment and jumps to the original entry point at 0x1400886c0 (inside the newly decompressed UPX0 region). ^[r2:0x1429491b1]

Anti-Analysis: UPX Modification

Standard UPX 4.2.2 refuses to decompress the file:

upx: CantUnpackException: file is possibly modified/hacked/protected; take care!

^[terminal]

This is a common anti-analysis technique: the author packs with UPX, then modifies a magic byte, version field, or checksum so that the official UPX decompressor aborts. The binary decompresses correctly at runtime because the Windows loader simply executes the entry point stub, which contains its own decompressor code. Manual decompression would require either:

  • Forcing UPX with --force and repairing headers, or
  • Dumping the decompressed image from memory after the VirtualProtect + jmp OEP sequence.

Compressed-Payload Opacity

Because the actual code and strings reside inside the compressed UPX1 section, static extraction yields nothing:

  • No URLs, IPs, or C2 infrastructure visible in plaintext strings ^[strings.txt] ^[floss.txt]
  • No API strings beyond the four KERNEL32 imports ^[pefile.txt]
  • No PDB path, no version-info, no resource strings ^[binwalk.txt]
  • binwalk found only AES S-Boxes near the tail of UPX1, likely from the compressed payload rather than the dropper stub itself. ^[binwalk.txt]

No CAPE Detonation

dynamic-analysis.md explicitly states: skipped — no CAPE machine currently available for platform windows. ^[dynamic-analysis.md] No runtime IOCs are available.

Decompiled Behavior

Ghidra/r2 analysis is limited to the UPX stub. The decompiled entry point is the decompressor, not the payload. The notable control flow:

  • entry0 (0x142948f40) — UPX stub entry. Bitstream decoder, import resolver, reloc fixup, OEP jump. ^[r2:entry0]
  • After decompression and IAT rebuild, execution transfers to 0x1400886c0, which is inside UPX0 and therefore all nulls on disk. ^[r2:0x1429491b1]

Without a decompressed dump or dynamic trace, the payload's behavior is entirely opaque.

C2 Infrastructure

  • Static C2: None observable. All indicators are compressed inside UPX1.
  • Attribution: OpenCTI co-label dropped-by-amadey places this in the Amadey downloader delivery chain. The 3b13b28c sibling already showed HTTP payload fetch to 80.253.249.169:5000. This sample may stage a similar payload, but that is inference, not observation.

Interesting Tidbits

  • Modified UPX as the primary defence: Rather than null-padding or obfuscation, this sample's entire opsec relies on UPX compression + modification. Static tools see only the stub.
  • Sibling diversity in 54e64e: The cluster already contains at least two distinct build morphs: (1) the 5.2 MB MSVC C++ null-padded "certpert" dropper with plaintext strings and Defender-exclusion powershell, and (2) this 3.1 MB UPX-packed x64 dropper with a compressed payload. Both share the dropped-by-amadey label.
  • AES S-Boxes in compressed payload: binwalk located AES S-Box tables at file offsets ~0x2F4357 and ~0x2F4479 (inside UPX1). These likely belong to the decompressed payload, suggesting the second stage may use AES for config or traffic encryption. ^[binwalk.txt]
  • UPX0 zero-size on disk: UPX0 has SizeOfRawData == 0, meaning the decompressed image is not present in the file at all — it is built purely in memory. ^[pefile.txt:93-95]

How To Mess With It (Homelab Replication)

Goal: Observe the decompressed payload without executing the binary.

  1. Dump from a Windows VM:

    • Launch the binary under x64dbg.
    • Set a breakpoint on VirtualProtect (the second call, after permissions are flipped).
    • When hit, the OEP jump is imminent. Dump the UPX0 memory region (base 0x140001000, size 0x2627000) to disk.
    • Fix the PE headers (section table, EntryPoint = 0x886c0) and analyse with r2/Ghidra.
  2. Force UPX decompression (destructive, make a copy):

    • upx -df --force-overwrite -o unpacked.bin sample.bin
    • If UPX 4.2.2 still refuses, try older UPX versions (3.96) or Quarkus UPX dumpers.
  3. Alternative: unpack manually via r2:

    • Set a breakpoint at the jmp OEP (offset 0x1429491b1 virtual).
    • Dump memory at 0x140001000 for 0x2627000 bytes, then fix section raw-sizes to match virtual sizes.

Deployable Signatures

YARA

rule upx_packed_54e64e_amadey {
    meta:
        description = "UPX-packed 54e64e / dropped-by-amadey sibling"
        author = "Titus / PacketPursuit"
        date = "2026-06-02"
        sha256 = "c8db13c15ad99cc002dda644384e730497972a9995510918f5fc7e2c071b9a0f"
    strings:
        $upx0 = "UPX0" ascii
        $upx1 = "UPX1" ascii
        $upx2 = "UPX2" ascii
        $mz = { 4d 5a }
    condition:
        uint16(0) == 0x5A4D and
        all of ($upx*) and
        pe.number_of_sections == 3 and
        pe.sections[0].name == "UPX0" and
        pe.sections[0].raw_data_size == 0 and
        pe.sections[1].name == "UPX1" and
        pe.sections[2].name == "UPX2" and
        pe.machine == pe.MACHINE_AMD64 and
        for any i in (0 .. pe.number_of_sections - 1): (
            pe.sections[i].name == "UPX1" and
            pe.sections[i].virtual_size > 0x300000
        )
}

Behavioral Fingerprint

This family exhibits two distinct build morphs: (1) oversized MSVC C++ PE binaries padded with null bytes containing plaintext strings, Defender-exclusion PowerShell commands, and hardcoded HTTP payload URLs; and (2) UPX-packed x64 PEs with three sections (UPX0/UPX1/UPX2), minimal KERNEL32 import table, and compressed payloads that resist standard UPX decompression. Both morphs are co-labeled dropped-by-amadey by OpenCTI/MalwareBazaar. Detection should focus on the cluster's macro-behaviour (admin gate, HTTP payload fetch, Defender tampering) rather than static strings when the UPX variant is encountered.

IOC List

Type Value Context
SHA-256 c8db13c15ad99cc002dda644384e730497972a9995510918f5fc7e2c071b9a0f UPX-packed dropper
Family label 54e64e OpenCTI / MalwareBazaar
Co-label dropped-by-amadey Delivery-chain tag (Amadey downloader)
Packer Modified UPX (3 sections, UPX0 raw_size=0) Build trait
Imports KERNEL32.DLL: LoadLibraryA, GetProcAddress, VirtualProtect, ExitProcess Rebuilt IAT
Observed OEP 0x1400886c0 Post-decompression entry point
Compressed payload indicators AES S-Box tables at offsets ~0x2F4357 and ~0x2F4479 Inside UPX1; likely second-stage encryption
URLs/IPs None visible statically Compressed inside UPX1
Registry/Mutex None observed

Detection Signatures

Mapped MITRE ATT&CK (static inference; dynamic unconfirmed):

Technique ID Evidence
Obfuscated Files or Information T1027.002 UPX-packed payload with modified/hacked header
Deobfuscate/Decode Files or Information T1140 Runtime decompression via UPX stub into UPX0
Ingress Tool Transfer T1105 Inferred (Amadey dropper pedigree)
User Execution T1204.002 User-launched PE GUI executable

References

  • Artifact ID: b9b59b41-4af9-4933-9ea0-b81ced71613b (OpenCTI)
  • MalwareBazaar / abuse.ch family label: 54e64e
  • Co-label: dropped-by-amadey
  • Related wiki pages: 54e64e

Provenance

Analysis performed 2026-06-02 on pp-hermes. Tools: radare2 (level-2 auto-analysis), pefile, ExifTool, binwalk, strings, UPX 4.2.2, custom Python probes. Ghidra import was not attempted because the raw file contains only a decompressor stub; meaningful decompilation requires a memory dump or successful decompression. CAPE sandbox unavailable for this platform. All claims trace to the tool outputs cited with ^[...] markers. The 54e64e family attribution derives from OpenCTI labels; confidence is medium because the label is an opaque cluster identifier and the build morphs within it are heterogeneous.