
Паковщик и криптер с открытым исходным кодом для Windows x64 PE. Сжимает и шифрует исполняемые файлы с помощью пользовательской виртуальной машины, помещая их в самораспаковывающуюся заглушку.
TinyLoad — это шифратор/упаковщик PE для x64 исполняемых файлов. Он упаковывает входной exe с различными уровнями защиты, чтобы предотвратить его реверс-инжиниринг. Это один единственный .cpp файл без внешних зависимостей.
TinyLoad добавляет вашу полезную нагрузку к своей собственной копии. Когда упакованный exe запускается, он извлекает полезную нагрузку, расшифровывает её и выполняет непосредственно в памяти, никогда не записывая оригинал на диск. Каждый раз при упаковке опкоды VM случайным образом изменяются и помещаются в 4 независимо ключуемые подтаблицы, поэтому не бывает двух одинаковых сборок.
flowchart TD
start["pack(in, out, vm, comp, veh, noconsole)"] --> load["loadFile(in) → orig"]
load --> pe{"MZ + PE00 + Machine 0x8664 + NumberOfSections in 1..96?"}
pe -->|fail| err["printf error / return false"]
pe -->|ok| init["pay = orig flags = 0"]
init --> cdec{"--c ?"}
cdec -->|yes| lz["flags |= 1<br/>lzPack: WINDOW=0xFFFF MAXCHAIN=4096<br/>MAXMATCH=258 MINMATCH=3 HSIZE=65536<br/>hash4 = FNV-ish fold * 0x1000193<br/>lazy lookahead la = 1,2<br/>out = uint32le origSz + 8-token flag bytes<br/>match → varint dist, varint len-3"]
cdec -->|no| vmq
lz --> vmq{"--vm ?"}
vmq -->|yes| scatter["flags |= 2<br/>Fisher-Yates 32 slots, scatter NUM_OPS=28<br/>into subtables 4x8, unused stay 0xFF<br/>opmap_enc op = tbl shl 6 OR idx"]
scatter --> canary["canary corridor n=8, skip first 0x400 of pay<br/>canExp c = pay at off XOR prev"]
canary --> mkvm["makeVmProgram opmap_enc, key1, key2, canary<br/>key1,key2 ← mt19937_64 TickCount64 XOR QPC<br/>opaque predicates + stream loop + canary mask"]
mkvm --> venc["vmEncryptPayload 192-bit state<br/>k3 = 0x9E3779B97F4A7C15<br/>ks = uint8((k1 XOR k2) + k3)<br/>b = NOT(b XOR ks)<br/>k1 = rotl64(k1,11) XOR k2<br/>k2 = rotr64(k2,19) + k1 + k3<br/>k3 = k3 * phi XOR k1"]
vmq -->|no| vehq
venc --> vehq{"--veh ?"}
vehq -->|yes| vflag["flags |= FLAG_VEH 4"]
vehq -->|no| stub
vflag --> stub["stub = loadFile GetModuleFileNameA self<br/>saveFile out, stub as host image"]
stub --> skey["stubKey = 0x9E3779B97F4A7C15<br/>for i in 0x1000 .. min n, 0x2000:<br/>stubKey = (stubKey XOR stub i) * phi"]
skey --> disp["prime vmRun dispKey=0 → fill g_off<br/>dispKey ← rng<br/>encOff j = uint64(g_off j) XOR dispKey"]
disp --> tail["Tail: sig TINYLD60, origSz, packSz, flags,<br/>dispOff x28, subtables 4x8, vmCodeSz,<br/>dispKey, canary*, chunk*,<br/>payHash = SHA-256 orig first 8B"]
tail --> hide["sig i XOR= stubKey shr 8i<br/>xorOpmap FNV-1a 0x811C9DC5 / 0x01000193<br/>feeds origSz, packSz, vmCodeSz + first 32B<br/>of vmCode and pay, per-table<br/>flags |= FLAG_CHUNK 8"]
hide --> tenc["XOR-encode Tail fields with stubKey slices<br/>vmCode vi XOR= stubKey shr ((vi*3) and 63)"]
tenc --> ovl["payload = vmCode || pay<br/>interleave: insert 0x00 after every 3 bytes"]
ovl --> chunks["split into 4 chunks, shuffle physical order<br/>Tail placeholder TAIL_SERIALIZED_SZ = 419<br/>before each chunk: 128..640 RNG junk"]
chunks --> tea["xxteaEncrypt bytes after Tail<br/>DELTA=0x9E3779B9 rounds=6+52/n<br/>remainder XOR pxKey0 xor pxKey1 xor pxKey2 xor pxKey3<br/>patch k i XOR FEEDF00D / CAFEBABE /<br/>DEADBEEF / 8BADF00D into _pxBlock<br/>between C0DE1337 and B007DEAD"]
tea --> ser["XOR chunkOff / chunkSz / chunkOrder / chunkCnt<br/>serializeTail: 16 TLV records id + u16le sz + bytes<br/>Fisher-Yates order, LCG 1103515245+12345<br/>seed = stubKey XOR filesize<br/>append DWORD LE: tailOff XOR stubKey low 32"]
ser --> fin{"--noconsole ?"}
fin -->|yes| gui["OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI"]
fin -->|no| scr
gui --> scr["scrambleSections: cycle names<br/>.text .data .rdata .bss .idata"]
scr --> done["saveFile out, result"]Возьмите готовый бинарник из релизов или соберите его самостоятельно.
Вам понадобится MinGW (g++). Просто запустите:
g++ -o TinyLoad.exe TinyLoad.cpp -static -O2 -s
или используйте build.bat.
TinyLoad.exe --i <input> [--o <output>] [--vm] [--c] [--veh]
Вам нужен как минимум 1 из --vm, --c или --veh.
Пользовательское сжатие LZ77 с сопоставлением по хеш-цепочкам и скользящим окном 64 КБ. Сначала выполняется сжатие, затем поверх него накладывается VM-шифрование, чтобы любые закономерности в сжатых данных тоже были скрыты.
Пользовательская виртуальная машина на 28 опкодов, которая выполняется внутри заглушки. Опкоды случайным образом распределяются по 4 подтаблицам по 8 штук, и каждая подтаблица XOR-шифруется отдельным ключом, производным от данных полезной нагрузки. Взлом одной подтаблицы раскрывает максимум 8 опкодов из 28. Сам шифр — это 128-битный потоковый шифр с перемешиванием ключа через rotl и rotr, выполняемый целиком внутри интерпретатора VM. Полезная нагрузка шифруется с помощью XXTEA.
При включённом --veh все страницы секций PE отображаются как PAGE_NOACCESS. Когда программа пытается обратиться к странице, возникает исключение, и векторизованный обработчик исключений расшифровывает только эту 1 страницу и устанавливает правильную защиту. В фоновом режиме работает поток-сторож, который повторно шифрует любую страницу, к которой не обращались в течение 200 мс. В любой момент времени большая часть вашей программы остаётся зашифрованной в памяти, поэтому дампы памяти захватывают только то, к чему недавно обращались.
4 наиболее критичных API (GetModuleHandleA, GetProcAddress, ExitProcess, VirtualAlloc) перенаправляются через функции-обёртки внутри заглушки. После загрузки полезной нагрузки весь каталог импорта обнуляется, поэтому дампы памяти не могут восстановить таблицу импорта.
MIT
| флаг | что делает |
|---|
--i <file> | входной exe для упаковки |
--o <file> | путь к выходному файлу (по умолчанию: input_packed.exe) |
--vm | VM-шифрование |
--c | сжатие LZ77 |
--veh | расшифровка по страничным исключениям VEH |
--noconsole | подсистема GUI |