Skip to content
Packing & Cryptersintermediate

aPLib / LZ Compression Packing

Compressing a payload with aPLib's LZ-based codec so the real code is unreadable on disk until a tiny depacker inflates it in memory at runtime.

aPLib is a compact LZ77-derived compression library that malware authors favour because its depacker is tiny — a few hundred bytes of position-independent code that can be inlined into a loader. A stage is compressed with aP_pack, embedded as a blob, and reconstructed at runtime with aP_depack before control is handed to the recovered code. Because the data on disk is an LZ bitstream rather than plaintext, static strings and disassembly reveal nothing of the payload.

Unlike XOR or RC4 stubs, the giveaway is not high uniform entropy but a compressed structure: a stream of control bits interleaving literal bytes and back-reference (offset, length) pairs. Loaders for TrickBot and Emotet stages have repeatedly shipped aPLib-compressed modules for exactly this small-footprint reason.

How it works

The depacker walks a bitstream, copying literal bytes directly and resolving matches as copies from already-decoded output — a classic sliding-window LZ:

c
/* aPLib-style depack: literals + (offset,length) back-references */
BYTE *out = dst, *in = src;
int tag = 0, bits = 0;

for (;;) {
    if (getbit(&in, &tag, &bits)) {          /* 1 -> match */
        int off = getgamma(&in, &tag, &bits);
        int len = getgamma(&in, &tag, &bits);
        BYTE *ref = out - off;               /* copy from window */
        while (len--) *out++ = *ref++;
    } else {
        *out++ = *in++;                      /* 0 -> literal byte */
    }
    if (in >= src_end) break;
}
/* out now holds the reconstructed stage; jump to its OEP */
((void(*)(void))dst)();

The packed blob usually sits in a high-entropy section or overlay, prefixed by a small header recording the unpacked size so the depacker can allocate the output buffer. After depacking, the loader typically maps or copies the result and transfers control, just like any runtime unpacker.

Detection & analysis

Static analysis: Look for the depacker shape rather than a decrypt loop — a bit-reader (getbit/getgamma reading a tag dword), a literal-copy path, and a back-reference copy that reads from already-written output. The compressed blob shows elevated but non-uniform entropy and no readable strings. The original aPLib stream often begins with the recognisable AP32 header on packed files, and depacker code constants/structure are flagged by community YARA rules.

Dynamic analysis: Breakpoint after the depack loop completes (the point where out stops advancing) and dump the reconstructed buffer — that is the inflated stage. Follow the tail call/jmp into the buffer to reach the OEP, then dump with Scylla or pe-sieve and rebuild the IAT if the stage is a full PE. The aplib Python module or unpack utilities can decompress an extracted blob offline once you locate its start and unpacked-size header.

Detection rule hint: Match the aPLib AP32 magic and the depacker's characteristic byte sequences (gamma-decode and window-copy idioms), combined with a small import table and a high-entropy embedded blob followed by a control transfer into freshly written memory — a profile that separates LZ depacking from ordinary use of a system decompression API.

Votes

Comments(0)