A compiler-integrated build & obfuscation framework for your own C/C++ and C#/.NET software. ForgeVeil weaves protective transformations into the build of a program (from source), rather than mangling an already-compiled binary. Its goal is research-grade, defensive obfuscation: making static analysis harder and reducing what your shipped binaries leak through symbols, strings, metadata and control-flow structure.
Status: implemented end-to-end. Both build pipelines run the whole way and are validated across a broad corpus: C/C++ (Clang → LLVM IR → obfuscation passes → LLVM verifier → codegen (llc) → link → PE post-processing (LIEF) → validation → real x86-64 Windows PE via the Docker mingw-w64 image, or a runnable native binary on the host
nativetarget) and C#/.NET (Roslyn → CIL passes → cleanup → MVID → JIT-all validation → managed PE). The corpus (recursion, exceptions, classes/virtual, templates/STL, generics, async/await, LINQ, delegates, reflection) builds under full obfuscation and is proven to produce identical output to a plain build;forgeveil benchmarkreports original vs light/medium/heavy. Every obfuscation pass is proven output-preserving by building and running the result (each pass alone and all combined), and the string-encryption pass is checked to remove the plaintext from the binary.PE workbench. On top of building, ForgeVeil inspects, diffs, edits, profiles and signs existing PEs: reusable metadata profiles (extract / apply / bundle import-export), a unified transform pipeline, semantic diffing (structural, resources, imports/exports, Rich Header), and Authenticode signing with the user's own certificate (osslsigncode) under an enforced sign→verify invariant. See the PE workbench docs.
Correctness-first throughout: any construct a pass cannot safely transform (exception handlers, async/iterator state machines, compiler-generated types, generic-instantiation call sites, reflected symbols) is skipped, never mis-transformed. ForgeVeil never fakes an unfinished capability: a step it cannot perform is reported and skipped, never pretended.
ForgeVeil is a command-line tool with two independent build pipelines — one for C/C++ and one for C#/.NET — that compile your source code and, along the way, apply configurable obfuscating transformations. It is meant for protecting your own software (a build step you run on code you control), not for altering third-party binaries.
The single guiding principle is: correctness and provable semantic equivalence first, obfuscation second. A transformation that cannot be shown to preserve program behaviour does not ship.
A compiled program leaks a surprising amount of information: function and type names, human-readable string literals, debug metadata, and a control-flow shape that mirrors your source. That makes reverse-engineering and static analysis easier. ForgeVeil reduces that leakage by:
- renaming internal-only symbols (never exported/ABI/reflected ones);
- encoding string literals so they are reconstructed at runtime;
- reshaping control flow (splitting, bogus branches, flattening);
- rewriting arithmetic into equivalent but opaque forms (MBA);
- cleaning debug artifacts and normalizing metadata.
Everything is opt-in, seeded for reproducibility, and gated behind semantic-equivalence tests.
ForgeVeil is a polyglot system with a Python orchestrator at the centre:
forgeveil (Python CLI / orchestrator)
config · pipeline engine · pass registry · logging · diff · benchmark
│ │
(subprocess) │ │ (subprocess: dotnet)
┌─────────────▼───────────────┐ ┌────────────▼─────────────────┐
│ C/C++ toolchain │ │ ForgeVeil.Cil (.NET tool) │
│ clang → llvm-link → opt │ │ dnlib-based CIL passes │
│ loads libForgeVeilLLVM.so │ │ rename/strings/split/... │
│ llc → lld/mingw → PE │ │ metadata cleanup · MVID │
└──────────────┬──────────────┘ └────────────┬─────────────────┘
└──────► shared PEPass (LIEF) ◄──┘
resources · version-info · strip
│
Validator → optional signing
Why polyglot? Each stage is done with the technology where the transformation is correct by construction: LLVM passes operate on LLVM IR through LLVM's official plugin ABI; CIL passes operate on .NET metadata through dnlib; Python is the glue (CLI, YAML, subprocess, tests).
Thin CLI + everything in Docker. The Python CLI runs on your host, but every
toolchain stage is dispatched into a pinned Docker image through a single
ExecutionBackend abstraction. Your host needs only Python + Docker; Clang,
LLVM, the .NET SDK, mingw-w64, LIEF and osslsigncode all live in the image. This
gives a reproducible environment and is the seam toward Compose/Kubernetes
backends on the roadmap.
C/C++ source
→ SourcePass (rename internal identifiers; transform some constants)
→ Clang frontend (each .c/.cc/.cpp → LLVM IR)
→ llvm-link (combine translation units into one module)
→ normal LLVM optimization
→ LLVM obfuscation passes (split, mba, bcf, fla/vmf, icall, indibr, ...)
→ safe post-obfuscation optimization
→ LLVM verifier
→ LLVM backend (llc) → object files
→ linker (lld/mingw) → PE
→ PE post-processing → validation → optional signing
C# project
→ ProjectPass (GUID regen, AssemblyInfo/Version/Company normalization)
→ C# compiler (Roslyn) → CIL + metadata
→ CIL obfuscation passes (rename, strings, constants, split, mba, bcf, fla, ...)
→ metadata cleanup
→ MVID regeneration (last, after all CIL edits)
→ managed validation (metadata/CIL/stack/branch/EH + load smoke-test)
→ PE post-processing → optional signing
LLVM IR (LLVM Intermediate Representation) is a typed, low-level, platform-independent program representation that sits between source code and machine code. Clang compiles C/C++ into LLVM IR; LLVM optimizations and ForgeVeil's obfuscation passes transform the IR; then a backend turns IR into machine code. Operating at the IR level is what lets a transformation be precise about types and control flow — and therefore verifiably correct.
CIL (Common Intermediate Language) is the stack-based bytecode that C# (and other .NET languages) compile to. A .NET assembly is CIL plus metadata (the tables describing types, methods, fields, references). ForgeVeil's managed passes rewrite CIL and metadata directly, using the dnlib library.
A pass is one self-contained transformation over a program representation. In
ForgeVeil every pass has: a unique name, a description, a declared input and
output artifact type, parameters, an application probability, an enable switch, a
deterministic seed, diagnostic logging, and unit tests. Passes are decoupled —
they communicate only through immutable artifacts — so they can be enabled,
ordered and tested independently. See the Pass
interface.
An artifact is the typed payload flowing between stages. ForgeVeil is a
generic typed artifact pipeline (Artifact → Stage → Artifact): a pipeline
may start from source or from an existing binary/script. Artifact types span
sources (SourceC/SourceCpp/SourceCSharp, CppProject/CSharpProject),
intermediates (LlvmIr/LlvmBitcode, CilAssembly, ObjectNative), binaries
(PeNative/PeManaged/DllNative/DllManaged), scripts (PowerShellScript)
and extracted artifacts (PeMetadata/PeResources/SignatureInfo) —
see ArtifactType.
Each artifact carries its lineage: type, architecture, language,
producer, parent_artifact, transformation_history, seed, hash,
temporary and validation_state. Artifacts are immutable — a stage returns a
new artifact whose history extends the old one, which is what makes diff,
intermediate dumps and artifact-history.json provenance trustworthy. Each
stage declares accepted_input_types / produced_output_types /
mutates_artifact / requires_validation, and the engine rejects an
incompatible transition before running (e.g. PeNative → llvm-pass is refused;
PeNative → pe_inspect is allowed).
Pipeline modes: build (source → compile → IR → transform → link → PE),
existing (PE → pe_inspect → pe_extract → pe_validate), managed
(CilAssembly → inspect → CIL transform → validate), script
(PowerShellScript → ps_inspect → ps_validate). Run one with
forgeveil pipeline run config.yaml (see config/pipelines/);
inspect any file with forgeveil artifact inspect|info|history|validate <file>
and a PE with forgeveil pe inspect|extract (both read-only).
Term recap: CFG = Control Flow Graph; MBA = Mixed Boolean-Arithmetic; BCF = Bogus Control Flow; FLA = Control Flow Flattening; MVID = Module Version ID; PE = Portable Executable (the Windows executable format).
Host requirements (Phase 1): Python 3.11+ and (for real toolchain work) Docker.
git clone <this-repo> && cd ForgeVeil
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
forgeveil --versionBuild the toolchain image (contains Clang/LLVM/.NET/mingw/LIEF/osslsigncode):
docker compose build # produces forgeveil/toolchain:pinnedFor local C/C++ development (running the LLVM pipeline on the host, no Docker), you also need LLVM on the host and the ForgeVeil plugin built once:
# LLVM providing clang/opt/llvm-link/llvm-config (e.g. `brew install llvm`)
bash native/llvm-plugin/build.sh # → native/llvm-plugin/build/libForgeVeilLLVM.*Set toolchain.backend: local in your config to use the host toolchain. If LLVM
or the plugin is missing, the C/C++ pipeline stops with a clear message (it never
fakes a result).
- Orchestrator (Python): Typer (CLI), Rich (logging), Pydantic v2 (config + JSON Schema), PyYAML, pytest + hypothesis (tests).
- C/C++ (in Docker): Clang, LLVM (
opt/llc/llvm-link), lld, mingw-w64; the native C++ pass plugin (built with CMake against LLVM). - C#/.NET (in Docker): .NET SDK (Roslyn), dnlib (the
ForgeVeil.Ciltool). - PE / signing (in Docker): LIEF (resources/version-info/strip), osslsigncode (Authenticode).
Rationale for each choice is recorded in the plan and docs/architecture.md.
# Inspect a project's metrics and the planned passes
forgeveil inspect -c examples/cpp/hello-world/forgeveil.yml
# Run the pipeline (compiles to IR, runs `split`, verifies)
forgeveil build -c examples/cpp/hello-world/forgeveil.yml --keep-intermediate
# See what each pass changed
forgeveil diff -c examples/cpp/hello-world/forgeveil.yml
# Emit the config JSON Schema (for editor autocompletion)
forgeveil schema -o config/schema/forgeveil.schema.jsonThe Docker image is the canonical environment; the host needs only Docker.
# Docker: source → container → PE → validation → report
docker build -t forgeveil/toolchain:pinned .
./forgeveil-docker doctor
./forgeveil-docker pipeline build-and-validate config/build.yaml \
--rules rules/ --report output/report.json
# Kubernetes: catalog → Jobs → artifacts (same image, no build logic in k8s)
forgeveil k8s render examples/containerization/catalog.yaml \
--pvc fv-artifacts --config-map fv-config -o jobs.yaml
forgeveil k8s submit examples/containerization/catalog.yaml \
--config-map fv-config --pvc fv-artifactsDocs: docker.md · kubernetes.md ·
catalog.md · yara.md · toolchain.md.
Linux-only limits: Windows runtime is never executed (RUNTIME: NOT_TESTED);
structural/compiler/cryptographic validation is.
See examples/cpp/hello-world. It contains the
source, a forgeveil.yml, build instructions, an explanation, and the expected
result. In Phase 1, build runs the reference passes (stats, passthrough)
and reports structural metrics; obfuscation and PE production arrive later.
See examples/csharp/hello-world. Same structure;
Roslyn compilation and CIL passes arrive in Phase 6+.
Builds are described by a YAML file validated against a strict schema (unknown keys are rejected — a typo fails fast). Minimal example:
project:
language: cpp # cpp | csharp
input: ./main.cpp # source file or directory (never a prebuilt binary)
output: ./build/app.exe
seed: 1337 # global deterministic seed
toolchain:
backend: docker # docker (default) | local | compose/k8s (roadmap)
optimization: O2 # normal optimization applied BEFORE obfuscation
passes:
stats: { enabled: true }
passthrough: { enabled: true }
split: { enabled: false, probability: 70 }
mba: { enabled: false, mode: fixed } # fixed | linear | polynomial | mixed
bcf: { enabled: false, predicate_complexity: 1, fake_blocks_max: 2 }
flattening: { mode: none } # none | fla (fla and vmf are exclusive)
vmf: { enabled: false } # VM flattening (opcode/PC interpreter)
strings: { enabled: false, mode: strenc }The full schema is exported by forgeveil schema and stored at
config/schema/forgeveil.schema.json.
Point your editor at it for autocompletion and validation.
Ready-made configs live in config/profiles:
light.yml, medium.yml, heavy.yml. Copy one and set project.input/output.
The selective block lets critical functions get a heavier policy than the
rest (spec §5.4). In Phase 1 the obfuscation passes are reserved (disabled),
so profiles currently differ in intent and documentation, not in emitted code.
Each pass below shows a conceptual before/after. Conceptual example — actual compiler output may differ. "Phase" marks when it becomes available.
- split ✅ (implemented, v2) — Basic Block Splitting. Cuts one basic block
into several, joined by unconditional branches (semantically transparent).
mode: uniform | random— random gathers every legal split point (never a PHI/landingpad position, never producing an empty block), shuffles them under the seed, and takes up tomax_splits. Output-preserving by test.A: op1; op2; op3; return → A1: op1; op2; goto A2 A2: op3; return - mba ✅ (implemented, v2) — Mixed Boolean-Arithmetic. Replaces integer
+ - ^ & |with equivalent expressions of identical width (exact under two's-complement wrapping), e.g.a + b → (a ^ b) + ((a & b) << 1).mode: fixed | linear | polynomial | mixeddraws from verified identity families (random per site under the seed), withrounds,max_depth, and optional integer-constant splitting (constants). Thesynthesizedmode is a real generator: it builds a fresh linear-MBA expression from the 2-variable bitwise basis via random null-space coefficients (terms_min..terms_max), and each expression is PROVEN before it is emitted — an exhaustive i8 equivalence check plus the width-independent per-bit truth-table argument (add/sub use a proven arithmetic skeleton with synthesized bitwise leaves);proof_requiredrejects anything unproven (§39). Every mode/width is gated by a property-based equivalence test over random inputs. - bcf ✅ (implemented, v2) — Bogus Control Flow. Guards blocks with an
opaque-true predicate from the shared Opaque Predicate Engine (8 provably-
always-true families — parity, quadratic, mod-2ᵏ, bitvector, bit-idempotence,
a 2-var MBA — each exhaustively self-tested;
predicate_complexitycomposes several,helper_predicateshides them in a helper function) and never-executed fake blocks with varied junk templates.predicate_complexity,rounds,fake_blocks_min/max,clone_blocks, andhelper_predicates(predicate moved into a helper function) tune the disguise; splits after the PHI/EH prologue so SSA stays valid, and never splits at an EH-pad boundary (EH-aware). - fla ✅ (implemented) — Control Flow Flattening. Demotes PHIs and
cross-block values to stack slots (so dominance holds), then routes all blocks
through a
switchdispatcher driven by a state variable. EH-aware: aninvoke's normal edge is split into a stub that routes through the dispatcher while its unwind edge to the landingpad is preserved; landingpad/resumeblocks are reached only by unwinding, never by the dispatcher. Windows funclet EH (catchpad/cleanuppad) is still skipped rather than mis-transformed.A → B → C → state=1; loop: switch(state){ 1:A;state=2 2:B;state=3 3:C } - vmf ✅ (implemented, v2) — VM Flattening, an independent pass (not a mode
of
fla). Each dispatchable block becomes a handler for a random opcode; a randomized program array maps a virtual PC to an opcode, and the dispatcher readsprogram[PC], decodes the opcode, runs the block, and the block sets the next PC. This adds a decode/interpreter layer of indirection the flatswitchlacks. Reuses fla's EH-aware, SSA-safe lowering, so correctness matches flattening; mutually exclusive withfla(config-validated). - vmf2 ✅ (implemented, P0 True VM) — a genuinely different virtualizer: the
function is compiled into a bytecode program (a global i32 array, stored
apart from the CFG) run by a generated interpreter. Its own per-function ISA
({EXEC_BLOCK, JUMP, JUMP_IF, RETURN} with randomized opcodes + decoy opcodes),
an encoded virtual PC, a randomized bytecode layout, execution
mode: block | region | mixed, anddispatcher: switch | tree | two_stage | indirect. Architecturally independent offla/vmf(all three mutually exclusive). Emitsbefore.ll/vm-ir.txt/after.llfor inspection. - icall ✅ (implemented, v2) — Indirect Calls.
mode: pointer | table | encoded_table: a transformed call site fetches the target from a randomized function table (optionally XOR-encoded with a per-table key, filled by a startup ctor) instead of naming the callee; ABI/cc untouched, variadic/musttail skipped, multiple tables per module. - indibr ✅ (implemented, v2) — Indirect Branches.
br→indirectbr;mode: blockaddress | direct_table | encoded_tablewithstorage: global | stackand a per-function key (encoded targets areptrtoint(blockaddress) ^ key, computed at runtime). Successor PHIs and exception edges are preserved. - funwra ✅ (implemented, v2) — Function Wrappers. Inserts proxy functions
in front of internal functions (
A → wrapper… → B), copying the exact type/CC/attributes so the ABI is preserved.mode: per_function | per_callsite— per_callsite gives each call site its own chain of random depth in[depth_min, depth_max], so two calls to one function differ;callandinvokesites both handled (exceptions unwind through the wrapper frame). - ipobf ✅ (implemented, §4.9) — Function Context Transformation: adds a leading opaque context-pointer parameter to eligible internal functions and rewrites their direct call sites (address-taken/EH functions are skipped, so the ABI is unaffected).
- strenc / strcry ✅ (implemented, v2) — String Transformation, now two
distinct passes.
strencis a simple per-string XOR;strcryadds a per-string scheme (position-dependent XOR / ADD / multiplicative-XOR) and handles i8 (UTF-8) and i16 (UTF-16). Literals are stored encoded and decoded in place by a global constructor beforemain— Unicode-correct (byte-wise), thread-safe (runs before threads exist), leak-free (in place), deduplicated (by LLVM). For application string data only. Verified to remove the plaintext from the binary. - globalenc ✅ (implemented, v2) — Global Data Transformation. Encodes eligible internal integer globals and integer constant arrays / lookup tables (per-global XOR key) and decodes them in place at startup. Only provably-safe globals are touched: internal linkage, scalar-int or int-array only, never pointer-containing or externally visible data, never embedded in another global's constant initializer.
- symbols ✅ (implemented) — safe renaming of internal (local-linkage)
functions/globals — never exported/dynamic/ABI symbols,
main,llvm.*, or llvm.used members — plus optional debug-info stripping. - outline ✅ (implemented, §5.2) — Function outlining: extracts eligible
regions into generated helper functions via LLVM's own
CodeExtractor(semantics-preserving by construction). - merge ✅ (implemented, §5.3) — Function merging: internal functions of
identical signature (non-address-taken, non-EH) are folded into one
selector-dispatched function; direct call sites become
merged(i, args). - indibr also supports an address-table variant (
options: { table: true }): block addresses are stored in a private global array and loaded by index, instead of an inlineselectof blockaddresses.
See docs/llvm-passes.md.
Mirrors of the C/C++ passes at the CIL level (Phase 7/8): renaming (namespaces, internal types, methods, fields, properties — only when provably safe w.r.t. reflection/serialization/P-Invoke/COM/DI/XAML/dynamic), string transformation, constant transformation, basic-block splitting (recomputing branch targets and exception regions), MBA, BCF, flattening (never auto-applied to constructors, async/iterator state machines or exception-sensitive methods until proven), proxy methods and call indirection (preserving virtual dispatch, interfaces, generics, delegates). See docs/cil-passes.md.
For native PE, VERSIONINFO and icon are authored properly at link time: a
.rc script is generated from the pe: config and compiled with mingw's
windres, then linked in (the standard, robust mechanism). The shared PEPass
(LIEF) additionally sets the subsystem (console/GUI). For managed PE, identity is
set via the .csproj (ProjectPass). Symbol/debug stripping is applied at link time (-s) and
the LIEF round-trip normalizes the PE. It does not forge another vendor's
identity or clone/steal a trusted signature. ✅ (implemented, Phase 5.) PE
editing is best-effort and never fails the build; full version-info/icon/manifest
authoring across LIEF versions is on the roadmap.
Beyond build-time authoring, ForgeVeil is also a standalone PE workbench for inspecting, diffing, comparing-to-a-reference, editing and signing existing Windows PEs (native and managed) — read-only unless you ask it to write, and it always writes a new file, never the input. Start at docs/pe-workbench.md; the rest of the workbench docs are cross-linked from there (profiles · profile backends · signing · version-info · managed-metadata · pe-diff · reference-workflow · legacy/SDK .NET project build · auto-detected .NET builds · Tekton remote build backend · declarative chains), along with the generated cli-reference.md and pass-registry.md.
- After each structural LLVM pass (optionally) and always at the end of the LLVM pipeline, the LLVM verifier runs; on failure the pipeline stops, names the offending pass, and saves diagnostic IR.
- The managed validator checks metadata, CIL, stack balance, branch targets and exception regions, then a smoke test loads the assembly in a fresh runtime.
- ✅ (implemented, Phase 5 for C/C++.) The
validatestage checks the PE structurally via LIEF (valid PE, has an entry point) for a Windows target, and smoke-runs the produced native binary (must exit 0) for the hostnativetarget. On failure the engine stops the line with a diagnostic.
Flags: --verbose, --debug, --keep-intermediate, --dump-ir (Phase 2+),
--dump-cil (Phase 6+). The engine logs a [i/N] Running <pass> line per stage.
With --keep-intermediate, each stage's artifact is written under
build/forgeveil/intermediate/NN-<pass>/. Later phases add the IR/assembly dumps
(original.ll → optimized.ll → split.ll → …, and the CIL dumps).
Every random transformation is seeded from the global seed. Each pass derives
an independent sub-seed from (seed, pass_name), so adding or reordering passes
does not perturb another pass's randomness. The derivation uses BLAKE2b (stable
across platforms), unlike Python's per-process-salted hash(). Limits:
toolchains introduce their own non-determinism (timestamps, symbol ordering,
MVID) — the Docker image pins tool versions to minimize this; full
bit-reproducibility is a documented goal, not yet a guarantee.
pytest -q runs unit, integration, property-based (hypothesis) and regression
tests, plus a corpus (tests/corpus) of recursion, exceptions,
classes/virtual, templates/STL, generics, async/await, LINQ, delegates and
reflection — each built under FULL obfuscation and checked to produce identical
output to a plain build. Every transformation follows input → transform → execute → compare with original. See docs/testing.md.
forgeveil benchmark -c <config> -o report.md builds the project under
original vs light vs medium vs heavy obfuscation and reports build time,
output size, execution time (when runnable on the host) and obfuscation stages.
See benchmarks/ for sample reports. Typical shape: build time and
binary size grow with intensity; runtime overhead is small for the structural
passes and depends on how hot the flattened/MBA code is.
- The C/C++ channel builds end-to-end to a real Windows PE via Docker
(mingw-w64) and to a runnable native binary on the host
nativetarget. Authenticode signing (osslsigncode) with the user's own certificate is implemented for the PE workbench under an enforced sign→verify invariant. - Windows PE cross-linking requires the Docker toolchain image (the host macOS/ arm64 has no lld/mingw).
- Only modern .NET is targeted by the current image; .NET Framework is roadmap.
compose/k8sbackends are declared but not implemented (they fail loudly).
- "configuration invalid …" — the YAML violates the schema; the message names
the offending key. Run
forgeveil schemato see valid keys. - "project input not found" — check
project.inputrelative to the config file. - "pass 'X' is reserved for a later phase" — that obfuscation pass is not in this build yet; it is skipped, not applied.
Signing. ForgeVeil signs only with a certificate you actually possess
(your own .pfx/.p12, self-signed or CA-issued), via osslsigncode, strictly
last (signing.enabled: true, signing.pfx, signing.password). It will not
copy, clone or reuse another PE's certificate or signature, and will not forge
another vendor's identity — that is signature theft / impersonation, explicitly
out of scope (§8/§9). Changing a file after signing breaks Authenticode integrity,
which is why signing runs last.
ForgeVeil is for defensive, research, and self-protection use on software you own. It takes source as input (a build step), not arbitrary third-party binaries. String transformation is for your application's string data only — not for hidden loading or execution of external payloads. PE post-processing edits your own legitimate identity fields; it never impersonates another vendor and never clones or steals a trusted digital signature. Signing uses only a certificate you actually possess, and runs strictly last — changing a file after signing breaks Authenticode integrity.
pip install -e ".[dev]"
pytest -q # tests
ruff check src tests # lint
mypy src # typesCI (.github/workflows/ci.yml) runs lint, types, tests and the toolchain-image build, and checks the committed JSON Schema is current.
- Implement the transformation in the native plugin
(
native/llvm-plugin/, Phase 2+), with GoogleTest + FileCheck + equivalence-harness tests. - Add a thin Python wrapper pass in
src/forgeveil/passes/, register it indefault_registry(), and give it a canonical-order slot. - Add a config slot in
PassesConfigand unit tests. See docs/adding-pass.md.
Same shape, implemented in managed/ForgeVeil.Cil/ (dnlib) with xUnit/CsCheck
tests and a load smoke-test, wrapped by a Python pass. See
docs/adding-pass.md.
See docs/architecture.md for the full tree. Top level:
src/forgeveil (orchestrator), native/llvm-plugin (C++ passes),
managed/ForgeVeil.Cil (.NET/dnlib), tests, examples, config, docs,
Dockerfile/docker-compose.yml.
| Phase | Content | Status |
|---|---|---|
| 1 | Foundation: engine, config, CLI, logging, Docker, tests | ✅ done |
| 2 | C/C++ compile (Clang→IR→link→opt), LLVM plugin, verifier, split |
✅ done |
| 3 | mba, bcf, fla (split landed in Phase 2) | ✅ done |
| 4 | icall, indibr, funwra, strings, symbols | ✅ done |
| 5 | codegen, link, PEPass (LIEF), validation, real Windows PE | ✅ done |
| 6 | C# ProjectPass, Roslyn build, ForgeVeil.Cil, MVID, managed validate | ✅ done |
| 7 | CIL rename, strings, constants, splitting | ✅ done |
| 8 | CIL mba, bcf, flattening, proxy/icall | ✅ done |
| 9 | managed validation (JIT-all), metadata cleanup, MVID | ✅ done |
| 10 | full corpus tests, benchmarks, docs, release | ✅ done |
| ✚ | Function outlining + merging (§5.2/5.3), int64 CIL MBA, indibr address-table variant |
✅ done |
| ✚ | vmf flattening (encoded-state), Compose + Kubernetes backends, Authenticode signing (your own cert) | ✅ done |
| ✚ | ipobf (function context transform), position-dependent CIL strings (strcry), PE version-info/icon authoring (windres) | ✅ done |
| ✚ | EH-aware bcf/split/flattening (both channels: CIL try/catch atomic spans + strong-verifier clean; LLVM invoke/landingpad/resume) | ✅ done |
| ✚ | P0 v2 (LLVM): TransformationContext RNG, MBA v2 (linear/polynomial/mixed), BCF v2 (predicate families/rounds/clone/helper), VMF v2 (opcode-VM, own pass), IPObf v2 (real A→B→C deps), per-stage verify + repro.json |
✅ done |
| ✚ | P1 v2 (LLVM): Indirect Call v2 (pointer/table/encoded_table), Indirect Branch v2 (blockaddress/direct/encoded × global/stack), fv-strcry (own pass, per-string scheme, i8/i16), fv-globalenc (global data), pass metrics (per-pass JSON report) |
✅ done |
| ✚ | P2 v2 (LLVM): Function Wrapper v2 (per_callsite chains, call+invoke), Basic Block Splitting v2 (randomized positions) | ✅ done |
| ✚ | True VM (P0): fv-vmf2 — bytecode + interpreter, per-function ISA, block/region/mixed × switch/tree/two_stage/indirect, decoys, encoded PC |
✅ done |
| ✚ | Synthesized MBA (P0): mba.mode=synthesized — null-space linear-MBA generator + build-time equivalence proof (exhaustive i8 + width-independent per-bit) |
✅ done |
| ✚ | Opaque Predicate Engine (P0): shared subsystem, 8 self-tested families, composition + helper mode; BCF no longer hard-wires a predicate | ✅ done |
| ✚ | Generic artifact pipeline: 17-type taxonomy, enriched Artifact (lineage/hash/validation), Stage interface + pre-run transition validation, pipeline run + artifact CLI, artifact-history.json provenance |
✅ done |
| ✚ | PE Inspector + Extractor (read-only): pe inspect/pe extract — headers/sections/imports/exports/resources/manifest/version/debug/rich/security/.NET; PowerShell as a first-class artifact (inspect/validate) |
✅ done |
| ✚ | Unified PE object model (PEModel): one LIEF-backed parse → immutable model + JSON/diff/validate + PEEditor (writes a new PE); Inspector/Extractor refactored onto it (foundation of the PE workbench) |
✅ done |
| ✚ | ManifestModel + manifest CLI + ManifestPass (pe/manifest.py): parse/normalize/validate/diff/merge/override of application manifests (unknown elements preserved); ManifestPass embeds an edited manifest into a new PE |
✅ done |
| ✚ | MetadataProfileStore + profile CLI (pe/profile.py): extract/show/list/diff/merge/apply reusable metadata profiles with base inheritance (JSON+YAML, no private keys, signature info-only) |
✅ done |
| ✚ | Existing-PE transform pipeline (passes/pe_transform.py + pe transform-spec): pe_optheader/pe_debug_cleanup/pe_checksum passes edit a real PE via PEEditor into a new file (input untouched); passes declare pe-reads/writes/signature+checksum invalidation, pipeline re-validates each mutated PE and tracks signature invalidation |
✅ done |
| ✚ | Unified ExistingPePipeline (pipeline/existing_pe.py + pe transform): MetadataProfile/ResourceProfile-driven orchestrator over ProfileApplyPlanner + PeTransaction — plan → execute → post-validate → semantic diff → ExistingPeResult (CHANGED/UNCHANGED/FAILED/DRY_RUN); rolls back atomically on any transaction failure |
✅ done |
| ✚ | ResourceProfile + full resource round-trip (pe/resource_profile.py + pe resource + ResourcePass): capture the raw .rsrc tree (type→name→lang, source of truth) with sidecar blobs; extract/show/diff/apply/validate; re-embed into a new PE (byte-faithful leaves, input untouched) |
✅ done |
| ✚ | PE workbench Wave 2 — Authenticode verifier (pe/authenticode.py + pe verify + pe_verify stage): LIEF verify_signature() verdict + full per-cert chain (subject/issuer/serial/validity/thumbprints), optional openssl; PE Diff Engine (pe/diff.py + pe diff): domain-aware semantic diff (sections/imports/dirs keyed, headers field-level); Transaction System (pe/transaction.py): atomic multi-edit → temp → validate → os.replace, rollback on any failure; Validator v2 (pe/validator.py + pe check): entry-point/alignment/raw-bounds/dir-bounds/checksum/entropy/managed-CLR checks with severity summary. All read-only on input; signature = inspection only |
✅ done |
| ✚ | PE workbench Wave 3 — Managed Metadata Workbench (pe/managed.py + pe managed): direct COR20/metadata-root parse → runtime/metadata version, COR20 flags, MVID, module name, #~/#Strings/#GUID/#Blob streams, per-table row counts (LIEF exposes none of this); Reference Profile Import (pe/reference.py + profile conform): import a reference profile (from a PE or file) and report a target PE's conformance/deviations; Round-Trip Fidelity Tests (tests/test_roundtrip_fidelity.py): byte-faithful resource round-trip + parse/MVID stability across a corpus of real PEs |
✅ done |
| ✚ | PE workbench Wave 4 — Build Identity Profiles (pe/build_identity.py + pe identity): normalized toolchain/structure fingerprint (format/machine/linker/section-layout/imports/rich/managed shape) with a stable sha256 that excludes volatile timestamp/MVID; Unified Reports (pe/report.py + pe report): one report aggregating structure + Validator v2 + Authenticode + managed metadata + build identity, sub-reports degrade gracefully |
✅ done |
| ✚ | Real PowerShell AST transform (passes/ps_transform.py ps_strings): pwsh-AST-driven constant-string reconstruction ([char[]](…) -join ''), skips command names/interpolated strings, re-parses output; verified by stdout equivalence. Configurable .NET target framework (passes/cs_project.py): project stage target_framework option (net48/net472/…) retargets the csproj and injects Microsoft.NETFramework.ReferenceAssemblies so it compiles off-Windows |
✅ done |
| ✚ | Profile integration W1 — ManagedMetadata→MetadataProfile (real .NET metadata in the managed section via an ECMA-335 #~ table walker pe/_cli_metadata.py: assembly name/version, MVID, module/assembly references, streams); schema v2 + migration (pe/profile.py: build_metadata section, _migrate v1→v2); ProfileApplyPlanner (pe/apply_planner.py + profile apply --target/--dry-run): maps sections→executors with apply/skip/incompatible/pending + signature invalidation, applies via transaction; controlled ReferenceProfile apply (pe/reference.py + profile extract-reference/apply-reference): selective sections, compatibility (arch), semantic-diff report; cert/signature never auto-applied |
✅ done |
| ✚ | Existing-PE editing W2 — VersionInfoModel+Pass (pe/version_info.py + pe_version): byte-faithful VS_VERSION_INFO parse/serialize (unknown entries, translation tables, fixed info preserved), edit one field without losing others; unified existing-PE pipeline (passes/profile_apply_stage.py pe_profile_apply + pe_version/manifest_edit/resource_edit/pe_checksum/pe_validate through one engine with post-stage reparse+validate); ManagedMetadataEditor (pe/managed_editor.py + dotnet inspect/metadata diff/metadata edit/mvid regenerate): dnlib-based (ForgeVeil.Cil edit) assembly name/version/MVID edits, JIT-all validated, input untouched; transaction enforcement (pipeline/generic.py): engine proves a mutating PE stage never edits its input in place + records an impact analysis |
✅ done |
| ✚ | Authenticode W3 — mandatory Sign→Verify (pe/signing.py): SigningPass reparses + cryptographically verifies its output and refuses signed=true until it passes (states SIGNED_VALID / SIGNED_VALID_UNTRUSTED / SIGNED_INVALID / SIGNED_UNVERIFIED, diagnostic report on failure); crypto/trust model (pe/trust.py + signature verify): separates cryptographic validity / certificate validity / chain trust / revocation / timestamp into normalized statuses — a self-signed sig is VALID_UNTRUSTED, OS-trust/revocation report NOT_CHECKED (never guessed); signature diff (pe/signature_diff.py + signature diff): compares signer/certs/digests/thumbprints/timestamp/verdict, separating metadata-equal from crypto-different |
✅ done |
| ✚ | Workbench quality W4 — Semantic PE Diff v2 (pe/semantic_diff.py + pe diff --semantic): ignore policy (timestamp/checksum/offsets/MVID/resource-ordering) + levels (structural/metadata/resource/managed/signature) so a harmless relayout is not "different"; Round-Trip Fidelity Framework (pe/fidelity.py + pe fidelity): semantic / opaque-blob / known-model fidelity, a CI guard against silent data loss; profile catalog/index (pe/profile_catalog.py + profile search/tag): tags/arch/kind/schema search over the file store, tags in a sidecar (SQLite-swappable); docs sync (docs/pe-workbench.md) |
✅ done |
| ✚ | Containerized execution (Docker + Kubernetes) — canonical multi-stage Dockerfile (pinned toolchain toolchain.lock.yaml + Toolchain ID, non-root, /workspace/*, YARA, self-test stage); forgeveil doctor/selftest/pipeline build-and-validate/yara/catalog/k8s CLIs; orchestration/ catalog+matrix, batch runner (isolation/limits/cache), ArtifactStore, aggregate report; K8s Job generation (labels/limits/activeDeadlineSeconds, ConfigMap/Secret-ref/PVC, non-root) + deploy/kubernetes/; CI builds the image, runs the container self-test + kind smoke; RUNTIME: NOT_TESTED on Linux (never faked) |
✅ done |
| — | Windows funclet-EH flattening (LLVM — needs an LLVM dev toolchain to build/verify), finally/filter CIL flattening (correctness-critical EH work, gated pending verified implementation) | roadmap |