Skip to content

Repository files navigation

ForgeVeil

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 native target) 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 benchmark reports 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.


1. What is this project?

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.

2. What problem does it solve?

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.

3. Architecture overview

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.

4. C/C++ pipeline

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

5. C#/.NET pipeline

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

6. What is LLVM IR?

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.

7. What is CIL?

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.

8. What is a compiler pass?

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.

9. Artifact lifecycle

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).

10. Installation

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 --version

Build the toolchain image (contains Clang/LLVM/.NET/mingw/LIEF/osslsigncode):

docker compose build      # produces forgeveil/toolchain:pinned

For 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).

11. Dependencies

  • 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.Cil tool).
  • PE / signing (in Docker): LIEF (resources/version-info/strip), osslsigncode (Authenticode).

Rationale for each choice is recorded in the plan and docs/architecture.md.

12. Quick Start

# 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.json

Containerized (canonical) — no host toolchain

The 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-artifacts

Docs: 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.

13. First C++ example

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.

14. First C# example

See examples/csharp/hello-world. Same structure; Roslyn compilation and CIL passes arrive in Phase 6+.

15. Configuration

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.

16. Profiles

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.

17. Description of every C/C++ pass

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 to max_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 | mixed draws from verified identity families (random per site under the seed), with rounds, max_depth, and optional integer-constant splitting (constants). The synthesized mode 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_required rejects 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_complexity composes several, helper_predicates hides them in a helper function) and never-executed fake blocks with varied junk templates. predicate_complexity, rounds, fake_blocks_min/max, clone_blocks, and helper_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 switch dispatcher driven by a state variable. EH-aware: an invoke's normal edge is split into a stub that routes through the dispatcher while its unwind edge to the landingpad is preserved; landingpad/resume blocks 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 reads program[PC], decodes the opcode, runs the block, and the block sets the next PC. This adds a decode/interpreter layer of indirection the flat switch lacks. Reuses fla's EH-aware, SSA-safe lowering, so correctness matches flattening; mutually exclusive with fla (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, and dispatcher: switch | tree | two_stage | indirect. Architecturally independent of fla/vmf (all three mutually exclusive). Emits before.ll / vm-ir.txt / after.ll for 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. brindirectbr; mode: blockaddress | direct_table | encoded_table with storage: global | stack and a per-function key (encoded targets are ptrtoint(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; call and invoke sites 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. strenc is a simple per-string XOR; strcry adds 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 before main — 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 inline select of blockaddresses.

See docs/llvm-passes.md.

18. Description of every C# pass

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.

19. PE post-processing

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.

20. Validation

  • 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 validate stage 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 host native target. On failure the engine stops the line with a diagnostic.

21. Debugging transformations

Flags: --verbose, --debug, --keep-intermediate, --dump-ir (Phase 2+), --dump-cil (Phase 6+). The engine logs a [i/N] Running <pass> line per stage.

22. Intermediate artifacts

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).

23. Deterministic builds

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.

24. Testing

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.

25. Performance impact

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.

26. Known limitations

  • 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 native target. 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/k8s backends are declared but not implemented (they fail loudly).

27. Troubleshooting

  • "configuration invalid …" — the YAML violates the schema; the message names the offending key. Run forgeveil schema to see valid keys.
  • "project input not found" — check project.input relative 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.

28. Security / scope statement

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.

29. Development guide

pip install -e ".[dev]"
pytest -q            # tests
ruff check src tests # lint
mypy src             # types

CI (.github/workflows/ci.yml) runs lint, types, tests and the toolchain-image build, and checks the committed JSON Schema is current.

30. Adding a new LLVM pass

  1. Implement the transformation in the native plugin (native/llvm-plugin/, Phase 2+), with GoogleTest + FileCheck + equivalence-harness tests.
  2. Add a thin Python wrapper pass in src/forgeveil/passes/, register it in default_registry(), and give it a canonical-order slot.
  3. Add a config slot in PassesConfig and unit tests. See docs/adding-pass.md.

31. Adding a new CIL pass

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.

32. Project structure

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.

33. Roadmap

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 2Authenticode 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 3Managed 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 4Build 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 W1ManagedMetadata→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 W2VersionInfoModel+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 W3mandatory 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 W4Semantic 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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages