TinyLoad v7 is out. if you haven't seen it before — it's a PE
packer for Windows. one .cpp file, no dependencies, MIT.
repo here.
v6 killed the switch statement and split the opcode table. v7 goes
after the two things that were still exploitable — a readable
overlay and a dumpable memory image. both are gone now.
VEH page-fault decryption
this is the headline feature. instead of decrypting the payload
into a normal RWX allocation and jumping to it, v7 maps every
section as PAGE_NOACCESS after loading:
cpp// after mapping sections, mark them inaccessible
for each section:
VirtualProtect(page, 0x1000, PAGE_NOACCESS, &old);
vehPageXor(page, 0x1000, g_vehKey, (uintptr_t)page);
a vectored exception handler catches every ACCESS_VIOLATION,
checks if the fault address is inside the payload range, decrypts
the faulting page, and marks it PAGE_EXECUTE_READWRITE:
cppLONG CALLBACK vehHandler(EXCEPTION_POINTERS* ex) {
uintptr_t fault = ex->ExceptionRecord->ExceptionInformation[1];
uintptr_t pg = fault & ~(uintptr_t)0xFFF;
VirtualProtect((LPVOID)pg, 0x1000, PAGE_READWRITE, &old);
vehPageXor((BYTE*)pg, 0x1000, g_vehKey, pg);
VirtualProtect((LPVOID)pg, 0x1000, PAGE_EXECUTE_READWRITE, &old);
// add to LRU cache...
return EXCEPTION_CONTINUE_EXECUTION;
}
a background watchdog thread scans the LRU cache every 50ms and
re-encrypts any page that hasn't been touched in 200ms, setting it
back to PAGE_NOACCESS. the cache holds 256 slots with
thread-safe eviction.
the result is that at any point in time only the currently
executing page (and recently accessed pages) are ever in plaintext
in memory. a full memory dump gives you at most a few hot pages.
the rest is encrypted noise.
each page uses a unique key: g_vehKey ^ (addr >> 12) — so even
if you recover one page's key you can't decrypt the others.
fully encrypted overlay
in v6 the overlay was still partially readable — the signature,
metadata fields, and VM bytecode were sitting in the clear. v7
encrypts everything with per-file stub-derived keys:
[stub binary]
[encrypted Tail struct] — sig, origSz, packSz, flags, dispKey all XOR'ed
[encrypted VM bytecode] — full bytecode blob XOR'ed with stubKey stream
[chunk 0][junk][chunk 1][junk][chunk 2][junk][chunk 3] — payload split into 4
[encrypted tail offset] — 4-byte EOF pointer XOR'ed
the stubKey is derived from a hash of the stub binary itself —
so it's different for every build and can't be precomputed without
the stub. even the 4-byte tail offset at the very end of the file
is encrypted, so the overlay start address isn't readable from a
hex dump.
zero-filler interleaving pads the overlay at a 3:1 ratio giving
roughly 6.73 bits/byte entropy, making it blend visually with
normal PE sections rather than standing out as high-entropy packed
data.
canary integrity chain
8 canary values are embedded in the VM bytecode. each one is
checked in sequence — if any check fails, a corruption mask
escalates from 1 bit to 8 bits and gets XORed into the decrypted
plaintext. tampering with the overlay produces garbage output
rather than a clean crash, which makes it harder to know if your
modification worked.
payload chunk splitting
the payload is split into 4 chunks with random junk gaps of
128–640 bytes between them. automated overlay scanners that look
for a contiguous payload region won't find one.
usage
v7 adds a new --veh flag:
TinyLoad.exe --i myapp.exe --vm --c --veh
build from source:
g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s
grab the binary from
releases.
what's left
the main thing still missing is payload stub dependency — making
the payload actively call back into the stub at runtime so a
reconstructed dump is broken without the stub mapped. that's v8.
if you find files it breaks on, open an issue. star helps a lot ❤️
repo: github.com/iamsopotatoe-coder/TinyLoad
blog: iamsopotatoe-coder.github.io/TinyLoad/#blog
Top comments (0)