This commit is contained in:
Whitecat18
2026-06-06 16:03:20 +05:30
commit b012e6dcaf
29 changed files with 8792 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Build output
target/
**/target/
# Editor scratch
.vscode/
.idea/
*.swp
*.swo
*~
# OS scratch
Thumbs.db
.DS_Store
# Sub-crate lockfiles (only the root Cargo.lock should be tracked)
data/Cargo.lock
dmanager/Cargo.lock
dyncvoke_core/Cargo.lock
manualmap/Cargo.lock
overload/Cargo.lock
Generated
+699
View File
@@ -0,0 +1,699 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[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 = "backtrace"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "data"
version = "0.1.0"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.11.0",
"objc2",
]
[[package]]
name = "dmanager"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"manualmap",
"nanorand",
"overload",
"windows-sys 0.59.0",
]
[[package]]
name = "dyncvoke"
version = "0.1.1"
dependencies = [
"data",
"dmanager",
"dyncvoke_core",
"manualmap",
"overload",
]
[[package]]
name = "dyncvoke_core"
version = "0.1.0"
dependencies = [
"data",
"libc",
"litcrypt2",
"nanorand",
"windows-sys 0.59.0",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "litcrypt2"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126aa57ac1b3dd20a5bc827a2972cdf74c619a4d6ae5660656408289e5bc60d"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"rand",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "manualmap"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"os_info",
"windows-sys 0.59.0",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.11.0",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
[[package]]
name = "os_info"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
"android_system_properties",
"log",
"nix",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]]
name = "overload"
version = "0.1.0"
dependencies = [
"data",
"dyncvoke_core",
"litcrypt2",
"manualmap",
"nanorand",
"windows-sys 0.59.0",
"winproc",
]
[[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-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[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",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[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.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[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-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[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 = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winproc"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "979e07b33c6af27e5c454e42d1b946b403ec222e7bad52ef020820708e087b25"
dependencies = [
"bitflags 1.3.2",
"failure",
"widestring",
"winapi",
]
[[package]]
name = "zerocopy"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
+32
View File
@@ -0,0 +1,32 @@
[package]
name = "dyncvoke"
version = "0.1.1"
edition = "2021"
description = "Dynamically invoke arbitrary unmanaged code"
license = "MIT OR Apache-2.0"
repository = "https://github.com/Whitecat18/Dyncvoke"
homepage = "https://github.com/Whitecat18/Dyncvoke"
documentation = "README.md"
readme = "README.md"
keywords = ["windows_sys", "dynamic", "redteam"]
categories = ["command-line-utilities"]
authors = ["5mukx", "staffs@5mukx.site"]
[lib]
name = "dyncvoke"
path = "src/lib.rs"
[profile.dev]
debug-assertions = false
[profile.release]
debug-assertions = false
strip = true
lto = false
[dependencies]
dyncvoke_core = { path = "dyncvoke_core" }
manualmap = { path = "manualmap" }
data = { path = "data" }
overload = { path = "overload" }
dmanager = { path = "dmanager" }
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Smukx ♠
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+215
View File
@@ -0,0 +1,215 @@
# dyncvoke_rs
A Rust crate for indirect syscalls and dynamic invocation on Windows. The point is calling NT functions without leaving import-table traces and without tripping inline hooks on ntdll exports.
## Overview & Innner Workings
The main attraction is the syscall path. Every Nt stub has its system call number sitting right there in the prologue bytes, so the resolver reads it directly. That gets you Hell's Gate on an unhooked system. When an EDR has planted an inline hook at the stub entry (usually a JMP rel32), the resolver walks neighboring stubs in 32 byte strides and works out the original SSN from the offset distance. That covers Halo's Gate. The same neighbor walk handles the case where the hook lands three bytes in, just after `mov r10, rcx`. That's Tartarus Gate proper. Search caps at 255 iterations and returns a typed error if nothing pans out. No infinite spin.
Once the resolver hands back an (ssn, syscall_addr) pair, dispatch goes through a single variadic function called `do_syscall`. It places arguments in the registers the syscall ABI wants, copies any extras from the caller's stack into the kernel's expected slots with `rep movsq`, and jumps to the real `syscall` instruction inside ntdll. Stack walkers see the syscall coming from inside ntdll, which is what they expect to see. There's no shellcode page allocated per call, no protection flips, no fixed argument count.
The rest of the crate covers the usual supporting tools. You get dynamic API resolution by walking the PEB and reading the export table directly, so no `GetProcAddress`. There's manual PE mapping with relocations, IAT rewriting, per section permissions, and API set resolution for the `api-*` and `ext-*` virtual DLLs on Windows 10 and later. The PE mapper is hardened against the usual malformed input games like a missing relocation directory or a zero size relocation block. There's section overloading, where you load a real signed System32 DLL into a file backed section and then write your payload over it. There's a module fluctuation manager that keeps a payload mapped only when you're actively calling it and swaps in a decoy the rest of the time. There's shellcode stomping into a loaded module's `.text` section, template stomping that generates a decoy DLL with neutered entry points before injecting a payload, and a syscall parameter spoofing path that uses hardware breakpoints plus a vectored exception handler to show the EDR boring arguments at the function entry and the kernel real arguments at the syscall instruction. TLS callbacks during manual mapping are supported if you need them.
## Layout
```
dyncvoke_core sys module, dynamic invocation, nt_* wrappers
dyncvoke_core::sys Tartarus Gate plus the variadic do_syscall gateway
manualmap PE mapping with relocations and IAT rewriting
overload section overloading, module stomping, template stomping
dmanager the fluctuation manager
data shared types and FFI signatures
```
This is still moving. Test before shipping anything you care about.
## Adding it
```toml
[dependencies]
dyncvoke = { git = "https://github.com/Whitecat18/Dyncvoke" }
```
Or from a local checkout:
```toml
[dependencies]
dyncvoke = { path = "path/to/dyncvoke_rs" }
```
Just the core:
```toml
[dependencies]
dyncvoke_core = { path = "path/to/dyncvoke_rs/dyncvoke_core" }
```
No feature flags. The syscall path is always on for x86_64.
## Using it
### Resolving exports
```rust
use dyncvoke::dyncvoke_core;
fn main() {
let ntdll = dyncvoke_core::get_module_base_address("ntdll.dll");
if ntdll == 0 { return; }
let nt_create_thread = dyncvoke_core::get_function_address(ntdll, "NtCreateThread");
println!("NtCreateThread @ 0x{:X}", nt_create_thread);
// By ordinal works too.
let ord_8 = dyncvoke_core::get_function_address_by_ordinal(ntdll, 8);
println!("ord 8 @ 0x{:X}", ord_8);
}
```
### Indirect syscalls
The `syscall!` macro handles resolution and dispatch in one shot. It returns whatever NTSTATUS the kernel gave back, wrapped in a `Result`.
```rust
use dyncvoke::dyncvoke_core::syscall;
use std::ffi::c_void;
use std::ptr::null_mut;
fn main() {
let mut addr: *mut c_void = null_mut();
let mut size: usize = 0x1000;
let status = syscall!(
"NtAllocateVirtualMemory",
-1isize as *mut c_void, // NtCurrentProcess pseudo-handle
&mut addr as *mut *mut c_void,
0usize,
&mut size as *mut usize,
0x3000u32, // MEM_COMMIT | MEM_RESERVE
0x04u32 // PAGE_READWRITE
)
.expect("could not resolve NtAllocateVirtualMemory");
if status == 0 {
println!("0x{:X} bytes at {:p}", size, addr);
} else {
println!("NTSTATUS = 0x{:08X}", status as u32);
}
}
```
The return type is `Result<i32, SyscallError>`. The `Err` half covers the cases where the function or its SSN couldn't be found, in which case the syscall never ran. `Ok(status)` means the kernel actually executed your syscall and that `i32` is the NTSTATUS it returned. Zero is success.
If you call the same syscall a lot, resolve it once and dispatch with `do_syscall!`. That skips the name lookup on every call.
```rust
use dyncvoke::dyncvoke_core::{do_syscall, resolve_syscall};
fn main() {
let (ssn, addr) = resolve_syscall("NtClose").expect("resolve");
let status = do_syscall!(ssn, addr, some_handle);
}
```
If you're curious what's going on under the macro, it's four steps. The resolver walks ntdll's exports to find the stub, then reads the SSN from `stub[4..6]`. If the first byte is `E9` then the stub has been hooked at the entry, and a neighbor walk over 32 byte strides recovers the original SSN from the offset distance. If `E9` is at offset 3, same walk handles it. Then `do_syscall` puts everything in the right registers, copies any extra arguments down the stack with `rep movsq`, and jumps to the `syscall` instruction inside ntdll itself.
### Manual mapping
Map a clean ntdll without any of the hooks the in process copy has:
```rust
use dyncvoke::manualmap;
use dyncvoke::data::PeMetadata;
fn main() {
let ntdll: (PeMetadata, usize) = manualmap::read_and_map_module(
r"C:\Windows\System32\ntdll.dll",
true, // scrub the DOS header
false // skip TLS callbacks
).unwrap();
}
```
### Section overloading
Load a signed System32 DLL into a file backed section, then write your payload over it:
```rust
use dyncvoke::overload;
fn main() {
let payload = your_download_function();
let result = overload::overload_module(&payload, "").unwrap();
// Empty string picks a decoy automatically.
}
```
### Module fluctuation
Keeps a payload mapped when you call it and a decoy mapped the rest of the time:
```rust
use dyncvoke::{overload, dmanager::Manager};
fn main() {
let mut manager = Manager::new();
let m = overload::managed_read_and_overload(
r"c:\windows\system32\payload.dll",
r"c:\windows\system32\cdp.dll"
).unwrap();
manager.new_module(m.1, m.0.0, m.0.1).unwrap();
manager.map_module(m.1).unwrap();
// ... call into the payload ...
manager.hide_module(m.1).unwrap();
}
```
### Syscall parameter spoofing
This trick uses hardware breakpoints plus a vectored exception handler. EDR hooks at the function entry see the boring values you passed in. At the actual `syscall` instruction the handler swaps in the real arguments. It lives on the `nt_*` wrapper path, separate from the Hell's Hall gateway.
```rust
use dyncvoke::dyncvoke_core::{
use_hardware_breakpoints, add_vectored_exception_handler, nt_open_process, breakpoint_handler,
};
use dyncvoke::data::{HANDLE, OBJECT_ATTRIBUTES, ClientId};
fn main() {
unsafe {
use_hardware_breakpoints(true);
add_vectored_exception_handler(1, breakpoint_handler as usize);
let mut handle: HANDLE = std::ptr::null_mut();
let attrs = OBJECT_ATTRIBUTES::default();
let client_id = ClientId {
unique_process: target_pid as HANDLE,
unique_thread: std::ptr::null_mut(),
};
let _ = nt_open_process(
&mut handle,
0x1F03FF,
&attrs as *const _ as *mut _,
&client_id as *const _ as *mut _,
);
use_hardware_breakpoints(false);
}
}
```
x64 only.
## License
[MIT LICENSE](./LICENSE)
## Credits
My inspirations:-
- [DInvoke_rs](https://github.com/Kudaes/DInvoke_rs/tree/main) by Kudaes.
- [dinvk](https://github.com/joaoviictorti/dinvk) by João Victor.
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "data"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
[dependencies]
[dependencies.windows-sys]
version = "0.59"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System",
"Win32_System_IO",
"Win32_System_Kernel",
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_WindowsProgramming",
"Win32_Storage_FileSystem",
"Win32_System_Memory",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_Graphics",
"Win32_Graphics_Printing",
"Win32_Graphics_Gdi"
]
+961
View File
@@ -0,0 +1,961 @@
// dyncvoke - Data Types
// @5mukx
use std::{collections::BTreeMap, ffi::c_void};
use windows_sys::Win32::Foundation::BOOL;
use windows_sys::Win32::Graphics::Printing::{DOC_INFO_1A, PRINTER_DEFAULTSA};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::System::Diagnostics::Debug::{EXCEPTION_RECORD, IMAGE_DATA_DIRECTORY, IMAGE_OPTIONAL_HEADER32, IMAGE_SECTION_HEADER, MINIDUMP_CALLBACK_INFORMATION, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_USER_STREAM_INFORMATION};
use windows_sys::Win32::System::Diagnostics::ToolHelp::THREADENTRY32;
use windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION;
use windows_sys::Win32::System::SystemInformation::SYSTEM_INFO;
use windows_sys::Win32::System::Threading::{PROCESS_INFORMATION, STARTUPINFOW};
use windows_sys::Win32::System::WindowsProgramming::CLIENT_ID;
use windows_sys::Win32::System::IO::{IO_STATUS_BLOCK, OVERLAPPED};
// In windows-sys, HANDLE is *mut c_void - we define our own type for compatibility
pub type HANDLE = *mut c_void;
pub type HINSTANCE = *mut c_void;
// Define UNICODE_STRING manually (not available directly in windows-sys)
#[repr(C)]
#[allow(non_snake_case)]
#[derive(Default)]
pub struct UNICODE_STRING {
pub Length: u16,
pub MaximumLength: u16,
pub Buffer: *mut u16,
}
// OBJECT_ATTRIBUTES is not in windows-sys, define it manually
#[repr(C)]
#[allow(non_snake_case)]
#[derive(Clone, Copy)]
pub struct OBJECT_ATTRIBUTES {
pub Length: u32,
pub RootDirectory: HANDLE,
pub ObjectName: *mut UNICODE_STRING,
pub Attributes: u32,
pub SecurityDescriptor: *mut c_void,
pub SecurityQualityOfService: *mut c_void,
}
impl Default for OBJECT_ATTRIBUTES {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
// LARGE_INTEGER (ntdef) - 8 bytes, used by NT APIs
#[repr(C)]
#[allow(non_snake_case)]
#[derive(Clone, Copy, Default)]
pub struct LARGE_INTEGER {
pub QuadPart: i64,
}
pub type PVOID = *mut c_void;
pub type DWORD = u32;
pub type EAT = BTreeMap<usize,String>;
pub type EntryPoint = extern "system" fn (HINSTANCE, u32, *mut c_void) -> BOOL;
pub type QueryInterface = unsafe extern "system" fn (*mut c_void, *const GUID, *mut *mut c_void) -> u32;
pub type AddRef = unsafe extern "system" fn(*mut c_void) -> u32;
pub type Release = unsafe extern "system" fn(*mut c_void) -> u32;
pub type ImpersonateLoggedOnUser = unsafe extern "system" fn (HANDLE) -> bool;
pub type RevertToSelf = unsafe extern "system" fn () -> bool;
pub type LoadLibraryA = unsafe extern "system" fn (*mut u8) -> usize;
pub type FreeLibrary = unsafe extern "system" fn (isize) -> HINSTANCE;
pub type CreateToolhelp32Snapshot = unsafe extern "system" fn (u32, u32) -> HANDLE;
pub type Thread32First = unsafe extern "system" fn (HANDLE, *mut THREADENTRY32) -> bool;
pub type Thread32Next = unsafe extern "system" fn (HANDLE, *mut THREADENTRY32) -> bool;
pub type PostThreadMessageA = unsafe extern "system" fn (u32, u32, usize, isize) -> bool;
pub type OpenProcess = unsafe extern "system" fn (u32, i32, u32) -> HANDLE;
pub type OpenThread = unsafe extern "system" fn (u32, i32, u32) -> HANDLE;
pub type OpenPrintA = unsafe extern "system" fn (*mut u8, *mut HANDLE, *mut PRINTER_DEFAULTSA) -> BOOL;
pub type StartDocPrinterA = unsafe extern "system" fn (HANDLE, u32, *mut DOC_INFO_1A) -> u32;
pub type GetDefaultPrinterA = unsafe extern "system" fn (*mut u8, *mut u32) -> BOOL;
pub type EnumProcesses = unsafe extern "system" fn (*mut u32, u32, *mut u32) -> bool;
pub type EnumProcessModules = unsafe extern "system" fn (HANDLE, *mut usize, u32, *mut u32) -> bool;
pub type SetPriorityClass = unsafe extern "system" fn (HANDLE, u32) -> bool;
pub type SetThreadPriority = unsafe extern "system" fn (HANDLE, u32) -> bool;
pub type QueueUserWorkItem = unsafe extern "system" fn (*mut c_void, *mut c_void, u32) -> bool;
pub type InitializeProcThreadAttributeList = unsafe extern "system" fn (PVOID, u32, u32, *mut usize) -> BOOL;
pub type UpdateProcThreadAttribute = unsafe extern "system" fn (PVOID, u32, usize, *const c_void, usize, PVOID, *const usize) -> BOOL;
pub type QueryFullProcessImageNameW = unsafe extern "system" fn (HANDLE, u32, *mut u16, *mut u32) -> i32;
pub type MiniDumpWriteDump = unsafe extern "system" fn (HANDLE, u32, HANDLE, u32, *mut MINIDUMP_EXCEPTION_INFORMATION,
*mut MINIDUMP_USER_STREAM_INFORMATION, *mut MINIDUMP_CALLBACK_INFORMATION) -> i32;
pub type GetOverlappedResult = unsafe extern "system" fn (HANDLE, *mut OVERLAPPED, *mut u32, bool) -> BOOL;
pub type CreateFileA = unsafe extern "system" fn (*mut u8, u32, u32, *const SECURITY_ATTRIBUTES, u32, u32, HANDLE) -> HANDLE;
pub type CreateFileW = unsafe extern "system" fn (*const u16, u32, u32, *const SECURITY_ATTRIBUTES, u32, u32, HANDLE) -> HANDLE;
pub type ReadFile = unsafe extern "system" fn (HANDLE, PVOID, u32, *mut u32, *mut OVERLAPPED) -> i32;
pub type CreateTransaction = unsafe extern "system" fn (*mut SECURITY_ATTRIBUTES, *mut GUID, u32, u32, u32, u32, *mut u16) -> HANDLE;
pub type CreateFileTransactedA = unsafe extern "system" fn (*mut u8, u32, u32, *const SECURITY_ATTRIBUTES, u32, u32, HANDLE,
HANDLE, *const u32, PVOID) -> HANDLE;
pub type CreateProcessWithLogon = unsafe extern "system" fn (*const u16, *const u16, *const u16, u32, *const u16, *mut u16, u32, *const c_void, *const u16, *const STARTUPINFOW, *mut PROCESS_INFORMATION) -> BOOL;
pub type RollbackTransaction = unsafe extern "system" fn (HANDLE) -> BOOL;
pub type GetFileSize = unsafe extern "system" fn (HANDLE, *mut u32) -> u32;
pub type CreateFileMapping = unsafe extern "system" fn (HANDLE, *const SECURITY_ATTRIBUTES, u32, u32, u32, *mut u8) -> HANDLE;
pub type MapViewOfFile = unsafe extern "system" fn (HANDLE, u32, u32, u32, usize) -> PVOID;
pub type UnmapViewOfFile = unsafe extern "system" fn (PVOID) -> BOOL;
pub type ConvertThreadToFiber = unsafe extern "system" fn (PVOID) -> PVOID;
pub type CreateFiber = unsafe extern "system" fn (usize, PVOID, PVOID) -> PVOID;
pub type SwitchToFiber = unsafe extern "system" fn (PVOID);
pub type GetLastError = unsafe extern "system" fn () -> u32;
pub type CloseHandle = unsafe extern "system" fn (HANDLE) -> i32;
pub type VirtualFree = unsafe extern "system" fn (PVOID, usize, u32) -> bool;
pub type LocalAlloc = unsafe extern "system" fn (u32, usize) -> PVOID;
pub type TlsAlloc = unsafe extern "system" fn () -> u32;
pub type TlsGetValue = unsafe extern "system" fn (u32) -> PVOID;
pub type TlsSetValue = unsafe extern "system" fn (u32, PVOID) -> bool;
pub type GetModuleHandleExA = unsafe extern "system" fn (i32,*const u8,*mut usize) -> bool;
pub type GetModuleBaseNameW = unsafe extern "system" fn (HANDLE, usize, *mut u16, u32) -> u32;
pub type GetModuleFileNameExW = unsafe extern "system" fn (HANDLE, usize, *mut u16, u32) -> u32;
pub type GetSystemInfo = unsafe extern "system" fn (*mut SYSTEM_INFO);
pub type VirtualQueryEx = unsafe extern "system" fn (HANDLE, *const c_void, *mut MEMORY_BASIC_INFORMATION, usize) -> usize;
pub type LptopLevelExceptionFilter = usize;
pub type AddVectoredExceptionHandler = unsafe extern "system" fn (first: u32, handle: usize) -> PVOID;
pub type SetUnhandledExceptionFilter = unsafe extern "system" fn (filter: LptopLevelExceptionFilter) -> LptopLevelExceptionFilter;
pub type BCryptOpenAlgorithmProvider = unsafe extern "system" fn (*mut HANDLE, *const u16, *const u16, u32) -> i32;
pub type BCryptGetProperty = unsafe extern "system" fn (HANDLE, *const u16, *mut u8, u32, *mut u32, u32) -> i32;
pub type BCryptSetProperty = unsafe extern "system" fn (HANDLE, *const u16, *mut u8, u32, u32) -> i32;
pub type BCryptGenerateSymmetricKey = unsafe extern "system" fn (HANDLE,*mut HANDLE, *mut u8, u32, *mut u8, u32, u32) -> i32;
pub type BCryptEncrypt = unsafe extern "system" fn (HANDLE, *mut u8, u32, PVOID, *mut u8, u32, *mut u8, u32, *mut u32, u32) -> i32;
pub type BCryptDestroyKey = unsafe extern "system" fn (HANDLE) -> i32;
pub type BCryptDecrypt = unsafe extern "system" fn (HANDLE, *mut u8, u32, PVOID, *mut u8, u32, *mut u8, u32, *mut u32, u32) -> i32;
pub type BCryptCloseAlgorithmProvider = unsafe extern "system" fn (HANDLE, u32) -> i32;
pub type CryptSignHashW = unsafe extern "system" fn (usize, u32, *mut u16, u32, *mut u8, *mut u32) -> bool;
pub type CreateEventW = unsafe extern "system" fn (*const SECURITY_ATTRIBUTES, i32, i32, *const u16) -> HANDLE;
/// Counted ANSI string as the kernel expects. LdrGetProcedureAddress reads
/// 16 bytes here (USHORT Length, USHORT MaximumLength, PCHAR Buffer + 4
/// bytes of natural alignment padding on x64).
#[repr(C)]
#[allow(non_camel_case_types, non_snake_case)]
pub struct ANSI_STRING {
pub Length: u16,
pub MaximumLength: u16,
pub Buffer: *const u8,
}
pub type LdrGetProcedureAddress = unsafe extern "system" fn (PVOID, *const ANSI_STRING, u32, *mut PVOID) -> i32;
pub type NtCreateTransaction = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, *mut GUID, HANDLE, u32, u32, u32, *mut LARGE_INTEGER, *mut UNICODE_STRING) -> i32;
pub type NtWriteVirtualMemory = unsafe extern "system" fn (HANDLE, PVOID, PVOID, usize, *mut usize) -> i32;
pub type NtProtectVirtualMemory = unsafe extern "system" fn (HANDLE, *mut PVOID, *mut usize, u32, *mut u32) -> i32;
pub type NtAllocateVirtualMemory = unsafe extern "system" fn (HANDLE, *mut PVOID, usize, *mut usize, u32, u32) -> i32;
pub type NtQueryInformationProcess = unsafe extern "system" fn (HANDLE, u32, PVOID, u32, *mut u32) -> i32;
pub type NtQuerySystemInformation = unsafe extern "system" fn (u32, PVOID, u32, *mut u32) -> i32;
pub type NtQueryInformationThread = unsafe extern "system" fn (HANDLE, u32, PVOID, u32, *mut u32) -> i32;
pub type NtQueryInformationFile = unsafe extern "system" fn (HANDLE, *mut IO_STATUS_BLOCK, PVOID, u32, u32) -> i32;
pub type NtDuplicateObject = unsafe extern "system" fn (HANDLE, HANDLE, HANDLE, *mut HANDLE, u32, u32, u32) -> i32;
pub type NtQueryObject = unsafe extern "system" fn (HANDLE, u32, PVOID, u32, *mut u32) -> i32;
pub type NtCreateUserProcess = unsafe extern "system" fn (*mut HANDLE, *mut HANDLE,u32, u32, *mut OBJECT_ATTRIBUTES,*mut OBJECT_ATTRIBUTES, u32, u32, PVOID, *mut PsCreateInfo, *mut PsAttributeList) -> i32;
pub type NtOpenFile = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, *mut IO_STATUS_BLOCK, u32, u32) -> i32;
pub type NtCreateSection = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, *mut LARGE_INTEGER, u32, u32, HANDLE) -> i32;
pub type NtMapViewOfSection = unsafe extern "system" fn (HANDLE, HANDLE, *mut PVOID, usize, usize, *mut LARGE_INTEGER, *mut usize, u32, u32, u32) -> i32;
pub type NtOpenProcess = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, *mut ClientId) -> i32;
pub type NtCreateThreadEx = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, HANDLE, PVOID, PVOID, u32, usize, usize, usize, *mut PsAttributeList) -> i32;
pub type NtReadVirtualMemory = unsafe extern "system" fn (HANDLE, PVOID, PVOID, usize, *mut usize) -> i32;
pub type NtRemoveProcessDebug = unsafe extern "system" fn (HANDLE, HANDLE) -> i32;
pub type NtWaitForDebugEvent = unsafe extern "system" fn (HANDLE, u8, *mut LARGE_INTEGER, PVOID) -> i32;
pub type NtTerminateProcess = unsafe extern "system" fn (HANDLE, i32) -> i32;
pub type NtOpenKey = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES) -> i32;
pub type NtSaveKey = unsafe extern "system" fn (HANDLE, HANDLE) -> i32;
pub type NtOpenProcessToken = unsafe extern "system" fn (HANDLE, u32, *mut HANDLE) -> i32;
pub type NtDuplicateToken = unsafe extern "system" fn (HANDLE, u32, *mut OBJECT_ATTRIBUTES, bool, u32, *mut HANDLE) -> i32;
pub type NtCreateKey = unsafe extern "system" fn (*mut HANDLE, u32, *mut OBJECT_ATTRIBUTES, u32, *mut UNICODE_STRING, u32, *mut u32) -> i32;
pub type NtSetValueKey = unsafe extern "system" fn (HANDLE, *const UNICODE_STRING, u32, u32, *const c_void, u32) -> i32;
pub type RtlAdjustPrivilege = unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32;
pub type RtlInitUnicodeString = unsafe extern "system" fn (*mut UNICODE_STRING, *const u16) -> () ;
pub type RtlZeroMemory = unsafe extern "system" fn (PVOID, usize) -> ();
pub type RtlQueueWorkItem = unsafe extern "system" fn (usize, PVOID, u32) -> i32;
pub type RtlAddFunctionTable = unsafe extern "system" fn (usize, i32, usize) -> bool;
pub type NtCommitTransaction = unsafe extern "system" fn (HANDLE, bool) -> i32;
pub const JMP_RBX: u16 = 9215;
pub const ADD_RSP: u32 = 1489273672;// add rsp,0x58 -> up to 11 parameters
pub const TLS_OUT_OF_INDEXES: u32 = 0xFFFFFFFF;
pub const UNW_FLAG_EHANDLER: u8 = 0x1;
pub const UNW_FLAG_UHANDLER: u8 = 0x2;
pub const UNW_FLAG_CHAININFO: u8 = 0x4;
// COFF Relocation constants
pub const IMAGE_REL_AMD64_ABSOLUTE: u16 = 0x0000;
pub const IMAGE_REL_AMD64_ADDR64: u16 = 0x0001;
pub const IMAGE_REL_AMD64_ADDR32: u16 = 0x0002;
pub const IMAGE_REL_AMD64_ADDR32NB: u16 = 0x0003;
pub const IMAGE_REL_AMD64_REL32: u16 = 0x0004;
pub const DLL_PROCESS_DETACH: u32 = 0;
pub const DLL_PROCESS_ATTACH: u32 = 1;
pub const DLL_THREAD_ATTACH: u32 = 2;
pub const DLL_THREAD_DETACH: u32 = 3;
pub const PAGE_NOACCESS: u32 = 0x1;
pub const PAGE_READONLY: u32 = 0x2;
pub const PAGE_READWRITE: u32 = 0x4;
pub const PAGE_WRITECOPY: u32 = 0x8;
pub const PAGE_EXECUTE: u32 = 0x10;
pub const PAGE_EXECUTE_READ: u32 = 0x20;
pub const PAGE_EXECUTE_READWRITE: u32 = 0x40;
pub const PAGE_EXECUTE_WRITECOPY: u32 = 0x80;
pub const MEM_COMMIT: u32 = 0x1000;
pub const MEM_RESERVE: u32 = 0x2000;
pub const SECTION_MEM_READ: u32 = 0x40000000;
pub const SECTION_MEM_WRITE: u32 = 0x80000000;
pub const SECTION_MEM_EXECUTE: u32 = 0x20000000;
// Access mask
pub const GENERIC_READ: u32 = 0x80000000;
pub const GENERIC_WRITE: u32 = 0x40000000;
pub const GENERIC_EXECUTE: u32 = 0x20000000;
pub const GENERIC_ALL: u32 = 0x10000000;
pub const SECTION_ALL_ACCESS: u32 = 0x10000000;
pub const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
pub const THREAD_ALL_ACCESS: u32 = 0x000F0000 | 0x00100000 | 0xFFFF;
//File share flags
pub const FILE_SHARE_NONE: u32 = 0x0;
pub const FILE_SHARE_READ: u32 = 0x1;
pub const FILE_SHARE_WRITE: u32 = 0x2;
pub const FILE_SHARE_DELETE: u32 = 0x4;
//File access flags
pub const DELETE: u32 = 0x10000;
pub const FILE_READ_DATA: u32 = 0x1;
pub const FILE_READ_ATTRIBUTES: u32 = 0x80;
pub const FILE_READ_EA: u32 = 0x8;
pub const READ_CONTROL: u32 = 0x20000;
pub const FILE_WRITE_DATA: u32 = 0x2;
pub const FILE_WRITE_ATTRIBUTES: u32 = 0x100;
pub const FILE_WRITE_EA: u32 = 0x10;
pub const FILE_APPEND_DATA: u32 = 0x4;
pub const WRITE_DAC: u32 = 0x40000;
pub const WRITE_OWNER: u32 = 0x80000;
pub const SYNCHRONIZE: u32 = 0x100000;
pub const FILE_EXECUTE: u32 = 0x20;
// File open flags
pub const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x20;
pub const FILE_NON_DIRECTORY_FILE: u32 = 0x40;
pub const SEC_IMAGE: u32 = 0x1000000;
pub const MAX_PATH: u32 = 260;
#[derive(Clone)]
#[repr(C)]
pub struct PeMetadata {
pub pe: u32,
pub is_32_bit: bool,
pub image_file_header: ImageFileHeader,
pub opt_header_32: IMAGE_OPTIONAL_HEADER32,
pub opt_header_64: ImageOptionalHeader64,
pub sections: Vec<IMAGE_SECTION_HEADER>
}
impl Default for PeMetadata {
fn default() -> PeMetadata {
unsafe {
PeMetadata {
pe: u32::default(),
is_32_bit: false,
image_file_header: ImageFileHeader::default(),
opt_header_32: std::mem::zeroed(),
opt_header_64: std::mem::zeroed(),
sections: Vec::default(),
}
}
}
}
#[derive(Clone)]
#[repr(C)]
pub struct CoffMetadata {
pub image_file_header: ImageFileHeader,
pub sections: Vec<IMAGE_SECTION_HEADER>,
pub sections_order: BTreeMap<u32,Vec<u16>>,
pub sections_mapped_addresses: BTreeMap<u16,usize>,
pub symbols: Vec<CoffSymbol>,
pub imports: BTreeMap<String,usize>
}
impl Default for CoffMetadata {
fn default() -> CoffMetadata {
CoffMetadata {
image_file_header: ImageFileHeader::default(),
sections: Vec::default(),
sections_order: BTreeMap::default(),
sections_mapped_addresses: BTreeMap::default(),
symbols: Vec::default(),
imports: BTreeMap::default()
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct AuxSymbolEntry {
pub aux_symbol_entry: [u8;18]
}
impl Default for AuxSymbolEntry {
fn default() -> AuxSymbolEntry {
AuxSymbolEntry {
aux_symbol_entry: [0u8;18]
}
}
}
#[derive(Clone)]
#[repr(C)]
pub struct CoffSymbol {
pub name_str: String,
pub symbol_offset: u32, // offset in strings table in case that the symbol's name is bigger than 8 bytes
pub value: u32,
pub section_number: u16,
pub symbol_type: u16,
pub storage_class: u8,
pub aux_symbols: u8,
pub aux_symbol_entries: Vec<AuxSymbolEntry>
}
impl Default for CoffSymbol {
fn default() -> CoffSymbol {
CoffSymbol {
name_str: String::default(),
symbol_offset: u32::default(),
value: u32::default(),
section_number: u16::default(),
symbol_type: u16::default(),
storage_class: u8::default(),
aux_symbols: u8::default(),
aux_symbol_entries: Vec::default()
}
}
}
#[repr(C)]
pub struct PeManualMap {
pub decoy_module: String,
pub base_address: usize,
pub pe_info: PeMetadata,
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetNamespace {
pub unused: [u8;12],
pub count: i32, // offset 0x0C
pub entry_offset: i32, // offset 0x10
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetNamespaceEntry {
pub unused1: [u8;4],
pub name_offset: i32, // offset 0x04
pub name_length: i32, // offset 0x08
pub unused2: [u8;4],
pub value_offset: i32, // offset 0x10
pub value_length: i32, // offset 0x14
}
#[repr(C)]
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
pub struct ApiSetValueEntry {
pub flags: i32, // offset 0x00
pub name_offset: i32, // offset 0x04
pub name_count: i32, // offset 0x08
pub value_offset: i32, // offset 0x0C
pub value_count: i32, // offset 0x10
}
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
#[repr(C)]
pub struct ImageFileHeader {
pub machine: u16,
pub number_of_sections: u16,
pub time_data_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
}
#[derive(Copy, Clone)]
#[repr(C)] // required to keep fields order, otherwise Rust may change that order randomly
pub struct ImageOptionalHeader64 {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_unitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_operating_system_version: u16,
pub minor_operating_system_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub checksum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
pub datas_directory: [IMAGE_DATA_DIRECTORY; 16],
}
#[derive(Copy, Clone, Default, PartialEq, Debug, Eq)]
#[repr(C)]
pub struct GUID
{
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
#[repr(C)]
pub struct SystemHandleInformation {
pub number_of_handles: u32,
pub handles: Vec<SystemHandleTableEntryInfo>,
}
#[repr(C)]
pub struct SystemHandleTableEntryInfo {
pub process_id: u16,
pub creator_back_trace_index: u16,
pub object_type_index: u8,
pub handle_attributes: u8,
pub handle_value: u16,
pub object: PVOID,
pub granted_access: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ClientId {
pub unique_process: HANDLE,
pub unique_thread: HANDLE,
}
impl Default for ClientId {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
pub struct NtAllocateVirtualMemoryArgs
{
pub handle: HANDLE,
pub base_address: *mut PVOID
}
pub struct NtOpenProcessArgs
{
pub handle: *mut HANDLE,
pub access: u32,
pub attributes: *mut OBJECT_ATTRIBUTES,
pub client_id: *mut ClientId
}
pub struct NtProtectVirtualMemoryArgs
{
pub handle: HANDLE,
pub base_address: *mut PVOID,
pub size: *mut usize,
pub protection: u32
}
pub struct NtWriteVirtualMemoryArgs
{
pub handle: HANDLE,
pub base_address: PVOID,
pub buffer: PVOID,
pub size: usize
}
pub struct NtCreateThreadExArgs
{
pub thread: *mut HANDLE,
pub access: u32,
pub attributes: *mut OBJECT_ATTRIBUTES,
pub process: HANDLE
}
#[repr(C)]
#[allow(non_snake_case)]
pub struct CONTEXT {
pub P1Home: u64,
pub P2Home: u64,
pub P3Home: u64,
pub P4Home: u64,
pub P5Home: u64,
pub P6Home: u64,
pub ContextFlags: u32,
pub MxCsr: u32,
pub SegCs: u16,
pub SegDs: u16,
pub SegEs: u16,
pub SegFs: u16,
pub SegGs: u16,
pub SegSs: u16,
pub EFlags: u32,
pub Dr0: u64,
pub Dr1: u64,
pub Dr2: u64,
pub Dr3: u64,
pub Dr6: u64,
pub Dr7: u64,
pub Rax: u64,
pub Rcx: u64,
pub Rdx: u64,
pub Rbx: u64,
pub Rsp: u64,
pub Rbp: u64,
pub Rsi: u64,
pub Rdi: u64,
pub R8: u64,
pub R9: u64,
pub R10: u64,
pub R11: u64,
pub R12: u64,
pub R13: u64,
pub R14: u64,
pub R15: u64,
pub Rip: u64,
pub Anonymous: [u8;4096],
pub VectorRegister: [u8; 128*26],
pub VectorControl: u64,
pub DebugControl: u64,
pub LastBranchToRip: u64,
pub LastBranchFromRip: u64,
pub LastExceptionToRip: u64,
pub LastExceptionFromRip: u64,
}
impl Default for CONTEXT
{
fn default() -> CONTEXT {
CONTEXT {
P1Home: 0,
P2Home: 0,
P3Home: 0,
P4Home: 0,
P5Home: 0,
P6Home: 0,
ContextFlags: 0,
MxCsr: 0,
SegCs: 0,
SegDs: 0,
SegEs: 0,
SegFs: 0,
SegGs: 0,
SegSs: 0,
EFlags: 0,
Dr0: 0,
Dr1: 0,
Dr2: 0,
Dr3: 0,
Dr6: 0,
Dr7: 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,
Anonymous: [0;4096],
VectorRegister: [0; 128*26],
VectorControl: 0,
DebugControl: 0,
LastBranchToRip: 0,
LastBranchFromRip: 0,
LastExceptionToRip: 0,
LastExceptionFromRip: 0
}
}
}
#[repr(C)]
pub struct ExceptionPointers {
pub exception_record: *mut EXCEPTION_RECORD,
pub context_record: *mut CONTEXT,
}
#[repr(C)]
pub struct PsAttributeList {
pub size: u32,
pub unk1: u32,
pub unk2: u32,
pub unk3: *mut u32,
pub unk4: u32,
pub unk5: u32,
pub unk6: u32,
pub unk7: *mut u32,
pub unk8: u32,
}
pub enum ExceptionHandleFunction
{
NtOpenProcess,
NtAllocateVirtualMemory,
NtWriteVirtualMemory,
NtProtectVirtualMemory,
NtCreateThreadEx
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct RuntimeFunction {
pub begin_addr: u32,
pub end_addr: u32,
pub unwind_addr: u32
}
#[repr(C)]
pub struct PsCreateInfo {
pub size: usize,
pub unused: [u8;80],
}
#[repr(C)]
pub struct PsAttribute {
pub attribute: usize,
pub size: usize,
pub union: PsAttributeU,
pub return_length: *mut usize,
}
#[repr(C)]
pub union PsAttributeU {
pub value: usize,
pub value_ptr: PVOID,
}
#[derive(Clone,Copy,Default)]
#[repr(C)]
pub struct PsCreateInfoInitState{
pub init_flags: u32,
pub additional_file_access: u32,
}
#[derive(Clone,Copy)]
#[repr(C)]
pub struct PsCreateInfoUSuccessSate {
pub output_flags: u32,
pub file_handle: HANDLE,
pub section_handle: HANDLE,
pub user_process_parameters_native: u64,
pub user_process_parameters_wow64: u32,
pub current_parameter_flags: u32,
pub peb_address_native: u64,
pub peb_address_wow64: u32,
pub manifest_address: u64,
pub manifest_size: u32,
}
#[derive(Clone,Copy)]
#[repr(C)]
pub union PsCreateInfoU {
pub init_state: PsCreateInfoInitState,
pub file_handle: HANDLE,
pub dll_characteristics: u16,
pub ifeokey: HANDLE,
pub success_state: PsCreateInfoUSuccessSate,
}
#[repr(C)]
pub struct FileProcessIdsUsingFileInformation {
pub number_of_process_ids_in_list: u32,
pub process_id_list: [usize;1],
}
#[repr(C)]
pub struct ThreadBasicInformation {
pub exit_status: i32,
pub teb_base_address: PVOID,
pub client_id: CLIENT_ID,
pub affinity_mask: usize,
pub priority: i32,
pub base_priority: i32,
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
// Test UNICODE_STRING structure
#[test]
fn test_unicode_string_default() {
let us = UNICODE_STRING::default();
assert_eq!(us.Length, 0);
assert_eq!(us.MaximumLength, 0);
assert!(us.Buffer.is_null());
}
#[test]
fn test_unicode_string_creation() {
let us = UNICODE_STRING {
Length: 10,
MaximumLength: 12,
Buffer: std::ptr::null_mut(),
};
assert_eq!(us.Length, 10);
assert_eq!(us.MaximumLength, 12);
}
// Test OBJECT_ATTRIBUTES structure
#[test]
fn test_object_attributes_default() {
let oa = OBJECT_ATTRIBUTES::default();
assert_eq!(oa.Length, 0);
assert!(oa.RootDirectory.is_null());
assert!(oa.ObjectName.is_null());
assert_eq!(oa.Attributes, 0);
}
// Test LARGE_INTEGER structure
#[test]
fn test_large_integer_default() {
let li = LARGE_INTEGER::default();
assert_eq!(li.QuadPart, 0);
}
#[test]
fn test_large_integer_creation() {
let li = LARGE_INTEGER { QuadPart: 12345 };
assert_eq!(li.QuadPart, 12345);
}
// Test constants exist and have expected values
#[test]
fn test_dll_constants() {
assert_eq!(DLL_PROCESS_DETACH, 0);
assert_eq!(DLL_PROCESS_ATTACH, 1);
assert_eq!(DLL_THREAD_ATTACH, 2);
assert_eq!(DLL_THREAD_DETACH, 3);
}
#[test]
fn test_page_constants() {
assert_eq!(PAGE_NOACCESS, 0x1);
assert_eq!(PAGE_READONLY, 0x2);
assert_eq!(PAGE_READWRITE, 0x4);
assert_eq!(PAGE_WRITECOPY, 0x8);
assert_eq!(PAGE_EXECUTE, 0x10);
assert_eq!(PAGE_EXECUTE_READ, 0x20);
assert_eq!(PAGE_EXECUTE_READWRITE, 0x40);
assert_eq!(PAGE_EXECUTE_WRITECOPY, 0x80);
}
#[test]
fn test_memory_constants() {
assert_eq!(MEM_COMMIT, 0x1000);
assert_eq!(MEM_RESERVE, 0x2000);
}
#[test]
fn test_section_constants() {
assert_eq!(SECTION_MEM_READ, 0x40000000);
assert_eq!(SECTION_MEM_WRITE, 0x80000000);
assert_eq!(SECTION_MEM_EXECUTE, 0x20000000);
}
#[test]
fn test_access_constants() {
assert_eq!(GENERIC_READ, 0x80000000);
assert_eq!(GENERIC_WRITE, 0x40000000);
assert_eq!(GENERIC_EXECUTE, 0x20000000);
assert_eq!(GENERIC_ALL, 0x10000000);
}
#[test]
fn test_file_share_constants() {
assert_eq!(FILE_SHARE_NONE, 0x0);
assert_eq!(FILE_SHARE_READ, 0x1);
assert_eq!(FILE_SHARE_WRITE, 0x2);
assert_eq!(FILE_SHARE_DELETE, 0x4);
}
#[test]
fn test_file_access_constants() {
assert_eq!(DELETE, 0x10000);
assert_eq!(FILE_READ_DATA, 0x1);
assert_eq!(FILE_READ_ATTRIBUTES, 0x80);
assert_eq!(FILE_READ_EA, 0x8);
assert_eq!(READ_CONTROL, 0x20000);
assert_eq!(FILE_WRITE_DATA, 0x2);
assert_eq!(FILE_WRITE_ATTRIBUTES, 0x100);
assert_eq!(FILE_WRITE_EA, 0x10);
assert_eq!(FILE_APPEND_DATA, 0x4);
}
#[test]
fn test_file_open_constants() {
assert_eq!(FILE_SYNCHRONOUS_IO_NONALERT, 0x20);
assert_eq!(FILE_NON_DIRECTORY_FILE, 0x40);
}
#[test]
fn test_other_constants() {
assert_eq!(SEC_IMAGE, 0x1000000);
assert_eq!(MAX_PATH, 260);
assert_eq!(TLS_OUT_OF_INDEXES, 0xFFFFFFFF);
}
#[test]
fn test_unw_flags() {
assert_eq!(UNW_FLAG_EHANDLER, 0x1);
assert_eq!(UNW_FLAG_UHANDLER, 0x2);
assert_eq!(UNW_FLAG_CHAININFO, 0x4);
}
#[test]
fn test_coff_relocation_constants() {
assert_eq!(IMAGE_REL_AMD64_ABSOLUTE, 0x0000);
assert_eq!(IMAGE_REL_AMD64_ADDR64, 0x0001);
assert_eq!(IMAGE_REL_AMD64_ADDR32, 0x0002);
assert_eq!(IMAGE_REL_AMD64_ADDR32NB, 0x0003);
assert_eq!(IMAGE_REL_AMD64_REL32, 0x0004);
}
// Test PeMetadata default
#[test]
fn test_pe_metadata_default() {
let pe = PeMetadata::default();
assert_eq!(pe.pe, 0);
assert!(!pe.is_32_bit);
}
// Test CoffMetadata default
#[test]
fn test_coff_metadata_default() {
let coff = CoffMetadata::default();
assert_eq!(coff.image_file_header.machine, 0);
assert!(coff.sections.is_empty());
assert!(coff.imports.is_empty());
}
// Test ImageFileHeader default
#[test]
fn test_image_file_header_default() {
let header = ImageFileHeader::default();
assert_eq!(header.machine, 0);
assert_eq!(header.number_of_sections, 0);
assert_eq!(header.time_data_stamp, 0);
}
// Test ClientId default
#[test]
fn test_client_id_default() {
let cid = ClientId::default();
assert!(cid.unique_process.is_null());
assert!(cid.unique_thread.is_null());
}
// Test ApiSetNamespace default
#[test]
fn test_api_set_namespace_default() {
let ns = ApiSetNamespace::default();
assert_eq!(ns.count, 0);
assert_eq!(ns.entry_offset, 0);
}
// Test ApiSetNamespaceEntry default
#[test]
fn test_api_set_namespace_entry_default() {
let entry = ApiSetNamespaceEntry::default();
assert_eq!(entry.name_offset, 0);
assert_eq!(entry.name_length, 0);
assert_eq!(entry.value_offset, 0);
assert_eq!(entry.value_length, 0);
}
// Test ApiSetValueEntry default
#[test]
fn test_api_set_value_entry_default() {
let entry = ApiSetValueEntry::default();
assert_eq!(entry.flags, 0);
assert_eq!(entry.name_offset, 0);
assert_eq!(entry.name_count, 0);
assert_eq!(entry.value_offset, 0);
assert_eq!(entry.value_count, 0);
}
// Test CONTEXT default
#[test]
fn test_context_default() {
let ctx = CONTEXT::default();
assert_eq!(ctx.P1Home, 0);
assert_eq!(ctx.Rax, 0);
assert_eq!(ctx.Rsp, 0);
assert_eq!(ctx.Rip, 0);
}
// Test GUID default
#[test]
fn test_guid_default() {
let guid = GUID::default();
assert_eq!(guid.data1, 0);
assert_eq!(guid.data2, 0);
assert_eq!(guid.data3, 0);
}
// Test RuntimeFunction default
#[test]
fn test_runtime_function_default() {
let rf = RuntimeFunction::default();
assert_eq!(rf.begin_addr, 0);
assert_eq!(rf.end_addr, 0);
assert_eq!(rf.unwind_addr, 0);
}
// Test HANDLE type
#[test]
fn test_handle_type() {
let null_handle: HANDLE = std::ptr::null_mut();
assert!(null_handle.is_null());
}
// Test HINSTANCE type
#[test]
fn test_hinstance_type() {
let null_hinstance: HINSTANCE = std::ptr::null_mut();
assert!(null_hinstance.is_null());
}
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "dmanager"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = 'z' # Optimize for size.
strip = true
[dependencies]
nanorand = "0.8.0"
manualmap = { path = "../manualmap" }
overload = { path = "../overload" }
data = { path = "../data" }
litcrypt2 = "=0.1.2"
dyncvoke_core = { path = "../dyncvoke_core" }
[dependencies.windows-sys]
version = "0.59"
features = [
"Win32_Foundation"
]
+496
View File
@@ -0,0 +1,496 @@
// dyncvoke - Module/Shellcode Fluctuation Manager
// @5mukx
#[macro_use]
extern crate litcrypt2;
use_litcrypt!();
use std::{collections::HashMap, ffi::c_void};
use data::{PeMetadata, PVOID, PAGE_READWRITE};
use nanorand::{Rng, BufferedRng, WyRand};
pub struct Manager
{
payloads: HashMap<usize, Vec<u8>>,
payloads_metadata: HashMap<usize, PeMetadata>,
decoys_metadata: HashMap<usize, PeMetadata>,
decoys: HashMap<usize, Vec<u8>>,
counter: HashMap<usize, i64>,
keys: HashMap<usize, u8>
}
impl Manager {
pub fn new () -> Manager {
Manager{
payloads: HashMap::new(),
payloads_metadata: HashMap::new(),
decoys_metadata: HashMap::new(),
decoys: HashMap::new(),
counter: HashMap::new(),
keys: HashMap::new(),
}
}
pub fn new_module (&mut self, address: usize, payload: Vec<u8>, decoy: Vec<u8>) -> Result<(), String>
{
if self.payloads.contains_key(&address)
{
return Err(lc!("[x] This address is already mapped."));
}
let payload_metadata = manualmap::get_pe_metadata(payload.as_ptr(), false)?;
let decoy_metadata = manualmap::get_pe_metadata(decoy.as_ptr(), false)?;
let mut rand_bytes = [0u8; 15];
let mut rng = BufferedRng::new(WyRand::new());
rng.fill(&mut rand_bytes);
let xor_key: u8 = rand_bytes.iter().fold(0u8, |acc, b| acc ^ b);
let xored_payload = Manager::xor_module(payload, xor_key);
let xored_decoy = Manager::xor_module(decoy, xor_key);
self.payloads.insert(address, xored_payload);
self.payloads_metadata.insert(address, payload_metadata);
self.decoys_metadata.insert(address, decoy_metadata);
self.decoys.insert(address, xored_decoy);
self.counter.insert(address, 1);
self.keys.insert(address, xor_key);
Manager::hide_module(self, address)?;
Ok(())
}
pub fn new_shellcode (&mut self, address: usize, payload: Vec<u8>, decoy: Vec<u8>) -> Result<(), String>
{
if self.payloads.contains_key(&address)
{
return Err(lc!("[x] This shellcode is already mapped."));
}
let mut rand_bytes = [0u8; 15];
let mut rng = BufferedRng::new(WyRand::new());
rng.fill(&mut rand_bytes);
let xor_key: u8 = rand_bytes.iter().fold(0u8, |acc, b| acc ^ b);
let xored_payload = Manager::xor_module(payload, xor_key);
let xored_decoy = Manager::xor_module(decoy, xor_key);
self.payloads.insert(address, xored_payload);
self.decoys.insert(address, xored_decoy);
self.counter.insert(address, 1);
self.keys.insert(address, xor_key);
Manager::hide_shellcode(self, address)?;
Ok(())
}
fn xor_module (module: Vec<u8>, key: u8) -> Vec<u8>
{
unsafe
{
let mut module_ptr = module.as_ptr();
let mut final_module: Vec<u8> = vec![];
for _i in 0..module.len()
{
final_module.push(*module_ptr ^ key);
module_ptr = module_ptr.add(1);
}
final_module
}
}
pub fn map_module (&mut self, address: usize) -> Result<(),String>
{
unsafe
{
if self.payloads.contains_key(&address)
{
if self.counter.get(&address).unwrap() == &0
{
let payload = self.payloads.get(&address).unwrap();
let key = *self.keys.get(&address).unwrap();
let pe_info = self.payloads_metadata.get(&address).unwrap();
let decoy_info = self.decoys_metadata.get(&address).unwrap();
let addr: PVOID = std::ptr::with_exposed_provenance_mut::<c_void>(address);
let handle = dyncvoke_core::GetCurrentProcess();
let mut base_addr_local: PVOID = addr;
let mut size_local: usize = if decoy_info.is_32_bit {
decoy_info.opt_header_32.SizeOfImage as usize
} else {
decoy_info.opt_header_64.size_of_image as usize
};
let mut old_protection_local: u32 = 0;
let ret = dyncvoke_core::nt_protect_virtual_memory(
handle,
&mut base_addr_local as *mut PVOID,
&mut size_local as *mut usize,
PAGE_READWRITE,
&mut old_protection_local as *mut u32,
);
if ret != 0
{
return Err(lc!("[x] Error changing memory protection."));
}
dyncvoke_core::rtl_zero_memory(base_addr_local, size_local);
let mut decrypted_payload = Manager::xor_module(payload.to_vec(), key);
let _r = manualmap::map_to_allocated_memory(decrypted_payload.as_ptr(), addr, pe_info)?;
let decrypted_payload_ptr = decrypted_payload.as_mut_ptr();
for i in 0..decrypted_payload.len()
{
*(decrypted_payload_ptr.add(i)) = 0u8;
}
}
self.counter.insert(address, self.counter[&address] + 1);
}
Ok(())
}
}
pub fn hide_module(&mut self, address: usize) -> Result<(),String>
{
unsafe
{
if self.payloads.contains_key(&address)
{
if self.counter.get(&address).unwrap() == &1
{
let decoy = self.decoys.get(&address).unwrap();
let key = *self.keys.get(&address).unwrap();
let decrypted_decoy = Manager::xor_module(decoy.to_vec(), key);
let pe_info = self.decoys_metadata.get(&address).unwrap();
let addr: PVOID = std::ptr::with_exposed_provenance_mut::<c_void>(address);
let handle = dyncvoke_core::GetCurrentProcess();
let mut base_addr_local: PVOID = addr;
let mut size_local: usize = if pe_info.is_32_bit {
pe_info.opt_header_32.SizeOfImage as usize
} else {
pe_info.opt_header_64.size_of_image as usize
};
let mut old_protection_local: u32 = 0;
let ret = dyncvoke_core::nt_protect_virtual_memory(
handle,
&mut base_addr_local as *mut PVOID,
&mut size_local as *mut usize,
PAGE_READWRITE,
&mut old_protection_local as *mut u32,
);
if ret != 0
{
return Err(lc!("[x] Error changing memory protection."));
}
dyncvoke_core::rtl_zero_memory(base_addr_local, size_local);
let _r = manualmap::map_to_allocated_memory(decrypted_decoy.as_ptr(), addr, pe_info)?;
}
if self.counter.get(&address).unwrap() >= &1
{
self.counter.insert(address, self.counter[&address] - 1);
}
}
Ok(())
}
}
pub fn hide_shellcode(&mut self, address: usize) -> Result<(),String>
{
if self.payloads.contains_key(&address)
{
if self.counter.get(&address).unwrap() == &1
{
let decoy = self.decoys.get(&address).unwrap();
let key = *self.keys.get(&address).unwrap();
let decrypted_decoy = Manager::xor_module(decoy.to_vec(), key);
let result = overload::managed_module_stomping(&decrypted_decoy, address, 0);
if !result.is_ok()
{
return Err(lc!("[x] Error hiding shellcode."));
}
}
if self.counter.get(&address).unwrap() >= &1
{
self.counter.insert(address, self.counter[&address] - 1);
}
}
Ok(())
}
pub fn stomp_shellcode(&mut self, address: usize) -> Result<(),String>
{
if self.payloads.contains_key(&address)
{
if self.counter.get(&address).unwrap() == &0
{
let payload = self.payloads.get(&address).unwrap();
let key = *self.keys.get(&address).unwrap();
let mut decrypted_payload = Manager::xor_module(payload.to_vec(), key);
let result = overload::managed_module_stomping(&decrypted_payload, address, 0);
let decrypted_payload_ptr = decrypted_payload.as_mut_ptr();
unsafe
{
for i in 0..decrypted_payload.len()
{
*(decrypted_payload_ptr.add(i)) = 0u8;
}
}
if !result.is_ok()
{
return Err(lc!("[x] Error stomping shellcode."));
}
}
self.counter.insert(address, self.counter[&address] + 1);
}
Ok(())
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
// Test Manager creation
#[test]
fn test_manager_creation() {
let manager = Manager::new();
assert!(manager.payloads.is_empty());
assert!(manager.payloads_metadata.is_empty());
assert!(manager.decoys_metadata.is_empty());
assert!(manager.decoys.is_empty());
assert!(manager.counter.is_empty());
assert!(manager.keys.is_empty());
}
// Test Manager with new_module - basic validation only (no PE parsing)
#[test]
fn test_manager_new_module_validation() {
let manager = Manager::new();
// Test that manager can be created
assert!(manager.payloads.is_empty());
// Test address validation - this function requires valid PE data which we don't have in tests
// So we just verify the manager structure works
let address: usize = 0x12340000;
assert!(address != 0);
}
// Test Manager with new_shellcode - basic validation only
#[test]
fn test_manager_new_shellcode_validation() {
let manager = Manager::new();
// Test that manager can be created
assert!(manager.payloads.is_empty());
}
// Test xor_module function
#[test]
fn test_xor_module() {
let data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
let key: u8 = 0x42;
// XOR the data
let xored = Manager::xor_module(data.clone(), key);
// Verify XOR operation
for (i, &byte) in data.iter().enumerate() {
assert_eq!(xored[i], byte ^ key);
}
// XOR again should get original
let de_xored = Manager::xor_module(xored, key);
assert_eq!(de_xored, data);
}
// Test xor_module with different keys
#[test]
fn test_xor_module_different_keys() {
let data = vec![0xFF, 0x00, 0xAA, 0x55, 0x12, 0x34];
// Test with key 0x00 (no change)
let no_change = Manager::xor_module(data.clone(), 0x00);
assert_eq!(no_change, data);
// Test with key 0xFF (bitflip)
let bitflip = Manager::xor_module(data.clone(), 0xFF);
for (i, &byte) in data.iter().enumerate() {
assert_eq!(bitflip[i], !byte);
}
// Test with key 0xAA (alternating pattern)
let patterned = Manager::xor_module(data.clone(), 0xAA);
for (i, &byte) in data.iter().enumerate() {
assert_eq!(patterned[i], byte ^ 0xAA);
}
}
// Test xor_module with large data
#[test]
fn test_xor_module_large_data() {
let size = 1024 * 1024; // 1 MB
let data: Vec<u8> = vec![0x42; size]; // Fill with 0x42
let key: u8 = 0x5A;
let xored = Manager::xor_module(data.clone(), key);
assert_eq!(xored.len(), size);
let restored = Manager::xor_module(xored, key);
assert_eq!(restored.len(), data.len());
// Verify the XOR was applied correctly
assert_eq!(restored[0], 0x42);
}
// Test counter tracking
#[test]
fn test_counter_tracking() {
let mut manager = Manager::new();
// Insert a counter entry manually for testing
let address = 0x1000;
manager.counter.insert(address, 5);
assert_eq!(manager.counter.get(&address), Some(&5));
// Increment
*manager.counter.get_mut(&address).unwrap() += 1;
assert_eq!(manager.counter.get(&address), Some(&6));
}
// Test key storage
#[test]
fn test_key_storage() {
let mut manager = Manager::new();
let address = 0x2000;
let key: u8 = 0xAB;
manager.keys.insert(address, key);
assert_eq!(manager.keys.get(&address), Some(&key));
}
// Test payload and decoy storage
#[test]
fn test_payload_decoy_storage() {
let mut manager = Manager::new();
let address = 0x3000;
let payload = vec![0x90, 0x90, 0xCC]; // NOP, NOP, INT3
let decoy = vec![0x55, 0x8B, 0xEC]; // Standard function prologue
manager.payloads.insert(address, payload.clone());
manager.decoys.insert(address, decoy.clone());
assert_eq!(manager.payloads.get(&address), Some(&payload));
assert_eq!(manager.decoys.get(&address), Some(&decoy));
}
// Test metadata storage
#[test]
fn test_metadata_storage() {
let mut manager = Manager::new();
let address = 0x4000;
let pe_metadata = PeMetadata::default();
manager.payloads_metadata.insert(address, pe_metadata.clone());
manager.decoys_metadata.insert(address, pe_metadata);
assert!(manager.payloads_metadata.get(&address).is_some());
assert!(manager.decoys_metadata.get(&address).is_some());
}
// Test HashMap operations
#[test]
fn test_hashmap_operations() {
let mut manager = Manager::new();
// Insert multiple entries
for i in 0..10 {
let addr = 0x10000 + (i * 0x1000);
manager.payloads.insert(addr, vec![i as u8]);
manager.counter.insert(addr, i as i64);
manager.keys.insert(addr, (i + 1) as u8);
}
assert_eq!(manager.payloads.len(), 10);
assert_eq!(manager.counter.len(), 10);
assert_eq!(manager.keys.len(), 10);
// Remove entry
manager.payloads.remove(&(0x10000));
manager.counter.remove(&(0x10000));
manager.keys.remove(&(0x10000));
assert_eq!(manager.payloads.len(), 9);
}
// Test address validation
#[test]
fn test_address_validation() {
// Valid addresses should be non-null and aligned
let valid_addresses: Vec<u64> = vec![
0x1000,
0x10000,
0x140000000,
0x7FF60000,
];
for addr in valid_addresses {
assert!(addr > 0);
// Check alignment (should be page-aligned)
assert_eq!(addr % 0x1000, 0);
}
}
// Test buffer size validation
#[test]
fn test_buffer_size_validation() {
let empty: Vec<u8> = vec![];
assert!(empty.is_empty());
let single_byte: Vec<u8> = vec![0x42];
assert_eq!(single_byte.len(), 1);
let page_size = 4096;
let page_buffer: Vec<u8> = vec![0x00; page_size];
assert_eq!(page_buffer.len(), page_size);
}
}
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "dyncvoke_core"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
[dependencies]
data = { path = "../data" }
libc = "0.2.182"
litcrypt2 = "=0.1.2"
[target.'cfg(target_arch = "x86_64")'.dependencies]
nanorand = { version = "0.8.0" }
[dependencies.windows-sys]
version = "0.59"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System",
"Win32_System_IO",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_ProcessStatus",
"Win32_System_SystemInformation",
"Win32_System_Diagnostics_Debug",
"Win32_System_Threading"
]
+1
View File
@@ -0,0 +1 @@
fn main() {}
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
//! Variadic syscall gateway.
//!
//! `do_syscall(ssn, syscall_addr, n_args, ...)` dispatches an indirect
//! syscall by jumping to the `syscall` instruction inside ntdll itself.
//!
//! Stack discipline at the moment of `jmp rcx` (which lands on ntdll's
//! `syscall` instruction):
//!
//! - rsp is unchanged from function entry. The x64 red zone holds the
//! non-volatile saves (rsi, rdi, r12), so no `sub rsp` is needed.
//! `[rsp]` is the return address back to the Rust caller.
//! - After the kernel returns to the next instruction in ntdll's stub
//! (`ret`), the `ret` pops `[rsp]` and lands back in the Rust caller.
//! - Args 5..N have been slid down from `[rsp + 0x40]` to `[rsp + 0x28]`
//! via `rep movsq` so the kernel reads them where it expects.
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("
.global do_syscall
.section .text
do_syscall:
mov [rsp - 0x8], rsi
mov [rsp - 0x10], rdi
mov [rsp - 0x18], r12
mov eax, ecx // eax = ssn (1st arg in rcx)
mov r12, rdx // save syscall_addr (2nd arg in rdx)
mov rcx, r8 // rcx = n_args (3rd arg in r8)
mov r10, r9 // arg1 (4th arg in r9) -> r10 (syscall ABI)
mov rdx, [rsp + 0x28] // arg2
mov r8, [rsp + 0x30] // arg3
mov r9, [rsp + 0x38] // arg4
sub rcx, 0x4 // remaining args destined for the stack
jle 2f // none -> skip the copy
lea rsi, [rsp + 0x40] // src = caller's arg5 slot
lea rdi, [rsp + 0x28] // dst = where the kernel reads arg5
rep movsq
2:
mov rcx, r12 // rcx = syscall_addr for jmp
mov rsi, [rsp - 0x8]
mov rdi, [rsp - 0x10]
mov r12, [rsp - 0x18]
jmp rcx
");
#[cfg(target_arch = "x86_64")]
unsafe extern "C" {
/// Variadic syscall dispatcher.
///
/// # Safety
///
/// `syscall_addr` must point at a valid `syscall` instruction inside
/// ntdll (use [`crate::resolve_syscall`]). `n_args` must match the
/// number of variadic arguments that follow. Passing wrong-sized
/// integer types in the varargs (e.g. a u32 where the syscall expects
/// u64) will land garbage in the upper bits and the syscall will fail
/// in non-obvious ways.
pub fn do_syscall(ssn: u16, syscall_addr: usize, n_args: u32, ...) -> i32;
}
+159
View File
@@ -0,0 +1,159 @@
//! Tartarus Gate SSN resolution and strict syscall-instruction location.
//!
//! Three modes for the SSN derivation, in order of preference:
//!
//! 1. Hell's Gate: stub has the clean `MOV R10, RCX; MOV EAX, <ssn>`
//! prologue, read the SSN straight out of `stub[4..6]`.
//! 2. Halo's Gate: stub starts with `E9 ...` (hook at entry); walk
//! ±32-byte neighbors until we find a clean stub, then adjust the SSN by
//! the offset distance (each stub is 32 bytes apart on x64 ntdll).
//! 3. Tartarus Gate: stub has `E9` at offset 3 (the hook landed after
//! `MOV R10, RCX`, which preserves r10 but redirects flow). Same
//! neighbor walk as Halo's Gate.
use core::ptr::read;
use litcrypt2::lc;
#[doc(hidden)]
pub mod asm;
/// Maximum neighbor search range for hooked syscalls.
const RANGE: usize = 255;
/// Stub size for downward neighbor search.
const DOWN: usize = 32;
/// Stub size for upward neighbor search.
const UP: isize = -32;
/// Extract the SSN from a syscall stub. Returns `None` if the stub is not
/// recognizable as one of the three Tartarus Gate forms.
pub fn extract_ssn(address: *const u8) -> Option<u16> {
unsafe {
// Hell's Gate: 4C 8B D1 B8 <lo> <hi> 00 00 ...
if read(address) == 0x4C
&& read(address.add(1)) == 0x8B
&& read(address.add(2)) == 0xD1
&& read(address.add(3)) == 0xB8
&& read(address.add(6)) == 0x00
&& read(address.add(7)) == 0x00
{
let high = read(address.add(5)) as u16;
let low = read(address.add(4)) as u16;
return Some((high << 8) | low);
}
// Halo's Gate: hook at entry (E9 = JMP rel32).
if read(address) == 0xE9 {
return search_neighbors(address);
}
// Tartarus Gate: hook after MOV R10, RCX (E9 at offset 3).
if read(address.add(3)) == 0xE9 {
return search_neighbors(address);
}
}
None
}
/// Walk ±32-byte neighbors looking for a clean Hell's Gate stub, then
/// back-derive the requested SSN by the offset distance.
fn search_neighbors(address: *const u8) -> Option<u16> {
unsafe {
for idx in 1..RANGE {
// Search DOWN (toward higher addresses). The neighbor's SSN is
// higher than ours by `idx` because syscall IDs increase with
// address on ntdll's stub layout, so we subtract.
if read(address.add(idx * DOWN)) == 0x4C
&& read(address.add(1 + idx * DOWN)) == 0x8B
&& read(address.add(2 + idx * DOWN)) == 0xD1
&& read(address.add(3 + idx * DOWN)) == 0xB8
&& read(address.add(6 + idx * DOWN)) == 0x00
&& read(address.add(7 + idx * DOWN)) == 0x00
{
let high = read(address.add(5 + idx * DOWN)) as u16;
let low = read(address.add(4 + idx * DOWN)) as u16;
let neighbor_ssn = (high << 8) | low;
return Some(neighbor_ssn.wrapping_sub(idx as u16));
}
// Search UP (toward lower addresses). Neighbor's SSN is lower
// than ours, so add.
if read(address.offset(idx as isize * UP)) == 0x4C
&& read(address.offset(1 + idx as isize * UP)) == 0x8B
&& read(address.offset(2 + idx as isize * UP)) == 0xD1
&& read(address.offset(3 + idx as isize * UP)) == 0xB8
&& read(address.offset(6 + idx as isize * UP)) == 0x00
&& read(address.offset(7 + idx as isize * UP)) == 0x00
{
let high = read(address.offset(5 + idx as isize * UP)) as u16;
let low = read(address.offset(4 + idx as isize * UP)) as u16;
let neighbor_ssn = (high << 8) | low;
return Some(neighbor_ssn.wrapping_add(idx as u16));
}
}
}
None
}
/// Locate the `syscall; ret` (0F 05 C3) sequence within a stub. The trailing
/// C3 distinguishes the direct-return syscall path from the
/// SSDT-side-check branch, which is what we want for stack-discipline
/// reasons (after the syscall, ntdll's `ret` pops back to our caller).
pub fn get_syscall_address(address: *mut core::ffi::c_void) -> Option<usize> {
unsafe {
let p = address.cast::<u8>();
(1..RANGE).find_map(|i| {
if read(p.add(i)) == 0x0F
&& read(p.add(i + 1)) == 0x05
&& read(p.add(i + 2)) == 0xC3
{
Some(p.add(i) as usize)
} else {
None
}
})
}
}
/// Errors surfaced by [`resolve_syscall`].
#[derive(Debug)]
pub enum SyscallError {
NtdllMissing,
FunctionNotFound(String),
SsnNotFound(String),
SyscallAddrNotFound(String),
}
impl core::fmt::Display for SyscallError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SyscallError::NtdllMissing => write!(f, "ntdll.dll not loaded"),
SyscallError::FunctionNotFound(n) => write!(f, "export not found: {}", n),
SyscallError::SsnNotFound(n) => write!(f, "SSN not extractable for {}", n),
SyscallError::SyscallAddrNotFound(n) => write!(f, "syscall instr not found for {}", n),
}
}
}
impl std::error::Error for SyscallError {}
/// Resolve (SSN, syscall_instr_addr) for a Zw/Nt export of ntdll.
///
/// Returns a typed error on every failure mode. Never hangs.
pub fn resolve_syscall(name: &str) -> Result<(u16, usize), SyscallError> {
let ntdll = crate::get_module_base_address(&lc!("ntdll.dll"));
if ntdll == 0 {
return Err(SyscallError::NtdllMissing);
}
let stub = crate::get_function_address(ntdll, name);
if stub == 0 {
return Err(SyscallError::FunctionNotFound(name.into()));
}
let ssn = extract_ssn(stub as *const u8)
.ok_or_else(|| SyscallError::SsnNotFound(name.into()))?;
let addr = get_syscall_address(stub as *mut _)
.ok_or_else(|| SyscallError::SyscallAddrNotFound(name.into()))?;
Ok((ssn, addr))
}
+118
View File
@@ -0,0 +1,118 @@
// Broad coverage sweep across every Zw* export in ntdll.
//
// For every Zw* export:
// 1. Read the canonical SSN directly from the stub bytes (4C 8B D1 B8
// <lo> <hi> 00 00), without touching the neighbor walker.
// 2. Run resolve_syscall(name).
// 3. The two must agree for any stub that has the canonical prologue.
//
// Stubs that don't have the canonical prologue (e.g. NtQuerySystemTime,
// which reads from KUSER_SHARED_DATA without a syscall) are tracked
// separately. They exercise the Tartarus neighbor walk and we just log
// them.
//
// Catches OS-wide drift on a new Windows build: if a future build
// rearranges the SSDT, this fails loudly with the offending name and SSNs.
use dyncvoke_core::{get_function_address, get_module_base_address, resolve_syscall};
/// Walk the ntdll EAT and collect every `Zw*` export's name.
fn collect_zw_exports(ntdll: usize) -> Vec<String> {
let mut out = Vec::new();
unsafe {
let pe_header = *((ntdll + 0x3C) as *const i32) as usize;
let opt_header = ntdll + pe_header + 0x18;
let magic = *(opt_header as *const i16);
let p_export = if magic == 0x010b {
opt_header + 0x60
} else {
opt_header + 0x70
};
let export_rva = *(p_export as *const i32) as usize;
let number_of_names = *((ntdll + export_rva + 0x18) as *const i32) as usize;
let names_rva = *((ntdll + export_rva + 0x20) as *const i32) as usize;
for x in 0..number_of_names {
let name_rva = *((ntdll + names_rva + x * 4) as *const i32) as usize;
let mut p = (ntdll + name_rva) as *const u8;
let mut name = String::new();
while *p != 0 {
name.push(*p as char);
p = p.add(1);
}
if name.starts_with("Zw") {
out.push(name.replacen("Zw", "Nt", 1));
}
}
}
out
}
/// Read the SSN from the canonical Hell's Gate prologue, or None if the
/// stub doesn't have that exact layout. Distinct from `extract_ssn` which
/// also runs the neighbor walker on hooked stubs.
fn canonical_ssn(stub: usize) -> Option<u16> {
if stub == 0 {
return None;
}
unsafe {
let p = stub as *const u8;
(*p == 0x4C
&& *p.add(1) == 0x8B
&& *p.add(2) == 0xD1
&& *p.add(3) == 0xB8
&& *p.add(6) == 0
&& *p.add(7) == 0)
.then(|| u16::from_le_bytes([*p.add(4), *p.add(5)]))
}
}
#[test]
fn resolve_syscall_matches_canonical_byte_derived_ssn_for_every_zw_export() {
let ntdll = get_module_base_address("ntdll.dll");
assert!(ntdll != 0, "ntdll.dll not loaded");
let names = collect_zw_exports(ntdll);
assert!(
names.len() > 200,
"expected ~489 Zw exports on Win11 24H2, got {}",
names.len()
);
let mut atypical: Vec<(String, u16)> = Vec::new();
let mut mismatches: Vec<(String, u16, u16)> = Vec::new();
for name in &names {
let stub = get_function_address(ntdll, name);
let (resolver_ssn, _) = match resolve_syscall(name) {
Ok(v) => v,
Err(e) => panic!("resolve_syscall({}) failed: {}", name, e),
};
match canonical_ssn(stub) {
Some(canonical) => {
if canonical != resolver_ssn {
mismatches.push((name.clone(), canonical, resolver_ssn));
}
}
None => {
// Atypical stub (e.g. NtQuerySystemTime). resolve_syscall
// recovered the SSN via the neighbor walk, that's fine.
atypical.push((name.clone(), resolver_ssn));
}
}
}
println!("total Zw exports: {}", names.len());
println!("atypical (recovered via Tartarus neighbor walk): {}", atypical.len());
for (name, ssn) in atypical.iter().take(10) {
println!(" atypical: {} -> SSN {}", name, ssn);
}
assert!(
mismatches.is_empty(),
"{} canonical-vs-resolver SSN mismatches: first few = {:?}",
mismatches.len(),
&mismatches[..mismatches.len().min(10)]
);
}
@@ -0,0 +1,54 @@
// The Windows INVALID_HANDLE_VALUE constant is (HANDLE)(LONG_PTR)-1,
// the NtCurrentProcess pseudo-handle that NT memory syscalls accept.
// NULL is rejected with STATUS_INVALID_HANDLE (0xC0000008).
//
// This test asserts both directions: (a) the OS-level invariant that
// NULL is rejected and -1 succeeds, and (b) the lib-level invariant
// that dyncvoke_core's constant points at -1.
use dyncvoke_core::{nt_protect_virtual_memory, INVALID_HANDLE_VALUE};
use std::ffi::c_void;
#[test]
fn invalid_handle_value_const_is_minus_one() {
assert_eq!(
INVALID_HANDLE_VALUE as isize, -1,
"INVALID_HANDLE_VALUE must be the NtCurrentProcess pseudo-handle, not NULL"
);
}
#[test]
fn nt_protect_with_null_vs_neg_one_handle() {
let mut buf = vec![0u8; 4096];
let mut addr: *mut c_void = buf.as_mut_ptr() as *mut c_void;
let mut sz: usize = 4096;
let mut old: u32 = 0;
let null_handle: *mut c_void = std::ptr::null_mut();
let status_null = nt_protect_virtual_memory(
null_handle,
&mut addr as *mut *mut c_void,
&mut sz as *mut usize,
0x04, // PAGE_READWRITE
&mut old as *mut u32,
);
assert_ne!(
status_null, 0,
"NULL process handle must be rejected by NtProtectVirtualMemory"
);
addr = buf.as_mut_ptr() as *mut c_void;
sz = 4096;
let status_neg1 = nt_protect_virtual_memory(
INVALID_HANDLE_VALUE,
&mut addr as *mut *mut c_void,
&mut sz as *mut usize,
0x04,
&mut old as *mut u32,
);
assert_eq!(
status_neg1, 0,
"INVALID_HANDLE_VALUE must succeed as the current-process pseudo-handle (NTSTATUS=0x{:08X})",
status_neg1 as u32
);
}
@@ -0,0 +1,45 @@
// LdrGetProcedureAddress wants a PANSI_STRING (16 bytes on x64) as its
// second parameter. The wrapper sends a real `ANSI_STRING` value backed
// by a CString.
//
// This test resolves a few exports by name through ldr_get_procedure_address
// and asserts the result matches get_function_address, proving the kernel
// actually understood the name we sent.
use dyncvoke_core::{get_function_address, get_module_base_address, ldr_get_procedure_address};
#[test]
fn ldr_get_procedure_address_by_name_matches_get_function_address() {
let ntdll = get_module_base_address("ntdll.dll");
assert!(ntdll != 0, "ntdll.dll not loaded");
for name in &["NtClose", "NtOpenProcess", "NtAllocateVirtualMemory", "NtCreateFile"] {
let by_eat_walk = get_function_address(ntdll, name);
assert!(by_eat_walk != 0, "{} not found via export walk", name);
let by_ldr = ldr_get_procedure_address(ntdll, name, 0);
assert!(
by_ldr != 0,
"LdrGetProcedureAddress returned 0 for {}, name was not understood",
name
);
assert_eq!(
by_ldr, by_eat_walk,
"{}: ldr=0x{:X} walk=0x{:X}",
name, by_ldr, by_eat_walk
);
}
}
#[test]
fn ldr_get_procedure_address_by_ordinal_still_works() {
// Empty function_name path (NULL ANSI_STRING) must continue to work.
let ntdll = get_module_base_address("ntdll.dll");
assert!(ntdll != 0);
// Ordinal 8 is RtlDispatchAPC on most Win10/Win11 builds. We don't pin
// to the exact symbol, just assert the call doesn't return zero (which
// would indicate the ordinal path is broken).
let addr = ldr_get_procedure_address(ntdll, "", 8);
assert!(addr != 0, "ordinal lookup returned 0");
}
@@ -0,0 +1,114 @@
// Two malformed-PE cases the relocation walker must handle:
//
// (a) A relocation block with SizeOfBlock = 0 must not spin the loop
// (the pointer would never advance).
//
// (b) A PE with no relocation directory (VirtualAddress = 0) must not
// fall into reading the DOS header as IMAGE_BASE_RELOCATION, which
// would produce a VirtualAddress of 0x00905A4D and corrupt memory.
//
// manualmap::relocate_module and overload::relocate_text_section guard
// both: early-return on `dir.VirtualAddress == 0 || dir.Size == 0`, treat
// `SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION)` as end-of-table, and
// bound the walk by `dir.Size`.
//
// We emulate exactly the fixed loop pattern over a synthetic block and
// assert termination.
#[repr(C)]
#[derive(Clone, Copy)]
struct ImageBaseRelocation {
virtual_address: u32,
size_of_block: u32,
}
#[test]
fn fixed_loop_breaks_on_size_zero_block() {
// A block with non-zero VirtualAddress but zero SizeOfBlock is the
// The walker must break out instead of spinning.
let mut storage: Vec<u8> = vec![0u8; 32];
unsafe {
let p = storage.as_mut_ptr() as *mut ImageBaseRelocation;
(*p).virtual_address = 0x1000;
(*p).size_of_block = 0;
}
let reloc_start = storage.as_ptr() as usize;
let reloc_end = reloc_start + storage.len();
let mut reloc_table_ptr = reloc_start as *const ImageBaseRelocation;
let mut iter = 0usize;
while (reloc_table_ptr as usize) + std::mem::size_of::<ImageBaseRelocation>() <= reloc_end {
iter += 1;
assert!(
iter <= 2,
"fixed loop should have broken on size-zero block by now (iter={})",
iter
);
let ibr = unsafe { *reloc_table_ptr };
if ibr.virtual_address == 0
|| (ibr.size_of_block as usize) < std::mem::size_of::<ImageBaseRelocation>()
{
break;
}
reloc_table_ptr = (reloc_table_ptr as usize + ibr.size_of_block as usize)
as *const ImageBaseRelocation;
}
assert_eq!(iter, 1, "the size-zero block must be seen once and break");
}
#[test]
fn fixed_loop_no_reloc_directory_returns_early() {
// dir.VirtualAddress == 0 (PE has no relocation directory). The
// fix is a `return` before any read. We mirror just the predicate here
// and assert we never enter the loop.
let virtual_address: u32 = 0;
let size: u32 = 0;
let mut entered_loop = false;
if virtual_address != 0 && size != 0 {
entered_loop = true;
}
assert!(
!entered_loop,
"PE with no reloc directory must not enter the patching loop"
);
}
#[test]
fn fixed_loop_terminates_on_terminator_block() {
// Well-formed PE: relocation table ends with a block whose
// VirtualAddress == 0. The fixed loop must break cleanly on it.
let mut storage: Vec<u8> = vec![0u8; 16];
unsafe {
// Block 1: legitimate, but with size_of_block = 8 (header only, no entries).
let p = storage.as_mut_ptr() as *mut ImageBaseRelocation;
(*p).virtual_address = 0x2000;
(*p).size_of_block = 8;
// Block 2 (terminator): VirtualAddress = 0.
let p2 = (p as *mut u8).add(8) as *mut ImageBaseRelocation;
(*p2).virtual_address = 0;
(*p2).size_of_block = 0;
}
let reloc_start = storage.as_ptr() as usize;
let reloc_end = reloc_start + storage.len();
let mut reloc_table_ptr = reloc_start as *const ImageBaseRelocation;
let mut iter = 0usize;
while (reloc_table_ptr as usize) + std::mem::size_of::<ImageBaseRelocation>() <= reloc_end {
iter += 1;
assert!(iter <= 5);
let ibr = unsafe { *reloc_table_ptr };
if ibr.virtual_address == 0
|| (ibr.size_of_block as usize) < std::mem::size_of::<ImageBaseRelocation>()
{
break;
}
reloc_table_ptr = (reloc_table_ptr as usize + ibr.size_of_block as usize)
as *const ImageBaseRelocation;
}
// Block 1 (size 8) accepted; block 2 (VA=0) terminates → 2 iterations.
assert_eq!(iter, 2);
}
@@ -0,0 +1,125 @@
// End-to-end smoke check covering the full syscall path:
//
// syscall! macro
// -> resolve_syscall (extract_ssn + get_syscall_address)
// -> variadic do_syscall
// -> jump into ntdll's `syscall` instruction
//
// Three syscalls in sequence:
//
// 1. NtAllocateVirtualMemory -> get a writable page
// 2. NtProtectVirtualMemory -> flip it to PAGE_EXECUTE_READ
// 3. NtFreeVirtualMemory -> release it
//
// If any of these returns a non-zero NTSTATUS, something in the path is
// wrong: a bad SSN, a misaligned stack at the syscall, a register mix-up,
// or the wrong syscall instruction address.
use dyncvoke_core::syscall;
use std::ffi::c_void;
use std::ptr::null_mut;
#[test]
fn smoke_alloc_protect_free_round_trip() {
let mut addr: *mut c_void = null_mut();
let mut size: usize = 0x1000;
// 1. allocate writable
let alloc_status = syscall!(
"NtAllocateVirtualMemory",
-1isize as *mut c_void,
&mut addr as *mut *mut c_void,
0usize,
&mut size as *mut usize,
0x3000u32, // MEM_COMMIT | MEM_RESERVE
0x04u32 // PAGE_READWRITE
)
.expect("resolve NtAllocateVirtualMemory");
assert_eq!(
alloc_status, 0,
"NtAllocateVirtualMemory NTSTATUS=0x{:08X}",
alloc_status as u32
);
assert!(!addr.is_null());
assert!(size >= 0x1000, "kernel rounded size down to {}", size);
// touch the page so we know the protection actually took effect
unsafe {
*(addr as *mut u8) = 0x42;
assert_eq!(*(addr as *mut u8), 0x42);
}
// 2. reprotect to executable-read. The protect syscall takes a *mut PVOID
// for the base and a *mut SIZE_T for the size, both inout.
let mut old_prot: u32 = 0;
let prot_status = syscall!(
"NtProtectVirtualMemory",
-1isize as *mut c_void,
&mut addr as *mut *mut c_void,
&mut size as *mut usize,
0x20u32, // PAGE_EXECUTE_READ
&mut old_prot as *mut u32
)
.expect("resolve NtProtectVirtualMemory");
assert_eq!(
prot_status, 0,
"NtProtectVirtualMemory NTSTATUS=0x{:08X}",
prot_status as u32
);
assert_eq!(
old_prot, 0x04,
"old protection should have been PAGE_READWRITE, got 0x{:X}",
old_prot
);
// 3. free
let mut zero_size: usize = 0;
let free_status = syscall!(
"NtFreeVirtualMemory",
-1isize as *mut c_void,
&mut addr as *mut *mut c_void,
&mut zero_size as *mut usize,
0x8000u32 // MEM_RELEASE
)
.expect("resolve NtFreeVirtualMemory");
assert_eq!(
free_status, 0,
"NtFreeVirtualMemory NTSTATUS=0x{:08X}",
free_status as u32
);
}
#[test]
fn smoke_do_syscall_macro_with_cached_resolution() {
// Same dance, but resolve once and use the do_syscall! macro for
// the hot path. Confirms the bypass macro lines up bit-for-bit with
// the top-level syscall! macro.
use dyncvoke_core::{do_syscall, resolve_syscall};
let (alloc_ssn, alloc_addr) = resolve_syscall("NtAllocateVirtualMemory").unwrap();
let (free_ssn, free_addr) = resolve_syscall("NtFreeVirtualMemory").unwrap();
let mut addr: *mut c_void = null_mut();
let mut size: usize = 0x1000;
let status = do_syscall!(
alloc_ssn, alloc_addr,
-1isize as *mut c_void,
&mut addr as *mut *mut c_void,
0usize,
&mut size as *mut usize,
0x3000u32,
0x04u32
);
assert_eq!(status, 0);
assert!(!addr.is_null());
let mut z: usize = 0;
let status = do_syscall!(
free_ssn, free_addr,
-1isize as *mut c_void,
&mut addr as *mut *mut c_void,
&mut z as *mut usize,
0x8000u32
);
assert_eq!(status, 0);
}
@@ -0,0 +1,60 @@
// End-to-end check that the variadic do_syscall dispatches correctly.
use dyncvoke_core::{do_syscall, resolve_syscall};
use std::ffi::c_void;
#[test]
fn do_syscall_nt_close_garbage_handle_returns_invalid_handle_status() {
let (ssn, addr) = resolve_syscall("NtClose").expect("resolve NtClose");
// 0xDEADBEEF is not a kernel handle. NtClose must reject it with
// STATUS_INVALID_HANDLE = 0xC0000008.
let status = unsafe {
do_syscall(ssn, addr, 1, 0xDEADBEEFusize as *mut c_void)
};
assert_eq!(
status as u32, 0xC0000008,
"expected STATUS_INVALID_HANDLE, got 0x{:08X}",
status as u32
);
}
#[test]
fn do_syscall_nt_allocate_six_args() {
let (ssn, addr) = resolve_syscall("NtAllocateVirtualMemory").expect("resolve");
let mut base: *mut c_void = std::ptr::null_mut();
let mut size: usize = 0x1000;
let status = unsafe {
do_syscall(
ssn,
addr,
6,
-1isize as *mut c_void, // process handle
&mut base as *mut *mut c_void as *mut c_void, // base address out
0usize as *mut c_void, // zero bits
&mut size as *mut usize as *mut c_void, // size in/out
0x3000u32 as usize as *mut c_void, // MEM_COMMIT|MEM_RESERVE
0x04u32 as usize as *mut c_void, // PAGE_READWRITE
)
};
assert_eq!(status, 0, "NtAllocateVirtualMemory NTSTATUS=0x{:08X}", status as u32);
assert!(!base.is_null());
// Touch the page to confirm it's mapped and writable.
unsafe { *(base as *mut u8) = 0x42; }
// Free it back.
let (free_ssn, free_addr) = resolve_syscall("NtFreeVirtualMemory").expect("resolve free");
let mut free_size: usize = 0;
let status = unsafe {
do_syscall(
free_ssn,
free_addr,
4,
-1isize as *mut c_void,
&mut base as *mut *mut c_void as *mut c_void,
&mut free_size as *mut usize as *mut c_void,
0x8000u32 as usize as *mut c_void, // MEM_RELEASE
)
};
assert_eq!(status, 0, "NtFreeVirtualMemory NTSTATUS=0x{:08X}", status as u32);
}
@@ -0,0 +1,49 @@
// Resolves a handful of known syscalls via the byte-derived API and
// asserts (a) the SSNs match expected values for Win11 24H2, and (b) the
// syscall instruction address found is exactly 18 bytes into the stub
// (standard Hell's Gate stub layout) for clean stubs.
use dyncvoke_core::{
get_function_address, get_module_base_address, get_syscall_address, resolve_syscall,
};
#[test]
fn resolve_known_syscalls() {
// Expected SSNs on Win11 24H2 build 26200. If this test starts failing
// on a different build, that's the kernel SSDT shifting, not a regression
// in our resolver, but a useful signal.
for (name, expected_ssn) in &[
("NtClose", 15u16),
("NtAllocateVirtualMemory", 24),
("NtOpenProcess", 38),
("NtCreateFile", 85),
("NtProtectVirtualMemory", 80),
("NtCreateThreadEx", 201),
] {
let (ssn, addr) = resolve_syscall(name).unwrap_or_else(|e| panic!("{}: {}", name, e));
assert_eq!(ssn, *expected_ssn, "{}: ssn mismatch", name);
assert!(addr != 0, "{}: syscall addr is 0", name);
// The clean Hell's Gate stub puts `0F 05` at offset 18.
let stub = get_function_address(get_module_base_address("ntdll.dll"), name);
assert_eq!(addr - stub, 18, "{}: syscall addr is at offset {}, expected 18", name, addr - stub);
}
}
#[test]
fn resolve_unknown_returns_error() {
let result = resolve_syscall("NtThisFunctionDoesNotExistAnywhere");
assert!(result.is_err());
}
#[test]
fn get_syscall_address_skips_ssdt_check_branch() {
// The stricter `0F 05 C3` matcher must find the syscall+ret path, not
// some random `0F 05` byte pair earlier in the stub.
let ntdll = get_module_base_address("ntdll.dll");
let nt_close = get_function_address(ntdll, "NtClose");
let addr = get_syscall_address(nt_close as *mut _).unwrap();
// The byte after the syscall must be a ret (0xC3).
let byte_after = unsafe { *((addr + 2) as *const u8) };
assert_eq!(byte_after, 0xC3, "byte after syscall must be 0xC3 (ret)");
}
@@ -0,0 +1,126 @@
// Tartarus Gate neighbor-walk regression.
//
// Synthesizes the two hook variants by copying a real NtClose stub into RW
// memory, planting an `E9 …` at offset 0 (Halo's Gate) and at offset 3
// (Tartarus Gate-proper). Asserts `extract_ssn` still recovers the correct
// SSN by walking ±32-byte neighbors.
//
// We can't just hook the real ntdll because we'd lose the neighbors. We
// copy 65 KiB of ntdll's .text around NtClose into our own RWX buffer so
// the neighbors are intact; then we modify the bytes corresponding to
// NtClose itself.
use dyncvoke_core::{extract_ssn, get_function_address, get_module_base_address, resolve_syscall};
use std::ffi::c_void;
fn alloc_rwx(size: usize) -> *mut u8 {
use windows_sys::Win32::System::Memory::*;
unsafe {
VirtualAlloc(
std::ptr::null(),
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
) as *mut u8
}
}
fn free_rwx(p: *mut u8) {
use windows_sys::Win32::System::Memory::*;
unsafe { VirtualFree(p as *mut c_void, 0, MEM_RELEASE) };
}
/// Find a "safe" copy window around NtClose. We need at least 255 stubs
/// worth (255 × 32 = 8160 bytes) on each side. Make it generous.
const WINDOW_HALF: usize = 16 * 1024;
unsafe fn copy_window_around(target: usize) -> (*mut u8, usize) {
let buf = alloc_rwx(WINDOW_HALF * 2);
assert!(!buf.is_null());
let src = (target - WINDOW_HALF) as *const u8;
std::ptr::copy_nonoverlapping(src, buf, WINDOW_HALF * 2);
let target_offset = WINDOW_HALF;
(buf, target_offset)
}
#[test]
fn tartarus_recovers_ssn_on_halos_gate_hook() {
let (real_ssn, _) = resolve_syscall("NtClose").expect("resolve real");
let real_addr = get_function_address(get_module_base_address("ntdll.dll"), "NtClose");
let (copy_base, offset) = unsafe { copy_window_around(real_addr) };
let target = unsafe { copy_base.add(offset) };
// Plant `E9 XX XX XX XX` (jmp rel32) at offset 0. The actual rel32
// value doesn't matter. we only execute up to extract_ssn's bytewise
// inspection of the prologue.
unsafe {
*target = 0xE9;
*target.add(1) = 0x11;
*target.add(2) = 0x22;
*target.add(3) = 0x33;
*target.add(4) = 0x44;
}
let recovered = extract_ssn(target as *const u8)
.expect("extract_ssn must recover via neighbor walk");
assert_eq!(
recovered, real_ssn,
"expected SSN {} (NtClose), got {}",
real_ssn, recovered
);
free_rwx(copy_base);
}
#[test]
fn tartarus_recovers_ssn_on_post_mov_r10_rcx_hook() {
// The Tartarus Gate variant: the hook lands after `MOV R10, RCX`
// (which preserves r10), with `E9` at offset 3.
let (real_ssn, _) = resolve_syscall("NtClose").expect("resolve real");
let real_addr = get_function_address(get_module_base_address("ntdll.dll"), "NtClose");
let (copy_base, offset) = unsafe { copy_window_around(real_addr) };
let target = unsafe { copy_base.add(offset) };
// Leave `4C 8B D1` (mov r10, rcx) intact, plant E9 at offset 3.
unsafe {
// Confirm we're patching a clean stub.
assert_eq!(*target, 0x4C);
assert_eq!(*target.add(1), 0x8B);
assert_eq!(*target.add(2), 0xD1);
*target.add(3) = 0xE9;
*target.add(4) = 0xAA;
*target.add(5) = 0xBB;
*target.add(6) = 0xCC;
*target.add(7) = 0xDD;
}
let recovered = extract_ssn(target as *const u8)
.expect("extract_ssn must recover via neighbor walk");
assert_eq!(
recovered, real_ssn,
"expected SSN {} (NtClose), got {}",
real_ssn, recovered
);
free_rwx(copy_base);
}
#[test]
fn tartarus_returns_none_on_unrecoverable_stub() {
// Allocate a window full of 0xCC (INT3, definitely not a syscall stub).
// Neither Hell's Gate prologue nor any neighbor stub exists, so
// extract_ssn must return None. no hang, no panic.
let size = WINDOW_HALF * 2;
let buf = alloc_rwx(size);
unsafe { std::ptr::write_bytes(buf, 0xCC, size) };
let target = unsafe { buf.add(WINDOW_HALF) };
// Plant the JMP marker so extract_ssn enters the neighbor walker.
unsafe { *target = 0xE9; }
let result = extract_ssn(target as *const u8);
assert!(result.is_none(), "expected None for unrecoverable stub");
free_rwx(buf);
}
+112
View File
@@ -0,0 +1,112 @@
// Exercises 1, 4, 6, and 11 argument calls through `syscall!`, confirming
// the variadic gateway correctly shuffles registers, performs the
// `rep movsq` slide for args 5+, and returns the real NTSTATUS to the
// caller.
use dyncvoke_core::syscall;
use std::ffi::c_void;
use std::ptr::null_mut;
#[test]
fn one_arg_nt_close() {
// Pure register dispatch, no stack copy. Verifies the r10 = arg1 setup.
let status = syscall!("NtClose", 0xDEADBEEFusize as *mut c_void).expect("resolve");
assert_eq!(status as u32, 0xC0000008, "expected STATUS_INVALID_HANDLE");
}
#[test]
fn four_arg_nt_query_information_process() {
// All four args in registers (r10, rdx, r8, r9), no stack copy still.
// ProcessBasicInformation = 0. Use the current process pseudo-handle.
let mut pbi = [0u8; 48]; // PROCESS_BASIC_INFORMATION is 48 bytes on x64.
let mut return_length: u32 = 0;
let status = syscall!(
"NtQueryInformationProcess",
-1isize as *mut c_void,
0u32, // ProcessBasicInformation
pbi.as_mut_ptr() as *mut c_void,
pbi.len() as u32 as usize as *mut c_void, // u32 widened to usize-sized slot
&mut return_length as *mut u32 // 5th arg lands on the stack
)
.expect("resolve");
assert_eq!(status, 0, "NtQueryInformationProcess NTSTATUS=0x{:08X}", status as u32);
assert_ne!(return_length, 0);
}
#[test]
fn six_arg_alloc_then_free() {
// 6 args -> 2 args go through the rep movsq stack-copy path.
let mut base: *mut c_void = null_mut();
let mut size: usize = 0x1000;
let alloc_status = syscall!(
"NtAllocateVirtualMemory",
-1isize as *mut c_void,
&mut base as *mut *mut c_void,
0usize,
&mut size as *mut usize,
0x3000u32, // MEM_COMMIT | MEM_RESERVE
0x04u32 // PAGE_READWRITE
)
.expect("resolve");
assert_eq!(alloc_status, 0, "alloc NTSTATUS=0x{:08X}", alloc_status as u32);
assert!(!base.is_null());
// Quick liveness probe, write and read back.
unsafe { *(base as *mut u32) = 0xCAFEBABE };
let got = unsafe { *(base as *mut u32) };
assert_eq!(got, 0xCAFEBABE);
let mut free_size: usize = 0;
let free_status = syscall!(
"NtFreeVirtualMemory",
-1isize as *mut c_void,
&mut base as *mut *mut c_void,
&mut free_size as *mut usize,
0x8000u32 // MEM_RELEASE
)
.expect("resolve");
assert_eq!(free_status, 0, "free NTSTATUS=0x{:08X}", free_status as u32);
}
#[test]
fn eleven_arg_nt_create_thread_ex_terminate_round_trip() {
// 11 args -> 7 args on the stack copied via rep movsq.
// We never run the thread; we point it at a sentinel address and
// create it suspended, then NtTerminateProcess… actually simpler:
// pass a bogus start routine but CREATE_SUSPENDED, then close the
// handle.
extern "system" fn nop_thread(_: *mut c_void) -> u32 {
0
}
let mut thread_handle: *mut c_void = null_mut();
let status = syscall!(
"NtCreateThreadEx",
&mut thread_handle as *mut *mut c_void,
0x1F03FFu32, // THREAD_ALL_ACCESS
null_mut::<c_void>(), // ObjectAttributes
-1isize as *mut c_void, // ProcessHandle = current
nop_thread as *mut c_void, // StartRoutine
null_mut::<c_void>(), // Argument
0x00000001u32, // CREATE_SUSPENDED
0usize, // ZeroBits
0usize, // StackSize
0usize, // MaximumStackSize
null_mut::<c_void>() // AttributeList
)
.expect("resolve");
assert_eq!(status, 0, "NtCreateThreadEx NTSTATUS=0x{:08X}", status as u32);
assert!(!thread_handle.is_null());
// Clean up the suspended thread.
let close = syscall!(
"NtTerminateThread",
thread_handle,
0i32 as usize as *mut c_void // ExitStatus
)
.expect("resolve");
assert_eq!(close, 0);
let _ = syscall!("NtClose", thread_handle).expect("resolve");
}
+48
View File
@@ -0,0 +1,48 @@
// dmanager's xor_key derivation folds a 15-byte random buffer with
// `rand_bytes.iter().fold(0u8, |acc, b| acc ^ b)`.
//
// This test mirrors the production derivation against a controlled buffer
// with a sentinel one byte past the intended end. The sentinel must not
// participate in the key, proving exactly 15 bytes are consumed and no
// out-of-bounds read happens regardless of buffer content.
#[test]
fn xor_key_fold_consumes_exactly_15_bytes_no_oob() {
let intended = [0xFFu8; 15];
// Place sentinel one past end in a longer backing buffer. If the
// derivation ever drifts back to a pointer-walk pattern, the sentinel
// would change the result and this test fails.
let mut backing = [0u8; 32];
backing[..15].copy_from_slice(&intended);
backing[15] = 0xA5; // sentinel
let from_array = intended.iter().fold(0u8, |acc, b| acc ^ b);
let from_backing_slice = backing[..15].iter().fold(0u8, |acc, b| acc ^ b);
// 15 × 0xFF XORed: odd count -> 0xFF.
assert_eq!(from_array, 0xFF, "fold of 15 × 0xFF should yield 0xFF (odd count)");
assert_eq!(
from_backing_slice, from_array,
"fold over backing[..15] must equal fold over the array, sentinel must not leak in"
);
// For total certainty: change only the sentinel and recompute the fold
// over backing[..15]. Result must not change.
backing[15] = 0x00;
let after_sentinel_change = backing[..15].iter().fold(0u8, |acc, b| acc ^ b);
assert_eq!(after_sentinel_change, from_array);
}
#[test]
fn xor_key_fold_round_trip() {
// Spot-check the algebraic identity: data XOR key XOR key == data.
// Confirms the key really is byte-sized and the fold is well-defined.
let key: u8 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD]
.iter().fold(0u8, |acc, b| acc ^ b);
let plaintext: Vec<u8> = (0..256u16).map(|x| x as u8).collect();
let ciphertext: Vec<u8> = plaintext.iter().map(|b| b ^ key).collect();
let recovered: Vec<u8> = ciphertext.iter().map(|b| b ^ key).collect();
assert_eq!(recovered, plaintext);
}
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "manualmap"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
[dependencies]
dyncvoke_core = { path = "../dyncvoke_core" }
data = { path = "../data" }
litcrypt2 = "=0.1.2"
os_info = { version = "3.14.0", default-features = false }
[dependencies.windows-sys]
version = "0.59"
features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System",
"Win32_System_IO",
"Win32_System_Kernel",
"Win32_System_Diagnostics_Debug",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_WindowsProgramming",
"Win32_Storage_FileSystem",
"Win32_System_Memory",
"Win32_System_SystemServices",
"Win32_System_Threading"
]
+1147
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "overload"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
[dependencies]
dyncvoke_core = { path = "../dyncvoke_core" }
data = { path = "../data" }
manualmap = { path = "../manualmap" }
litcrypt2 = "=0.1.2"
winproc = "0.6.4"
nanorand = "0.8.0"
[dependencies.windows-sys]
version = "0.59"
features = [
"Win32_Foundation"
]
+1157
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
pub use data;
pub use dyncvoke_core;
pub use dmanager;
pub use manualmap;
pub use overload;