惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
月光博客
月光博客
腾讯CDC
Engineering at Meta
Engineering at Meta
博客园 - Franky
Vercel News
Vercel News
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
GbyAI
GbyAI
B
Blog
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
TinyLoad v5 — encrypted strings, opmap obfuscation, and I...
iamsopotatoe · 2026-05-19 · via DEV Community

TinyLoad v5 is out. if you haven't seen the project before — it's a PE packer for Windows. you give it an .exe, it compresses and VM-encrypts it into a self-extracting stub that runs the original entirely in RAM. one .cpp file, no dependencies, MIT. repo here.

v4 added opaque predicates, anti-debug, and section scrambling. v5 is about hardening the stub itself — the code that actually runs when the packed exe launches. three main additions: encrypted API strings, content-derived opmap obfuscation, and IAT wiping post-load.


quick recap

when you run a TinyLoad packed exe, the stub spins up a custom 32-opcode VM interpreter, executes a decryption bytecode program against the payload, then manually maps the original PE into memory and runs it. every packed file gets a randomly shuffled opcode table so the bytecode looks different every time.

v5 makes the stub itself harder to analyse statically.


1. encrypted API strings

in v4 the DLL and function names the stub needs — kernel32.dll, GetModuleHandleA, VirtualAlloc etc — were sitting as plaintext strings in the binary. any static analysis tool would immediately see what APIs the stub calls just from strings view.

v5 XOR-encrypts all of them:

static const BYTE _ed_k32[]  = {0x3A,0x37,0x21,0x3A,0x30,0x3A,0x64,0x6A,0x77,0x3E,0x37,0x30};
static const BYTE _ed_gmha[] = {0x70,0x5D,0x4D,0x77,0x54,0x58,0x48,0x52,0x5A,0x08,0x20,0x2C,0x27,0x28,0x20,0x07};
static const BYTE _ed_gpa[]  = {0x06,0x27,0x37,0x14,0x37,0x29,0x24,0x09,0x2D,0x2E,0x39,0x29,0x3E,0x3D};

Enter fullscreen mode Exit fullscreen mode

each one decrypts at runtime with a rolling XOR:

static char* sdec2(char* buf, const BYTE* enc, size_t n, uint8_t k) {
    for (size_t i = 0; i < n; i++) buf[i] = enc[i] ^ (uint8_t)(k + i);
    buf[n] = 0; return buf;
}

Enter fullscreen mode Exit fullscreen mode

no readable strings in the binary anymore. strings view on the packed output is clean.


2. opmap obfuscation via FNV hash

this one directly addresses feedback from the r/ReverseEngineering community who pointed out that the opmap decode table was sitting plaintext right behind the stub — effectively a silver platter for static analysis.

v5 derives a per-file XOR mask for the opmap using FNV-1a hash, seeded from the file's own content:

static void xorOpmap(BYTE* opmap, const Tail& t, const BYTE* vmCode, const BYTE* pay) {
    uint32_t h = 0x811C9DC5u;
    auto feed = [&](uint8_t b) { h ^= b; h *= 0x01000193u; };
    for (int i = 0; i < 4; i++) {
        feed((uint8_t)(t.origSz >> (i * 8)));
        feed((uint8_t)(t.packSz >> (i * 8)));
        feed((uint8_t)(t.vmCodeSz >> (i * 8)));
    }
    DWORD vmLim = t.vmCodeSz < 32 ? t.vmCodeSz : 32;
    if (vmCode) for (DWORD i = 0; i < vmLim; i++) feed(vmCode[i]);
    DWORD payLim = t.packSz < 32 ? t.packSz : 32;
    if (pay) for (DWORD i = 0; i < payLim; i++) feed(pay[i]);
    for (int i = 0; i < NUM_OPS; i++) {
        feed((uint8_t)i);
        opmap[i] ^= (uint8_t)((h >> 24) ^ (h >> 16) ^ (h >> 8) ^ h);
    }
}

Enter fullscreen mode Exit fullscreen mode

the hash feeds on origSz, packSz, vmCodeSz, and the first 32 bytes of both the VM bytecode and the payload. the resulting mask is different for every single packed file — you can't extract the opmap from one sample and apply it to another. the stub runs xorOpmap at unpack time to recover the real table before passing it to the VM.


3. IAT wiping post-load

after the stub manually maps the packed PE into memory and resolves all its imports, v5 zeroes out the import structures:

// kill recovery
imp->OriginalFirstThunk = 0;
imp->Name = 0;
imp++;

// ...
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = 0;
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = 0;

Enter fullscreen mode Exit fullscreen mode

once imports are resolved the IAT entries aren't needed anymore. zeroing them means if someone dumps the process from memory after load, the import directory is gone — no DLL names, no function names, no reconstruction path from the dump alone.


4. dead code and junk VM instructions

v5 also adds dead code functions in the stub (__attribute__((used)) to force the compiler to keep them despite -O2) and junk NOP and self-mov instructions scattered through the VM bytecode to inflate and confuse disassembly:

eOp(bc,enc,NOP_I);              // junk
eOp(bc,enc,MOV_I); eR(bc,6); eR(bc,6); // junk — mov r6, r6

Enter fullscreen mode Exit fullscreen mode

minor individually but they add noise on top of everything else.


current usage

TinyLoad.exe --i myapp.exe --vm --c

Enter fullscreen mode Exit fullscreen mode

build from source:

g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s

Enter fullscreen mode Exit fullscreen mode

or grab the binary from releases.


if you run into files it doesn't pack correctly, open an issue. and if you find it useful, a star helps a lot ❤️

repo: github.com/iamsopotatoe-coder/TinyLoad
blog + changelogs: iamsopotatoe-coder.github.io/TinyLoad/#blog