c8db13c15ad99cc002dda644384e730497972a9995510918f5fc7e2c071b9a0f54e64e: 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 taggeddropped-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:
- Decompression: Reads compressed data from UPX1 and writes the uncompressed image into UPX0 (virtual address
0x140001000). ^[r2:0x142948f40] - Import rebuilding: Once decompressed, it walks the embedded names and uses
LoadLibraryA+GetProcAddressto repopulate the IAT entries in UPX2. ^[r2:0x1429490be] - Reloc fixups: A relocation pass adds the image base to addresses stored in the decompressed image. ^[r2:0x14294911a]
- OEP transfer: After calling
VirtualProtectto restore the correct page permissions, the stub adjusts the stack alignment and jumps to the original entry point at0x1400886c0(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
--forceand repairing headers, or - Dumping the decompressed image from memory after the
VirtualProtect+jmp OEPsequence.
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-amadeyplaces this in the Amadey downloader delivery chain. The 3b13b28c sibling already showed HTTP payload fetch to80.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-amadeylabel. - 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.
-
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
UPX0memory region (base0x140001000, size0x2627000) to disk. - Fix the PE headers (section table, EntryPoint =
0x886c0) and analyse with r2/Ghidra.
-
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.
-
Alternative: unpack manually via r2:
- Set a breakpoint at the
jmp OEP(offset0x1429491b1virtual). - Dump memory at
0x140001000for0x2627000bytes, then fix section raw-sizes to match virtual sizes.
- Set a breakpoint at the
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.