diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d28ef5..167b8fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project ~~adheres to~~ -**_will adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once it reaches 0.2.0_**. +and this project adheres to **_will adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once it reaches 0.3.0_**. ## [Unreleased] ### Changed +### Added + +### Fixed + +## [0.2.0] - 2025-02-04 + +### Changed + +- VmiOs refactored from the ground up + - Each OS component is now a separate struct + - Common OS components are now traits (VmiOsProcess, VmiOsThread, ...) - VmiHandler::finished() is renamed to VmiHandler::check_completion(), which now returns an Option<Output> instead of a bool ### Added +- New drivers for offline analysis + - VmiDriverKdmp, VmiDriverXenCoreDump - Implemented handling of PFN changes in the PageTableMonitor - Added Output type to the VmiHandler - vmi_core::os::OsModule + VmiOs::modules() to get the list of loaded modules diff --git a/Cargo.lock b/Cargo.lock index 148f566..4b64e34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ar" @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.70.1" +version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ "bitflags", "cexpr", @@ -108,18 +108,18 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -129,9 +129,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bzip2" @@ -156,9 +156,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.37" +version = "1.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" dependencies = [ "jobserver", "libc", @@ -262,6 +262,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -279,12 +285,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -301,9 +307,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" @@ -319,9 +325,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -335,9 +341,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" @@ -421,7 +427,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -431,21 +449,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator 0.3.0", - "indexmap 2.6.0", + "indexmap 2.7.1", "stable_deref_trait", ] [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -453,7 +471,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.6.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -468,26 +486,20 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -519,15 +531,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "hyper" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -545,9 +557,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", @@ -746,12 +758,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -766,15 +778,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "isr" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a7d72d55de0118f860db9f8e9d928289f8952e653571885f17452dd9124bc1f" +checksum = "d7d7d4052aefb3885a25913eb130f9a9d04ea11756b23ac5d718509c4358b72b" dependencies = [ "isr-cache", "isr-core", @@ -787,9 +799,9 @@ dependencies = [ [[package]] name = "isr-cache" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57c10be1c546915ad498ea424f400cfe2c79f5e682e2e2e54498899dc39ac3a" +checksum = "b16ca7fcfd382cf1be049f9ab79f29031720ca8eff048ccef16fb0c7a4199fed" dependencies = [ "bincode", "isr-core", @@ -801,90 +813,90 @@ dependencies = [ "rmp-serde", "serde", "serde_json", - "thiserror", + "thiserror 2.0.11", "tracing", ] [[package]] name = "isr-core" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e9f0114ba6ebc8b5c8f6255083a754176877eb6875c1b56ea7f8a8dcb91aab" +checksum = "1a889940b5d72d09a40ccd3eba3d0383d0fb34e42ba1a272ce5d65a9e317a827" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "smallvec", ] [[package]] name = "isr-dl-linux" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72bb2343ea9f9eae0d754aa3e0ac64675157b152381a2b886cfc00a4c1a51a49" +checksum = "05377f73e59e32f31ceac5ee77e04fdab7cfaa961898e61d901d9c85d5800265" dependencies = [ "debpkg", "flate2", - "indexmap 2.6.0", + "indexmap 2.7.1", "regex", "reqwest", - "thiserror", + "thiserror 2.0.11", "tracing", "url", ] [[package]] name = "isr-dl-pdb" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7105d41eb95e86862490cd53e79a05683d6f2f4012541d98a2aa8a27a331a7" +checksum = "b274d45cf4c924e3381ae931b4ddd86661e0ee84683a7138f90ad73a462858be" dependencies = [ "object", "reqwest", - "thiserror", + "thiserror 2.0.11", "tracing", ] [[package]] name = "isr-dwarf" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdff591004404c670c4f01ff2f7f00cd7193e84d3d56a9084346de7009029af8" +checksum = "d1e751f30f103bc912ac48b0199cc4a602ee273138851121f15c1fa1bfdea7cf" dependencies = [ "gimli", - "indexmap 2.6.0", + "indexmap 2.7.1", "isr-core", "memmap2", "object", "smallvec", - "thiserror", + "thiserror 2.0.11", "tracing", ] [[package]] name = "isr-macros" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a16ac35394b3b5e59f4edd6f3f1e7338ad1d3b86b9725ab4f0334b9bf8b052" +checksum = "2f75b8df93c6068b8a729de7d99884ae2591c55c1444057f6fd2a81e123f04a5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "isr-core", "rmp-serde", "serde", "serde_json", "smallvec", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "isr-pdb" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd61ef4282a14622e4b6abc3472e16aa1bbdacb2fcad7c0172dd2eff6681a0e2" +checksum = "544e046ac203ea82a5303546410b3bf0886851203482ddb8a9a14482d3a17e52" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "isr-core", "pdb", - "thiserror", + "thiserror 2.0.11", "tracing", ] @@ -899,9 +911,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -914,13 +926,24 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] +[[package]] +name = "kdmp-parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc18d3097f3f1f9ca93a0fc0add3b6502a810fc678e2ab238ba3217d777d72ab" +dependencies = [ + "bitflags", + "thiserror 1.0.69", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -929,15 +952,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets", @@ -956,22 +979,22 @@ dependencies = [ [[package]] name = "libxen" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8e0c665235e811a6dc4976cb9a00d4449f1088bfcc4fd53cc7e0152e5fe46a" +checksum = "d7ffb1e9e14ae41467c49e6d6a8f63c159fd3b0160b6614c4fcd313dca70290e" dependencies = [ "bitflags", "libc", "libxen-sys", - "thiserror", + "thiserror 2.0.11", "tracing", ] [[package]] name = "libxen-sys" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0f7caaf357e5b4e459e218092674b09a77fdc5b8081d43aa17b24c7768fdd" +checksum = "f3e1e4079999bb44cc7f0528c06dbbd6f9db6108c0de6140eea9e7d6d29436be" dependencies = [ "bindgen", "pkg-config", @@ -979,29 +1002,29 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru" -version = "0.12.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -1044,30 +1067,29 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -1111,9 +1133,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "flate2", "memchr", @@ -1128,9 +1150,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags", "cfg-if", @@ -1154,15 +1176,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -1201,9 +1223,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1219,9 +1241,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", "syn", @@ -1229,27 +1251,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] @@ -1268,9 +1290,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1285,9 +1307,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64", "bytes", @@ -1319,6 +1341,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tower", "tower-service", "url", "wasm-bindgen", @@ -1335,7 +1358,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -1372,28 +1395,28 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "once_cell", "rustls-pki-types", @@ -1413,9 +1436,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -1428,26 +1451,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ruzstd" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3938e133aac070997ddc684d4b393777d293ba170f2988c8fd5ea2ad4ce21" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" dependencies = [ "twox-hash", ] [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -1473,9 +1502,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -1483,18 +1512,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -1503,9 +1532,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -1579,9 +1608,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1613,9 +1642,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1624,9 +1653,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -1676,12 +1705,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1689,18 +1719,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.1" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c1e40dd48a282ae8edc36c732cbc219144b87fb6a4c7316d611c6b1f06ec0c" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "thiserror-impl" -version = "2.0.1" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874aa7e446f1da8d9c3a5c95b1c5eb41d800045252121dc7f8e0ba370cee55f5" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -1729,9 +1779,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -1754,20 +1804,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -1776,6 +1825,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -1784,9 +1854,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1795,9 +1865,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -1806,9 +1876,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -1827,9 +1897,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1857,9 +1927,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "untrusted" @@ -1869,9 +1939,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -1892,15 +1962,15 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -1910,9 +1980,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vmi" -version = "0.1.1" +version = "0.2.0" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "isr", "isr-core", "isr-macros", @@ -1922,7 +1992,9 @@ dependencies = [ "tracing-subscriber", "vmi-arch-amd64", "vmi-core", + "vmi-driver-kdmp", "vmi-driver-xen", + "vmi-driver-xen-core-dump", "vmi-os-linux", "vmi-os-windows", "vmi-utils", @@ -1931,7 +2003,7 @@ dependencies = [ [[package]] name = "vmi-arch-amd64" -version = "0.1.1" +version = "0.2.0" dependencies = [ "bitflags", "smallvec", @@ -1941,24 +2013,36 @@ dependencies = [ [[package]] name = "vmi-core" -version = "0.1.1" +version = "0.2.0" dependencies = [ "bitflags", - "indexmap 2.6.0", + "indexmap 2.7.1", "isr-core", "isr-macros", "lru", "serde", "smallvec", - "thiserror", + "thiserror 2.0.11", "tracing", "vmi-macros", "zerocopy", ] +[[package]] +name = "vmi-driver-kdmp" +version = "0.2.0" +dependencies = [ + "kdmp-parser", + "memmap2", + "tracing", + "vmi-arch-amd64", + "vmi-core", + "zerocopy", +] + [[package]] name = "vmi-driver-xen" -version = "0.1.1" +version = "0.2.0" dependencies = [ "libc", "libxen", @@ -1967,9 +2051,22 @@ dependencies = [ "vmi-core", ] +[[package]] +name = "vmi-driver-xen-core-dump" +version = "0.2.0" +dependencies = [ + "elf", + "libxen", + "memmap2", + "tracing", + "vmi-arch-amd64", + "vmi-core", + "zerocopy", +] + [[package]] name = "vmi-macros" -version = "0.1.1" +version = "0.2.0" dependencies = [ "proc-macro2", "quote", @@ -1978,11 +2075,13 @@ dependencies = [ [[package]] name = "vmi-os-linux" -version = "0.1.1" +version = "0.2.0" dependencies = [ "isr-core", "isr-macros", "memchr", + "once_cell", + "thiserror 2.0.11", "tracing", "vmi-arch-amd64", "vmi-core", @@ -1990,7 +2089,7 @@ dependencies = [ [[package]] name = "vmi-os-windows" -version = "0.1.1" +version = "0.2.0" dependencies = [ "bitflags", "isr", @@ -1998,7 +2097,8 @@ dependencies = [ "isr-dl-pdb", "isr-macros", "object", - "thiserror", + "once_cell", + "thiserror 2.0.11", "tracing", "vmi-arch-amd64", "vmi-core", @@ -2008,7 +2108,7 @@ dependencies = [ [[package]] name = "vmi-utils" -version = "0.1.1" +version = "0.2.0" dependencies = [ "isr-core", "isr-macros", @@ -2034,26 +2134,35 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -2062,21 +2171,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2084,9 +2194,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -2097,15 +2207,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2245,6 +2358,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -2259,9 +2381,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys", @@ -2279,9 +2401,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -2291,9 +2413,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -2303,18 +2425,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.9" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49e690f8f352f4a9ee8679a8c5921f42ffd0d6d6413a0a66b8e81cf524e109c" +checksum = "a1e101d4bc320b6f9abb68846837b70e25e380ca2f467ab494bf29fcc435fcc3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.9" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa732fcc881df7a6fbe8e3ed17baadece53b379ad58fe2633396b1a2b108a7b1" +checksum = "03a73df1008145cd135b3c780d275c57c3e6ba8324a41bd5e0008fe167c3bc7c" dependencies = [ "proc-macro2", "quote", @@ -2323,18 +2445,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4560d82..6c2aae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,15 @@ missing_docs = "warn" [workspace.dependencies] bitflags = "2" +elf = "0.7" indexmap = "2" +kdmp-parser = "0.6" libc = "0.2" -lru = "0.12" +lru = "0.13" memchr = "2.7" +memmap2 = "0.9" object = "0.36" +once_cell = "1" serde = "1" smallvec = "1" thiserror = "2.0" @@ -34,21 +38,38 @@ zerocopy = "0.8" signal-hook = "0.3" tracing-subscriber = "0.3" -isr = "0.1.2" -isr-core = "0.1.1" -isr-dl-pdb = "0.1.1" -isr-macros = "0.1.2" +isr = "0.2.0" +isr-core = "0.2.0" +isr-dl-pdb = "0.2.0" +isr-macros = "0.2.0" -vmi = { path = "./crates/vmi", version = "0.1.1" } -vmi-arch-amd64 = { path = "./crates/vmi-arch-amd64", version = "0.1.1" } -vmi-core = { path = "./crates/vmi-core", version = "0.1.1" } -vmi-driver-xen = { path = "./crates/vmi-driver-xen", version = "0.1.1" } -vmi-macros = { path = "./crates/vmi-macros", version = "0.1.1" } -vmi-os-linux = { path = "./crates/vmi-os-linux", version = "0.1.1" } -vmi-os-windows = { path = "./crates/vmi-os-windows", version = "0.1.1" } -vmi-utils = { path = "./crates/vmi-utils", version = "0.1.1" } +# +# For development. +# -xen = { package = "libxen", version = "0.1.2" } +# isr = { path = "../isr", version = "0.2.0" } +# isr-core = { path = "../isr/crates/isr-core", version = "0.2.0" } +# isr-dl-pdb = { path = "../isr/crates/isr-dl-pdb", version = "0.2.0" } +# isr-macros = { path = "../isr/crates/isr-macros", version = "0.2.0" } + +# vmi = { path = "./crates/vmi", version = "0.2.0" } +vmi-arch-amd64 = { path = "./crates/vmi-arch-amd64", version = "0.2.0" } +vmi-core = { path = "./crates/vmi-core", version = "0.2.0" } +vmi-driver-kdmp = { path = "./crates/vmi-driver-kdmp", version = "0.2.0" } +vmi-driver-xen = { path = "./crates/vmi-driver-xen", version = "0.2.0" } +vmi-driver-xen-core-dump = { path = "./crates/vmi-driver-xen-core-dump", version = "0.2.0" } +vmi-macros = { path = "./crates/vmi-macros", version = "0.2.0" } +vmi-os-linux = { path = "./crates/vmi-os-linux", version = "0.2.0" } +vmi-os-windows = { path = "./crates/vmi-os-windows", version = "0.2.0" } +vmi-utils = { path = "./crates/vmi-utils", version = "0.2.0" } + +xen = { package = "libxen", version = "0.2.0" } + +# +# For development. +# + +# xen = { path = "../xen", package = "libxen", version = "0.2.0" } [profile.release] debug = 1 @@ -59,7 +80,7 @@ debug = 1 [package] name = "vmi" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } @@ -91,7 +112,9 @@ isr-macros = { workspace = true } vmi-core = { workspace = true } vmi-arch-amd64 = { workspace = true, optional = true } +vmi-driver-kdmp = { workspace = true, optional = true } vmi-driver-xen = { workspace = true, optional = true } +vmi-driver-xen-core-dump = { workspace = true, optional = true } vmi-os-linux = { workspace = true, optional = true } vmi-os-windows = { workspace = true, optional = true } vmi-utils = { workspace = true, optional = true } @@ -99,7 +122,9 @@ vmi-utils = { workspace = true, optional = true } [features] default = [ "arch-amd64", + "driver-kdmp", "driver-xen", + "driver-xen-core-dump", "os-linux", "os-windows", "utils" @@ -109,7 +134,9 @@ arch-amd64 = [ "vmi-arch-amd64", "vmi-utils?/arch-amd64" ] +driver-kdmp = ["vmi-driver-kdmp"] driver-xen = ["vmi-driver-xen"] +driver-xen-core-dump = ["vmi-driver-xen-core-dump"] os-linux = ["vmi-os-linux"] os-windows = [ "vmi-os-windows", @@ -128,7 +155,9 @@ isr = { workspace = true } # vmi = { workspace = true } vmi-core = { workspace = true } vmi-arch-amd64 = { workspace = true } +vmi-driver-kdmp = { workspace = true } vmi-driver-xen = { workspace = true } +vmi-driver-xen-core-dump = { workspace = true } vmi-os-linux = { workspace = true } vmi-os-windows = { workspace = true } @@ -145,10 +174,15 @@ path = "examples/basic-process-list.rs" doc-scrape-examples = true [[example]] -name = "breakpoint-manager" +name = "windows-breakpoint-manager" path = "examples/windows-breakpoint-manager.rs" doc-scrape-examples = true +[[example]] +name = "windows-dump" +path = "examples/windows-dump.rs" +doc-scrape-examples = true + [[example]] name = "windows-recipe-messagebox" path = "examples/windows-recipe-messagebox.rs" diff --git a/README.md b/README.md index e10f74a..7427ec6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ manipulating virtual machine state from the outside. - [Address Contexts](#address-contexts) - [Architecture](#architecture) - [Core Components](#core-components) - - [Relationship between `VmiCore`, `VmiSession`, and `VmiContext`](#relationship-between-vmicore-vmisession-and-vmicontext) + - [Relationship between `VmiCore`, `VmiSession`, `VmiState` and `VmiContext`](#relationship-between-vmicore-vmisession-vmistate-and-vmicontext) - [OS-Specific Operations](#os-specific-operations) - [Implicit vs. Explicit Registers](#implicit-vs-explicit-registers) - [Event Handling](#event-handling) @@ -117,7 +117,7 @@ Add the following to your `Cargo.toml`: ```toml [dependencies] -vmi = "0.1" +vmi = "0.2" ``` Basic usage example: @@ -127,7 +127,7 @@ use isr::{cache::JsonCodec, IsrCache}; use vmi::{ arch::amd64::Amd64, driver::xen::VmiXenDriver, - os::windows::WindowsOs, + os::{windows::WindowsOs, VmiOsProcess as _}, VcpuId, VmiCore, VmiSession, }; use xen::XenDomainId; @@ -140,9 +140,20 @@ fn main() -> Result<(), Box> { // Try to find the kernel information. // This is necessary in order to load the profile. let kernel_info = { + // Pause the VCPU to get consistent state. let _pause_guard = core.pause_guard()?; + + // Get the register state for the first VCPU. let registers = core.registers(VcpuId(0))?; + // On AMD64 architecture, the kernel is usually found using the + // `MSR_LSTAR` register, which contains the address of the system call + // handler. This register is set by the operating system during boot + // and is left unchanged (unless some rootkits are involved). + // + // Therefore, we can take an arbitrary registers at any point in time + // (as long as the OS has booted and the page tables are set up) and + // use them to find the kernel. WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information") }; @@ -153,14 +164,29 @@ fn main() -> Result<(), Box> { let profile = entry.profile()?; // Create the VMI session. + tracing::info!("Creating VMI session"); let os = WindowsOs::>::new(&profile)?; let session = VmiSession::new(&core, &os); - // Get the list of processes and print them. + // Pause the VM again to get consistent state. let _pause_guard = session.pause_guard()?; + + // Create a new `VmiState` with the current register. let registers = session.registers(VcpuId(0))?; - let processes = session.os().processes(®isters)?; - println!("Processes: {processes:#?}"); + let vmi = session.with_registers(®isters); + + // Get the list of processes and print them. + for process in vmi.os().processes()? { + let process = process?; + + println!( + "{} [{}] {} (root @ {})", + process.object()?, + process.id()?, + process.name()?, + process.translation_root()? + ); + } Ok(()) } @@ -170,11 +196,11 @@ fn main() -> Result<(), Box> { But first, you need to install the prerequisites. -The framework has been tested on Ubuntu 22.04 and Xen 4.19. -Note that Xen 4.19 is the minimum version required to use the +The framework has been tested on Ubuntu 22.04 and Xen 4.20. +Note that Xen 4.20 is the minimum version required to use the framework, and it is the current version (at the time of writing). -Unfortunately, Xen 4.19 is not available in the official Ubuntu +Unfortunately, Xen 4.20 is not available in the official Ubuntu repositories, so it must be built from source. This guide assumes you have a fresh Ubuntu 22.04 installation. @@ -201,6 +227,10 @@ capabilities, from basic operations to more complex scenarios. Illustrates the usage of the [`BreakpointManager`] and [`PageTableMonitor`] to set and manage breakpoints on Windows systems. +- **[`windows-dump.rs`]** + + Demonstrates how to use the VMI library to analyze a Windows kernel dump file. + - **[`windows-recipe-messagebox.rs`]** A simple example of code injection using a recipe to display a message @@ -332,34 +362,41 @@ The core components of the framework are: to provide OS-aware operations. This enables high-level introspection tasks, but - like `VmiCore` - `VmiSession` does not store register state. -- [`VmiContext`]: Represents a point-in-time state of the virtual CPU - during event handling. Unlike `VmiCore` (and `VmiSession`), `VmiContext` - *does* hold the register state at the time of the event, simplifying - event handler logic. +- [`VmiState`]: Represents a state of the virtual machine at a given moment, + combining [`VmiSession`] with [`Architecture::Registers`]. + This allows for consistent access to memory, registers, and OS-level + abstractions without requiring explicit register state management for + every operation. - It provides access to both `VmiCore` and `VmiOs` functionality within - a specific [`VmiEvent`]. +- [`VmiContext`]: Represents a point-in-time state of the virtual CPU + during event handling, combining [`VmiState`] with a [`VmiEvent`]. - [`VmiError`]: Represents errors that can occur during VMI operations, including translation faults ([`PageFault`]). -### Relationship between `VmiCore`, `VmiSession`, and `VmiContext` +### Relationship between `VmiCore`, `VmiSession`, `VmiState` and `VmiContext` Each of these structures can be implicitly dereferenced down the hierarchy. -This means that `VmiContext` implements [`Deref`] to `VmiSession`, -which in turn implements `Deref` to `VmiCore`. +This means that: + +- `VmiContext` implements [`Deref`] to `VmiState` + - which in turn implements `Deref` to `VmiSession` + - which in turn implements `Deref` to `VmiCore`. This design enables convenient access to lower-level functionality: -- Access `VmiCore` methods directly from a `VmiSession` or `VmiContext` - without explicit dereferencing. +- Access `VmiCore` methods directly from a `VmiSession`, `VmiState` or + `VmiContext` without explicit dereferencing. -- Pass a `&VmiContext` to functions expecting a `&VmiSession` +- Pass a `&VmiContext` to functions expecting a `&VmiState`, `&VmiSession` or `&VmiCore`. #### OS-Specific Operations -Both `VmiSession` and `VmiContext` provide access to OS-specific +> *Consult the [`os`] module documentation for more information +> and examples.* + +Both `VmiState` and `VmiContext` provide access to OS-specific functionality through the [`os()`] method. This method returns a structure implementing the [`VmiOs`] trait methods, as well as any additional OS-specific operations. @@ -371,9 +408,9 @@ state. This means that functions requiring register information (e.g., for address translation or OS-specific operations) must be explicitly provided with the register state. -`VmiContext`, on the other hand, *does* hold the register state at -the time of the event. This difference has important implications for -how you interact with these components: +`VmiState` and `VmiContext`, on the other hand, *do* hold the register +state. This difference has important implications for how you interact with +these components: - With `VmiCore` and `VmiSession`, you must explicitly provide the translation root (e.g., `CR3`) when performing memory operations: @@ -386,7 +423,7 @@ how you interact with these components: let value = vmi.read_u64((va, registers.cr3.into()))?; // Explicitly pass the translation root (CR3) ``` -- With `VmiContext`, register state is managed internally: +- With `VmiState` and `VmiContext`, register state is managed internally: ```rust,ignore // let vmi: &VmiContext = ...; @@ -398,18 +435,20 @@ This extends to OS-specific operations as well. - `VmiSession` requires explicit register state: ```rust,ignore -// let vmi: &VmiSession = ...; -let registers = vmi.registers(VcpuId(0))?; -let process = vmi.os().current_process(®isters)?; -let process_id = vmi.os().process_id(®isters, process)?; +// let session: &VmiSession = ...; +let registers = session.registers(VcpuId(0))?; +let vmi = session.with_registers(®isters); // Create a new VmiState +let process = vmi.os().current_process()?; +let process_id = process.id()?; ``` -- `VmiContext` simplifies this by providing register state implicitly: +- `VmiState` and `VmiContext` simplifies this by providing register state + implicitly: ```rust,ignore // let vmi: &VmiContext = ...; let process = vmi.os().current_process()?; -let process_id = vmi.os().process_id(process)?; +let process_id = process.id()?; ``` ## Event Handling @@ -525,6 +564,7 @@ This project is licensed under the MIT license. [`basic-process-list.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic-process-list.rs [`windows-breakpoint-manager.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-breakpoint-manager.rs +[`windows-dump.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-dump.rs [`windows-recipe-messagebox.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-messagebox.rs [`windows-recipe-writefile.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile.rs [`windows-recipe-writefile-advanced.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile-advanced.rs @@ -546,6 +586,7 @@ This project is licensed under the MIT license. [`VmiHandler`]: https://docs.rs/vmi/latest/vmi/trait.VmiHandler.html [`VmiOs`]: https://docs.rs/vmi/latest/vmi/trait.VmiOs.html [`VmiSession`]: https://docs.rs/vmi/latest/vmi/struct.VmiSession.html +[`VmiState`]: https://docs.rs/vmi/latest/vmi/struct.VmiState.html [`Amd64`]: https://docs.rs/vmi/latest/vmi/arch/amd64/struct.Amd64.html [`VmiXenDriver`]: https://docs.rs/vmi/latest/vmi/driver/xen/struct.VmiXenDriver.html @@ -561,7 +602,8 @@ This project is licensed under the MIT license. [`PageIn`]: https://docs.rs/vmi/latest/vmi/utils/ptm/enum.PageTableMonitorEvent.html#variant.PageIn [`PageOut`]: https://docs.rs/vmi/latest/vmi/utils/ptm/enum.PageTableMonitorEvent.html#variant.PageOut [`handle_event`]: https://docs.rs/vmi/latest/vmi/trait.VmiHandler.html#tymethod.handle_event -[`os()`]: https://docs.rs/vmi/latest/vmi/struct.VmiSession.html#method.os +[`os()`]: https://docs.rs/vmi/latest/vmi/struct.VmiState.html#method.os +[`os`]: https://docs.rs/vmi/latest/vmi/os/index.html [physical page lookups]: https://docs.rs/vmi/latest/vmi/struct.VmiCore.html#method.with_gfn_cache [Virtual-to-Physical address translations]: https://docs.rs/vmi/latest/vmi/struct.VmiCore.html#method.with_v2p_cache @@ -581,6 +623,7 @@ This project is licensed under the MIT license. [`basic.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic.rs [`basic-process-list.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic-process-list.rs [`windows-breakpoint-manager.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-breakpoint-manager.rs +[`windows-dump.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-dump.rs [`windows-recipe-messagebox.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-messagebox.rs [`windows-recipe-writefile.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile.rs [`windows-recipe-writefile-advanced.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile-advanced.rs diff --git a/README.tpl b/README.tpl index 2570aa3..40444db 100644 --- a/README.tpl +++ b/README.tpl @@ -22,6 +22,7 @@ [`VmiHandler`]: https://docs.rs/vmi/latest/vmi/trait.VmiHandler.html [`VmiOs`]: https://docs.rs/vmi/latest/vmi/trait.VmiOs.html [`VmiSession`]: https://docs.rs/vmi/latest/vmi/struct.VmiSession.html +[`VmiState`]: https://docs.rs/vmi/latest/vmi/struct.VmiState.html [`Amd64`]: https://docs.rs/vmi/latest/vmi/arch/amd64/struct.Amd64.html [`VmiXenDriver`]: https://docs.rs/vmi/latest/vmi/driver/xen/struct.VmiXenDriver.html @@ -37,7 +38,8 @@ [`PageIn`]: https://docs.rs/vmi/latest/vmi/utils/ptm/enum.PageTableMonitorEvent.html#variant.PageIn [`PageOut`]: https://docs.rs/vmi/latest/vmi/utils/ptm/enum.PageTableMonitorEvent.html#variant.PageOut [`handle_event`]: https://docs.rs/vmi/latest/vmi/trait.VmiHandler.html#tymethod.handle_event -[`os()`]: https://docs.rs/vmi/latest/vmi/struct.VmiSession.html#method.os +[`os()`]: https://docs.rs/vmi/latest/vmi/struct.VmiState.html#method.os +[`os`]: https://docs.rs/vmi/latest/vmi/os/index.html [physical page lookups]: https://docs.rs/vmi/latest/vmi/struct.VmiCore.html#method.with_gfn_cache [Virtual-to-Physical address translations]: https://docs.rs/vmi/latest/vmi/struct.VmiCore.html#method.with_v2p_cache @@ -57,6 +59,7 @@ [`basic.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic.rs [`basic-process-list.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic-process-list.rs [`windows-breakpoint-manager.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-breakpoint-manager.rs +[`windows-dump.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-dump.rs [`windows-recipe-messagebox.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-messagebox.rs [`windows-recipe-writefile.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile.rs [`windows-recipe-writefile-advanced.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile-advanced.rs diff --git a/crates/vmi-arch-amd64/Cargo.toml b/crates/vmi-arch-amd64/Cargo.toml index be83dca..b911ef4 100644 --- a/crates/vmi-arch-amd64/Cargo.toml +++ b/crates/vmi-arch-amd64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-arch-amd64" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } diff --git a/crates/vmi-arch-amd64/src/cr/cr0.rs b/crates/vmi-arch-amd64/src/cr/cr0.rs index 64f39d9..ee98071 100644 --- a/crates/vmi-arch-amd64/src/cr/cr0.rs +++ b/crates/vmi-arch-amd64/src/cr/cr0.rs @@ -24,7 +24,7 @@ impl Cr0 { /// the TS flag is also set. If the MP flag is clear, the WAIT instruction /// ignores the setting of the TS flag. pub fn monitor_coprocessor(self) -> bool { - self.0 >> 1 & 1 != 0 + (self.0 >> 1) & 1 != 0 } /// Checks if the CR0.EM flag is set. @@ -34,7 +34,7 @@ impl Cr0 { /// also affects the execution of MMX/SSE/SSE2/SSE3/SSSE3/SSE4 /// instructions. pub fn emulation(self) -> bool { - self.0 >> 2 & 1 != 0 + (self.0 >> 2) & 1 != 0 } /// Checks if the CR0.TS flag is set. @@ -46,7 +46,7 @@ impl Cr0 { /// tests it when executing x87 FPU/MMX/SSE/SSE2/SSE3/SSSE3/SSE4 /// instructions. pub fn task_switched(self) -> bool { - self.0 >> 3 & 1 != 0 + (self.0 >> 3) & 1 != 0 } /// Checks if the CR0.ET flag is set. @@ -57,7 +57,7 @@ impl Cr0 { /// processors, this flag indicates support of Intel 387 DX math coprocessor /// instructions when set. pub fn extension_type(self) -> bool { - self.0 >> 4 & 1 != 0 + (self.0 >> 4) & 1 != 0 } /// Checks if the CR0.NE flag is set. @@ -66,7 +66,7 @@ impl Cr0 { /// when set; enables the PC-style x87 FPU error reporting mechanism when /// clear. pub fn numeric_error(self) -> bool { - self.0 >> 5 & 1 != 0 + (self.0 >> 5) & 1 != 0 } /// Checks if the CR0.WP flag is set. @@ -78,7 +78,7 @@ impl Cr0 { /// method of creating a new process (forking) used by operating systems /// such as UNIX. pub fn write_protect(self) -> bool { - self.0 >> 16 & 1 != 0 + (self.0 >> 16) & 1 != 0 } /// Checks if the CR0.AM flag is set. @@ -88,7 +88,7 @@ impl Cr0 { /// flag is set, the AC flag in the EFLAGS register is set, CPL is 3, /// and the processor is operating in either protected or virtual-8086 mode. pub fn alignment_mask(self) -> bool { - self.0 >> 18 & 1 != 0 + (self.0 >> 18) & 1 != 0 } /// Checks if the CR0.NW flag is set. @@ -97,7 +97,7 @@ impl Cr0 { /// enabled for writes that hit the cache and invalidation cycles are /// enabled. pub fn not_write_through(self) -> bool { - self.0 >> 29 & 1 != 0 + (self.0 >> 29) & 1 != 0 } /// Checks if the CR0.CD flag is set. @@ -106,7 +106,7 @@ impl Cr0 { /// the whole of physical memory in the processor’s internal (and external) /// caches is enabled. When the CD flag is set, caching is restricted. pub fn cache_disable(self) -> bool { - self.0 >> 30 & 1 != 0 + (self.0 >> 30) & 1 != 0 } /// Checks if the CR0.PG flag is set. @@ -120,7 +120,7 @@ impl Cr0 { /// On Intel 64 processors, enabling and disabling IA-32e mode operation /// also requires modifying CR0.PG. pub fn paging(self) -> bool { - self.0 >> 31 & 1 != 0 + (self.0 >> 31) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/cr/cr3.rs b/crates/vmi-arch-amd64/src/cr/cr3.rs index 341e3fa..ab4f3f3 100644 --- a/crates/vmi-arch-amd64/src/cr/cr3.rs +++ b/crates/vmi-arch-amd64/src/cr/cr3.rs @@ -13,12 +13,12 @@ impl Cr3 { /// Returns the page table base physical address. pub fn page_frame_number(self) -> u64 { - self.0 >> 12 & 0x000f_ffff_ffff_ffff + (self.0 >> 12) & 0x000f_ffff_ffff_ffff } /// Returns true if the PCID should be invalidated. pub fn pcid_invalidate(self) -> bool { - self.0 >> 63 & 1 != 0 + (self.0 >> 63) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/cr/cr4.rs b/crates/vmi-arch-amd64/src/cr/cr4.rs index a173155..054f33d 100644 --- a/crates/vmi-arch-amd64/src/cr/cr4.rs +++ b/crates/vmi-arch-amd64/src/cr/cr4.rs @@ -19,7 +19,7 @@ impl Cr4 { /// flag (VIF) in protected mode when set; disables the VIF flag in /// protected mode when clear. pub fn protected_mode_virtual_interrupts(self) -> bool { - self.0 >> 1 & 1 != 0 + (self.0 >> 1) & 1 != 0 } /// Checks if the CR4.TSD flag is set. @@ -29,7 +29,7 @@ impl Cr4 { /// executed at any privilege level when clear. This bit also applies to /// the RDTSCP instruction if supported (if CPUID.80000001H:EDX\[27\] = 1). pub fn timestamp_disable(self) -> bool { - self.0 >> 2 & 1 != 0 + (self.0 >> 2) & 1 != 0 } /// Checks if the CR4.DE flag is set. @@ -39,7 +39,7 @@ impl Cr4 { /// aliases references to registers DR4 and DR5 for compatibility with /// software written to run on earlier IA-32 processors. pub fn debugging_extensions(self) -> bool { - self.0 >> 3 & 1 != 0 + (self.0 >> 3) & 1 != 0 } /// Checks if the CR4.PSE flag is set. @@ -47,7 +47,7 @@ impl Cr4 { /// Enables 4-MByte pages with 32-bit paging when set; restricts /// 32-bit paging to pages of 4 KBytes when clear. pub fn page_size_extension(self) -> bool { - self.0 >> 4 & 1 != 0 + (self.0 >> 4) & 1 != 0 } /// Checks if the CR4.PAE flag is set. @@ -56,7 +56,7 @@ impl Cr4 { /// with more than 32 bits. When clear, restricts physical addresses to 32 /// bits. PAE must be set before entering IA-32e mode. pub fn physical_address_extension(self) -> bool { - self.0 >> 5 & 1 != 0 + (self.0 >> 5) & 1 != 0 } /// Checks if the CR4.MCE flag is set. @@ -64,7 +64,7 @@ impl Cr4 { /// Enables the machine-check exception when set; disables the /// machine-check exception when clear. pub fn machine_check_enable(self) -> bool { - self.0 >> 6 & 1 != 0 + (self.0 >> 6) & 1 != 0 } /// Checks if the CR4.PGE flag is set. @@ -82,7 +82,7 @@ impl Cr4 { /// set. Reversing this sequence may affect program correctness, and /// processor performance will be impacted. pub fn page_global_enable(self) -> bool { - self.0 >> 7 & 1 != 0 + (self.0 >> 7) & 1 != 0 } /// Checks if the CR4.PCE flag is set. @@ -92,7 +92,7 @@ impl Cr4 { /// RDPMC instruction can be executed only at protection level 0 when /// clear. pub fn performance_monitoring_counter_enable(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// Checks if the CR4.OSFXSR flag is set. @@ -116,7 +116,7 @@ impl Cr4 { /// MOVNTI, CLFLUSH, CRC32, and POPCNT. The operating system or executive /// must explicitly set this flag. pub fn os_fxsr_support(self) -> bool { - self.0 >> 9 & 1 != 0 + (self.0 >> 9) & 1 != 0 } /// Checks if the CR4.OSXMMEXCPT flag is set. @@ -133,7 +133,7 @@ impl Cr4 { /// exception (#UD) whenever it detects an unmasked SIMD floating-point /// exception. pub fn os_xmm_exception_support(self) -> bool { - self.0 >> 10 & 1 != 0 + (self.0 >> 10) & 1 != 0 } /// Checks if the CR4.UMIP flag is set. @@ -142,7 +142,7 @@ impl Cr4 { /// executed if CPL > 0: SGDT, SIDT, SLDT, SMSW, and STR. An attempt at such /// execution causes a general-protection exception (#GP). pub fn usermode_instruction_prevention(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } /// Checks if the CR4.LA57 flag is set. @@ -152,21 +152,21 @@ impl Cr4 { /// uses 4-level paging to translate 48-bit linear addresses. /// This bit cannot be modified in IA-32e mode. pub fn linear_address_57_bit(self) -> bool { - self.0 >> 12 & 1 != 0 + (self.0 >> 12) & 1 != 0 } /// Checks if the CR4.VMXE flag is set. /// /// Enables VMX operation when set. pub fn vmx_enable(self) -> bool { - self.0 >> 13 & 1 != 0 + (self.0 >> 13) & 1 != 0 } /// Checks if the CR4.SMXE flag is set. /// /// Enables SMX operation when set. pub fn smx_enable(self) -> bool { - self.0 >> 14 & 1 != 0 + (self.0 >> 14) & 1 != 0 } /// Checks if the CR4.FSGSBASE flag is set. @@ -174,7 +174,7 @@ impl Cr4 { /// Enables the instructions RDFSBASE, RDGSBASE, WRFSBASE, /// and WRGSBASE. pub fn fsgsbase_enable(self) -> bool { - self.0 >> 16 & 1 != 0 + (self.0 >> 16) & 1 != 0 } /// Checks if the CR4.PCIDE flag is set. @@ -182,7 +182,7 @@ impl Cr4 { /// Enables process-context identifiers (PCIDs) when set. /// Can be set only in IA-32e mode (if IA32_EFER.LMA = 1). pub fn pcid_enable(self) -> bool { - self.0 >> 17 & 1 != 0 + (self.0 >> 17) & 1 != 0 } /// Checks if the CR4.OSXSAVE flag is set. @@ -197,7 +197,7 @@ impl Cr4 { /// - enables the processor to execute XGETBV and XSETBV instructions in /// order to read and write XCR0. pub fn os_xsave(self) -> bool { - self.0 >> 18 & 1 != 0 + (self.0 >> 18) & 1 != 0 } /// Checks if the CR4.KL flag is set. @@ -209,21 +209,21 @@ impl Cr4 { /// CPUID.19H:EBX.AESKLE[bit 0] is enumerated as 0 and execution of any /// Key Locker instruction causes an invalid-opcode exception (#UD). pub fn key_locker_enable(self) -> bool { - self.0 >> 19 & 1 != 0 + (self.0 >> 19) & 1 != 0 } /// Checks if the CR4.SMEP flag is set. /// /// Enables supervisor-mode execution prevention (SMEP) when set. pub fn smep_enable(self) -> bool { - self.0 >> 20 & 1 != 0 + (self.0 >> 20) & 1 != 0 } /// Checks if the CR4.SMAP flag is set. /// /// Enables supervisor-mode access prevention (SMAP) when set. pub fn smap_enable(self) -> bool { - self.0 >> 21 & 1 != 0 + (self.0 >> 21) & 1 != 0 } /// Checks if the CR4.PKE flag is set. @@ -236,7 +236,7 @@ impl Cr4 { /// be read or written. This bit also enables access to the PKRU register /// using the RDPKRU and WRPKRU instructions. pub fn protection_key_for_user_mode_enable(self) -> bool { - self.0 >> 22 & 1 != 0 + (self.0 >> 22) & 1 != 0 } /// Checks if the CR4.CET flag is set. @@ -245,7 +245,7 @@ impl Cr4 { /// set only if CR0.WP is set, and it must be clear before CR0.WP can be /// cleared. pub fn control_flow_enforcement(self) -> bool { - self.0 >> 23 & 1 != 0 + (self.0 >> 23) & 1 != 0 } /// Checks if the CR4.PKS flag is set. @@ -256,7 +256,7 @@ impl Cr4 { /// whether supervisor-mode linear addresses with that protection key can be /// read or written. pub fn protection_key_for_supervisor_mode_enable(self) -> bool { - self.0 >> 24 & 1 != 0 + (self.0 >> 24) & 1 != 0 } /// Checks if the CR4.UINTR flag is set. @@ -265,14 +265,14 @@ impl Cr4 { /// delivery, user-interrupt notification identification, and the /// user-interrupt instructions. pub fn user_interrupts_enable(self) -> bool { - self.0 >> 25 & 1 != 0 + (self.0 >> 25) & 1 != 0 } /// Checks if the CR4.LAM_SUP flag is set. /// /// When set, enables LAM (linear-address masking) for supervisor pointers. pub fn supervisor_lam_enable(self) -> bool { - self.0 >> 28 & 1 != 0 + (self.0 >> 28) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/dr/dr6.rs b/crates/vmi-arch-amd64/src/dr/dr6.rs index 2e4c84b..419a36d 100644 --- a/crates/vmi-arch-amd64/src/dr/dr6.rs +++ b/crates/vmi-arch-amd64/src/dr/dr6.rs @@ -31,7 +31,7 @@ impl Dr6 { /// following reset.) This bit is always 1 if the processor does not support /// OS buslock detection pub fn bus_lock_detected(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } /// BD (debug register access detected) flag (bit 13). @@ -41,7 +41,7 @@ impl Dr6 { /// is enabled when the GD (general detect) flag in debug control /// register DR7 is set. pub fn debug_register_access_detected(self) -> bool { - self.0 >> 13 & 1 != 0 + (self.0 >> 13) & 1 != 0 } /// BS (single step) flag (bit 14). @@ -52,7 +52,7 @@ impl Dr6 { /// debug exception. When the BS flag is set, any of the other debug status /// bits also may be set. pub fn single_step(self) -> bool { - self.0 >> 14 & 1 != 0 + (self.0 >> 14) & 1 != 0 } /// BT (task switch) flag (bit 15). @@ -63,7 +63,7 @@ impl Dr6 { /// debug exception. When the BS flag is set, any of the other debug status /// bits also may be set. pub fn task_switch(self) -> bool { - self.0 >> 15 & 1 != 0 + (self.0 >> 15) & 1 != 0 } /// RTM (restricted transactional memory) flag (bit 16). @@ -76,7 +76,7 @@ impl Dr6 { /// enabled). This bit is always 1 if the processor does not support /// RTM. pub fn restricted_transactional_memory(self) -> bool { - self.0 >> 16 & 1 != 0 + (self.0 >> 16) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/dr/dr7.rs b/crates/vmi-arch-amd64/src/dr/dr7.rs index 82a4f16..2ee8243 100644 --- a/crates/vmi-arch-amd64/src/dr/dr7.rs +++ b/crates/vmi-arch-amd64/src/dr/dr7.rs @@ -50,7 +50,7 @@ impl Dr7 { /// clear this flag on a task switch, allowing a breakpoint to be enabled /// for all tasks. pub fn global_breakpoint_0(self) -> bool { - self.0 >> 1 & 1 != 0 + (self.0 >> 1) & 1 != 0 } /// L1 (local breakpoint enable) flag (bit 2). @@ -61,7 +61,7 @@ impl Dr7 { /// processor automatically clears this flag on every task switch to /// avoid unwanted breakpoint conditions in the new task. pub fn local_breakpoint_1(self) -> bool { - self.0 >> 2 & 1 != 0 + (self.0 >> 2) & 1 != 0 } /// G1 (global breakpoint enable) flag (bit 3). @@ -72,7 +72,7 @@ impl Dr7 { /// clear this flag on a task switch, allowing a breakpoint to be enabled /// for all tasks. pub fn global_breakpoint_1(self) -> bool { - self.0 >> 3 & 1 != 0 + (self.0 >> 3) & 1 != 0 } /// L2 (local breakpoint enable) flag (bit 4). @@ -83,7 +83,7 @@ impl Dr7 { /// processor automatically clears this flag on every task switch to /// avoid unwanted breakpoint conditions in the new task. pub fn local_breakpoint_2(self) -> bool { - self.0 >> 4 & 1 != 0 + (self.0 >> 4) & 1 != 0 } /// G2 (global breakpoint enable) flag (bit 5). @@ -94,7 +94,7 @@ impl Dr7 { /// clear this flag on a task switch, allowing a breakpoint to be enabled /// for all tasks. pub fn global_breakpoint_2(self) -> bool { - self.0 >> 5 & 1 != 0 + (self.0 >> 5) & 1 != 0 } /// L3 (local breakpoint enable) flag (bit 6). @@ -105,7 +105,7 @@ impl Dr7 { /// processor automatically clears this flag on every task switch to /// avoid unwanted breakpoint conditions in the new task. pub fn local_breakpoint_3(self) -> bool { - self.0 >> 6 & 1 != 0 + (self.0 >> 6) & 1 != 0 } /// G3 (global breakpoint enable) flag (bit 7). @@ -116,7 +116,7 @@ impl Dr7 { /// clear this flag on a task switch, allowing a breakpoint to be enabled /// for all tasks. pub fn global_breakpoint_3(self) -> bool { - self.0 >> 7 & 1 != 0 + (self.0 >> 7) & 1 != 0 } /// LE (local exact breakpoint enable) flag (bit 8). @@ -133,7 +133,7 @@ impl Dr7 { /// is recommended that the LE and GE flags be set to 1 if exact /// breakpoints are required. pub fn local_exact_breakpoint_0(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// GE (global exact breakpoint enable) flag (bit 9). @@ -150,7 +150,7 @@ impl Dr7 { /// is recommended that the LE and GE flags be set to 1 if exact /// breakpoints are required. pub fn global_exact_breakpoint_0(self) -> bool { - self.0 >> 9 & 1 != 0 + (self.0 >> 9) & 1 != 0 } /// RTM (restricted transactional memory) flag (bit 11). @@ -159,7 +159,7 @@ impl Dr7 { /// transactional regions. This advanced debugging is enabled only if /// IA32_DEBUGCTL.RTM is also set. pub fn restricted_transactional_memory(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } /// GD (general detect enable) flag (bit 13). @@ -178,7 +178,7 @@ impl Dr7 { /// The processor clears the GD flag upon entering to the debug exception /// handler, to allow the handler access to the debug registers. pub fn general_detect(self) -> bool { - self.0 >> 13 & 1 != 0 + (self.0 >> 13) & 1 != 0 } /// Condition for breakpoint 0 (R/W0). @@ -186,7 +186,7 @@ impl Dr7 { /// Specifies the breakpoint condition for the corresponding breakpoint /// (DR0). pub fn condition_0(self) -> BreakpointCondition { - match self.0 >> 16 & 0b11 { + match (self.0 >> 16) & 0b11 { 0b00 => BreakpointCondition::Execution, 0b01 => BreakpointCondition::Write, 0b10 => BreakpointCondition::Io, @@ -200,7 +200,7 @@ impl Dr7 { /// Specifies the size of the memory location at the address specified in /// the corresponding breakpoint address register (DR0). pub fn length_0(self) -> BreakpointLength { - match self.0 >> 18 & 0b11 { + match (self.0 >> 18) & 0b11 { 0b00 => BreakpointLength::Byte, 0b01 => BreakpointLength::Word, 0b10 => BreakpointLength::Quadword, @@ -214,7 +214,7 @@ impl Dr7 { /// Specifies the breakpoint condition for the corresponding breakpoint /// (DR1). pub fn condition_1(self) -> BreakpointCondition { - match self.0 >> 20 & 0b11 { + match (self.0 >> 20) & 0b11 { 0b00 => BreakpointCondition::Execution, 0b01 => BreakpointCondition::Write, 0b10 => BreakpointCondition::Io, @@ -228,7 +228,7 @@ impl Dr7 { /// Specifies the size of the memory location at the address specified in /// the corresponding breakpoint address register (DR1). pub fn length_1(self) -> BreakpointLength { - match self.0 >> 22 & 0b11 { + match (self.0 >> 22) & 0b11 { 0b00 => BreakpointLength::Byte, 0b01 => BreakpointLength::Word, 0b10 => BreakpointLength::Quadword, @@ -242,7 +242,7 @@ impl Dr7 { /// Specifies the breakpoint condition for the corresponding breakpoint /// (DR2). pub fn condition_2(self) -> BreakpointCondition { - match self.0 >> 24 & 0b11 { + match (self.0 >> 24) & 0b11 { 0b00 => BreakpointCondition::Execution, 0b01 => BreakpointCondition::Write, 0b10 => BreakpointCondition::Io, @@ -256,7 +256,7 @@ impl Dr7 { /// Specifies the size of the memory location at the address specified in /// the corresponding breakpoint address register (DR2). pub fn length_2(self) -> BreakpointLength { - match self.0 >> 26 & 0b11 { + match (self.0 >> 26) & 0b11 { 0b00 => BreakpointLength::Byte, 0b01 => BreakpointLength::Word, 0b10 => BreakpointLength::Quadword, @@ -270,7 +270,7 @@ impl Dr7 { /// Specifies the breakpoint condition for the corresponding breakpoint /// (DR3). pub fn condition_3(self) -> BreakpointCondition { - match self.0 >> 28 & 0b11 { + match (self.0 >> 28) & 0b11 { 0b00 => BreakpointCondition::Execution, 0b01 => BreakpointCondition::Write, 0b10 => BreakpointCondition::Io, @@ -284,7 +284,7 @@ impl Dr7 { /// Specifies the size of the memory location at the address specified in /// the corresponding breakpoint address register (DR3). pub fn length_3(self) -> BreakpointLength { - match self.0 >> 30 & 0b11 { + match (self.0 >> 30) & 0b11 { 0b00 => BreakpointLength::Byte, 0b01 => BreakpointLength::Word, 0b10 => BreakpointLength::Quadword, diff --git a/crates/vmi-arch-amd64/src/efer.rs b/crates/vmi-arch-amd64/src/efer.rs index d2fc4f2..0f85e05 100644 --- a/crates/vmi-arch-amd64/src/efer.rs +++ b/crates/vmi-arch-amd64/src/efer.rs @@ -16,7 +16,7 @@ impl MsrEfer { /// it. Long Mode becomes active when both this bit and the paging /// enable bit in CR0 are set. pub fn long_mode_enable(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// Checks if Long Mode (aka IA-32e mode) is active (LMA bit). @@ -25,7 +25,7 @@ impl MsrEfer { /// active. It is set by the processor when Long Mode is enabled and /// paging is turned on. pub fn long_mode_active(self) -> bool { - self.0 >> 10 & 1 != 0 + (self.0 >> 10) & 1 != 0 } /// Checks if the Execute Disable (NX) feature is enabled. @@ -34,7 +34,7 @@ impl MsrEfer { /// It allows marking of memory pages as non-executable, enhancing security /// by preventing the execution of code from data pages. pub fn execute_disable(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/event.rs b/crates/vmi-arch-amd64/src/event.rs index a1b9d20..7a3df43 100644 --- a/crates/vmi-arch-amd64/src/event.rs +++ b/crates/vmi-arch-amd64/src/event.rs @@ -203,27 +203,78 @@ impl EventReason { } /// Specifies which hardware events should be monitored. +/// +/// This enum is used to specify which hardware events should be monitored by +/// the VMI system. #[derive(Debug, Clone, Copy)] pub enum EventMonitor { // MemoryAccess, (implicit) /// Monitor writes to a specific control register. + /// + /// This method allows for setting up event triggers when certain CPU + /// registers are accessed or modified. The specific registers that can + /// be monitored depend on the architecture and are defined by the + /// [`Architecture::MonitorRegisterOptions`] type. + /// + /// When enabled, relevant events will be passed to the event callback + /// function. Register(ControlRegister), /// Monitor specific hardware interrupts or exception vectors. + /// + /// This method sets up event triggers for specified interrupt events. The + /// types of interrupts that can be monitored are defined by the + /// [`Architecture::MonitorInterruptOptions`] type, which is specific to + /// the architecture being used. + /// + /// When an interrupt event occurs, it will be passed to the event callback + /// function. Interrupt(ExceptionVector), /// Monitor singlestep execution of instructions. + /// + /// When enabled, this method causes the VMI system to generate an event + /// after each instruction execution in the guest. This can be useful + /// for detailed analysis of guest behavior, but may have a significant + /// performance impact. + /// + /// Single-step events will be passed to the event callback function when + /// they occur. Singlestep, /// Monitor execution of VMCALL instructions. + /// + /// When enabled, this method generates an event each time a VMCALL + /// instruction is executed in the guest. This can be useful for + /// implementing custom guest requests or for implementing custom + /// virtual device behavior. + /// + /// VMCALL events will be passed to the event callback function when they + /// occur. GuestRequest { /// Allow userspace to handle the VMCALL. allow_userspace: bool, }, /// Monitor execution of CPUID instructions. + /// + /// When enabled, this method generates an event each time a CPUID + /// instruction is executed in the guest. This can be useful for + /// analyzing how the guest queries CPU features or for implementing CPU + /// feature spoofing. + /// + /// CPUID events will be passed to the event callback function when they + /// occur. CpuId, /// Monitor I/O port accesses. + /// + /// When enabled, this method generates events for I/O port read and write + /// operations performed by the guest. This can be useful for analyzing + /// guest interactions with virtual hardware or for implementing custom + /// virtual device behavior. + /// + /// I/O port events will be passed to the event callback function when they + /// occur. Io, } diff --git a/crates/vmi-arch-amd64/src/interrupt/exception.rs b/crates/vmi-arch-amd64/src/interrupt/exception.rs index 5faf598..d208360 100644 --- a/crates/vmi-arch-amd64/src/interrupt/exception.rs +++ b/crates/vmi-arch-amd64/src/interrupt/exception.rs @@ -2,7 +2,7 @@ #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ExceptionVector(pub u8); -#[allow(non_upper_case_globals)] +#[expect(non_upper_case_globals)] impl ExceptionVector { /// Divide Error (#DE). /// diff --git a/crates/vmi-arch-amd64/src/interrupt/idt.rs b/crates/vmi-arch-amd64/src/interrupt/idt.rs index e1f43dc..071b133 100644 --- a/crates/vmi-arch-amd64/src/interrupt/idt.rs +++ b/crates/vmi-arch-amd64/src/interrupt/idt.rs @@ -16,12 +16,12 @@ impl IdtAccess { /// Returns the type of the interrupt gate. fn typ(self) -> u8 { - (self.0 >> 8 & 0b1111) as _ + ((self.0 >> 8) & 0b1111) as _ } /// Returns the descriptor type. fn descriptor_type(self) -> DescriptorType { - if self.0 >> 11 & 1 == 0 { + if (self.0 >> 11) & 1 == 0 { DescriptorType::System } else { @@ -31,12 +31,12 @@ impl IdtAccess { /// Returns the descriptor privilege level. fn descriptor_privilege_level(self) -> u8 { - (self.0 >> 13 & 0b11) as _ + ((self.0 >> 13) & 0b11) as _ } /// Returns whether the interrupt gate is present. fn present(self) -> bool { - self.0 >> 15 & 1 != 0 + (self.0 >> 15) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/lib.rs b/crates/vmi-arch-amd64/src/lib.rs index 6b8e0ab..2b365e1 100644 --- a/crates/vmi-arch-amd64/src/lib.rs +++ b/crates/vmi-arch-amd64/src/lib.rs @@ -14,7 +14,8 @@ mod segment; mod translation; use vmi_core::{ - AddressContext, Architecture, Gfn, MemoryAccess, Pa, Va, VmiCore, VmiDriver, VmiError, + AccessContext, AddressContext, Architecture, Gfn, MemoryAccess, Pa, Va, VmiCore, VmiDriver, + VmiError, }; use zerocopy::FromBytes; @@ -216,12 +217,9 @@ impl Amd64 { /// /// - **No Paging**: When paging is disabled (CR0.PG = 0) /// - **32-bit Paging**: Used when CR0.PG = 1 and CR4.PAE = 0 - /// - **PAE Paging**: Used when CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = - /// 0 - /// - **4-level Paging**: Used when CR0.PG = 1, CR4.PAE = 1, IA32_EFER.LME = - /// 1, and CR4.LA57 = 0 - /// - **5-level Paging**: Used when CR0.PG = 1, CR4.PAE = 1, IA32_EFER.LME = - /// 1, and CR4.LA57 = 1 + /// - **PAE Paging**: Used when CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 0 + /// - **4-level Paging**: Used when CR0.PG = 1, CR4.PAE = 1, IA32_EFER.LME = 1, and CR4.LA57 = 0 + /// - **5-level Paging**: Used when CR0.PG = 1, CR4.PAE = 1, IA32_EFER.LME = 1, and CR4.LA57 = 1 /// /// If paging is disabled, the function returns `None`. pub fn paging_mode(registers: &Registers) -> Option { @@ -517,6 +515,10 @@ impl vmi_core::arch::Registers for Registers { } } + fn access_context(&self, va: Va) -> AccessContext { + self.address_context(va).into() + } + fn address_context(&self, va: Va) -> AddressContext { (va, self.cr3.into()).into() } diff --git a/crates/vmi-arch-amd64/src/paging.rs b/crates/vmi-arch-amd64/src/paging.rs index 2edf141..886d5c6 100644 --- a/crates/vmi-arch-amd64/src/paging.rs +++ b/crates/vmi-arch-amd64/src/paging.rs @@ -85,50 +85,50 @@ impl PageTableEntry { /// Checks if the page is writable. pub fn write(self) -> bool { - self.0 >> 1 & 1 != 0 + (self.0 >> 1) & 1 != 0 } /// Checks if the page is accessible in user mode. /// Note: Returns true for user mode, false for supervisor mode. pub fn supervisor(self) -> bool { - self.0 >> 2 & 1 != 0 + (self.0 >> 2) & 1 != 0 } /// Checks if write-through caching is enabled for the page. pub fn page_level_write_through(self) -> bool { - self.0 >> 3 & 1 != 0 + (self.0 >> 3) & 1 != 0 } /// Checks if caching is disabled for the page. pub fn page_level_cache_disable(self) -> bool { - self.0 >> 4 & 1 != 0 + (self.0 >> 4) & 1 != 0 } /// Checks if the page has been accessed. pub fn accessed(self) -> bool { - self.0 >> 5 & 1 != 0 + (self.0 >> 5) & 1 != 0 } /// Checks if the page has been written to. pub fn dirty(self) -> bool { - self.0 >> 6 & 1 != 0 + (self.0 >> 6) & 1 != 0 } /// Checks if this entry refers to a large page. pub fn large(self) -> bool { - self.0 >> 7 & 1 != 0 + (self.0 >> 7) & 1 != 0 } /// Checks if the page is global (shared between all processes). pub fn global(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// Extracts the page frame number from the entry. pub fn pfn(self) -> Gfn { const BITS: u64 = 40; const MASK: u64 = (1 << BITS) - 1; - Gfn::new(self.0 >> 12 & MASK) + Gfn::new((self.0 >> 12) & MASK) } } diff --git a/crates/vmi-arch-amd64/src/registers.rs b/crates/vmi-arch-amd64/src/registers.rs index d5be618..35586c6 100644 --- a/crates/vmi-arch-amd64/src/registers.rs +++ b/crates/vmi-arch-amd64/src/registers.rs @@ -4,7 +4,7 @@ use super::{ }; /// The state of the CPU registers. -#[allow(missing_docs)] +#[expect(missing_docs)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Registers { pub rax: u64, @@ -66,7 +66,7 @@ pub struct Registers { // vmtrace_pos: u64, } -#[allow(missing_docs)] +#[expect(missing_docs)] /// General-purpose registers. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct GpRegisters { diff --git a/crates/vmi-arch-amd64/src/rflags.rs b/crates/vmi-arch-amd64/src/rflags.rs index 6fa72cb..87174ce 100644 --- a/crates/vmi-arch-amd64/src/rflags.rs +++ b/crates/vmi-arch-amd64/src/rflags.rs @@ -34,7 +34,7 @@ impl Rflags { /// Set if the least-significant byte of the result contains an even number /// of 1 bits; cleared otherwise. pub fn parity(self) -> bool { - self.0 >> 2 & 1 != 0 + (self.0 >> 2) & 1 != 0 } /// Checks if the Auxiliary Carry Flag (AF) is set. @@ -43,14 +43,14 @@ impl Rflags { /// 3 of the result; cleared otherwise. This flag is used in binary-coded /// decimal (BCD) arithmetic. pub fn auxiliary_carry(self) -> bool { - self.0 >> 4 & 1 != 0 + (self.0 >> 4) & 1 != 0 } /// Checks if the Zero Flag (ZF) is set. /// /// Set if the result is zero; cleared otherwise. pub fn zero(self) -> bool { - self.0 >> 6 & 1 != 0 + (self.0 >> 6) & 1 != 0 } /// Checks if the Sign Flag (SF) is set. @@ -59,7 +59,7 @@ impl Rflags { /// bit of a signed integer. (0 indicates a positive value and 1 /// indicates a negative value.) pub fn sign(self) -> bool { - self.0 >> 7 & 1 != 0 + (self.0 >> 7) & 1 != 0 } /// Checks if the Trap Flag (TF) is set. @@ -67,7 +67,7 @@ impl Rflags { /// Set to enable single-step mode for debugging; clear to disable /// single-step mode. pub fn trap(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// Checks if the Interrupt Enable Flag (IF) is set. @@ -76,7 +76,7 @@ impl Rflags { /// requests. Set to respond to maskable interrupts; cleared to inhibit /// maskable interrupts. pub fn interrupt_enable(self) -> bool { - self.0 >> 9 & 1 != 0 + (self.0 >> 9) & 1 != 0 } /// Checks if the Direction Flag (DF) is set. @@ -87,7 +87,7 @@ impl Rflags { /// DF flag causes the string instructions to auto-increment (process /// strings from low addresses to high addresses). pub fn direction(self) -> bool { - self.0 >> 10 & 1 != 0 + (self.0 >> 10) & 1 != 0 } /// Checks if the Overflow Flag (OF) is set. @@ -97,7 +97,7 @@ impl Rflags { /// operand; cleared otherwise. This flag indicates an overflow /// condition for signed-integer (two’s complement) arithmetic. pub fn overflow(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } /// Returns the I/O Privilege Level (IOPL). @@ -112,7 +112,7 @@ impl Rflags { /// /// A value between 0 and 3, representing the current I/O privilege level. pub fn io_privilege_level(self) -> u8 { - (self.0 >> 12 & 0b11) as _ + ((self.0 >> 12) & 0b11) as _ } /// Checks if the Nested Task (NT) flag is set. @@ -121,14 +121,14 @@ impl Rflags { /// current task is linked to the previously executed task; cleared when the /// current task is not linked to another task. pub fn nested_task(self) -> bool { - self.0 >> 14 & 1 != 0 + (self.0 >> 14) & 1 != 0 } /// Checks if the Resume Flag (RF) is set. /// /// Controls the processor’s response to debug exceptions. pub fn resume(self) -> bool { - self.0 >> 16 & 1 != 0 + (self.0 >> 16) & 1 != 0 } /// Checks if Virtual 8086 Mode (VM) is active. @@ -136,7 +136,7 @@ impl Rflags { /// Set to enable virtual-8086 mode; clear to return to protected /// mode without virtual-8086 mode semantics. pub fn virtual_8086_mode(self) -> bool { - self.0 >> 17 & 1 != 0 + (self.0 >> 17) & 1 != 0 } /// Checks if the Alignment Check (AC) flag is set. @@ -147,7 +147,7 @@ impl Rflags { /// supervisor-mode data accesses to user-mode pages are allowed if and /// only if this bit is 1. pub fn alignment_check(self) -> bool { - self.0 >> 18 & 1 != 0 + (self.0 >> 18) & 1 != 0 } /// Checks if the Virtual Interrupt Flag (VIF) is set. @@ -156,7 +156,7 @@ impl Rflags { /// (To use this flag and the VIP flag the virtual mode extensions are /// enabled by setting the VME flag in control register CR4.) pub fn virtual_interrupt(self) -> bool { - self.0 >> 19 & 1 != 0 + (self.0 >> 19) & 1 != 0 } /// Checks if the Virtual Interrupt Pending (VIP) flag is set. @@ -165,7 +165,7 @@ impl Rflags { /// interrupt is pending. (Software sets and clears this flag; the processor /// only reads it.) Used in conjunction with the VIF flag. pub fn virtual_interrupt_pending(self) -> bool { - self.0 >> 20 & 1 != 0 + (self.0 >> 20) & 1 != 0 } /// Checks if the Identification Flag (ID) is set. @@ -173,7 +173,7 @@ impl Rflags { /// The ability of a program to set or clear this flag indicates support for /// the CPUID instruction. pub fn identification(self) -> bool { - self.0 >> 21 & 1 != 0 + (self.0 >> 21) & 1 != 0 } } diff --git a/crates/vmi-arch-amd64/src/segment/mod.rs b/crates/vmi-arch-amd64/src/segment/mod.rs index 05b8509..183e0c0 100644 --- a/crates/vmi-arch-amd64/src/segment/mod.rs +++ b/crates/vmi-arch-amd64/src/segment/mod.rs @@ -53,7 +53,7 @@ impl SegmentAccess { /// Specifies whether the segment descriptor is for a system segment (S flag /// is clear) or a code or data segment (S flag is set). pub fn descriptor_type(self) -> DescriptorType { - if self.0 >> 4 & 1 == 0 { + if (self.0 >> 4) & 1 == 0 { DescriptorType::System } else { @@ -67,7 +67,7 @@ impl SegmentAccess { /// Levels”, for a description of the relationship of the DPL to the CPL of /// the executing code segment and the RPL of a segment selector. pub fn descriptor_privilege_level(self) -> u8 { - (self.0 >> 5 & 0b11) as _ + ((self.0 >> 5) & 0b11) as _ } /// Indicates whether the segment is present in memory (set) or not present @@ -79,12 +79,12 @@ impl SegmentAccess { /// time. It offers a control in addition to paging for managing virtual /// memory. pub fn present(self) -> bool { - self.0 >> 7 & 1 != 0 + (self.0 >> 7) & 1 != 0 } /// This bit is available for use by system software. pub fn available_bit(self) -> bool { - self.0 >> 8 & 1 != 0 + (self.0 >> 8) & 1 != 0 } /// In IA-32e mode, bit 21 of the second doubleword of the segment @@ -96,7 +96,7 @@ impl SegmentAccess { /// or for non-code segments, bit 21 is reserved and should always be set to /// 0. pub fn long_mode(self) -> bool { - self.0 >> 9 & 1 != 0 + (self.0 >> 9) & 1 != 0 } /// Performs different functions depending on whether the segment descriptor @@ -128,7 +128,7 @@ impl SegmentAccess { /// upper bound is FFFFFFFFH (4 GBytes); if the flag is clear, the upper /// bound is FFFFH (64 KBytes). pub fn operation_size(self) -> OperationSize { - if self.0 >> 10 & 1 == 0 { + if (self.0 >> 10) & 1 == 0 { OperationSize::Default } else { @@ -145,7 +145,7 @@ impl SegmentAccess { /// offset against the segment limit. For example, when the granularity flag /// is set, a limit of 0 results in valid offsets from 0 to 4095. pub fn granularity(self) -> Granularity { - if self.0 >> 11 & 1 == 0 { + if (self.0 >> 11) & 1 == 0 { Granularity::Byte } else { diff --git a/crates/vmi-arch-amd64/src/segment/selector.rs b/crates/vmi-arch-amd64/src/segment/selector.rs index d4b75a3..b92f480 100644 --- a/crates/vmi-arch-amd64/src/segment/selector.rs +++ b/crates/vmi-arch-amd64/src/segment/selector.rs @@ -27,7 +27,7 @@ impl Selector { /// Specifies the descriptor table to use: clearing this flag selects the /// GDT; setting this flag selects the current LDT. pub fn table(self) -> DescriptorTable { - match self.0 >> 2 & 1 { + match (self.0 >> 2) & 1 { 0 => DescriptorTable::Gdt, 1 => DescriptorTable::Ldt, _ => unreachable!(), @@ -39,7 +39,7 @@ impl Selector { /// descriptor) and adds the result to the base address of the GDT or /// LDT (from the GDTR or LDTR register, respectively). pub fn index(self) -> u16 { - self.0 >> 3 & 0x1fff + (self.0 >> 3) & 0x1fff } } diff --git a/crates/vmi-core/Cargo.toml b/crates/vmi-core/Cargo.toml index 924e28b..1b33468 100644 --- a/crates/vmi-core/Cargo.toml +++ b/crates/vmi-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-core" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } diff --git a/crates/vmi-core/docs/os.md b/crates/vmi-core/docs/os.md index 777de7e..f0fec36 100644 --- a/crates/vmi-core/docs/os.md +++ b/crates/vmi-core/docs/os.md @@ -5,23 +5,99 @@ virtual machines. It allows for high-level analysis and manipulation of guest operating systems, abstracting away many of the low-level details of different OS implementations. +## Overview + +This module is at the heart of OS introspection. It defines several traits that +allow users to implement OS-specific logic while offering a consistent interface +to: +- Enumerate and inspect processes and threads. +- Analyze memory regions, including both private and file-backed (mapped) regions. +- Inspect kernel modules and executable images. +- Safely read structured data from guest memory. + +Additionally, a dummy implementation ([`NoOS`]) is provided as a placeholder for +cases where an OS-specific implementation is not available or required. + ## Key Components -- [`VmiOs`]: The core trait for implementing OS-specific introspection - capabilities. -- [`OsProcess`]: A process within the guest OS. -- [`OsRegion`]: A memory region within a process. -- [`ProcessObject`]: An opaque handle to a process in the guest OS. -- [`ThreadObject`]: An opaque handle to a thread in the guest OS. +### Core OS Trait + +- **[`VmiOs`]** + This is the central trait for OS introspection. It defines associated types + for the following: + - **Process**: Represented via the [`VmiOsProcess`] trait. + - **Thread**: Represented via the [`VmiOsThread`] trait. + - **Executable Image**: Represented via the [`VmiOsImage`] trait. + - **Kernel Module**: Represented via the [`VmiOsModule`] trait. + - **Memory Region**: Represented via the [`VmiOsRegion`] trait. + - **Mapped Region**: Represented via the [`VmiOsMapped`] trait. + + In addition to these, it provides methods to retrieve critical OS-specific + information, such as the kernel image base, and whether Kernel Page Table + Isolation (KPTI) is enabled. + +### Process and Thread Introspection + +- **[`VmiOsProcess`]** + + Provides an interface for inspecting guest processes. It offers methods to + obtain the process ID, name, parent process ID, memory translation roots, + and memory regions. + +- **[`ProcessObject`] and [`ProcessId`]** + + Strong types that represent underlying OS process structures such as + `_EPROCESS` on Windows or `task_struct` on Linux. This design minimizes + mistakes by ensuring that process objects are used correctly within the API. + +- **[`VmiOsThread`]** + + Offers methods to inspect thread objects, including obtaining the + thread ID and an associated thread object. + +- **[`ThreadObject`] and [`ThreadId`]** + + Similar to process types, these are strong types representing underlying + OS thread structures (e.g., `_ETHREAD` on Windows). Their strong typing helps + prevent mix-ups with other addresses or identifiers. + +### Memory Region and Mapped Region Introspection + +- **[`VmiOsRegion`]** + + Defines the interface for memory region introspection. Methods include + obtaining the start and end addresses, memory protection details, and the + kind of region (private or mapped). + +- **[`VmiOsRegionKind`]** + + An enum that distinguishes between private and mapped memory regions. + +- **[`VmiOsMapped`]** + + Specializes in introspecting memory regions that are file-backed. It provides + a method to retrieve the backing file’s path. + +### Kernel Modules and Executable Images + +- **[`VmiOsModule`]** + + Offers an abstraction for kernel module introspection, providing methods to + access a module’s base address, size, and name. + +- **[`VmiOsImage`]** + + Defines methods for executable images (binaries, shared libraries) to retrieve + the base address, target architecture, and exported symbols. + +### Additional Components -## Usage +- **[`StructReader`]** -Implementations of `VmiOs` provide methods for introspecting various aspects -of the guest OS, such as enumerating processes, analyzing memory regions, -and extracting OS-specific information. + A utility for safely reading structured data (such as C structs) from guest + memory. -To use OS-aware introspection: +- **[`NoOS`]** -1. Implement the `VmiOs` trait for your specific guest OS. -2. Use the implemented methods to perform high-level analysis of the guest - OS. + A dummy implementation of the `VmiOs` trait for cases where no specific OS + introspection is provided. It serves as a placeholder or for testing purposes. diff --git a/crates/vmi-core/src/arch.rs b/crates/vmi-core/src/arch.rs index f4862db..d0d81ca 100644 --- a/crates/vmi-core/src/arch.rs +++ b/crates/vmi-core/src/arch.rs @@ -2,7 +2,9 @@ use std::fmt::Debug; -use crate::{AddressContext, Gfn, MemoryAccess, Pa, Va, VmiCore, VmiDriver, VmiError}; +use crate::{ + AccessContext, AddressContext, Gfn, MemoryAccess, Pa, Va, VmiCore, VmiDriver, VmiError, +}; /// Defines an interface for CPU architecture-specific operations and constants. /// @@ -228,6 +230,9 @@ where /// - **AMD64**: 8 bytes if the `CS.L` bit is set, otherwise 4 bytes fn effective_address_width(&self) -> usize; + /// Creates an access context for a given virtual address. + fn access_context(&self, va: Va) -> AccessContext; + /// Creates an address context for a given virtual address. fn address_context(&self, va: Va) -> AddressContext; diff --git a/crates/vmi-core/src/context.rs b/crates/vmi-core/src/context.rs deleted file mode 100644 index dd9866c..0000000 --- a/crates/vmi-core/src/context.rs +++ /dev/null @@ -1,500 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use indexmap::IndexSet; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -use crate::{ - os::VmiOs, - session::{VmiSession, VmiSessionProber}, - Architecture, Pa, PageFault, PageFaults, Registers as _, Va, VmiCore, VmiDriver, VmiError, - VmiEvent, -}; - -/// A VMI context. -/// -/// `VmiContext` combines access to a [`VmiSession`] with [`VmiEvent`] to -/// provide unified access to VMI operations in the context of a specific event. -/// -/// This structure is created inside the [`VmiSession::handle`] method and -/// passed to the [`VmiHandler::handle_event`] method to handle VMI events. -/// -/// [`VmiHandler::handle_event`]: crate::VmiHandler::handle_event -pub struct VmiContext<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI session. - pub(crate) session: &'a VmiSession<'a, Driver, Os>, - - /// The VMI event. - pub(crate) event: &'a VmiEvent, -} - -impl<'a, Driver, Os> std::ops::Deref for VmiContext<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - type Target = VmiSession<'a, Driver, Os>; - - fn deref(&self) -> &Self::Target { - self.session - } -} - -impl<'a, Driver, Os> VmiContext<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Creates a new VMI context. - pub fn new( - session: &'a VmiSession, - event: &'a VmiEvent, - ) -> Self { - Self { session, event } - } - - /// Returns the VMI session. - pub fn session(&self) -> &VmiSession { - self.session - } - - /// Returns the VMI core. - pub fn core(&self) -> &VmiCore { - self.session.core() - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &Os { - self.session.underlying_os() - } - - /// Returns a wrapper providing access to OS-specific operations. - pub fn os(&self) -> VmiOsContext { - VmiOsContext { - session: self.session, - event: self.event, - } - } - - /// Creates a prober for safely handling page faults during memory access operations. - pub fn prober(&'a self, restricted: &IndexSet) -> VmiContextProber<'a, Driver, Os> { - VmiContextProber::new(self, restricted) - } - - /// Returns the current VMI event. - pub fn event(&self) -> &VmiEvent { - self.event - } - - /// Returns the CPU registers associated with the current event. - pub fn registers(&self) -> &::Registers { - self.event.registers() - } - - /// Returns the return address from the current stack frame. - pub fn return_address(&self) -> Result { - self.registers().return_address(self.core()) - } - - /// Reads memory from the virtual machine. - pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError> { - self.core().read(self.access_context(address), buffer) - } - - /// Writes memory to the virtual machine. - pub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError> { - self.core().write(self.access_context(address), buffer) - } - - /// Reads a single byte from the virtual machine. - pub fn read_u8(&self, address: Va) -> Result { - self.core().read_u8(self.access_context(address)) - } - - /// Reads a 16-bit unsigned integer from the virtual machine. - pub fn read_u16(&self, address: Va) -> Result { - self.core().read_u16(self.access_context(address)) - } - - /// Reads a 32-bit unsigned integer from the virtual machine. - pub fn read_u32(&self, address: Va) -> Result { - self.core().read_u32(self.access_context(address)) - } - - /// Reads a 64-bit unsigned integer from the virtual machine. - pub fn read_u64(&self, address: Va) -> Result { - self.core().read_u64(self.access_context(address)) - } - - /// Reads a virtual address from the virtual machine. - pub fn read_va(&self, address: Va) -> Result { - self.core().read_va( - self.access_context(address), - self.registers().effective_address_width(), - ) - } - - /// Reads a 32-bit virtual address from the virtual machine. - pub fn read_va32(&self, address: Va) -> Result { - self.core().read_va32(self.access_context(address)) - } - - /// Reads a 64-bit virtual address from the virtual machine. - pub fn read_va64(&self, address: Va) -> Result { - self.core().read_va64(self.access_context(address)) - } - - /// Reads a null-terminated string of bytes from the virtual machine. - pub fn read_string_bytes(&self, address: Va) -> Result, VmiError> { - self.core().read_string_bytes(self.access_context(address)) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring_bytes(&self, address: Va) -> Result, VmiError> { - self.core().read_wstring_bytes(self.access_context(address)) - } - - /// Reads a null-terminated string from the virtual machine. - pub fn read_string(&self, address: Va) -> Result { - self.core().read_string(self.access_context(address)) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring(&self, address: Va) -> Result { - self.core().read_wstring(self.access_context(address)) - } - - /// Reads a struct from the virtual machine. - pub fn read_struct(&self, address: Va) -> Result - where - T: IntoBytes + FromBytes, - { - self.core().read_struct(self.access_context(address)) - } - - /// Writes a single byte to the virtual machine. - pub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError> { - self.core().write_u8(self.access_context(address), value) - } - - /// Writes a 16-bit unsigned integer to the virtual machine. - pub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError> { - self.core().write_u16(self.access_context(address), value) - } - - /// Writes a 32-bit unsigned integer to the virtual machine. - pub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError> { - self.core().write_u32(self.access_context(address), value) - } - - /// Writes a 64-bit unsigned integer to the virtual machine. - pub fn write_u64(&self, address: Va, value: u64) -> Result<(), VmiError> { - self.core().write_u64(self.access_context(address), value) - } - - /// Writes a struct to the virtual machine. - pub fn write_struct(&self, address: Va, value: T) -> Result<(), VmiError> - where - T: FromBytes + IntoBytes + Immutable, - { - self.core() - .write_struct(self.access_context(address), value) - } - - /// Translates a virtual address to a physical address. - pub fn translate_address(&self, va: Va) -> Result { - self.core() - .translate_address((va, self.translation_root(va))) - } - - /// Returns the physical address of the root of the current page table - /// hierarchy for a given virtual address. - fn translation_root(&self, va: Va) -> Pa { - self.registers().translation_root(va) - } - - /// Creates an address context for a given virtual address. - fn access_context(&self, address: Va) -> (Va, Pa) { - (address, self.translation_root(address)) - } -} - -/// Wrapper providing access to OS-specific operations. -pub struct VmiOsContext<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI session. - pub(crate) session: &'a VmiSession<'a, Driver, Os>, - - /// The VMI event. - pub(crate) event: &'a VmiEvent, -} - -impl<'a, Driver, Os> VmiOsContext<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Returns the VMI context. - pub fn core(&self) -> &'a VmiCore { - self.session.core - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &'a Os { - self.session.os - } - - /// Returns the current VMI event. - pub fn event(&self) -> &'a VmiEvent { - self.event - } - - /* - pub fn function_argument_for_registers( - &self, - regs: &::Registers, - index: u64, - ) -> Result { - self.0.session.os().function_argument(regs, index) - } - - pub fn function_return_value_for_registers( - &self, - regs: &::Registers, - ) -> Result { - self.0.session.os().function_return_value(regs) - } - */ -} - -/// Prober for safely handling page faults during memory access operations. -pub struct VmiContextProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI context. - pub(crate) context: &'a VmiContext<'a, Driver, Os>, - - /// The set of restricted page faults that are allowed to occur. - pub(crate) restricted: Rc>, - - /// The set of page faults that have occurred. - pub(crate) page_faults: Rc>>, -} - -impl<'a, Driver, Os> std::ops::Deref for VmiContextProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - type Target = VmiContext<'a, Driver, Os>; - - fn deref(&self) -> &Self::Target { - self.context - } -} - -impl<'a, Driver, Os> VmiContextProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Creates a new VMI context prober. - pub fn new(context: &'a VmiContext, restricted: &IndexSet) -> Self { - Self { - context, - restricted: Rc::new(restricted.clone()), - page_faults: Rc::new(RefCell::new(IndexSet::new())), - } - } - - /// Checks for any unexpected page faults that have occurred and returns an error if any are present. - #[tracing::instrument(skip_all)] - pub fn error_for_page_faults(&self) -> Result<(), VmiError> { - let pfs = self.page_faults.borrow(); - let new_pfs = &*pfs - &self.restricted; - if !new_pfs.is_empty() { - tracing::trace!(?new_pfs); - return Err(VmiError::page_faults(new_pfs)); - } - - Ok(()) - } - - /// Returns the VMI session prober. - pub fn session(&self) -> VmiSessionProber<'a, Driver, Os> { - VmiSessionProber { - session: self.context.session, - restricted: self.restricted.clone(), - page_faults: self.page_faults.clone(), - } - } - - /// Returns a wrapper providing access to OS-specific operations. - pub fn os(&self) -> VmiOsContextProber { - VmiOsContextProber(self) - } - - /// Returns the current VMI event. - pub fn event(&self) -> &VmiEvent { - self.context.event() - } - - /// Returns the CPU registers associated with the current event. - pub fn registers(&self) -> &::Registers { - self.context.registers() - } - - /// Returns the return address from the current stack frame. - pub fn return_address(&self) -> Result, VmiError> { - self.check_result(self.context.return_address()) - } - - /// Reads memory from the virtual machine. - pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result, VmiError> { - self.check_result(self.context.read(address, buffer)) - } - - /// Reads a single byte from the virtual machine. - pub fn read_u8(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_u8(address)) - } - - /// Reads a 16-bit unsigned integer from the virtual machine. - pub fn read_u16(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_u16(address)) - } - - /// Reads a 32-bit unsigned integer from the virtual machine. - pub fn read_u32(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_u32(address)) - } - - /// Reads a 64-bit unsigned integer from the virtual machine. - pub fn read_u64(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_u64(address)) - } - - /// Reads a virtual address from the virtual machine. - pub fn read_va(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_va(address)) - } - - /// Reads a 32-bit virtual address from the virtual machine. - pub fn read_va32(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_va32(address)) - } - - /// Reads a 64-bit virtual address from the virtual machine. - pub fn read_va64(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_va64(address)) - } - - /// Reads a null-terminated string of bytes from the virtual machine. - pub fn read_string_bytes(&self, address: Va) -> Result>, VmiError> { - self.check_result(self.context.read_string_bytes(address)) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring_bytes(&self, address: Va) -> Result>, VmiError> { - self.check_result(self.context.read_wstring_bytes(address)) - } - - /// Reads a null-terminated string from the virtual machine. - pub fn read_string(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_string(address)) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring(&self, address: Va) -> Result, VmiError> { - self.check_result(self.context.read_wstring(address)) - } - - /// Reads a struct from the virtual machine. - pub fn read_struct(&self, address: Va) -> Result, VmiError> - where - T: IntoBytes + FromBytes, - { - self.check_result(self.context.read_struct(address)) - } - - /// Handles a result that may contain page faults, returning the value if successful. - pub fn check_result(&self, result: Result) -> Result, VmiError> { - match result { - Ok(value) => Ok(Some(value)), - Err(VmiError::PageFault(pfs)) => { - self.check_restricted(pfs); - Ok(None) - } - Err(err) => Err(err), - } - } - - /// Records any page faults that are not in the restricted set. - fn check_restricted(&self, pfs: PageFaults) { - let mut page_faults = self.page_faults.borrow_mut(); - for pf in pfs { - if !self.restricted.contains(&pf) { - tracing::trace!(va = %pf.address, "page fault"); - page_faults.insert(pf); - } - else { - tracing::trace!(va = %pf.address, "restricted page fault"); - } - } - } -} - -/// Wrapper providing access to OS-specific operations with page fault handling. -pub struct VmiOsContextProber<'a, Driver, Os>(pub(crate) &'a VmiContextProber<'a, Driver, Os>) -where - Driver: VmiDriver, - Os: VmiOs; - -impl<'a, Driver, Os> VmiOsContextProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Returns the VMI context prober. - pub fn core(&self) -> &'a VmiContextProber<'a, Driver, Os> { - self.0 - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &'a Os { - self.0.underlying_os() - } - - /// Returns the current VMI event. - pub fn event(&self) -> &'a VmiEvent { - self.0.event - } - - /// Retrieves a specific function argument according to the calling - /// convention of the operating system. - pub fn function_argument_for_registers( - &self, - regs: &::Registers, - index: u64, - ) -> Result, VmiError> { - self.0 - .check_result(self.0.context.session.os().function_argument(regs, index)) - } - - /// Retrieves the return value of a function. - pub fn function_return_value_for_registers( - &self, - regs: &::Registers, - ) -> Result, VmiError> { - self.0 - .check_result(self.0.context.session.os().function_return_value(regs)) - } -} diff --git a/crates/vmi-core/src/core/access_context.rs b/crates/vmi-core/src/core/access_context.rs index dd83e09..6b85678 100644 --- a/crates/vmi-core/src/core/access_context.rs +++ b/crates/vmi-core/src/core/access_context.rs @@ -14,6 +14,12 @@ impl Va { } } +/// A trait for types that have a virtual address. +pub trait VmiVa { + /// Returns the virtual address. + fn va(&self) -> Va; +} + /// The mechanism used for translating virtual addresses to physical addresses. /// /// Understanding and navigating the memory translation mechanisms of the target @@ -101,7 +107,12 @@ impl From<(Va, Pa)> for AccessContext { impl From for AccessContext { fn from(value: AddressContext) -> Self { - Self::paging(value.va, value.root) + Self { + address: value.va.0, + mechanism: TranslationMechanism::Paging { + root: Some(value.root), + }, + } } } diff --git a/crates/vmi-core/src/core/address_context.rs b/crates/vmi-core/src/core/address_context.rs index fff1e44..ede7add 100644 --- a/crates/vmi-core/src/core/address_context.rs +++ b/crates/vmi-core/src/core/address_context.rs @@ -54,9 +54,21 @@ impl AddressContext { } } +//impl From for AddressContext { +// fn from(value: Va) -> Self { +// Self { +// va: value, +// root: None, +// } +// } +//} + impl From<(Va, Pa)> for AddressContext { fn from(value: (Va, Pa)) -> Self { - Self::new(value.0, value.1) + Self { + va: value.0, + root: value.1, + } } } diff --git a/crates/vmi-core/src/core/mod.rs b/crates/vmi-core/src/core/mod.rs index f653846..775488f 100644 --- a/crates/vmi-core/src/core/mod.rs +++ b/crates/vmi-core/src/core/mod.rs @@ -8,7 +8,7 @@ mod vcpu_id; mod view; pub use self::{ - access_context::{AccessContext, Gfn, Pa, TranslationMechanism, Va}, + access_context::{AccessContext, Gfn, Pa, TranslationMechanism, Va, VmiVa}, address_context::AddressContext, hex::Hex, info::VmiInfo, diff --git a/crates/vmi-core/src/ctx/context.rs b/crates/vmi-core/src/ctx/context.rs new file mode 100644 index 0000000..6d547bb --- /dev/null +++ b/crates/vmi-core/src/ctx/context.rs @@ -0,0 +1,136 @@ +use super::{state::VmiState, VmiOsState}; +use crate::{os::VmiOs, VmiCore, VmiDriver, VmiEvent}; + +/// A VMI context. +/// +/// The context combines access to a [`VmiState`] with [`VmiEvent`] to +/// provide unified access to VMI operations in the context of a specific +/// event. +/// +/// This structure is created inside the [`VmiSession::handle`] method and +/// passed to the [`VmiHandler::handle_event`] method to handle VMI events. +/// +/// [`VmiHandler::handle_event`]: crate::VmiHandler::handle_event +pub struct VmiContext<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// The VMI session. + state: &'a VmiState<'a, Driver, Os>, + + /// The VMI event. + event: &'a VmiEvent, +} + +impl Clone for VmiContext<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Copy for VmiContext<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ +} + +impl<'a, Driver, Os> std::ops::Deref for VmiContext<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + type Target = VmiState<'a, Driver, Os>; + + fn deref(&self) -> &Self::Target { + self.state + } +} + +impl<'a, Driver, Os> VmiContext<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Creates a new VMI context. + pub fn new( + state: &'a VmiState<'a, Driver, Os>, + event: &'a VmiEvent, + ) -> Self { + debug_assert_eq!(state.registers() as *const _, event.registers() as *const _); + + Self { state, event } + } + + // Note that `core()` and `underlying_os()` and other methods are delegated + // to the `VmiState`. + + /// Returns the VMI session. + pub fn state(&self) -> VmiState<'a, Driver, Os> { + *self.state + } + + /// Returns the current VMI event. + pub fn event(&self) -> &VmiEvent { + self.event + } + + /// Returns a wrapper providing access to OS-specific operations. + pub fn os(&self) -> VmiOsContext { + VmiOsContext { + state: self.state.os(), + event: self.event, + } + } +} + +/// Wrapper providing access to OS-specific operations. +pub struct VmiOsContext<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// The VMI OS state. + state: VmiOsState<'a, Driver, Os>, + + /// The VMI event. + event: &'a VmiEvent, +} + +impl<'a, Driver, Os> std::ops::Deref for VmiOsContext<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + type Target = VmiOsState<'a, Driver, Os>; + + fn deref(&self) -> &Self::Target { + &self.state + } +} + +impl VmiOsContext<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Returns the VMI context. + pub fn core(&self) -> &VmiCore { + self.state.core() + } + + /// Returns the underlying OS-specific implementation. + pub fn underlying_os(&self) -> &Os { + self.state.underlying_os() + } + + /// Returns the current VMI event. + pub fn event(&self) -> &VmiEvent { + self.event + } +} diff --git a/crates/vmi-core/src/ctx/mod.rs b/crates/vmi-core/src/ctx/mod.rs new file mode 100644 index 0000000..7de54be --- /dev/null +++ b/crates/vmi-core/src/ctx/mod.rs @@ -0,0 +1,11 @@ +mod context; +mod prober; +mod session; +mod state; + +pub use self::{ + context::{VmiContext, VmiOsContext}, + prober::VmiProber, + session::VmiSession, + state::{VmiOsState, VmiState}, +}; diff --git a/crates/vmi-core/src/ctx/prober.rs b/crates/vmi-core/src/ctx/prober.rs new file mode 100644 index 0000000..82938db --- /dev/null +++ b/crates/vmi-core/src/ctx/prober.rs @@ -0,0 +1,164 @@ +use std::cell::RefCell; + +use indexmap::IndexSet; + +use crate::{AddressContext, PageFaults, VmiError}; + +/// Prober for safely handling page faults during memory access operations. +pub struct VmiProber { + /// The set of restricted page faults that are allowed to occur. + restricted: IndexSet, + + /// The set of page faults that have occurred. + page_faults: RefCell>, +} + +impl VmiProber { + /// Creates a new prober. + pub fn new(restricted: &IndexSet) -> Self { + Self { + restricted: restricted.clone(), + page_faults: RefCell::new(IndexSet::new()), + } + } + + /// Handles a result that may contain page faults, returning the value + /// if successful. + pub fn check_result(&self, result: Result) -> Result, VmiError> { + match result { + Ok(value) => Ok(Some(value)), + Err(VmiError::Translation(pfs)) => { + self.check_restricted(pfs); + Ok(None) + } + Err(err) => Err(err), + } + } + + /* + /// Handles a result that may contain page faults over a memory range, + /// returning the value if successful. + fn check_result_range( + &self, + result: Result, + ctx: AccessContext, + length: usize, + ) -> Result, VmiError> { + match result { + Ok(value) => Ok(Some(value)), + Err(VmiError::PageFault(pfs)) => { + debug_assert_eq!(pfs.len(), 1); + self.check_restricted_range(pfs[0], ctx, length); + Ok(None) + } + Err(err) => Err(err), + } + } + */ + + /// Records any page faults that are not in the restricted set. + fn check_restricted(&self, pfs: PageFaults) { + let mut page_faults = self.page_faults.borrow_mut(); + for pf in pfs { + if !self.restricted.contains(&pf) { + tracing::trace!(va = %pf.va, "page fault"); + page_faults.insert(pf); + } + else { + tracing::trace!(va = %pf.va, "page fault (restricted)"); + } + } + } + + /* + /// Records any page faults that are not in the restricted set over + /// a memory range. + fn check_restricted_range(&self, pf: PageFault, ctx: AccessContext, mut length: usize) { + let mut page_faults = self.page_faults.borrow_mut(); + + if length == 0 { + length = 1; + } + + // + // Generate page faults for the range of addresses that would be accessed by the read. + // Start at the page containing the faulting address and end at the page containing the + // last byte of the read. + // + + let pf_page = pf.address.0 >> Driver::Architecture::PAGE_SHIFT; + let last_page = (ctx.address + length as u64 - 1) >> Driver::Architecture::PAGE_SHIFT; + let number_of_pages = last_page.saturating_sub(pf_page) + 1; + + let pf_address_aligned = Va(pf_page << Driver::Architecture::PAGE_SHIFT); + let last_address_aligned = Va(last_page << Driver::Architecture::PAGE_SHIFT); + + if number_of_pages > 1 { + tracing::debug!( + from = %pf_address_aligned, + to = %last_address_aligned, + number_of_pages, + "page fault (range)" + ); + + if number_of_pages >= 4096 { + tracing::warn!( + from = %pf_address_aligned, + to = %last_address_aligned, + number_of_pages, + "page fault range too large" + ); + } + } + + for i in 0..number_of_pages { + // + // Ensure that the page fault is for the root that we are tracking. + // + + debug_assert_eq!( + pf.root, + match ctx.mechanism { + TranslationMechanism::Paging { root: Some(root) } => root, + _ => panic!("page fault root doesn't match the context root"), + } + ); + + let pf = PageFault { + address: pf_address_aligned + i * Driver::Architecture::PAGE_SIZE, + root: pf.root, + }; + + if !self.restricted.contains(&pf) { + tracing::trace!(va = %pf.address, "page fault"); + page_faults.insert(pf); + } + else { + tracing::trace!(va = %pf.address, "page fault (restricted)"); + } + } + } + */ + + /// Checks for any unexpected page faults that have occurred and returns + /// an error if any are present. + #[tracing::instrument(skip_all)] + pub fn error_for_page_faults(&self) -> Result<(), VmiError> { + let pfs = self.page_faults.borrow(); + let new_pfs = &*pfs - &self.restricted; + if !new_pfs.is_empty() { + tracing::trace!(?new_pfs); + return Err(VmiError::page_faults(new_pfs)); + } + + Ok(()) + } +} + +/// Probes for safely handling page faults during memory access operations. +#[macro_export] +macro_rules! vmi_probe { + ($prober:expr, $expr:expr) => { + $prober.check_result(|| -> Result<_, VmiError> { $expr }()) + }; +} diff --git a/crates/vmi-core/src/ctx/session.rs b/crates/vmi-core/src/ctx/session.rs new file mode 100644 index 0000000..243181d --- /dev/null +++ b/crates/vmi-core/src/ctx/session.rs @@ -0,0 +1,171 @@ +use std::{io::ErrorKind, time::Duration}; + +use super::{context::VmiContext, VmiState}; +use crate::{ + os::{NoOS, VmiOs}, + Architecture, VmiCore, VmiDriver, VmiError, VmiHandler, +}; + +/// A VMI session. +/// +/// The session combines a [`VmiCore`] with an OS-specific [`VmiOs`] +/// implementation to provide unified access to both low-level VMI operations +/// and higher-level OS abstractions. +pub struct VmiSession<'a, Driver, Os = NoOS> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// The VMI core providing low-level VM introspection capabilities. + core: &'a VmiCore, + + /// The OS-specific operations and abstractions. + os: &'a Os, +} + +impl Clone for VmiSession<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Copy for VmiSession<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ +} + +impl std::ops::Deref for VmiSession<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + type Target = VmiCore; + + fn deref(&self) -> &Self::Target { + self.core + } +} + +impl<'a, Driver, Os> VmiSession<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Creates a new VMI session. + pub fn new(core: &'a VmiCore, os: &'a Os) -> Self { + Self { core, os } + } + + /// Creates a new VMI state with the specified registers. + pub fn with_registers( + &'a self, + registers: &'a ::Registers, + ) -> VmiState<'a, Driver, Os> { + VmiState::new(self, registers) + } + + /// Creates a new VMI session without an OS-specific implementation. + pub fn without_os(&self) -> VmiSession<'a, Driver, NoOS> { + VmiSession { + core: self.core, + os: &NoOS, + } + } + + /// Returns the VMI core. + pub fn core(&self) -> &'a VmiCore { + self.core + } + + /// Returns the underlying OS-specific implementation. + pub fn underlying_os(&self) -> &'a Os { + self.os + } + + /// Waits for an event to occur and processes it with the provided handler. + /// + /// This method blocks until an event occurs or the specified timeout is + /// reached. When an event occurs, it is passed to the provided callback + /// function for processing. + pub fn wait_for_event( + &self, + timeout: Duration, + handler: &mut impl VmiHandler, + ) -> Result<(), VmiError> { + self.core.wait_for_event(timeout, |event| { + let state = VmiState::new(self, event.registers()); + handler.handle_event(VmiContext::new(&state, event)) + }) + } + + /// Enters the main event handling loop that processes VMI events until + /// finished. + pub fn handle( + &self, + handler_factory: impl FnOnce(&VmiSession) -> Result, + ) -> Result, VmiError> + where + Handler: VmiHandler, + { + self.handle_with_timeout(Duration::from_millis(5000), handler_factory) + } + + /// Enters the main event handling loop that processes VMI events until + /// finished, with a timeout for each event. + pub fn handle_with_timeout( + &self, + timeout: Duration, + handler_factory: impl FnOnce(&VmiSession) -> Result, + ) -> Result, VmiError> + where + Handler: VmiHandler, + { + let mut result; + let mut handler = handler_factory(self)?; + + loop { + result = handler.check_completion(); + + if result.is_some() { + break; + } + + match self.wait_for_event(timeout, &mut handler) { + Err(VmiError::Timeout) => { + tracing::trace!("timeout"); + handler.handle_timeout(self); + } + Err(VmiError::Io(err)) if err.kind() == ErrorKind::Interrupted => { + tracing::trace!("interrupted"); + handler.handle_interrupted(self); + break; + } + Err(err) => return Err(err), + Ok(_) => {} + } + } + + tracing::trace!("disabling monitor"); + self.core.reset_state()?; + tracing::trace!(pending_events = self.events_pending()); + + let _pause_guard = self.pause_guard()?; + if self.events_pending() > 0 { + match self.wait_for_event(Duration::from_millis(0), &mut handler) { + Err(VmiError::Timeout) => { + tracing::trace!("timeout"); + } + Err(err) => return Err(err), + Ok(_) => {} + } + } + + Ok(result) + } +} diff --git a/crates/vmi-core/src/ctx/state.rs b/crates/vmi-core/src/ctx/state.rs new file mode 100644 index 0000000..ec71ecd --- /dev/null +++ b/crates/vmi-core/src/ctx/state.rs @@ -0,0 +1,558 @@ +use isr_macros::Field; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use super::session::VmiSession; +use crate::{ + os::{NoOS, VmiOs}, + AccessContext, AddressContext, Architecture, Pa, Registers as _, Va, VmiCore, VmiDriver, + VmiError, +}; + +/// A VMI state. +/// +/// The state combines access to a [`VmiSession`] with [`Architecture::Registers`] +/// to provide unified access to VMI operations in the context of a specific +/// virtual machine state. +pub struct VmiState<'a, Driver, Os = NoOS> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// The VMI session. + session: VmiSession<'a, Driver, Os>, + + /// The CPU registers associated with the current VM state. + registers: &'a ::Registers, +} + +impl Clone for VmiState<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + fn clone(&self) -> Self { + *self + } +} + +impl Copy for VmiState<'_, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ +} + +impl<'a, Driver, Os> std::ops::Deref for VmiState<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + type Target = VmiSession<'a, Driver, Os>; + + fn deref(&self) -> &Self::Target { + &self.session + } +} + +impl<'a, Driver, Os> VmiState<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Creates a new VMI state. + pub fn new( + session: &'a VmiSession<'a, Driver, Os>, + registers: &'a ::Registers, + ) -> Self { + Self { + session: *session, + registers, + } + } + + /// Creates a new VMI state with the specified registers. + pub fn with_registers( + &'a self, + registers: &'a ::Registers, + ) -> Self { + Self { + session: self.session, + registers, + } + } + + /// Creates a new VMI state without an OS-specific implementation. + pub fn without_os(&self) -> VmiState<'a, Driver, NoOS> { + VmiState { + session: self.session.without_os(), + registers: self.registers, + } + } + + // Note that `core()` and `underlying_os()` are delegated to the `VmiSession`. + + /// Returns the VMI session. + pub fn session(&self) -> &VmiSession<'a, Driver, Os> { + &self.session + } + + /// Returns the CPU registers associated with the current event. + pub fn registers(&self) -> &'a ::Registers { + self.registers + } + + /// Returns a wrapper providing access to OS-specific operations. + pub fn os(&self) -> VmiOsState<'a, Driver, Os> { + VmiOsState(*self) + } + + /// Creates an address context for a given virtual address. + pub fn access_context(&self, address: Va) -> AccessContext { + self.registers().access_context(address) + } + + /// Creates an address context for a given virtual address. + pub fn address_context(&self, address: Va) -> AddressContext { + self.registers().address_context(address) + } + + /// Returns the physical address of the root of the current page table + /// hierarchy for a given virtual address. + pub fn translation_root(&self, va: Va) -> Pa { + self.registers().translation_root(va) + } + + /// Returns the return address from the current stack frame. + pub fn return_address(&self) -> Result { + self.registers().return_address(self.core()) + } + + /// Translates a virtual address to a physical address. + pub fn translate_address(&self, va: Va) -> Result { + self.core().translate_address(self.address_context(va)) + } + + // region: Read + + /// Reads memory from the virtual machine. + pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError> { + self.read_in(self.access_context(address), buffer) + } + + /// Writes memory to the virtual machine. + pub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError> { + self.write_in(self.access_context(address), buffer) + } + + /// Reads a single byte from the virtual machine. + pub fn read_u8(&self, address: Va) -> Result { + self.read_u8_in(self.access_context(address)) + } + + /// Reads a 16-bit unsigned integer from the virtual machine. + pub fn read_u16(&self, address: Va) -> Result { + self.read_u16_in(self.access_context(address)) + } + + /// Reads a 32-bit unsigned integer from the virtual machine. + pub fn read_u32(&self, address: Va) -> Result { + self.read_u32_in(self.access_context(address)) + } + + /// Reads a 64-bit unsigned integer from the virtual machine. + pub fn read_u64(&self, address: Va) -> Result { + self.read_u64_in(self.access_context(address)) + } + + /// Reads an unsigned integer of the specified size from the virtual machine. + /// + /// This method reads an unsigned integer of the specified size (in bytes) + /// from the virtual machine. Note that the size must be 1, 2, 4, or 8. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_uint(&self, address: Va, size: usize) -> Result { + self.read_uint_in(self.access_context(address), size) + } + + /// Reads a field of a structure from the virtual machine. + /// + /// This method reads a field from the virtual machine. The field is + /// defined by the provided [`Field`] structure, which specifies the + /// offset and size of the field within the memory region. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_field(&self, base_address: Va, field: &Field) -> Result { + self.read_field_in(self.access_context(base_address), field) + } + + /// Reads an address-sized unsigned integer from the virtual machine. + pub fn read_address(&self, address: Va) -> Result { + self.read_address_in(self.access_context(address)) + } + + /// Reads an address-sized unsigned integer from the virtual machine. + pub fn read_address_native(&self, address: Va) -> Result { + self.read_address_native_in(self.access_context(address)) + } + + /// Reads a 32-bit address from the virtual machine. + pub fn read_address32(&self, address: Va) -> Result { + self.read_address32_in(self.access_context(address)) + } + + /// Reads a 64-bit address from the virtual machine. + pub fn read_address64(&self, address: Va) -> Result { + self.read_address64_in(self.access_context(address)) + } + + /// Reads a virtual address from the virtual machine. + pub fn read_va(&self, address: Va) -> Result { + self.read_va_in(self.access_context(address)) + } + + /// Reads a virtual address from the virtual machine. + pub fn read_va_native(&self, address: Va) -> Result { + self.read_va_native_in(self.access_context(address)) + } + + /// Reads a 32-bit virtual address from the virtual machine. + pub fn read_va32(&self, address: Va) -> Result { + self.read_va32_in(self.access_context(address)) + } + + /// Reads a 64-bit virtual address from the virtual machine. + pub fn read_va64(&self, address: Va) -> Result { + self.read_va64_in(self.access_context(address)) + } + + /// Reads a null-terminated string of bytes from the virtual machine with a + /// specified limit. + pub fn read_string_bytes_limited( + &self, + address: Va, + limit: usize, + ) -> Result, VmiError> { + self.read_string_bytes_limited_in(self.access_context(address), limit) + } + + /// Reads a null-terminated string of bytes from the virtual machine. + pub fn read_string_bytes(&self, address: Va) -> Result, VmiError> { + self.read_string_bytes_in(self.access_context(address)) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine + /// with a specified limit. + pub fn read_wstring_bytes_limited( + &self, + address: Va, + limit: usize, + ) -> Result, VmiError> { + self.read_wstring_bytes_limited_in(self.access_context(address), limit) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine. + pub fn read_wstring_bytes(&self, address: Va) -> Result, VmiError> { + self.read_wstring_bytes_in(self.access_context(address)) + } + + /// Reads a null-terminated string from the virtual machine with a specified + /// limit. + pub fn read_string_limited(&self, address: Va, limit: usize) -> Result { + self.read_string_limited_in(self.access_context(address), limit) + } + + /// Reads a null-terminated string from the virtual machine. + pub fn read_string(&self, address: Va) -> Result { + self.read_string_in(self.access_context(address)) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine + /// with a specified limit. + pub fn read_wstring_limited(&self, address: Va, limit: usize) -> Result { + self.read_wstring_limited_in(self.access_context(address), limit) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine. + pub fn read_wstring(&self, address: Va) -> Result { + self.read_wstring_in(self.access_context(address)) + } + + /// Reads a struct from the virtual machine. + pub fn read_struct(&self, address: Va) -> Result + where + T: IntoBytes + FromBytes, + { + self.read_struct_in(self.access_context(address)) + } + + // endregion: Read + + // region: Read in + + /// Reads memory from the virtual machine. + pub fn read_in( + &self, + ctx: impl Into, + buffer: &mut [u8], + ) -> Result<(), VmiError> { + self.core().read(ctx, buffer) + } + + /// Writes memory to the virtual machine. + pub fn write_in(&self, ctx: impl Into, buffer: &[u8]) -> Result<(), VmiError> { + self.core().write(ctx, buffer) + } + + /// Reads a single byte from the virtual machine. + pub fn read_u8_in(&self, ctx: impl Into) -> Result { + self.core().read_u8(ctx) + } + + /// Reads a 16-bit unsigned integer from the virtual machine. + pub fn read_u16_in(&self, ctx: impl Into) -> Result { + self.core().read_u16(ctx) + } + + /// Reads a 32-bit unsigned integer from the virtual machine. + pub fn read_u32_in(&self, ctx: impl Into) -> Result { + self.core().read_u32(ctx) + } + + /// Reads a 64-bit unsigned integer from the virtual machine. + pub fn read_u64_in(&self, ctx: impl Into) -> Result { + self.core().read_u64(ctx) + } + + /// Reads an unsigned integer of the specified size from the virtual machine. + /// + /// This method reads an unsigned integer of the specified size (in bytes) + /// from the virtual machine. Note that the size must be 1, 2, 4, or 8. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_uint_in( + &self, + ctx: impl Into, + size: usize, + ) -> Result { + self.core().read_uint(ctx, size) + } + + /// Reads a field of a structure from the virtual machine. + /// + /// This method reads a field from the virtual machine. The field is + /// defined by the provided [`Field`] structure, which specifies the + /// offset and size of the field within the memory region. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_field_in( + &self, + ctx: impl Into, + field: &Field, + ) -> Result { + self.core().read_field(ctx, field) + } + + /// Reads an address-sized unsigned integer from the virtual machine. + pub fn read_address_in(&self, ctx: impl Into) -> Result { + self.core() + .read_address(ctx, self.registers().effective_address_width()) + } + + /// Reads an address-sized unsigned integer from the virtual machine. + pub fn read_address_native_in(&self, ctx: impl Into) -> Result { + self.core() + .read_address(ctx, self.registers().address_width()) + } + + /// Reads a 32-bit address from the virtual machine. + pub fn read_address32_in(&self, ctx: impl Into) -> Result { + self.core().read_address32(ctx) + } + + /// Reads a 64-bit address from the virtual machine. + pub fn read_address64_in(&self, ctx: impl Into) -> Result { + self.core().read_address64(ctx) + } + + /// Reads a virtual address from the virtual machine. + pub fn read_va_in(&self, ctx: impl Into) -> Result { + self.core() + .read_va(ctx, self.registers().effective_address_width()) + } + + /// Reads a virtual address from the virtual machine. + pub fn read_va_native_in(&self, ctx: impl Into) -> Result { + self.core().read_va(ctx, self.registers().address_width()) + } + + /// Reads a 32-bit virtual address from the virtual machine. + pub fn read_va32_in(&self, ctx: impl Into) -> Result { + self.core().read_va32(ctx) + } + + /// Reads a 64-bit virtual address from the virtual machine. + pub fn read_va64_in(&self, ctx: impl Into) -> Result { + self.core().read_va64(ctx) + } + + /// Reads a null-terminated string of bytes from the virtual machine with a + /// specified limit. + pub fn read_string_bytes_limited_in( + &self, + ctx: impl Into, + limit: usize, + ) -> Result, VmiError> { + self.core().read_string_bytes_limited(ctx, limit) + } + + /// Reads a null-terminated string of bytes from the virtual machine. + pub fn read_string_bytes_in(&self, ctx: impl Into) -> Result, VmiError> { + self.core().read_string_bytes(ctx) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine + /// with a specified limit. + pub fn read_wstring_bytes_limited_in( + &self, + ctx: impl Into, + limit: usize, + ) -> Result, VmiError> { + self.core().read_wstring_bytes_limited(ctx, limit) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine. + pub fn read_wstring_bytes_in( + &self, + ctx: impl Into, + ) -> Result, VmiError> { + self.core().read_wstring_bytes(ctx) + } + + /// Reads a null-terminated string from the virtual machine with a specified + /// limit. + pub fn read_string_limited_in( + &self, + ctx: impl Into, + limit: usize, + ) -> Result { + self.core().read_string_limited(ctx, limit) + } + + /// Reads a null-terminated string from the virtual machine. + pub fn read_string_in(&self, ctx: impl Into) -> Result { + self.core().read_string(ctx) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine + /// with a specified limit. + pub fn read_wstring_limited_in( + &self, + ctx: impl Into, + limit: usize, + ) -> Result { + self.core().read_wstring_limited(ctx, limit) + } + + /// Reads a null-terminated wide string (UTF-16) from the virtual machine. + pub fn read_wstring_in(&self, ctx: impl Into) -> Result { + self.core().read_wstring(ctx) + } + + /// Reads a struct from the virtual machine. + pub fn read_struct_in(&self, ctx: impl Into) -> Result + where + T: IntoBytes + FromBytes, + { + self.core().read_struct(ctx) + } + + // endregion: Read in + + /// Writes a single byte to the virtual machine. + pub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError> { + self.core().write_u8(self.access_context(address), value) + } + + /// Writes a 16-bit unsigned integer to the virtual machine. + pub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError> { + self.core().write_u16(self.access_context(address), value) + } + + /// Writes a 32-bit unsigned integer to the virtual machine. + pub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError> { + self.core().write_u32(self.access_context(address), value) + } + + /// Writes a 64-bit unsigned integer to the virtual machine. + pub fn write_u64(&self, address: Va, value: u64) -> Result<(), VmiError> { + self.core().write_u64(self.access_context(address), value) + } + + /// Writes a struct to the virtual machine. + pub fn write_struct(&self, address: Va, value: T) -> Result<(), VmiError> + where + T: FromBytes + IntoBytes + Immutable, + { + self.core() + .write_struct(self.access_context(address), value) + } +} + +/// Wrapper providing access to OS-specific operations. +pub struct VmiOsState<'a, Driver, Os>(VmiState<'a, Driver, Os>) +where + Driver: VmiDriver, + Os: VmiOs; + +impl<'a, Driver, Os> VmiOsState<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Returns the VMI core. + pub fn core(&self) -> &'a VmiCore { + self.0.core() + } + + /// Returns the underlying OS-specific implementation. + pub fn underlying_os(&self) -> &'a Os { + self.0.underlying_os() + } + + /// Returns the VMI session. + pub fn session(&self) -> &VmiSession<'a, Driver, Os> { + self.0.session() + } + + /// Returns the VMI state. + pub fn state(&self) -> VmiState<'a, Driver, Os> { + self.0 + } + + /// Returns the CPU registers associated with the current event. + pub fn registers(&self) -> &::Registers { + self.0.registers() + } + + /// Retrieves a specific function argument according to the calling + /// convention of the operating system. + pub fn function_argument_for_registers( + &self, + registers: &::Registers, + index: u64, + ) -> Result { + Os::function_argument(self.0.with_registers(registers), index) + } + + /// Retrieves the return value of a function. + pub fn function_return_value_for_registers( + &self, + registers: &::Registers, + ) -> Result { + Os::function_return_value(self.0.with_registers(registers)) + } +} diff --git a/crates/vmi-core/src/driver.rs b/crates/vmi-core/src/driver.rs index b5b0059..99d4442 100644 --- a/crates/vmi-core/src/driver.rs +++ b/crates/vmi-core/src/driver.rs @@ -6,11 +6,14 @@ use crate::{ }; /// A trait for implementing a VMI driver. -pub trait VmiDriver { +/// +// The 'static lifetime is required in order to use the driver with the VmiOs +// enumerators. +pub trait VmiDriver: 'static { /// The architecture supported by the driver. type Architecture: Architecture + ?Sized; - /// Retrieves information about the virtual machine. + /// Returns information about the virtual machine. fn info(&self) -> Result; /// Pauses the virtual machine. @@ -19,7 +22,7 @@ pub trait VmiDriver { /// Resumes the virtual machine. fn resume(&self) -> Result<(), VmiError>; - /// Retrieves the registers of a specific virtual CPU. + /// Returns the registers of a specific virtual CPU. fn registers( &self, vcpu: VcpuId, @@ -32,7 +35,7 @@ pub trait VmiDriver { registers: ::Registers, ) -> Result<(), VmiError>; - /// Retrieves the memory access permissions for a specific GFN. + /// Returns the memory access permissions for a specific GFN. fn memory_access(&self, gfn: Gfn, view: View) -> Result; /// Sets the memory access permissions for a specific GFN. diff --git a/crates/vmi-core/src/error.rs b/crates/vmi-core/src/error.rs index e6a2f88..368c8b0 100644 --- a/crates/vmi-core/src/error.rs +++ b/crates/vmi-core/src/error.rs @@ -1,4 +1,4 @@ -use crate::{Pa, Va}; +use crate::AddressContext; /// An error that can occur when working with the VMI. #[derive(thiserror::Error, Debug)] @@ -19,9 +19,9 @@ pub enum VmiError { #[error(transparent)] Isr(#[from] isr_macros::Error), - /// A page fault occurred. - #[error("Page not present ({:?}, len: {})", .0[0], .0.len())] - PageFault(PageFaults), + /// A translation error occurred. + #[error("Translation error ({:?}, len: {})", .0[0], .0.len())] + Translation(PageFaults), /// The given address has invalid width. #[error("Invalid address width")] @@ -56,33 +56,17 @@ pub enum VmiError { Other(&'static str), } -/// A page fault. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PageFault { - /// The virtual address that caused the page fault. - pub address: Va, - - /// The root of the page table hierarchy. - pub root: Pa, -} - /// A collection of page faults. -pub type PageFaults = smallvec::SmallVec<[PageFault; 1]>; - -impl From<(Va, Pa)> for PageFault { - fn from((address, root): (Va, Pa)) -> Self { - Self { address, root } - } -} +pub type PageFaults = smallvec::SmallVec<[AddressContext; 1]>; impl VmiError { /// Creates a new page fault error. - pub fn page_fault(pf: impl Into) -> Self { - Self::PageFault(smallvec::smallvec![pf.into()]) + pub fn page_fault(pf: impl Into) -> Self { + Self::Translation(smallvec::smallvec![pf.into()]) } /// Creates a new page fault error with multiple page faults. - pub fn page_faults(pfs: impl IntoIterator) -> Self { - Self::PageFault(pfs.into_iter().collect()) + pub fn page_faults(pfs: impl IntoIterator) -> Self { + Self::Translation(pfs.into_iter().collect()) } } diff --git a/crates/vmi-core/src/lib.rs b/crates/vmi-core/src/lib.rs index 0383a3a..1d2070c 100644 --- a/crates/vmi-core/src/lib.rs +++ b/crates/vmi-core/src/lib.rs @@ -1,35 +1,34 @@ //! Core VMI functionality. pub mod arch; -mod context; mod core; +mod ctx; mod driver; mod error; mod event; mod handler; pub mod os; mod page; -mod session; use std::{cell::RefCell, num::NonZeroUsize, time::Duration}; +use isr_macros::Field; use lru::LruCache; use zerocopy::{FromBytes, Immutable, IntoBytes}; pub use self::{ arch::{Architecture, Registers}, - context::{VmiContext, VmiContextProber, VmiOsContext, VmiOsContextProber}, core::{ AccessContext, AddressContext, Gfn, Hex, MemoryAccess, MemoryAccessOptions, Pa, - TranslationMechanism, Va, VcpuId, View, VmiInfo, + TranslationMechanism, Va, VcpuId, View, VmiInfo, VmiVa, }, + ctx::{VmiContext, VmiOsContext, VmiOsState, VmiProber, VmiSession, VmiState}, driver::VmiDriver, - error::{PageFault, PageFaults, VmiError}, + error::{PageFaults, VmiError}, event::{VmiEvent, VmiEventFlags, VmiEventResponse, VmiEventResponseFlags}, handler::VmiHandler, os::VmiOs, page::VmiMappedPage, - session::{VmiOsSession, VmiOsSessionProber, VmiSession, VmiSessionProber}, }; struct Cache { @@ -70,7 +69,7 @@ impl VmiCore where Driver: VmiDriver, { - /// Creates a new VmiCore instance with the given driver. + /// Creates a new `VmiCore` instance with the given driver. /// /// Both the GFN cache and the V2P cache are enabled by default, /// each with a capacity of 8192 entries. @@ -499,6 +498,17 @@ where } /// Enables monitoring of specific events. + /// + /// This method allows you to enable monitoring of specific events, such as + /// control register writes, interrupts, or single-step execution. + /// Monitoring events can be useful for tracking specific guest behavior or + /// for implementing custom analysis tools. + /// + /// The type of event to monitor is defined by the architecture-specific + /// [`Architecture::EventMonitor`] type. + /// + /// When an event occurs, it will be passed to the event callback function + /// for processing. pub fn monitor_enable( &self, option: ::EventMonitor, @@ -507,6 +517,13 @@ where } /// Disables monitoring of specific events. + /// + /// This method allows you to disable monitoring of specific events that + /// were previously enabled. It can be used to stop tracking certain + /// hardware events or to reduce the overhead of event processing. + /// + /// The type of event to disable is defined by the architecture-specific + /// [`Architecture::EventMonitor`] type. pub fn monitor_disable( &self, option: ::EventMonitor, @@ -514,81 +531,6 @@ where self.driver.monitor_disable(option) } - /* - /// Enables or disables monitoring of specific CPU registers. - /// - /// This method allows for setting up event triggers when certain CPU - /// registers are accessed or modified. The specific registers that can - /// be monitored depend on the architecture and are defined by the - /// [`Architecture::MonitorRegisterOptions`] type. - /// - /// When enabled, relevant events will be passed to the event callback - /// function. - pub fn monitor_register( - &self, - enable: bool, - options: ::MonitorRegisterOptions, - ) -> Result<(), VmiError> { - self.driver.monitor_register(enable, options) - } - - /// Enables or disables monitoring of specific interrupts. - /// - /// This method sets up event triggers for specified interrupt events. The - /// types of interrupts that can be monitored are defined by the - /// [`Architecture::MonitorInterruptOptions`] type, which is specific to - /// the architecture being used. - /// - /// When an interrupt event occurs, it will be passed to the event callback - /// function. - pub fn monitor_interrupt( - &self, - enable: bool, - options: ::MonitorInterruptOptions, - ) -> Result<(), VmiError> { - self.driver.monitor_interrupt(enable, options) - } - - /// Enables or disables single-step monitoring. - /// - /// When enabled, this method causes the VMI system to generate an event - /// after each instruction execution in the guest. This can be useful - /// for detailed analysis of guest behavior, but may have a significant - /// performance impact. - /// - /// Single-step events will be passed to the event callback function when - /// they occur. - pub fn monitor_singlestep(&self, enable: bool) -> Result<(), VmiError> { - self.driver.monitor_singlestep(enable) - } - - /// Enables or disables monitoring of CPUID instruction execution. - /// - /// When enabled, this method generates an event each time a CPUID - /// instruction is executed in the guest. This can be useful for - /// analyzing how the guest queries CPU features or for implementing CPU - /// feature spoofing. - /// - /// CPUID events will be passed to the event callback function when they - /// occur. - pub fn monitor_cpuid(&self, enable: bool) -> Result<(), VmiError> { - self.driver.monitor_cpuid(enable) - } - - /// Enables or disables monitoring of I/O port operations. - /// - /// When enabled, this method generates events for I/O port read and write - /// operations performed by the guest. This can be useful for analyzing - /// guest interactions with virtual hardware or for implementing custom - /// virtual device behavior. - /// - /// I/O port events will be passed to the event callback function when they - /// occur. - pub fn monitor_io(&self, enable: bool) -> Result<(), VmiError> { - self.driver.monitor_io(enable) - } - */ - /// Injects an interrupt into a specific virtual CPU. /// /// This method allows for the injection of architecture-specific interrupts @@ -724,6 +666,39 @@ where Ok(u64::from_le_bytes(buffer)) } + /// Reads an unsigned integer of the specified size from the virtual machine. + /// + /// This method reads an unsigned integer of the specified size (in bytes) + /// from the virtual machine. Note that the size must be 1, 2, 4, or 8. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_uint(&self, ctx: impl Into, size: usize) -> Result { + match size { + 1 => self.read_u8(ctx).map(u64::from), + 2 => self.read_u16(ctx).map(u64::from), + 4 => self.read_u32(ctx).map(u64::from), + 8 => self.read_u64(ctx), + _ => Err(VmiError::InvalidAddressWidth), + } + } + + /// Reads a field of a structure from the virtual machine. + /// + /// This method reads a field from the virtual machine. The field is + /// defined by the provided [`Field`] structure, which specifies the + /// offset and size of the field within the memory region. + /// + /// The result is returned as a [`u64`] to accommodate the widest possible + /// integer size. + pub fn read_field( + &self, + ctx: impl Into, + field: &Field, + ) -> Result { + self.read_uint(ctx.into() + field.offset(), field.size() as usize) + } + /// Reads an address-sized unsigned integer from the virtual machine. /// /// This method reads an address of the specified width (in bytes) from diff --git a/crates/vmi-core/src/os/common.rs b/crates/vmi-core/src/os/common.rs deleted file mode 100644 index 1544b40..0000000 --- a/crates/vmi-core/src/os/common.rs +++ /dev/null @@ -1,284 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{MemoryAccess, Pa, Va, VmiError}; - -/// A process object within a system. -/// -/// Equivalent to `EPROCESS*` on Windows or `task_struct*` on Linux. -#[derive( - Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct ProcessObject(pub Va); - -impl ProcessObject { - /// Checks if the process object is a null reference. - pub fn is_null(&self) -> bool { - self.0 .0 == 0 - } - - /// Converts the process object to a 64-bit unsigned integer. - pub fn to_u64(&self) -> u64 { - self.0 .0 - } -} - -impl From for ProcessObject { - fn from(va: Va) -> Self { - Self(va) - } -} - -impl From for Va { - fn from(value: ProcessObject) -> Self { - value.0 - } -} - -impl std::fmt::Display for ProcessObject { - fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A thread object within a system. -/// -/// Equivalent to `ETHREAD*` on Windows. -#[derive( - Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct ThreadObject(pub Va); - -impl ThreadObject { - /// Checks if the thread object is a null reference. - pub fn is_null(&self) -> bool { - self.0 .0 == 0 - } - - /// Converts the thread object to a 64-bit unsigned integer. - pub fn to_u64(&self) -> u64 { - self.0 .0 - } -} - -impl From for ThreadObject { - fn from(va: Va) -> Self { - Self(va) - } -} - -impl From for Va { - fn from(value: ThreadObject) -> Self { - value.0 - } -} - -impl std::fmt::Display for ThreadObject { - fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A process ID within a system. -#[derive( - Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct ProcessId(pub u32); - -impl From for ProcessId { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: ProcessId) -> Self { - value.0 - } -} - -impl std::fmt::Display for ProcessId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A thread ID within a system. -#[derive( - Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -pub struct ThreadId(pub u32); - -impl From for ThreadId { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: ThreadId) -> Self { - value.0 - } -} - -impl std::fmt::Display for ThreadId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// The architecture of the operating system. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum OsArchitecture { - /// The architecture is unknown. - Unknown, - - /// The x86 architecture. - X86, - - /// The x86-64 architecture. - Amd64, -} - -/// Represents information about a kernel module in the target system. -#[derive(Debug, Serialize, Deserialize)] -pub struct OsModule { - /// The base address of the module. - /// - /// # Platform-specific - /// - /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.DllBase` - /// - **Linux**: - /// - since v6.4-rc1: `module::mem[0 /* MOD_TEXT */].base` - /// - before v6.4-rc1: `module::core_layout.base` - pub base_address: Va, - - /// The size of the module. - /// - /// # Platform-specific - /// - /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.SizeOfImage` - /// - **Linux**: - /// - since v6.4-rc1: sum of `module::mem[MOD_*].size` - /// - before v6.4-rc1: `module::init_layout.size + module::core_layout.size (+ module::data_layout.size)` - pub size: u64, - - /// The short name of the module. - /// - /// # Platform-specific - /// - /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.BaseDllName` - /// - **Linux**: `module::name` - pub name: String, -} - -/// Represents information about a process in the target system. -#[derive(Debug, Serialize, Deserialize)] -pub struct OsProcess { - /// The PID of the process. - pub id: ProcessId, - - /// The process object. - pub object: ProcessObject, - - /// The short name of the process. - /// - /// # Platform-specific - /// - /// - **Windows**: `_EPROCESS::ImageFileName` (limited to 16 characters). - /// - **Linux**: `_task_struct::comm` (limited to 16 characters). - pub name: String, - - /// The translation root of the process. - pub translation_root: Pa, -} - -/// A region of memory within a process. -#[derive(Debug, Serialize, Deserialize)] -pub struct OsRegion { - /// The start address of the region. - pub start: Va, - - /// The end address of the region. - pub end: Va, - - /// The protection flags of the region. - pub protection: MemoryAccess, - - /// The kind of memory region. - pub kind: OsRegionKind, -} - -/// Specifies the kind of memory region. -#[derive(Debug, Serialize, Deserialize)] -pub enum OsRegionKind { - /// A private region of memory. - /// - /// Such regions are usually created by functions like `VirtualAlloc` on - /// Windows. - Private, - - /// A mapped region of memory. Might be backed by a file. - /// - /// Such regions are usually created by functions like `MapViewOfFile` on - /// Windows. - Mapped(OsMapped), -} - -/// Contains information about a mapped memory region. -#[derive(Debug, Serialize, Deserialize)] -pub struct OsMapped { - /// The path to the file backing the region. - /// - /// This field is represented as a [`Result, VmiError>`] to - /// handle cases where the path is not available (e.g., due to a page - /// fault). - #[serde(with = "serde_result_option")] - pub path: Result, VmiError>, -} - -/// An exported symbol from an image (e.g., DLL or .so file). -#[derive(Debug, Serialize, Deserialize)] -pub struct OsImageExportedSymbol { - /// The name of the symbol. - pub name: String, - - /// The virtual address of the symbol. - pub address: Va, -} - -/// Custom serialization module for [`Result, VmiError>`]. -/// -/// Provides custom serialization and deserialization logic for handling -/// the path field in [`OsMapped`], which may be unavailable due to paging -/// issues. -mod serde_result_option { - use crate::VmiError; - - pub fn serialize( - value: &Result, VmiError>, - serializer: S, - ) -> Result - where - S: serde::Serializer, - { - match value { - Ok(Some(value)) => serializer.serialize_some(value), - _ => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>( - deserializer: D, - ) -> Result, VmiError>, D::Error> - where - D: serde::Deserializer<'de>, - { - use serde::Deserialize as _; - - let value = Option::deserialize(deserializer)?; - match value { - Some(value) => Ok(Ok(Some(value))), - None => Ok(Err(VmiError::Other("PageFault"))), - } - } -} diff --git a/crates/vmi-core/src/os/dummy.rs b/crates/vmi-core/src/os/dummy.rs new file mode 100644 index 0000000..0dd7053 --- /dev/null +++ b/crates/vmi-core/src/os/dummy.rs @@ -0,0 +1,283 @@ +use super::{ + ProcessId, ProcessObject, ThreadId, ThreadObject, VmiOs, VmiOsImage, VmiOsImageArchitecture, + VmiOsImageSymbol, VmiOsMapped, VmiOsModule, VmiOsProcess, VmiOsRegion, VmiOsRegionKind, + VmiOsThread, +}; +use crate::{MemoryAccess, Pa, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +/// Marker type for a missing OS implementation. +pub struct NoOS; + +impl VmiVa for NoOS { + fn va(&self) -> Va { + unimplemented!() + } +} + +impl VmiOs for NoOS +where + Driver: VmiDriver, +{ + type Process<'a> = NoOS; + type Thread<'a> = NoOS; + type Image<'a> = NoOS; + type Module<'a> = NoOS; + type Region<'a> = NoOS; + type Mapped<'a> = NoOS; + + fn kernel_image_base(_vmi: VmiState) -> Result { + unimplemented!() + } + + fn kernel_information_string(_vmi: VmiState) -> Result { + unimplemented!() + } + + fn kpti_enabled(_vmi: VmiState) -> Result { + unimplemented!() + } + + fn modules( + _vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> + } + } + + fn processes( + _vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> + } + } + + fn process<'a>( + _vmi: VmiState<'_, Driver, Self>, + _process: ProcessObject, + ) -> Result, VmiError> { + unimplemented!() + } + + fn current_process<'a>( + _vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError> { + unimplemented!() + } + + fn system_process<'a>(_vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + unimplemented!() + } + + fn thread<'a>( + _vmi: VmiState<'_, Driver, Self>, + _thread: ThreadObject, + ) -> Result, VmiError> { + unimplemented!() + } + + fn current_thread(_vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + unimplemented!() + } + + fn image<'a>( + _vmi: VmiState<'_, Driver, Self>, + _image_base: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn module<'a>( + _vmi: VmiState<'_, Driver, Self>, + _module: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn region<'a>( + _vmi: VmiState<'_, Driver, Self>, + _region: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn syscall_argument(_vmi: VmiState, _index: u64) -> Result { + unimplemented!() + } + + fn function_argument(_vmi: VmiState, _index: u64) -> Result { + unimplemented!() + } + + fn function_return_value(_vmi: VmiState) -> Result { + unimplemented!() + } + + fn last_error(_vmi: VmiState) -> Result, VmiError> { + unimplemented!() + } +} + +impl VmiOsImage<'_, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn base_address(&self) -> Va { + unimplemented!() + } + + fn architecture(&self) -> Result, VmiError> { + unimplemented!() + } + + fn exports(&self) -> Result, VmiError> { + unimplemented!() + } +} + +impl VmiOsMapped<'_, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn path(&self) -> Result, VmiError> { + unimplemented!() + } +} + +impl VmiOsModule<'_, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn base_address(&self) -> Result { + unimplemented!() + } + + fn size(&self) -> Result { + unimplemented!() + } + + fn name(&self) -> Result { + unimplemented!() + } +} + +impl<'a, Driver> VmiOsProcess<'a, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn id(&self) -> Result { + unimplemented!() + } + + fn object(&self) -> Result { + unimplemented!() + } + + fn name(&self) -> Result { + unimplemented!() + } + + fn parent_id(&self) -> Result { + unimplemented!() + } + + fn architecture(&self) -> Result { + unimplemented!() + } + + fn translation_root(&self) -> Result { + unimplemented!() + } + + fn user_translation_root(&self) -> Result { + unimplemented!() + } + + fn image_base(&self) -> Result { + unimplemented!() + } + + fn regions( + &self, + ) -> Result< + impl Iterator>::Region<'_>, VmiError>>, + VmiError, + > { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> + } + } + + fn find_region( + &self, + _address: Va, + ) -> Result>::Region<'a>>, VmiError> { + unimplemented!() + } + + fn threads( + &self, + ) -> Result< + impl Iterator>::Thread<'a>, VmiError>>, + VmiError, + > { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> + } + } + + fn is_valid_address(&self, _address: Va) -> Result, VmiError> { + unimplemented!() + } +} + +impl<'a, Driver> VmiOsRegion<'a, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn start(&self) -> Result { + unimplemented!() + } + + fn end(&self) -> Result { + unimplemented!() + } + + fn protection(&self) -> Result { + unimplemented!() + } + + fn kind(&self) -> Result, VmiError> { + unimplemented!() + } +} + +impl VmiOsThread<'_, Driver> for NoOS +where + Driver: VmiDriver, +{ + type Os = NoOS; + + fn id(&self) -> Result { + unimplemented!() + } + + fn object(&self) -> Result { + unimplemented!() + } +} diff --git a/crates/vmi-core/src/os/image.rs b/crates/vmi-core/src/os/image.rs new file mode 100644 index 0000000..6f37f42 --- /dev/null +++ b/crates/vmi-core/src/os/image.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +use super::VmiOs; +use crate::{Va, VmiDriver, VmiError, VmiVa}; + +/// A trait for executable images. +/// +/// This trait provides an abstraction over executable images, +/// such as binaries and shared libraries, within a guest OS. +pub trait VmiOsImage<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the base address of the image. + fn base_address(&self) -> Va; + + /// Returns the target architecture for which the image was compiled. + fn architecture(&self) -> Result, VmiError>; + + /// Returns the exported symbols. + fn exports(&self) -> Result, VmiError>; +} + +/// The architecture of the operating system. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum VmiOsImageArchitecture { + /// The x86 architecture. + X86, + + /// The x86-64 architecture. + Amd64, +} + +/// An exported symbol from an image (e.g., DLL or .so file). +#[derive(Debug, Serialize, Deserialize)] +pub struct VmiOsImageSymbol { + /// The name of the symbol. + pub name: String, + + /// The virtual address of the symbol. + pub address: Va, +} diff --git a/crates/vmi-core/src/os/mapped.rs b/crates/vmi-core/src/os/mapped.rs new file mode 100644 index 0000000..6fdfe81 --- /dev/null +++ b/crates/vmi-core/src/os/mapped.rs @@ -0,0 +1,18 @@ +use super::VmiOs; +use crate::{VmiDriver, VmiError, VmiVa}; + +/// A trait for memory mapped regions. +/// +/// This trait provides an abstraction over memory mapped regions. +pub trait VmiOsMapped<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the path to the file backing the region. + /// + /// If the mapping is not backed by a file, this method will return `None`. + fn path(&self) -> Result, VmiError>; +} diff --git a/crates/vmi-core/src/os/mod.rs b/crates/vmi-core/src/os/mod.rs index 487337b..35baf02 100644 --- a/crates/vmi-core/src/os/mod.rs +++ b/crates/vmi-core/src/os/mod.rs @@ -1,33 +1,65 @@ #![doc = include_str!("../../docs/os.md")] -mod common; +mod dummy; +mod image; +mod mapped; +mod module; +mod process; +mod region; mod struct_reader; +mod thread; use vmi_macros::derive_os_wrapper; pub use self::{ - common::{ - OsArchitecture, OsImageExportedSymbol, OsMapped, OsModule, OsProcess, OsRegion, - OsRegionKind, ProcessId, ProcessObject, ThreadId, ThreadObject, - }, + dummy::NoOS, + image::{VmiOsImage, VmiOsImageArchitecture, VmiOsImageSymbol}, + mapped::VmiOsMapped, + module::VmiOsModule, + process::{ProcessId, ProcessObject, VmiOsProcess}, + region::{VmiOsRegion, VmiOsRegionKind}, struct_reader::StructReader, + thread::{ThreadId, ThreadObject, VmiOsThread}, }; -use crate::{ - Architecture, Pa, Va, VmiCore, VmiDriver, VmiError, VmiOsContext, VmiOsContextProber, - VmiOsSession, VmiOsSessionProber, -}; +use crate::{Va, VmiDriver, VmiError, VmiOsState, VmiState}; /// Operating system trait. -#[derive_os_wrapper( - os_session_name = VmiOsSession, - os_context_name = VmiOsContext, - os_session_prober_name = VmiOsSessionProber, - os_context_prober_name = VmiOsContextProber -)] -pub trait VmiOs +#[expect(clippy::needless_lifetimes)] +#[derive_os_wrapper(VmiOsState)] +pub trait VmiOs: Sized where Driver: VmiDriver, { + /// The process type. + type Process<'a>: VmiOsProcess<'a, Driver> + 'a + where + Self: 'a; + + /// The thread type. + type Thread<'a>: VmiOsThread<'a, Driver> + 'a + where + Self: 'a; + + /// The image type. + type Image<'a>: VmiOsImage<'a, Driver> + 'a + where + Self: 'a; + + /// The kernel module type. + type Module<'a>: VmiOsModule<'a, Driver> + 'a + where + Self: 'a; + + /// The memory region type. + type Region<'a>: VmiOsRegion<'a, Driver> + 'a + where + Self: 'a; + + /// The memory mapped region type. + type Mapped<'a>: VmiOsMapped<'a, Driver> + 'a + where + Self: 'a; + /// Retrieves the base address of the kernel image. /// /// The kernel image base address is usually found using some special @@ -50,11 +82,7 @@ where /// /// A malicious code (such as a rootkit) could modify values of the /// registers, so the returned value might not be accurate. - fn kernel_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn kernel_image_base(vmi: VmiState) -> Result; /// Retrieves an implementation-specific string containing kernel /// information. @@ -63,216 +91,92 @@ where /// /// - **Windows**: Retrieves the `NtBuildLab` string from the kernel image. /// - **Linux**: Retrieves the `linux_banner` string from the kernel image. - fn kernel_information_string( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn kernel_information_string(vmi: VmiState) -> Result; /// Checks if Kernel Page Table Isolation (KPTI) is enabled. - fn kpti_enabled( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + /// + /// # Platform-specific + /// + /// - **Windows**: Retrieves the `KiKvaShadow` global variable, if it exists. + fn kpti_enabled(vmi: VmiState) -> Result; - /// Retrieves a list of loaded kernel modules. + /// Returns an iterator over the loaded kernel modules. /// /// # Platform-specific /// /// - **Windows**: Retrieves information from the `PsLoadedModuleList`. /// - **Linux**: Retrieves information from the `modules` list. - fn modules( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError>; + fn modules<'a>( + vmi: VmiState<'a, Driver, Self>, + ) -> Result, VmiError>> + 'a, VmiError>; - /// Retrieves the system process object. - /// - /// The system process is the first process created by the kernel. + /// Returns an iterator over the processes. /// /// # Platform-specific /// - /// - **Windows**: Retrieves the `PsInitialSystemProcess` global variable. - /// - **Linux**: Retrieves the `init_task` global variable. - fn system_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; - - /// Retrieves the thread ID for a given thread object. - fn thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - thread: ThreadObject, - ) -> Result; - - /// Retrieves the process ID for a given process object. - fn process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; - - /// Retrieves the current thread object. - fn current_thread( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + /// - **Windows**: Retrieves information from the `PsActiveProcessHead` list. + /// - **Linux**: Retrieves information from the `tasks` list. + fn processes<'a>( + vmi: VmiState<'a, Driver, Self>, + ) -> Result, VmiError>> + 'a, VmiError>; - /// Retrieves the current thread ID. - fn current_thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; - - /// Retrieves the current process object. - fn current_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; - - /// Retrieves the current process ID. - fn current_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; - - /// Retrieves a list of all processes in the system. - fn processes( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError>; - - /// Retrieves the parent process ID for a given process object. - fn process_parent_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, + /// Returns the process corresponding to the given process object. + fn process<'a>( + vmi: VmiState<'a, Driver, Self>, process: ProcessObject, - ) -> Result; + ) -> Result, VmiError>; - /// Retrieves the architecture of a given process. - fn process_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; + /// Returns the currently executing process. + fn current_process<'a>(vmi: VmiState<'a, Driver, Self>) -> Result, VmiError>; - /// Retrieves the translation root for a given process. - /// - /// The translation root is the root of the page table hierarchy (also - /// known as the Directory Table Base (DTB) or Page Global Directory (PGD)). - /// - /// # Architecture-specific + /// Returns the system process object. /// - /// - **AMD64**: The translation root corresponds with the CR3 register - /// and PML4 table. + /// The system process is the first process created by the kernel. /// /// # Platform-specific /// - /// - **Windows**: Retrieves the `DirectoryTableBase` field from the - /// `KPROCESS` structure. - /// - **Linux**: Retrieves the `mm->pgd` field from the `task_struct`. - fn process_translation_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; - - /// Retrieves the base address of the user translation root for a given - /// process. - /// - /// If KPTI is disabled, this function will return the same value as - /// [`VmiOs::process_translation_root`]. - fn process_user_translation_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; - - /// Retrieves the filename of a given process. - fn process_filename( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; - - /// Retrieves the base address of the process image. - fn process_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result; - - /// Retrieves a list of memory regions for a given process. - fn process_regions( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result, VmiError>; + /// - **Windows**: Retrieves the `PsInitialSystemProcess` global variable. + /// - **Linux**: Retrieves the `init_task` global variable. + fn system_process<'a>(vmi: VmiState<'a, Driver, Self>) -> Result, VmiError>; - /// Checks if a given virtual address is valid in a given process. - fn process_address_is_valid( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError>; + /// Returns the thread corresponding to the given thread object. + fn thread<'a>( + vmi: VmiState<'a, Driver, Self>, + thread: ThreadObject, + ) -> Result, VmiError>; - /// Finds a specific memory region in a process given an address. - fn find_process_region( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError>; + /// Returns the currently executing thread. + fn current_thread<'a>(vmi: VmiState<'a, Driver, Self>) -> Result, VmiError>; - /// Retrieves the architecture of an image at a given base address. - fn image_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, + /// Returns the image corresponding to the given base address. + fn image<'a>( + vmi: VmiState<'a, Driver, Self>, image_base: Va, - ) -> Result; + ) -> Result, VmiError>; - /// Retrieves a list of exported symbols from an image at a given base - /// address. - fn image_exported_symbols( - &self, - vmi: &VmiCore, - registers: &::Registers, - image_base: Va, - ) -> Result, VmiError>; + /// Returns the kernel module corresponding to the given base address. + fn module<'a>( + vmi: VmiState<'a, Driver, Self>, + module: Va, + ) -> Result, VmiError>; + + /// Returns the memory region corresponding to the given address. + /// + /// # Platform-specific + /// + /// - **Windows**: The region is represented by the `_MMVAD` structure. + /// - **Linux**: The region is represented by the `vm_area_struct` structure. + fn region<'a>( + vmi: VmiState<'a, Driver, Self>, + region: Va, + ) -> Result, VmiError>; /// Retrieves a specific syscall argument according to the system call ABI. /// /// This function assumes that it is called in the prologue of the system /// call handler, i.e., the instruction pointer is pointing to the first /// instruction of the function. - fn syscall_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result; + fn syscall_argument(vmi: VmiState, index: u64) -> Result; /// Retrieves a specific function argument according to the calling /// convention of the operating system. @@ -285,22 +189,13 @@ where /// /// - **Windows**: Assumes that the function is using the `stdcall` /// calling convention. - fn function_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result; + fn function_argument(vmi: VmiState, index: u64) -> Result; /// Retrieves the return value of a function. /// /// This function assumes that it is called immediately after the function /// returns. - fn function_return_value( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn function_return_value(vmi: VmiState) -> Result; /// Retrieves the last error value. /// @@ -309,43 +204,5 @@ where /// - **Windows**: Retrieves the value of the `NtCurrentTeb()->LastErrorValue` /// field. /// - See also: [`WindowsOs::last_status()`](../../../../vmi_os_windows/struct.WindowsOs.html#method.last_status) - fn last_error( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError>; -} - -/// Operating system extension trait. -pub trait OsExt -where - Driver: VmiDriver, -{ - /// Enumerates a linked list. - /// - /// # Platform-specific - /// - /// - **Windows**: Enumerates a `LIST_ENTRY` structure. - /// - **Linux**: Enumerates a `list_head` structure. - fn enumerate_list( - &self, - vmi: &VmiCore, - registers: &::Registers, - list_head: Va, - callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError>; - - /// Enumerates a tree structure. - /// - /// # Platform-specific - /// - /// - **Windows 7**: Enumerates a `MMADDRESS_NODE` structure. - /// - **Windows 10+**: Enumerates a `RTL_BALANCED_NODE` structure. - fn enumerate_tree( - &self, - vmi: &VmiCore, - registers: &::Registers, - root: Va, - callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError>; + fn last_error(vmi: VmiState) -> Result, VmiError>; } diff --git a/crates/vmi-core/src/os/module.rs b/crates/vmi-core/src/os/module.rs new file mode 100644 index 0000000..bc6457c --- /dev/null +++ b/crates/vmi-core/src/os/module.rs @@ -0,0 +1,42 @@ +use super::VmiOs; +use crate::{Va, VmiDriver, VmiError, VmiVa}; + +/// A trait for kernel modules. +/// +/// This trait provides an abstraction over dynamically loaded modules, +/// such as kernel drivers and shared libraries, within a guest OS. +pub trait VmiOsModule<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the base address of the module. + /// + /// # Platform-specific + /// + /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.DllBase` + /// - **Linux**: + /// - since v6.4-rc1: `module::mem[0 /* MOD_TEXT */].base` + /// - before v6.4-rc1: `module::core_layout.base` + fn base_address(&self) -> Result; + + /// Returns the size of the module. + /// + /// # Platform-specific + /// + /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.SizeOfImage` + /// - **Linux**: + /// - since v6.4-rc1: sum of `module::mem[MOD_*].size` + /// - before v6.4-rc1: `module::init_layout.size + module::core_layout.size (+ module::data_layout.size)` + fn size(&self) -> Result; + + /// Returns the name of the module. + /// + /// # Platform-specific + /// + /// - **Windows**: `KLDR_DATA_TABLE_ENTRY.BaseDllName` + /// - **Linux**: `module::name` + fn name(&self) -> Result; +} diff --git a/crates/vmi-core/src/os/process.rs b/crates/vmi-core/src/os/process.rs new file mode 100644 index 0000000..8d76bfe --- /dev/null +++ b/crates/vmi-core/src/os/process.rs @@ -0,0 +1,141 @@ +use serde::{Deserialize, Serialize}; + +use super::{VmiOs, VmiOsImageArchitecture}; +use crate::{Pa, Va, VmiDriver, VmiError, VmiVa}; + +/// A process ID within a system. +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] +pub struct ProcessId(pub u32); + +impl From for ProcessId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: ProcessId) -> Self { + value.0 + } +} + +impl std::fmt::Display for ProcessId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A process object within a system. +/// +/// Equivalent to `EPROCESS*` on Windows or `task_struct*` on Linux. +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] +pub struct ProcessObject(pub Va); + +impl ProcessObject { + /// Checks if the process object is a null reference. + pub fn is_null(&self) -> bool { + self.0 .0 == 0 + } + + /// Converts the process object to a 64-bit unsigned integer. + pub fn to_u64(&self) -> u64 { + self.0 .0 + } +} + +impl From for ProcessObject { + fn from(va: Va) -> Self { + Self(va) + } +} + +impl From for Va { + fn from(value: ProcessObject) -> Self { + value.0 + } +} + +impl std::fmt::Display for ProcessObject { + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A trait for process objects. +/// +/// This trait provides an abstraction over processes within a guest OS. +pub trait VmiOsProcess<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the process ID. + fn id(&self) -> Result; + + /// Returns the process object. + fn object(&self) -> Result; + + /// Returns the name of the process. + /// + /// # Platform-specific + /// + /// - **Windows**: `_EPROCESS.ImageFileName` (limited to 16 characters). + /// - **Linux**: `_task_struct.comm` (limited to 16 characters). + fn name(&self) -> Result; + + /// Returns the parent process ID. + fn parent_id(&self) -> Result; + + /// Returns the architecture of the process. + fn architecture(&self) -> Result; + + /// Returns the process's page table translation root. + fn translation_root(&self) -> Result; + + /// Returns the user-mode page table translation root. + /// + /// If KPTI is disabled, this function will return the same value as + /// [`translation_root`](Self::translation_root). + fn user_translation_root(&self) -> Result; + + /// Returns the base address of the process image. + fn image_base(&self) -> Result; + + /// Returns an iterator over the process's memory regions. + fn regions( + &self, + ) -> Result< + impl Iterator>::Region<'a>, VmiError>>, + VmiError, + >; + + /// Finds the memory region containing the given address. + fn find_region( + &self, + address: Va, + ) -> Result>::Region<'a>>, VmiError>; + + /// Returns an iterator over the threads in the process. + /// + /// # Platform-specific + /// + /// - **Windows**: `_EPROCESS.ThreadListHead`. + fn threads( + &self, + ) -> Result< + impl Iterator>::Thread<'a>, VmiError>>, + VmiError, + >; + + /// Checks whether the given virtual address is valid in the process. + /// + /// This method checks if page-faulting on the address would result in + /// a successful access. + fn is_valid_address(&self, address: Va) -> Result, VmiError>; +} diff --git a/crates/vmi-core/src/os/region.rs b/crates/vmi-core/src/os/region.rs new file mode 100644 index 0000000..423046b --- /dev/null +++ b/crates/vmi-core/src/os/region.rs @@ -0,0 +1,75 @@ +use super::VmiOs; +use crate::{MemoryAccess, Va, VmiDriver, VmiError, VmiVa}; + +/// A trait for memory regions. +/// +/// This trait provides an abstraction over memory regions within a guest OS. +pub trait VmiOsRegion<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the starting virtual address of the memory region. + fn start(&self) -> Result; + + /// Returns the ending virtual address of the memory region. + fn end(&self) -> Result; + + /// Returns the memory protection of the memory region. + fn protection(&self) -> Result; + + /// Returns the memory region's kind. + fn kind(&self) -> Result, VmiError>; +} + +/// Specifies the kind of memory region. +pub enum VmiOsRegionKind<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, + Self: 'a, +{ + /// A private region of memory. + /// + /// Such regions are usually created by functions like `VirtualAlloc` on + /// Windows. + Private, + + /// A mapped region of memory. Might be backed by a file. + /// + /// Such regions are usually created by functions like `MapViewOfFile` on + /// Windows. + MappedData(Os::Mapped<'a>), + + /// A mapped image region of memory. Might be backed by a file. + /// + /// Such regions are usually created by functions like `MapViewOfFile` on + /// Windows. + MappedImage(Os::Mapped<'a>), +} + +impl<'a, Driver, Os> VmiOsRegionKind<'a, Driver, Os> +where + Driver: VmiDriver, + Os: VmiOs, +{ + /// Checks if the region is private. + pub fn is_private(&self) -> bool { + matches!(self, Self::Private) + } + + /// Checks if the region is mapped. + pub fn is_mapped(&self) -> bool { + matches!(self, Self::MappedData(_) | Self::MappedImage(_)) + } + + /// Returns the mapped region. + pub fn mapped(&self) -> Option<&Os::Mapped<'a>> { + match self { + Self::MappedData(mapped) | Self::MappedImage(mapped) => Some(mapped), + _ => None, + } + } +} diff --git a/crates/vmi-core/src/os/struct_reader.rs b/crates/vmi-core/src/os/struct_reader.rs index 3d08e32..1f93755 100644 --- a/crates/vmi-core/src/os/struct_reader.rs +++ b/crates/vmi-core/src/os/struct_reader.rs @@ -1,6 +1,7 @@ use isr_macros::Field; -use crate::{AccessContext, VmiCore, VmiDriver, VmiError}; +use super::VmiOs; +use crate::{AccessContext, Registers, Va, VmiCore, VmiDriver, VmiError, VmiState}; /// A handler for reading structured data from guest memory. /// @@ -55,7 +56,22 @@ impl StructReader { /// [`read`] method with appropriate field descriptors. /// /// [`read`]: Self::read - pub fn new( + pub fn new(vmi: &VmiState, va: Va, len: usize) -> Result + where + Driver: VmiDriver, + Os: VmiOs, + { + Self::new_in(vmi, vmi.registers().address_context(va), len) + } + + /// Creates a new structure reader. + /// + /// Reads `len` bytes from the guest memory at the specified address into + /// a new `StructReader` instance. The data can then be accessed using the + /// [`read`] method with appropriate field descriptors. + /// + /// [`read`]: Self::read + pub fn new_in( vmi: &VmiCore, ctx: impl Into, len: usize, @@ -64,7 +80,7 @@ impl StructReader { Driver: VmiDriver, { let mut buffer = vec![0u8; len]; - vmi.read(ctx, &mut buffer)?; + vmi.read(ctx.into(), &mut buffer)?; Ok(Self(buffer)) } @@ -73,15 +89,15 @@ impl StructReader { /// Extracts a value from the buffer using the provided field descriptor, /// which specifies the offset and size of the field. /// The value is interpreted as a little-endian integer of the appropriate - /// size and returned as a `u64`. + /// size and returned as a [`u64`]. /// /// # Endianness /// - /// Values are always read as little-endian integers. The returned `u64` + /// Values are always read as little-endian integers. The returned [`u64`] /// will contain the zero-extended value. pub fn read(&self, field: Field) -> Result { - let offset = field.offset as usize; - let size = field.size as usize; + let offset = field.offset() as usize; + let size = field.size() as usize; let offset_end = match offset.checked_add(size) { Some(offset_end) => offset_end, diff --git a/crates/vmi-core/src/os/thread.rs b/crates/vmi-core/src/os/thread.rs new file mode 100644 index 0000000..a71c943 --- /dev/null +++ b/crates/vmi-core/src/os/thread.rs @@ -0,0 +1,83 @@ +use serde::{Deserialize, Serialize}; + +use super::VmiOs; +use crate::{Va, VmiDriver, VmiError, VmiVa}; + +/// A thread ID within a system. +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] +pub struct ThreadId(pub u32); + +impl From for ThreadId { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: ThreadId) -> Self { + value.0 + } +} + +impl std::fmt::Display for ThreadId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A thread object within a system. +/// +/// Equivalent to `ETHREAD*` on Windows. +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, +)] +pub struct ThreadObject(pub Va); + +impl ThreadObject { + /// Checks if the thread object is a null reference. + pub fn is_null(&self) -> bool { + self.0 .0 == 0 + } + + /// Converts the thread object to a 64-bit unsigned integer. + pub fn to_u64(&self) -> u64 { + self.0 .0 + } +} + +impl From for ThreadObject { + fn from(va: Va) -> Self { + Self(va) + } +} + +impl From for Va { + fn from(value: ThreadObject) -> Self { + value.0 + } +} + +impl std::fmt::Display for ThreadObject { + fn fmt(&self, f: &mut std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A trait for thread objects. +/// +/// This trait provides an abstraction over threads within a guest OS. +pub trait VmiOsThread<'a, Driver>: VmiVa + 'a +where + Driver: VmiDriver, +{ + /// The VMI OS type. + type Os: VmiOs; + + /// Returns the thread ID. + fn id(&self) -> Result; + + /// Returns the thread object. + fn object(&self) -> Result; +} diff --git a/crates/vmi-core/src/session.rs b/crates/vmi-core/src/session.rs deleted file mode 100644 index 39a5115..0000000 --- a/crates/vmi-core/src/session.rs +++ /dev/null @@ -1,493 +0,0 @@ -use std::{cell::RefCell, io::ErrorKind, rc::Rc, time::Duration}; - -use indexmap::IndexSet; -use zerocopy::{FromBytes, IntoBytes}; - -use crate::{ - context::VmiContext, os::VmiOs, AccessContext, Architecture, PageFault, PageFaults, - TranslationMechanism, Va, VmiCore, VmiDriver, VmiError, VmiHandler, -}; - -/// A VMI session. -/// -/// The session combines a [`VmiCore`] with an OS-specific [`VmiOs`] -/// implementation to provide unified access to both low-level VMI operations -/// and higher-level OS abstractions. -pub struct VmiSession<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI core providing low-level VM introspection capabilities. - pub(crate) core: &'a VmiCore, - - /// The OS-specific operations and abstractions. - pub(crate) os: &'a Os, -} - -impl std::ops::Deref for VmiSession<'_, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - type Target = VmiCore; - - fn deref(&self) -> &Self::Target { - self.core - } -} - -impl<'a, Driver, Os> VmiSession<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Creates a new VMI session. - pub fn new(core: &'a VmiCore, os: &'a Os) -> Self { - Self { core, os } - } - - /// Returns the VMI core. - pub fn core(&self) -> &VmiCore { - self.core - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &Os { - self.os - } - - /// Returns a wrapper providing access to the OS-specific operations. - pub fn os(&self) -> VmiOsSession { - VmiOsSession { - core: self.core, - os: self.os, - } - } - - /// Creates a prober for safely handling page faults during memory access operations. - pub fn prober(&'a self, restricted: &IndexSet) -> VmiSessionProber<'a, Driver, Os> { - VmiSessionProber::new(self, restricted) - } - - /// Waits for an event to occur and processes it with the provided handler. - /// - /// This method blocks until an event occurs or the specified timeout is - /// reached. When an event occurs, it is passed to the provided callback - /// function for processing. - pub fn wait_for_event( - &self, - timeout: Duration, - handler: &mut impl VmiHandler, - ) -> Result<(), VmiError> { - self.core.wait_for_event(timeout, |event| { - handler.handle_event(VmiContext::new(self, event)) - }) - } - - /// Enters the main event handling loop that processes VMI events until - /// finished. - pub fn handle( - &self, - handler_factory: impl FnOnce(&VmiSession) -> Result, - ) -> Result, VmiError> - where - Handler: VmiHandler, - { - self.handle_with_timeout(Duration::from_millis(5000), handler_factory) - } - - /// Enters the main event handling loop that processes VMI events until - /// finished, with a timeout for each event. - pub fn handle_with_timeout( - &self, - timeout: Duration, - handler_factory: impl FnOnce(&VmiSession) -> Result, - ) -> Result, VmiError> - where - Handler: VmiHandler, - { - let mut result; - let mut handler = handler_factory(self)?; - - loop { - result = handler.check_completion(); - - if result.is_some() { - break; - } - - match self.wait_for_event(timeout, &mut handler) { - Err(VmiError::Timeout) => { - tracing::trace!("timeout"); - handler.handle_timeout(self); - } - Err(VmiError::Io(err)) if err.kind() == ErrorKind::Interrupted => { - tracing::trace!("interrupted"); - handler.handle_interrupted(self); - break; - } - Err(err) => return Err(err), - Ok(_) => {} - } - } - - tracing::trace!("disabling monitor"); - self.core.reset_state()?; - tracing::trace!(pending_events = self.events_pending()); - - let _pause_guard = self.pause_guard()?; - if self.events_pending() > 0 { - match self.wait_for_event(Duration::from_millis(0), &mut handler) { - Err(VmiError::Timeout) => { - tracing::trace!("timeout"); - } - Err(err) => return Err(err), - Ok(_) => {} - } - } - - Ok(result) - } -} - -/// Wrapper providing access to OS-specific operations. -pub struct VmiOsSession<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI core providing low-level VM introspection capabilities. - pub(crate) core: &'a VmiCore, - - /// The OS-specific operations and abstractions. - pub(crate) os: &'a Os, -} - -impl<'a, Driver, Os> VmiOsSession<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Returns the VMI session. - pub fn core(&self) -> &'a VmiCore { - self.core - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &'a Os { - self.os - } -} - -/// Prober for safely handling page faults during memory access operations. -pub struct VmiSessionProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// The VMI session. - pub(crate) session: &'a VmiSession<'a, Driver, Os>, - - /// The set of restricted page faults that are allowed to occur. - pub(crate) restricted: Rc>, - - /// The set of page faults that have occurred. - pub(crate) page_faults: Rc>>, -} - -impl<'a, Driver, Os> std::ops::Deref for VmiSessionProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - type Target = VmiSession<'a, Driver, Os>; - - fn deref(&self) -> &Self::Target { - self.session - } -} - -impl<'a, Driver, Os> VmiSessionProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Creates a new VMI session prober. - pub fn new(session: &'a VmiSession, restricted: &IndexSet) -> Self { - Self { - session, - restricted: Rc::new(restricted.clone()), - page_faults: Rc::new(RefCell::new(IndexSet::new())), - } - } - - /// Checks for any unexpected page faults that have occurred and returns an error if any are present. - #[tracing::instrument(skip_all)] - pub fn error_for_page_faults(&self) -> Result<(), VmiError> { - let pfs = self.page_faults.borrow(); - let new_pfs = &*pfs - &self.restricted; - if !new_pfs.is_empty() { - tracing::trace!(?new_pfs); - return Err(VmiError::page_faults(new_pfs)); - } - - Ok(()) - } - - /// Returns a wrapper providing access to OS-specific operations. - pub fn os(&self) -> VmiOsSessionProber { - VmiOsSessionProber(self) - } - - /// Reads memory from the virtual machine. - pub fn read( - &self, - ctx: impl Into, - buffer: &mut [u8], - ) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read(ctx, buffer), ctx, buffer.len()) - } - - /// Reads a single byte from the virtual machine. - pub fn read_u8(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_u8(ctx), ctx, size_of::()) - } - - /// Reads a 16-bit unsigned integer from the virtual machine. - pub fn read_u16(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_u16(ctx), ctx, size_of::()) - } - - /// Reads a 32-bit unsigned integer from the virtual machine. - pub fn read_u32(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_u32(ctx), ctx, size_of::()) - } - - /// Reads a 64-bit unsigned integer from the virtual machine. - pub fn read_u64(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_u64(ctx), ctx, size_of::()) - } - - /// Reads a virtual address from the virtual machine. - pub fn read_va( - &self, - ctx: impl Into, - address_width: usize, - ) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range( - self.session.core().read_va(ctx, address_width), - ctx, - address_width, - ) - } - - /// Reads a 32-bit virtual address from the virtual machine. - pub fn read_va32(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_va32(ctx), ctx, size_of::()) - } - - /// Reads a 64-bit virtual address from the virtual machine. - pub fn read_va64(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_va64(ctx), ctx, size_of::()) - } - - /// Reads a null-terminated string of bytes from the virtual machine. - pub fn read_string_bytes( - &self, - ctx: impl Into, - ) -> Result>, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_string_bytes(ctx), ctx, 1) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring_bytes( - &self, - ctx: impl Into, - ) -> Result>, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_wstring_bytes(ctx), ctx, 2) - } - - /// Reads a null-terminated string from the virtual machine. - pub fn read_string(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_string(ctx), ctx, 1) - } - - /// Reads a null-terminated wide string (UTF-16) from the virtual machine. - pub fn read_wstring(&self, ctx: impl Into) -> Result, VmiError> { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_wstring(ctx), ctx, 2) - } - - /// Reads a struct from the virtual machine. - pub fn read_struct(&self, ctx: impl Into) -> Result, VmiError> - where - T: IntoBytes + FromBytes, - { - let ctx = ctx.into(); - self.check_result_range(self.session.core().read_struct(ctx), ctx, size_of::()) - } - - /// Handles a result that may contain page faults, returning the value if successful. - pub fn check_result(&self, result: Result) -> Result, VmiError> { - match result { - Ok(value) => Ok(Some(value)), - Err(VmiError::PageFault(pfs)) => { - self.check_restricted(pfs); - Ok(None) - } - Err(err) => Err(err), - } - } - - /// Handles a result that may contain page faults over a memory range, returning the value if successful. - fn check_result_range( - &self, - result: Result, - ctx: AccessContext, - length: usize, - ) -> Result, VmiError> { - match result { - Ok(value) => Ok(Some(value)), - Err(VmiError::PageFault(pfs)) => { - debug_assert_eq!(pfs.len(), 1); - self.check_restricted_range(pfs[0], ctx, length); - Ok(None) - } - Err(err) => Err(err), - } - } - - /// Records any page faults that are not in the restricted set. - fn check_restricted(&self, pfs: PageFaults) { - let mut page_faults = self.page_faults.borrow_mut(); - for pf in pfs { - if !self.restricted.contains(&pf) { - tracing::trace!(va = %pf.address, "page fault"); - page_faults.insert(pf); - } - else { - tracing::trace!(va = %pf.address, "page fault (restricted)"); - } - } - } - - /// Records any page faults that are not in the restricted set over a memory range. - fn check_restricted_range(&self, pf: PageFault, ctx: AccessContext, mut length: usize) { - let mut page_faults = self.page_faults.borrow_mut(); - - if length == 0 { - length = 1; - } - - // - // Generate page faults for the range of addresses that would be accessed by the read. - // Start at the page containing the faulting address and end at the page containing the - // last byte of the read. - // - - let pf_page = pf.address.0 >> Driver::Architecture::PAGE_SHIFT; - let last_page = (ctx.address + length as u64 - 1) >> Driver::Architecture::PAGE_SHIFT; - let number_of_pages = last_page.saturating_sub(pf_page) + 1; - - let pf_address_aligned = Va(pf_page << Driver::Architecture::PAGE_SHIFT); - let last_address_aligned = Va(last_page << Driver::Architecture::PAGE_SHIFT); - - if number_of_pages > 1 { - tracing::debug!( - from = %pf_address_aligned, - to = %last_address_aligned, - number_of_pages, - "page fault (range)" - ); - - if number_of_pages >= 4096 { - tracing::warn!( - from = %pf_address_aligned, - to = %last_address_aligned, - number_of_pages, - "page fault range too large" - ); - } - } - - for i in 0..number_of_pages { - // - // Ensure that the page fault is for the root that we are tracking. - // - - debug_assert_eq!( - pf.root, - match ctx.mechanism { - TranslationMechanism::Paging { root: Some(root) } => root, - _ => panic!("page fault root doesn't match the context root"), - } - ); - - let pf = PageFault { - address: pf_address_aligned + i * Driver::Architecture::PAGE_SIZE, - root: pf.root, - }; - - if !self.restricted.contains(&pf) { - tracing::trace!(va = %pf.address, "page fault"); - page_faults.insert(pf); - } - else { - tracing::trace!(va = %pf.address, "page fault (restricted)"); - } - } - } -} - -/// Wrapper providing access to OS-specific operations with page fault handling. -pub struct VmiOsSessionProber<'a, Driver, Os>(pub(crate) &'a VmiSessionProber<'a, Driver, Os>) -where - Driver: VmiDriver, - Os: VmiOs; - -impl<'a, Driver, Os> VmiOsSessionProber<'a, Driver, Os> -where - Driver: VmiDriver, - Os: VmiOs, -{ - /// Returns the VMI session prober. - pub fn core(&self) -> &'a VmiSessionProber<'a, Driver, Os> { - self.0 - } - - /// Returns the underlying OS-specific implementation. - pub fn underlying_os(&self) -> &'a Os { - self.0.os - } - - /* - pub fn function_argument_for_registers( - &self, - regs: &::Registers, - index: u64, - ) -> Result, VmiError> { - self.0 - .check_result(self.0.context.session().os().function_argument(regs, index)) - } - - pub fn function_return_value_for_registers( - &self, - regs: &::Registers, - ) -> Result, VmiError> { - self.0 - .check_result(self.0.context.session.os().function_return_value(regs)) - } - */ -} diff --git a/crates/vmi-driver-kdmp/Cargo.toml b/crates/vmi-driver-kdmp/Cargo.toml new file mode 100644 index 0000000..817b6a0 --- /dev/null +++ b/crates/vmi-driver-kdmp/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "vmi-driver-kdmp" +version = "0.2.0" +license = "MIT" +authors = { workspace = true } +edition = { workspace = true } +publish = { workspace = true } +rust-version = { workspace = true } + +homepage = { workspace = true } +repository = { workspace = true } +description = "VMI driver for Windows kernel dump" +keywords = [ + "vmi", + "windows", + "dump", +] + +[lints] +workspace = true + +[dependencies] +kdmp-parser = { workspace = true } +memmap2 = { workspace = true } +tracing = { workspace = true } +zerocopy = { workspace = true } + +vmi-arch-amd64 = { workspace = true } +vmi-core = { workspace = true } diff --git a/crates/vmi-driver-kdmp/README.md b/crates/vmi-driver-kdmp/README.md new file mode 100644 index 0000000..20b5035 --- /dev/null +++ b/crates/vmi-driver-kdmp/README.md @@ -0,0 +1 @@ +VMI driver for Windows kernel dump. diff --git a/crates/vmi-driver-kdmp/README.tpl b/crates/vmi-driver-kdmp/README.tpl new file mode 100644 index 0000000..274bb44 --- /dev/null +++ b/crates/vmi-driver-kdmp/README.tpl @@ -0,0 +1 @@ +{{readme}} diff --git a/crates/vmi-driver-kdmp/src/arch/amd64.rs b/crates/vmi-driver-kdmp/src/arch/amd64.rs new file mode 100644 index 0000000..8442051 --- /dev/null +++ b/crates/vmi-driver-kdmp/src/arch/amd64.rs @@ -0,0 +1,19 @@ +use vmi_arch_amd64::{Amd64, Cr0, Cr3, Cr4, MsrEfer, Registers}; +use vmi_core::VcpuId; + +use crate::{ArchAdapter, Error, KdmpDriver}; + +impl ArchAdapter for Amd64 { + fn registers(driver: &KdmpDriver, _vcpu: VcpuId) -> Result { + let headers = driver.dump.headers(); + + Ok(Registers { + cr0: Cr0(0x80050031), + cr3: Cr3(headers.directory_table_base), + cr4: Cr4(0x350ef8), + msr_lstar: headers.ps_active_process_head, + msr_efer: MsrEfer(0x501), + ..Default::default() + }) + } +} diff --git a/crates/vmi-driver-kdmp/src/arch/mod.rs b/crates/vmi-driver-kdmp/src/arch/mod.rs new file mode 100644 index 0000000..6533b1b --- /dev/null +++ b/crates/vmi-driver-kdmp/src/arch/mod.rs @@ -0,0 +1,10 @@ +mod amd64; + +use vmi_core::{Architecture, VcpuId}; + +use crate::{Error, KdmpDriver}; + +/// Architecture-specific adapter for Xen. +pub trait ArchAdapter: Architecture + Sized + 'static { + fn registers(driver: &KdmpDriver, vcpu: VcpuId) -> Result; +} diff --git a/crates/vmi-driver-kdmp/src/driver.rs b/crates/vmi-driver-kdmp/src/driver.rs new file mode 100644 index 0000000..ad58925 --- /dev/null +++ b/crates/vmi-driver-kdmp/src/driver.rs @@ -0,0 +1,165 @@ +use std::{path::Path, time::Duration}; + +use kdmp_parser::{Gpa, KernelDumpParser, MappedFileReader}; +use vmi_core::{ + Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiEvent, VmiEventResponse, + VmiInfo, VmiMappedPage, +}; + +use crate::{ArchAdapter, Error}; + +/// VMI driver for Xen core dump. +pub struct KdmpDriver +where + Arch: Architecture + ArchAdapter, +{ + pub(crate) dump: KernelDumpParser, + _marker: std::marker::PhantomData, +} + +impl KdmpDriver +where + Arch: Architecture + ArchAdapter, +{ + pub fn new(path: impl AsRef) -> Result { + let dump = KernelDumpParser::with_reader(MappedFileReader::new(path)?)?; + + Ok(Self { + dump, + _marker: std::marker::PhantomData, + }) + } + + pub fn info(&self) -> Result { + Ok(VmiInfo { + page_size: 4096, + page_shift: 12, + max_gfn: Gfn(0), + vcpus: 0, + }) + } + + pub fn pause(&self) -> Result<(), Error> { + Ok(()) + } + + pub fn resume(&self) -> Result<(), Error> { + Ok(()) + } + + pub fn registers(&self, vcpu: VcpuId) -> Result { + Arch::registers(self, vcpu) + } + + pub fn set_registers(&self, _vcpu: VcpuId, _registers: Arch::Registers) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn memory_access(&self, _gfn: Gfn, _view: View) -> Result { + Err(Error::NotSupported) + } + + pub fn set_memory_access( + &self, + _gfn: Gfn, + _view: View, + _access: MemoryAccess, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn set_memory_access_with_options( + &self, + _gfn: Gfn, + _view: View, + _access: MemoryAccess, + _options: MemoryAccessOptions, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn read_page(&self, gfn: Gfn) -> Result { + let mut content = [0u8; 4096]; + self.dump + .phys_read_exact(Gpa::new(gfn.0 << 12), &mut content)?; + + Ok(VmiMappedPage::new(Vec::from(content))) + } + + pub fn write_page( + &self, + _gfn: Gfn, + _offset: u64, + _content: &[u8], + ) -> Result { + Err(Error::NotSupported) + } + + pub fn allocate_gfn(&self, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn free_gfn(&self, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn default_view(&self) -> View { + View(0) + } + + pub fn create_view(&self, _default_access: MemoryAccess) -> Result { + Err(Error::NotSupported) + } + + pub fn destroy_view(&self, _view: View) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn switch_to_view(&self, _view: View) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn change_view_gfn(&self, _view: View, _old_gfn: Gfn, _new_gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn reset_view_gfn(&self, _view: View, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn monitor_enable(&self, _option: Arch::EventMonitor) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn monitor_disable(&self, _option: Arch::EventMonitor) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn inject_interrupt( + &self, + _vcpu: VcpuId, + _interrupt: Arch::Interrupt, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn events_pending(&self) -> usize { + 0 + } + + pub fn event_processing_overhead(&self) -> Duration { + Duration::from_secs(0) + } + + pub fn wait_for_event( + &self, + _timeout: Duration, + _handler: impl FnMut(&VmiEvent) -> VmiEventResponse, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn reset_state(&self) -> Result<(), Error> { + Err(Error::NotSupported) + } +} diff --git a/crates/vmi-driver-kdmp/src/error.rs b/crates/vmi-driver-kdmp/src/error.rs new file mode 100644 index 0000000..51a9526 --- /dev/null +++ b/crates/vmi-driver-kdmp/src/error.rs @@ -0,0 +1,40 @@ +/// Error type for the Xen driver. +pub enum Error { + /// An error occurred while parsing a kernel dump file. + Kdmp(kdmp_parser::KdmpParserError), + + /// An I/O error occurred. + Io(std::io::Error), + + /// Operation not supported. + NotSupported, + + /// Out of bounds. + OutOfBounds, +} + +impl From for Error { + fn from(value: kdmp_parser::KdmpParserError) -> Self { + Self::Kdmp(value) + } +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl From for vmi_core::VmiError { + fn from(value: Error) -> Self { + match value { + Error::Kdmp(kdmp_parser::KdmpParserError::AddrTranslation( + kdmp_parser::AddrTranslationError::Virt(gva, _), + )) => Self::page_fault((vmi_core::Va(u64::from(gva)), vmi_core::Pa(0))), + Error::Kdmp(value) => Self::Driver(Box::new(value)), + Error::Io(value) => Self::Io(value), + Error::NotSupported => Self::NotSupported, + Error::OutOfBounds => Self::OutOfBounds, + } + } +} diff --git a/crates/vmi-driver-kdmp/src/lib.rs b/crates/vmi-driver-kdmp/src/lib.rs new file mode 100644 index 0000000..cf4b5b9 --- /dev/null +++ b/crates/vmi-driver-kdmp/src/lib.rs @@ -0,0 +1,159 @@ +//! VMI driver for Xen core dump. + +mod arch; +mod driver; +mod error; + +use std::{path::Path, time::Duration}; + +use vmi_core::{ + Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiDriver, VmiError, + VmiEvent, VmiEventResponse, VmiInfo, VmiMappedPage, +}; + +pub use self::error::Error; +use self::{arch::ArchAdapter, driver::KdmpDriver}; + +/// VMI driver for Xen core dump. +pub struct VmiKdmpDriver +where + Arch: Architecture + ArchAdapter, +{ + inner: KdmpDriver, +} + +impl VmiKdmpDriver +where + Arch: Architecture + ArchAdapter, +{ + /// Creates a new VMI driver for Xen core dump. + pub fn new(path: impl AsRef) -> Result { + Ok(Self { + inner: KdmpDriver::new(path)?, + }) + } +} + +impl VmiDriver for VmiKdmpDriver +where + Arch: Architecture + ArchAdapter, +{ + type Architecture = Arch; + + fn info(&self) -> Result { + Ok(self.inner.info()?) + } + + fn pause(&self) -> Result<(), VmiError> { + Ok(self.inner.pause()?) + } + + fn resume(&self) -> Result<(), VmiError> { + Ok(self.inner.resume()?) + } + + fn registers(&self, vcpu: VcpuId) -> Result { + Ok(self.inner.registers(vcpu)?) + } + + fn set_registers(&self, vcpu: VcpuId, registers: Arch::Registers) -> Result<(), VmiError> { + Ok(self.inner.set_registers(vcpu, registers)?) + } + + fn memory_access(&self, gfn: Gfn, view: View) -> Result { + Ok(self.inner.memory_access(gfn, view)?) + } + + fn set_memory_access( + &self, + gfn: Gfn, + view: View, + access: MemoryAccess, + ) -> Result<(), VmiError> { + Ok(self.inner.set_memory_access(gfn, view, access)?) + } + + fn set_memory_access_with_options( + &self, + gfn: Gfn, + view: View, + access: MemoryAccess, + options: MemoryAccessOptions, + ) -> Result<(), VmiError> { + Ok(self + .inner + .set_memory_access_with_options(gfn, view, access, options)?) + } + + fn read_page(&self, gfn: Gfn) -> Result { + Ok(self.inner.read_page(gfn)?) + } + + fn write_page(&self, gfn: Gfn, offset: u64, content: &[u8]) -> Result { + Ok(self.inner.write_page(gfn, offset, content)?) + } + + fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.allocate_gfn(gfn)?) + } + + fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.free_gfn(gfn)?) + } + + fn default_view(&self) -> View { + self.inner.default_view() + } + + fn create_view(&self, default_access: MemoryAccess) -> Result { + Ok(self.inner.create_view(default_access)?) + } + + fn destroy_view(&self, view: View) -> Result<(), VmiError> { + Ok(self.inner.destroy_view(view)?) + } + + fn switch_to_view(&self, view: View) -> Result<(), VmiError> { + Ok(self.inner.switch_to_view(view)?) + } + + fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.change_view_gfn(view, old_gfn, new_gfn)?) + } + + fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.reset_view_gfn(view, gfn)?) + } + + fn monitor_enable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> { + Ok(self.inner.monitor_enable(option)?) + } + + fn monitor_disable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> { + Ok(self.inner.monitor_disable(option)?) + } + + fn inject_interrupt(&self, vcpu: VcpuId, interrupt: Arch::Interrupt) -> Result<(), VmiError> { + Ok(self.inner.inject_interrupt(vcpu, interrupt)?) + } + + fn events_pending(&self) -> usize { + self.inner.events_pending() + } + + fn event_processing_overhead(&self) -> Duration { + self.inner.event_processing_overhead() + } + + fn wait_for_event( + &self, + timeout: Duration, + handler: impl FnMut(&VmiEvent) -> VmiEventResponse, + ) -> Result<(), VmiError> { + Ok(self.inner.wait_for_event(timeout, handler)?) + } + + fn reset_state(&self) -> Result<(), VmiError> { + Ok(self.inner.reset_state()?) + } +} diff --git a/crates/vmi-driver-xen-core-dump/Cargo.toml b/crates/vmi-driver-xen-core-dump/Cargo.toml new file mode 100644 index 0000000..73fdced --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "vmi-driver-xen-core-dump" +version = "0.2.0" +license = "MIT" +authors = { workspace = true } +edition = { workspace = true } +publish = { workspace = true } +rust-version = { workspace = true } + +homepage = { workspace = true } +repository = { workspace = true } +description = "VMI driver for Xen core dump" +keywords = [ + "vmi", + "xen", + "dump", +] + +[lints] +workspace = true + +[dependencies] +elf = { workspace = true } +memmap2 = { workspace = true } +tracing = { workspace = true } +zerocopy = { workspace = true } + +vmi-arch-amd64 = { workspace = true } +vmi-core = { workspace = true } + +xen = { workspace = true } diff --git a/crates/vmi-driver-xen-core-dump/README.md b/crates/vmi-driver-xen-core-dump/README.md new file mode 100644 index 0000000..90b7fa0 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/README.md @@ -0,0 +1 @@ +VMI driver for Xen core dump. diff --git a/crates/vmi-driver-xen-core-dump/README.tpl b/crates/vmi-driver-xen-core-dump/README.tpl new file mode 100644 index 0000000..274bb44 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/README.tpl @@ -0,0 +1 @@ +{{readme}} diff --git a/crates/vmi-driver-xen-core-dump/src/arch/amd64.rs b/crates/vmi-driver-xen-core-dump/src/arch/amd64.rs new file mode 100644 index 0000000..8b3f6e1 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/arch/amd64.rs @@ -0,0 +1,19 @@ +use vmi_arch_amd64::{Amd64, Cr0, Cr2, Cr3, Cr4, MsrEfer, Registers}; +use vmi_core::VcpuId; + +use crate::{ArchAdapter, Error, XenCoreDumpDriver}; + +impl ArchAdapter for Amd64 { + fn registers(driver: &XenCoreDumpDriver, vcpu: VcpuId) -> Result { + let prstatus = driver.dump.xen_prstatus()?; + + Ok(Registers { + cr0: Cr0(prstatus[vcpu.0 as usize].ctrlreg[0]), + cr2: Cr2(prstatus[vcpu.0 as usize].ctrlreg[2]), + cr3: Cr3(prstatus[vcpu.0 as usize].ctrlreg[3]), + cr4: Cr4(prstatus[vcpu.0 as usize].ctrlreg[4]), + msr_efer: MsrEfer(0x501), + ..Default::default() + }) + } +} diff --git a/crates/vmi-driver-xen-core-dump/src/arch/mod.rs b/crates/vmi-driver-xen-core-dump/src/arch/mod.rs new file mode 100644 index 0000000..80f1224 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/arch/mod.rs @@ -0,0 +1,10 @@ +mod amd64; + +use vmi_core::{Architecture, VcpuId}; + +use crate::{Error, XenCoreDumpDriver}; + +/// Architecture-specific adapter for Xen. +pub trait ArchAdapter: Architecture + Sized + 'static { + fn registers(driver: &XenCoreDumpDriver, vcpu: VcpuId) -> Result; +} diff --git a/crates/vmi-driver-xen-core-dump/src/driver.rs b/crates/vmi-driver-xen-core-dump/src/driver.rs new file mode 100644 index 0000000..b98ce01 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/driver.rs @@ -0,0 +1,190 @@ +use std::{collections::HashMap, path::Path, time::Duration}; + +use vmi_core::{ + Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiEvent, VmiEventResponse, + VmiInfo, VmiMappedPage, +}; + +use crate::{dump::Dump, ArchAdapter, Error}; + +/// VMI driver for Xen core dump. +pub struct XenCoreDumpDriver +where + Arch: Architecture + ArchAdapter, +{ + pub(crate) dump: Dump, + pfn_cache: HashMap, + max_gfn: Gfn, + _marker: std::marker::PhantomData, +} + +impl XenCoreDumpDriver +where + Arch: Architecture + ArchAdapter, +{ + pub fn new(path: impl AsRef) -> Result { + let dump = Dump::new(path)?; + + let mut pfn_cache = HashMap::new(); + let mut max_gfn = Gfn(0); + for (index, &pfn) in dump.xen_pfn()?.iter().enumerate() { + // > The value, ~(uint64_t)0, means invalid pfn and the + // > corresponding page has zero. + // + // ref: https://github.com/xen-project/xen/blob/staging/docs/misc/dump-core-format.txt#L95 + if pfn == 0 || pfn == !0 { + continue; + } + + pfn_cache.insert(Gfn(pfn), index); + max_gfn = max_gfn.max(Gfn(pfn)); + } + + Ok(Self { + dump, + pfn_cache, + max_gfn, + _marker: std::marker::PhantomData, + }) + } + + pub fn info(&self) -> Result { + Ok(VmiInfo { + page_size: self.dump.page_size(), + page_shift: 12, + max_gfn: self.max_gfn, + vcpus: 0, + }) + } + + pub fn pause(&self) -> Result<(), Error> { + Ok(()) + } + + pub fn resume(&self) -> Result<(), Error> { + Ok(()) + } + + pub fn registers(&self, vcpu: VcpuId) -> Result { + Arch::registers(self, vcpu) + } + + pub fn set_registers(&self, _vcpu: VcpuId, _registers: Arch::Registers) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn memory_access(&self, _gfn: Gfn, _view: View) -> Result { + Err(Error::NotSupported) + } + + pub fn set_memory_access( + &self, + _gfn: Gfn, + _view: View, + _access: MemoryAccess, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn set_memory_access_with_options( + &self, + _gfn: Gfn, + _view: View, + _access: MemoryAccess, + _options: MemoryAccessOptions, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn read_page(&self, gfn: Gfn) -> Result { + let index = self + .pfn_cache + .get(&gfn) + .copied() + .ok_or(Error::OutOfBounds)?; + + let pages = self.dump.xen_pages()?; + let start = index * self.dump.page_size() as usize; + let end = start + self.dump.page_size() as usize; + let content = &pages[start..end]; + + Ok(VmiMappedPage::new(Vec::from(content))) + } + + pub fn write_page( + &self, + _gfn: Gfn, + _offset: u64, + _content: &[u8], + ) -> Result { + Err(Error::NotSupported) + } + + pub fn allocate_gfn(&self, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn free_gfn(&self, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn default_view(&self) -> View { + View(0) + } + + pub fn create_view(&self, _default_access: MemoryAccess) -> Result { + Err(Error::NotSupported) + } + + pub fn destroy_view(&self, _view: View) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn switch_to_view(&self, _view: View) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn change_view_gfn(&self, _view: View, _old_gfn: Gfn, _new_gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn reset_view_gfn(&self, _view: View, _gfn: Gfn) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn monitor_enable(&self, _option: Arch::EventMonitor) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn monitor_disable(&self, _option: Arch::EventMonitor) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn inject_interrupt( + &self, + _vcpu: VcpuId, + _interrupt: Arch::Interrupt, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn events_pending(&self) -> usize { + 0 + } + + pub fn event_processing_overhead(&self) -> Duration { + Duration::from_secs(0) + } + + pub fn wait_for_event( + &self, + _timeout: Duration, + _handler: impl FnMut(&VmiEvent) -> VmiEventResponse, + ) -> Result<(), Error> { + Err(Error::NotSupported) + } + + pub fn reset_state(&self) -> Result<(), Error> { + Err(Error::NotSupported) + } +} diff --git a/crates/vmi-driver-xen-core-dump/src/dump.rs b/crates/vmi-driver-xen-core-dump/src/dump.rs new file mode 100644 index 0000000..c8fefd5 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/dump.rs @@ -0,0 +1,162 @@ +use std::{fs::File, path::Path}; + +use elf::{endian::AnyEndian, note::Note, section::SectionHeader, ElfBytes, ParseError}; +use memmap2::Mmap; +use xen::sys::vcpu_guest_context; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::Error; + +pub struct Dump { + mmap: Mmap, + xen_pfn_shdr: SectionHeader, + xen_pages_shdr: SectionHeader, + xen_prstatus_shdr: SectionHeader, + nr_vcpus: u64, + nr_pages: u64, + page_size: u64, +} + +impl Dump { + pub fn new(path: impl AsRef) -> Result { + let file = File::open(path)?; + let mmap = unsafe { Mmap::map(&file)? }; + + let elf = ElfBytes::::minimal_parse(&mmap)?; + + let xen_note = parse_xen_dumpcore_elfnote_header_desc(&elf)?.expect("xen note not found"); + + let xen_pfn_shdr = elf + .section_header_by_name(".xen_pfn")? + .expect("xen_pfn section not found"); + + if xen_pfn_shdr.sh_size != xen_note.xch_nr_pages * size_of::() as u64 { + panic!("xen_pfn section size does not match the number of pages"); + } + + let xen_pages_shdr = elf + .section_header_by_name(".xen_pages")? + .expect("xen_pages section not found"); + + if xen_pages_shdr.sh_size != xen_note.xch_nr_pages * xen_note.xch_page_size { + panic!("xen_pages section size does not match the number of pages"); + } + + let xen_prstatus_shdr = elf + .section_header_by_name(".xen_prstatus")? + .expect("xen_prstatus section not found"); + + if xen_prstatus_shdr.sh_size + != xen_note.xch_nr_vcpus * size_of::() as u64 + { + panic!("xen_prstatus section size does not match the number of vcpus"); + } + + Ok(Self { + mmap, + xen_pfn_shdr, + xen_pages_shdr, + xen_prstatus_shdr, + nr_vcpus: xen_note.xch_nr_vcpus, + nr_pages: xen_note.xch_nr_pages, + page_size: xen_note.xch_page_size, + }) + } + + pub fn xen_pfn(&self) -> Result<&[u64], ParseError> { + let data = self.data(&self.xen_pfn_shdr)?; + + let ptr = data.as_ptr() as *const u64; + let len = data.len() / size_of::(); + + // SAFETY: The data is guaranteed to be correctly aligned and sized. + Ok(unsafe { std::slice::from_raw_parts(ptr, len) }) + } + + pub fn xen_pages(&self) -> Result<&[u8], ParseError> { + self.data(&self.xen_pages_shdr) + } + + pub fn xen_prstatus(&self) -> Result<&[vcpu_guest_context], ParseError> { + let data = self.data(&self.xen_prstatus_shdr)?; + + let ptr = data.as_ptr() as *const vcpu_guest_context; + let len = data.len() / size_of::(); + + // SAFETY: The data is guaranteed to be correctly aligned and sized. + Ok(unsafe { std::slice::from_raw_parts(ptr, len) }) + } + + #[expect(unused)] + pub fn nr_vcpus(&self) -> u64 { + self.nr_vcpus + } + + #[expect(unused)] + pub fn nr_pages(&self) -> u64 { + self.nr_pages + } + + pub fn page_size(&self) -> u64 { + self.page_size + } + + fn data(&self, shdr: &SectionHeader) -> Result<&[u8], ParseError> { + let start = usize::try_from(shdr.sh_offset)?; + let size = usize::try_from(shdr.sh_size)?; + let end = start.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + + self.mmap + .get(start..end) + .ok_or(ParseError::SliceReadError((start, end))) + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)] +#[allow(non_camel_case_types)] +struct xen_dumpcore_elfnote_header_desc { + xch_magic: u64, + xch_nr_vcpus: u64, + xch_nr_pages: u64, + xch_page_size: u64, +} + +fn parse_xen_dumpcore_elfnote_header_desc( + elf: &ElfBytes, +) -> Result, ParseError> { + let mut xen_notes = elf + .section_header_by_name(".note.Xen")? + .expect("xen notes not found"); + + // Xen stores the notes in a section with alignment 0, which is not + // allowed by the ELF specification. We set the alignment to 1 to + // work around this. + if xen_notes.sh_addralign == 0 { + xen_notes.sh_addralign = 1; + } + + for note in elf.section_data_as_notes(&xen_notes)? { + if let Note::Unknown(note) = note { + const XEN_ELFNOTE_DUMPCORE_HEADER: u64 = 0x2000001; + + // const XC_CORE_MAGIC: u64 = 0xF00FEBED; // for paravirtualized domain + // const XC_CORE_MAGIC_HVM: u64 = 0xF00FEBEE; // for full virtualized domain + + if note.n_type != XEN_ELFNOTE_DUMPCORE_HEADER + || note.desc.len() != size_of::() + { + continue; + } + + // SAFETY: The note.desc is guaranteed to be the correct size. + return Ok(Some( + xen_dumpcore_elfnote_header_desc::ref_from_bytes(note.desc) + .copied() + .unwrap(), + )); + } + } + + Ok(None) +} diff --git a/crates/vmi-driver-xen-core-dump/src/error.rs b/crates/vmi-driver-xen-core-dump/src/error.rs new file mode 100644 index 0000000..1d5c6b6 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/error.rs @@ -0,0 +1,37 @@ +/// Error type for the Xen driver. +pub enum Error { + /// An error occurred while parsing an ELF file. + Elf(elf::ParseError), + + /// An I/O error occurred. + Io(std::io::Error), + + /// Operation not supported. + NotSupported, + + /// Out of bounds. + OutOfBounds, +} + +impl From for Error { + fn from(value: elf::ParseError) -> Self { + Self::Elf(value) + } +} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl From for vmi_core::VmiError { + fn from(value: Error) -> Self { + match value { + Error::Elf(value) => Self::Driver(Box::new(value)), + Error::Io(value) => Self::Io(value), + Error::NotSupported => Self::NotSupported, + Error::OutOfBounds => Self::OutOfBounds, + } + } +} diff --git a/crates/vmi-driver-xen-core-dump/src/lib.rs b/crates/vmi-driver-xen-core-dump/src/lib.rs new file mode 100644 index 0000000..8b045a2 --- /dev/null +++ b/crates/vmi-driver-xen-core-dump/src/lib.rs @@ -0,0 +1,160 @@ +//! VMI driver for Xen core dump. + +mod arch; +mod driver; +mod dump; +mod error; + +use std::{path::Path, time::Duration}; + +use vmi_core::{ + Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiDriver, VmiError, + VmiEvent, VmiEventResponse, VmiInfo, VmiMappedPage, +}; + +pub use self::error::Error; +use self::{arch::ArchAdapter, driver::XenCoreDumpDriver}; + +/// VMI driver for Xen core dump. +pub struct VmiXenCoreDumpDriver +where + Arch: Architecture + ArchAdapter, +{ + inner: XenCoreDumpDriver, +} + +impl VmiXenCoreDumpDriver +where + Arch: Architecture + ArchAdapter, +{ + /// Creates a new VMI driver for Xen core dump. + pub fn new(path: impl AsRef) -> Result { + Ok(Self { + inner: XenCoreDumpDriver::new(path)?, + }) + } +} + +impl VmiDriver for VmiXenCoreDumpDriver +where + Arch: Architecture + ArchAdapter, +{ + type Architecture = Arch; + + fn info(&self) -> Result { + Ok(self.inner.info()?) + } + + fn pause(&self) -> Result<(), VmiError> { + Ok(self.inner.pause()?) + } + + fn resume(&self) -> Result<(), VmiError> { + Ok(self.inner.resume()?) + } + + fn registers(&self, vcpu: VcpuId) -> Result { + Ok(self.inner.registers(vcpu)?) + } + + fn set_registers(&self, vcpu: VcpuId, registers: Arch::Registers) -> Result<(), VmiError> { + Ok(self.inner.set_registers(vcpu, registers)?) + } + + fn memory_access(&self, gfn: Gfn, view: View) -> Result { + Ok(self.inner.memory_access(gfn, view)?) + } + + fn set_memory_access( + &self, + gfn: Gfn, + view: View, + access: MemoryAccess, + ) -> Result<(), VmiError> { + Ok(self.inner.set_memory_access(gfn, view, access)?) + } + + fn set_memory_access_with_options( + &self, + gfn: Gfn, + view: View, + access: MemoryAccess, + options: MemoryAccessOptions, + ) -> Result<(), VmiError> { + Ok(self + .inner + .set_memory_access_with_options(gfn, view, access, options)?) + } + + fn read_page(&self, gfn: Gfn) -> Result { + Ok(self.inner.read_page(gfn)?) + } + + fn write_page(&self, gfn: Gfn, offset: u64, content: &[u8]) -> Result { + Ok(self.inner.write_page(gfn, offset, content)?) + } + + fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.allocate_gfn(gfn)?) + } + + fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.free_gfn(gfn)?) + } + + fn default_view(&self) -> View { + self.inner.default_view() + } + + fn create_view(&self, default_access: MemoryAccess) -> Result { + Ok(self.inner.create_view(default_access)?) + } + + fn destroy_view(&self, view: View) -> Result<(), VmiError> { + Ok(self.inner.destroy_view(view)?) + } + + fn switch_to_view(&self, view: View) -> Result<(), VmiError> { + Ok(self.inner.switch_to_view(view)?) + } + + fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.change_view_gfn(view, old_gfn, new_gfn)?) + } + + fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> { + Ok(self.inner.reset_view_gfn(view, gfn)?) + } + + fn monitor_enable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> { + Ok(self.inner.monitor_enable(option)?) + } + + fn monitor_disable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> { + Ok(self.inner.monitor_disable(option)?) + } + + fn inject_interrupt(&self, vcpu: VcpuId, interrupt: Arch::Interrupt) -> Result<(), VmiError> { + Ok(self.inner.inject_interrupt(vcpu, interrupt)?) + } + + fn events_pending(&self) -> usize { + self.inner.events_pending() + } + + fn event_processing_overhead(&self) -> Duration { + self.inner.event_processing_overhead() + } + + fn wait_for_event( + &self, + timeout: Duration, + handler: impl FnMut(&VmiEvent) -> VmiEventResponse, + ) -> Result<(), VmiError> { + Ok(self.inner.wait_for_event(timeout, handler)?) + } + + fn reset_state(&self) -> Result<(), VmiError> { + Ok(self.inner.reset_state()?) + } +} diff --git a/crates/vmi-driver-xen/Cargo.toml b/crates/vmi-driver-xen/Cargo.toml index 507971a..f85eadc 100644 --- a/crates/vmi-driver-xen/Cargo.toml +++ b/crates/vmi-driver-xen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-driver-xen" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } @@ -9,7 +9,7 @@ rust-version = { workspace = true } homepage = { workspace = true } repository = { workspace = true } -description = "Xen driver for VMI" +description = "VMI driver for Xen hypervisor" keywords = [ "vmi", "xen", diff --git a/crates/vmi-driver-xen/src/arch/mod.rs b/crates/vmi-driver-xen/src/arch/mod.rs index 6fcbd1e..29f1f78 100644 --- a/crates/vmi-driver-xen/src/arch/mod.rs +++ b/crates/vmi-driver-xen/src/arch/mod.rs @@ -6,7 +6,7 @@ use xen::{ctrl::VmEvent, Architecture as XenArchitecture}; use crate::{Error, XenDriver}; /// Architecture-specific adapter for Xen. -pub trait ArchAdapter: Architecture + Sized { +pub trait ArchAdapter: Architecture + Sized + 'static { type XenArch: XenArchitecture; fn registers(driver: &XenDriver, vcpu: VcpuId) -> Result; diff --git a/crates/vmi-driver-xen/src/driver.rs b/crates/vmi-driver-xen/src/driver.rs index 0f00b66..b64bf89 100644 --- a/crates/vmi-driver-xen/src/driver.rs +++ b/crates/vmi-driver-xen/src/driver.rs @@ -15,8 +15,7 @@ use xen::{ XenMonitor, }; -use super::arch::ArchAdapter; -use crate::{Error, IntoExt as _}; +use crate::{ArchAdapter, Error, IntoExt as _}; /// VMI driver for Xen hypervisor. pub struct XenDriver @@ -152,7 +151,7 @@ where return Err(Error::NotSupported); } - xen_access = xen::MemoryAccess::R2PW; + xen_access = xen::MemoryAccess::R_PW; } if view.0 == 0 { diff --git a/crates/vmi-driver-xen/src/error.rs b/crates/vmi-driver-xen/src/error.rs index b846c1f..9260fea 100644 --- a/crates/vmi-driver-xen/src/error.rs +++ b/crates/vmi-driver-xen/src/error.rs @@ -23,16 +23,16 @@ pub enum Error { } impl From for Error { - fn from(error: xen::XenError) -> Self { - Self::Xen(error) + fn from(value: xen::XenError) -> Self { + Self::Xen(value) } } impl From for vmi_core::VmiError { - fn from(error: Error) -> Self { - match error { - Error::Xen(error) => Self::Driver(Box::new(error)), - Error::Io(error) => Self::Io(error), + fn from(value: Error) -> Self { + match value { + Error::Xen(value) => Self::Driver(Box::new(value)), + Error::Io(value) => Self::Io(value), Error::InvalidTimeout => Self::InvalidTimeout, Error::NotSupported => Self::NotSupported, Error::OutOfBounds => Self::OutOfBounds, diff --git a/crates/vmi-macros/Cargo.toml b/crates/vmi-macros/Cargo.toml index 6b7d76d..b2acbea 100644 --- a/crates/vmi-macros/Cargo.toml +++ b/crates/vmi-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-macros" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } diff --git a/crates/vmi-macros/src/common.rs b/crates/vmi-macros/src/common.rs new file mode 100644 index 0000000..fe38444 --- /dev/null +++ b/crates/vmi-macros/src/common.rs @@ -0,0 +1,55 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{FnArg, GenericParam, Lifetime, Pat, PatType, Signature}; + +pub const VMI_LIFETIME: &str = "__vmi"; + +pub fn __vmi_lifetime() -> Lifetime { + syn::parse_str::(&format!("'{VMI_LIFETIME}")).unwrap() +} + +pub fn build_args(sig: &Signature) -> Option<(Vec, Vec)> { + let ident = &sig.ident; + + let mut args = Vec::new(); + let mut arg_names = Vec::new(); + + // Skip the first two arguments (`self` and `Vmi*`). + for arg in sig.inputs.iter().skip(1) { + let PatType { pat, ty, .. } = match arg { + FnArg::Typed(pat_type) => pat_type, + _ => panic!("`{ident}`: argument is not typed, skipping"), + }; + + let pat_ident = match &**pat { + Pat::Ident(pat_ident) => pat_ident, + _ => return None, + }; + + args.push(quote! { #pat_ident: #ty }); + arg_names.push(quote! { #pat_ident }); + } + + Some((args, arg_names)) +} + +pub fn build_where_clause(sig: &Signature) -> Option { + let mut lifetime_params = Vec::new(); + let vmi_lifetime = __vmi_lifetime(); + + for param in &sig.generics.params { + let lifetime = match param { + GenericParam::Lifetime(lifetime) => lifetime, + _ => continue, + }; + + let lifetime = &lifetime.lifetime; + lifetime_params.push(quote! { #vmi_lifetime: #lifetime }); + } + + if lifetime_params.is_empty() { + return None; + } + + Some(quote! { where #(#lifetime_params),* }) +} diff --git a/crates/vmi-macros/src/derive_os_impl.rs b/crates/vmi-macros/src/derive_os_impl.rs index eab3f5f..a753737 100644 --- a/crates/vmi-macros/src/derive_os_impl.rs +++ b/crates/vmi-macros/src/derive_os_impl.rs @@ -1,279 +1,48 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, Error, FnArg, GenericParam, Generics, Ident, ItemImpl, Lifetime, Pat, - PatType, Path, Receiver, Result, Token, Visibility, -}; +use syn::{parse_macro_input, Error, GenericParam, Generics, Ident, ItemImpl, Result, Visibility}; use crate::{ - lifetime, + common::{self, __vmi_lifetime}, method::{FnArgExt, ItemExt, ItemFnExt}, - transform, }; -const VMI_LIFETIME: &str = "__vmi"; - -struct Args { - os_session_name: Path, - os_context_name: Path, - os_session_prober_name: Path, - os_context_prober_name: Path, -} - -impl Parse for Args { - fn parse(input: ParseStream) -> Result { - let mut os_session_name = None; - let mut os_context_name = None; - let mut os_session_prober_name = None; - let mut os_context_prober_name = None; - - while !input.is_empty() { - let ident = input.parse::()?; - let _ = input.parse::()?; - let value = input.parse::()?; - - match ident.to_string().as_str() { - "os_session_name" => os_session_name = Some(value), - "os_context_name" => os_context_name = Some(value), - "os_session_prober_name" => os_session_prober_name = Some(value), - "os_context_prober_name" => os_context_prober_name = Some(value), - _ => return Err(Error::new(ident.span(), "unknown argument")), - } - - if !input.is_empty() { - input.parse::()?; - } - } - - let os_session_name = os_session_name - .ok_or_else(|| Error::new(Span::call_site(), "missing `os_session_name` argument"))?; - - let os_context_name = os_context_name - .ok_or_else(|| Error::new(Span::call_site(), "missing `os_context_name` argument"))?; - - let os_session_prober_name = os_session_prober_name.ok_or_else(|| { - Error::new( - Span::call_site(), - "missing `os_session_prober_name` argument", - ) - })?; - - let os_context_prober_name = os_context_prober_name.ok_or_else(|| { - Error::new( - Span::call_site(), - "missing `os_context_prober_name` argument", - ) - })?; - - Ok(Self { - os_session_name, - os_context_name, - os_session_prober_name, - os_context_prober_name, - }) - } -} - struct TraitFn { - os_session_sig: TokenStream, - os_session_fn: TokenStream, - os_context_sig: TokenStream, os_context_fn: TokenStream, - - os_session_prober_sig: TokenStream, - os_session_prober_fn: TokenStream, - - os_context_prober_sig: TokenStream, - os_context_prober_fn: TokenStream, } -fn generate_trait_fn(item_fn: impl ItemFnExt, skip: usize) -> Option { +fn generate_trait_fn(item_fn: impl ItemFnExt) -> Option { let sig = item_fn.sig(); let ident = &sig.ident; - let lifetime_of_self = sig - .receiver() - .and_then(Receiver::lifetime) - .map(|lifetime| lifetime.ident.to_string()); - let mut generics = sig.generics.clone(); - - if let Some(lifetime_of_self) = &lifetime_of_self { - lifetime::remove_in_generics(&mut generics, lifetime_of_self); - } + let generics = &sig.generics; + let return_type = &sig.output; + //let receiver = sig.receiver()?; - let (maybe_core, maybe_registers) = match skip { - // Skip the first argument (`self`). - 1 => (quote! {}, quote! {}), - - // Skip the first two arguments (`self` and `&VmiCore`). - 2 => (quote! { self.core(), }, quote! {}), - - // Skip the first three arguments (`self`, `&VmiCore`, and - // `&::Registers`). - 3 => ( - quote! { self.core(), }, - quote! { self.event().registers(), }, - ), - - // This should never happen. - _ => panic!("unexpected number of arguments to skip"), - }; - - let mut session_args = Vec::new(); - let mut session_arg_names = Vec::new(); - - for arg in sig.inputs.clone().iter_mut().skip(skip.min(2)) { - // If a lifetime has been applied to `self`, and the argument - // is a reference type, then we need to check if the reference - // type has a lifetime that matches the lifetime applied to `self`. - // - // If it does, then we need to replace that lifetime with a `__vmi` - // lifetime instead. - if let Some(lifetime_of_self) = &lifetime_of_self { - lifetime::replace_in_fn_arg(arg, lifetime_of_self, VMI_LIFETIME); - } - - match arg { - FnArg::Typed(PatType { pat, ty, .. }) => { - let pat_ident = match &**pat { - Pat::Ident(pat_ident) => pat_ident, - _ => return None, - }; - - session_args.push(quote! { #pat_ident: #ty }); - session_arg_names.push(quote! { #pat_ident }); - } - _ => panic!("`{ident}`: argument is not typed, skipping"), - } - } - - let (context_args, context_arg_names) = match skip { - 3 => (&session_args[1..], &session_arg_names[1..]), - _ => (&session_args[..], &session_arg_names[..]), - }; - - let mut return_type = sig.output.clone(); - - // If a lifetime has been applied to `self`, and the return type - // is an `impl Trait` type, then we need to check if the `impl Trait` - // type has a lifetime that matches the lifetime applied to `self`. - // - // If it does, then we need to replace that lifetime with a `__vmi` - // lifetime instead. - if let Some(lifetime_of_self) = &lifetime_of_self { - lifetime::replace_in_return_type(&mut return_type, lifetime_of_self, VMI_LIFETIME); - } - - let prober_return_type = transform::result_to_result_option(&return_type); - - // Generate the implementation for `VmiOsSession`. - let doc = item_fn.doc(); - let os_session_sig = quote! { - #(#doc)* - fn #ident #generics(&self, #(#session_args),*) #return_type; - }; - - let doc = item_fn.doc(); - let os_session_fn = quote! { - #(#doc)* - fn #ident #generics(&self, #(#session_args),*) #return_type { - self.underlying_os().#ident( - #maybe_core - #(#session_arg_names),* - ) - } - }; + let (args, arg_names) = common::build_args(sig)?; + let where_clause = common::build_where_clause(sig); // Generate the implementation for `VmiOsContext`. let doc = item_fn.doc(); let os_context_sig = quote! { #(#doc)* - fn #ident #generics(&self, #(#context_args),*) #return_type; + fn #ident #generics(&self, #(#args),*) #return_type + #where_clause; }; let doc = item_fn.doc(); let os_context_fn = quote! { #(#doc)* - fn #ident #generics(&self, #(#context_args),*) #return_type { - self.underlying_os().#ident( - #maybe_core - #maybe_registers - #(#context_arg_names),* - ) - } - }; - - // Generate the implementation for `VmiOsSessionProber`. - let (os_session_prober_sig, os_session_prober_fn) = match &prober_return_type { - Some(prober_return_type) => { - let doc = item_fn.doc(); - let os_session_prober_sig = quote! { - #(#doc)* - fn #ident #generics(&self, #(#session_args),*) #prober_return_type; - }; - - let doc = item_fn.doc(); - let os_session_prober_fn = quote! { - #(#doc)* - fn #ident #generics(&self, #(#session_args),*) #prober_return_type { - self.core().check_result( - self.core() // -> &VmiSessionProber - .underlying_os() // -> &Os - .#ident( - #maybe_core - #(#session_arg_names),* - ), - ) - } - }; - - (os_session_prober_sig, os_session_prober_fn) - } - - None => (os_session_sig.clone(), os_session_fn.clone()), - }; - - // Generate the implementation for `VmiOsContextProber`. - let (os_context_prober_sig, os_context_prober_fn) = match &prober_return_type { - Some(prober_return_type) => { - let doc = item_fn.doc(); - let os_context_prober_sig = quote! { - #(#doc)* - fn #ident #generics(&self, #(#context_args),*) #prober_return_type; - }; - - let doc = item_fn.doc(); - let os_context_prober_fn = quote! { - #(#doc)* - fn #ident #generics(&self, #(#context_args),*) #prober_return_type { - self.core().check_result( - self.core() // -> &VmiContextProber - .underlying_os() // -> &Os - .#ident( - #maybe_core - #maybe_registers - #(#context_arg_names),* - ), - ) - } - }; - - (os_context_prober_sig, os_context_prober_fn) + fn #ident #generics(&self, #(#args),*) #return_type + #where_clause + { + <::Os>::#ident(self.state(), #(#arg_names),*) } - - None => (os_context_sig.clone(), os_context_fn.clone()), }; Some(TraitFn { - os_session_sig, - os_session_fn, os_context_sig, os_context_fn, - os_session_prober_sig, - os_session_prober_fn, - os_context_prober_sig, - os_context_prober_fn, }) } @@ -289,28 +58,18 @@ fn transform_fn_to_trait_fn(item_fn: impl ItemFnExt) -> Option { let mut inputs = sig.inputs.iter(); // First argument must be a receiver (`self`). - inputs.next()?.receiver()?; + //inputs.next()?.receiver()?; // Second argument _might_ be of type `&VmiCore`. if !inputs .next() - .map(|fn_arg| fn_arg.contains("VmiCore")) + .map(|fn_arg| fn_arg.contains("VmiState")) .unwrap_or(false) { - return generate_trait_fn(item_fn, 1); + return None; } - // Third argument _might_ be of type `&::Registers`. - // If it is, include it in the trait method signature. - if !inputs - .next() - .map(|fn_arg| fn_arg.contains("Registers")) - .unwrap_or(false) - { - return generate_trait_fn(item_fn, 2); - } - - generate_trait_fn(item_fn, 3) + generate_trait_fn(item_fn) } fn verify_generics(generics: &Generics) -> Result<()> { @@ -344,12 +103,7 @@ pub fn derive_trait_from_impl( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let Args { - os_session_name, - os_context_name, - os_session_prober_name, - os_context_prober_name, - } = parse_macro_input!(args as Args); + let os_context_name = parse_macro_input!(args as Ident); let input = parse_macro_input!(item as ItemImpl); match verify_generics(&input.generics) { @@ -371,56 +125,25 @@ pub fn derive_trait_from_impl( .filter_map(filter_pub) .filter_map(transform_fn_to_trait_fn); - let os_session_sigs = fns.clone().map(|m| m.os_session_sig); - let os_session_fns = fns.clone().map(|m| m.os_session_fn); let os_context_sigs = fns.clone().map(|m| m.os_context_sig); let os_context_fns = fns.clone().map(|m| m.os_context_fn); - let os_session_prober_sigs = fns.clone().map(|m| m.os_session_prober_sig); - let os_session_prober_fns = fns.clone().map(|m| m.os_session_prober_fn); - let os_context_prober_sigs = fns.clone().map(|m| m.os_context_prober_sig); - let os_context_prober_fns = fns.clone().map(|m| m.os_context_prober_fn); - let lt = syn::parse_str::(&format!("'{VMI_LIFETIME}")).unwrap(); + let vmi_lifetime = __vmi_lifetime(); let expanded = quote! { #input - // - // OS Session - // - - #[doc = concat!("[`", #struct_type_raw, "`] extensions for the [`VmiSession`].")] - #[doc = ""] - #[doc = "[`VmiSession`]: vmi_core::VmiSession"] - pub trait #os_session_name <#lt, Driver> - #where_clause - { - #(#os_session_sigs)* - } - - impl<#lt, Driver> #os_session_name <#lt, Driver> for vmi_core::VmiOsSession<#lt, Driver, #struct_type> - #where_clause - { - #(#os_session_fns)* + // A helper trait to expose the Os type from VmiOsState + trait VmiOsStateExt { + type Os; } - // - // OS Session Prober - // - - #[doc = concat!("[`", #struct_type_raw, "`] extensions for the [`VmiSessionProber`].")] - #[doc = ""] - #[doc = "[`VmiSessionProber`]: vmi_core::VmiSessionProber"] - pub trait #os_session_prober_name <#lt, Driver> - #where_clause - { - #(#os_session_prober_sigs)* - } - - impl<#lt, Driver> #os_session_prober_name <#lt, Driver> for vmi_core::VmiOsSessionProber<#lt, Driver, #struct_type> - #where_clause + impl<'__vmi, TDriver, TOs> VmiOsStateExt for vmi_core::VmiOsState<'__vmi, TDriver, TOs> + where + TDriver: VmiDriver, + TOs: VmiOs, { - #(#os_session_prober_fns)* + type Os = TOs; } // @@ -430,36 +153,18 @@ pub fn derive_trait_from_impl( #[doc = concat!("[`", #struct_type_raw, "`] extensions for the [`VmiContext`].")] #[doc = ""] #[doc = "[`VmiContext`]: vmi_core::VmiContext"] - pub trait #os_context_name <#lt, Driver> + pub trait #os_context_name <#vmi_lifetime, Driver> #where_clause { #(#os_context_sigs)* } - impl<#lt, Driver> #os_context_name <#lt, Driver> for vmi_core::VmiOsContext<#lt, Driver, #struct_type> + impl<#vmi_lifetime, Driver> #os_context_name <#vmi_lifetime, Driver> + for vmi_core::VmiOsState<#vmi_lifetime, Driver, #struct_type> #where_clause { #(#os_context_fns)* } - - // - // OS Context Prober - // - - #[doc = concat!("[`", #struct_type_raw, "`] extensions for the [`VmiContextProber`].")] - #[doc = ""] - #[doc = "[`VmiContextProber`]: vmi_core::VmiContextProber"] - pub trait #os_context_prober_name <#lt, Driver> - #where_clause - { - #(#os_context_prober_sigs)* - } - - impl<#lt, Driver> #os_context_prober_name <#lt, Driver> for vmi_core::VmiOsContextProber<#lt, Driver, #struct_type> - #where_clause - { - #(#os_context_prober_fns)* - } }; proc_macro::TokenStream::from(expanded) diff --git a/crates/vmi-macros/src/derive_os_trait.rs b/crates/vmi-macros/src/derive_os_trait.rs index 82ede5e..487c868 100644 --- a/crates/vmi-macros/src/derive_os_trait.rs +++ b/crates/vmi-macros/src/derive_os_trait.rs @@ -1,236 +1,43 @@ -use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - spanned::Spanned as _, - Error, FnArg, Ident, ItemTrait, Pat, PatType, Path, PathArguments, Result, ReturnType, Token, - Type, +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Ident, ItemTrait}; + +use crate::{ + common::{self, __vmi_lifetime}, + method::{FnArgExt, ItemExt, ItemFnExt}, + transform, }; -use crate::method::{FnArgExt, ItemExt, ItemFnExt}; - -struct Args { - os_session_name: Path, - os_context_name: Path, - os_session_prober_name: Path, - os_context_prober_name: Path, -} - -impl Parse for Args { - fn parse(input: ParseStream) -> Result { - let mut os_session_name = None; - let mut os_context_name = None; - let mut os_session_prober_name = None; - let mut os_context_prober_name = None; - - while !input.is_empty() { - let ident = input.parse::()?; - let _ = input.parse::()?; - let value = input.parse::()?; - - match ident.to_string().as_str() { - "os_session_name" => os_session_name = Some(value), - "os_context_name" => os_context_name = Some(value), - "os_session_prober_name" => os_session_prober_name = Some(value), - "os_context_prober_name" => os_context_prober_name = Some(value), - _ => return Err(Error::new(ident.span(), "unknown argument")), - } - - if !input.is_empty() { - input.parse::()?; - } - } - - let os_session_name = os_session_name - .ok_or_else(|| Error::new(Span::call_site(), "missing `os_session_name` argument"))?; - - let os_context_name = os_context_name - .ok_or_else(|| Error::new(Span::call_site(), "missing `os_context_name` argument"))?; - - let os_session_prober_name = os_session_prober_name.ok_or_else(|| { - Error::new( - Span::call_site(), - "missing `os_session_prober_name` argument", - ) - })?; - - let os_context_prober_name = os_context_prober_name.ok_or_else(|| { - Error::new( - Span::call_site(), - "missing `os_context_prober_name` argument", - ) - })?; - - Ok(Self { - os_session_name, - os_context_name, - os_session_prober_name, - os_context_prober_name, - }) - } -} - struct TraitFn { - os_session_fn: TokenStream, os_context_fn: TokenStream, - os_session_prober_fn: TokenStream, - os_context_prober_fn: TokenStream, -} - -/// Transform the return type from `Result` to -/// `Result, VmiError>`. -fn transform_return_type(return_type: &ReturnType) -> TokenStream { - let ty = match &return_type { - ReturnType::Type(_, ty) => ty, - ReturnType::Default => { - return quote_spanned! { - return_type.span() => - compile_error!("expected return type `Result`") - } - } - }; - - let type_path = match &**ty { - Type::Path(type_path) => type_path, - _ => { - return quote_spanned! { - ty.span() => compile_error!("expected path type") - } - } - }; - - let segment = match type_path.path.segments.last() { - Some(segment) => segment, - None => { - return quote_spanned! { - ty.span() => compile_error!("expected path segment") - } - } - }; - - if segment.ident != "Result" { - return quote_spanned! { - ty.span() => compile_error!("expected `Result` type"); - }; - } - - let args = match &segment.arguments { - PathArguments::AngleBracketed(args) => args, - _ => { - return quote_spanned! { - ty.span() => compile_error!("expected angle-bracketed arguments in `Result` type"); - } - } - }; - - if args.args.len() != 2 { - return quote_spanned! { - args.span() => compile_error!("expected two arguments in `Result` type"); - }; - } - - let t = &args.args[0]; - quote! { -> Result, VmiError> } } fn generate_impl_fns(item_fn: impl ItemFnExt) -> Option { let sig = item_fn.sig(); let ident = &sig.ident; let generics = &sig.generics; - - let mut session_args = Vec::new(); - let mut session_arg_names = Vec::new(); - - // Skip the first two arguments (`self` and `&VmiCore`). - for arg in sig.inputs.iter().skip(2) { - match arg { - FnArg::Typed(PatType { pat, ty, .. }) => { - let pat_ident = match &**pat { - Pat::Ident(pat_ident) => pat_ident, - _ => { - eprintln!("`{ident}`: argument is not an identifier, skipping"); - return None; - } - }; - - session_args.push(quote! { #pat_ident: #ty }); - session_arg_names.push(quote! { #pat_ident }); - } - _ => { - eprintln!("`{ident}`: argument is not typed, skipping"); - return None; - } - } - } - - // For the context functions, we need to skip the - // `&::Registers`, - // since they are provided by the `event` in the `VmiContext`. - let context_args = &session_args[1..]; - let context_arg_names = &session_arg_names[1..]; - - // Transform the return type from `Result` to - // `Result, VmiError>`. let return_type = &sig.output; - let prober_return_type = transform_return_type(return_type); + //let receiver = sig.receiver()?; - // Generate the implementation for `VmiOsSession`. - let doc = item_fn.doc(); - let os_session_fn = quote! { - #(#doc)* - pub fn #ident #generics(&self, #(#session_args),*) #return_type { - self.os.#ident(self.core, #(#session_arg_names),*) - } - }; + let (args, arg_names) = common::build_args(sig)?; + let where_clause = common::build_where_clause(sig); + + // Replace `Self` with `Os` in the return type. + let mut return_type = return_type.clone(); + transform::replace_self_with_os(&mut return_type); // Generate the implementation for `VmiOsContext`. let doc = item_fn.doc(); let os_context_fn = quote! { #(#doc)* - pub fn #ident #generics(&self, #(#context_args),*) #return_type { - self.session.os.#ident( - &self.session, - self.event.registers(), - #(#context_arg_names),* - ) - } - }; - - // Generate the implementation for `VmiOsSessionProber`. - let doc = item_fn.doc(); - let os_session_prober_fn = quote! { - #(#doc)* - pub fn #ident #generics(&self, #(#session_args),*) #prober_return_type { - self.0.check_result( - self.0 - .session - .os() - .#ident(#(#session_arg_names),*), - ) - } - }; - - // Generate the implementation for `VmiOsContextProber`. - let doc = item_fn.doc(); - let os_context_prober_fn = quote! { - #(#doc)* - pub fn #ident #generics(&self, #(#context_args),*) #prober_return_type { - self.0.check_result( - self.0 - .context - .os() - .#ident(#(#context_arg_names),*), - ) + pub fn #ident #generics(&self, #(#args),*) #return_type + #where_clause + { + Os::#ident(self.state(), #(#arg_names),*) } }; - Some(TraitFn { - os_session_fn, - os_context_fn, - os_session_prober_fn, - os_context_prober_fn, - }) + Some(TraitFn { os_context_fn }) } fn transform_fn_to_trait_fn(item_fn: impl ItemFnExt) -> Option { @@ -238,15 +45,10 @@ fn transform_fn_to_trait_fn(item_fn: impl ItemFnExt) -> Option { let mut inputs = sig.inputs.iter(); // First argument must be a receiver (`self`). - inputs.next()?.receiver()?; - - // Second argument must be of type `&VmiCore`. - if !inputs.next()?.contains("VmiCore") { - return None; - } + // inputs.next()?.receiver()?; - // Third argument must be of type `&::Registers`. - if !inputs.next()?.contains("Registers") { + // Second argument must be of type `Vmi*`. + if !inputs.next()?.contains("VmiState") { return None; } @@ -258,12 +60,7 @@ pub fn derive_os_wrapper( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let Args { - os_session_name, - os_context_name, - os_session_prober_name, - os_context_prober_name, - } = parse_macro_input!(args as Args); + let os_context_name = parse_macro_input!(args as Ident); let input = parse_macro_input!(item as ItemTrait); let trait_name = &input.ident; let trait_methods = input @@ -272,62 +69,25 @@ pub fn derive_os_wrapper( .filter_map(ItemExt::as_fn) .filter_map(transform_fn_to_trait_fn); - let os_session_methods = trait_methods.clone().map(|m| m.os_session_fn); let os_context_methods = trait_methods.clone().map(|m| m.os_context_fn); - let os_session_prober_methods = trait_methods.clone().map(|m| m.os_session_prober_fn); - let os_context_prober_methods = trait_methods.clone().map(|m| m.os_context_prober_fn); + + let vmi_lifetime = __vmi_lifetime(); // Generate the wrapper struct and its implementation let expanded = quote! { #input - // - // OS Session - // - - impl #os_session_name<'_, Driver, Os> - where - Driver: crate::VmiDriver, - Os: #trait_name, - { - #(#os_session_methods)* - } - - // - // OS Session Prober - // - - impl #os_session_prober_name<'_, Driver, Os> - where - Driver: crate::VmiDriver, - Os: #trait_name, - { - #(#os_session_prober_methods)* - } - // // OS Context // - impl #os_context_name<'_, Driver, Os> + impl<#vmi_lifetime, Driver, Os> #os_context_name<#vmi_lifetime, Driver, Os> where Driver: crate::VmiDriver, Os: #trait_name, { #(#os_context_methods)* } - - // - // OS Context Prober - // - - impl #os_context_prober_name<'_, Driver, Os> - where - Driver: crate::VmiDriver, - Os: #trait_name, - { - #(#os_context_prober_methods)* - } }; proc_macro::TokenStream::from(expanded) diff --git a/crates/vmi-macros/src/lib.rs b/crates/vmi-macros/src/lib.rs index 15cc037..0e018a2 100644 --- a/crates/vmi-macros/src/lib.rs +++ b/crates/vmi-macros/src/lib.rs @@ -1,8 +1,8 @@ //! Procedural macros for the `vmi` crate. +mod common; mod derive_os_impl; mod derive_os_trait; -mod lifetime; mod method; mod transform; diff --git a/crates/vmi-macros/src/lifetime.rs b/crates/vmi-macros/src/lifetime.rs deleted file mode 100644 index 13308b1..0000000 --- a/crates/vmi-macros/src/lifetime.rs +++ /dev/null @@ -1,316 +0,0 @@ -use syn::{ - AngleBracketedGenericArguments, FnArg, GenericArgument, GenericParam, Generics, Ident, - Lifetime, PathArguments, ReturnType, Type, TypeImplTrait, TypeParamBound, TypePath, - TypeReference, -}; - -pub fn remove_in_generics(generics: &mut Generics, lifetime: &str) { - generics.params = generics - .params - .iter() - .filter(|¶m| match param { - GenericParam::Lifetime(lt) => lt.lifetime.ident != lifetime, - _ => true, - }) - .cloned() - .collect(); -} - -/// Replace the lifetime `'from` with `'to` in the given lifetime. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// 'from -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// 'to -/// ``` -pub fn replace_in_lifetime(lifetime: &mut Lifetime, from: &str, to: &str) { - if lifetime.ident != from { - return; - } - - lifetime.ident = Ident::new(to, lifetime.ident.span()); -} - -/// Replace the lifetime `'from` with `'to` in the given angle-bracketed generic arguments. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// <'from, T> -/// -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// <'to, T> -/// -/// ``` -pub fn replace_in_angle_bracketed_generic_arguments( - args: &mut AngleBracketedGenericArguments, - from: &str, - to: &str, -) { - for arg in &mut args.args { - match arg { - GenericArgument::Lifetime(lifetime) => { - replace_in_lifetime(lifetime, from, to); - } - GenericArgument::Type(ty) => replace_in_type(ty, from, to), - GenericArgument::Const(_) => {} - GenericArgument::AssocType(assoc_type) => { - if let Some(generics) = &mut assoc_type.generics { - replace_in_angle_bracketed_generic_arguments(generics, from, to); - } - replace_in_type(&mut assoc_type.ty, from, to); - } - GenericArgument::AssocConst(_) => {} - GenericArgument::Constraint(constraint) => { - if let Some(generics) = &mut constraint.generics { - replace_in_angle_bracketed_generic_arguments(generics, from, to); - } - - for bound in &mut constraint.bounds { - replace_in_type_param_bound(bound, from, to); - } - } - _ => panic!("unexpected generic argument"), - } - } -} - -/// Replace the lifetime `'from` with `'to` in the given generic parameter. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// 'a: 'from -/// 'from: 'b -/// T: 'from -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// 'a: 'to -/// 'to: 'b -/// T: 'to -/// ``` -pub fn replace_in_generic_param(param: &mut GenericParam, from: &str, to: &str) { - match param { - GenericParam::Lifetime(lifetime) => { - replace_in_lifetime(&mut lifetime.lifetime, from, to); - - for bound in &mut lifetime.bounds { - replace_in_lifetime(bound, from, to); - } - } - GenericParam::Type(ty) => { - for bound in &mut ty.bounds { - replace_in_type_param_bound(bound, from, to); - } - - if let Some(default) = &mut ty.default { - replace_in_type(default, from, to); - } - } - _ => {} - } -} - -/// Replace the lifetime `'from` with `'to` in the given type parameter bound. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// -pub fn replace_in_type_param_bound(bound: &mut TypeParamBound, from: &str, to: &str) { - match bound { - TypeParamBound::Trait(trait_bound) => { - if let Some(lifetimes) = &mut trait_bound.lifetimes { - for param in lifetimes.lifetimes.iter_mut() { - replace_in_generic_param(param, from, to); - } - } - } - TypeParamBound::Lifetime(lifetime) => replace_in_lifetime(lifetime, from, to), - _ => panic!("unexpected type parameter bound"), - } -} - -/// Replace the lifetime `'from` with `'to` in the given type path. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// Foo<'from, T> -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// Foo<'to, T> -/// ``` -pub fn replace_in_type_path(type_path: &mut TypePath, from: &str, to: &str) { - let segment = match type_path.path.segments.last_mut() { - Some(segment) => segment, - None => return, - }; - - let args = match &mut segment.arguments { - PathArguments::AngleBracketed(args) => args, - _ => return, - }; - - replace_in_angle_bracketed_generic_arguments(args, from, to); -} - -/// Replace the lifetime `'from` with `'to` in the given type reference. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// &'from T<'from> -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// &'to T<'to> -/// ``` -pub fn replace_in_type_reference(ty_ref: &mut TypeReference, from: &str, to: &str) { - replace_in_type(&mut ty_ref.elem, from, to); - - if let Some(lifetime) = &mut ty_ref.lifetime { - replace_in_lifetime(lifetime, from, to); - } -} - -/// Replace the lifetime `'from` with `'to` in the given function argument. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// arg: &'from T -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// arg: &'to T -/// ``` -pub fn replace_in_fn_arg(arg: &mut FnArg, from: &str, to: &str) { - let pat_type = match arg { - FnArg::Typed(pat_type) => pat_type, - _ => return, - }; - - #[allow(clippy::single_match, clippy::needless_return)] - match pat_type.ty.as_mut() { - Type::Reference(ty_ref) => replace_in_type_reference(ty_ref, from, to), - _ => return, - } -} - -/// Replace the lifetime `'from` with `'to` in the given type impl trait. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// impl Trait + 'from -/// impl Trait -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// impl Trait + 'to -/// impl Trait -/// ``` -pub fn replace_in_type_impl_trait(impl_trait: &mut TypeImplTrait, from: &str, to: &str) { - for bound in &mut impl_trait.bounds { - // TODO: Handle `TypeParamBound::TraitBound` - if let TypeParamBound::Lifetime(lifetime) = bound { - replace_in_lifetime(lifetime, from, to); - } - } -} - -/// Replace the lifetime `'from` with `'to` in the given type. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// Foo<'from, T> -/// impl Trait + 'from -/// // impl Trait // TODO: Handle this case -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// Foo<'to, T> -/// impl Trait + 'to -/// // impl Trait // TODO: Handle this case -/// ``` -pub fn replace_in_type(ty: &mut Type, from: &str, to: &str) { - match ty { - Type::ImplTrait(impl_trait) => replace_in_type_impl_trait(impl_trait, from, to), - Type::Path(type_path) => replace_in_type_path(type_path, from, to), - _ => {} - }; -} - -/// Replace the lifetime `'from` with `'to` in the given return type. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// -> &'from T -/// -> Result<&'from T, VmiError> -/// -> Result, VmiError> -/// -> impl Trait + 'from -/// -> Result -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// -> &'to T -/// -> Result<&'to T, VmiError> -/// -> Result, VmiError> -/// -> impl Trait + 'to -/// -> Result -/// ``` -pub fn replace_in_return_type(return_type: &mut ReturnType, from: &str, to: &str) { - match return_type { - ReturnType::Type(_, ty) => replace_in_type(ty, from, to), - ReturnType::Default => {} - } -} diff --git a/crates/vmi-macros/src/method.rs b/crates/vmi-macros/src/method.rs index 79e8d0a..dbb06b2 100644 --- a/crates/vmi-macros/src/method.rs +++ b/crates/vmi-macros/src/method.rs @@ -1,7 +1,7 @@ use quote::quote; use syn::{ - Attribute, FnArg, ImplItem, ImplItemFn, PatType, Receiver, Signature, TraitItem, TraitItemFn, - Type, TypeReference, Visibility, + Attribute, FnArg, ImplItem, ImplItemFn, PatType, Signature, TraitItem, TraitItemFn, Type, + TypeReference, Visibility, }; /// Trait for items. @@ -69,7 +69,6 @@ impl ItemFnExt for &TraitItemFn { } pub trait FnArgExt { - fn receiver(&self) -> Option<&Receiver>; fn typed(&self) -> Option<&PatType>; fn contains(&self, needle: &str) -> bool; } @@ -80,13 +79,6 @@ pub trait TypeExt { } impl FnArgExt for FnArg { - fn receiver(&self) -> Option<&Receiver> { - match self { - FnArg::Receiver(receiver) => Some(receiver), - _ => None, - } - } - fn typed(&self) -> Option<&PatType> { match self { FnArg::Typed(typed) => Some(typed), @@ -100,12 +92,10 @@ impl FnArgExt for FnArg { None => return false, }; - let ty_ref = match ty.reference() { - Some(ty_ref) => ty_ref, - None => return false, - }; - - ty_ref.elem.contains(needle) + match ty.reference() { + Some(ty_ref) => ty_ref.elem.contains(needle), + None => ty.contains(needle), + } } } diff --git a/crates/vmi-macros/src/transform.rs b/crates/vmi-macros/src/transform.rs index 25a782d..3badeb9 100644 --- a/crates/vmi-macros/src/transform.rs +++ b/crates/vmi-macros/src/transform.rs @@ -1,55 +1,101 @@ -use syn::{GenericArgument, PathArguments, ReturnType, Type}; - -/// Transform the return type from `Result` to -/// `Result, VmiError>`. -/// -/// # Example -/// -/// Before: -/// -/// ```rust,ignore -/// -> Result -/// -> Result -/// -> Result + 'a, VmiError> -/// ``` -/// -/// After: -/// -/// ```rust,ignore -/// -> Result, VmiError> -/// -> Result, VmiError> -/// -> Result + 'a>, VmiError> -/// ``` -pub fn result_to_result_option(return_type: &ReturnType) -> Option { - let ty = match &return_type { - ReturnType::Type(_, ty) => ty, - ReturnType::Default => return None, - }; +use syn::{ + AngleBracketedGenericArguments, GenericArgument, Ident, Path, PathArguments, ReturnType, + TraitBound, Type, TypeImplTrait, TypeParamBound, TypePath, TypeReference, +}; - let type_path = match &**ty { - Type::Path(type_path) => type_path, - _ => return None, - }; +pub fn replace_self_with_os(return_type: &mut ReturnType) { + replace_in_return_type(return_type); +} - let segment = type_path.path.segments.last()?; +fn replace_in_return_type(return_type: &mut ReturnType) { + match return_type { + ReturnType::Type(_, ty) => replace_in_type(ty), + ReturnType::Default => {} + } +} - if segment.ident != "Result" { - return None; +fn replace_in_path(path: &mut Path) { + for segment in &mut path.segments { + // TODO: Handle `PathArguments::Parenthesized` + if let PathArguments::AngleBracketed(args) = &mut segment.arguments { + replace_in_angle_bracketed_generic_arguments(args); + } } - let args = match &segment.arguments { - PathArguments::AngleBracketed(args) => args, - _ => return None, + let segment = match path.segments.first_mut() { + Some(segment) => segment, + None => return, }; - if args.args.len() != 2 { - return None; + if segment.ident != "Self" { + return; } - let arg_type = match &args.args[0] { - GenericArgument::Type(arg_type) => arg_type, - _ => return None, + segment.ident = Ident::new("Os", segment.ident.span()); +} + +fn replace_in_angle_bracketed_generic_arguments(args: &mut AngleBracketedGenericArguments) { + for arg in &mut args.args { + match arg { + GenericArgument::Lifetime(_) => {} + GenericArgument::Type(ty) => replace_in_type(ty), + GenericArgument::Const(_) => {} + GenericArgument::AssocType(assoc_type) => { + if let Some(generics) = &mut assoc_type.generics { + replace_in_angle_bracketed_generic_arguments(generics); + } + replace_in_type(&mut assoc_type.ty); + } + GenericArgument::AssocConst(_) => {} + GenericArgument::Constraint(constraint) => { + if let Some(generics) = &mut constraint.generics { + replace_in_angle_bracketed_generic_arguments(generics); + } + + for bound in &mut constraint.bounds { + replace_in_type_param_bound(bound); + } + } + _ => panic!("unexpected generic argument"), + } + } +} + +fn replace_in_type(ty: &mut Type) { + match ty { + Type::Reference(ty_ref) => replace_in_type_reference(ty_ref), + Type::ImplTrait(impl_trait) => replace_in_type_impl_trait(impl_trait), + Type::Path(type_path) => replace_in_type_path(type_path), + _ => {} }; +} + +fn replace_in_type_param_bound(bound: &mut TypeParamBound) { + match bound { + TypeParamBound::Trait(trait_bound) => replace_in_trait_bound(trait_bound), + TypeParamBound::Lifetime(_) => {} + _ => panic!("unexpected type parameter bound"), + } +} + +fn replace_in_type_reference(ty_ref: &mut TypeReference) { + replace_in_type(&mut ty_ref.elem); +} - Some(syn::parse_quote! { -> Result, VmiError> }) +fn replace_in_type_path(type_path: &mut TypePath) { + replace_in_path(&mut type_path.path); +} + +fn replace_in_trait_bound(trait_bound: &mut TraitBound) { + replace_in_path(&mut trait_bound.path); +} + +fn replace_in_type_impl_trait(impl_trait: &mut TypeImplTrait) { + for bound in &mut impl_trait.bounds { + #[expect(clippy::single_match)] + match bound { + TypeParamBound::Trait(trait_bound) => replace_in_trait_bound(trait_bound), + _ => {} + } + } } diff --git a/crates/vmi-os-linux/Cargo.toml b/crates/vmi-os-linux/Cargo.toml index 85c71d6..0e13b4c 100644 --- a/crates/vmi-os-linux/Cargo.toml +++ b/crates/vmi-os-linux/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-os-linux" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } @@ -19,6 +19,8 @@ workspace = true [dependencies] memchr = { workspace = true } +once_cell = { workspace = true } +thiserror = { workspace = true } tracing = { workspace = true } isr-core = { workspace = true } diff --git a/crates/vmi-os-linux/src/arch/amd64.rs b/crates/vmi-os-linux/src/arch/amd64.rs index 2843a68..4d5789f 100644 --- a/crates/vmi-os-linux/src/arch/amd64.rs +++ b/crates/vmi-os-linux/src/arch/amd64.rs @@ -1,58 +1,50 @@ -use vmi_arch_amd64::{Amd64, Registers}; -use vmi_core::{Architecture as _, Registers as _, Va, VmiCore, VmiDriver, VmiError}; +use vmi_arch_amd64::Amd64; +use vmi_core::{Architecture, Registers as _, Va, VmiCore, VmiDriver, VmiError, VmiState}; use super::ArchAdapter; use crate::LinuxOs; -#[allow(non_snake_case)] +#[expect(non_snake_case)] impl ArchAdapter for Amd64 where Driver: VmiDriver, { fn syscall_argument( - _os: &LinuxOs, - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result { match index { - 0 => Ok(registers.r10), - 1 => Ok(registers.rdx), - 2 => Ok(registers.r8), - 3 => Ok(registers.r9), + 0 => Ok(vmi.registers().r10), + 1 => Ok(vmi.registers().rdx), + 2 => Ok(vmi.registers().r8), + 3 => Ok(vmi.registers().r9), _ => { let index = index + 1; - let stack = registers.rsp + index * size_of::() as u64; - vmi.read_u64(registers.address_context(stack.into())) + let stack = vmi.registers().rsp + index * size_of::() as u64; + vmi.read_u64(stack.into()) } } } fn function_argument( - _os: &LinuxOs, - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result { - if registers.cs.access.long_mode() { - function_argument_x64(vmi, registers, index) + if vmi.registers().cs.access.long_mode() { + function_argument_x64(vmi, index) } else { - function_argument_x86(vmi, registers, index) + function_argument_x86(vmi, index) } } - fn function_return_value( - _os: &LinuxOs, - _vmi: &VmiCore, - registers: &Registers, - ) -> Result { - Ok(registers.rax) + fn function_return_value(vmi: VmiState>) -> Result { + Ok(vmi.registers().rax) } fn find_banner( vmi: &VmiCore, - registers: &Registers, + registers: &::Registers, ) -> Result, VmiError> { /// Maximum backward search distance for the kernel image base. const MAX_FORWARD_SEARCH: u64 = 16 * 1024 * 1024; @@ -76,7 +68,7 @@ where match vmi.read(registers.address_context(va), &mut data) { Ok(()) => {} - Err(VmiError::PageFault(_)) => continue, + Err(VmiError::Translation(_)) => continue, Err(err) => return Err(err), } @@ -111,11 +103,8 @@ where Ok(None) } - fn kernel_image_base( - os: &LinuxOs, - _vmi: &VmiCore, - registers: &Registers, - ) -> Result { + fn kernel_image_base(vmi: VmiState>) -> Result { + let os = vmi.underlying_os(); let entry_SYSCALL_64 = os.symbols.entry_SYSCALL_64; let _text = os.symbols._text; @@ -123,7 +112,7 @@ where return Ok(kernel_image_base); } - let kaslr_offset = registers.msr_lstar - entry_SYSCALL_64; + let kaslr_offset = vmi.registers().msr_lstar - entry_SYSCALL_64; *os.kaslr_offset.borrow_mut() = Some(kaslr_offset); let kernel_image_base = Va::new(_text + kaslr_offset); @@ -131,66 +120,61 @@ where Ok(kernel_image_base) } - fn kaslr_offset( - os: &LinuxOs, - _vmi: &VmiCore, - registers: &Registers, - ) -> Result { + fn kaslr_offset(vmi: VmiState>) -> Result { + let os = vmi.underlying_os(); let entry_SYSCALL_64 = os.symbols.entry_SYSCALL_64; if let Some(kaslr_offset) = *os.kaslr_offset.borrow() { return Ok(kaslr_offset); } - let kaslr_offset = registers.msr_lstar - entry_SYSCALL_64; + let kaslr_offset = vmi.registers().msr_lstar - entry_SYSCALL_64; *os.kaslr_offset.borrow_mut() = Some(kaslr_offset); Ok(kaslr_offset) } - fn per_cpu(_os: &LinuxOs, _vmi: &VmiCore, registers: &Registers) -> Va { - if registers.cs.selector.request_privilege_level() != 0 - || (registers.gs.base & (1 << 47)) == 0 + fn per_cpu(vmi: VmiState>) -> Va { + if vmi.registers().cs.selector.request_privilege_level() != 0 + || (vmi.registers().gs.base & (1 << 47)) == 0 { - registers.shadow_gs.into() + vmi.registers().shadow_gs.into() } else { - registers.gs.base.into() + vmi.registers().gs.base.into() } } } fn function_argument_x86( - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result where Driver: VmiDriver, { let index = index + 1; - let stack = registers.rsp + index * size_of::() as u64; - Ok(vmi.read_u32(registers.address_context(stack.into()))? as u64) + let stack = vmi.registers().rsp + index * size_of::() as u64; + Ok(vmi.read_u32(stack.into())? as u64) } fn function_argument_x64( - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result where Driver: VmiDriver, { match index { - 0 => Ok(registers.rdi), - 1 => Ok(registers.rsi), - 2 => Ok(registers.rdx), - 3 => Ok(registers.rcx), - 4 => Ok(registers.r8), - 5 => Ok(registers.r9), + 0 => Ok(vmi.registers().rdi), + 1 => Ok(vmi.registers().rsi), + 2 => Ok(vmi.registers().rdx), + 3 => Ok(vmi.registers().rcx), + 4 => Ok(vmi.registers().r8), + 5 => Ok(vmi.registers().r9), _ => { let index = index - 6 + 1; - let stack = registers.rsp + index * size_of::() as u64; - vmi.read_u64(registers.address_context(stack.into())) + let stack = vmi.registers().rsp + index * size_of::() as u64; + vmi.read_u64(stack.into()) } } } diff --git a/crates/vmi-os-linux/src/arch/mod.rs b/crates/vmi-os-linux/src/arch/mod.rs index 927dce0..58528cd 100644 --- a/crates/vmi-os-linux/src/arch/mod.rs +++ b/crates/vmi-os-linux/src/arch/mod.rs @@ -1,6 +1,6 @@ mod amd64; -use vmi_core::{Architecture, Va, VmiCore, VmiDriver, VmiError}; +use vmi_core::{Architecture, Va, VmiCore, VmiDriver, VmiError, VmiState}; use crate::LinuxOs; @@ -9,45 +9,25 @@ where Driver: VmiDriver, { fn syscall_argument( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, + vmi: VmiState>, index: u64, ) -> Result; fn function_argument( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, + vmi: VmiState>, index: u64, ) -> Result; - fn function_return_value( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn function_return_value(vmi: VmiState>) -> Result; fn find_banner( vmi: &VmiCore, registers: &::Registers, ) -> Result, VmiError>; - fn kernel_image_base( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn kernel_image_base(vmi: VmiState>) -> Result; - fn kaslr_offset( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn kaslr_offset(vmi: VmiState>) -> Result; - fn per_cpu( - os: &LinuxOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Va; + fn per_cpu(vmi: VmiState>) -> Va; } diff --git a/crates/vmi-os-linux/src/comps/_dummy.rs b/crates/vmi-os-linux/src/comps/_dummy.rs new file mode 100644 index 0000000..03e4052 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/_dummy.rs @@ -0,0 +1,115 @@ +use vmi_core::{ + os::{ + ThreadId, ThreadObject, VmiOsImage, VmiOsImageArchitecture, VmiOsImageSymbol, VmiOsMapped, + VmiOsModule, VmiOsThread, + }, + Architecture, Va, VmiDriver, VmiError, VmiVa, +}; + +use crate::{arch::ArchAdapter, LinuxOs}; + +/// Dummy implementation for Linux OS image. +pub struct LinuxImage; + +impl VmiVa for LinuxImage { + fn va(&self) -> Va { + unimplemented!() + } +} + +#[expect(clippy::needless_lifetimes)] +impl<'a, Driver> VmiOsImage<'a, Driver> for LinuxImage +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + fn base_address(&self) -> Va { + unimplemented!() + } + + fn architecture(&self) -> Result, VmiError> { + unimplemented!() + } + + fn exports(&self) -> Result, VmiError> { + unimplemented!() + } +} + +/// Dummy implementation for Linux OS mapped memory. +pub struct LinuxMapped; + +impl VmiVa for LinuxMapped { + fn va(&self) -> Va { + unimplemented!() + } +} + +impl VmiOsMapped<'_, Driver> for LinuxMapped +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + fn path(&self) -> Result, VmiError> { + unimplemented!() + } +} + +/// Dummy implementation for Linux OS kernel module. +pub struct LinuxModule; + +impl VmiVa for LinuxModule { + fn va(&self) -> Va { + unimplemented!() + } +} + +impl VmiOsModule<'_, Driver> for LinuxModule +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + fn base_address(&self) -> Result { + unimplemented!() + } + + fn size(&self) -> Result { + unimplemented!() + } + + fn name(&self) -> Result { + unimplemented!() + } +} + +/// Dummy implementation for Linux OS thread. +pub struct LinuxThread; + +impl VmiVa for LinuxThread { + fn va(&self) -> Va { + unimplemented!() + } +} + +#[expect(clippy::needless_lifetimes)] +impl<'a, Driver> VmiOsThread<'a, Driver> for LinuxThread +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + fn id(&self) -> Result { + unimplemented!() + } + + fn object(&self) -> Result { + unimplemented!() + } +} diff --git a/crates/vmi-os-linux/src/comps/dentry.rs b/crates/vmi-os-linux/src/comps/dentry.rs new file mode 100644 index 0000000..1f932d3 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/dentry.rs @@ -0,0 +1,79 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, LinuxQStr}; +use crate::{ArchAdapter, LinuxOs}; + +/// A Linux dentry struct. +/// +/// A `dentry` is a directory entry in the Linux kernel. It represents a file +/// or directory in the filesystem. +/// +/// # Implementation Details +/// +/// Corresponds to `dentry`. +pub struct LinuxDEntry<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `dentry` structure. + va: Va, +} + +impl VmiVa for LinuxDEntry<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxDEntry<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `dentry`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the name of the dentry. + /// + /// # Implementation Details + /// + /// Corresponds to `dentry.d_name`. + pub fn name(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __dentry = &offsets.dentry; + + LinuxQStr::new(self.vmi, self.va + __dentry.d_name.offset()).name() + } + + /// Returns the parent dentry. + /// + /// # Implementation Details + /// + /// Corresponds to `dentry.d_parent`. + pub fn parent(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let __dentry = &offsets.dentry; + + let parent = self + .vmi + .read_va_native(self.va + __dentry.d_parent.offset())?; + + if parent.is_null() { + return Ok(None); + } + + Ok(Some(LinuxDEntry::new(self.vmi, parent))) + } +} diff --git a/crates/vmi-os-linux/src/comps/file.rs b/crates/vmi-os-linux/src/comps/file.rs new file mode 100644 index 0000000..2b94c0a --- /dev/null +++ b/crates/vmi-os-linux/src/comps/file.rs @@ -0,0 +1,58 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, path::LinuxPath}; +use crate::{ArchAdapter, LinuxOs}; + +/// A Linux file struct. +/// +/// A `file` is a representation of a file in the Linux kernel. +/// +/// # Implementation Details +/// +/// Corresponds to `file`. +pub struct LinuxFile<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `file` structure. + va: Va, +} + +impl VmiVa for LinuxFile<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxFile<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `file`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the path of the file. + /// + /// # Implementation Details + /// + /// Corresponds to `file.f_path`. + pub fn path(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __file = &offsets.file; + + Ok(LinuxPath::new(self.vmi, self.va + __file.f_path.offset())) + } +} diff --git a/crates/vmi-os-linux/src/comps/fs_struct.rs b/crates/vmi-os-linux/src/comps/fs_struct.rs new file mode 100644 index 0000000..48f7041 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/fs_struct.rs @@ -0,0 +1,76 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, path::LinuxPath}; +use crate::{ArchAdapter, LinuxOs}; + +/// A Linux fs struct. +/// +/// The `fs_struct` structure is responsible for tracking filesystem-related +/// information for a process. Each process in Linux has a reference to an +/// `fs_struct`, which is shared across threads within the same thread group +/// (i.e., threads in the same process share the same filesystem state). +/// +/// # Implementation Details +/// +/// Corresponds to `fs_struct`. +pub struct LinuxFsStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `fs_struct` structure. + va: Va, +} + +impl VmiVa for LinuxFsStruct<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxFsStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `fs_struct`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the root directory (`/`) of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `fs_struct.root`. + pub fn root(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __fs_struct = &offsets.fs_struct; + + Ok(LinuxPath::new( + self.vmi, + self.va + __fs_struct.root.offset(), + )) + } + + /// Returns the current working directory (CWD) of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `fs_struct.pwd`. + pub fn pwd(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __fs_struct = &offsets.fs_struct; + + Ok(LinuxPath::new(self.vmi, self.va + __fs_struct.pwd.offset())) + } +} diff --git a/crates/vmi-os-linux/src/comps/macros.rs b/crates/vmi-os-linux/src/comps/macros.rs new file mode 100644 index 0000000..3a47804 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/macros.rs @@ -0,0 +1,9 @@ +macro_rules! impl_offsets { + () => { + fn offsets(&self) -> &$crate::offsets::Offsets { + &self.vmi.underlying_os().offsets + } + }; +} + +pub(crate) use impl_offsets; diff --git a/crates/vmi-os-linux/src/comps/mm_struct.rs b/crates/vmi-os-linux/src/comps/mm_struct.rs new file mode 100644 index 0000000..2cc8560 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/mm_struct.rs @@ -0,0 +1,105 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, LinuxFile}; +use crate::{ArchAdapter, LinuxOs, MapleTree}; + +/// A Linux mm struct. +/// +/// The `mm_struct` structure is responsible for memory management information +/// of a process. It is responsible for tracking virtual memory mappings, +/// memory regions, page tables, and other crucial memory-related data. +/// +/// # Implementation Details +/// +/// Corresponds to `mm_struct`. +pub struct LinuxMmStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `mm_struct` structure. + va: Va, +} + +impl VmiVa for LinuxMmStruct<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxMmStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `mm_struct`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the page global directory (PGD) of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `mm_struct.pgd`. + pub fn pgd(&self) -> Result { + let offsets = self.offsets(); + let __mm_struct = &offsets.mm_struct; + + self.vmi.read_field(self.va, &__mm_struct.pgd) + } + + /// Returns the executable file of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `mm_struct.exe_file`. + pub fn exe_file(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let __mm_struct = &offsets.mm_struct; + + let exe_file = self + .vmi + .read_va_native(self.va + __mm_struct.exe_file.offset())?; + + if exe_file.is_null() { + return Ok(None); + } + + Ok(Some(LinuxFile::new(self.vmi, exe_file))) + } + + /// Returns the memory map of the process. + /// + /// This is a data structure for managing virtual memory areas (VMAs). + /// It replaces the older mm->mmap (linked list of VMAs) for faster lookups. + /// + /// # Implementation Details + /// + /// Corresponds to `mm_struct.mm_mt`. + pub fn mm_mt(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __mm_struct = &offsets.mm_struct; + + Ok(MapleTree::new( + self.vmi, + self.va + __mm_struct.mm_mt.offset(), + )) + } + + /// Returns the address of the `mm_mt` field. + pub fn mm_mt_va(&self) -> Result { + let offsets = self.offsets(); + let __mm_struct = &offsets.mm_struct; + Ok(self.va + __mm_struct.mm_mt.offset()) + } +} diff --git a/crates/vmi-os-linux/src/comps/mod.rs b/crates/vmi-os-linux/src/comps/mod.rs new file mode 100644 index 0000000..177b54d --- /dev/null +++ b/crates/vmi-os-linux/src/comps/mod.rs @@ -0,0 +1,24 @@ +mod _dummy; +mod dentry; +mod file; +mod fs_struct; +pub(crate) mod macros; +mod mm_struct; +mod path; +mod qstr; +mod task_struct; +mod vfsmount; +mod vm_area_struct; + +pub use self::{ + _dummy::{LinuxImage, LinuxMapped, LinuxModule, LinuxThread}, + dentry::LinuxDEntry, + file::LinuxFile, + fs_struct::LinuxFsStruct, + mm_struct::LinuxMmStruct, + path::LinuxPath, + qstr::LinuxQStr, + task_struct::LinuxTaskStruct, + vfsmount::LinuxVFSMount, + vm_area_struct::LinuxVmAreaStruct, +}; diff --git a/crates/vmi-os-linux/src/comps/path.rs b/crates/vmi-os-linux/src/comps/path.rs new file mode 100644 index 0000000..684bfc8 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/path.rs @@ -0,0 +1,83 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, LinuxDEntry, LinuxVFSMount}; +use crate::{ArchAdapter, LinuxError, LinuxOs}; + +/// A Linux path struct. +/// +/// The struct `path` is a fundamental structure in the Linux kernel used to +/// represent a location in the filesystem. +/// +/// # Implementation Details +/// +/// Corresponds to `path`. +pub struct LinuxPath<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `path` structure. + va: Va, +} + +impl VmiVa for LinuxPath<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxPath<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `path`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the directory entry (dentry) in the VFS (Virtual Filesystem). + /// + /// # Implementation Details + /// + /// Corresponds to `path->dentry`. + pub fn dentry(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __path = &offsets.path; + + let dentry = self.vmi.read_va_native(self.va + __path.dentry.offset())?; + + if dentry.is_null() { + return Err(LinuxError::CorruptedStruct("path->dentry").into()); + } + + Ok(LinuxDEntry::new(self.vmi, dentry)) + } + + /// Returns the the mount point associated with the path. + /// + /// # Implementation Details + /// + /// Corresponds to `path->mnt`. + pub fn mnt(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __path = &offsets.path; + + let mnt = self.vmi.read_va_native(self.va + __path.mnt.offset())?; + + if mnt.is_null() { + return Err(LinuxError::CorruptedStruct("path->mnt").into()); + } + + Ok(LinuxVFSMount::new(self.vmi, mnt)) + } +} diff --git a/crates/vmi-os-linux/src/comps/qstr.rs b/crates/vmi-os-linux/src/comps/qstr.rs new file mode 100644 index 0000000..dbb96b0 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/qstr.rs @@ -0,0 +1,100 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::macros::impl_offsets; +use crate::{ArchAdapter, LinuxOs}; + +/// A Linux qstr struct. +/// +/// The struct `qstr` (short for "quick string") is a structure used in the +/// Virtual Filesystem (VFS) layer of the Linux kernel. It is primarily used +/// to represent filenames and directory entry names efficiently. +/// +/// # Implementation Details +/// +/// Corresponds to `qstr`. +pub struct LinuxQStr<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `qstr` structure. + va: Va, + + len: OnceCell, +} + +impl VmiVa for LinuxQStr<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxQStr<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new `qstr`. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { + vmi, + va, + len: OnceCell::new(), + } + } + + /// Returns the filename or directory name. + /// + /// # Implementation Details + /// + /// Corresponds to `qstr->name`. + pub fn name(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __qstr = &offsets.qstr; + + let name = self.vmi.read_va_native(self.va + __qstr.name.offset())?; + + if name.is_null() { + return Ok(None); + } + + Ok(Some( + self.vmi.read_string_limited(name, self.len()? as usize)?, + )) + } + + /// Returns the length of the string. + /// + /// # Implementation Details + /// + /// Corresponds to `qstr.len`. + pub fn len(&self) -> Result { + self.len + .get_or_try_init(|| { + let offsets = self.offsets(); + let __qstr = &offsets.qstr; + + self.vmi.read_u32(self.va + __qstr.len.offset()) + }) + .copied() + } + + /// Returns whether the string is empty. + /// + /// # Implementation Details + /// + /// Corresponds to `qstr.len == 0`. + pub fn is_empty(&self) -> Result { + Ok(self.len()? == 0) + } +} diff --git a/crates/vmi-os-linux/src/comps/task_struct.rs b/crates/vmi-os-linux/src/comps/task_struct.rs new file mode 100644 index 0000000..8ac468e --- /dev/null +++ b/crates/vmi-os-linux/src/comps/task_struct.rs @@ -0,0 +1,317 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{ + os::{ProcessId, ProcessObject, VmiOsImageArchitecture, VmiOsProcess}, + Architecture, Pa, Va, VmiDriver, VmiError, VmiOs, VmiState, VmiVa, +}; + +use super::{macros::impl_offsets, LinuxFsStruct, LinuxMmStruct, LinuxPath, LinuxVmAreaStruct}; +use crate::{ArchAdapter, LinuxError, LinuxOs}; + +/// A Linux task struct. +/// +/// The `task_struct` is the process descriptor in the Linux kernel, +/// representing a task (process or thread). +/// +/// # Implementation Details +/// +/// Corresponds to `task_struct`. +pub struct LinuxTaskStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `task_struct` structure. + va: Va, + + /// Cached flags. + flags: OnceCell, +} + +impl VmiVa for LinuxTaskStruct<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxTaskStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Linux task struct. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, process: ProcessObject) -> Self { + Self { + vmi, + va: process.0, + flags: OnceCell::new(), + } + } + + /// Returns the process flags. + /// + /// Process flags in Linux include information about the process state, + /// such as whether it's exiting, a kernel thread, etc. + /// + /// # Implementation Details + /// + /// Corresponds to `task_struct.flags`. + pub fn flags(&self) -> Result { + self.flags + .get_or_try_init(|| { + let offsets = self.offsets(); + let __task_struct = &offsets.task_struct; + + self.vmi.read_u32(self.va + __task_struct.flags.offset()) + }) + .copied() + } + + /// Returns the memory descriptor (`mm_struct`) of the user-mode process. + /// + /// The `mm_struct` contains the memory management information for a process. + /// Kernel threads don't have an `mm_struct` and return `None`. + /// + /// # Implementation Details + /// + /// Corresponds to `task_struct->mm`. + pub fn mm(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let __task_struct = &offsets.task_struct; + + let mm = self + .vmi + .read_va_native(self.va + __task_struct.mm.offset())?; + + if mm.is_null() { + return Ok(None); + } + + Ok(Some(LinuxMmStruct::new(self.vmi, mm))) + } + + /// Returns the active memory context (`mm_struct`) of the process. + /// + /// Used by kernel threads to reference the last used `mm_struct` before + /// entering kernel mode. + /// + /// If a kernel thread ([`mm()`] is `None`) needs memory access, + /// it temporarily borrows `active_mm` from the last scheduled user-space + /// process. + /// + /// When the kernel thread exits, the original `mm_struct` is restored. + /// + /// # Implementation Details + /// + /// Corresponds to `task_struct->active_mm`. + /// + /// [`mm()`]: Self::mm + pub fn active_mm(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __task_struct = &offsets.task_struct; + + let mm = self + .vmi + .read_va_native(self.va + __task_struct.active_mm.offset())?; + + if mm.is_null() { + return Err(LinuxError::CorruptedStruct("task_struct->active_mm").into()); + } + + Ok(LinuxMmStruct::new(self.vmi, mm)) + } + + /// Returns the filesystem context (`fs_struct`) of the process. + /// + /// `fs_struct` contains: + /// - [`root`]: The process’s root directory (used for chroot). + /// - [`pwd`]: The current working directory. + /// + /// All threads in the same process share the same `fs_struct`, unless + /// explicitly changed. + /// + /// Kernel threads don't have an `fs_struct` and return `None`. + /// + /// # Implementation Details + /// + /// Corresponds to `task_struct->fs`. + /// + /// [`root`]: LinuxFsStruct::root + /// [`pwd`]: LinuxFsStruct::pwd + pub fn fs(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let __task_struct = &offsets.task_struct; + let __fs_struct = &offsets.fs_struct; + + let fs = self + .vmi + .read_va_native(self.va + __task_struct.fs.offset())?; + + if fs.is_null() { + return Ok(None); + } + + Ok(Some(LinuxFsStruct::new(self.vmi, fs))) + } + + /// Constructs the absolute path from a `path` structure. + /// + /// Takes into account the process's filesystem root when constructing the + /// absolute path. + /// + /// Returns the resolved path as a string if successful, or `None` if the path + /// could not be resolved (e.g., if the root is null). + /// + /// # Implementation Details + /// + /// Concatenates `task_struct->fs->root` with the `path` structure to construct + /// the absolute path. + pub fn d_path(&self, path: &LinuxPath) -> Result, VmiError> { + let root = match self.fs()? { + Some(root) => root.root()?, + None => return Ok(None), + }; + + Ok(Some(LinuxOs::::construct_path( + self.vmi, path, &root, + )?)) + } + + /// Returns the path of the executable image for a process. + /// + /// Returns the executable path as a string, or `None` for special processes + /// like kernel threads or those in the process of exiting. + /// + /// # Implementation Details + /// + /// Corresponds to `d_path(task->mm->exe_file->f_path)`. + pub fn image_path(&self) -> Result, VmiError> { + let flags = self.flags()?; + + const PF_EXITING: u32 = 0x00000004; // getting shut down + const PF_KTHREAD: u32 = 0x00200000; // kernel thread + + if flags & PF_KTHREAD != 0 { + return Ok(None); + } + + if flags & PF_EXITING != 0 { + return Ok(None); + } + + let mm = match self.mm()? { + Some(mm) => mm, + None => return Ok(None), + }; + + let exe_file = match mm.exe_file()? { + Some(exe_file) => exe_file, + None => return Ok(None), + }; + + self.d_path(&exe_file.path()?) + } +} + +impl<'a, Driver> VmiOsProcess<'a, Driver> for LinuxTaskStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + fn id(&self) -> Result { + let offsets = self.offsets(); + let __task_struct = &offsets.task_struct; + + let result = self.vmi.read_u32(self.va + __task_struct.tgid.offset())?; + + Ok(ProcessId(result)) + } + + fn object(&self) -> Result { + Ok(ProcessObject(self.va)) + } + + fn name(&self) -> Result { + let task_struct_comm_offset = 0xBC0; + + self.vmi.read_string(self.va + task_struct_comm_offset) + } + + fn parent_id(&self) -> Result { + unimplemented!() + } + + fn architecture(&self) -> Result { + unimplemented!() + } + + fn translation_root(&self) -> Result { + unimplemented!() + } + + fn user_translation_root(&self) -> Result { + unimplemented!() + } + + fn image_base(&self) -> Result { + unimplemented!() + } + + fn regions( + &self, + ) -> Result< + impl Iterator>::Region<'a>, VmiError>>, + VmiError, + > { + let mut result = Vec::new(); + + let mm = match self.mm()? { + Some(mm) => mm, + None => return Ok(result.into_iter()), + }; + + let mt = mm.mm_mt()?; + mt.enumerate(|node| { + println!("XXXNode: {}", node); + if !node.is_null() { + result.push(Ok(LinuxVmAreaStruct::new(self.vmi, node))); + } + true + })?; + + Ok(result.into_iter()) + } + + fn find_region( + &self, + _address: Va, + ) -> Result>::Region<'a>>, VmiError> { + unimplemented!() + } + + fn threads( + &self, + ) -> Result< + impl Iterator>::Thread<'a>, VmiError>>, + VmiError, + > { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> + } + } + + fn is_valid_address(&self, _address: Va) -> Result, VmiError> { + unimplemented!() + } +} diff --git a/crates/vmi-os-linux/src/comps/vfsmount.rs b/crates/vmi-os-linux/src/comps/vfsmount.rs new file mode 100644 index 0000000..f2529bb --- /dev/null +++ b/crates/vmi-os-linux/src/comps/vfsmount.rs @@ -0,0 +1,73 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, LinuxDEntry}; +use crate::{ArchAdapter, LinuxError, LinuxOs}; + +/// A Linux VFS mount struct. +/// +/// The `vfsmount` structure is used to represent a mounted filesystem instance +/// in the Linux Virtual Filesystem (VFS). +/// +/// # Implementation Details +/// +/// Corresponds to `vfsmount`. +pub struct LinuxVFSMount<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `vfsmount` structure. + va: Va, +} + +impl VmiVa for LinuxVFSMount<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> LinuxVFSMount<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new VFS mount. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the root directory entry (`dentry`) of the mount. + /// + /// `mnt_root` points to the root directory (`/`) of the mounted filesystem. + /// It is a `dentry` (directory entry) that represents the starting point of + /// the filesystem. Every mount point has a `mnt_root`, but this root does + /// not necessarily have to be `/` (e.g., for a chroot environment, the root + /// could be `/home/user`). + /// + /// # Implementation Details + /// + /// Corresponds to `vfsmount->mnt_root`. + pub fn mnt_root(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __vfsmount = &offsets.vfsmount; + + let mnt_root = self + .vmi + .read_va_native(self.va + __vfsmount.mnt_root.offset())?; + + if mnt_root.is_null() { + return Err(LinuxError::CorruptedStruct("vfsmount->mnt_root").into()); + } + + Ok(LinuxDEntry::new(self.vmi, mnt_root)) + } +} diff --git a/crates/vmi-os-linux/src/comps/vm_area_struct.rs b/crates/vmi-os-linux/src/comps/vm_area_struct.rs new file mode 100644 index 0000000..6fb8169 --- /dev/null +++ b/crates/vmi-os-linux/src/comps/vm_area_struct.rs @@ -0,0 +1,177 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{ + os::{VmiOsRegion, VmiOsRegionKind}, + Architecture, MemoryAccess, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +use super::macros::impl_offsets; +use crate::{ArchAdapter, LinuxOs}; + +/// A Linux VM area struct. +/// +/// A `vm_area_struct` is a structure that represents a memory region (Virtual +/// Memory Area, or VMA) in a process's address space. +/// +/// # Implementation Details +/// +/// Corresponds to `vm_area_struct`. +pub struct LinuxVmAreaStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The virtual address of the `vm_area_struct` structure. + va: Va, + + /// Cached flags. + flags: OnceCell, +} + +impl VmiVa for LinuxVmAreaStruct<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl std::fmt::Debug for LinuxVmAreaStruct<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let start = self.start(); + let end = self.end(); + let protection = self.protection(); + //let kind = self.kind(); + + f.debug_struct("LinuxVmAreaStruct") + .field("start", &start) + .field("end", &end) + .field("protection", &protection) + //.field("kind", &kind) + .finish() + } +} + +impl<'a, Driver> LinuxVmAreaStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new VM area struct. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, vad: Va) -> Self { + Self { + vmi, + va: vad, + flags: OnceCell::new(), + } + } + + /// Returns the flags of the memory region. + /// + /// The flags are a bitmask that represent the memory region's permissions + /// and other attributes. + /// + /// # Notes + /// + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `vm_area_struct.vm_flags`. + pub fn flags(&self) -> Result { + self.flags + .get_or_try_init(|| { + let offsets = self.offsets(); + let __vm_area_struct = &offsets.vm_area_struct; + + self.vmi.read_field(self.va, &__vm_area_struct.vm_flags) + }) + .copied() + } +} + +impl<'a, Driver> VmiOsRegion<'a, Driver> for LinuxVmAreaStruct<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = LinuxOs; + + /// Returns the starting virtual address of the memory region. + /// + /// # Implementation Details + /// + /// Corresponds to `vm_area_struct.vm_start`. + fn start(&self) -> Result { + let offsets = self.offsets(); + let __vm_area_struct = &offsets.vm_area_struct; + + self.vmi + .read_va_native(self.va + __vm_area_struct.vm_start.offset()) + } + + /// Returns the ending virtual address of the memory region. + /// + /// # Implementation Details + /// + /// Corresponds to `vm_area_struct.vm_end`. + fn end(&self) -> Result { + let offsets = self.offsets(); + let __vm_area_struct = &offsets.vm_area_struct; + + self.vmi + .read_va_native(self.va + __vm_area_struct.vm_end.offset()) + } + + /// Returns the memory protection of the memory region. + /// + /// # Implementation Details + /// + /// Calculated from `vm_area_struct.vm_flags` field. + fn protection(&self) -> Result { + const VM_READ: u64 = 0x00000001; + const VM_WRITE: u64 = 0x00000002; + const VM_EXEC: u64 = 0x00000004; + //const VM_SHARED: u64 = 0x00000008; + + let flags = self.flags()?; + let mut protection = MemoryAccess::default(); + if flags & VM_READ != 0 { + protection |= MemoryAccess::R; + } + if flags & VM_WRITE != 0 { + protection |= MemoryAccess::W; + } + if flags & VM_EXEC != 0 { + protection |= MemoryAccess::X; + } + + Ok(protection) + } + + /// Returns the memory region's kind. + fn kind(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let __vm_area_struct = &offsets.vm_area_struct; + + let file = self + .vmi + .read_va_native(self.va + __vm_area_struct.vm_file.offset())?; + + if file.is_null() { + return Ok(VmiOsRegionKind::Private); + } + + unimplemented!() + } +} diff --git a/crates/vmi-os-linux/src/error.rs b/crates/vmi-os-linux/src/error.rs new file mode 100644 index 0000000..51f4c27 --- /dev/null +++ b/crates/vmi-os-linux/src/error.rs @@ -0,0 +1,13 @@ +/// Error types for Linux operations. +#[derive(thiserror::Error, Debug)] +pub enum LinuxError { + /// Corrupted struct. + #[error("Corrupted struct: {0}")] + CorruptedStruct(&'static str), +} + +impl From for vmi_core::VmiError { + fn from(value: LinuxError) -> Self { + vmi_core::VmiError::Os(value.into()) + } +} diff --git a/crates/vmi-os-linux/src/iter/list.rs b/crates/vmi-os-linux/src/iter/list.rs new file mode 100644 index 0000000..d7185b9 --- /dev/null +++ b/crates/vmi-os-linux/src/iter/list.rs @@ -0,0 +1,132 @@ +use std::iter::FusedIterator; + +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState}; + +use crate::{ArchAdapter, LinuxOs}; + +/// An iterator for traversing list entries. +/// +/// Iterate over entries in a linked list structure, specifically `LIST_ENTRY`. +pub struct ListEntryIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, + + /// Current entry. + current: Option, + + /// Address of the list head. + list_head: Va, + + /// Offset to the containing structure. + /// + /// The offset is subtracted from the entry address to get the containing + /// structure, similar to the `CONTAINING_RECORD` macro in the Windows + /// kernel. + offset: u64, + + /// Offset to the forward link pointer (`LIST_ENTRY.Flink`). + offset_next: u64, + + /// Offset to the backward link pointer (`LIST_ENTRY.Blink`). + offset_prev: u64, +} + +impl<'a, Driver> ListEntryIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new list entry iterator. + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, list_head: Va, offset: u64) -> Self { + let __list_head = &vmi.underlying_os().offsets.list_head; + let (offset_next, offset_prev) = (__list_head.next.offset(), __list_head.prev.offset()); + + Self { + vmi, + current: None, + list_head, + offset, + offset_next, + offset_prev, + } + } + + fn __first(&mut self) -> Result { + self.vmi.read_va_native(self.list_head + self.offset_next) + } + + fn __last(&mut self) -> Result { + self.vmi.read_va_native(self.list_head + self.offset_prev) + } + + fn __next(&mut self) -> Result, VmiError> { + let entry = match self.current { + Some(entry) => entry, + None => { + let flink = self.__first()?; + self.current = Some(flink); + flink + } + }; + + if entry == self.list_head { + return Ok(None); + } + + self.current = Some(self.vmi.read_va_native(entry + self.offset_next)?); + + Ok(Some(entry - self.offset)) + } + + fn __next_back(&mut self) -> Result, VmiError> { + let entry = match self.current { + Some(entry) => entry, + None => { + let blink = self.__last()?; + self.current = Some(blink); + blink + } + }; + + if entry == self.list_head { + return Ok(None); + } + + self.current = Some(self.vmi.read_va_native(entry + self.offset_prev)?); + + Ok(Some(entry - self.offset)) + } +} + +impl Iterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.__next().transpose() + } +} + +impl DoubleEndedIterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn next_back(&mut self) -> Option { + self.__next_back().transpose() + } +} + +impl FusedIterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ +} diff --git a/crates/vmi-os-linux/src/maple_tree.rs b/crates/vmi-os-linux/src/iter/maple_tree.rs similarity index 74% rename from crates/vmi-os-linux/src/maple_tree.rs rename to crates/vmi-os-linux/src/iter/maple_tree.rs index 6456598..040d5cb 100644 --- a/crates/vmi-os-linux/src/maple_tree.rs +++ b/crates/vmi-os-linux/src/iter/maple_tree.rs @@ -13,9 +13,9 @@ //! - [Kernel Documentation - Maple Tree](https://docs.kernel.org/core-api/maple_tree.html) #![allow(dead_code)] -use vmi_core::{Architecture, Registers as _, Va, VmiCore, VmiDriver, VmiError}; +use vmi_core::{Architecture, Registers as _, Va, VmiDriver, VmiError, VmiState, VmiVa}; -use crate::Offsets; +use crate::{arch::ArchAdapter, LinuxOs, Offsets}; /// Represents different node types in a Maple Tree. #[derive(Debug)] @@ -159,28 +159,37 @@ const fn mte_is_leaf(entry: Va /* maple_enode */) -> bool { pub struct MapleTree<'a, Driver> where Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, { - /// The VMI core. - vmi: &'a VmiCore, + /// The VMI state. + vmi: VmiState<'a, Driver, LinuxOs>, - /// The CPU register state. - regs: &'a ::Registers, + /// The virtual address of the Maple Tree root. + root: Va, +} - /// Offsets for the Maple Tree data structure. - offsets: &'a Offsets, +impl VmiVa for MapleTree<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.root + } } impl<'a, Driver> MapleTree<'a, Driver> where Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, { /// Creates a new MapleTree instance. - pub fn new( - vmi: &'a VmiCore, - regs: &'a ::Registers, - offsets: &'a Offsets, - ) -> Self { - Self { vmi, regs, offsets } + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, root: Va) -> Self { + Self { vmi, root } + } + + fn offsets(&self) -> &Offsets { + &self.vmi.underlying_os().offsets } // region: Enumerate @@ -188,20 +197,19 @@ where /// Enumerates all entries in the Maple Tree. /// /// Traverses the tree structure and calls the provided callback for each entry found. - pub fn enumerate( - &self, - root: Va, - mut callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError> { - let __maple_tree = &self.offsets.maple_tree; + pub fn enumerate(&self, mut callback: impl FnMut(Va) -> bool) -> Result<(), VmiError> { + let offsets = self.offsets(); + let __maple_tree = &offsets.maple_tree; - let entry = self.read_va(root + __maple_tree.ma_root.offset)?; + let entry = self + .vmi + .read_va_native(self.root + __maple_tree.ma_root.offset())?; if !xa_is_node(entry) { self.enumerate_entry(entry, &mut callback); } else if !entry.is_null() { - self.enumerate_node(root, entry, 0, u64::MAX, &mut callback)?; + self.enumerate_node(self.root, entry, 0, u64::MAX, &mut callback)?; } Ok(()) @@ -227,8 +235,9 @@ where max: u64, callback: &mut impl FnMut(Va) -> bool, ) -> Result<(), VmiError> { - let __maple_tree = &self.offsets.maple_tree; - let __maple_node = &self.offsets.maple_node; + let offsets = self.offsets(); + let __maple_tree = &offsets.maple_tree; + let __maple_node = &offsets.maple_node; let node = mte_to_node(entry); let typ = mte_node_type(entry); @@ -238,7 +247,9 @@ where // const MAPLE_NODE_SLOTS: u64 = 63; // 32 bit OS const MAPLE_NODE_SLOTS: u64 = 31; // 64 bit OS for i in 0..MAPLE_NODE_SLOTS { - let slot = self.read_va(node + __maple_node.slot.offset + i * 8)?; + let slot = self + .vmi + .read_va_native(node + __maple_node.slot.offset() + i * 8)?; if !slot.is_null() { self.enumerate_entry(slot, callback); @@ -265,32 +276,35 @@ where max: u64, callback: &mut impl FnMut(Va) -> bool, ) -> Result<(), VmiError> { - let __maple_node = &self.offsets.maple_node; - let __maple_range_64 = &self.offsets.maple_range_64; + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + let __maple_range_64 = &offsets.maple_range_64; - let node = mte_to_node(entry) + __maple_node.mr64.offset; + let node = mte_to_node(entry) + __maple_node.mr64.offset(); let leaf = mte_is_leaf(entry); let mut first = min; const MAPLE_RANGE64_SLOTS_32: u64 = 32; // 32 bit OS const MAPLE_RANGE64_SLOTS_64: u64 = 16; // 64 bit OS - #[allow(non_snake_case)] - let MAPLE_RANGE64_SLOTS = match self.regs.address_width() { + #[expect(non_snake_case)] + let MAPLE_RANGE64_SLOTS = match self.vmi.registers().address_width() { 4 => MAPLE_RANGE64_SLOTS_32, 8 => MAPLE_RANGE64_SLOTS_64, _ => 0, }; let __pivot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_range_64.pivot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_range_64.pivot.offset() + offset) .map(u64::from) }; let __slot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_range_64.slot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_range_64.slot.offset() + offset) }; for i in 0..MAPLE_RANGE64_SLOTS { @@ -337,32 +351,35 @@ where max: u64, callback: &mut impl FnMut(Va) -> bool, ) -> Result<(), VmiError> { - let __maple_node = &self.offsets.maple_node; - let __maple_arange_64 = &self.offsets.maple_arange_64; + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + let __maple_arange_64 = &offsets.maple_arange_64; - let node = mte_to_node(entry) + __maple_node.ma64.offset; + let node = mte_to_node(entry) + __maple_node.ma64.offset(); let leaf = mte_is_leaf(entry); let mut first = min; const MAPLE_ARANGE64_SLOTS_32: u64 = 21; // 32 bit OS const MAPLE_ARANGE64_SLOTS_64: u64 = 10; // 64 bit OS - #[allow(non_snake_case)] - let MAPLE_ARANGE64_SLOTS = match self.regs.address_width() { + #[expect(non_snake_case)] + let MAPLE_ARANGE64_SLOTS = match self.vmi.registers().address_width() { 4 => MAPLE_ARANGE64_SLOTS_32, 8 => MAPLE_ARANGE64_SLOTS_64, _ => 0, }; let __pivot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_arange_64.pivot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_arange_64.pivot.offset() + offset) .map(u64::from) }; let __slot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_arange_64.slot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_arange_64.slot.offset() + offset) }; for i in 0..MAPLE_ARANGE64_SLOTS { @@ -406,15 +423,20 @@ where // region: Dump /// Dumps the entire Maple Tree structure for debugging. - pub fn dump(&self, mt: Va) -> Result<(), VmiError> { - let __maple_tree = &self.offsets.maple_tree; + pub fn dump(&self) -> Result<(), VmiError> { + let offsets = self.offsets(); + let __maple_tree = &offsets.maple_tree; - let flags = self.read_u32(mt + __maple_tree.ma_flags.offset)?; - let entry = self.read_va(mt + __maple_tree.ma_root.offset)?; + let flags = self + .vmi + .read_u32(self.root + __maple_tree.ma_flags.offset())?; + let entry = self + .vmi + .read_va_native(self.root + __maple_tree.ma_root.offset())?; println!( "maple_tree({}) flags {:X}, height {} root {:X}", - mt, + self.root, flags, mt_flags_height(flags), entry @@ -424,7 +446,7 @@ where self.dump_entry(entry, 0, 0, 0); } else if !entry.is_null() { - self.dump_node(mt, entry, 0, u64::MAX, 0)?; + self.dump_node(self.root, entry, 0, u64::MAX, 0)?; } Ok(()) @@ -459,43 +481,48 @@ where } else { println!("{:?}", entry); - /* if entry.is_null() { return; } - let __vm_area_struct = &self.offsets.vm_area_struct; - let start = self.read_u64(entry + __vm_area_struct.vm_start.offset); - let end = self.read_u64(entry + __vm_area_struct.vm_end.offset); - let file = self.read_va(entry + __vm_area_struct.vm_file.offset); + let offsets = self.offsets(); + let __vm_area_struct = &offsets.vm_area_struct; + + let start = self + .vmi + .read_u64(entry + __vm_area_struct.vm_start.offset()); + let end = self.vmi.read_u64(entry + __vm_area_struct.vm_end.offset()); + let file = self + .vmi + .read_va_native(entry + __vm_area_struct.vm_file.offset()); println!("Range: {:X?}-{:X?}", start, end); if let Ok(file) = file { if !file.is_null() { - let __dentry = &self.offsets.dentry; - let __file = &self.offsets.file; - let __path = &self.offsets.path; - let __qstr = &self.offsets.qstr; - - let f_path = file + __file.f_path.offset; - - if let Ok(dentry) = self.read_va(f_path + __path.dentry.offset) { - if let Ok(d_name) = - self.read_va(dentry + __dentry.d_name.offset + __qstr.name.offset) - { - let name = self.read_string(d_name); + let __dentry = &offsets.dentry; + let __file = &offsets.file; + let __path = &offsets.path; + let __qstr = &offsets.qstr; + + let f_path = file + __file.f_path.offset(); + + if let Ok(dentry) = self.vmi.read_va_native(f_path + __path.dentry.offset()) { + if let Ok(d_name) = self.vmi.read_va_native( + dentry + __dentry.d_name.offset() + __qstr.name.offset(), + ) { + let name = self.vmi.read_string(d_name); println!(" File: {:?}", name); } } } } - */ } } fn dump_node(&self, mt: Va, entry: Va, min: u64, max: u64, depth: u64) -> Result<(), VmiError> { - let __maple_tree = &self.offsets.maple_tree; - let __maple_node = &self.offsets.maple_node; + let offsets = self.offsets(); + let __maple_tree = &offsets.maple_tree; + let __maple_node = &offsets.maple_node; let node = mte_to_node(entry); let typ = mte_node_type(entry); @@ -508,7 +535,7 @@ where depth, mte_node_type(entry), if !node.is_null() { - self.read_va(node + __maple_node.parent.offset) + self.vmi.read_va_native(node + __maple_node.parent.offset()) } else { Ok(Va::default()) @@ -520,7 +547,9 @@ where // const MAPLE_NODE_SLOTS: u64 = 63; // 32 bit OS const MAPLE_NODE_SLOTS: u64 = 31; // 64 bit OS for i in 0..MAPLE_NODE_SLOTS { - let slot = self.read_va(node + __maple_node.slot.offset + i * 8)?; + let slot = self + .vmi + .read_va_native(node + __maple_node.slot.offset() + i * 8)?; if !slot.is_null() { self.dump_entry(slot, min, max, depth); @@ -547,10 +576,11 @@ where max: u64, depth: u64, ) -> Result<(), VmiError> { - let __maple_node = &self.offsets.maple_node; - let __maple_range_64 = &self.offsets.maple_range_64; + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + let __maple_range_64 = &offsets.maple_range_64; - let node = mte_to_node(entry) + __maple_node.mr64.offset; + let node = mte_to_node(entry) + __maple_node.mr64.offset(); let leaf = mte_is_leaf(entry); let mut first = min; @@ -559,12 +589,14 @@ where let __pivot = |i: u64| { let offset = i * size_of::() as u64; - self.read_u64(node + __maple_range_64.pivot.offset + offset) + self.vmi + .read_u64(node + __maple_range_64.pivot.offset() + offset) }; let __slot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_range_64.slot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_range_64.slot.offset() + offset) }; for i in 0..MAPLE_RANGE64_SLOTS - 1 { @@ -616,10 +648,11 @@ where max: u64, depth: u64, ) -> Result<(), VmiError> { - let __maple_node = &self.offsets.maple_node; - let __maple_arange_64 = &self.offsets.maple_arange_64; + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + let __maple_arange_64 = &offsets.maple_arange_64; - let node = mte_to_node(entry) + __maple_node.ma64.offset; + let node = mte_to_node(entry) + __maple_node.ma64.offset(); let leaf = mte_is_leaf(entry); let mut first = min; @@ -628,12 +661,14 @@ where let __pivot = |i: u64| { let offset = i * size_of::() as u64; - self.read_u64(node + __maple_arange_64.pivot.offset + offset) + self.vmi + .read_u64(node + __maple_arange_64.pivot.offset() + offset) }; let __slot = |i: u64| { - let offset = i * self.regs.address_width() as u64; - self.read_va(node + __maple_arange_64.slot.offset + offset) + let offset = i * self.vmi.registers().address_width() as u64; + self.vmi + .read_va_native(node + __maple_arange_64.slot.offset() + offset) }; for i in 0..MAPLE_ARANGE64_SLOTS - 1 { @@ -678,26 +713,4 @@ where } // endregion - - // region: Helpers - - /// Reads a 32-bit value from virtual memory. - fn read_u32(&self, va: Va) -> Result { - self.vmi.read_u32((va, self.regs.translation_root(va))) - } - - /// Reads a 64-bit value from virtual memory. - fn read_u64(&self, va: Va) -> Result { - self.vmi.read_u64((va, self.regs.translation_root(va))) - } - - /// Reads a virtual address from virtual memory. - fn read_va(&self, va: Va) -> Result { - self.vmi.read_va( - (va, self.regs.translation_root(va)), - self.regs.address_width(), - ) - } - - // endregion } diff --git a/crates/vmi-os-linux/src/iter/maple_tree.rs.todo b/crates/vmi-os-linux/src/iter/maple_tree.rs.todo new file mode 100644 index 0000000..b1ce61a --- /dev/null +++ b/crates/vmi-os-linux/src/iter/maple_tree.rs.todo @@ -0,0 +1,291 @@ +//! A MapleTree implementation based on the Linux kernel's data structure. +//! +//! The Maple Tree is a data structure used in the Linux kernel for efficiently storing +//! and searching ranges of values. It's particularly used for managing virtual memory +//! areas (VMAs) in the memory management subsystem. +//! +//! # References +//! +//! - [Linux Kernel Source - maple_tree.c](https://elixir.bootlin.com/linux/v6.10.5/source/lib/maple_tree.c) +//! - [Linux Kernel Source - maple_tree.h](https://elixir.bootlin.com/linux/v6.10.5/source/include/linux/maple_tree.h) +//! - [Process Address Space Documentation](https://students.mimuw.edu.pl/ZSO/Wyklady/04_processes2/ProcessAddressSpace.pdf) +//! - [Maple Tree: Storing Ranges](https://blogs.oracle.com/linux/post/maple-tree-storing-ranges) +//! - [Kernel Documentation - Maple Tree](https://docs.kernel.org/core-api/maple_tree.html) + +#![allow(dead_code)] +use std::iter::FusedIterator; + +use vmi_core::{Architecture, Registers as _, Va, VmiDriver, VmiError, VmiState}; + +use crate::{ArchAdapter, LinuxOs, Offsets}; + +/// Represents different node types in a Maple Tree. +#[derive(Debug)] +enum MapleType { + /// Dense nodes contain directly addressable slots. + Dense, + + /// Leaf nodes. + Leaf64, + + /// Range nodes. + Range64, + + /// Allocation range nodes. + Arange64, +} + +/// Create an internal entry. +/// @v: Value to turn into an internal entry. +/// +/// Internal entries are used for a number of purposes. Entries 0-255 are +/// used for sibling entries (only 0-62 are used by the current code). 256 +/// is used for the retry entry. 257 is used for the reserved / zero entry. +/// Negative internal entries are used to represent errnos. Node pointers +/// are also tagged as internal entries in some situations. +/// +/// Context: Any context. +/// Return: An XArray internal entry corresponding to this value. +const fn xa_mk_internal(v: u64) -> Va { + Va::new((v << 2) | 2) +} + +/// Extract the value from an internal entry. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: The value which was stored in the internal entry. +const fn xa_to_internal(entry: Va) -> u64 { + entry.0 >> 2 +} + +/// Is the entry an internal entry? +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: %true if the entry is an internal entry. +const fn xa_is_internal(entry: Va) -> bool { + (entry.0 & 3) == 2 +} + +/// Is the entry a zero entry? +/// @entry: Entry retrieved from the XArray +/// +/// The normal API will return NULL as the contents of a slot containing +/// a zero entry. You can only see zero entries by using the advanced API. +/// +/// Return: %true if the entry is a zero entry. +const fn xa_is_zero(entry: Va) -> bool { + const XA_ZERO_ENTRY: Va = xa_mk_internal(257); + entry.0 == XA_ZERO_ENTRY.0 +} + +/// Get value stored in an XArray entry. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: The value stored in the XArray entry. +const fn xa_to_value(entry: Va) -> u64 { + entry.0 >> 1 +} + +/// Determine if an entry is a value. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: True if the entry is a value, false if it is a pointer. +const fn xa_is_value(entry: Va) -> bool { + (entry.0 & 1) == 1 +} + +const fn xa_is_node(entry: Va) -> bool { + xa_is_internal(entry) && entry.0 > 4096 +} + +const fn mt_flags_height(ma_flags: u32) -> u32 { + const MT_FLAGS_HEIGHT_OFFSET: u32 = 0x02; + const MT_FLAGS_HEIGHT_MASK: u32 = 0x7C; + + (ma_flags & MT_FLAGS_HEIGHT_MASK) >> MT_FLAGS_HEIGHT_OFFSET +} + +/// We also reserve values with the bottom two bits set to '10' which are +/// below 4096 +const fn mt_is_reserved(entry: Va /* void* */) -> bool { + const MAPLE_RESERVED_RANGE: u64 = 4096; + (entry.0 < MAPLE_RESERVED_RANGE) && xa_is_internal(entry) +} + +const fn mt_node_max(entry: Va) -> Option { + match mte_node_type(entry) { + Some(MapleType::Dense) => Some(31), + Some(MapleType::Leaf64) => Some(u64::MAX), + Some(MapleType::Range64) => Some(u64::MAX), + Some(MapleType::Arange64) => Some(u64::MAX), + None => None, + } +} + +const fn mte_node_type(entry: Va /* maple_enode */) -> Option { + const MAPLE_NODE_TYPE_MASK: u64 = 0x0F; + const MAPLE_NODE_TYPE_SHIFT: u64 = 0x03; + match (entry.0 >> MAPLE_NODE_TYPE_SHIFT) & MAPLE_NODE_TYPE_MASK { + 0x00 => Some(MapleType::Dense), + 0x01 => Some(MapleType::Leaf64), + 0x02 => Some(MapleType::Range64), + 0x03 => Some(MapleType::Arange64), + _ => None, + } +} + +const fn ma_is_leaf(typ: MapleType) -> bool { + matches!(typ, MapleType::Dense | MapleType::Leaf64) +} + +const fn mte_to_node(entry: Va /* maple_enode */) -> Va { + const MAPLE_NODE_MASK: u64 = 255; + Va::new(entry.0 & !MAPLE_NODE_MASK) +} + +const fn mte_is_leaf(entry: Va /* maple_enode */) -> bool { + match mte_node_type(entry) { + Some(typ) => ma_is_leaf(typ), + None => false, + } +} + +pub struct MapleTreeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + vmi: VmiState<'a, Driver, LinuxOs>, + root: Va, + stack: Vec, + done: bool, +} + +impl<'a, Driver> MapleTreeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, root: Va) -> Result { + Ok(Self { + vmi, + root, + stack: Vec::new(), + done: false, + }) + } + + fn offsets(&self) -> &Offsets { + &self.vmi.underlying_os().offsets + } + + fn parent(&self, node: Va) -> Result { + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + + self.vmi.read_va_native(node + __maple_node.parent.offset()) + } + + fn num_slots(&self, node: Va) -> Result { + let address_width = self.vmi.registers().address_width(); + + match mte_node_type(node) { + Some(MapleType::Dense) => { + const MAPLE_NODE_SLOTS_32: u64 = 63; // 32 bit OS + const MAPLE_NODE_SLOTS_64: u64 = 31; // 64 bit OS + + match address_width { + 4 => Ok(MAPLE_NODE_SLOTS_32), + 8 => Ok(MAPLE_NODE_SLOTS_64), + _ => Err(VmiError::NotSupported), + } + } + Some(MapleType::Leaf64 | MapleType::Range64) => { + const MAPLE_RANGE64_SLOTS_32: u64 = 32; // 32 bit OS + const MAPLE_RANGE64_SLOTS_64: u64 = 16; // 64 bit OS + + match address_width { + 4 => Ok(MAPLE_RANGE64_SLOTS_32), + 8 => Ok(MAPLE_RANGE64_SLOTS_64), + _ => Err(VmiError::NotSupported), + } + } + Some(MapleType::Arange64) => { + const MAPLE_ARANGE64_SLOTS_32: u64 = 21; // 32 bit OS + const MAPLE_ARANGE64_SLOTS_64: u64 = 10; // 64 bit OS + + match address_width { + 4 => Ok(MAPLE_ARANGE64_SLOTS_32), + 8 => Ok(MAPLE_ARANGE64_SLOTS_64), + _ => Err(VmiError::NotSupported), + } + } + None => Err(VmiError::NotSupported), + } + } + + fn slot(&self, node: Va, slot: u64) -> Result { + let offsets = self.offsets(); + let __maple_node = &offsets.maple_node; + let __maple_range_64 = &offsets.maple_range_64; + let __maple_arange_64 = &offsets.maple_arange_64; + + let address_width = self.vmi.registers().address_width() as u64; + let array_offset = slot * address_width; + + let offset = match mte_node_type(node) { + Some(MapleType::Dense) => __maple_node.slot.offset() + array_offset, + Some(MapleType::Leaf64) => __maple_range_64.slot.offset() + array_offset, + Some(MapleType::Range64) => __maple_range_64.slot.offset() + array_offset, + Some(MapleType::Arange64) => __maple_arange_64.slot.offset() + array_offset, + _ => return Err(VmiError::NotSupported), + }; + + self.vmi.read_va_native(node + offset) + } + + fn pivot(&self, node: Va, slot: u64) -> Result { + let offsets = self.offsets(); + let __maple_range_64 = &offsets.maple_range_64; + let __maple_arange_64 = &offsets.maple_arange_64; + + let address_width = self.vmi.registers().address_width() as u64; + let array_offset = slot * address_width; + + let offset = match mte_node_type(node) { + Some(MapleType::Leaf64) => __maple_range_64.pivot.offset() + array_offset, + Some(MapleType::Range64) => __maple_range_64.pivot.offset() + array_offset, + Some(MapleType::Arange64) => __maple_arange_64.pivot.offset() + array_offset, + _ => return Err(VmiError::NotSupported), + }; + + Ok(self.vmi.read_va_native(node + offset)?.0) + } + + fn __next(&mut self) -> Result, VmiError> { + Ok(None) + } +} + +impl Iterator for MapleTreeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.__next().transpose() + } +} + +impl FusedIterator for MapleTreeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ +} diff --git a/crates/vmi-os-linux/src/iter/maple_tree_new.rs b/crates/vmi-os-linux/src/iter/maple_tree_new.rs new file mode 100644 index 0000000..6baa5a4 --- /dev/null +++ b/crates/vmi-os-linux/src/iter/maple_tree_new.rs @@ -0,0 +1,474 @@ +//! A MapleTree implementation based on the Linux kernel's data structure. +//! +//! The Maple Tree is a data structure used in the Linux kernel for efficiently storing +//! and searching ranges of values. It's particularly used for managing virtual memory +//! areas (VMAs) in the memory management subsystem. +//! +//! # References +//! +//! - [Linux Kernel Source - maple_tree.c](https://elixir.bootlin.com/linux/v6.10.5/source/lib/maple_tree.c) +//! - [Linux Kernel Source - maple_tree.h](https://elixir.bootlin.com/linux/v6.10.5/source/include/linux/maple_tree.h) +//! - [Process Address Space Documentation](https://students.mimuw.edu.pl/ZSO/Wyklady/04_processes2/ProcessAddressSpace.pdf) +//! - [Maple Tree: Storing Ranges](https://blogs.oracle.com/linux/post/maple-tree-storing-ranges) +//! - [Kernel Documentation - Maple Tree](https://docs.kernel.org/core-api/maple_tree.html) + +#![allow(dead_code)] +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState}; + +use crate::{arch::ArchAdapter, LinuxOs}; + +/// Represents different node types in a Maple Tree. +#[derive(Debug)] +enum MapleType { + /// Dense nodes contain directly addressable slots. + Dense, + + /// Leaf nodes. + Leaf64, + + /// Range nodes. + Range64, + + /// Allocation range nodes. + Arange64, +} + +/// Create an internal entry. +/// @v: Value to turn into an internal entry. +/// +/// Internal entries are used for a number of purposes. Entries 0-255 are +/// used for sibling entries (only 0-62 are used by the current code). 256 +/// is used for the retry entry. 257 is used for the reserved / zero entry. +/// Negative internal entries are used to represent errnos. Node pointers +/// are also tagged as internal entries in some situations. +/// +/// Context: Any context. +/// Return: An XArray internal entry corresponding to this value. +const fn xa_mk_internal(v: u64) -> Va { + Va::new((v << 2) | 2) +} + +/// Extract the value from an internal entry. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: The value which was stored in the internal entry. +const fn xa_to_internal(entry: Va) -> u64 { + entry.0 >> 2 +} + +/// Is the entry an internal entry? +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: %true if the entry is an internal entry. +const fn xa_is_internal(entry: Va) -> bool { + (entry.0 & 3) == 2 +} + +/// Is the entry a zero entry? +/// @entry: Entry retrieved from the XArray +/// +/// The normal API will return NULL as the contents of a slot containing +/// a zero entry. You can only see zero entries by using the advanced API. +/// +/// Return: %true if the entry is a zero entry. +const fn xa_is_zero(entry: Va) -> bool { + const XA_ZERO_ENTRY: Va = xa_mk_internal(257); + entry.0 == XA_ZERO_ENTRY.0 +} + +/// Get value stored in an XArray entry. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: The value stored in the XArray entry. +const fn xa_to_value(entry: Va) -> u64 { + entry.0 >> 1 +} + +/// Determine if an entry is a value. +/// @entry: XArray entry. +/// +/// Context: Any context. +/// Return: True if the entry is a value, false if it is a pointer. +const fn xa_is_value(entry: Va) -> bool { + (entry.0 & 1) == 1 +} + +const fn xa_is_node(entry: Va) -> bool { + xa_is_internal(entry) && entry.0 > 4096 +} + +const fn mt_flags_height(ma_flags: u32) -> u32 { + const MT_FLAGS_HEIGHT_OFFSET: u32 = 0x02; + const MT_FLAGS_HEIGHT_MASK: u32 = 0x7C; + + (ma_flags & MT_FLAGS_HEIGHT_MASK) >> MT_FLAGS_HEIGHT_OFFSET +} + +/// We also reserve values with the bottom two bits set to '10' which are +/// below 4096 +const fn mt_is_reserved(entry: Va /* void* */) -> bool { + const MAPLE_RESERVED_RANGE: u64 = 4096; + (entry.0 < MAPLE_RESERVED_RANGE) && xa_is_internal(entry) +} + +const fn mt_node_max(entry: Va) -> Option { + match mte_node_type(entry) { + Some(MapleType::Dense) => Some(31), + Some(MapleType::Leaf64) => Some(u64::MAX), + Some(MapleType::Range64) => Some(u64::MAX), + Some(MapleType::Arange64) => Some(u64::MAX), + None => None, + } +} + +const fn mte_node_type(entry: Va /* maple_enode */) -> Option { + const MAPLE_NODE_TYPE_MASK: u64 = 0x0F; + const MAPLE_NODE_TYPE_SHIFT: u64 = 0x03; + match (entry.0 >> MAPLE_NODE_TYPE_SHIFT) & MAPLE_NODE_TYPE_MASK { + 0x00 => Some(MapleType::Dense), + 0x01 => Some(MapleType::Leaf64), + 0x02 => Some(MapleType::Range64), + 0x03 => Some(MapleType::Arange64), + _ => None, + } +} + +const fn ma_is_leaf(typ: MapleType) -> bool { + matches!(typ, MapleType::Dense | MapleType::Leaf64) +} + +const fn mte_to_node(entry: Va /* maple_enode */) -> Va { + const MAPLE_NODE_MASK: u64 = 255; + Va::new(entry.0 & !MAPLE_NODE_MASK) +} + +const fn mte_is_leaf(entry: Va /* maple_enode */) -> bool { + match mte_node_type(entry) { + Some(typ) => ma_is_leaf(typ), + None => false, + } +} + +/// An iterator for traversing entries in a MapleTree +pub struct MapleTreeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// VMI state + vmi: VmiState<'a, Driver, LinuxOs>, + + /// The maple tree root VA + root: Va, + + /// Current node + node: Option, + + /// Current index + index: u64, + + /// Current last value (for range) + last: u64, + + /// Min value in current node + min: u64, + + /// Max value in current node + max: u64, + + /// Current offset in node + offset: u8, + + /// Current node type + node_type: Option, + + /// Whether initial setup has been done + initialized: bool, +} + +impl<'a, Driver> MapleTreeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Create a new MapleTreeIterator + pub fn new(vmi: VmiState<'a, Driver, LinuxOs>, root: Va) -> Self { + Self { + vmi, + root, + node: None, + index: 0, + last: u64::MAX, + min: 0, + max: u64::MAX, + offset: 0, + node_type: None, + initialized: false, + } + } + + fn initialize(&mut self) -> Result<(), VmiError> { + if self.initialized { + return Ok(()); + } + + let offsets = &self.vmi.underlying_os().offsets; + let __maple_tree = &offsets.maple_tree; + + let entry = self + .vmi + .read_va_native(self.root + __maple_tree.ma_root.offset())?; + + if !xa_is_node(entry) { + // Handle non-node entry (direct value/zero entry) + self.node = Some(entry); + } + else if !entry.is_null() { + self.node = Some(entry); + self.node_type = mte_node_type(entry); + self.walk_to_first()?; + } + + self.initialized = true; + Ok(()) + } + + fn walk_to_first(&mut self) -> Result<(), VmiError> { + while let Some(node) = self.node { + if mte_is_leaf(node) { + break; + } + + let child = self.read_slot(0)?; + if child.is_null() { + break; + } + + self.node = Some(child); + self.node_type = mte_node_type(child); + self.offset = 0; + } + Ok(()) + } + + fn walk_next(&mut self) -> Result, VmiError> { + let Some(current_node) = self.node + else { + return Ok(None); + }; + + // If we're at a non-node entry + if !xa_is_node(current_node) { + let entry = if xa_is_value(current_node) { + Va::new(xa_to_value(current_node)) + } + else if xa_is_zero(current_node) { + Va::new(xa_to_internal(current_node)) + } + else if !mt_is_reserved(current_node) { + current_node + } + else { + return Ok(None); + }; + + self.offset += 1; // Advance to next slot + + // Move to next entry in current node + let next = match self.node_type { + Some(MapleType::Dense) => self.walk_next_dense()?, + Some(MapleType::Leaf64 | MapleType::Range64) => self.walk_next_range64()?, + Some(MapleType::Arange64) => self.walk_next_arange64()?, + None => None, + }; + + if let Some(next) = next { + self.node = Some(next); + } + else { + self.node = None; + } + + return Ok(Some(entry)); + } + + // Handle node traversal based on type + let next = match self.node_type { + Some(MapleType::Dense) => self.walk_next_dense()?, + Some(MapleType::Leaf64 | MapleType::Range64) => self.walk_next_range64()?, + Some(MapleType::Arange64) => self.walk_next_arange64()?, + None => return Ok(None), + }; + + if let Some(next) = next { + self.node = Some(next); + self.walk_next() + } + else { + Ok(None) + } + } + + fn walk_next_dense(&mut self) -> Result, VmiError> { + let node = mte_to_node(self.node.unwrap()); + let offsets = &self.vmi.underlying_os().offsets; + let __maple_node = &offsets.maple_node; + + // Get next non-null slot + while self.offset < 31 { + // MAPLE_NODE_SLOTS for 64-bit + self.offset += 1; + let slot = self + .vmi + .read_va_native(node + __maple_node.slot.offset() + (self.offset as u64 * 8))?; + if !slot.is_null() { + return Ok(Some(slot)); + } + } + + self.node = None; + Ok(None) + } + + fn walk_next_range64(&mut self) -> Result, VmiError> { + let node = mte_to_node(self.node.unwrap()); + let offsets = &self.vmi.underlying_os().offsets; + let __maple_node = &offsets.maple_node; + let __maple_range_64 = &offsets.maple_range_64; + + let node = node + __maple_node.mr64.offset(); + + // Check if we've reached end of current node + if self.offset >= 16 { + // MAPLE_RANGE64_SLOTS + return Ok(None); + } + + // Read current slot + let slot = self + .vmi + .read_va_native(node + __maple_range_64.slot.offset() + (self.offset as u64 * 8))?; + + // Skip empty slots + if slot.is_null() { + return Ok(None); + } + + // Get range end + let last = if self.offset < 15 { + self.vmi + .read_u64(node + __maple_range_64.pivot.offset() + (self.offset as u64 * 8))? + } + else { + self.max + }; + + self.last = last; + + if mte_is_leaf(self.node.unwrap()) { + Ok(Some(slot)) + } + else { + // Descend into child node + self.node = Some(slot); + self.node_type = mte_node_type(slot); + self.offset = 0; + self.walk_to_first()?; + self.walk_next() + } + } + + fn walk_next_arange64(&mut self) -> Result, VmiError> { + let node = mte_to_node(self.node.unwrap()); + let offsets = &self.vmi.underlying_os().offsets; + let __maple_node = &offsets.maple_node; + let __maple_arange_64 = &offsets.maple_arange_64; + + let node = node + __maple_node.ma64.offset(); + + #[expect(clippy::never_loop)] + while self.offset < 10 { + // MAPLE_ARANGE64_SLOTS for 64-bit + self.offset += 1; + + // Read slot and pivot + let slot = self.vmi.read_va_native( + node + __maple_arange_64.slot.offset() + (self.offset as u64 * 8), + )?; + + if slot.is_null() { + self.node = None; + return Ok(None); + } + + let last = if self.offset < 9 { + self.vmi + .read_u64(node + __maple_arange_64.pivot.offset() + (self.offset as u64 * 8))? + } + else { + self.max + }; + + self.last = last; + if mte_is_leaf(self.node.unwrap()) { + return Ok(Some(slot)); + } + else { + self.node = Some(slot); + self.node_type = mte_node_type(slot); + self.offset = 0; + self.walk_to_first()?; + return self.walk_next(); + } + } + + self.node = None; + Ok(None) + } + + fn read_slot(&self, offset: u8) -> Result { + let node = mte_to_node(self.node.unwrap()); + let offsets = &self.vmi.underlying_os().offsets; + let __maple_node = &offsets.maple_node; + + match self.node_type { + Some(MapleType::Dense) => self + .vmi + .read_va_native(node + __maple_node.slot.offset() + (offset as u64 * 8)), + Some(MapleType::Leaf64 | MapleType::Range64) => { + let node = node + __maple_node.mr64.offset(); + self.vmi.read_va_native( + node + offsets.maple_range_64.slot.offset() + (offset as u64 * 8), + ) + } + Some(MapleType::Arange64) => { + let node = node + __maple_node.ma64.offset(); + self.vmi.read_va_native( + node + offsets.maple_arange_64.slot.offset() + (offset as u64 * 8), + ) + } + None => Ok(Va(0)), + } + } +} + +impl Iterator for MapleTreeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + if !self.initialized { + if let Err(e) = self.initialize() { + return Some(Err(e)); + } + } + + self.walk_next().transpose() + } +} diff --git a/crates/vmi-os-linux/src/iter/mod.rs b/crates/vmi-os-linux/src/iter/mod.rs new file mode 100644 index 0000000..aaa5cc4 --- /dev/null +++ b/crates/vmi-os-linux/src/iter/mod.rs @@ -0,0 +1,8 @@ +mod list; +mod maple_tree; +mod maple_tree_new; + +pub use self::{ + list::ListEntryIterator, maple_tree::MapleTree, + maple_tree_new::MapleTreeIterator as MapleTreeIteratorNew, +}; diff --git a/crates/vmi-os-linux/src/iter/tree.rs b/crates/vmi-os-linux/src/iter/tree.rs new file mode 100644 index 0000000..4a5a9b7 --- /dev/null +++ b/crates/vmi-os-linux/src/iter/tree.rs @@ -0,0 +1,186 @@ +use std::iter::FusedIterator; + +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState}; + +use crate::{arch::ArchAdapter, offsets::OffsetsExt, WindowsOs}; + +/// An iterator for traversing tree nodes. +/// +/// Iterate over nodes in a tree-like structure, specifically `MMADDRESS_NODE` +/// (Windows 7) and `RTL_BALANCED_NODE` (Windows 8.1+). +pub struct TreeNodeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// Current node. + current: Option, + + /// Offset to the left child pointer. + /// + /// Either `MMADDRESS_NODE.LeftChild` or `RTL_BALANCED_NODE.Left`. + offset_left: u64, + + /// Offset to the right child pointer. + /// + /// Either `MMADDRESS_NODE.RightChild` or `RTL_BALANCED_NODE.Right`. + offset_right: u64, + + /// Offset to the parent pointer. + /// + /// Either `MMADDRESS_NODE.Parent` or `RTL_BALANCED_NODE.ParentValue`. + offset_parent: u64, +} + +impl<'a, Driver> TreeNodeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new tree node iterator. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, root: Va) -> Result { + let offsets = &vmi.underlying_os().offsets; + + let (mut current, offset_left, offset_right, offset_parent) = match &offsets.ext { + Some(OffsetsExt::V1(offsets)) => { + let MMADDRESS_NODE = &offsets._MMADDRESS_NODE; + + ( + vmi.read_va_native(root + MMADDRESS_NODE.RightChild.offset())?, + MMADDRESS_NODE.LeftChild.offset(), + MMADDRESS_NODE.RightChild.offset(), + MMADDRESS_NODE.Parent.offset(), + ) + } + Some(OffsetsExt::V2(offsets)) => { + let RTL_BALANCED_NODE = &offsets._RTL_BALANCED_NODE; + + ( + root, + RTL_BALANCED_NODE.Left.offset(), + RTL_BALANCED_NODE.Right.offset(), + RTL_BALANCED_NODE.ParentValue.offset(), + ) + } + None => panic!("OffsetsExt not set"), + }; + + loop { + let left = vmi.read_va_native(current + offset_left)?; + + if left.is_null() { + break; + } + + current = left; + } + + Ok(Self { + vmi, + current: Some(current), + offset_left, + offset_right, + offset_parent, + }) + } + + fn left(&self, node: Va) -> Result { + self.vmi.read_va_native(node + self.offset_left) + } + + fn right(&self, node: Va) -> Result { + self.vmi.read_va_native(node + self.offset_right) + } + + fn parent(&self, node: Va) -> Result { + let result = self.vmi.read_va_native(node + self.offset_parent)?; + + // + // We need to clear the Balance bits from the Parent pointer: + // + // MMADDRESS_NODE: + // union { + // LONG_PTR Balance : 2; + // struct _MMADDRESS_NODE *Parent; + // } + // + // RTL_BALANCED_NODE: + // union { + // UCHAR Red : 1; + // UCHAR Balance : 2; + // ULONG_PTR ParentValue; + // } + // + + Ok(result & !0b11) + } + + fn __next(&mut self) -> Result, VmiError> { + let result = self.current; + + let mut current = match self.current { + Some(current) => current, + None => return Ok(None), + }; + + let right = self.right(current)?; + + if !right.is_null() { + current = right; + + loop { + let left = self.left(current)?; + + if left.is_null() { + self.current = Some(current); + break; + } + + current = left; + } + } + else { + loop { + let parent = self.parent(current)?; + + if parent.is_null() || parent == current { + self.current = None; + break; + } + + let left = self.left(parent)?; + + if left == current { + self.current = Some(parent); + break; + } + + current = parent; + } + } + + Ok(result) + } +} + +impl Iterator for TreeNodeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.__next().transpose() + } +} + +impl FusedIterator for TreeNodeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ +} diff --git a/crates/vmi-os-linux/src/lib.rs b/crates/vmi-os-linux/src/lib.rs index b7720a5..c823301 100644 --- a/crates/vmi-os-linux/src/lib.rs +++ b/crates/vmi-os-linux/src/lib.rs @@ -4,22 +4,48 @@ use std::cell::RefCell; use isr_core::Profile; use vmi_core::{ - os::{ - OsArchitecture, OsExt, OsImageExportedSymbol, OsMapped, OsModule, OsProcess, OsRegion, - OsRegionKind, ProcessId, ProcessObject, ThreadId, ThreadObject, - }, - Architecture, MemoryAccess, Pa, Registers as _, Va, VmiCore, VmiDriver, VmiError, VmiOs, + os::{ProcessObject, ThreadObject}, + Architecture, Va, VmiCore, VmiDriver, VmiError, VmiOs, VmiState, VmiVa as _, }; mod arch; use self::arch::ArchAdapter; -mod maple_tree; -pub use self::maple_tree::MapleTree; +mod comps; +pub use self::comps::{ + LinuxDEntry, LinuxFile, LinuxFsStruct, LinuxImage, LinuxMapped, LinuxMmStruct, LinuxModule, + LinuxPath, LinuxQStr, LinuxTaskStruct, LinuxThread, LinuxVFSMount, LinuxVmAreaStruct, +}; + +mod error; +pub use self::error::LinuxError; + +mod iter; +pub use self::iter::{ListEntryIterator, MapleTree, MapleTreeIteratorNew}; mod offsets; pub use self::offsets::{Offsets, Symbols}; +macro_rules! offset { + ($vmi:expr, $field:ident) => { + &__self(&$vmi).offsets.$field + }; +} + +macro_rules! symbol { + ($vmi:expr, $field:ident) => { + __self(&$vmi).symbols.$field + }; +} + +fn __self<'a, Driver>(vmi: &VmiState<'a, Driver, LinuxOs>) -> &'a LinuxOs +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + vmi.underlying_os() +} + /// VMI operations for the Linux operating system. /// /// `LinuxOs` provides methods and utilities for introspecting a Linux-based @@ -37,7 +63,8 @@ where _marker: std::marker::PhantomData, } -#[allow(non_snake_case, unused_variables)] +//#[expect(non_snake_case, unused_variables)] +#[expect(clippy::needless_lifetimes)] impl LinuxOs where Driver: VmiDriver, @@ -68,46 +95,29 @@ where /// /// This value represents the randomized offset applied to the kernel's base address /// when KASLR is enabled. - pub fn kaslr_offset( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - Driver::Architecture::kaslr_offset(self, vmi, registers) + pub fn kaslr_offset(vmi: VmiState) -> Result { + Driver::Architecture::kaslr_offset(vmi) } /// Retrieves the per-CPU base address for the current CPU. /// /// Linux maintains per-CPU data structures, and this method returns the base /// address for accessing such data on the current processor. - pub fn per_cpu( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Va { - Driver::Architecture::per_cpu(self, vmi, registers) + pub fn per_cpu(vmi: VmiState) -> Va { + Driver::Architecture::per_cpu(vmi) } - /// Resolves a file path from a `struct path` pointer. - /// - /// Takes into account the process's filesystem root when constructing the - /// absolute path. + /// Returns an iterator over a doubly-linked list of `LIST_ENTRY` structures. /// - /// Returns the resolved path as a string if successful, or `None` if the path - /// could not be resolved (e.g., if the root is null). - pub fn d_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - path: Va, // struct path* - ) -> Result, VmiError> { - let root = self.process_fs_root(vmi, registers, process)?; - if root.is_null() { - return Ok(None); - } - - Ok(Some(self.construct_path(vmi, registers, path, root)?)) + /// This method is used to iterate over a doubly-linked list of `LIST_ENTRY` + /// structures in memory. It returns an iterator that yields the virtual + /// addresses of each `LIST_ENTRY` structure in the list. + pub fn linked_list<'a>( + vmi: VmiState<'a, Driver, Self>, + list_head: Va, + offset: u64, + ) -> Result> + 'a, VmiError> { + Ok(ListEntryIterator::new(vmi, list_head, offset)) } /// Constructs a file path string from path components in the kernel. @@ -116,60 +126,29 @@ where /// mount points and filesystem boundaries appropriately. Both the `path` /// and `root` arguments should be pointers to `struct path` objects. pub fn construct_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - path: Va, // struct path* - root: Va, // struct path* + _vmi: VmiState, + path: &LinuxPath, + root: &LinuxPath, ) -> Result { - let __dentry = &self.offsets.dentry; - let __path = &self.offsets.path; - let __vfsmount = &self.offsets.vfsmount; - let __qstr = &self.offsets.qstr; + let mut dentry = path.dentry()?; + let mnt = path.mnt()?; + let root_dentry = root.dentry()?; + let root_mnt = root.mnt()?; + let mnt_mnt_root = mnt.mnt_root()?; let mut result = String::new(); - let mut dentry = vmi.read_va( - registers.address_context(path + __path.dentry.offset), - registers.address_width(), - )?; - - let mnt = vmi.read_va( - registers.address_context(path + __path.mnt.offset), - registers.address_width(), - )?; - - let root_dentry = vmi.read_va( - registers.address_context(root + __path.dentry.offset), - registers.address_width(), - )?; - - let root_mnt = vmi.read_va( - registers.address_context(root + __path.mnt.offset), - registers.address_width(), - )?; - - while dentry != root_dentry || mnt != root_mnt { - let mnt_mnt_root = vmi.read_va( - registers.address_context(mnt + __vfsmount.mnt_root.offset), - registers.address_width(), - )?; - - let dentry_parent = vmi.read_va( - registers.address_context(dentry + __dentry.d_parent.offset), - registers.address_width(), - )?; - - if dentry == mnt_mnt_root || dentry == dentry_parent { + while dentry.va() != root_dentry.va() || mnt.va() != root_mnt.va() { + let dentry_parent = match dentry.parent()? { + Some(dentry) => dentry, + None => break, + }; + + if dentry.va() == mnt_mnt_root.va() || dentry.va() == dentry_parent.va() { break; } - let d_name = vmi.read_va( - registers.address_context(dentry + __dentry.d_name.offset + __qstr.name.offset), - registers.address_width(), - )?; - - let name = vmi.read_string(registers.address_context(d_name))?; + let name = dentry.name()?.unwrap_or_else(|| String::from("")); result.insert_str(0, &name); result.insert(0, '/'); @@ -179,637 +158,124 @@ where Ok(result) } - - /// Constructs an [`OsProcess`] from a `task_struct`. - pub fn task_struct_to_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let __task_struct = &self.offsets.task_struct; - - let id = vmi.read_u32(registers.address_context(process.0 + __task_struct.tgid.offset))?; - let name = match self.process_image_path(vmi, registers, process) { - Ok(Some(name)) => name, - _ => { - vmi.read_string(registers.address_context(process.0 + __task_struct.comm.offset))? - } - }; - let translation_root = self.process_pgd(vmi, registers, process)?; - - Ok(OsProcess { - id: id.into(), - object: process, - name, - translation_root: translation_root.unwrap_or_default(), - }) - } - - /// Gets the process flags from a `task_struct`. - /// - /// Process flags in Linux include information about the process state, - /// such as whether it's exiting, a kernel thread, etc. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return task->flags; - /// ``` - pub fn process_flags( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let __task_struct = &self.offsets.task_struct; - - vmi.read_u32(registers.address_context(process.0 + __task_struct.flags.offset)) - } - - /// Gets the address of `mm_struct` from a `task_struct`. - /// - /// The `mm_struct` contains the memory management information for a process. - /// Kernel threads don't have an `mm_struct` and return a null pointer. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return task->mm; - /// ``` - pub fn process_mm( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let __task_struct = &self.offsets.task_struct; - - vmi.read_va( - registers.address_context(process.0 + __task_struct.mm.offset), - registers.address_width(), - ) - } - - /// Gets the address of `active_mm` from a `task_struct`. - /// - /// The `active_mm` field is used primarily for kernel threads that temporarily - /// need to use a userspace process's address space. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return task->active_mm; - /// ``` - pub fn process_active_mm( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let __task_struct = &self.offsets.task_struct; - - vmi.read_va( - registers.address_context(process.0 + __task_struct.active_mm.offset), - registers.address_width(), - ) - } - - /// Gets the filesystem root of a process. - /// - /// Retrieves the root directory entry from the process's `fs_struct`. - /// This is used for path resolution relative to the process's root. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return task->fs->root; - /// ``` - pub fn process_fs_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - // struct path* - let __task_struct = &self.offsets.task_struct; - let __fs_struct = &self.offsets.fs_struct; - - let fs = vmi.read_va( - registers.address_context(process.0 + __task_struct.fs.offset), - registers.address_width(), - )?; - - Ok(fs + __fs_struct.root.offset) - } - - /// Gets the page directory base (PGD) for a process. - /// - /// The PGD is the top-level structure for virtual address translation. - /// This method handles both regular processes and kernel threads. - /// - /// Returns the physical address of the process's page directory base, - /// or `None` if the process has no address space (e.g., a kernel thread - /// with no borrowed `mm_struct`). - pub fn process_pgd( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result, VmiError> { - let __mm_struct = &self.offsets.mm_struct; - - let mut mm = self.process_mm(vmi, registers, process)?; - if mm.is_null() { - mm = self.process_active_mm(vmi, registers, process)?; - - if mm.is_null() { - return Ok(None); - } - } - - let pgd = vmi.read_va( - registers.address_context(mm + __mm_struct.pgd.offset), - registers.address_width(), - )?; - Ok(Some(vmi.translate_address(registers.address_context(pgd))?)) - } - - /// Gets the path of the executable image for a process. - /// - /// Retrieves the full path to the executable by traversing the process's - /// `mm_struct` and file structures. - /// - /// Returns the executable path as a string, or `None` for special processes - /// like kernel threads or those in the process of exiting. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// if (flags & PF_KTHREAD) { - /// return NULL; - /// } - /// - /// if (flags & PF_EXITING) { - /// return NULL; - /// } - /// - /// return d_path(task->mm->exe_file->f_path); - /// ``` - pub fn process_image_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result, VmiError> { - let __mm_struct = &self.offsets.mm_struct; - let __file = &self.offsets.file; - - let flags = self.process_flags(vmi, registers, process)?; - - const PF_EXITING: u32 = 0x00000004; // getting shut down - const PF_KTHREAD: u32 = 0x00200000; // kernel thread - - if flags & PF_KTHREAD != 0 { - return Ok(None); // self.process_filename(vmi, registers, process); - } - - if flags & PF_EXITING != 0 { - let pid = self.process_id(vmi, registers, process)?; - return Ok(None); - } - - let mm = self.process_mm(vmi, registers, process)?; - let exe_file = vmi.read_va( - registers.address_context(mm + __mm_struct.exe_file.offset), - registers.address_width(), - )?; - - let f_path = exe_file + __file.f_path.offset; - self.d_path(vmi, registers, process, f_path) - } - - /// Converts a VMA (Virtual Memory Area) to an [`OsRegion`] structure. - /// - /// VMAs represent continuous regions of virtual memory in a process's - /// address space. This method extracts information about the memory - /// region's address range, permissions, and backing (file or anonymous). - pub fn process_vm_area_to_region( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - entry: Va, - ) -> Result { - let __vm_area_struct = &self.offsets.vm_area_struct; - let __file = &self.offsets.file; - - let start = vmi.read_va( - registers.address_context(entry + __vm_area_struct.vm_start.offset), - registers.address_width(), - )?; - - let end = vmi.read_va( - registers.address_context(entry + __vm_area_struct.vm_end.offset), - registers.address_width(), - )?; - - let file = vmi.read_va( - registers.address_context(entry + __vm_area_struct.vm_file.offset), - registers.address_width(), - )?; - - let flags = u64::from(vmi.read_va( - registers.address_context(entry + __vm_area_struct.vm_flags.offset), - registers.address_width(), - )?); - - const VM_READ: u64 = 0x00000001; - const VM_WRITE: u64 = 0x00000002; - const VM_EXEC: u64 = 0x00000004; - //const VM_SHARED: u64 = 0x00000008; - - let mut protection = MemoryAccess::default(); - if flags & VM_READ != 0 { - protection |= MemoryAccess::R; - } - if flags & VM_WRITE != 0 { - protection |= MemoryAccess::W; - } - if flags & VM_EXEC != 0 { - protection |= MemoryAccess::X; - } - - let kind = if file.is_null() { - OsRegionKind::Private - } - else { - let f_path = file + __file.f_path.offset; - - let path = self.d_path(vmi, registers, process, f_path); - OsRegionKind::Mapped(OsMapped { path }) - }; - - Ok(OsRegion { - start, - end, - protection, - kind, - }) - } } -#[allow(non_snake_case, unused_variables)] +//#[expect(non_snake_case, unused_variables)] impl VmiOs for LinuxOs where Driver: VmiDriver, Driver::Architecture: Architecture + ArchAdapter, { - fn kernel_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - Driver::Architecture::kernel_image_base(self, vmi, registers) - } - - fn kernel_information_string( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { - unimplemented!() - } - - fn kpti_enabled( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { - unimplemented!() - } - - fn modules( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result, VmiError> { + type Process<'a> = LinuxTaskStruct<'a, Driver>; + type Thread<'a> = LinuxThread; + type Image<'a> = LinuxImage; + type Module<'a> = LinuxModule; + type Region<'a> = LinuxVmAreaStruct<'a, Driver>; + type Mapped<'a> = LinuxMapped; + + fn kernel_image_base(_vmi: VmiState) -> Result { unimplemented!() } - fn system_process( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { + fn kernel_information_string(_vmi: VmiState) -> Result { unimplemented!() } - fn thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - thread: ThreadObject, - ) -> Result { + fn kpti_enabled(_vmi: VmiState) -> Result { unimplemented!() } - fn process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let task_struct = &self.offsets.task_struct; - - let result = - vmi.read_u32(registers.address_context(process.0 + task_struct.tgid.offset))?; - - Ok(ProcessId(result)) - } - - fn current_thread( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - unimplemented!() - } - - fn current_thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let task_struct = &self.offsets.task_struct; - - let process = self.current_process(vmi, registers)?; - - if process.is_null() { - return Err(VmiError::Other("Invalid process")); - } - - let result = vmi.read_u32(registers.address_context(process.0 + task_struct.pid.offset))?; - - Ok(ThreadId(result)) - } - - fn current_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let pcpu_hot_offset = self.symbols.pcpu_hot; - let pcpu_hot = &self.offsets.pcpu_hot; - - let per_cpu = self.per_cpu(vmi, registers); - if per_cpu.is_null() { - return Err(VmiError::Other("Invalid per_cpu")); - } - - let addr = per_cpu + pcpu_hot_offset + pcpu_hot.current_task.offset; - let result = vmi.read_va(registers.address_context(addr), registers.address_width())?; - - Ok(ProcessObject(result)) - } - - fn current_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let process = self.current_process(vmi, registers)?; - - if process.is_null() { - return Err(VmiError::Other("Invalid process")); + fn modules( + _vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + #[expect(unreachable_code)] + { + unimplemented!() as Result, VmiError> } - - self.process_id(vmi, registers, process) } fn processes( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - let init_task_address = self.symbols.init_task; - let task_struct = &self.offsets.task_struct; - - let mut result = Vec::new(); - - let init_task = Va(init_task_address) + self.kaslr_offset(vmi, registers)?; - let tasks = init_task + task_struct.tasks.offset; - - self.enumerate_list(vmi, registers, tasks, |entry| { - let process_object = entry - task_struct.tasks.offset; - - if let Ok(process) = self.task_struct_to_process(vmi, registers, process_object.into()) - { - result.push(process) - } - - true - })?; - - Ok(result) - } - - fn process_parent_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - unimplemented!() - } + vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + let __init_task = symbol!(vmi, init_task); + let __task_struct = &offset!(vmi, task_struct); - fn process_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - unimplemented!() - } + let init_task = Va(__init_task) + Self::kaslr_offset(vmi)?; + let tasks = init_task + __task_struct.tasks.offset(); - fn process_translation_root( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - process: ProcessObject, - ) -> Result { - unimplemented!() + Ok(Self::linked_list(vmi, tasks, __task_struct.tasks.offset())? + .map(move |result| result.map(|entry| LinuxTaskStruct::new(vmi, ProcessObject(entry))))) } - fn process_user_translation_root( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, + fn process( + vmi: VmiState<'_, Driver, Self>, process: ProcessObject, - ) -> Result { - unimplemented!() + ) -> Result, VmiError> { + Ok(LinuxTaskStruct::new(vmi, process)) } - fn process_filename( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let task_struct_comm_offset = 0xBC0; + fn current_process(vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + let pcpu_hot = symbol!(vmi, pcpu_hot); + let __pcpu_hot = offset!(vmi, pcpu_hot); - vmi.read_string(registers.address_context(process.0 + task_struct_comm_offset)) - } - - fn process_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - unimplemented!() - } - - fn process_regions( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result, VmiError> { - let __mm_struct = &self.offsets.mm_struct; - - let mm = self.process_mm(vmi, registers, process)?; - if mm.is_null() { - return Ok(Vec::new()); + let per_cpu = Self::per_cpu(vmi); + if per_cpu.is_null() { + return Err(LinuxError::CorruptedStruct("per_cpu").into()); } - let mut result = Vec::new(); + let addr = per_cpu + pcpu_hot + __pcpu_hot.current_task.offset(); + let result = vmi.read_va_native(addr)?; - let mt = MapleTree::new(vmi, registers, &self.offsets); - mt.enumerate(mm + __mm_struct.mm_mt.offset, |entry| { - if entry.is_null() { - return true; - } - - match self.process_vm_area_to_region(vmi, registers, process, entry) { - Ok(region) => result.push(region), - Err(err) => tracing::warn!(?err, ?entry, "Failed to convert MT entry to region"), - } + Ok(LinuxTaskStruct::new(vmi, ProcessObject(result))) + } - true - })?; + fn system_process(vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + let __init_task = symbol!(vmi, init_task); + let __task_struct = &offset!(vmi, task_struct); - Ok(result) + let init_task = Va(__init_task) + Self::kaslr_offset(vmi)?; + Ok(LinuxTaskStruct::new(vmi, ProcessObject(init_task))) } - fn process_address_is_valid( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError> { + fn thread( + _vmi: VmiState<'_, Driver, Self>, + _thread: ThreadObject, + ) -> Result, VmiError> { unimplemented!() } - fn find_process_region( - &self, - _vmi: &VmiCore, - _registers: &::Registers, - _process: ProcessObject, - _address: Va, - ) -> Result, VmiError> { + fn current_thread(_vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { unimplemented!() } - fn image_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, - image_base: Va, - ) -> Result { + fn image( + _vmi: VmiState<'_, Driver, Self>, + _image_base: Va, + ) -> Result, VmiError> { unimplemented!() } - fn image_exported_symbols( - &self, - vmi: &VmiCore, - registers: &::Registers, - image_base: Va, - ) -> Result, VmiError> { + fn module(_vmi: VmiState<'_, Driver, Self>, _module: Va) -> Result, VmiError> { unimplemented!() } - fn syscall_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result { - Driver::Architecture::syscall_argument(self, vmi, registers, index) + fn region(vmi: VmiState<'_, Driver, Self>, region: Va) -> Result, VmiError> { + Ok(LinuxVmAreaStruct::new(vmi, region)) } - fn function_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result { - Driver::Architecture::function_argument(self, vmi, registers, index) + fn syscall_argument(vmi: VmiState, index: u64) -> Result { + Driver::Architecture::syscall_argument(vmi, index) } - fn function_return_value( - &self, - _vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - Driver::Architecture::function_return_value(self, _vmi, registers) + fn function_argument(vmi: VmiState, index: u64) -> Result { + Driver::Architecture::function_argument(vmi, index) } - fn last_error( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - unimplemented!() - } -} - -impl OsExt for LinuxOs -where - Driver: VmiDriver, - Driver::Architecture: Architecture + ArchAdapter, -{ - fn enumerate_list( - &self, - vmi: &VmiCore, - registers: &::Registers, - list_head: Va, - mut callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError> { - let mut entry = vmi.read_va( - registers.address_context(list_head), - registers.address_width(), - )?; - - while entry != list_head { - if !callback(entry) { - break; - } - - entry = vmi.read_va(registers.address_context(entry), registers.address_width())?; - } - - Ok(()) + fn function_return_value(vmi: VmiState) -> Result { + Driver::Architecture::function_return_value(vmi) } - fn enumerate_tree( - &self, - _vmi: &VmiCore, - _registers: &::Registers, - _root: Va, - _callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError> { + fn last_error(_vmi: VmiState) -> Result, VmiError> { unimplemented!() } } diff --git a/crates/vmi-os-linux/src/lib.rs.old b/crates/vmi-os-linux/src/lib.rs.old new file mode 100644 index 0000000..dfcbdc8 --- /dev/null +++ b/crates/vmi-os-linux/src/lib.rs.old @@ -0,0 +1,741 @@ +//! Linux OS-specific VMI operations. + +use std::cell::RefCell; + +use isr_core::Profile; +use vmi_core::{ + os::{ + OsArchitecture, OsExt, OsImageExportedSymbol, OsMapped, OsModule, OsProcess, OsRegion, + OsRegionKind, ProcessId, ProcessObject, ThreadId, ThreadObject, VmiOsModule, VmiOsProcess, + }, + Architecture, MemoryAccess, Pa, Va, VmiCore, VmiDriver, VmiError, VmiOs, VmiState, +}; + +mod arch; +use self::arch::ArchAdapter; + +mod comps; + +mod maple_tree; +pub use self::maple_tree::MapleTree; + +mod offsets; +pub use self::offsets::{Offsets, Symbols}; + +/// VMI operations for the Linux operating system. +/// +/// `LinuxOs` provides methods and utilities for introspecting a Linux-based +/// virtual machine. It encapsulates Linux-specific knowledge and operations, +/// allowing for high-level interactions with the guest OS structures and processes. +pub struct LinuxOs +where + Driver: VmiDriver, +{ + offsets: Offsets, + symbols: Symbols, + kernel_image_base: RefCell>, + kaslr_offset: RefCell>, + + _marker: std::marker::PhantomData, +} + +#[expect(non_snake_case, unused_variables)] +impl LinuxOs +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new `LinuxOs` instance. + pub fn new(profile: &Profile) -> Result { + Ok(Self { + offsets: Offsets::new(profile)?, + symbols: Symbols::new(profile)?, + kernel_image_base: RefCell::new(None), + kaslr_offset: RefCell::new(None), + _marker: std::marker::PhantomData, + }) + } + + /// Locates and retrieves the Linux banner string from kernel memory. + /// + /// The banner string typically contains kernel version information and build details. + pub fn find_banner( + vmi: &VmiCore, + registers: &::Registers, + ) -> Result, VmiError> { + Driver::Architecture::find_banner(vmi, registers) + } + + /// Returns the KASLR (Kernel Address Space Layout Randomization) offset. + /// + /// This value represents the randomized offset applied to the kernel's base address + /// when KASLR is enabled. + pub fn kaslr_offset(&self, vmi: VmiState) -> Result { + Driver::Architecture::kaslr_offset(vmi, self) + } + + /// Retrieves the per-CPU base address for the current CPU. + /// + /// Linux maintains per-CPU data structures, and this method returns the base + /// address for accessing such data on the current processor. + pub fn per_cpu(&self, vmi: VmiState) -> Va { + Driver::Architecture::per_cpu(vmi, self) + } + + /// Resolves a file path from a `struct path` pointer. + /// + /// Takes into account the process's filesystem root when constructing the + /// absolute path. + /// + /// Returns the resolved path as a string if successful, or `None` if the path + /// could not be resolved (e.g., if the root is null). + pub fn d_path( + &self, + vmi: VmiState, + process: ProcessObject, + path: Va, // struct path* + ) -> Result, VmiError> { + let root = self.process_fs_root(vmi, process)?; + if root.is_null() { + return Ok(None); + } + + Ok(Some(self.construct_path(vmi, path, root)?)) + } + + /// Constructs a file path string from path components in the kernel. + /// + /// This method walks the dentry chain to build a complete path, handling + /// mount points and filesystem boundaries appropriately. Both the `path` + /// and `root` arguments should be pointers to `struct path` objects. + pub fn construct_path( + &self, + vmi: VmiState, + path: Va, // struct path* + root: Va, // struct path* + ) -> Result { + let __dentry = &self.offsets.dentry; + let __path = &self.offsets.path; + let __vfsmount = &self.offsets.vfsmount; + let __qstr = &self.offsets.qstr; + + let mut result = String::new(); + + let mut dentry = vmi.read_va_native(path + __path.dentry.offset)?; + let mnt = vmi.read_va_native(path + __path.mnt.offset)?; + let root_dentry = vmi.read_va_native(root + __path.dentry.offset)?; + let root_mnt = vmi.read_va_native(root + __path.mnt.offset)?; + + while dentry != root_dentry || mnt != root_mnt { + let mnt_mnt_root = vmi.read_va_native(mnt + __vfsmount.mnt_root.offset)?; + let dentry_parent = vmi.read_va_native(dentry + __dentry.d_parent.offset)?; + + if dentry == mnt_mnt_root || dentry == dentry_parent { + break; + } + + let d_name = + vmi.read_va_native(dentry + __dentry.d_name.offset + __qstr.name.offset)?; + + let name = vmi.read_string(d_name)?; + + result.insert_str(0, &name); + result.insert(0, '/'); + + dentry = dentry_parent; + } + + Ok(result) + } + + /// Constructs an [`OsProcess`] from a `task_struct`. + pub fn task_struct_to_process( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let __task_struct = &self.offsets.task_struct; + + let id = vmi.read_u32(process.0 + __task_struct.tgid.offset)?; + let name = match self.process_image_path(vmi, process) { + Ok(Some(name)) => name, + _ => vmi.read_string(process.0 + __task_struct.comm.offset)?, + }; + let translation_root = self.process_pgd(vmi, process)?; + + Ok(OsProcess { + id: id.into(), + object: process, + name, + translation_root: translation_root.unwrap_or_default(), + }) + } + + /// Gets the process flags from a `task_struct`. + /// + /// Process flags in Linux include information about the process state, + /// such as whether it's exiting, a kernel thread, etc. + /// + /// # Equivalent C pseudo-code + /// + /// ```c + /// return task->flags; + /// ``` + pub fn process_flags( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let __task_struct = &self.offsets.task_struct; + + vmi.read_u32(process.0 + __task_struct.flags.offset) + } + + /// Gets the address of `mm_struct` from a `task_struct`. + /// + /// The `mm_struct` contains the memory management information for a process. + /// Kernel threads don't have an `mm_struct` and return a null pointer. + /// + /// # Equivalent C pseudo-code + /// + /// ```c + /// return task->mm; + /// ``` + pub fn process_mm( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let __task_struct = &self.offsets.task_struct; + + vmi.read_va_native(process.0 + __task_struct.mm.offset) + } + + /// Gets the address of `active_mm` from a `task_struct`. + /// + /// The `active_mm` field is used primarily for kernel threads that temporarily + /// need to use a userspace process's address space. + /// + /// # Equivalent C pseudo-code + /// + /// ```c + /// return task->active_mm; + /// ``` + pub fn process_active_mm( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let __task_struct = &self.offsets.task_struct; + + vmi.read_va_native(process.0 + __task_struct.active_mm.offset) + } + + /// Gets the filesystem root of a process. + /// + /// Retrieves the root directory entry from the process's `fs_struct`. + /// This is used for path resolution relative to the process's root. + /// + /// # Equivalent C pseudo-code + /// + /// ```c + /// return task->fs->root; + /// ``` + pub fn process_fs_root( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + // struct path* + let __task_struct = &self.offsets.task_struct; + let __fs_struct = &self.offsets.fs_struct; + + let fs = vmi.read_va_native(process.0 + __task_struct.fs.offset)?; + + Ok(fs + __fs_struct.root.offset) + } + + /// Gets the page directory base (PGD) for a process. + /// + /// The PGD is the top-level structure for virtual address translation. + /// This method handles both regular processes and kernel threads. + /// + /// Returns the physical address of the process's page directory base, + /// or `None` if the process has no address space (e.g., a kernel thread + /// with no borrowed `mm_struct`). + pub fn process_pgd( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result, VmiError> { + let __mm_struct = &self.offsets.mm_struct; + + let mut mm = self.process_mm(vmi, process)?; + if mm.is_null() { + mm = self.process_active_mm(vmi, process)?; + + if mm.is_null() { + return Ok(None); + } + } + + let pgd = vmi.read_va_native(mm + __mm_struct.pgd.offset)?; + Ok(Some(vmi.translate_address(pgd)?)) + } + + /// Gets the path of the executable image for a process. + /// + /// Retrieves the full path to the executable by traversing the process's + /// `mm_struct` and file structures. + /// + /// Returns the executable path as a string, or `None` for special processes + /// like kernel threads or those in the process of exiting. + /// + /// # Equivalent C pseudo-code + /// + /// ```c + /// if (flags & PF_KTHREAD) { + /// return NULL; + /// } + /// + /// if (flags & PF_EXITING) { + /// return NULL; + /// } + /// + /// return d_path(task->mm->exe_file->f_path); + /// ``` + pub fn process_image_path( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result, VmiError> { + let __mm_struct = &self.offsets.mm_struct; + let __file = &self.offsets.file; + + let flags = self.process_flags(vmi, process)?; + + const PF_EXITING: u32 = 0x00000004; // getting shut down + const PF_KTHREAD: u32 = 0x00200000; // kernel thread + + if flags & PF_KTHREAD != 0 { + return Ok(None); // self.process_filename(vmi, process); + } + + if flags & PF_EXITING != 0 { + let pid = self.process_id(vmi, process)?; + return Ok(None); + } + + let mm = self.process_mm(vmi, process)?; + let exe_file = vmi.read_va_native(mm + __mm_struct.exe_file.offset)?; + + let f_path = exe_file + __file.f_path.offset; + self.d_path(vmi, process, f_path) + } + + /// Converts a VMA (Virtual Memory Area) to an [`OsRegion`] structure. + /// + /// VMAs represent continuous regions of virtual memory in a process's + /// address space. This method extracts information about the memory + /// region's address range, permissions, and backing (file or anonymous). + pub fn process_vm_area_to_region( + &self, + vmi: VmiState, + process: ProcessObject, + entry: Va, + ) -> Result { + let __vm_area_struct = &self.offsets.vm_area_struct; + let __file = &self.offsets.file; + + let start = vmi.read_va_native(entry + __vm_area_struct.vm_start.offset)?; + let end = vmi.read_va_native(entry + __vm_area_struct.vm_end.offset)?; + let file = vmi.read_va_native(entry + __vm_area_struct.vm_file.offset)?; + let flags = vmi.read_va_native(entry + __vm_area_struct.vm_flags.offset)?.0; + + const VM_READ: u64 = 0x00000001; + const VM_WRITE: u64 = 0x00000002; + const VM_EXEC: u64 = 0x00000004; + //const VM_SHARED: u64 = 0x00000008; + + let mut protection = MemoryAccess::default(); + if flags & VM_READ != 0 { + protection |= MemoryAccess::R; + } + if flags & VM_WRITE != 0 { + protection |= MemoryAccess::W; + } + if flags & VM_EXEC != 0 { + protection |= MemoryAccess::X; + } + + let kind = if file.is_null() { + OsRegionKind::Private + } + else { + let f_path = file + __file.f_path.offset; + + let path = self.d_path(vmi, process, f_path); + OsRegionKind::Mapped(OsMapped { path }) + }; + + Ok(OsRegion { + start, + end, + protection, + kind, + }) + } +} + +#[expect(non_snake_case, unused_variables)] +impl VmiOs for LinuxOs +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn kernel_image_base(&self, vmi: VmiState) -> Result { + Driver::Architecture::kernel_image_base(vmi, self) + } + + fn kernel_information_string(&self, vmi: VmiState) -> Result { + unimplemented!() + } + + fn kpti_enabled(&self, vmi: VmiState) -> Result { + unimplemented!() + } + + fn modules(&self, vmi: VmiState) -> Result, VmiError> { + unimplemented!() + } + + fn __modules<'a>( + &'a self, + vmi: VmiState<'a, Driver, Self>, + ) -> Result> + 'a, VmiError> { + struct Dummy; + impl VmiOsModule for Dummy { + fn va(&self) -> Va { + unimplemented!() + } + + fn base_address(&self) -> Result { + unimplemented!() + } + + fn size(&self) -> Result { + unimplemented!() + } + + fn name(&self) -> Result { + unimplemented!() + } + } + + Ok(std::iter::empty::>()) + } + + fn system_process(&self, vmi: VmiState) -> Result { + unimplemented!() + } + + fn __system_process<'a>( + &'a self, + vmi: VmiState, + ) -> Result { + struct Dummy; + impl VmiOsProcess for Dummy { + fn id(&self) -> Result { + unimplemented!() + } + + fn object(&self) -> Result { + unimplemented!() + } + + fn name(&self) -> Result { + unimplemented!() + } + + fn parent_id(&self) -> Result { + unimplemented!() + } + + fn architecture(&self) -> Result { + unimplemented!() + } + + fn translation_root(&self) -> Result { + unimplemented!() + } + + fn user_translation_root(&self) -> Result { + unimplemented!() + } + + fn image_base(&self) -> Result { + unimplemented!() + } + + fn regions(&self) -> Result, VmiError> { + unimplemented!() + } + + fn is_valid_address(&self, address: Va) -> Result, VmiError> { + unimplemented!() + } + } + + Ok(std::iter::empty::>()) + } + + fn thread_id( + &self, + vmi: VmiState, + thread: ThreadObject, + ) -> Result { + unimplemented!() + } + + fn process_id( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let task_struct = &self.offsets.task_struct; + + let result = vmi.read_u32(process.0 + task_struct.tgid.offset)?; + + Ok(ProcessId(result)) + } + + fn current_thread(&self, vmi: VmiState) -> Result { + unimplemented!() + } + + fn current_thread_id(&self, vmi: VmiState) -> Result { + let task_struct = &self.offsets.task_struct; + + let process = self.current_process(vmi)?; + + if process.is_null() { + return Err(VmiError::Other("Invalid process")); + } + + let result = vmi.read_u32(process.0 + task_struct.pid.offset)?; + + Ok(ThreadId(result)) + } + + fn current_process(&self, vmi: VmiState) -> Result { + let pcpu_hot_offset = self.symbols.pcpu_hot; + let pcpu_hot = &self.offsets.pcpu_hot; + + let per_cpu = self.per_cpu(vmi); + if per_cpu.is_null() { + return Err(VmiError::Other("Invalid per_cpu")); + } + + let addr = per_cpu + pcpu_hot_offset + pcpu_hot.current_task.offset; + let result = vmi.read_va_native(addr)?; + + Ok(ProcessObject(result)) + } + + fn current_process_id(&self, vmi: VmiState) -> Result { + let process = self.current_process(vmi)?; + + if process.is_null() { + return Err(VmiError::Other("Invalid process")); + } + + self.process_id(vmi, process) + } + + fn processes(&self, vmi: VmiState) -> Result, VmiError> { + let init_task_address = self.symbols.init_task; + let task_struct = &self.offsets.task_struct; + + let mut result = Vec::new(); + + let init_task = Va(init_task_address) + self.kaslr_offset(vmi)?; + let tasks = init_task + task_struct.tasks.offset; + + self.enumerate_list(vmi, tasks, |entry| { + let process_object = entry - task_struct.tasks.offset; + + if let Ok(process) = self.task_struct_to_process(vmi, process_object.into()) { + result.push(process) + } + + true + })?; + + Ok(result) + } + + fn process_parent_process_id( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + unimplemented!() + } + + fn process_architecture( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + unimplemented!() + } + + fn process_translation_root( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + unimplemented!() + } + + fn process_user_translation_root( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + unimplemented!() + } + + fn process_filename( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + let task_struct_comm_offset = 0xBC0; + + vmi.read_string(process.0 + task_struct_comm_offset) + } + + fn process_image_base( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result { + unimplemented!() + } + + fn process_regions( + &self, + vmi: VmiState, + process: ProcessObject, + ) -> Result, VmiError> { + let __mm_struct = &self.offsets.mm_struct; + + let mm = self.process_mm(vmi, process)?; + if mm.is_null() { + return Ok(Vec::new()); + } + + let mut result = Vec::new(); + + let mt = MapleTree::new(&vmi, &self.offsets); + mt.enumerate(mm + __mm_struct.mm_mt.offset, |entry| { + if entry.is_null() { + return true; + } + + match self.process_vm_area_to_region(vmi, process, entry) { + Ok(region) => result.push(region), + Err(err) => tracing::warn!(?err, ?entry, "Failed to convert MT entry to region"), + } + + true + })?; + + Ok(result) + } + + fn process_address_is_valid( + &self, + vmi: VmiState, + process: ProcessObject, + address: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn find_process_region( + &self, + _vmi: VmiState, + _process: ProcessObject, + _address: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn image_architecture( + &self, + vmi: VmiState, + image_base: Va, + ) -> Result { + unimplemented!() + } + + fn image_exported_symbols( + &self, + vmi: VmiState, + image_base: Va, + ) -> Result, VmiError> { + unimplemented!() + } + + fn syscall_argument(&self, vmi: VmiState, index: u64) -> Result { + Driver::Architecture::syscall_argument(vmi, self, index) + } + + fn function_argument(&self, vmi: VmiState, index: u64) -> Result { + Driver::Architecture::function_argument(vmi, self, index) + } + + fn function_return_value(&self, vmi: VmiState) -> Result { + Driver::Architecture::function_return_value(vmi, self) + } + + fn last_error(&self, vmi: VmiState) -> Result, VmiError> { + unimplemented!() + } +} + +impl OsExt for LinuxOs +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn enumerate_list( + &self, + vmi: VmiState, + list_head: Va, + mut callback: impl FnMut(Va) -> bool, + ) -> Result<(), VmiError> { + let mut entry = vmi.read_va_native(list_head)?; + + while entry != list_head { + if !callback(entry) { + break; + } + + entry = vmi.read_va_native(entry)?; + } + + Ok(()) + } + + fn enumerate_tree( + &self, + _vmi: VmiState, + _root: Va, + _callback: impl FnMut(Va) -> bool, + ) -> Result<(), VmiError> { + unimplemented!() + } +} diff --git a/crates/vmi-os-linux/src/offsets/mod.rs b/crates/vmi-os-linux/src/offsets/mod.rs index 049a3ae..78e83d8 100644 --- a/crates/vmi-os-linux/src/offsets/mod.rs +++ b/crates/vmi-os-linux/src/offsets/mod.rs @@ -32,15 +32,15 @@ offsets! { } struct fs_struct { - root: Field, - pwd: Field, + root: Field, // struct path root; + pwd: Field, // struct path pwd; } struct mm_struct { // mmap: Field, - mm_mt: Field, - pgd: Field, - exe_file: Field, + mm_mt: Field, // struct maple_tree mm_mt; + pgd: Field, // pgd_t *pgd; + exe_file: Field, // struct file *exe_file; } struct vm_area_struct { @@ -58,53 +58,62 @@ offsets! { active_mm: Field, pid: Field, tgid: Field, + real_parent: Field, // struct task_struct *real_parent; + parent: Field, // struct task_struct *parent; comm: Field, fs: Field, } struct dentry { - d_name: Field, - d_parent: Field, + d_name: Field, // struct qstr d_name; + d_parent: Field, // struct dentry *d_parent; } struct file { - f_path: Field, + f_path: Field, // struct path f_path; } struct path { - dentry: Field, - mnt: Field, + dentry: Field, // struct dentry *dentry; + mnt: Field, // struct vfsmount *mnt; } struct vfsmount { - mnt_root: Field, + mnt_root: Field, // struct dentry *mnt_root; } struct qstr { - name: Field, - len: Field, + name: Field, // const unsigned char *name; + len: Field, // u32 len; + } + + struct list_head { + next: Field, // struct list_head *next; + prev: Field, // struct list_head *prev; } struct maple_tree { - ma_flags: Field, // unsigned int ma_flags; - ma_root: Field, // void __rcu *ma_root; + ma_flags: Field, // unsigned int ma_flags; + ma_root: Field, // void __rcu *ma_root; } struct maple_node { - parent: Field, - slot: Field, - mr64: Field, - ma64: Field, + parent: Field, // struct maple_pnode *parent; + slot: Field, // void __rcu *slot[MAPLE_NODE_SLOTS]; + mr64: Field, // struct maple_range_64 mr64; + ma64: Field, // struct maple_arange_64 ma64; } struct maple_range_64 { - pivot: Field, // unsigned long pivot[MAPLE_RANGE64_SLOTS - 1]; - slot: Field, // void __rcu *slot[MAPLE_RANGE64_SLOTS]; + parent: Field, // struct maple_pnode *parent; + pivot: Field, // unsigned long pivot[MAPLE_RANGE64_SLOTS - 1]; + slot: Field, // void __rcu *slot[MAPLE_RANGE64_SLOTS]; } struct maple_arange_64 { - pivot: Field, // unsigned long pivot[MAPLE_ARANGE64_SLOTS - 1]; - slot: Field, // void __rcu *slot[MAPLE_ARANGE64_SLOTS]; + parent: Field, // struct maple_pnode *parent; + pivot: Field, // unsigned long pivot[MAPLE_ARANGE64_SLOTS - 1]; + slot: Field, // void __rcu *slot[MAPLE_ARANGE64_SLOTS]; } } } diff --git a/crates/vmi-os-windows/Cargo.toml b/crates/vmi-os-windows/Cargo.toml index 078fe27..5c8d129 100644 --- a/crates/vmi-os-windows/Cargo.toml +++ b/crates/vmi-os-windows/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-os-windows" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } @@ -20,6 +20,7 @@ workspace = true [dependencies] bitflags = { workspace = true, features = ["serde"] } object = { workspace = true } +once_cell = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } zerocopy = { workspace = true } diff --git a/crates/vmi-os-windows/src/arch/amd64.rs b/crates/vmi-os-windows/src/arch/amd64.rs index 1b5dc7b..a4c443e 100644 --- a/crates/vmi-os-windows/src/arch/amd64.rs +++ b/crates/vmi-os-windows/src/arch/amd64.rs @@ -1,13 +1,11 @@ -use object::{FileKind, LittleEndian as LE}; use vmi_arch_amd64::{Amd64, PageTableEntry, PageTableLevel, Registers}; use vmi_core::{ - os::ProcessObject, Architecture as _, Registers as _, Va, VmiCore, VmiDriver, VmiError, + os::{NoOS, VmiOsImage}, + Architecture as _, Va, VmiCore, VmiDriver, VmiError, VmiSession, VmiState, }; use super::ArchAdapter; -use crate::{ - pe::codeview::codeview_from_pe, PeLite32, PeLite64, WindowsKernelInformation, WindowsOs, -}; +use crate::{WindowsImage, WindowsKernelInformation, WindowsOs}; /// An extension trait for [`PageTableEntry`] that provides access to /// Windows-specific fields. @@ -21,11 +19,11 @@ trait WindowsPageTableEntry { impl WindowsPageTableEntry for PageTableEntry { fn windows_prototype(self) -> bool { - self.0 >> 10 & 1 != 0 + (self.0 >> 10) & 1 != 0 } fn windows_transition(self) -> bool { - self.0 >> 11 & 1 != 0 + (self.0 >> 11) & 1 != 0 } } @@ -34,11 +32,11 @@ where Driver: VmiDriver, { fn syscall_argument( - _os: &WindowsOs, - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result { + let registers = vmi.registers(); + match index { 0 => Ok(registers.r10), 1 => Ok(registers.rdx), @@ -47,30 +45,28 @@ where _ => { let index = index + 1; let stack = registers.rsp + index * size_of::() as u64; - vmi.read_u64(registers.address_context(stack.into())) + vmi.read_u64(stack.into()) } } } fn function_argument( - _os: &WindowsOs, - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result { + let registers = vmi.registers(); + if registers.cs.access.long_mode() { - function_argument_x64(vmi, registers, index) + function_argument_x64(vmi, index) } else { - function_argument_x86(vmi, registers, index) + function_argument_x86(vmi, index) } } - fn function_return_value( - _os: &WindowsOs, - _vmi: &VmiCore, - registers: &Registers, - ) -> Result { + fn function_return_value(vmi: VmiState>) -> Result { + let registers = vmi.registers(); + Ok(registers.rax) } @@ -81,6 +77,9 @@ where /// Maximum backward search distance for the kernel image base. const MAX_BACKWARD_SEARCH: u64 = 32 * 1024 * 1024; + let session = VmiSession::new(vmi, &NoOS); + let vmi = session.with_registers(registers); + // Align MSR_LSTAR to 4KB. let lstar = registers.msr_lstar & Amd64::PAGE_MASK; @@ -97,9 +96,9 @@ where // Ignore page faults. // - match vmi.read(registers.address_context(base_address), &mut data) { + match vmi.read(base_address, &mut data) { Ok(()) => {} - Err(VmiError::PageFault(_)) => continue, + Err(VmiError::Translation(_)) => continue, Err(err) => return Err(err), } @@ -108,50 +107,13 @@ where } tracing::debug!(%base_address, "found MZ"); - match FileKind::parse(&data[..]) { - Ok(FileKind::Pe32) => { - let pe = PeLite32::parse(&data).map_err(|err| VmiError::Os(err.into()))?; - if let Some(codeview) = - codeview_from_pe(vmi, registers.address_context(base_address), &pe)? - { - let optional_header = &pe.nt_headers.optional_header; - - return Ok(Some(WindowsKernelInformation { - base_address, - version_major: optional_header.major_operating_system_version.get(LE), - version_minor: optional_header.minor_operating_system_version.get(LE), - codeview, - })); - } - else { - tracing::warn!("No codeview found"); - } - } - Ok(FileKind::Pe64) => { - let pe = PeLite64::parse(&data).map_err(|err| VmiError::Os(err.into()))?; - if let Some(codeview) = - codeview_from_pe(vmi, registers.address_context(base_address), &pe)? - { - let optional_header = &pe.nt_headers.optional_header; - - return Ok(Some(WindowsKernelInformation { - base_address, - version_major: optional_header.major_operating_system_version.get(LE), - version_minor: optional_header.minor_operating_system_version.get(LE), - codeview, - })); - } - else { - tracing::warn!("No codeview found"); - } - } - Ok(kind) => { - tracing::warn!(?kind, "Unsupported architecture"); - } - Err(err) => { - tracing::warn!(%err, "Error parsing PE"); - } - } + + let image = WindowsImage::new_without_os(vmi, base_address); + match image_codeview(&image) { + Ok(Some(result)) => return Ok(Some(result)), + Ok(None) => tracing::warn!("No codeview found"), + Err(err) => tracing::warn!(%err, "Error parsing PE"), + }; } tracing::warn!( @@ -162,56 +124,30 @@ where Ok(None) } - fn kernel_image_base( - os: &WindowsOs, - _vmi: &VmiCore, - registers: &Registers, - ) -> Result { - let KiSystemCall64 = os.symbols.KiSystemCall64; - - if let Some(kernel_image_base) = *os.kernel_image_base.borrow() { - return Ok(kernel_image_base); - } + fn kernel_image_base(vmi: VmiState>) -> Result { + vmi.underlying_os() + .kernel_image_base + .get_or_try_init(|| { + let KiSystemCall64 = vmi.underlying_os().symbols.KiSystemCall64; - let kernel_image_base = Va::new(registers.msr_lstar - KiSystemCall64); - *os.kernel_image_base.borrow_mut() = Some(kernel_image_base); - Ok(kernel_image_base) + let registers = vmi.registers(); + Ok(Va(registers.msr_lstar - KiSystemCall64)) + }) + .copied() } - fn process_address_is_valid( - os: &WindowsOs, - vmi: &VmiCore, - registers: &Registers, - process: ProcessObject, + fn is_page_present_or_transition( + vmi: VmiState>, address: Va, - ) -> Result, VmiError> { - // - // So, the logic is roughly as follows: - // - Translate the address and try to find the page table entry. - // - If the page table entry is found: - // - If the page is present, the address is valid. - // - If the page is in transition AND not a prototype, the address is valid. - // - Find the VAD for the address. - // - If the VAD is not found, the address is invalid. - // - If the VadType is VadImageMap, the address is valid. - // - If the VadType is not VadImageMap, we don't care (VadAwe, physical - // memory, ...). - // - If the PrivateMemory bit is not set, the address is invalid. - // - If the MemCommit bit is not set, the address is invalid. - // - // References: - // - MmAccessFault - // - MiDispatchFault - // - MiQueryAddressState - // - MiCheckVirtualAddress - // - - let translation = Amd64::translation(vmi, address, registers.cr3.into()); + ) -> Result { + let registers = vmi.registers(); + + let translation = Amd64::translation(vmi.core(), address, registers.cr3.into()); if let Some(entry) = translation.entries().last() { if entry.level == PageTableLevel::Pt { if entry.entry.present() { // The address is valid if the page is present. - return Ok(Some(true)); + return Ok(true); } else if entry.entry.windows_transition() && !entry.entry.windows_prototype() { // The Transition bit being 1 indicates that the page is in a transitional @@ -225,51 +161,17 @@ where // This state is part of Windows' memory management optimization. // It allows the system to keep pages in memory that might be needed // again soon, without consuming the working set quota of processes. - return Ok(Some(true)); + return Ok(true); } } } - // - // TODO: The code below should be moved to a separate architecture-independent - // function. - // - - let vad_va = match os.find_process_vad(vmi, registers, process, address)? { - Some(vad_va) => vad_va, - None => return Ok(Some(false)), - }; - - const MM_ZERO_ACCESS: u8 = 0; // this value is not used. - const MM_DECOMMIT: u8 = 0x10; // NO_ACCESS, Guard page - const MM_NOACCESS: u8 = 0x18; // NO_ACCESS, Guard_page, nocache. - - const VadImageMap: u8 = 2; - - let vad = os.vad(vmi, registers, vad_va)?; - - if matches!(vad.protection, MM_ZERO_ACCESS | MM_DECOMMIT | MM_NOACCESS) { - return Ok(Some(false)); - } - - Ok(Some( - // Private memory must be committed. - (vad.private_memory && vad.mem_commit) || - - // Non-private memory must be mapped from an image. - // Note that this isn't actually correct, because - // some parts of the image might not be committed, - // or they can have different protection than the VAD. - // - // However, figuring out the correct protection would - // be quite complex, so we just assume that the image - // is always committed and has the same protection as - // the VAD. - (!vad.private_memory && vad.vad_type == VadImageMap), - )) + Ok(false) } - fn current_kpcr(_os: &WindowsOs, _vmi: &VmiCore, registers: &Registers) -> Va { + fn current_kpcr(vmi: VmiState>) -> Va { + let registers = vmi.registers(); + if registers.cs.selector.request_privilege_level() != 0 || (registers.gs.base & (1 << 47)) == 0 { @@ -281,27 +183,97 @@ where } } +/* + +fn find_kernel_slow( + vmi: VmiState, +) -> Result, VmiError> +where + Driver: VmiDriver, +{ + tracing::debug!("performing slow kernel search"); + + let info = vmi.info()?; + + for gfn in 0..info.max_gfn.0 { + let gfn = Gfn(gfn); + + let data = match vmi.read_page(gfn) { + Ok(page) => page, + Err(_) => continue, + }; + + if &data[..2] != b"MZ" { + continue; + } + + let image = WindowsImage::new_from_pa(vmi, Amd64::pa_from_gfn(gfn)); + let result = match image_codeview(&image) { + Ok(Some(result)) => result, + _ => continue, + }; + + if !result.codeview.path.starts_with("nt") { + continue; + } + + return Ok(Some(result)); + } + + Ok(None) +} + +*/ + +fn image_codeview( + image: &WindowsImage, +) -> Result, VmiError> +where + Driver: VmiDriver, +{ + let debug_directory = match image.debug_directory()? { + Some(debug_directory) => debug_directory, + None => return Ok(None), + }; + + let codeview = match debug_directory.codeview()? { + Some(codeview) => codeview, + None => return Ok(None), + }; + + let optional_header = image.nt_headers()?.optional_header(); + + Ok(Some(WindowsKernelInformation { + base_address: image.base_address(), + version_major: optional_header.major_operating_system_version(), + version_minor: optional_header.minor_operating_system_version(), + codeview, + })) +} + fn function_argument_x86( - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result where Driver: VmiDriver, { + let registers = vmi.registers(); + let index = index + 1; let stack = registers.rsp + index * size_of::() as u64; - Ok(vmi.read_u32(registers.address_context(stack.into()))? as u64) + Ok(vmi.read_u32(stack.into())? as u64) } fn function_argument_x64( - vmi: &VmiCore, - registers: &Registers, + vmi: VmiState>, index: u64, ) -> Result where Driver: VmiDriver, { + let registers = vmi.registers(); + match index { 0 => Ok(registers.rcx), 1 => Ok(registers.rdx), @@ -310,7 +282,7 @@ where _ => { let index = index + 1; let stack = registers.rsp + index * size_of::() as u64; - vmi.read_u64(registers.address_context(stack.into())) + vmi.read_u64(stack.into()) } } } diff --git a/crates/vmi-os-windows/src/arch/mod.rs b/crates/vmi-os-windows/src/arch/mod.rs index 52fc812..c3858c7 100644 --- a/crates/vmi-os-windows/src/arch/mod.rs +++ b/crates/vmi-os-windows/src/arch/mod.rs @@ -1,6 +1,6 @@ mod amd64; -use vmi_core::{os::ProcessObject, Architecture, Va, VmiCore, VmiDriver, VmiError}; +use vmi_core::{Architecture, Va, VmiCore, VmiDriver, VmiError, VmiState}; use crate::{WindowsKernelInformation, WindowsOs}; @@ -10,47 +10,28 @@ where Driver: VmiDriver, { fn syscall_argument( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, + vmi: VmiState>, index: u64, ) -> Result; fn function_argument( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, + vmi: VmiState>, index: u64, ) -> Result; - fn function_return_value( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn function_return_value(vmi: VmiState>) -> Result; fn find_kernel( vmi: &VmiCore, registers: &::Registers, ) -> Result, VmiError>; - fn kernel_image_base( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result; + fn kernel_image_base(vmi: VmiState>) -> Result; - fn process_address_is_valid( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, + fn is_page_present_or_transition( + vmi: VmiState>, address: Va, - ) -> Result, VmiError>; + ) -> Result; - fn current_kpcr( - os: &WindowsOs, - vmi: &VmiCore, - registers: &::Registers, - ) -> Va; + fn current_kpcr(vmi: VmiState>) -> Va; } diff --git a/crates/vmi-os-windows/src/comps/control_area.rs b/crates/vmi-os-windows/src/comps/control_area.rs new file mode 100644 index 0000000..32ea877 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/control_area.rs @@ -0,0 +1,91 @@ +use vmi_core::{os::VmiOsMapped, Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, object::WindowsFileObject}; +use crate::{ArchAdapter, WindowsOs}; + +/// A Windows control area. +/// +/// A control area is a kernel structure that describes a mapped section +/// of memory, typically associated with file-backed or pagefile-backed sections. +/// It manages shared pages and tracks section usage. +/// +/// # Implementation Details +/// +/// Corresponds to `_CONTROL_AREA`. +pub struct WindowsControlArea<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_CONTROL_AREA` structure. + va: Va, +} + +impl VmiVa for WindowsControlArea<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsControlArea<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows control area. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the file object associated with the control area. + /// + /// # Implementation Details + /// + /// Corresponds to `_CONTROL_AREA.FilePointer` (with reference count masked out). + pub fn file_object(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let EX_FAST_REF = &offsets._EX_FAST_REF; + let CONTROL_AREA = &offsets._CONTROL_AREA; + + let file_pointer = self + .vmi + .read_va_native(self.va + CONTROL_AREA.FilePointer.offset())?; + + // The file pointer is in fact an `_EX_FAST_REF` structure, + // where the low bits are used to store the reference count. + debug_assert_eq!(EX_FAST_REF.RefCnt.offset(), 0); + debug_assert_eq!(EX_FAST_REF.RefCnt.bit_position(), 0); + let file_pointer = file_pointer & !((1 << EX_FAST_REF.RefCnt.bit_length()) - 1); + //let file_pointer = file_pointer & !0xf; + + if file_pointer.is_null() { + return Ok(None); + } + + Ok(Some(WindowsFileObject::new(self.vmi, file_pointer))) + } +} + +impl<'a, Driver> VmiOsMapped<'a, Driver> for WindowsControlArea<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + fn path(&self) -> Result, VmiError> { + match self.file_object()? { + Some(file_object) => Ok(Some(file_object.filename()?)), + None => Ok(None), + } + } +} diff --git a/crates/vmi-os-windows/src/comps/handle_table.rs b/crates/vmi-os-windows/src/comps/handle_table.rs new file mode 100644 index 0000000..772bbb5 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/handle_table.rs @@ -0,0 +1,211 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, WindowsHandleTableEntry}; +use crate::{ArchAdapter, WindowsOs}; + +/// A Windows handle table. +/// +/// A handle table in Windows tracks handles to kernel objects +/// for a specific process, allowing access control and management. +/// +/// # Implementation Details +/// +/// Corresponds to `_HANDLE_TABLE`. +pub struct WindowsHandleTable<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the handle table. + va: Va, + + /// Corresponds to `_HANDLE_TABLE.TableCode`. + table_code: OnceCell, + + /// Corresponds to `_HANDLE_TABLE.NextHandleNeedingPool`. + next_handle_needing_pool: OnceCell, +} + +impl VmiVa for WindowsHandleTable<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsHandleTable<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows module object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { + vmi, + va, + table_code: OnceCell::new(), + next_handle_needing_pool: OnceCell::new(), + } + } + + /// Returns the table code of the handle table. + /// + /// # Notes + /// + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `_HANDLE_TABLE.TableCode`. + pub fn table_code(&self) -> Result { + self.table_code + .get_or_try_init(|| { + let offsets = self.offsets(); + let HANDLE_TABLE = &offsets._HANDLE_TABLE; + + self.vmi.read_field(self.va, &HANDLE_TABLE.TableCode) + }) + .copied() + } + + /// Returns the next handle needing pool. + /// + /// This value tracks the next handle slot that requires additional pool + /// allocation. + /// + /// # Notes + /// + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `_HANDLE_TABLE.NextHandleNeedingPool`. + pub fn next_handle_needing_pool(&self) -> Result { + self.next_handle_needing_pool + .get_or_try_init(|| { + let offsets = self.offsets(); + let HANDLE_TABLE = &offsets._HANDLE_TABLE; + + self.vmi + .read_field(self.va, &HANDLE_TABLE.NextHandleNeedingPool) + }) + .copied() + } + + /// Iterates over all handle table entries. + /// + /// Returns an iterator over all handle table entries that have a valid + /// object pointer. The iterator yields a tuple containing the handle + /// value and the handle table entry. + /// + /// # Implementation Details + /// + /// The functionality is similar to the Windows kernel's internal + /// `ExpSnapShotHandleTables()` function. + pub fn iter( + &self, + ) -> Result)>, VmiError> { + const HANDLE_VALUE_INC: u64 = 4; + + let mut entries = Vec::new(); + let mut handle = 0; + + loop { + let entry = match self.lookup(handle)? { + Some(entry) => entry, + None => break, + }; + + if entry.object()?.is_some() { + entries.push((handle, entry)); + } + + handle += HANDLE_VALUE_INC; + } + + Ok(entries.into_iter()) + } + + /// Performs a lookup in the handle table to find the address of a handle + /// table entry. + /// + /// Implements the multi-level handle table lookup algorithm used by + /// Windows. Returns the virtual address of the handle table entry. + /// + /// # Implementation Details + /// + /// The functionality is similar to the Windows kernel's internal + /// `ExpLookupHandleTableEntry()` function. + pub fn lookup( + &self, + handle: u64, + ) -> Result>, VmiError> { + const SIZEOF_POINTER: u64 = 8; + const SIZEOF_HANDLE_TABLE_ENTRY: u64 = 16; + + const LOWLEVEL_COUNT: u64 = 256; // (TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY)) + const MIDLEVEL_COUNT: u64 = 512; // (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY)) + + const LEVEL_CODE_MASK: u64 = 3; + const HANDLE_VALUE_INC: u64 = 4; + + // The 2 least significant bits of a handle are available to the + // application and are ignored by the system. + let mut index = handle & !0b11; + + // See if this can be a valid handle given the table levels. + if index >= self.next_handle_needing_pool()? { + return Ok(None); + } + + let table_code = self.table_code()?; + let level = table_code & LEVEL_CODE_MASK; + let table = Va(table_code - level); + + let entry = match level { + 0 => table + index * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC), + + 1 => { + let table2 = table; + let i = index % (LOWLEVEL_COUNT * HANDLE_VALUE_INC); + + index -= i; + let j = index / (LOWLEVEL_COUNT * HANDLE_VALUE_INC); + + let table1 = self.vmi.read_va_native(table2 + j * SIZEOF_POINTER)?; + + table1 + i * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC) + } + + 2 => { + let table3 = table; + let i = index % (LOWLEVEL_COUNT * HANDLE_VALUE_INC); + + index -= i; + let mut k = index / (LOWLEVEL_COUNT * HANDLE_VALUE_INC); + + let j = k % MIDLEVEL_COUNT; + k -= j; + k /= MIDLEVEL_COUNT; + + let table2 = self.vmi.read_va_native(table3 + k * SIZEOF_POINTER)?; + let table1 = self.vmi.read_va_native(table2 + j * SIZEOF_POINTER)?; + + table1 + i * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC) + } + + _ => unreachable!(), + }; + + Ok(Some(WindowsHandleTableEntry::new(self.vmi, entry))) + } +} diff --git a/crates/vmi-os-windows/src/comps/handle_table_entry.rs b/crates/vmi-os-windows/src/comps/handle_table_entry.rs new file mode 100644 index 0000000..fc57f5c --- /dev/null +++ b/crates/vmi-os-windows/src/comps/handle_table_entry.rs @@ -0,0 +1,239 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{ + macros::{impl_offsets, impl_offsets_ext_v1, impl_offsets_ext_v2}, + WindowsObject, +}; +use crate::{ArchAdapter, OffsetsExt, WindowsOs}; + +/// A Windows handle table entry. +/// +/// A handle table entry maps a handle to a kernel object +/// within the process's handle table. +/// +/// # Implementation Details +/// +/// Corresponds to `_HANDLE_TABLE_ENTRY`. +pub struct WindowsHandleTableEntry<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + inner: Inner<'a, Driver>, +} + +impl VmiVa for WindowsHandleTableEntry<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + match &self.inner { + Inner::V1(inner) => inner.va, + Inner::V2(inner) => inner.va, + } + } +} + +impl<'a, Driver> WindowsHandleTableEntry<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new Windows handle table entry. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + let inner = match vmi.underlying_os().offsets.ext() { + Some(OffsetsExt::V1(_)) => Inner::V1(WindowsHandleTableEntryV1::new(vmi, va)), + Some(OffsetsExt::V2(_)) => Inner::V2(WindowsHandleTableEntryV2::new(vmi, va)), + None => unimplemented!(), + }; + + Self { inner } + } + + /// Returns the object associated with this handle. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_HEADER.Object` or `_OBJECT_HEADER.ObjectPointerBits`. + pub fn object(&self) -> Result>, VmiError> { + match &self.inner { + Inner::V1(inner) => inner.object(), + Inner::V2(inner) => inner.object(), + } + } + + /// Returns the handle attributes. + /// + /// # Implementation Details + /// + /// Corresponds to `_HANDLE_TABLE_ENTRY.ObAttributes` or `_HANDLE_TABLE_ENTRY.Attributes`. + pub fn attributes(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.attributes(), + Inner::V2(inner) => inner.attributes(), + } + } + + /// Returns the granted access rights for this handle. + /// + /// # Implementation Details + /// + /// Corresponds to `_HANDLE_TABLE_ENTRY.GrantedAccess` or `_HANDLE_TABLE_ENTRY.GrantedAccessBits`. + pub fn granted_access(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.granted_access(), + Inner::V2(inner) => inner.granted_access(), + } + } +} + +enum Inner<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + V1(WindowsHandleTableEntryV1<'a, Driver>), + V2(WindowsHandleTableEntryV2<'a, Driver>), +} + +const OBJ_PROTECT_CLOSE: u64 = 0x00000001; +const OBJ_INHERIT: u64 = 0x00000002; +const OBJ_AUDIT_OBJECT_CLOSE: u64 = 0x00000004; +const OBJ_HANDLE_ATTRIBUTES: u64 = OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE; + +struct WindowsHandleTableEntryV1<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_HANDLE_TABLE_ENTRY` structure. + va: Va, +} + +impl<'a, Driver> WindowsHandleTableEntryV1<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + impl_offsets_ext_v1!(); + + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + fn object(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let offsets_ext = self.offsets_ext(); + + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + let OBJECT_HEADER = &offsets._OBJECT_HEADER; + + let object = self.vmi.read_field(self.va, &HANDLE_TABLE_ENTRY.Object)?; + let object = Va(object & !OBJ_HANDLE_ATTRIBUTES); + + if object.is_null() { + return Ok(None); + } + + let object = object + OBJECT_HEADER.Body.offset(); + + Ok(Some(WindowsObject::new(self.vmi, object))) + } + + fn attributes(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + + let attributes = self + .vmi + .read_field(self.va, &HANDLE_TABLE_ENTRY.ObAttributes)?; + let attributes = (attributes & OBJ_HANDLE_ATTRIBUTES) as u32; + + Ok(attributes) + } + + fn granted_access(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + + Ok(self + .vmi + .read_field(self.va, &HANDLE_TABLE_ENTRY.GrantedAccess)? as u32) + } +} + +struct WindowsHandleTableEntryV2<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_HANDLE_TABLE_ENTRY` structure. + va: Va, +} + +impl<'a, Driver> WindowsHandleTableEntryV2<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + impl_offsets_ext_v2!(); + + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + fn object(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let offsets_ext = self.offsets_ext(); + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + let OBJECT_HEADER = &offsets._OBJECT_HEADER; + + let object_pointer_bits = self + .vmi + .read_field(self.va, &HANDLE_TABLE_ENTRY.ObjectPointerBits)?; + + let object_pointer_bits = HANDLE_TABLE_ENTRY + .ObjectPointerBits + .extract(object_pointer_bits); + + if object_pointer_bits == 0 { + return Ok(None); + } + + let object = Va(0xffff_0000_0000_0000 | (object_pointer_bits << 4)); + let object = object + OBJECT_HEADER.Body.offset(); + + Ok(Some(WindowsObject::new(self.vmi, object))) + } + + fn attributes(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + + let attributes = self + .vmi + .read_field(self.va, &HANDLE_TABLE_ENTRY.Attributes)?; + + Ok(HANDLE_TABLE_ENTRY.Attributes.extract(attributes) as u32) + } + + fn granted_access(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let HANDLE_TABLE_ENTRY = &offsets_ext._HANDLE_TABLE_ENTRY; + + let granted_access = self + .vmi + .read_field(self.va, &HANDLE_TABLE_ENTRY.GrantedAccessBits)?; + + Ok(HANDLE_TABLE_ENTRY.GrantedAccessBits.extract(granted_access) as u32) + } +} diff --git a/crates/vmi-os-windows/src/comps/image.rs b/crates/vmi-os-windows/src/comps/image.rs new file mode 100644 index 0000000..0720cd7 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/image.rs @@ -0,0 +1,193 @@ +use object::{ + endian::LittleEndian as LE, + pe::{ImageDataDirectory, IMAGE_DIRECTORY_ENTRY_DEBUG, IMAGE_DIRECTORY_ENTRY_EXPORT}, + read::pe::ExportTarget, +}; +use once_cell::unsync::OnceCell; +use vmi_core::{ + os::{VmiOsImage, VmiOsImageArchitecture, VmiOsImageSymbol}, + Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +use crate::{ + pe::{ + ImageDosHeader, ImageNtHeaders, ImageOptionalHeader, Pe, PeDebugDirectory, + PeExportDirectory, + }, + ArchAdapter, WindowsError, WindowsOs, +}; + +/// A Windows executable image (PE). +/// +/// A Windows image is an executable or DLL mapped into memory. +pub struct WindowsImage<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state (without the OS context). + /// + /// The OS context is omitted so that the image can be used + /// in [`ArchAdapter::find_kernel`], where the OS context is + /// not available. + pub(crate) vmi: VmiState<'a, Driver>, + + /// The base address of the image. + va: Va, + + /// Cached PE parser. + pe: OnceCell, +} + +impl VmiVa for WindowsImage<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsImage<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + const MAX_DATA_DIRECTORY_SIZE: u32 = 1024 * 1024; // 1MB + + /// Creates a new Windows image. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self::new_without_os(vmi.without_os(), va) + } + + /// Creates a new Windows image without the OS context. + pub(crate) fn new_without_os(vmi: VmiState<'a, Driver>, va: Va) -> Self { + Self { + vmi, + va, + pe: OnceCell::new(), + } + } + + /// Returns the DOS header. + pub fn dos_header(&self) -> Result<&ImageDosHeader, VmiError> { + Ok(self.pe()?.dos_header()) + } + + /// Returns the NT headers. + pub fn nt_headers(&self) -> Result<&ImageNtHeaders, VmiError> { + Ok(self.pe()?.nt_headers()) + } + + /// Returns the debug directory. + /// + /// # Implementation Details + /// + /// Corresponds to `_IMAGE_OPTIONAL_HEADER.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]`. + pub fn debug_directory(&'a self) -> Result>, VmiError> { + let entry = match self.find_data_directory(IMAGE_DIRECTORY_ENTRY_DEBUG)? { + Some(entry) => entry, + None => return Ok(None), + }; + + let data = self.read_data_directory(&entry)?; + + Ok(Some(PeDebugDirectory::new(self, entry, data))) + } + + /// Returns the export directory. + /// + /// # Implementation Details + /// + /// Corresponds to `_IMAGE_OPTIONAL_HEADER.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]`. + pub fn export_directory(&self) -> Result>, VmiError> { + let entry = match self.find_data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT)? { + Some(entry) => entry, + None => return Ok(None), + }; + + let data = self.read_data_directory(&entry)?; + + Ok(Some(PeExportDirectory::new(self, entry, data))) + } + + /// Finds the specified data directory entry. + fn find_data_directory(&self, index: usize) -> Result, VmiError> { + let entry = match self.pe()?.data_directories().get(index).copied() { + Some(entry) => entry, + None => return Ok(None), + }; + + if entry.virtual_address.get(LE) == 0 + || entry.size.get(LE) == 0 + || entry.size.get(LE) > Self::MAX_DATA_DIRECTORY_SIZE + { + return Ok(None); + } + + Ok(Some(entry)) + } + + /// Reads the contents of a data directory entry. + fn read_data_directory(&self, entry: &ImageDataDirectory) -> Result, VmiError> { + let mut data = vec![0; entry.size.get(LE) as usize]; + self.vmi + .read(self.va + entry.virtual_address.get(LE) as u64, &mut data)?; + + Ok(data) + } + + /// Returns the parsed PE representation of the image. + fn pe(&self) -> Result<&Pe, VmiError> { + self.pe.get_or_try_init(|| { + let mut data = vec![0; Driver::Architecture::PAGE_SIZE as usize]; + self.vmi.read(self.va, &mut data)?; + Ok(Pe::new(&data).map_err(WindowsError::from)?) + }) + } +} + +impl<'a, Driver> VmiOsImage<'a, Driver> for WindowsImage<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + /// Returns the base address of the image. + fn base_address(&self) -> Va { + self.va + } + + /// Returns the target architecture for which the image was compiled. + fn architecture(&self) -> Result, VmiError> { + match self.pe()?.nt_headers().optional_header() { + ImageOptionalHeader::ImageOptionalHeader32(_) => Ok(Some(VmiOsImageArchitecture::X86)), + ImageOptionalHeader::ImageOptionalHeader64(_) => { + Ok(Some(VmiOsImageArchitecture::Amd64)) + } + } + } + + /// Returns the exported symbols. + fn exports(&self) -> Result, VmiError> { + let directory = match self.export_directory()? { + Some(directory) => directory, + None => return Ok(Vec::new()), + }; + + let exports = directory.exports().map_err(WindowsError::from)?; + + Ok(exports + .into_iter() + .filter_map(|export| match export.target { + ExportTarget::Address(address) => Some(VmiOsImageSymbol { + name: String::from_utf8_lossy(export.name?).to_string(), + address: self.va + address as u64, + }), + _ => None, + }) + .collect()) + } +} diff --git a/crates/vmi-os-windows/src/comps/key_control_block.rs b/crates/vmi-os-windows/src/comps/key_control_block.rs new file mode 100644 index 0000000..52606fd --- /dev/null +++ b/crates/vmi-os-windows/src/comps/key_control_block.rs @@ -0,0 +1,146 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, WindowsObject}; +use crate::{ArchAdapter, WindowsError, WindowsOs}; + +/// A Windows registry key control block. +/// +/// A registry key in the kernel mode registry cache. It helps the Configuration +/// Manager manage registry keys efficiently by avoiding redundant registry +/// lookups. +/// +/// # Implementation Details +/// +/// Corresponds to `_CM_KEY_CONTROL_BLOCK`. +pub struct WindowsKeyControlBlock<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_CM_KEY_CONTROL_BLOCK` structure. + va: Va, + + /// Cached virtual address of the `_CM_NAME_CONTROL_BLOCK` structure. + name_block: OnceCell, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsKeyControlBlock<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsKeyControlBlock<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsKeyControlBlock<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows key control block. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { + vmi, + va, + name_block: OnceCell::new(), + } + } + + /// Returns the parent key control block. + /// + /// # Implementation Details + /// + /// Corresponds to `_CM_KEY_CONTROL_BLOCK.ParentKcb`. + pub fn parent(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let CM_KEY_CONTROL_BLOCK = &offsets._CM_KEY_CONTROL_BLOCK; + + let parent_kcb = self + .vmi + .read_va_native(self.va + CM_KEY_CONTROL_BLOCK.ParentKcb.offset())?; + + if parent_kcb.is_null() { + return Ok(None); + } + + Ok(Some(WindowsKeyControlBlock::new(self.vmi, parent_kcb))) + } + + /// Returns the name of the key. + /// + /// # Implementation Details + /// + /// Corresponds to `_CM_KEY_CONTROL_BLOCK.NameBlock`. + /// If the `_CM_NAME_CONTROL_BLOCK.Compressed` field is set, the name is + /// read as a multibyte string. Otherwise, the name is read as a UTF-16 + /// string. + pub fn name(&self) -> Result { + let offsets = self.offsets(); + let CM_NAME_CONTROL_BLOCK = &offsets._CM_NAME_CONTROL_BLOCK; + + let name_block = self.name_block()?; + + let compressed = self + .vmi + .read_field(name_block, &CM_NAME_CONTROL_BLOCK.Compressed)?; + + let compressed = CM_NAME_CONTROL_BLOCK.Compressed.extract(compressed) != 0; + + let name_length = self + .vmi + .read_field(name_block, &CM_NAME_CONTROL_BLOCK.NameLength)?; + + if compressed { + self.vmi.read_string_limited( + name_block + CM_NAME_CONTROL_BLOCK.Name.offset(), + name_length as usize, + ) + } + else { + self.vmi.read_wstring_limited( + name_block + CM_NAME_CONTROL_BLOCK.Name.offset(), + name_length as usize, + ) + } + } + + /// Returns the name control block associated with the key control block. + fn name_block(&self) -> Result { + self.name_block + .get_or_try_init(|| { + let offsets = self.offsets(); + let CM_KEY_CONTROL_BLOCK = &offsets._CM_KEY_CONTROL_BLOCK; + + let name_block = self + .vmi + .read_va_native(self.va + CM_KEY_CONTROL_BLOCK.NameBlock.offset())?; + + if name_block.is_null() { + return Err( + WindowsError::CorruptedStruct("CM_KEY_CONTROL_BLOCK.NameBlock").into(), + ); + } + + Ok(name_block) + }) + .copied() + } +} diff --git a/crates/vmi-os-windows/src/comps/macros.rs b/crates/vmi-os-windows/src/comps/macros.rs new file mode 100644 index 0000000..0d40fad --- /dev/null +++ b/crates/vmi-os-windows/src/comps/macros.rs @@ -0,0 +1,42 @@ +macro_rules! impl_symbols { + () => { + fn symbols(&self) -> &$crate::offsets::Symbols { + &self.vmi.underlying_os().symbols + } + }; +} + +macro_rules! impl_offsets { + () => { + fn offsets(&self) -> &$crate::offsets::Offsets { + &self.vmi.underlying_os().offsets + } + }; +} + +macro_rules! impl_offsets_ext_v1 { + () => { + fn offsets_ext(&self) -> &$crate::offsets::v1::Offsets { + match self.vmi.underlying_os().offsets.ext() { + Some($crate::offsets::OffsetsExt::V1(offsets)) => offsets, + _ => unreachable!(), + } + } + }; +} + +macro_rules! impl_offsets_ext_v2 { + () => { + fn offsets_ext(&self) -> &$crate::offsets::v2::Offsets { + match self.vmi.underlying_os().offsets.ext() { + Some($crate::offsets::OffsetsExt::V2(offsets)) => offsets, + _ => unreachable!(), + } + } + }; +} + +pub(crate) use impl_offsets; +pub(crate) use impl_offsets_ext_v1; +pub(crate) use impl_offsets_ext_v2; +pub(crate) use impl_symbols; diff --git a/crates/vmi-os-windows/src/comps/mod.rs b/crates/vmi-os-windows/src/comps/mod.rs new file mode 100644 index 0000000..1983bb7 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/mod.rs @@ -0,0 +1,34 @@ +mod control_area; +mod handle_table; +mod handle_table_entry; +mod image; +mod key_control_block; +pub(crate) mod macros; +mod module; +mod name_info; +mod object; +mod object_attributes; +mod peb; +mod process_parameters; +mod region; +mod session; + +pub use self::{ + control_area::WindowsControlArea, + handle_table::WindowsHandleTable, + handle_table_entry::WindowsHandleTableEntry, + image::WindowsImage, + key_control_block::WindowsKeyControlBlock, + module::WindowsModule, + name_info::WindowsObjectHeaderNameInfo, + object::{ + ParseObjectTypeError, WindowsDirectoryObject, WindowsFileObject, WindowsObject, + WindowsObjectType, WindowsObjectTypeKind, WindowsProcess, WindowsSectionObject, + WindowsThread, + }, + object_attributes::WindowsObjectAttributes, + peb::{WindowsPeb, WindowsWow64Kind}, + process_parameters::WindowsProcessParameters, + region::WindowsRegion, + session::WindowsSession, +}; diff --git a/crates/vmi-os-windows/src/comps/module.rs b/crates/vmi-os-windows/src/comps/module.rs new file mode 100644 index 0000000..42be7b9 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/module.rs @@ -0,0 +1,123 @@ +use vmi_core::{os::VmiOsModule, Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::macros::impl_offsets; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A Windows kernel module. +/// +/// A module in Windows refers to a loaded executable or driver, +/// typically tracked in the kernel's module list. +/// +/// # Implementation Details +/// +/// Corresponds to `_KLDR_DATA_TABLE_ENTRY`. +pub struct WindowsModule<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_KLDR_DATA_TABLE_ENTRY` structure. + va: Va, +} + +impl VmiVa for WindowsModule<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsModule<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows kernel module. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the entry point of the module. + /// + /// # Implementation Details + /// + /// Corresponds to `_KLDR_DATA_TABLE_ENTRY.EntryPoint`. + pub fn entry_point(&self) -> Result { + let offsets = self.offsets(); + let KLDR_DATA_TABLE_ENTRY = &offsets._KLDR_DATA_TABLE_ENTRY; + + self.vmi + .read_va_native(self.va + KLDR_DATA_TABLE_ENTRY.EntryPoint.offset()) + } + + /// Returns the full name of the module. + /// + /// # Implementation Details + /// + /// Corresponds to `_KLDR_DATA_TABLE_ENTRY.FullDllName`. + pub fn full_name(&self) -> Result { + let offsets = self.offsets(); + let KLDR_DATA_TABLE_ENTRY = &offsets._KLDR_DATA_TABLE_ENTRY; + + self.vmi + .os() + .read_unicode_string(self.va + KLDR_DATA_TABLE_ENTRY.FullDllName.offset()) + } +} + +impl<'a, Driver> VmiOsModule<'a, Driver> for WindowsModule<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + /// Returns the base address of the module. + /// + /// # Implementation Details + /// + /// Corresponds to `_KLDR_DATA_TABLE_ENTRY.DllBase`. + fn base_address(&self) -> Result { + let offsets = self.offsets(); + let KLDR_DATA_TABLE_ENTRY = &offsets._KLDR_DATA_TABLE_ENTRY; + + self.vmi + .read_va_native(self.va + KLDR_DATA_TABLE_ENTRY.DllBase.offset()) + } + + /// Returns the size of the module. + /// + /// # Implementation Details + /// + /// Corresponds to `_KLDR_DATA_TABLE_ENTRY.SizeOfImage`. + fn size(&self) -> Result { + let offsets = self.offsets(); + let KLDR_DATA_TABLE_ENTRY = &offsets._KLDR_DATA_TABLE_ENTRY; + + Ok(self + .vmi + .read_u32(self.va + KLDR_DATA_TABLE_ENTRY.SizeOfImage.offset())? as u64) + } + + /// Returns the name of the module. + /// + /// # Implementation Details + /// + /// Corresponds to `_KLDR_DATA_TABLE_ENTRY.BaseDllName`. + fn name(&self) -> Result { + let offsets = self.offsets(); + let KLDR_DATA_TABLE_ENTRY = &offsets._KLDR_DATA_TABLE_ENTRY; + + self.vmi + .os() + .read_unicode_string(self.va + KLDR_DATA_TABLE_ENTRY.BaseDllName.offset()) + } +} diff --git a/crates/vmi-os-windows/src/comps/name_info.rs b/crates/vmi-os-windows/src/comps/name_info.rs new file mode 100644 index 0000000..75dc947 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/name_info.rs @@ -0,0 +1,118 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, WindowsObject}; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A name information for a Windows object. +/// +/// This structure stores the name and directory information +/// associated with a named kernel object. +/// +/// # Implementation Details +/// +/// Corresponds to `_OBJECT_HEADER_NAME_INFO`. +pub struct WindowsObjectHeaderNameInfo<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_OBJECT_HEADER_NAME_INFO` structure. + va: Va, +} + +impl VmiVa for WindowsObjectHeaderNameInfo<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl std::fmt::Debug for WindowsObjectHeaderNameInfo<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let directory = self.directory(); + let name = self.name(); + + f.debug_struct("WindowsObjectHeaderNameInfo") + .field("directory", &directory) + .field("name", &name) + .finish() + } +} + +impl<'a, Driver> WindowsObjectHeaderNameInfo<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows object header name info. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the directory object associated with the object name. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_HEADER_NAME_INFO.Directory`. + pub fn directory(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let OBJECT_HEADER_NAME_INFO = &offsets._OBJECT_HEADER_NAME_INFO; + + let directory = self + .vmi + .read_va_native(self.va + OBJECT_HEADER_NAME_INFO.Directory.offset())?; + + if directory.is_null() { + return Ok(None); + } + + Ok(Some(WindowsObject::new(self.vmi, directory))) + } + + /// Returns the name of the object. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_HEADER_NAME_INFO.Name`. + pub fn name(&self) -> Result { + let offsets = self.offsets(); + let OBJECT_HEADER_NAME_INFO = &offsets._OBJECT_HEADER_NAME_INFO; + + self.vmi + .os() + .read_unicode_string(self.va + OBJECT_HEADER_NAME_INFO.Name.offset()) + } + + /// Constructs the full path of a named object from its name information. + /// + /// # Implementation Details + pub fn full_path(&self) -> Result { + let mut path = String::new(); + + if let Some(directory) = self.directory()? { + if let Some(directory_path) = directory.full_path()? { + path.push_str(&directory_path); + } + + if directory.va() != self.vmi.os().object_root_directory()?.va() { + path.push('\\'); + } + } + + path.push_str(&self.name()?); + + Ok(path) + } +} diff --git a/crates/vmi-os-windows/src/comps/object/directory.rs b/crates/vmi-os-windows/src/comps/object/directory.rs new file mode 100644 index 0000000..c7e9438 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/directory.rs @@ -0,0 +1,142 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{super::macros::impl_offsets, WindowsObject}; +use crate::{ArchAdapter, WindowsOs}; + +/// A Windows directory object. +/// +/// A directory object is a kernel-managed container that stores named objects +/// such as events, mutexes, symbolic links, and device objects. +/// +/// # Implementation Details +/// +/// Corresponds to `_OBJECT_DIRECTORY`. +pub struct WindowsDirectoryObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_OBJECT_DIRECTORY` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsDirectoryObject<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsDirectoryObject<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsDirectoryObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows directory object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Iterates over the objects in the directory. + pub fn iter( + &self, + ) -> Result, VmiError>>, VmiError> { + let offsets = self.offsets(); + let OBJECT_DIRECTORY = &offsets._OBJECT_DIRECTORY; + let OBJECT_DIRECTORY_ENTRY = &offsets._OBJECT_DIRECTORY_ENTRY; + + let mut entries = Vec::new(); + + for i in 0..37 { + let hash_bucket = self + .vmi + .read_va_native(self.va + OBJECT_DIRECTORY.HashBuckets.offset() + i * 8)?; + + let mut entry = hash_bucket; + while !entry.is_null() { + let object = self + .vmi + .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.Object.offset())?; + + let object = WindowsObject::new(self.vmi, object); + + entries.push(Ok(object)); + + entry = self + .vmi + .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.ChainLink.offset())?; + } + } + + Ok(entries.into_iter()) + } + + /// Performs a lookup in the directory. + pub fn lookup( + &self, + needle: impl AsRef, + ) -> Result>, VmiError> { + let offsets = self.offsets(); + let OBJECT_DIRECTORY = &offsets._OBJECT_DIRECTORY; + let OBJECT_DIRECTORY_ENTRY = &offsets._OBJECT_DIRECTORY_ENTRY; + + let needle = needle.as_ref(); + + for i in 0..37 { + println!("i: {}", i); + + let hash_bucket = self + .vmi + .read_va_native(self.va + OBJECT_DIRECTORY.HashBuckets.offset() + i * 8)?; + + let mut entry = hash_bucket; + while !entry.is_null() { + println!(" entry: {}", entry); + + let object = self + .vmi + .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.Object.offset())?; + println!(" object: {}", object); + + let hash_value = self + .vmi + .read_u32(entry + OBJECT_DIRECTORY_ENTRY.HashValue.offset())?; + println!(" hash_value: {}", hash_value); + + let object = WindowsObject::new(self.vmi, object); + + if let Some(name) = object.name()? { + println!(" name: {name}"); + + if name == needle { + return Ok(Some(object)); + } + } + + entry = self + .vmi + .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.ChainLink.offset())?; + } + } + + Ok(None) + } +} diff --git a/crates/vmi-os-windows/src/comps/object/file.rs b/crates/vmi-os-windows/src/comps/object/file.rs new file mode 100644 index 0000000..d60677a --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/file.rs @@ -0,0 +1,131 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{super::macros::impl_offsets, WindowsObject}; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A Windows file object. +/// +/// A file object is a kernel structure that represents an open file or device +/// in the Windows Object Manager. It contains metadata about the file, its access +/// permissions, and associated device or volume. +/// +/// # Implementation Details +/// +/// Corresponds to `_FILE_OBJECT`. +pub struct WindowsFileObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_FILE_OBJECT` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsFileObject<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsFileObject<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsFileObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows file object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the device object associated with the file object. + /// + /// # Implementation Details + /// + /// Corresponds to `_FILE_OBJECT.DeviceObject`. + pub fn device_object(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let FILE_OBJECT = &offsets._FILE_OBJECT; + + let device_object = self + .vmi + .read_va_native(self.va + FILE_OBJECT.DeviceObject.offset())?; + + Ok(WindowsObject::new(self.vmi, device_object)) + } + + /// Returns the filename associated with the file object. + /// + /// # Implementation Details + /// + /// Corresponds to `_FILE_OBJECT.FileName`. + /// + /// # Notes + /// + /// This operation might fail as the filename is allocated from paged pool. + pub fn filename(&self) -> Result { + let offsets = self.offsets(); + let FILE_OBJECT = &offsets._FILE_OBJECT; + + // Note that filename is allocated from paged pool, + // so this read might fail. + self.vmi + .os() + .read_unicode_string(self.va + FILE_OBJECT.FileName.offset()) + } + + /// Constructs the full path of a file from its `FILE_OBJECT`. + /// + /// This function first reads the `DeviceObject` field of the `FILE_OBJECT` + /// structure. Then it reads the `ObjectNameInfo` of the `DeviceObject` + /// and its directory. Finally, it concatenates the device directory + /// name, device name, and file name. + /// + /// # Implementation Details + /// + /// Corresponds to `_FILE_OBJECT.DeviceObject.NameInfo.Name` concatenated + /// with `_FILE_OBJECT.FileName`. + pub fn full_path(&self) -> Result { + let device = self.device_object()?.name_info()?; + let directory = match &device { + Some(device) => match device.directory()? { + Some(directory) => directory.name_info()?, + None => None, + }, + None => None, + }; + + let mut result = String::new(); + if let Some(directory) = directory { + result.push('\\'); + result.push_str(&directory.name()?); + } + + if let Some(device) = device { + result.push('\\'); + result.push_str(&device.name()?); + } + + result.push_str(&self.filename()?); + + Ok(result) + } +} diff --git a/crates/vmi-os-windows/src/comps/object/key.rs b/crates/vmi-os-windows/src/comps/object/key.rs new file mode 100644 index 0000000..6702a95 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/key.rs @@ -0,0 +1,124 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{ + super::{macros::impl_offsets, WindowsKeyControlBlock}, + WindowsObject, +}; +use crate::{ArchAdapter, WindowsError, WindowsOs}; + +/// A Windows registry key. +/// +/// An open handle to a registry key in kernel mode. +/// +/// # Notes +/// +/// Multiple `_CM_KEY_BODY` structures can reference a single +/// `_CM_KEY_CONTROL_BLOCK`. +/// +/// # Implementation Details +/// +/// Corresponds to `_CM_KEY_BODY`. +pub struct WindowsKey<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_CM_KEY_BODY` structure. + va: Va, + + /// Cached virtual address of the `_CM_KEY_CONTROL_BLOCK` structure. + key_control_block: OnceCell, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsKey<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsKey<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsKey<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows registry key. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { + vmi, + va, + key_control_block: OnceCell::new(), + } + } + + /// Returns the name of the key. + /// + /// # Implementation Details + /// + /// Corresponds to `_CM_KEY_BODY.KeyControlBlock.NameBlock`. + pub fn name(&self) -> Result { + let kcb = WindowsKeyControlBlock::new(self.vmi, self.key_control_block()?); + kcb.name() + } + + /// Constructs the full path of the key. + /// + /// # Implementation Details + /// + /// This method recursively traverses the parent keys to construct the full path. + pub fn full_path(&self) -> Result { + let mut kcb = WindowsKeyControlBlock::new(self.vmi, self.key_control_block()?); + let mut result = kcb.name()?; + + while let Some(parent) = kcb.parent()? { + let parent_name = parent.name()?; + + result.insert(0, '\\'); + result.insert_str(0, &parent_name); + kcb = parent; + } + + result.insert(0, '\\'); + + Ok(result) + } + + /// Returns the key control block associated with the key. + fn key_control_block(&self) -> Result { + self.key_control_block + .get_or_try_init(|| { + let offsets = self.offsets(); + let CM_KEY_BODY = &offsets._CM_KEY_BODY; + + let key_control_block = self + .vmi + .read_va_native(self.va + CM_KEY_BODY.KeyControlBlock.offset())?; + + if key_control_block.is_null() { + return Err(WindowsError::CorruptedStruct("CM_KEY_BODY.KeyControlBlock").into()); + } + + Ok(key_control_block) + }) + .copied() + } +} diff --git a/crates/vmi-os-windows/src/comps/object/mod.rs b/crates/vmi-os-windows/src/comps/object/mod.rs new file mode 100644 index 0000000..8b83b0d --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/mod.rs @@ -0,0 +1,820 @@ +mod directory; +mod file; +mod key; +mod object_type; +mod process; +mod section; +mod thread; + +use vmi_core::{ + os::{ProcessObject, ThreadObject}, + Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +pub use self::{ + directory::WindowsDirectoryObject, file::WindowsFileObject, key::WindowsKey, + object_type::WindowsObjectType, process::WindowsProcess, section::WindowsSectionObject, + thread::WindowsThread, +}; +use super::{ + macros::{impl_offsets, impl_symbols}, + WindowsObjectHeaderNameInfo, +}; +use crate::{arch::ArchAdapter, WindowsOs, WindowsOsExt}; + +/// A Windows object. +/// +/// A Windows object is a kernel-managed entity that can be referenced +/// by handles or pointers. It includes processes, threads, files, and other +/// system resources managed by the Windows Object Manager. +/// +/// # Implementation Details +/// +/// Corresponds to `_OBJECT_HEADER.Body`. +pub struct WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the object. + va: Va, +} + +impl VmiVa for WindowsObject<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl std::fmt::Debug for WindowsObject<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name_info = self.name_info(); + let typ = self.type_kind(); + + f.debug_struct("WindowsObject") + .field("typ", &typ) + .field("name_info", &name_info) + .finish() + } +} + +impl<'a, Driver> WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_symbols!(); + impl_offsets!(); + + /// Creates a new Windows object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the virtual address of the `_OBJECT_HEADER` structure. + /// + /// # Implementation Details + /// + /// `_OBJECT_HEADER` is always at the beginning of the object. + pub fn header(&self) -> Va { + let offsets = self.offsets(); + let OBJECT_HEADER = &offsets._OBJECT_HEADER; + + self.va - OBJECT_HEADER.Body.offset() + } + + /// Returns the name information of the object. + pub fn name_info(&self) -> Result>, VmiError> { + let symbols = self.symbols(); + let offsets = self.offsets(); + let ObpInfoMaskToOffset = symbols.ObpInfoMaskToOffset; + let OBJECT_HEADER = &offsets._OBJECT_HEADER; + + let info_mask = self + .vmi + .read_u8(self.header() + OBJECT_HEADER.InfoMask.offset())?; + + bitflags::bitflags! { + struct InfoFlags: u8 { + const CREATOR_INFO = 0x01; + const NAME_INFO = 0x02; + const HANDLE_INFO = 0x04; + const QUOTA_INFO = 0x08; + const PROCESS_INFO = 0x10; + } + } + + let info_flags = InfoFlags::from_bits_truncate(info_mask); + if !info_flags.contains(InfoFlags::NAME_INFO) { + return Ok(None); + } + + // Offset = ObpInfoMaskToOffset[OBJECT_HEADER->InfoMask & (DesiredHeaderBit | (DesiredHeaderBit-1))] + + let mask = info_mask & (InfoFlags::NAME_INFO.bits() | (InfoFlags::NAME_INFO.bits() - 1)); + let mask = mask as u64; + + let kernel_image_base = self.vmi.os().kernel_image_base()?; + let offset = self + .vmi + .read_u8(kernel_image_base + ObpInfoMaskToOffset + mask)? as u64; + + Ok(Some(WindowsObjectHeaderNameInfo::new( + self.vmi, + self.header() - offset, + ))) + } + + /// Returns the directory object associated with the object name. + /// + /// Shortcut for `self.name_info()?.directory()`. + pub fn directory(&self) -> Result>, VmiError> { + let name_info = match self.name_info()? { + Some(name_info) => name_info, + None => return Ok(None), + }; + + name_info.directory() + } + + /// Returns the name of the object. + /// + /// Shortcut for `self.name_info()?.name()`. + pub fn name(&self) -> Result, VmiError> { + let name_info = match self.name_info()? { + Some(name_info) => name_info, + None => return Ok(None), + }; + + Ok(Some(name_info.name()?)) + } + + /// Constructs the full path of a named object from its name information. + /// + /// Shortcut for `self.name_info()?.full_path()`. + pub fn full_path(&self) -> Result, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::File(file)) => Ok(Some(file.full_path()?)), + Some(WindowsObjectKind::Key(key)) => Ok(Some(key.full_path()?)), + Some(WindowsObjectKind::Section(section)) => section.full_path(), + _ => { + let name_info = match self.name_info()? { + Some(name_info) => name_info, + None => return Ok(None), + }; + + Ok(Some(name_info.full_path()?)) + } + } + } + + /// Returns the type of a Windows kernel object. + /// + /// This method analyzes the object header of a kernel object and returns + /// its type object (`_OBJECT_TYPE`). It handles the obfuscation introduced + /// by the object header cookie, ensuring accurate type identification even + /// on systems with this security feature enabled. + pub fn object_type(&self) -> Result, VmiError> { + let symbols = self.symbols(); + let offsets = self.offsets(); + let ObTypeIndexTable = symbols.ObTypeIndexTable; + let OBJECT_HEADER = &offsets._OBJECT_HEADER; + + let object_header = self.va - OBJECT_HEADER.Body.offset(); + let type_index = self + .vmi + .read_u8(object_header + OBJECT_HEADER.TypeIndex.offset())?; + + let index = match self.vmi.os().object_header_cookie()? { + Some(cookie) => { + // + // TypeIndex + // ^ 2nd least significate byte of OBJECT_HEADER address + // ^ nt!ObHeaderCookie + // ref: https://medium.com/@ashabdalhalim/a-light-on-windows-10s-object-header-typeindex-value-e8f907e7073a + // + + let salt = (object_header.0 >> 8) as u8; + type_index ^ salt ^ cookie + } + None => type_index, + }; + + let index = index as u64; + + let kernel_image_base = self.vmi.os().kernel_image_base()?; + let object_type = self.vmi.read_va_native( + kernel_image_base + ObTypeIndexTable + index * 8, // REVIEW: replace 8 with registers.address_width()? + )?; + + Ok(WindowsObjectType::new(self.vmi, object_type)) + } + + /// Returns the object type name. + /// + /// # Implementation Details + /// + /// Shortcut for `self.object_type()?.name()`. + pub fn type_name(&self) -> Result { + self.object_type()?.name() + } + + /// Returns the object type kind. + /// + /// # Implementation Details + /// + /// Shortcut for `self.object_type()?.kind()`. + pub fn type_kind(&self) -> Result, VmiError> { + self.object_type()?.kind() + } + + /// Returns the specific kind of this object. + pub fn kind(&self) -> Result>, VmiError> { + let result = match self.type_kind()? { + Some(WindowsObjectTypeKind::Directory) => { + WindowsObjectKind::Directory(WindowsDirectoryObject::new(self.vmi, self.va)) + } + Some(WindowsObjectTypeKind::File) => { + WindowsObjectKind::File(WindowsFileObject::new(self.vmi, self.va)) + } + Some(WindowsObjectTypeKind::Key) => { + WindowsObjectKind::Key(WindowsKey::new(self.vmi, self.va)) + } + Some(WindowsObjectTypeKind::Process) => { + WindowsObjectKind::Process(WindowsProcess::new(self.vmi, ProcessObject(self.va))) + } + Some(WindowsObjectTypeKind::Section) => { + WindowsObjectKind::Section(WindowsSectionObject::new(self.vmi, self.va)) + } + Some(WindowsObjectTypeKind::Thread) => { + WindowsObjectKind::Thread(WindowsThread::new(self.vmi, ThreadObject(self.va))) + } + Some(WindowsObjectTypeKind::Type) => { + WindowsObjectKind::Type(WindowsObjectType::new(self.vmi, self.va)) + } + _ => return Ok(None), + }; + + Ok(Some(result)) + } + + /// Returns the object as a directory (`_OBJECT_DIRECTORY`). + pub fn as_directory(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Directory(directory)) => Ok(Some(directory)), + _ => Ok(None), + } + } + + /// Returns the object as a file (`_FILE_OBJECT`). + pub fn as_file(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::File(file)) => Ok(Some(file)), + _ => Ok(None), + } + } + + /// Returns the object as a key (`_CM_KEY_BODY`). + pub fn as_key(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Key(key)) => Ok(Some(key)), + _ => Ok(None), + } + } + + /// Returns the object as a process (`_EPROCESS`). + pub fn as_process(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Process(process)) => Ok(Some(process)), + _ => Ok(None), + } + } + + /// Returns the object as a section (`_SECTION_OBJECT`). + pub fn as_section(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Section(section)) => Ok(Some(section)), + _ => Ok(None), + } + } + + /// Returns the object as a thread (`_ETHREAD`). + pub fn as_thread(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Thread(thread)) => Ok(Some(thread)), + _ => Ok(None), + } + } + + /// Returns the object as an object type (`_OBJECT_TYPE`). + pub fn as_type(&self) -> Result>, VmiError> { + match self.kind()? { + Some(WindowsObjectKind::Type(object_type)) => Ok(Some(object_type)), + _ => Ok(None), + } + } +} + +/// Identifies the type of a Windows kernel object. +/// +/// Windows uses a object-based kernel architecture where various system +/// resources (processes, threads, files, etc.) are represented as kernel +/// objects. This enum identifies the different types of objects that can +/// be encountered during introspection. +/// +/// Each variant corresponds to a specific object type string used internally +/// by the Windows kernel. For example, "Process" for process objects, +/// "Thread" for thread objects, etc. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WindowsObjectTypeKind { + /// Activation object. + /// + /// Has `ActivationObject` type name. + ActivationObject, + + /// Activity reference object. + /// + /// Has `ActivityReference` type name. + ActivityReference, + + /// Adapter object. + /// + /// Represented by `_ADAPTER_OBJECT` structure. + /// Has `Adapter` type name. + Adapter, + + /// ALPC Port object. + /// + /// Represented by `_ALPC_PORT` structure. + /// Has `ALPC Port` type name. + AlpcPort, + + /// Callback object. + /// + /// Has `Callback` type name. + Callback, + + /// Composition object. + /// + /// Has `Composition` type name. + Composition, + + /// Controller object. + /// + /// Has `Controller` type name. + Controller, + + /// Core messaging object. + /// + /// Has `CoreMessaging` type name. + CoreMessaging, + + /// Coverage sampler object. + /// + /// Has `CoverageSampler` type name. + CoverageSampler, + + /// CPU partition object. + /// + /// Has `CpuPartition` type name. + CpuPartition, + + /// Debug object. + /// + /// Represented by `_DEBUG_OBJECT` structure. + /// Has `DebugObject` type name. + DebugObject, + + /// Desktop object. + /// + /// Has `Desktop` type name. + Desktop, + + /// Device object. + /// + /// Represented by `_DEVICE_OBJECT` structure. + /// Has `Device` type name. + Device, + + /// Directory object. + /// + /// Represented by `_OBJECT_DIRECTORY` structure. + /// Has `Directory` type name. + Directory, + + /// DMA adapter object. + /// + /// Has `DmaAdapter` type name. + DmaAdapter, + + /// Driver object. + /// + /// Represented by `_DRIVER_OBJECT` structure. + /// Has `Driver` type name. + Driver, + + /// DX Composition object. + /// + /// Has `DxgkCompositionObject` type name. + DxgkCompositionObject, + + /// DX Display Manager object. + /// + /// Has `DxgkDisplayManagerObject` type name. + DxgkDisplayManagerObject, + + /// DX Shared Bundle object. + /// + /// Has `DxgkSharedBundleObject` type name. + DxgkSharedBundleObject, + + /// DX Shared Keyed Mutex object. + /// + /// Has `DxgkSharedKeyedMutexObject` type name. + DxgkSharedKeyedMutexObject, + + /// DX Shared Protected Session object. + /// + /// Has `DxgkSharedProtectedSessionObject` type name. + DxgkSharedProtectedSessionObject, + + /// DX Shared Resource object. + /// + /// Has `DxgkSharedResource` type name. + DxgkSharedResource, + + /// DX Shared Swap Chain object. + /// + /// Has `DxgkSharedSwapChainObject` type name. + DxgkSharedSwapChainObject, + + /// DX Shared Sync object. + /// + /// Has `DxgkSharedSyncObject` type name. + DxgkSharedSyncObject, + + /// Energy tracker object. + /// + /// Has `EnergyTracker` type name. + EnergyTracker, + + /// ETW consumer object. + /// + /// Has `EtwConsumer` type name. + EtwConsumer, + + /// ETW registration object. + /// + /// Has `EtwRegistration` type name. + EtwRegistration, + + /// ETW session demux entry object. + /// + /// Has `EtwSessionDemuxEntry` type name. + EtwSessionDemuxEntry, + + /// Event object. + /// + /// Represented by `_KEVENT` structure. + /// Has `Event` type name. + Event, + + /// File object. + /// + /// Represented by `_FILE_OBJECT` structure. + /// Has `File` type name. + File, + + /// Filter communication port object. + /// + /// Has `FilterCommunicationPort` type name. + FilterCommunicationPort, + + /// Filter connection port object. + /// + /// Has `FilterConnectionPort` type name. + FilterConnectionPort, + + /// I/O completion object. + /// + /// Has `IoCompletion` type name. + IoCompletion, + + /// I/O completion reserve object. + /// + /// Has `IoCompletionReserve` type name. + IoCompletionReserve, + + /// I/O ring object. + /// + /// Has `IoRing` type name. + IoRing, + + /// IR timer object. + /// + /// Has `IRTimer` type name. + IRTimer, + + /// Job object. + /// + /// Represented by `_EJOB` structure. + /// Has `Job` type name. + Job, + + /// Key object. + /// + /// Represented by `_CM_KEY_BODY` structure. + /// Has `Key` type name. + Key, + + /// Keyed event object. + /// + /// Has `KeyedEvent` type name. + KeyedEvent, + + /// Mutant object. + /// + /// Represented by `_KMUTANT` structure. + /// Has `Mutant` type name. + Mutant, + + /// NDIS CM state object. + /// + /// Has `NdisCmState` type name. + NdisCmState, + + /// Partition object. + /// + /// Has `Partition` type name. + Partition, + + /// Performance counter object. + /// + /// Has `PcwObject` type name. + PcwObject, + + /// Power request object. + /// + /// Has `PowerRequest` type name. + PowerRequest, + + /// Port object. + /// + /// Represented by `_PORT_MESSAGE` structure. + /// Has `Port` type name. + Port, + + /// Process object. + /// + /// Represented by `_EPROCESS` structure. + /// Has `Process` type name. + Process, + + /// Process state change object. + /// + /// Has `ProcessStateChange` type name. + ProcessStateChange, + + /// Profile object. + /// + /// Has `Profile` type name. + Profile, + + /// Sile context (non-paged) object. + /// + /// Has `PsSiloContextNonPaged` type name. + PsSiloContextNonPaged, + + /// Sile context (paged) object. + /// + /// Has `PsSiloContextPaged` type name. + PsSiloContextPaged, + + /// Raw input manager object. + /// + /// Has `RawInputManager` type name. + RawInputManager, + + /// Registry transaction object. + /// + /// Has `RegistryTransaction` type name. + RegistryTransaction, + + /// Section object. + /// + /// Represented by `_SECTION` (or `_SECTION_OBJECT`) structure. + /// Has `Section` type name. + Section, + + /// Semaphore object. + /// + /// Represented by `_KSEMAPHORE` structure. + /// Has `Semaphore` type name. + Semaphore, + + /// Session object. + /// + /// Has `Session` type name. + Session, + + /// Symbolic link object. + /// + /// Represented by `_OBJECT_SYMBOLIC_LINK` structure. + /// Has `SymbolicLink` type name. + SymbolicLink, + + /// Thread object. + /// + /// Represented by `_ETHREAD` structure. + /// Has `Thread` type name. + Thread, + + /// Thread state change object. + /// + /// Has `ThreadStateChange` type name. + ThreadStateChange, + + /// Timer object. + /// + /// Represented by `_KTIMER` structure. + /// Has `Timer` type name. + Timer, + + /// Transaction manager (Enlistment) object. + /// + /// Has `TmEn` type name. + TmEn, + + /// Transaction manager (Resource Manager) object. + /// + /// Has `TmRm` type name. + TmRm, + + /// Transaction manager object. + TmTm, + + /// Transaction object. + TmTx, + + /// Token object. + /// + /// Represented by `_TOKEN` structure. + /// Has `Token` type name. + Token, + + /// Thread pool worker factory object. + /// + /// Has `TpWorkerFactory` type name. + TpWorkerFactory, + + /// Type object. + /// + /// Represented by `_OBJECT_TYPE` structure. + /// Has `Type` type name. + Type, + + /// User APC reserve object. + /// + /// Has `UserApcReserve` type name. + UserApcReserve, + + /// Wait completion packet object. + /// + /// Has `WaitCompletionPacket` type name. + WaitCompletionPacket, + + /// Window station object. + /// + /// Has `WindowStation` type name. + WindowStation, + + /// WMI GUID object. + /// + /// Has `WmiGuid` type name. + WmiGuid, +} + +/// Error parsing a Windows object type. +#[derive(Debug, PartialEq, Eq)] +pub struct ParseObjectTypeError; + +impl std::str::FromStr for WindowsObjectTypeKind { + type Err = ParseObjectTypeError; + + fn from_str(s: &str) -> Result { + use WindowsObjectTypeKind::*; + + match s { + "ActivationObject" => Ok(ActivationObject), + "ActivityReference" => Ok(ActivityReference), + "Adapter" => Ok(Adapter), + "ALPC Port" => Ok(AlpcPort), + "Callback" => Ok(Callback), + "Composition" => Ok(Composition), + "Controller" => Ok(Controller), + "CoreMessaging" => Ok(CoreMessaging), + "CoverageSampler" => Ok(CoverageSampler), + "CpuPartition" => Ok(CpuPartition), + "DebugObject" => Ok(DebugObject), + "Desktop" => Ok(Desktop), + "Device" => Ok(Device), + "Directory" => Ok(Directory), + "DmaAdapter" => Ok(DmaAdapter), + "Driver" => Ok(Driver), + "DxgkCompositionObject" => Ok(DxgkCompositionObject), + "DxgkDisplayManagerObject" => Ok(DxgkDisplayManagerObject), + "DxgkSharedBundleObject" => Ok(DxgkSharedBundleObject), + "DxgkSharedKeyedMutexObject" => Ok(DxgkSharedKeyedMutexObject), + "DxgkSharedProtectedSessionObject" => Ok(DxgkSharedProtectedSessionObject), + "DxgkSharedResource" => Ok(DxgkSharedResource), + "DxgkSharedSwapChainObject" => Ok(DxgkSharedSwapChainObject), + "DxgkSharedSyncObject" => Ok(DxgkSharedSyncObject), + "EnergyTracker" => Ok(EnergyTracker), + "EtwConsumer" => Ok(EtwConsumer), + "EtwRegistration" => Ok(EtwRegistration), + "EtwSessionDemuxEntry" => Ok(EtwSessionDemuxEntry), + "Event" => Ok(Event), + "File" => Ok(File), + "FilterCommunicationPort" => Ok(FilterCommunicationPort), + "FilterConnectionPort" => Ok(FilterConnectionPort), + "IoCompletion" => Ok(IoCompletion), + "IoCompletionReserve" => Ok(IoCompletionReserve), + "IoRing" => Ok(IoRing), + "IRTimer" => Ok(IRTimer), + "Job" => Ok(Job), + "Key" => Ok(Key), + "KeyedEvent" => Ok(KeyedEvent), + "Mutant" => Ok(Mutant), + "NdisCmState" => Ok(NdisCmState), + "Partition" => Ok(Partition), + "PcwObject" => Ok(PcwObject), + "PowerRequest" => Ok(PowerRequest), + "Port" => Ok(Port), + "Process" => Ok(Process), + "ProcessStateChange" => Ok(ProcessStateChange), + "Profile" => Ok(Profile), + "PsSiloContextNonPaged" => Ok(PsSiloContextNonPaged), + "PsSiloContextPaged" => Ok(PsSiloContextPaged), + "RawInputManager" => Ok(RawInputManager), + "RegistryTransaction" => Ok(RegistryTransaction), + "Section" => Ok(Section), + "Semaphore" => Ok(Semaphore), + "Session" => Ok(Session), + "SymbolicLink" => Ok(SymbolicLink), + "Thread" => Ok(Thread), + "ThreadStateChange" => Ok(ThreadStateChange), + "Timer" => Ok(Timer), + "TmEn" => Ok(TmEn), + "TmRm" => Ok(TmRm), + "TmTm" => Ok(TmTm), + "TmTx" => Ok(TmTx), + "Token" => Ok(Token), + "TpWorkerFactory" => Ok(TpWorkerFactory), + "Type" => Ok(Type), + "UserApcReserve" => Ok(UserApcReserve), + "WaitCompletionPacket" => Ok(WaitCompletionPacket), + "WindowStation" => Ok(WindowStation), + "WmiGuid" => Ok(WmiGuid), + _ => Err(ParseObjectTypeError), + } + } +} + +/// Represents a specific kind of Windows object. +pub enum WindowsObjectKind<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// A directory object (`_OBJECT_DIRECTORY`). + Directory(WindowsDirectoryObject<'a, Driver>), + + /// A file object (`_FILE_OBJECT`). + File(WindowsFileObject<'a, Driver>), + + /// A registry key object (`_CM_KEY_BODY`). + Key(WindowsKey<'a, Driver>), + + /// A process object (`_EPROCESS`). + Process(WindowsProcess<'a, Driver>), + + /// A section object (`_SECTION_OBJECT`). + Section(WindowsSectionObject<'a, Driver>), + + /// A thread object (`_ETHREAD`). + Thread(WindowsThread<'a, Driver>), + + /// An object type object (`_OBJECT_TYPE`). + Type(WindowsObjectType<'a, Driver>), +} diff --git a/crates/vmi-os-windows/src/comps/object/object_type.rs b/crates/vmi-os-windows/src/comps/object/object_type.rs new file mode 100644 index 0000000..8059fd6 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/object_type.rs @@ -0,0 +1,113 @@ +use std::str::FromStr as _; + +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{super::macros::impl_offsets, WindowsObject, WindowsObjectTypeKind}; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A Windows object type object. +/// +/// A type of kernel object managed by the Windows Object Manager. +/// +/// # Implementation Details +/// +/// Corresponds to `_OBJECT_TYPE`. +pub struct WindowsObjectType<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_OBJECT_TYPE` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsObjectType<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsObjectType<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsObjectType<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows directory object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the name of the object type. + /// + /// # Notes + /// + /// This method caches the object type name for this VA in the [`WindowsOs`] + /// instance. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_TYPE.Name`. + pub fn name(&self) -> Result { + let os = self.vmi.underlying_os(); + if let Some(object_name) = os.object_type_name_cache.borrow().get(&self.va) { + return Ok(object_name.clone()); + } + + let offsets = self.offsets(); + let OBJECT_TYPE = &offsets._OBJECT_TYPE; + + let object_name = self + .vmi + .os() + .read_unicode_string(self.va + OBJECT_TYPE.Name.offset())?; + + os.object_type_name_cache + .borrow_mut() + .insert(self.va, object_name.clone()); + + Ok(object_name) + } + + /// Returns the kind of the object type. + /// + /// # Notes + /// + /// This method caches the object type kind for this VA in the [`WindowsOs`] + /// instance. + pub fn kind(&self) -> Result, VmiError> { + let os = self.vmi.underlying_os(); + if let Some(object_type) = os.object_type_cache.borrow().get(&self.va).copied() { + return Ok(Some(object_type)); + } + + let object_type = match WindowsObjectTypeKind::from_str(&self.name()?) { + Ok(object_type) => object_type, + Err(_) => return Ok(None), + }; + + os.object_type_cache + .borrow_mut() + .insert(self.va, object_type); + + Ok(Some(object_type)) + } +} diff --git a/crates/vmi-os-windows/src/comps/object/process.rs b/crates/vmi-os-windows/src/comps/object/process.rs new file mode 100644 index 0000000..8c3e0c2 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/process.rs @@ -0,0 +1,534 @@ +use vmi_arch_amd64::Cr3; +use vmi_core::{ + os::{ProcessId, ProcessObject, ThreadObject, VmiOsImageArchitecture, VmiOsProcess}, + Architecture, Pa, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +use super::{ + super::{ + macros::impl_offsets, peb::WindowsWow64Kind, WindowsHandleTable, WindowsPeb, WindowsRegion, + WindowsSession, + }, + WindowsObject, WindowsThread, +}; +use crate::{ + offsets::{v1, v2}, + ArchAdapter, ListEntryIterator, OffsetsExt, TreeNodeIterator, WindowsOs, +}; + +/// A Windows process. +/// +/// A process in Windows is represented by the `_EPROCESS` structure, +/// which contains metadata about its execution state, memory layout, +/// and handles. +/// +/// # Implementation Details +/// +/// Corresponds to `_EPROCESS`. +pub struct WindowsProcess<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_EPROCESS` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsProcess<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsProcess<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsProcess<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows process. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, process: ProcessObject) -> Self { + Self { vmi, va: process.0 } + } + + /// Returns the process environment block (PEB). + /// + /// # Implementation Details + /// + /// The function first reads the `_EPROCESS.WoW64Process` field to determine + /// if the process is a 32-bit process. If the field is `NULL`, the process + /// is 64-bit. Otherwise, the function reads the `_EWOW64PROCESS.Peb` field + /// to get the 32-bit PEB. + pub fn peb(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let root = self.translation_root()?; + + let wow64 = self + .vmi + .read_va_native(self.va + EPROCESS.WoW64Process.offset())?; + + if wow64.is_null() { + let peb64 = self.vmi.read_va_native(self.va + EPROCESS.Peb.offset())?; + + Ok(WindowsPeb::new( + self.vmi, + peb64, + root, + WindowsWow64Kind::Native, + )) + } + else { + let peb32 = match &offsets.ext { + Some(OffsetsExt::V1(_)) => wow64, + Some(OffsetsExt::V2(v2)) => self + .vmi + .read_va_native(wow64 + v2._EWOW64PROCESS.Peb.offset())?, + None => panic!("OffsetsExt not set"), + }; + + Ok(WindowsPeb::new( + self.vmi, + peb32, + root, + WindowsWow64Kind::X86, + )) + } + } + + /// Returns the session of the process. + pub fn session(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let session = self + .vmi + .read_va_native(self.va + EPROCESS.Session.offset())?; + + if session.is_null() { + return Ok(None); + } + + Ok(Some(WindowsSession::new(self.vmi, session))) + } + + /// Returns the handle table of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.ObjectTable`. + pub fn handle_table(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let handle_table = self + .vmi + .read_va_native(self.va + EPROCESS.ObjectTable.offset())?; + + Ok(WindowsHandleTable::new(self.vmi, handle_table)) + } + + /// Returns the root of the virtual address descriptor (VAD) tree. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.VadRoot->BalancedRoot` for Windows 7 and + /// `_EPROCESS.VadRoot->Root` for Windows 8.1 and later. + pub fn vad_root(&self) -> Result>, VmiError> { + let node = match &self.offsets().ext() { + Some(OffsetsExt::V1(offsets)) => self.vad_root_v1(offsets)?, + Some(OffsetsExt::V2(offsets)) => self.vad_root_v2(offsets)?, + None => panic!("OffsetsExt not set"), + }; + + // `VadRoot` can be `NULL`. + if node.is_null() { + return Ok(None); + } + + Ok(Some(WindowsRegion::new(self.vmi, node))) + } + + fn vad_root_v1(&self, offsets_ext: &v1::Offsets) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + let MM_AVL_TABLE = &offsets_ext._MM_AVL_TABLE; + + // The `_MM_AVL_TABLE::BalancedRoot` field is of `_MMADDRESS_NODE` type, + // which represents the root. + let vad_root = self.va + EPROCESS.VadRoot.offset() + MM_AVL_TABLE.BalancedRoot.offset(); + + Ok(vad_root) + } + + fn vad_root_v2(&self, offsets_ext: &v2::Offsets) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + let RTL_AVL_TREE = &offsets_ext._RTL_AVL_TREE; + + // The `RTL_AVL_TREE::Root` field is of pointer type (`_RTL_BALANCED_NODE*`), + // thus we need to dereference it to get the actual node. + let vad_root = self + .vmi + .read_va_native(self.va + EPROCESS.VadRoot.offset() + RTL_AVL_TREE.Root.offset())?; + + Ok(vad_root) + } + + /// Returns the VAD hint node. + /// + /// The VAD hint is an optimization used by Windows to speed up VAD lookups. + /// This method returns the address of the hint node in the VAD tree. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.VadRoot->NodeHint` for Windows 7 and + /// `_EPROCESS.VadRoot->Hint` for Windows 8.1 and later. + pub fn vad_hint(&self) -> Result>, VmiError> { + let node = match &self.offsets().ext() { + Some(OffsetsExt::V1(offsets)) => self.vad_hint_v1(offsets)?, + Some(OffsetsExt::V2(offsets)) => self.vad_hint_v2(offsets)?, + None => panic!("OffsetsExt not set"), + }; + + if node.is_null() { + return Ok(None); + } + + Ok(Some(WindowsRegion::new(self.vmi, node))) + } + + fn vad_hint_v1(&self, offsets_ext: &v1::Offsets) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + let MM_AVL_TABLE = &offsets_ext._MM_AVL_TABLE; + + self.vmi + .read_va_native(self.va + EPROCESS.VadRoot.offset() + MM_AVL_TABLE.NodeHint.offset()) + } + + fn vad_hint_v2(&self, _offsets_ext: &v2::Offsets) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let VadHint = EPROCESS + .VadHint + .expect("VadHint is not present in common offsets"); + + self.vmi.read_va_native(self.va + VadHint.offset()) + } +} + +impl<'a, Driver> VmiOsProcess<'a, Driver> for WindowsProcess<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + /// Returns the process ID. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.UniqueProcessId`. + fn id(&self) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let result = self + .vmi + .read_u32(self.va + EPROCESS.UniqueProcessId.offset())?; + + Ok(ProcessId(result)) + } + + /// Returns the process object. + fn object(&self) -> Result { + Ok(ProcessObject(self.va)) + } + + /// Returns the name of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.ImageFileName`. + fn name(&self) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + self.vmi + .read_string(self.va + EPROCESS.ImageFileName.offset()) + } + + /// Returns the parent process ID. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.InheritedFromUniqueProcessId`. + fn parent_id(&self) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let result = self + .vmi + .read_u32(self.va + EPROCESS.InheritedFromUniqueProcessId.offset())?; + + Ok(ProcessId(result)) + } + + /// Returns the architecture of the process. + /// + /// # Implementation Details + /// + /// The function reads the `_EPROCESS.WoW64Process` field to determine if the + /// process is a 32-bit process. If the field is `NULL`, the process is 64-bit. + /// Otherwise, the process is 32-bit. + fn architecture(&self) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + let wow64process = self + .vmi + .read_va_native(self.va + EPROCESS.WoW64Process.offset())?; + + if wow64process.is_null() { + Ok(VmiOsImageArchitecture::Amd64) + } + else { + Ok(VmiOsImageArchitecture::X86) + } + } + + /// Returns the process's page table translation root. + /// + /// # Implementation Details + /// + /// Corresponds to `_KPROCESS.DirectoryTableBase`. + fn translation_root(&self) -> Result { + let offsets = self.offsets(); + let KPROCESS = &offsets._KPROCESS; + + // let current_process = self.vmi.os().current_process()?.object()?; + // + // if self.va == current_process.0 { + // return Ok(self.vmi.translation_root(self.va)); + // } + + let root = Cr3(self + .vmi + .read_va_native(self.va + KPROCESS.DirectoryTableBase.offset())? + .0); + + Ok(root.into()) + } + + /// Returns the user-mode page table translation root. + /// + /// If KPTI is disabled, this function will return the same value as + /// [`translation_root`](Self::translation_root). + /// + /// # Implementation Details + /// + /// Corresponds to `_KPROCESS.UserDirectoryTableBase`. + fn user_translation_root(&self) -> Result { + let offsets = self.offsets(); + let KPROCESS = &offsets._KPROCESS; + let UserDirectoryTableBase = match &KPROCESS.UserDirectoryTableBase { + Some(UserDirectoryTableBase) => UserDirectoryTableBase, + None => return self.translation_root(), + }; + + let root = Cr3(self + .vmi + .read_va_native(self.va + UserDirectoryTableBase.offset())? + .0); + + if root.0 < Driver::Architecture::PAGE_SIZE { + return self.translation_root(); + } + + Ok(root.into()) + } + + /// Returns the base address of the process image. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.SectionBaseAddress`. + fn image_base(&self) -> Result { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + + self.vmi + .read_va_native(self.va + EPROCESS.SectionBaseAddress.offset()) + } + + /// Returns an iterator over the process's memory regions (VADs). + /// + /// # Implementation Details + /// + /// The function iterates over the VAD tree of the process. + fn regions( + &self, + ) -> Result, VmiError>>, VmiError> { + let iterator = match self.vad_root()? { + Some(vad_root) => TreeNodeIterator::new(self.vmi, vad_root.va()), + None => TreeNodeIterator::empty(self.vmi), + }; + + Ok(iterator.map(move |result| result.map(|vad| WindowsRegion::new(self.vmi, vad)))) + } + + /// Finds the memory region (VAD) containing the given address. + /// + /// This method efficiently searches the VAD tree to find the VAD node that + /// corresponds to the given virtual address within the process's address + /// space. + /// + /// Returns the matching VAD if found, or `None` if the address is not + /// within any VAD. + /// + /// # Implementation Details + /// + /// The functionality is similar to the Windows kernel's internal + /// `MiLocateAddress()` function. + fn find_region(&self, address: Va) -> Result>, VmiError> { + let vad = match self.vad_hint()? { + Some(vad) => vad, + None => return Ok(None), + }; + + let vpn = address.0 >> 12; + + if vpn >= vad.starting_vpn()? && vpn <= vad.ending_vpn()? { + return Ok(Some(vad)); + } + + let mut next = self.vad_root()?; + while let Some(vad) = next { + if vpn < vad.starting_vpn()? { + next = vad.left_child()?; + } + else if vpn > vad.ending_vpn()? { + next = vad.right_child()?; + } + else { + return Ok(Some(vad)); + } + } + + Ok(None) + } + + /// Returns an iterator over the threads in the process. + /// + /// # Notes + /// + /// Both `_EPROCESS` and `_KPROCESS` structures contain the same list + /// of threads. + /// + /// # Implementation Details + /// + /// Corresponds to `_EPROCESS.ThreadListHead`. + fn threads( + &self, + ) -> Result< + impl Iterator>::Thread<'a>, VmiError>>, + VmiError, + > { + let offsets = self.offsets(); + let EPROCESS = &offsets._EPROCESS; + let ETHREAD = &offsets._ETHREAD; + + Ok(ListEntryIterator::new( + self.vmi, + self.va + EPROCESS.ThreadListHead.offset(), + ETHREAD.ThreadListEntry.offset(), + ) + .map(move |result| result.map(|entry| WindowsThread::new(self.vmi, ThreadObject(entry))))) + } + + /// Checks whether the given virtual address is valid in the process. + /// + /// This method checks if page-faulting on the address would result in + /// a successful access. + fn is_valid_address(&self, address: Va) -> Result, VmiError> { + // + // So, the logic is roughly as follows: + // - Translate the address and try to find the page table entry. + // - If the page table entry is found: + // - If the page is present, the address is valid. + // - If the page is in transition AND not a prototype, the address is valid. + // - Find the VAD for the address. + // - If the VAD is not found, the address is invalid. + // - If the VadType is VadImageMap, the address is valid. + // - If the VadType is not VadImageMap, we don't care (VadAwe, physical + // memory, ...). + // - If the PrivateMemory bit is not set, the address is invalid. + // - If the MemCommit bit is not set, the address is invalid. + // + // References: + // - MmAccessFault + // - MiDispatchFault + // - MiQueryAddressState + // - MiCheckVirtualAddress + // + + if Driver::Architecture::is_page_present_or_transition(self.vmi, address)? { + return Ok(Some(true)); + } + + let vad = match self.find_region(address)? { + Some(vad) => vad, + None => return Ok(Some(false)), + }; + + const MM_ZERO_ACCESS: u8 = 0; // this value is not used. + const MM_DECOMMIT: u8 = 0x10; // NO_ACCESS, Guard page + const MM_NOACCESS: u8 = 0x18; // NO_ACCESS, Guard_page, nocache. + + const VadImageMap: u8 = 2; + + if matches!( + vad.vad_protection()?, + MM_ZERO_ACCESS | MM_DECOMMIT | MM_NOACCESS + ) { + return Ok(Some(false)); + } + + Ok(Some( + // Private memory must be committed. + (vad.private_memory()? && vad.mem_commit()?) || + + // Non-private memory must be mapped from an image. + // Note that this isn't actually correct, because + // some parts of the image might not be committed, + // or they can have different protection than the VAD. + // + // However, figuring out the correct protection would + // be quite complex, so we just assume that the image + // is always committed and has the same protection as + // the VAD. + (!vad.private_memory()? && vad.vad_type()? == VadImageMap), + )) + } +} diff --git a/crates/vmi-os-windows/src/comps/object/section.rs b/crates/vmi-os-windows/src/comps/object/section.rs new file mode 100644 index 0000000..4fad2a6 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/section.rs @@ -0,0 +1,366 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{ + super::{ + macros::{impl_offsets, impl_offsets_ext_v1, impl_offsets_ext_v2}, + WindowsControlArea, + }, + WindowsFileObject, WindowsObject, +}; +use crate::{ArchAdapter, OffsetsExt, WindowsOs}; + +/// A Windows section object. +/// +/// A section object in Windows is a kernel structure used for memory mapping +/// and shared memory management. It allows multiple processes to share +/// memory regions or map files into their address space. +/// +/// # Implementation Details +/// +/// Corresponds to `_SECTION_OBJECT` or `_SECTION`. +pub struct WindowsSectionObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + inner: Inner<'a, Driver>, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsSectionObject<'a, Driver>) -> Self { + let (vmi, va) = match value.inner { + Inner::V1(inner) => (inner.vmi, inner.va), + Inner::V2(inner) => (inner.vmi, inner.va), + }; + + Self::new(vmi, va) + } +} + +impl VmiVa for WindowsSectionObject<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + match &self.inner { + Inner::V1(inner) => inner.va, + Inner::V2(inner) => inner.va, + } + } +} + +impl<'a, Driver> WindowsSectionObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new Windows section object. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + let inner = match vmi.underlying_os().offsets.ext() { + Some(OffsetsExt::V1(_)) => Inner::V1(WindowsSectionObjectV1::new(vmi, va)), + Some(OffsetsExt::V2(_)) => Inner::V2(WindowsSectionObjectV2::new(vmi, va)), + None => unimplemented!(), + }; + + Self { inner } + } + + /// Returns the starting address of the section. + /// + /// # Implementation Details + /// + /// Corresponds to `_SECTION_OBJECT.StartingVa` or `_SECTION.StartingVpn` + /// shifted left by 12 bits. + pub fn start(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.start(), + Inner::V2(inner) => inner.start(), + } + } + + /// Returns the ending address of the section (exclusive). + /// + /// # Implementation Details + /// + /// Corresponds to `_SECTION_OBJECT.EndingVa` or `_SECTION.EndingVpn` + /// incremented by 1 and shifted left by 12 bits. + pub fn end(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.end(), + Inner::V2(inner) => inner.end(), + } + } + + /// Returns the size of the section. + /// + /// # Implementation Details + /// + /// Corresponds to `_SECTION_OBJECT.SizeOfSegment` or `_SECTION.SizeOfSection`. + pub fn size(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.size(), + Inner::V2(inner) => inner.size(), + } + } + + /// Returns the flags of the section. + /// + /// # Implementation Details + /// + /// Corresponds to `_SECTION.Flags` or `_SEGMENT_OBJECT.MmSectionFlags`. + pub fn flags(&self) -> Result { + match &self.inner { + Inner::V1(inner) => inner.flags(), + Inner::V2(inner) => inner.flags(), + } + } + + /// Returns the file object of the section. + /// + /// # Implementation Details + /// + /// Corresponds to `_SECTION.ControlArea.FilePointer` or + /// `_SEGMENT_OBJECT.ControlArea.FilePointer`. + pub fn file_object(&self) -> Result>, VmiError> { + match &self.inner { + Inner::V1(inner) => inner.file_object(), + Inner::V2(inner) => inner.file_object(), + } + } + + /// Constructs the full path of the file object associated with the section. + /// + /// # Implementation Details + /// + /// Shortcut for `file_object()?.full_path()`. + pub fn full_path(&self) -> Result, VmiError> { + match self.file_object() { + Ok(Some(file_object)) => Ok(Some(file_object.full_path()?)), + _ => Ok(None), + } + } +} + +/// Inner representation of a Windows section object. +enum Inner<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + V1(WindowsSectionObjectV1<'a, Driver>), + V2(WindowsSectionObjectV2<'a, Driver>), +} + +/// A Windows section object. +struct WindowsSectionObjectV1<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_SECTION_OBJECT` structure. + va: Va, + + /// Cached virtual address of the `_SEGMENT_OBJECT` structure. + segment: OnceCell, +} + +impl<'a, Driver> WindowsSectionObjectV1<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + impl_offsets_ext_v1!(); + + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { + vmi, + va, + segment: OnceCell::new(), + } + } + + fn start(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION_OBJECT = &offsets_ext._SECTION_OBJECT; + + let starting_vpn = self.vmi.read_field(self.va, &SECTION_OBJECT.StartingVa)?; + + Ok(Va(starting_vpn << 12)) + } + + fn end(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION_OBJECT = &offsets_ext._SECTION_OBJECT; + + let ending_vpn = self.vmi.read_field(self.va, &SECTION_OBJECT.EndingVa)?; + + Ok(Va((ending_vpn + 1) << 12)) + } + + fn size(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SEGMENT_OBJECT = &offsets_ext._SEGMENT_OBJECT; + + let size = self + .vmi + .read_field(self.segment()?, &SEGMENT_OBJECT.SizeOfSegment)?; + + Ok(size) + } + + fn flags(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SEGMENT_OBJECT = &offsets_ext._SEGMENT_OBJECT; + + let flags = Va(self + .vmi + .read_field(self.segment()?, &SEGMENT_OBJECT.MmSectionFlags)?); + + Ok(self.vmi.read_u32(flags)? as u64) + } + + fn file_object(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let offsets_ext = self.offsets_ext(); + let SEGMENT_OBJECT = &offsets_ext._SEGMENT_OBJECT; + let MMSECTION_FLAGS = &offsets._MMSECTION_FLAGS; + + let flags = self.flags()?; + let file = MMSECTION_FLAGS.File.extract(flags) != 0; + + if !file { + return Ok(None); + } + + let control_area = Va(self + .vmi + .read_field(self.segment()?, &SEGMENT_OBJECT.ControlArea)?); + + WindowsControlArea::new(self.vmi, control_area).file_object() + } + + fn segment(&self) -> Result { + self.segment + .get_or_try_init(|| { + let offsets_ext = self.offsets_ext(); + let SECTION_OBJECT = &offsets_ext._SECTION_OBJECT; + + let segment = self.vmi.read_field(self.va, &SECTION_OBJECT.Segment)?; + + Ok(Va(segment)) + }) + .copied() + } +} + +struct WindowsSectionObjectV2<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_SECTION` structure. + va: Va, +} + +impl<'a, Driver> WindowsSectionObjectV2<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + impl_offsets_ext_v2!(); + + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + fn start(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION = &offsets_ext._SECTION; + + let starting_vpn = self.vmi.read_field(self.va, &SECTION.StartingVpn)?; + + Ok(Va(starting_vpn << 12)) + } + + fn end(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION = &offsets_ext._SECTION; + + let ending_vpn = self.vmi.read_field(self.va, &SECTION.EndingVpn)?; + + Ok(Va((ending_vpn + 1) << 12)) + } + + fn size(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION = &offsets_ext._SECTION; + + self.vmi.read_field(self.va, &SECTION.SizeOfSection) + } + + fn flags(&self) -> Result { + let offsets_ext = self.offsets_ext(); + let SECTION = &offsets_ext._SECTION; + + self.vmi.read_field(self.va, &SECTION.Flags) + } + + fn file_object(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let offsets_ext = self.offsets_ext(); + let MMSECTION_FLAGS = &offsets._MMSECTION_FLAGS; + let SECTION = &offsets_ext._SECTION; + + let flags = self.flags()?; + let file = MMSECTION_FLAGS.File.extract(flags) != 0; + + if !file { + return Ok(None); + } + + // + // We have to distinguish between FileObject and ControlArea. + // Here's an excerpt from _SECTION: + // + // union { + // union { + // PCONTROL_AREA ControlArea; + // PFILE_OBJECT FileObject; + // struct { + // ULONG_PTR RemoteImageFileObject : 1; + // ULONG_PTR RemoteDataFileObject : 1; + // }; + // }; + // }; + // + // Based on information from Geoff Chappell's website, we can determine whether + // ControlArea is in fact FileObject by checking the lowest 2 bits of the + // pointer. + // + // ref: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/mi/section.htm + // + + let control_area = Va(self.vmi.read_field(self.va, &SECTION.ControlArea)?); + + if control_area.0 & 0x3 != 0 { + let file_object = control_area; + return Ok(Some(WindowsFileObject::new(self.vmi, file_object))); + } + + WindowsControlArea::new(self.vmi, control_area).file_object() + } +} diff --git a/crates/vmi-os-windows/src/comps/object/thread.rs b/crates/vmi-os-windows/src/comps/object/thread.rs new file mode 100644 index 0000000..6abfd70 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object/thread.rs @@ -0,0 +1,123 @@ +use vmi_core::{ + os::{ProcessObject, ThreadId, ThreadObject, VmiOsThread}, + Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +use super::{super::macros::impl_offsets, WindowsObject, WindowsProcess}; +use crate::{ArchAdapter, WindowsOs}; + +/// A Windows thread. +/// +/// A thread in Windows is represented by the `_ETHREAD` structure, +/// which contains metadata about its execution state, context, and scheduling. +/// +/// # Implementation Details +/// +/// Corresponds to `_ETHREAD`. +pub struct WindowsThread<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_ETHREAD` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsThread<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsThread<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsThread<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows thread. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, thread: ThreadObject) -> Self { + Self { vmi, va: thread.0 } + } + + /// Returns the process object associated with the thread. + /// + /// # Implementation Details + /// + /// Corresponds to `_KTHREAD.Process`. + pub fn process(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let KTHREAD = &offsets._KTHREAD; + + let process = self + .vmi + .read_va_native(self.va + KTHREAD.Process.offset())?; + + Ok(WindowsProcess::new(self.vmi, ProcessObject(process))) + } + + /// Returns the process attached to the thread. + /// + /// # Implementation Details + /// + /// Corresponds to `_KTHREAD.ApcState.Process`. + pub fn attached_process(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let KTHREAD = &offsets._KTHREAD; + let KAPC_STATE = &offsets._KAPC_STATE; + + let process = self + .vmi + .read_va_native(self.va + KTHREAD.ApcState.offset() + KAPC_STATE.Process.offset())?; + + Ok(WindowsProcess::new(self.vmi, ProcessObject(process))) + } +} + +impl<'a, Driver> VmiOsThread<'a, Driver> for WindowsThread<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + /// Returns the thread ID. + /// + /// # Implementation Details + /// + /// Corresponds to `_ETHREAD.Cid.UniqueThread`. + fn id(&self) -> Result { + let offsets = self.offsets(); + let ETHREAD = &offsets._ETHREAD; + let CLIENT_ID = &offsets._CLIENT_ID; + + let result = self + .vmi + .read_u32(self.va + ETHREAD.Cid.offset() + CLIENT_ID.UniqueThread.offset())?; + + Ok(ThreadId(result)) + } + + /// Returns the thread object. + fn object(&self) -> Result { + Ok(ThreadObject(self.va)) + } +} diff --git a/crates/vmi-os-windows/src/comps/object_attributes.rs b/crates/vmi-os-windows/src/comps/object_attributes.rs new file mode 100644 index 0000000..8361ba6 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/object_attributes.rs @@ -0,0 +1,90 @@ +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::macros::impl_offsets; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A Windows object attributes. +/// +/// # Implementation Details +/// +/// Corresponds to `_OBJECT_ATTRIBUTES`. +pub struct WindowsObjectAttributes<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_OBJECT_ATTRIBUTES` structure. + va: Va, +} + +impl VmiVa for WindowsObjectAttributes<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsObjectAttributes<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows object attributes. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the root directory handle. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_ATTRIBUTES.RootDirectory`. + pub fn root_directory(&self) -> Result { + let offsets = self.offsets(); + let OBJECT_ATTRIBUTES = &offsets._OBJECT_ATTRIBUTES; + + self.vmi + .read_va_native(self.va + OBJECT_ATTRIBUTES.RootDirectory.offset()) + } + + /// Returns the root directory handle. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_ATTRIBUTES.ObjectName`. + pub fn object_name(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let OBJECT_ATTRIBUTES = &offsets._OBJECT_ATTRIBUTES; + + let object_name = self + .vmi + .read_va_native(self.va + OBJECT_ATTRIBUTES.ObjectName.offset())?; + + if object_name.is_null() { + return Ok(None); + } + + Ok(Some(self.vmi.os().read_unicode_string(object_name)?)) + } + + /// Returns the attributes. + /// + /// # Implementation Details + /// + /// Corresponds to `_OBJECT_ATTRIBUTES.Attributes`. + pub fn attributes(&self) -> Result { + let offsets = self.offsets(); + let OBJECT_ATTRIBUTES = &offsets._OBJECT_ATTRIBUTES; + + self.vmi + .read_u32(self.va + OBJECT_ATTRIBUTES.Attributes.offset()) + } +} diff --git a/crates/vmi-os-windows/src/comps/peb.rs b/crates/vmi-os-windows/src/comps/peb.rs new file mode 100644 index 0000000..02cc4da --- /dev/null +++ b/crates/vmi-os-windows/src/comps/peb.rs @@ -0,0 +1,119 @@ +use vmi_core::{Architecture, Pa, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, process_parameters::WindowsProcessParameters}; +use crate::{ArchAdapter, WindowsOs}; + +/// The address space type in a WoW64 process. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WindowsWow64Kind { + /// Native address space. + Native = 0, + + /// x86 (32-bit) address space under WoW64. + X86 = 1, + // Arm32 = 2, + // Amd64 = 3, + // ChpeX86 = 4, + // VsmEnclave = 5, +} + +/// A Windows process environment block (PEB). +/// +/// The PEB is a user-mode structure that stores process-wide information, +/// such as loaded modules, heap data, and environment settings. +/// This structure supports both **32-bit and 64-bit** PEBs. +/// +/// # Implementation Details +/// +/// Corresponds to `_PEB`. +pub struct WindowsPeb<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_PEB` structure. + va: Va, + + /// The translation root. + root: Pa, + + /// The kind of the process. + kind: WindowsWow64Kind, +} + +impl VmiVa for WindowsPeb<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl std::fmt::Debug for WindowsPeb<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let process_parameters = self.process_parameters(); + + f.debug_struct("WindowsOsPeb") + .field("process_parameters", &process_parameters) + .finish() + } +} + +impl<'a, Driver> WindowsPeb<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows PEB object. + pub fn new( + vmi: VmiState<'a, Driver, WindowsOs>, + va: Va, + root: Pa, + kind: WindowsWow64Kind, + ) -> Self { + Self { + vmi, + va, + root, + kind, + } + } + + /// Returns the process parameters of the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_PEB.ProcessParameters`. + pub fn process_parameters(&self) -> Result, VmiError> { + let va = match self.kind { + WindowsWow64Kind::Native => { + let offsets = self.offsets(); + let PEB = &offsets.common._PEB; + + self.vmi + .read_va_native_in((self.va + PEB.ProcessParameters.offset(), self.root))? + } + WindowsWow64Kind::X86 => { + const PEB32_ProcessParameters_offset: u64 = 0x10; + + self.vmi + .read_va_native_in((self.va + PEB32_ProcessParameters_offset, self.root))? + } + }; + + Ok(WindowsProcessParameters::new( + self.vmi, va, self.root, self.kind, + )) + } +} diff --git a/crates/vmi-os-windows/src/comps/process_parameters.rs b/crates/vmi-os-windows/src/comps/process_parameters.rs new file mode 100644 index 0000000..0f411a8 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/process_parameters.rs @@ -0,0 +1,277 @@ +use vmi_core::{Architecture, Pa, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, peb::WindowsWow64Kind}; +use crate::{ArchAdapter, WindowsOs, WindowsOsExt as _}; + +/// A Windows process parameters structure. +/// +/// Process parameters contain command-line arguments, environment variables, +/// and other startup information for a process. This structure supports both +/// **32-bit and 64-bit** structures. +pub struct WindowsProcessParameters<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + inner: Inner<'a, Driver>, +} + +impl VmiVa for WindowsProcessParameters<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + match &self.inner { + Inner::Native(inner) => inner.va, + Inner::X86(inner) => inner.va, + } + } +} + +impl std::fmt::Debug for WindowsProcessParameters<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let current_directory = self.current_directory(); + let dll_path = self.dll_path(); + let image_path_name = self.image_path_name(); + let command_line = self.command_line(); + + f.debug_struct("WindowsOsProcessParameters") + .field("current_directory", ¤t_directory) + .field("dll_path", &dll_path) + .field("image_path_name", &image_path_name) + .field("command_line", &command_line) + .finish() + } +} + +impl<'a, Driver> WindowsProcessParameters<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new Windows process parameters structure. + pub(crate) fn new( + vmi: VmiState<'a, Driver, WindowsOs>, + va: Va, + root: Pa, + kind: WindowsWow64Kind, + ) -> Self { + let inner = match kind { + WindowsWow64Kind::Native => { + Inner::Native(WindowsProcessParametersNative::new(vmi, va, root)) + } + WindowsWow64Kind::X86 => Inner::X86(WindowsProcessParameters32::new(vmi, va, root)), + }; + + Self { inner } + } + + /// Returns the current directory. + /// + /// This method returns the full path of the current directory + /// for the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_RTL_USER_PROCESS_PARAMETERS.CurrentDirectory`. + pub fn current_directory(&self) -> Result { + match &self.inner { + Inner::Native(inner) => inner.current_directory(), + Inner::X86(inner) => inner.current_directory(), + } + } + + /// Returns the DLL search path. + /// + /// This method returns the list of directories that the system searches + /// when loading DLLs for the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_RTL_USER_PROCESS_PARAMETERS.DllPath`. + pub fn dll_path(&self) -> Result { + match &self.inner { + Inner::Native(inner) => inner.dll_path(), + Inner::X86(inner) => inner.dll_path(), + } + } + + /// Returns the full path of the executable image. + /// + /// This method retrieves the full file system path of the main executable + /// that was used to create the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_RTL_USER_PROCESS_PARAMETERS.ImagePathName`. + pub fn image_path_name(&self) -> Result { + match &self.inner { + Inner::Native(inner) => inner.image_path_name(), + Inner::X86(inner) => inner.image_path_name(), + } + } + + /// Returns the command line used to launch the process. + /// + /// This method retrieves the full command line string, including the + /// executable path and any arguments, used to start the process. + /// + /// # Implementation Details + /// + /// Corresponds to `_RTL_USER_PROCESS_PARAMETERS.CommandLine`. + pub fn command_line(&self) -> Result { + match &self.inner { + Inner::Native(inner) => inner.command_line(), + Inner::X86(inner) => inner.command_line(), + } + } +} + +/// Inner representation of a Windows process parameters object. +enum Inner<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// A native (non-WoW64) process. + Native(WindowsProcessParametersNative<'a, Driver>), + + /// An x86 process running under WoW64. + X86(WindowsProcessParameters32<'a, Driver>), +} + +struct WindowsProcessParametersNative<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_RTL_USER_PROCESS_PARAMETERS` structure. + va: Va, + + /// The translation root. + root: Pa, +} + +impl<'a, Driver> WindowsProcessParametersNative<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va, root: Pa) -> Self { + Self { vmi, va, root } + } + + fn current_directory(&self) -> Result { + let offsets = self.offsets(); + let CURDIR = &offsets._CURDIR; + let RTL_USER_PROCESS_PARAMETERS = &offsets._RTL_USER_PROCESS_PARAMETERS; + + self.vmi.os().read_unicode_string_in(( + self.va + + RTL_USER_PROCESS_PARAMETERS.CurrentDirectory.offset() + + CURDIR.DosPath.offset(), + self.root, + )) + } + + fn dll_path(&self) -> Result { + let offsets = self.offsets(); + let RTL_USER_PROCESS_PARAMETERS = &offsets._RTL_USER_PROCESS_PARAMETERS; + + self.vmi.os().read_unicode_string_in(( + self.va + RTL_USER_PROCESS_PARAMETERS.DllPath.offset(), + self.root, + )) + } + + fn image_path_name(&self) -> Result { + let offsets = self.offsets(); + let RTL_USER_PROCESS_PARAMETERS = &offsets._RTL_USER_PROCESS_PARAMETERS; + + self.vmi.os().read_unicode_string_in(( + self.va + RTL_USER_PROCESS_PARAMETERS.ImagePathName.offset(), + self.root, + )) + } + + fn command_line(&self) -> Result { + let offsets = self.offsets(); + let RTL_USER_PROCESS_PARAMETERS = &offsets._RTL_USER_PROCESS_PARAMETERS; + + self.vmi.os().read_unicode_string_in(( + self.va + RTL_USER_PROCESS_PARAMETERS.CommandLine.offset(), + self.root, + )) + } +} + +struct WindowsProcessParameters32<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_RTL_USER_PROCESS_PARAMETERS32` structure. + va: Va, + + /// The translation root. + root: Pa, +} + +impl<'a, Driver> WindowsProcessParameters32<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va, root: Pa) -> Self { + Self { vmi, va, root } + } + + fn current_directory(&self) -> Result { + const RTL_USER_PROCESS_PARAMETERS32_CurrentDirectory_offset: u64 = 0x24; + + self.vmi.os().read_unicode_string32_in(( + self.va + RTL_USER_PROCESS_PARAMETERS32_CurrentDirectory_offset, + self.root, + )) + } + + fn dll_path(&self) -> Result { + const RTL_USER_PROCESS_PARAMETERS32_DllPath_offset: u64 = 0x30; + + self.vmi.os().read_unicode_string32_in(( + self.va + RTL_USER_PROCESS_PARAMETERS32_DllPath_offset, + self.root, + )) + } + + fn image_path_name(&self) -> Result { + const RTL_USER_PROCESS_PARAMETERS32_ImagePathName_offset: u64 = 0x38; + + self.vmi.os().read_unicode_string32_in(( + self.va + RTL_USER_PROCESS_PARAMETERS32_ImagePathName_offset, + self.root, + )) + } + + fn command_line(&self) -> Result { + const RTL_USER_PROCESS_PARAMETERS32_CommandLine_offset: u64 = 0x40; + + self.vmi.os().read_unicode_string32_in(( + self.va + RTL_USER_PROCESS_PARAMETERS32_CommandLine_offset, + self.root, + )) + } +} diff --git a/crates/vmi-os-windows/src/comps/region.rs b/crates/vmi-os-windows/src/comps/region.rs new file mode 100644 index 0000000..4a2f9b5 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/region.rs @@ -0,0 +1,339 @@ +use once_cell::unsync::OnceCell; +use vmi_core::{ + os::{VmiOsRegion, VmiOsRegionKind}, + Architecture, MemoryAccess, Va, VmiDriver, VmiError, VmiState, VmiVa, +}; + +use super::{macros::impl_offsets, WindowsControlArea}; +use crate::{ArchAdapter, OffsetsExt, WindowsOs}; + +/// A Windows memory region. +/// +/// A memory region represents a range of virtual memory allocated +/// within a process. It is managed by the Windows memory manager +/// and described by a **Virtual Address Descriptor (VAD)**. +/// +/// # Implementation Details +/// +/// Corresponds to `_MMVAD`. +pub struct WindowsRegion<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_MMVAD` structure. + va: Va, + + /// Cached VAD flags. + vad_flags: OnceCell, +} + +impl VmiVa for WindowsRegion<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl std::fmt::Debug for WindowsRegion<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let start = self.start(); + let end = self.end(); + let protection = self.protection(); + //let kind = self.kind(); + + f.debug_struct("WindowsOsRegion") + .field("start", &start) + .field("end", &end) + .field("protection", &protection) + //.field("kind", &kind) + .finish() + } +} + +impl<'a, Driver> WindowsRegion<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows memory region. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, vad: Va) -> Self { + Self { + vmi, + va: vad, + vad_flags: OnceCell::new(), + } + } + + /// Returns the starting VPN of the VAD. + /// + /// # Implementation Details + /// + /// The starting VPN is calculated from `_MMVAD_SHORT.StartingVpn` and, + /// if present, `_MMVAD_SHORT.StartingVpnHigh` fields. + pub fn starting_vpn(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + let starting_vpn_low = self.vmi.read_field(self.va, &MMVAD_SHORT.StartingVpn)?; + let starting_vpn_high = match &MMVAD_SHORT.StartingVpnHigh { + Some(StartingVpnHigh) => self.vmi.read_field(self.va, StartingVpnHigh)?, + None => 0, + }; + + Ok((starting_vpn_high << 32) | starting_vpn_low) + } + + /// Returns the ending VPN of the VAD. + /// + /// # Implementation Details + /// + /// The ending VPN is calculated from `_MMVAD_SHORT.EndingVpn` and, + /// if present, `_MMVAD_SHORT.EndingVpnHigh` fields. + pub fn ending_vpn(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + let ending_vpn_low = self.vmi.read_field(self.va, &MMVAD_SHORT.EndingVpn)?; + let ending_vpn_high = match &MMVAD_SHORT.EndingVpnHigh { + Some(EndingVpnHigh) => self.vmi.read_field(self.va, EndingVpnHigh)?, + None => 0, + }; + + Ok((ending_vpn_high << 32) | ending_vpn_low) + } + + /// Returns the VAD flags. + /// + /// # Notes + /// + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.VadFlags`. + pub fn vad_flags(&self) -> Result { + self.vad_flags + .get_or_try_init(|| { + let offsets = self.offsets(); + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + self.vmi.read_field(self.va, &MMVAD_SHORT.VadFlags) + }) + .copied() + } + + /// Returns the VAD type. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.VadFlags.VadType`. + pub fn vad_type(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_FLAGS = &offsets._MMVAD_FLAGS; + + let vad_flags = self.vad_flags()?; + Ok(MMVAD_FLAGS.VadType.extract(vad_flags) as u8) + } + + /// Returns the memory protection of the VAD. + /// + /// # Implementation Details + /// + /// Calculated from `_MMVAD_SHORT.VadFlags.Protection` field. + pub fn vad_protection(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_FLAGS = &offsets._MMVAD_FLAGS; + + let flags = self.vad_flags()?; + let protection = MMVAD_FLAGS.Protection.extract(flags) as u8; + + Ok(protection) + } + + /// Checks if the VAD represents private memory. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.VadFlags.PrivateMemory`. + pub fn private_memory(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_FLAGS = &offsets._MMVAD_FLAGS; + + let vad_flags = self.vad_flags()?; + Ok(MMVAD_FLAGS.PrivateMemory.extract(vad_flags) != 0) + } + + /// Checks if the memory of the VAD is committed. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.VadFlags.MemCommit` (Windows 7) or + /// `_MMVAD_SHORT.VadFlags1.MemCommit` (Windows 8+). + pub fn mem_commit(&self) -> Result { + let offsets = self.offsets(); + let MMVAD_FLAGS = &offsets._MMVAD_FLAGS; + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + let vad_flags = self.vad_flags()?; + + // If `MMVAD_FLAGS.MemCommit` is present (Windows 7), then we fetch the + // value from it. Otherwise, we load the `VadFlags1` field from the VAD + // and fetch it from there. + let mem_commit = match MMVAD_FLAGS.MemCommit { + // `MemCommit` is present in `MMVAD_FLAGS` + Some(MemCommit) => MemCommit.extract(vad_flags) != 0, + None => match (&self.offsets().ext(), MMVAD_SHORT.VadFlags1) { + // `MemCommit` is present in `MMVAD_FLAGS1` + (Some(OffsetsExt::V2(offsets)), Some(VadFlags1)) => { + let MMVAD_FLAGS1 = &offsets._MMVAD_FLAGS1; + let vad_flags1 = self.vmi.read_field(self.va, &VadFlags1)?; + MMVAD_FLAGS1.MemCommit.extract(vad_flags1) != 0 + } + _ => { + panic!("Failed to read MemCommit from VAD"); + } + }, + }; + + Ok(mem_commit) + } + + /// Returns the left child of the VAD. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.Left`. + pub fn left_child(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + let left_child = self.vmi.read_field(self.va, &MMVAD_SHORT.Left)?; + + if left_child == 0 { + return Ok(None); + } + + Ok(Some(WindowsRegion::new(self.vmi, Va(left_child)))) + } + + /// Returns the right child of the VAD. + /// + /// # Implementation Details + /// + /// Corresponds to `_MMVAD_SHORT.Right`. + pub fn right_child(&self) -> Result>, VmiError> { + let offsets = self.offsets(); + let MMVAD_SHORT = &offsets._MMVAD_SHORT; + + let right_child = self.vmi.read_field(self.va, &MMVAD_SHORT.Right)?; + + if right_child == 0 { + return Ok(None); + } + + Ok(Some(WindowsRegion::new(self.vmi, Va(right_child)))) + } +} + +impl<'a, Driver> VmiOsRegion<'a, Driver> for WindowsRegion<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Os = WindowsOs; + + /// Returns the starting virtual address of the memory region. + /// + /// # Implementation Details + /// + /// The starting address is calculated from `_MMVAD_SHORT.StartingVpn` and, + /// if present, `_MMVAD_SHORT.StartingVpnHigh` fields. + fn start(&self) -> Result { + Ok(Va(self.starting_vpn()? << 12)) + } + + /// Returns the ending virtual address of the memory region. + /// + /// # Implementation Details + /// + /// The ending address is calculated from `_MMVAD_SHORT.EndingVpn` and, + /// if present, `_MMVAD_SHORT.EndingVpnHigh` fields. + fn end(&self) -> Result { + Ok(Va((self.ending_vpn()? + 1) << 12)) + } + + /// Returns the memory protection of the memory region. + /// + /// # Implementation Details + /// + /// Calculated from `_MMVAD_SHORT.VadFlags.Protection` field. + fn protection(&self) -> Result { + const MM_ZERO_ACCESS: u8 = 0; // this value is not used. + const MM_READONLY: u8 = 1; + const MM_EXECUTE: u8 = 2; + const MM_EXECUTE_READ: u8 = 3; + const MM_READWRITE: u8 = 4; // bit 2 is set if this is writable. + const MM_WRITECOPY: u8 = 5; + const MM_EXECUTE_READWRITE: u8 = 6; + const MM_EXECUTE_WRITECOPY: u8 = 7; + + match self.vad_protection()? { + MM_ZERO_ACCESS => Ok(MemoryAccess::default()), + MM_READONLY => Ok(MemoryAccess::R), + MM_EXECUTE => Ok(MemoryAccess::X), + MM_EXECUTE_READ => Ok(MemoryAccess::RX), + MM_READWRITE => Ok(MemoryAccess::RW), + MM_WRITECOPY => Ok(MemoryAccess::RW), // REVIEW: is this correct? + MM_EXECUTE_READWRITE => Ok(MemoryAccess::RWX), + MM_EXECUTE_WRITECOPY => Ok(MemoryAccess::RWX), // REVIEW: is this correct? + _ => Ok(MemoryAccess::default()), + } + } + + /// Returns the memory region's kind. + fn kind(&self) -> Result, VmiError> { + let offsets = self.offsets(); + let MMVAD = &offsets._MMVAD; + let SUBSECTION = &offsets._SUBSECTION; + + /* + const VadImageMap: u8 = 2; + + let vad_type = self.vad_type()?; + if vad_type != VadImageMap { + return Ok(VmiOsRegionKind::Private); + } + */ + + if self.private_memory()? { + return Ok(VmiOsRegionKind::Private); + } + + let subsection = Va(self.vmi.read_field(self.va, &MMVAD.Subsection)?); + let control_area = Va(self.vmi.read_field(subsection, &SUBSECTION.ControlArea)?); + + let region_kind = WindowsControlArea::new(self.vmi, control_area); + + const VadImageMap: u8 = 2; + let vad_type = self.vad_type()?; + if vad_type == VadImageMap { + Ok(VmiOsRegionKind::MappedImage(region_kind)) + } + else { + Ok(VmiOsRegionKind::MappedData(region_kind)) + } + } +} diff --git a/crates/vmi-os-windows/src/comps/session.rs b/crates/vmi-os-windows/src/comps/session.rs new file mode 100644 index 0000000..be69c48 --- /dev/null +++ b/crates/vmi-os-windows/src/comps/session.rs @@ -0,0 +1,93 @@ +use vmi_core::{os::ProcessObject, Architecture, Va, VmiDriver, VmiError, VmiState, VmiVa}; + +use super::{macros::impl_offsets, WindowsObject, WindowsProcess}; +use crate::{ArchAdapter, ListEntryIterator, WindowsOs}; + +/// A Windows session space. +/// +/// The session space is a kernel structure that contains the +/// session-specific data for a process. +/// +/// Each logon session (e.g., when a user connects via Remote Desktop) gets +/// a separate instance of `_MM_SESSION_SPACE`. +/// +/// # Implementation Details +/// +/// Corresponds to `_MM_SESSION_SPACE`. +pub struct WindowsSession<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// The VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// The virtual address of the `_MM_SESSION_SPACE` structure. + va: Va, +} + +impl<'a, Driver> From> for WindowsObject<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn from(value: WindowsSession<'a, Driver>) -> Self { + Self::new(value.vmi, value.va) + } +} + +impl VmiVa for WindowsSession<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn va(&self) -> Va { + self.va + } +} + +impl<'a, Driver> WindowsSession<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + impl_offsets!(); + + /// Creates a new Windows session space. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, va: Va) -> Self { + Self { vmi, va } + } + + /// Returns the session ID. + /// + /// # Implementation Details + /// + /// Corresponds to `_MM_SESSION_SPACE.SessionId`. + pub fn id(&self) -> Result { + let offsets = self.offsets(); + let MM_SESSION_SPACE = &offsets._MM_SESSION_SPACE; + + self.vmi + .read_u32(self.va + MM_SESSION_SPACE.SessionId.offset()) + } + + /// Returns the list of processes in the session. + /// + /// # Implementation Details + /// + /// Corresponds to `_MM_SESSION_SPACE.ProcessList`. + pub fn processes( + &'a self, + ) -> Result, VmiError>>, VmiError> { + let offsets = self.offsets(); + let MM_SESSION_SPACE = &offsets._MM_SESSION_SPACE; + let EPROCESS = &offsets._EPROCESS; + + Ok(ListEntryIterator::new( + self.vmi, + self.va + MM_SESSION_SPACE.ProcessList.offset(), + EPROCESS.SessionProcessLinks.offset(), + ) + .map(move |result| result.map(|entry| WindowsProcess::new(self.vmi, ProcessObject(entry))))) + } +} diff --git a/crates/vmi-os-windows/src/error.rs b/crates/vmi-os-windows/src/error.rs new file mode 100644 index 0000000..a8abb3c --- /dev/null +++ b/crates/vmi-os-windows/src/error.rs @@ -0,0 +1,17 @@ +/// Error types for Windows operations. +#[derive(thiserror::Error, Debug)] +pub enum WindowsError { + /// Corrupted struct. + #[error("Corrupted struct: {0}")] + CorruptedStruct(&'static str), + + /// Corrupted struct. + #[error(transparent)] + Pe(#[from] crate::PeError), +} + +impl From for vmi_core::VmiError { + fn from(value: WindowsError) -> Self { + vmi_core::VmiError::Os(value.into()) + } +} diff --git a/crates/vmi-os-windows/src/iter/list.rs b/crates/vmi-os-windows/src/iter/list.rs new file mode 100644 index 0000000..d63eb80 --- /dev/null +++ b/crates/vmi-os-windows/src/iter/list.rs @@ -0,0 +1,205 @@ +use std::iter::FusedIterator; + +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState}; + +use crate::{ArchAdapter, WindowsOs}; + +/// An iterator for traversing list entries. +/// +/// Iterate over entries in a linked list structure, specifically `LIST_ENTRY`. +pub struct ListEntryIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// Current entry. + current: Option, + + /// Address of the list head. + list_head: Va, + + /// Offset to the containing structure. + /// + /// The offset is subtracted from the entry address to get the containing + /// structure, similar to the `CONTAINING_RECORD` macro in the Windows + /// kernel. + offset: u64, + + /// Offset to the forward link pointer (`LIST_ENTRY.Flink`). + offset_flink: u64, + + /// Offset to the backward link pointer (`LIST_ENTRY.Blink`). + offset_blink: u64, + + /// Whether the iterator has been initialized. + initialized: bool, +} + +impl<'a, Driver> ListEntryIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new list entry iterator. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, list_head: Va, offset: u64) -> Self { + let LIST_ENTRY = &vmi.underlying_os().offsets._LIST_ENTRY; + let (offset_flink, offset_blink) = (LIST_ENTRY.Flink.offset(), LIST_ENTRY.Blink.offset()); + + Self { + vmi, + current: None, + list_head, + offset, + offset_flink, + offset_blink, + initialized: false, + } + } + + /// Creates an empty tree node iterator. + pub fn empty(vmi: VmiState<'a, Driver, WindowsOs>) -> Self { + Self { + vmi, + current: None, + list_head: Va(0), + offset: 0, + offset_flink: 0, + offset_blink: 0, + initialized: true, + } + } + + /// Returns the next entry in the list. + /// + /// Corresponds to the `LIST_ENTRY.Flink` pointer. + fn next_entry(&self, entry: Va) -> Result { + self.vmi.read_va_native(entry + self.offset_flink) + } + + /// Returns the previous entry in the list. + /// + /// Corresponds to the `LIST_ENTRY.Blink` pointer. + fn previous_entry(&self, entry: Va) -> Result { + self.vmi.read_va_native(entry + self.offset_blink) + } + + /// Returns the first entry in the list. + /// + /// Returns `None` if the `list_head` is `NULL`. + fn first_entry(&self) -> Result, VmiError> { + if self.list_head.is_null() { + return Ok(None); + } + + Ok(Some(self.next_entry(self.list_head)?)) + } + + /// Returns the last entry in the list. + /// + /// Returns `None` if the `list_head` is `NULL`. + fn last_entry(&self) -> Result, VmiError> { + if self.list_head.is_null() { + return Ok(None); + } + + Ok(Some(self.previous_entry(self.list_head)?)) + } + + /// Walks to the next entry in the list. + fn walk_next(&mut self) -> Result, VmiError> { + let entry = match self.current { + Some(entry) => entry, + None => { + // If `self.current` is `None`, we need to initialize the iterator. + // + // However, if the iterator has already been initialized, we should + // return `None` to prevent infinite iteration. + if self.initialized { + return Ok(None); + } + + let first = match self.first_entry()? { + Some(first) => first, + None => return Ok(None), + }; + + self.current = Some(first); + self.initialized = true; + first + } + }; + + if entry == self.list_head { + return Ok(None); + } + + self.current = Some(self.next_entry(entry)?); + + Ok(Some(entry - self.offset)) + } + + /// Walks to the previous entry in the list. + fn walk_next_back(&mut self) -> Result, VmiError> { + let entry = match self.current { + Some(entry) => entry, + None => { + // If `self.current` is `None`, we need to initialize the iterator. + // + // However, if the iterator has already been initialized, we should + // return `None` to prevent infinite iteration. + if self.initialized { + return Ok(None); + } + + let last = match self.last_entry()? { + Some(last) => last, + None => return Ok(None), + }; + + self.current = Some(last); + self.initialized = true; + last + } + }; + + if entry == self.list_head { + return Ok(None); + } + + self.current = Some(self.previous_entry(entry)?); + + Ok(Some(entry - self.offset)) + } +} + +impl Iterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.walk_next().transpose() + } +} + +impl DoubleEndedIterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + fn next_back(&mut self) -> Option { + self.walk_next_back().transpose() + } +} + +impl FusedIterator for ListEntryIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ +} diff --git a/crates/vmi-os-windows/src/iter/mod.rs b/crates/vmi-os-windows/src/iter/mod.rs new file mode 100644 index 0000000..079f11e --- /dev/null +++ b/crates/vmi-os-windows/src/iter/mod.rs @@ -0,0 +1,4 @@ +mod list; +mod tree; + +pub use self::{list::ListEntryIterator, tree::TreeNodeIterator}; diff --git a/crates/vmi-os-windows/src/iter/tree.rs b/crates/vmi-os-windows/src/iter/tree.rs new file mode 100644 index 0000000..7b1ff1e --- /dev/null +++ b/crates/vmi-os-windows/src/iter/tree.rs @@ -0,0 +1,249 @@ +use std::iter::FusedIterator; + +use vmi_core::{Architecture, Va, VmiDriver, VmiError, VmiState}; + +use crate::{offsets::OffsetsExt, ArchAdapter, WindowsOs}; + +/// An iterator for traversing tree nodes. +/// +/// Iterate over nodes in a tree-like structure, specifically `MMADDRESS_NODE` +/// (Windows 7) and `RTL_BALANCED_NODE` (Windows 8.1+). +pub struct TreeNodeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// VMI state. + vmi: VmiState<'a, Driver, WindowsOs>, + + /// Current node. + current: Option, + + /// Root node. + root: Va, + + /// Offset to the left child pointer. + /// + /// Either `MMADDRESS_NODE.LeftChild` or `RTL_BALANCED_NODE.Left`. + offset_left: u64, + + /// Offset to the right child pointer. + /// + /// Either `MMADDRESS_NODE.RightChild` or `RTL_BALANCED_NODE.Right`. + offset_right: u64, + + /// Offset to the parent pointer. + /// + /// Either `MMADDRESS_NODE.Parent` or `RTL_BALANCED_NODE.ParentValue`. + offset_parent: u64, + + /// Whether the iterator has been initialized. + initialized: bool, +} + +impl<'a, Driver> TreeNodeIterator<'a, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new tree node iterator. + pub fn new(vmi: VmiState<'a, Driver, WindowsOs>, root: Va) -> Self { + let offsets = &vmi.underlying_os().offsets; + + let (offset_left, offset_right, offset_parent) = match &offsets.ext { + Some(OffsetsExt::V1(offsets)) => { + let MMADDRESS_NODE = &offsets._MMADDRESS_NODE; + + ( + MMADDRESS_NODE.LeftChild.offset(), + MMADDRESS_NODE.RightChild.offset(), + MMADDRESS_NODE.Parent.offset(), + ) + } + Some(OffsetsExt::V2(offsets)) => { + let RTL_BALANCED_NODE = &offsets._RTL_BALANCED_NODE; + + ( + RTL_BALANCED_NODE.Left.offset(), + RTL_BALANCED_NODE.Right.offset(), + RTL_BALANCED_NODE.ParentValue.offset(), + ) + } + None => panic!("OffsetsExt not set"), + }; + + Self { + vmi, + current: None, + root, + offset_left, + offset_right, + offset_parent, + initialized: false, + } + } + + /// Creates an empty tree node iterator. + pub fn empty(vmi: VmiState<'a, Driver, WindowsOs>) -> Self { + Self { + vmi, + current: None, + root: Va(0), + offset_left: 0, + offset_right: 0, + offset_parent: 0, + initialized: true, + } + } + + /// Returns the left child of a node. + fn left_child(&self, node: Va) -> Result { + self.vmi.read_va_native(node + self.offset_left) + } + + /// Returns the right child of a node. + fn right_child(&self, node: Va) -> Result { + self.vmi.read_va_native(node + self.offset_right) + } + + /// Returns the parent of a node. + fn parent_node(&self, node: Va) -> Result { + let result = self.vmi.read_va_native(node + self.offset_parent)?; + + // + // We need to clear the Balance bits from the Parent pointer: + // + // MMADDRESS_NODE: + // union { + // LONG_PTR Balance : 2; + // struct _MMADDRESS_NODE *Parent; + // } + // + // RTL_BALANCED_NODE: + // union { + // UCHAR Red : 1; + // UCHAR Balance : 2; + // ULONG_PTR ParentValue; + // } + // + + Ok(result & !0b11) + } + + /// Finds the first node in the tree. + /// + /// Returns `None` if the tree is `NULL`. + fn find_first(&self) -> Result, VmiError> { + let offsets = &self.vmi.underlying_os().offsets; + + if self.root.is_null() { + return Ok(None); + } + + let mut current = match &offsets.ext { + Some(OffsetsExt::V1(offsets)) => { + let MMADDRESS_NODE = &offsets._MMADDRESS_NODE; + + self.vmi + .read_va_native(self.root + MMADDRESS_NODE.RightChild.offset())? + } + Some(OffsetsExt::V2(_)) => self.root, + None => panic!("OffsetsExt not set"), + }; + + loop { + let left = self.left_child(current)?; + + if left.is_null() { + break; + } + + current = left; + } + + Ok(Some(current)) + } + + /// Walks to the next node in the tree. + fn walk_next(&mut self) -> Result, VmiError> { + let result = match self.current { + Some(current) => current, + None => { + // If `self.current` is `None`, we need to initialize the iterator. + // + // However, if the iterator has already been initialized, we should + // return `None` to prevent infinite iteration. + if self.initialized { + return Ok(None); + } + + let first = match self.find_first()? { + Some(first) => first, + None => return Ok(None), + }; + + self.current = Some(first); + self.initialized = true; + first + } + }; + + let mut current = result; + let right = self.right_child(current)?; + + if !right.is_null() { + current = right; + + loop { + let left = self.left_child(current)?; + + if left.is_null() { + self.current = Some(current); + break; + } + + current = left; + } + } + else { + loop { + let parent = self.parent_node(current)?; + + if parent.is_null() || parent == current { + self.current = None; + break; + } + + let left = self.left_child(parent)?; + + if left == current { + self.current = Some(parent); + break; + } + + current = parent; + } + } + + Ok(Some(result)) + } +} + +impl Iterator for TreeNodeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.walk_next().transpose() + } +} + +impl FusedIterator for TreeNodeIterator<'_, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ +} diff --git a/crates/vmi-os-windows/src/lib.rs b/crates/vmi-os-windows/src/lib.rs index 667ca1a..f4d2573 100644 --- a/crates/vmi-os-windows/src/lib.rs +++ b/crates/vmi-os-windows/src/lib.rs @@ -50,23 +50,12 @@ use std::{cell::RefCell, collections::HashMap}; -use ::object::{ - pe::{ - ImageNtHeaders32, ImageNtHeaders64, IMAGE_DIRECTORY_ENTRY_EXPORT, - IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC, - }, - read::pe::{optional_header_magic, ExportTarget, ImageNtHeaders}, - LittleEndian as LE, -}; use isr_core::Profile; -use vmi_arch_amd64::{Amd64, Cr3}; +use once_cell::unsync::OnceCell; use vmi_core::{ - os::{ - OsArchitecture, OsExt, OsImageExportedSymbol, OsMapped, OsModule, OsProcess, OsRegion, - OsRegionKind, ProcessId, ProcessObject, StructReader, ThreadId, ThreadObject, VmiOs, - }, - AccessContext, Architecture, Gfn, Hex, MemoryAccess, Pa, Registers as _, Va, VmiCore, - VmiDriver, VmiError, + os::{ProcessObject, ThreadObject, VmiOs, VmiOsThread}, + AccessContext, Architecture, Gfn, Hex, Registers as _, Va, VmiCore, VmiDriver, VmiError, + VmiState, }; use vmi_macros::derive_trait_from_impl; use zerocopy::{FromBytes, IntoBytes}; @@ -74,13 +63,27 @@ use zerocopy::{FromBytes, IntoBytes}; mod arch; use self::arch::ArchAdapter; -mod pe; -pub use self::pe::{CodeView, PeError, PeLite, PeLite32, PeLite64}; +mod error; +pub use self::error::WindowsError; + +mod iter; +pub use self::iter::{ListEntryIterator, TreeNodeIterator}; + +pub mod pe; +pub use self::pe::{CodeView, Pe, PeError}; mod offsets; -use self::offsets::{v1, v2}; pub use self::offsets::{Offsets, OffsetsExt, Symbols}; // TODO: make private + remove offsets() & symbols() methods +mod comps; +pub use self::comps::{ + ParseObjectTypeError, WindowsControlArea, WindowsDirectoryObject, WindowsFileObject, + WindowsHandleTable, WindowsHandleTableEntry, WindowsImage, WindowsModule, WindowsObject, + WindowsObjectAttributes, WindowsObjectHeaderNameInfo, WindowsObjectType, WindowsObjectTypeKind, + WindowsPeb, WindowsProcess, WindowsProcessParameters, WindowsRegion, WindowsSectionObject, + WindowsSession, WindowsThread, WindowsWow64Kind, +}; + /// VMI operations for the Windows operating system. /// /// `WindowsOs` provides methods and utilities for introspecting a Windows-based @@ -140,18 +143,17 @@ pub use self::offsets::{Offsets, OffsetsExt, Symbols}; // TODO: make private + r /// Retrieving information about the current process: /// /// ```no_run -/// # use vmi::{VcpuId, VmiDriver, VmiSession, os::windows::WindowsOs}; +/// # use vmi::{VcpuId, VmiDriver, VmiState, os::windows::WindowsOs}; /// # /// # fn example( -/// # vmi: &VmiSession>, +/// # vmi: &VmiState>, /// # ) -> Result<(), Box> /// # where /// # Driver: VmiDriver, /// # { -/// let registers = vmi.registers(VcpuId(0))?; -/// let current_process = vmi.os().current_process(®isters)?; -/// let process_id = vmi.os().process_id(®isters, current_process)?; -/// let process_name = vmi.os().process_filename(®isters, current_process)?; +/// let process = vmi.os().current_process()?; +/// let process_id = process.id()?; +/// let process_name = process.name()?; /// println!("Current process: {} (PID: {})", process_name, process_id); /// # Ok(()) /// # } @@ -160,18 +162,16 @@ pub use self::offsets::{Offsets, OffsetsExt, Symbols}; // TODO: make private + r /// Enumerating all processes: /// /// ```no_run -/// # use vmi::{VcpuId, VmiDriver, VmiSession, os::windows::WindowsOs}; +/// # use vmi::{VcpuId, VmiDriver, VmiState, os::windows::WindowsOs}; /// # /// # fn example( -/// # vmi: &VmiSession>, +/// # vmi: &VmiState>, /// # ) -> Result<(), Box> /// # where /// # Driver: VmiDriver, /// # { -/// let registers = vmi.registers(VcpuId(0))?; -/// let processes = vmi.os().processes(®isters)?; -/// for process in processes { -/// println!("Process: {} (PID: {})", process.name, process.id); +/// for process in vmi.os().processes()? { +/// println!("Process: {} (PID: {})", process.name()?, process.id()?); /// } /// # Ok(()) /// # } @@ -192,15 +192,17 @@ where offsets: Offsets, symbols: Symbols, - kernel_image_base: RefCell>, - highest_user_address: RefCell>, - object_header_cookie: RefCell>, - object_type_cache: RefCell>, + kernel_image_base: OnceCell, + highest_user_address: OnceCell, + object_root_directory: OnceCell, // _OBJECT_DIRECTORY* + object_header_cookie: OnceCell, + object_type_cache: RefCell>, + object_type_name_cache: RefCell>, - ki_kva_shadow: RefCell>, - mm_pfn_database: RefCell>, - nt_build_lab: RefCell>, - nt_build_lab_ex: RefCell>, + ki_kva_shadow: OnceCell, + mm_pfn_database: OnceCell, // _MMPFN* + nt_build_lab: OnceCell, + nt_build_lab_ex: OnceCell, _marker: std::marker::PhantomData, } @@ -256,297 +258,26 @@ pub struct WindowsExceptionRecord { pub information: Vec, } -/// Represents a `_HANDLE_TABLE` structure. -#[derive(Debug)] -pub struct WindowsHandleTable { - /// The `TableCode` field of the handle table. - /// - /// A pointer to the top level handle table tree node. - pub table_code: u64, -} - -/// Represents a `_HANDLE_TABLE_ENTRY` structure. -#[derive(Debug)] -pub struct WindowsHandleTableEntry { - /// The `Object` (or `ObjectPointerBits`) field of the handle table entry. - /// - /// A pointer to an `_OBJECT_HEADER` structure. - pub object: Va, // _OBJECT_HEADER* - - /// The `ObAttributes` (or `Attributes`) field of the handle table entry. - pub attributes: u32, - - /// The `GrantedAccess` (or `GrantedAccessBits`) field of the handle table entry. - pub granted_access: u32, -} - -/// Represents a `_PEB` structure. -#[derive(Debug)] -pub struct WindowsPeb { - /// The address of this `_PEB` structure. - pub address: Va, - - /// The `Peb->ProcessParameters->CurrentDirectory` field. - pub current_directory: String, - - /// The `Peb->ProcessParameters->DllPath` field. - pub dll_path: String, - - /// The `Peb->ProcessParameters->ImagePathName` field. - pub image_path_name: String, - - /// The `Peb->ProcessParameters->CommandLine` field. - pub command_line: String, -} - -/// Identifies the type of a Windows kernel object. -/// -/// Windows uses a object-based kernel architecture where various system -/// resources (processes, threads, files, etc.) are represented as kernel -/// objects. This enum identifies the different types of objects that can -/// be encountered during introspection. -/// -/// Each variant corresponds to a specific object type string used internally -/// by the Windows kernel. For example, "Process" for process objects, -/// "Thread" for thread objects, etc. -#[derive(Debug, Clone, Copy)] -pub enum WindowsObjectType { - /// ALPC Port object. - /// - /// Represented by `_ALPC_PORT` structure. - /// Has `ALPC Port` type name. - AlpcPort, - - /// Debug object. - /// - /// Represented by `_DEBUG_OBJECT` structure. - /// Has `DebugObject` type name. - DebugObject, - - /// Device object. - /// - /// Represented by `_DEVICE_OBJECT` structure. - /// Has `Device` type name. - Device, - - /// Directory object. - /// - /// Represented by `_OBJECT_DIRECTORY` structure. - /// Has `Directory` type name. - Directory, - - /// Driver object. - /// - /// Represented by `_DRIVER_OBJECT` structure. - /// Has `Driver` type name. - Driver, - - /// Event object. - /// - /// Represented by `_KEVENT` structure. - /// Has `Event` type name. - Event, - - /// File object. - /// - /// Represented by `_FILE_OBJECT` structure. - /// Has `File` type name. - File, - - /// Job object. - /// - /// Represented by `_EJOB` structure. - /// Has `Job` type name. - Job, - - /// Key object. - /// - /// Represented by `_CM_KEY_BODY` structure. - /// Has `Key` type name. - Key, - - /// Mutant object. - /// - /// Represented by `_KMUTANT` structure. - /// Has `Mutant` type name. - Mutant, - - /// Port object. - /// - /// Represented by `_PORT_MESSAGE` structure. - /// Has `Port` type name. - Port, - - /// Process object. - /// - /// Represented by `_EPROCESS` structure. - /// Has `Process` type name. - Process, - - /// Section object. - /// - /// Represented by `_SECTION` (or `_SECTION_OBJECT`) structure. - /// Has `Section` type name. - Section, - - /// Symbolic link object. - /// - /// Represented by `_OBJECT_SYMBOLIC_LINK` structure. - /// Has `SymbolicLink` type name. - SymbolicLink, - - /// Thread object. - /// - /// Represented by `_ETHREAD` structure. - /// Has `Thread` type name. - Thread, - - /// Timer object. - /// - /// Represented by `_KTIMER` structure. - /// Has `Timer` type name. - Timer, - - /// Token object. - /// - /// Represented by `_TOKEN` structure. - /// Has `Token` type name. - Token, - - /// Type object. - /// - /// Represented by `_OBJECT_TYPE` structure. - /// Has `Type` type name. - Type, -} - -/// A Windows object name. -/// -/// Represents the name of a Windows object, along with its directory. -#[derive(Debug)] -pub struct WindowsObjectName { - /// A `Directory` field of the `_OBJECT_HEADER_NAME_INFO` structure. - /// - /// A pointer to the `_OBJECT_DIRECTORY` structure. - pub directory: Va, // _OBJECT_DIRECTORY* - - /// A `Name` field of the `_OBJECT_HEADER_NAME_INFO` structure. - pub name: String, -} - -/// A Windows object. -#[derive(Debug)] -pub enum WindowsObject { - /// File object. - File(WindowsFileObject), - - /// Section object. - Section(WindowsSectionObject), -} - -/// A Windows file object. -#[derive(Debug)] -pub struct WindowsFileObject { - /// The `DeviceObject` field of the file object. - /// - /// A pointer to the `_DEVICE_OBJECT` structure. - pub device_object: Va, - - /// The `FileName` field of the file object. - pub filename: String, -} - -/// A Windows section object. -#[derive(Debug)] -pub struct WindowsSectionObject { - /// The virtual address range of the section. - pub region: OsRegion, - - /// The size of the section. - pub size: u64, -} - -/// Represents a `_VAD` structure. -#[derive(Debug)] -pub struct WindowsVad { - /// The `StartingVpn` field of the VAD. - /// - /// The starting virtual page number of the VAD. - pub starting_vpn: u64, - - /// The `EndingVpn` field of the VAD. - /// - /// The ending virtual page number of the VAD. - pub ending_vpn: u64, - - /// The `VadType` field of the VAD. - pub vad_type: u8, - - /// The `Protection` field of the VAD. - pub protection: u8, - - /// The `PrivateMemory` field of the VAD. - pub private_memory: bool, - - /// The `MemCommit` field of the VAD. - pub mem_commit: bool, - - /// The `Left` field of the VAD. - pub left_child: Va, - - /// The `Right` field of the VAD. - pub right_child: Va, -} - -// -// Private types -// - -/// The address space type in a WoW64 process. -enum WindowsWow64Kind { - /// Native address space - Native = 0, - - /// x86 (32-bit) address space under WoW64 - X86 = 1, - // Arm32 = 2, - // Amd64 = 3, - // ChpeX86 = 4, - // VsmEnclave = 5, +macro_rules! offset { + ($vmi:expr, $field:ident) => { + &this!($vmi).offsets.$field + }; } -/// A 32-bit or 64-bit virtual address in WoW64 processes. -struct WindowsWow64Va { - /// The virtual address. - va: Va, - - /// The kind of the virtual address. - kind: WindowsWow64Kind, +macro_rules! symbol { + ($vmi:expr, $field:ident) => { + this!($vmi).symbols.$field + }; } -impl WindowsWow64Va { - fn native(va: Va) -> Self { - Self { - va, - kind: WindowsWow64Kind::Native, - } - } - - fn x86(va: Va) -> Self { - Self { - va, - kind: WindowsWow64Kind::X86, - } - } +macro_rules! this { + ($vmi:expr) => { + $vmi.underlying_os() + }; } -#[derive_trait_from_impl( - os_session_name = WindowsOsSessionExt, - os_context_name = WindowsOsExt, - os_session_prober_name = WindowsOsSessionProberExt, - os_context_prober_name = WindowsOsProberExt -)] -#[allow(non_snake_case, non_upper_case_globals)] +#[derive_trait_from_impl(WindowsOsExt)] +#[expect(non_snake_case, non_upper_case_globals, clippy::needless_lifetimes)] impl WindowsOs where Driver: VmiDriver, @@ -566,1373 +297,394 @@ where /// Creates a new `WindowsOs` instance. pub fn new(profile: &Profile) -> Result { + Self::create(profile, OnceCell::new()) + } + + /// Creates a new `WindowsOs` instance with a known kernel base address. + pub fn with_kernel_base(profile: &Profile, kernel_base: Va) -> Result { + Self::create(profile, OnceCell::with_value(kernel_base)) + } + + fn create(profile: &Profile, kernel_image_base: OnceCell) -> Result { Ok(Self { offsets: Offsets::new(profile)?, symbols: Symbols::new(profile)?, - kernel_image_base: RefCell::new(None), - highest_user_address: RefCell::new(None), - object_header_cookie: RefCell::new(None), + kernel_image_base, + highest_user_address: OnceCell::new(), + object_root_directory: OnceCell::new(), + object_header_cookie: OnceCell::new(), object_type_cache: RefCell::new(HashMap::new()), - ki_kva_shadow: RefCell::new(None), - mm_pfn_database: RefCell::new(None), - nt_build_lab: RefCell::new(None), - nt_build_lab_ex: RefCell::new(None), + object_type_name_cache: RefCell::new(HashMap::new()), + ki_kva_shadow: OnceCell::new(), + mm_pfn_database: OnceCell::new(), + nt_build_lab: OnceCell::new(), + nt_build_lab_ex: OnceCell::new(), _marker: std::marker::PhantomData, }) } /// Returns a reference to the Windows-specific memory offsets. - pub fn offsets(&self) -> &Offsets { - &self.offsets + pub fn offsets(vmi: VmiState) -> &Offsets { + &this!(vmi).offsets } /// Returns a reference to the Windows-specific symbols. - pub fn symbols(&self) -> &Symbols { - &self.symbols - } - - #[expect(clippy::only_used_in_recursion)] - fn enumerate_tree_node_v1( - &self, - vmi: &VmiCore, - registers: &::Registers, - node: Va, - callback: &mut impl FnMut(Va) -> bool, - offsets: &v1::Offsets, - ) -> Result<(), VmiError> { - let MMADDRESS_NODE = &offsets._MMADDRESS_NODE; - - let balanced_node = StructReader::new( - vmi, - registers.address_context(node), - MMADDRESS_NODE.effective_len(), - )?; - - let left = Va(balanced_node.read(MMADDRESS_NODE.LeftChild)?); - if !left.is_null() { - self.enumerate_tree_node_v1(vmi, registers, left, callback, offsets)?; - } - - if !callback(node) { - return Ok(()); - } - - let right = Va(balanced_node.read(MMADDRESS_NODE.RightChild)?); - if !right.is_null() { - self.enumerate_tree_node_v1(vmi, registers, right, callback, offsets)?; - } - - Ok(()) - } - - #[expect(clippy::only_used_in_recursion)] - fn enumerate_tree_node_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - node: Va, - callback: &mut impl FnMut(Va) -> bool, - offsets: &v2::Offsets, - ) -> Result<(), VmiError> { - let RTL_BALANCED_NODE = &offsets._RTL_BALANCED_NODE; - - let balanced_node = StructReader::new( - vmi, - registers.address_context(node), - RTL_BALANCED_NODE.effective_len(), - )?; - - let left = Va(balanced_node.read(RTL_BALANCED_NODE.Left)?); - if !left.is_null() { - self.enumerate_tree_node_v2(vmi, registers, left, callback, offsets)?; - } - - if !callback(node) { - return Ok(()); - } - - let right = Va(balanced_node.read(RTL_BALANCED_NODE.Right)?); - if !right.is_null() { - self.enumerate_tree_node_v2(vmi, registers, right, callback, offsets)?; - } - - Ok(()) - } - - fn enumerate_tree_v1( - &self, - vmi: &VmiCore, - registers: &::Registers, - root: Va, - mut callback: impl FnMut(Va) -> bool, - offsets: &v1::Offsets, - ) -> Result<(), VmiError> { - let MM_AVL_TABLE = &offsets._MM_AVL_TABLE; - let MMADDRESS_NODE = &offsets._MMADDRESS_NODE; - - // NumberGenericTableElements is a ULONG_PTR, which is the same size - // as a pointer. - let count = vmi.read_va( - registers.address_context(root + MM_AVL_TABLE.NumberGenericTableElements.offset), - registers.address_width(), - )?; - - let count = MM_AVL_TABLE.NumberGenericTableElements.value_from(count.0); - if count == 0 { - return Ok(()); - } - - // Table->BalancedRoot.RightChild - let root = vmi.read_va( - registers.address_context( - root + MM_AVL_TABLE.BalancedRoot.offset + MMADDRESS_NODE.RightChild.offset, - ), - registers.address_width(), - )?; - - self.enumerate_tree_node_v1(vmi, registers, root, &mut callback, offsets) - } - - fn enumerate_tree_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - root: Va, - mut callback: impl FnMut(Va) -> bool, - offsets: &v2::Offsets, - ) -> Result<(), VmiError> { - self.enumerate_tree_node_v2(vmi, registers, root, &mut callback, offsets) + pub fn symbols(vmi: VmiState) -> &Symbols { + &this!(vmi).symbols } - fn image_exported_symbols_generic( - &self, + /// Locates the Windows kernel in memory based on the CPU registers. + /// This function is architecture-specific. + /// + /// On AMD64, the kernel is located by taking the `MSR_LSTAR` value and + /// reading the virtual memory page by page backwards until the `MZ` header + /// is found. + pub fn find_kernel( vmi: &VmiCore, registers: &::Registers, - image_base: Va, - ) -> Result, VmiError> - where - Pe: ImageNtHeaders, - { - let mut data = [0u8; Amd64::PAGE_SIZE as usize]; - vmi.read(registers.address_context(image_base), &mut data)?; - - let pe = PeLite::::parse(&data).map_err(|err| VmiError::Os(err.into()))?; - let entry = pe.data_directories[IMAGE_DIRECTORY_ENTRY_EXPORT]; - - let mut data = vec![0u8; entry.size.get(LE) as usize]; - vmi.read( - registers.address_context(image_base + entry.virtual_address.get(LE) as u64), - &mut data, - )?; - - let exports = pe.exports(&data).map_err(|err| VmiError::Os(err.into()))?; - Ok(exports - .iter() - .filter_map(|export| match export.target { - ExportTarget::Address(address) => Some(OsImageExportedSymbol { - name: String::from_utf8_lossy(export.name?).to_string(), - address: image_base + address as u64, - }), - _ => None, - }) - .collect()) + ) -> Result, VmiError> { + Driver::Architecture::find_kernel(vmi, registers) } - // region: File - - /// Extracts the `FileName` from a `FILE_OBJECT` structure. + /// Returns the kernel information string. /// - /// # Equivalent C pseudo-code + /// # Notes /// - /// ```c - /// UNICODE_STRING FileName = FileObject->FileName; - /// return FileName; - /// ``` + /// The kernel information string is cached after the first read. /// - /// # Notes + /// # Implementation Details /// - /// This operation might fail as the filename is allocated from paged pool. - pub fn file_object_to_filename( - &self, - vmi: &VmiCore, - registers: &::Registers, - file_object: Va, - ) -> Result { - let FILE_OBJECT = &self.offsets.common._FILE_OBJECT; + /// Corresponds to `NtBuildLab` symbol. + pub fn kernel_information_string_ex( + vmi: VmiState, + ) -> Result, VmiError> { + let NtBuildLabEx = match symbol!(vmi, NtBuildLabEx) { + Some(offset) => offset, + None => return Ok(None), + }; - // Note that filename is allocated from paged pool, - // so this read might fail. - self.read_unicode_string( - vmi, - registers.address_context(file_object + FILE_OBJECT.FileName.offset), - ) + Ok(Some( + this!(vmi) + .nt_build_lab_ex + .get_or_try_init(|| { + let kernel_image_base = Self::kernel_image_base(vmi)?; + vmi.read_string(kernel_image_base + NtBuildLabEx) + }) + .cloned()?, + )) } - /// Constructs the full path of a file from its `FILE_OBJECT`. - /// - /// This function first reads the `DeviceObject` field of the `FILE_OBJECT` - /// structure. Then it reads the `ObjectNameInfo` of the `DeviceObject` - /// and its directory. Finally, it concatenates the device directory - /// name, device name, and file name. + /// Checks if the given handle is a kernel handle. /// - /// # Equivalent C pseudo-code + /// A kernel handle is a handle with the highest bit set. + pub fn is_kernel_handle(vmi: VmiState, handle: u64) -> Result { + const KERNEL_HANDLE_MASK32: u64 = 0x8000_0000; + const KERNEL_HANDLE_MASK64: u64 = 0xffff_ffff_8000_0000; + + match vmi.registers().address_width() { + 4 => Ok(handle & KERNEL_HANDLE_MASK32 == KERNEL_HANDLE_MASK32), + 8 => Ok(handle & KERNEL_HANDLE_MASK64 == KERNEL_HANDLE_MASK64), + _ => panic!("Unsupported address width"), + } + } + + /// Returns the lowest user-mode address. /// - /// ```c - /// PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject; + /// This method returns a constant value (0x10000) representing the lowest + /// address that can be used by user-mode applications in Windows. /// - /// POBJECT_HEADER_NAME_INFO DeviceNameInfo = ObjectNameInfo(DeviceObject); - /// POBJECT_HEADER_NAME_INFO DeviceDirectoryNameInfo = DeviceNameInfo->Directory - /// ? ObjectNameInfo(DeviceNameInfo->Directory) - /// : NULL; + /// # Notes /// - /// if (DeviceDirectoryNameInfo->Name != NULL) { - /// FullPath += '\\' + DeviceDirectoryNameInfo->Name; - /// } + /// * Windows creates a `NO_ACCESS` VAD (Virtual Address Descriptor) for the first 64KB + /// of virtual memory. This means the VA range 0-0x10000 is off-limits for usage. + /// * This behavior is consistent across all Windows versions from XP through + /// recent Windows 11, and applies to x86, x64, and ARM64 architectures. + /// * Many Windows APIs leverage this fact to determine whether an input argument + /// is a pointer or not. Here are two notable examples: /// - /// if (DeviceNameInfo->Name != NULL) { - /// FullPath += '\\' + DeviceNameInfo->Name; - /// } + /// 1. The `FindResource()` function accepts an `lpName` parameter of type `LPCTSTR`, + /// which can be either: + /// - A pointer to a valid string + /// - A value created by `MAKEINTRESOURCE(ID)` /// - /// FullPath += FileObject->FileName; + /// This allows `FindResource()` to accept `WORD` values (unsigned shorts) with + /// a maximum value of 0xFFFF, distinguishing them from valid memory addresses. /// - /// return FullPath; - /// ``` + /// 2. The `AddAtom()` function similarly accepts an `lpString` parameter of type `LPCTSTR`. + /// This parameter can be: + /// - A pointer to a null-terminated string (max 255 bytes) + /// - An integer atom converted using the `MAKEINTATOM(ID)` macro /// - /// # Panics - /// Panics if the provided object is not a file object. - pub fn file_object_to_full_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - file_object: Va, - ) -> Result { - let file = match self.parse_file_object(vmi, registers, file_object)? { - WindowsObject::File(file) => file, - _ => panic!("Not a file object"), - }; - - let device = self.object_name(vmi, registers, file.device_object)?; - let directory = match &device { - Some(device) => self.object_name(vmi, registers, device.directory)?, - None => None, - }; - - let mut result = String::new(); - if let Some(directory) = directory { - result.push('\\'); - result.push_str(&directory.name); - } - - if let Some(device) = device { - result.push('\\'); - result.push_str(&device.name); - } - - result.push_str(&file.filename); - - Ok(result) + /// In both cases, the API can distinguish between valid pointers (which will be + /// above 0x10000) and integer values (which will be below 0x10000), allowing + /// for flexible parameter usage without ambiguity. + pub fn lowest_user_address(_vmi: VmiState) -> Result { + Ok(Va(0x10000)) } - /// Extracts the filename from a `CONTROL_AREA` structure. + /// Returns the highest user-mode address. /// - /// This function first reads the `FilePointer` field of the `CONTROL_AREA` - /// structure, then reads the `FileName` field of the `FILE_OBJECT` - /// structure pointed by the `FilePointer`. + /// This method reads the highest user-mode address from the Windows kernel. + /// The value is cached after the first read for performance. /// - /// # Equivalent C pseudo-code + /// # Notes /// - /// ```c - /// PFILE_OBJECT FileObject = ControlArea->FilePointer; - /// UNICODE_STRING FileName = FileObject->FileName; - /// return FileName; - /// ``` - pub fn control_area_to_filename( - &self, - vmi: &VmiCore, - registers: &::Registers, - control_area: Va, - ) -> Result { - let EX_FAST_REF = &self.offsets.common._EX_FAST_REF; - let CONTROL_AREA = &self.offsets.common._CONTROL_AREA; - - let file_pointer = vmi.read_va( - registers.address_context(control_area + CONTROL_AREA.FilePointer.offset), - registers.address_width(), - )?; - - // The file pointer is in fact an `_EX_FAST_REF` structure, - // where the low bits are used to store the reference count. - debug_assert_eq!(EX_FAST_REF.RefCnt.offset, 0); - debug_assert_eq!(EX_FAST_REF.RefCnt.bit_position, 0); - let file_pointer = file_pointer & !((1 << EX_FAST_REF.RefCnt.bit_length) - 1); - //let file_pointer = file_pointer & !0xf; + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `MmHighestUserAddress` symbol. + pub fn highest_user_address(vmi: VmiState) -> Result { + this!(vmi) + .highest_user_address + .get_or_try_init(|| { + let MmHighestUserAddress = + Self::kernel_image_base(vmi)? + symbol!(vmi, MmHighestUserAddress); - self.file_object_to_filename(vmi, registers, file_pointer) + vmi.read_va_native(MmHighestUserAddress) + }) + .copied() } - // endregion: File - - // region: Handle - - /// Checks if the given handle is a kernel handle. - pub fn is_kernel_handle( - &self, - _vmi: &VmiCore, - registers: &::Registers, - handle: u64, + /// Checks if a given address is a valid user-mode address. + /// + /// This method determines whether the provided address falls within + /// the range of valid user-mode addresses in Windows. + pub fn is_valid_user_address( + vmi: VmiState, + address: Va, ) -> Result { - const KERNEL_HANDLE_MASK32: u64 = 0x8000_0000; - const KERNEL_HANDLE_MASK64: u64 = 0xffff_ffff_8000_0000; + let lowest_user_address = Self::lowest_user_address(vmi)?; + let highest_user_address = Self::highest_user_address(vmi)?; - match registers.effective_address_width() { - 4 => Ok(handle & KERNEL_HANDLE_MASK32 == KERNEL_HANDLE_MASK32), - 8 => Ok(handle & KERNEL_HANDLE_MASK64 == KERNEL_HANDLE_MASK64), - _ => panic!("Unsupported address width"), - } + Ok(address >= lowest_user_address && address <= highest_user_address) } - /// Retrieves the handle table for a given process. - pub fn handle_table( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - let HANDLE_TABLE = &self.offsets.common._HANDLE_TABLE; - - let handle_table = vmi.read_va( - registers.address_context(process.0 + EPROCESS.ObjectTable.offset), - registers.address_width(), - )?; - - let table_code = vmi.read_address( - registers.address_context(handle_table + HANDLE_TABLE.TableCode.offset), - registers.address_width(), - )?; - - Ok(WindowsHandleTable { table_code }) + /// Returns the virtual address of the current Kernel Processor Control + /// Region (KPCR). + /// + /// The KPCR is a per-processor data structure in Windows that contains + /// critical information about the current processor state. This method + /// returns the virtual address of the KPCR for the current processor. + pub fn current_kpcr(vmi: VmiState) -> Va { + Driver::Architecture::current_kpcr(vmi) } - /// Looks up a specific handle table entry for a given process and handle. - pub fn handle_table_entry( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - handle: u64, - ) -> Result, VmiError> { - let mut process = process; - let mut handle = handle; - - if self.is_kernel_handle(vmi, registers, handle)? { - process = self.system_process(vmi, registers)?; - handle &= 0x7fff_ffff; + /// Returns information from an exception record at the specified address. + /// + /// This method reads and parses an `EXCEPTION_RECORD` structure from + /// memory, providing detailed information about an exception that has + /// occurred in the system. The returned [`WindowsExceptionRecord`] + /// contains data such as the exception code, flags, and related memory + /// addresses. + pub fn exception_record( + vmi: VmiState, + address: Va, + ) -> Result { + #[repr(C)] + #[derive(Debug, Copy, Clone, FromBytes, IntoBytes)] + #[allow(non_camel_case_types, non_snake_case)] + struct _EXCEPTION_RECORD { + ExceptionCode: u32, + ExceptionFlags: u32, + ExceptionRecord: u64, + ExceptionAddress: u64, + NumberParameters: u64, + ExceptionInformation: [u64; 15], } - let handle_table = self.handle_table(vmi, registers, process)?; - let entry_address = - self.handle_table_entry_lookup(vmi, registers, &handle_table, handle)?; - self.parse_handle_table_entry(vmi, registers, entry_address) + let record = vmi.read_struct::<_EXCEPTION_RECORD>(address)?; + + Ok(WindowsExceptionRecord { + code: record.ExceptionCode, + flags: record.ExceptionFlags, + record: record.ExceptionRecord.into(), + address: record.ExceptionAddress.into(), + information: record.ExceptionInformation + [..u64::min(record.NumberParameters, 15) as usize] + .to_vec(), + }) } - /// Performs a lookup in the handle table to find the address of a handle - /// table entry. + /// Returns the last status value for the current thread. /// - /// Implements the multi-level handle table lookup algorithm used by - /// Windows. Returns the virtual address of the handle table entry. - pub fn handle_table_entry_lookup( - &self, - vmi: &VmiCore, - registers: &::Registers, - handle_table: &WindowsHandleTable, - handle: u64, - ) -> Result { - const SIZEOF_POINTER: u64 = 8; - const SIZEOF_HANDLE_TABLE_ENTRY: u64 = 16; - - const LOWLEVEL_COUNT: u64 = 256; // (TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY)) - const MIDLEVEL_COUNT: u64 = 512; // (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY)) - - const LEVEL_CODE_MASK: u64 = 3; - const HANDLE_VALUE_INC: u64 = 4; - - let level = handle_table.table_code & LEVEL_CODE_MASK; - let table = Va(handle_table.table_code - level); - - // - // The 2 least significant bits of a handle are available to the - // application and are ignored by the system. - // - - let mut index = handle & !0b11; - - match level { - 0 => Ok(table + index * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC)), - - 1 => { - let table2 = table; - let i = index % (LOWLEVEL_COUNT * HANDLE_VALUE_INC); - - index -= i; - let j = index / (LOWLEVEL_COUNT * HANDLE_VALUE_INC); - - let table1 = vmi.read_va( - registers.address_context(table2 + j * SIZEOF_POINTER), - registers.address_width(), - )?; - - Ok(table1 + i * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC)) - } - - 2 => { - let table3 = table; - let i = index % (LOWLEVEL_COUNT * HANDLE_VALUE_INC); - - index -= i; - let mut k = index / (LOWLEVEL_COUNT * HANDLE_VALUE_INC); - - let j = k % MIDLEVEL_COUNT; - k -= j; - k /= MIDLEVEL_COUNT; - - let table2 = vmi.read_va( - registers.address_context(table3 + k * SIZEOF_POINTER), - registers.address_width(), - )?; - let table1 = vmi.read_va( - registers.address_context(table2 + j * SIZEOF_POINTER), - registers.address_width(), - )?; + /// In Windows, the last status value is typically used to store error codes + /// or success indicators from system calls. This method reads this value + /// from the Thread Environment Block (TEB) of the current thread, providing + /// insight into the outcome of recent operations performed by the thread. + /// + /// Returns `None` if the TEB is not available. + /// + /// # Notes + /// + /// `LastStatusValue` is a `NTSTATUS` value, whereas `LastError` is a Win32 + /// error code. The two values are related but not identical. You can obtain + /// the Win32 error code by calling + /// [`VmiOs::last_error`](crate::VmiOs::last_error). + /// + /// # Implementation Details + /// + /// Corresponds to `NtCurrentTeb()->LastStatusValue`. + pub fn last_status(vmi: VmiState) -> Result, VmiError> { + let KTHREAD = offset!(vmi, _KTHREAD); + let TEB = offset!(vmi, _TEB); - Ok(table1 + i * (SIZEOF_HANDLE_TABLE_ENTRY / HANDLE_VALUE_INC)) - } + let current_thread = Self::current_thread(vmi)?.object()?; + let teb = vmi.read_va_native(current_thread.0 + KTHREAD.Teb.offset())?; - _ => unreachable!(), + if teb.is_null() { + return Ok(None); } - } - - /// Converts a handle to the virtual address of the corresponding object. - /// - /// Uses the handle table entry lookup to find the object address for a - /// given handle. - pub fn handle_to_object_address( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - handle: u64, - ) -> Result, VmiError> { - Ok(self - .handle_table_entry(vmi, registers, process, handle)? - .map(|entry| entry.object)) - } - /// Retrieves the WindowsObject corresponding to a given handle in a - /// process. - pub fn handle_to_object( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - handle: u64, - ) -> Result, VmiError> { - match self.handle_to_object_address(vmi, registers, process, handle)? { - Some(object) => self.object_from_address(vmi, registers, object), - None => Ok(None), - } + let result = vmi.read_u32(teb + TEB.LastStatusValue.offset())?; + Ok(Some(result)) } - /// Parses a Windows object from its memory address. + /// Returns the virtual address of the Page Frame Number (PFN) database. /// - /// Determines the object type and calls the appropriate parsing method. - /// Currently supports File and Section object types. - pub fn object_from_address( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - ) -> Result, VmiError> { - match self.object_type(vmi, registers, object)? { - Some(WindowsObjectType::File) => { - Ok(Some(self.parse_file_object(vmi, registers, object)?)) - } - Some(WindowsObjectType::Section) => self.parse_section_object(vmi, registers, object), - _ => Ok(None), - } - } - - /// Parses a `FILE_OBJECT` structure. + /// The PFN database is a critical data structure in Windows memory management, + /// containing information about each physical page in the system. /// - /// Extracts the device object and filename from the `FILE_OBJECT`. - /// Returns a [`WindowsObject::File`] variant. - fn parse_file_object( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - ) -> Result { - let FILE_OBJECT = &self.offsets.common._FILE_OBJECT; - - let device_object = vmi.read_va( - registers.address_context(object + FILE_OBJECT.DeviceObject.offset), - registers.address_width(), - )?; - - let filename = self.file_object_to_filename(vmi, registers, object)?; - Ok(WindowsObject::File(WindowsFileObject { - device_object, - filename, - })) - } - - /// Parses a Windows section object. + /// # Notes /// - /// Delegates to version-specific parsing methods based on the available - /// offsets. Currently supports `SECTION_OBJECT` and `SECTION` structures. - /// Returns a [`WindowsObject::Section`] variant. - fn parse_section_object( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - ) -> Result, VmiError> { - match &self.offsets.ext { - Some(OffsetsExt::V1(offsets)) => Ok(Some( - self.parse_section_object_v1(vmi, registers, object, offsets)?, - )), - Some(OffsetsExt::V2(offsets)) => Ok(Some( - self.parse_section_object_v2(vmi, registers, object, offsets)?, - )), - None => panic!("OffsetsExt not set"), - } - } + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `MmPfnDatabase` symbol. + fn pfn_database(vmi: VmiState) -> Result { + let MmPfnDatabase = symbol!(vmi, MmPfnDatabase); - fn parse_section_object_v1( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - offsets: &v1::Offsets, - ) -> Result { - let SECTION_OBJECT = &offsets._SECTION_OBJECT; - let SEGMENT_OBJECT = &offsets._SEGMENT_OBJECT; - let MMSECTION_FLAGS = &self.offsets.common._MMSECTION_FLAGS; - - let section = StructReader::new( - vmi, - registers.address_context(object), - SECTION_OBJECT.effective_len(), - )?; - let starting_vpn = section.read(SECTION_OBJECT.StartingVa)?; - let ending_vpn = section.read(SECTION_OBJECT.EndingVa)?; - let segment = section.read(SECTION_OBJECT.Segment)?; - - let segment = StructReader::new( - vmi, - registers.address_context(segment.into()), - SEGMENT_OBJECT.effective_len(), - )?; - let size = segment.read(SEGMENT_OBJECT.SizeOfSegment)?; - let flags = segment.read(SEGMENT_OBJECT.MmSectionFlags)?; - let flags = vmi.read_u32(registers.address_context(flags.into()))? as u64; - - let file = MMSECTION_FLAGS.File.value_from(flags) != 0; - let _image = MMSECTION_FLAGS.Image.value_from(flags) != 0; - - let kind = if file { - let control_area = vmi.read_va( - registers.address_context(object + SEGMENT_OBJECT.ControlArea.offset), - registers.address_width(), - )?; - - let path = self.control_area_to_filename(vmi, registers, control_area); - - OsRegionKind::Mapped(OsMapped { - path: path.map(Some), + this!(vmi) + .mm_pfn_database + .get_or_try_init(|| { + let kernel_image_base = Self::kernel_image_base(vmi)?; + vmi.read_va_native(kernel_image_base + MmPfnDatabase) }) - } - else { - OsRegionKind::Private - }; - - Ok(WindowsObject::Section(WindowsSectionObject { - region: OsRegion { - start: Va(starting_vpn << 12), - end: Va((ending_vpn + 1) << 12), - protection: MemoryAccess::default(), - kind, - }, - size, - })) + .copied() } - fn parse_section_object_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - offsets: &v2::Offsets, - ) -> Result { - let SECTION = &offsets._SECTION; - let MMSECTION_FLAGS = &self.offsets.common._MMSECTION_FLAGS; + fn modify_pfn_reference_count( + vmi: VmiState, + pfn: Gfn, + increment: i16, + ) -> Result, VmiError> { + let MMPFN = offset!(vmi, _MMPFN); - let section = StructReader::new( - vmi, - registers.address_context(object), - SECTION.effective_len(), - )?; - let starting_vpn = section.read(SECTION.StartingVpn)?; - let ending_vpn = section.read(SECTION.EndingVpn)?; - let size = section.read(SECTION.SizeOfSection)?; - let flags = section.read(SECTION.Flags)?; + // const ZeroedPageList: u16 = 0; + // const FreePageList: u16 = 1; + const StandbyPageList: u16 = 2; //this list and before make up available pages. + const ModifiedPageList: u16 = 3; + const ModifiedNoWritePageList: u16 = 4; + // const BadPageList: u16 = 5; + const ActiveAndValid: u16 = 6; + // const TransitionPage: u16 = 7; - let file = MMSECTION_FLAGS.File.value_from(flags) != 0; - let _image = MMSECTION_FLAGS.Image.value_from(flags) != 0; + let pfn = Self::pfn_database(vmi)? + u64::from(pfn) * MMPFN.len() as u64; // - // We have to distinguish between FileObject and ControlArea. - // Here's an excerpt from _SECTION: + // In the _MMPFN structure, the fields are like this: // + // ```c + // struct _MMPFN { + // ... // union { - // union { - // PCONTROL_AREA ControlArea; - // PFILE_OBJECT FileObject; + // USHORT ReferenceCount; // struct { - // ULONG_PTR RemoteImageFileObject : 1; - // ULONG_PTR RemoteDataFileObject : 1; - // }; - // }; - // }; - // - // Based on information from Geoff Chappell's website, we can determine whether - // ControlArea is in fact FileObject by checking the lowest 2 bits of the - // pointer. + // UCHAR PageLocation : 3; + // ... + // } e1; + // ... + // } u3; + // }; + // ``` // - // ref: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/mi/section.htm + // On the systems tested (Win7 - Win11), the `PageLocation` is right + // after `ReferenceCount`. We can read the value of both fields at once. // - let kind = if file { - let control_area = vmi.read_va( - registers.address_context(object + SECTION.ControlArea.offset), - registers.address_width(), - )?; - - let path = if u64::from(control_area) & 0x3 != 0 { - let file_object = control_area; - self.file_object_to_filename(vmi, registers, file_object) - } - else { - self.control_area_to_filename(vmi, registers, control_area) - }; - - OsRegionKind::Mapped(OsMapped { - path: path.map(Some), - }) - } - else { - OsRegionKind::Private - }; - - Ok(WindowsObject::Section(WindowsSectionObject { - region: OsRegion { - start: Va(starting_vpn << 12), - end: Va((ending_vpn + 1) << 12), - protection: MemoryAccess::default(), - kind, - }, - size, - })) - } - - fn parse_handle_table_entry( - &self, - vmi: &VmiCore, - registers: &::Registers, - entry: Va, - ) -> Result, VmiError> { - match &self.offsets.ext { - Some(OffsetsExt::V1(offsets)) => { - self.parse_handle_table_entry_v1(vmi, registers, entry, offsets) - } - Some(OffsetsExt::V2(offsets)) => { - self.parse_handle_table_entry_v2(vmi, registers, entry, offsets) - } - None => panic!("OffsetsExt not set"), - } - } - - fn parse_handle_table_entry_v1( - &self, - vmi: &VmiCore, - registers: &::Registers, - entry: Va, - offsets: &v1::Offsets, - ) -> Result, VmiError> { - const OBJ_PROTECT_CLOSE: u64 = 0x00000001; - const OBJ_INHERIT: u64 = 0x00000002; - const OBJ_AUDIT_OBJECT_CLOSE: u64 = 0x00000004; - const OBJ_HANDLE_ATTRIBUTES: u64 = OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE; - - let HANDLE_TABLE_ENTRY = &offsets._HANDLE_TABLE_ENTRY; - let OBJECT_HEADER = &self.offsets.common._OBJECT_HEADER; - - let handle_table_entry = StructReader::new( - vmi, - registers.address_context(entry), - HANDLE_TABLE_ENTRY.effective_len(), - )?; - let object = handle_table_entry.read(HANDLE_TABLE_ENTRY.Object)?; - let attributes = handle_table_entry.read(HANDLE_TABLE_ENTRY.ObAttributes)?; - let granted_access = handle_table_entry.read(HANDLE_TABLE_ENTRY.GrantedAccess)? as u32; + debug_assert_eq!(MMPFN.ReferenceCount.size(), 2); + debug_assert_eq!( + MMPFN.ReferenceCount.offset() + MMPFN.ReferenceCount.size(), + MMPFN.PageLocation.offset() + ); + debug_assert_eq!(MMPFN.PageLocation.bit_position(), 0); + debug_assert_eq!(MMPFN.PageLocation.bit_length(), 3); - let object = Va(object & !OBJ_HANDLE_ATTRIBUTES); - let object = object + OBJECT_HEADER.Body.offset; + let pfn_value = vmi.read_u32(pfn + MMPFN.ReferenceCount.offset())?; + let flags = (pfn_value >> 16) as u16; + let ref_count = (pfn_value & 0xFFFF) as u16; - let attributes = (attributes & OBJ_HANDLE_ATTRIBUTES) as u32; + let page_location = flags & 7; - Ok(Some(WindowsHandleTableEntry { - object, - attributes, - granted_access, - })) - } + tracing::debug!( + %pfn, + ref_count, + flags = %Hex(flags), + page_location, + increment, + "Modifying PFN reference count" + ); - fn parse_handle_table_entry_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - entry: Va, - offsets: &v2::Offsets, - ) -> Result, VmiError> { - let HANDLE_TABLE_ENTRY = &offsets._HANDLE_TABLE_ENTRY; - let OBJECT_HEADER = &self.offsets.common._OBJECT_HEADER; + // + // Make sure the page is good (when coming from hibernate/standby pages + // can be in modified state). + // - #[repr(C)] - #[derive(Debug, Copy, Clone, FromBytes, IntoBytes)] - #[allow(non_camel_case_types, non_snake_case)] - struct _HANDLE_TABLE_ENTRY { - LowValue: u64, - HighValue: u64, + if !matches!( + page_location, + StandbyPageList | ModifiedPageList | ModifiedNoWritePageList | ActiveAndValid + ) { + tracing::warn!( + %pfn, + ref_count, + flags = %Hex(flags), + page_location, + increment, + "Page is not active and valid" + ); + return Ok(None); } - // Fetch the handle table entry - let handle_table_entry = - vmi.read_struct::<_HANDLE_TABLE_ENTRY>(registers.address_context(entry))?; - - // Parse the handle table entry - let object_pointer_bits = HANDLE_TABLE_ENTRY - .ObjectPointerBits - .value_from(handle_table_entry.LowValue); - - let attributes = HANDLE_TABLE_ENTRY - .Attributes - .value_from(handle_table_entry.LowValue) as u32; + if ref_count == 0 { + tracing::warn!( + %pfn, + ref_count, + flags = %Hex(flags), + page_location, + increment, + "Page is not initialized" + ); + return Ok(None); + } - let granted_access = HANDLE_TABLE_ENTRY - .GrantedAccessBits - .value_from(handle_table_entry.HighValue) as u32; + let new_ref_count = match ref_count.checked_add_signed(increment) { + Some(new_ref_count) => new_ref_count, + None => { + tracing::warn!( + %pfn, + ref_count, + flags = %Hex(flags), + page_location, + increment, + "Page is at maximum reference count" + ); + return Ok(None); + } + }; - let object = Va(0xffff_0000_0000_0000 | object_pointer_bits << 4); - let object = object + OBJECT_HEADER.Body.offset; + vmi.write_u16(pfn + MMPFN.ReferenceCount.offset(), new_ref_count)?; - Ok(Some(WindowsHandleTableEntry { - object, - attributes, - granted_access, - })) + Ok(Some(new_ref_count)) } - // endregion: Handle - - // region: Kernel - - /// Locates the Windows kernel in memory based on the CPU registers. - /// This function is architecture-specific. + /// Increments the reference count of a Page Frame Number (PFN). /// - /// On AMD64, the kernel is located by taking the `MSR_LSTAR` value and - /// reading the virtual memory page by page backwards until the `MZ` header - /// is found. - pub fn find_kernel( - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - Driver::Architecture::find_kernel(vmi, registers) - } - - /// Retrieves the kernel information string. - /// - /// # Implementation Details - /// - /// The kernel information string is located by reading the `NtBuildLabEx` - /// symbol from the kernel image. - pub fn kernel_information_string_ex( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result, VmiError> { - let NtBuildLabEx = match self.symbols.NtBuildLabEx { - Some(offset) => offset, - None => return Ok(None), - }; - - if let Some(nt_build_lab_ex) = self.nt_build_lab_ex.borrow().as_ref() { - return Ok(Some(nt_build_lab_ex.clone())); - } - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let nt_build_lab_ex = - vmi.read_string(registers.address_context(kernel_image_base + NtBuildLabEx))?; - *self.nt_build_lab_ex.borrow_mut() = Some(nt_build_lab_ex.clone()); - - Ok(Some(nt_build_lab_ex)) - } - - /// Retrieves information about a kernel module from a pointer to - /// `KLDR_DATA_TABLE_ENTRY` - pub fn kernel_module( - &self, - vmi: &VmiCore, - registers: &::Registers, - addr: Va, // _KLDR_DATA_TABLE_ENTRY* - ) -> Result { - let KLDR_DATA_TABLE_ENTRY = &self.offsets.common._KLDR_DATA_TABLE_ENTRY; - - let base_address = vmi.read_va( - registers.address_context(addr + KLDR_DATA_TABLE_ENTRY.DllBase.offset), - registers.address_width(), - )?; - - let size = vmi - .read_u32(registers.address_context(addr + KLDR_DATA_TABLE_ENTRY.SizeOfImage.offset))? - as u64; - - let name = self.read_unicode_string( - vmi, - registers.address_context(addr + KLDR_DATA_TABLE_ENTRY.BaseDllName.offset), - )?; - - Ok(OsModule { - base_address, - size, - name, - }) - } - - // endregion: Kernel - - // region: Memory - - /// Retrieves information about a Virtual Address Descriptor (VAD) for a - /// given address. - /// - /// This method extracts details such as the starting and ending virtual - /// page numbers, VAD type, memory protection, and other flags - /// associated with the specified VAD. - pub fn vad( - &self, - vmi: &VmiCore, - registers: &::Registers, - vad: Va, - ) -> Result { - let MMVAD_FLAGS = &self.offsets.common._MMVAD_FLAGS; - let MMVAD_SHORT = &self.offsets.common._MMVAD_SHORT; - - let mmvad = StructReader::new( - vmi, - registers.address_context(vad), - MMVAD_SHORT.effective_len(), - )?; - let starting_vpn_low = mmvad.read(MMVAD_SHORT.StartingVpn)?; - let ending_vpn_low = mmvad.read(MMVAD_SHORT.EndingVpn)?; - let starting_vpn_high = match MMVAD_SHORT.StartingVpnHigh { - Some(StartingVpnHigh) => mmvad.read(StartingVpnHigh)?, - None => 0, - }; - let ending_vpn_high = match MMVAD_SHORT.EndingVpnHigh { - Some(EndingVpnHigh) => mmvad.read(EndingVpnHigh)?, - None => 0, - }; - - let starting_vpn = (starting_vpn_high << 32) | starting_vpn_low; - let ending_vpn = (ending_vpn_high << 32) | ending_vpn_low; - - let vad_flags = mmvad.read(MMVAD_SHORT.VadFlags)?; - let vad_type = MMVAD_FLAGS.VadType.value_from(vad_flags) as u8; - let protection = MMVAD_FLAGS.Protection.value_from(vad_flags) as u8; - let private_memory = MMVAD_FLAGS.PrivateMemory.value_from(vad_flags) != 0; - - // If `MMVAD_FLAGS.MemCommit` is present (Windows 7), then we fetch the - // value from it. Otherwise, we load the `VadFlags1` field from the VAD - // and fetch it from there. - let mem_commit = match MMVAD_FLAGS.MemCommit { - // `MemCommit` is present in `MMVAD_FLAGS` - Some(MemCommit) => MemCommit.value_from(vad_flags) != 0, - - None => match (&self.offsets.ext, MMVAD_SHORT.VadFlags1) { - // `MemCommit` is present in `MMVAD_FLAGS1` - (Some(OffsetsExt::V2(offsets)), Some(VadFlags1)) => { - let MMVAD_FLAGS1 = &offsets._MMVAD_FLAGS1; - let vad_flags1 = mmvad.read(VadFlags1)?; - MMVAD_FLAGS1.MemCommit.value_from(vad_flags1) != 0 - } - _ => { - panic!("Failed to read MemCommit from VAD"); - } - }, - }; - - let left_child = Va(mmvad.read(MMVAD_SHORT.Left)?); - let right_child = Va(mmvad.read(MMVAD_SHORT.Right)?); - - Ok(WindowsVad { - starting_vpn, - ending_vpn, - vad_type, - protection, - private_memory, - mem_commit, - left_child, - right_child, - }) - } - - /// Locates the `VadRoot` for a given process. Returns the address of the - /// root node. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// // For Windows 7: - /// return Process->VadRoot->BalancedRoot; - /// - /// // For Windows 8.1 and later: - /// return Process->VadRoot->Root; - /// ``` - pub fn vad_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - match &self.offsets.ext { - Some(OffsetsExt::V1(offsets)) => self.vad_root_v1(vmi, registers, process, offsets), - Some(OffsetsExt::V2(offsets)) => self.vad_root_v2(vmi, registers, process, offsets), - None => panic!("OffsetsExt not set"), - } - } - - fn vad_root_v1( - &self, - _vmi: &VmiCore, - _registers: &::Registers, - process: ProcessObject, - offsets: &v1::Offsets, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - let MM_AVL_TABLE = &offsets._MM_AVL_TABLE; - - // The `_MM_AVL_TABLE::BalancedRoot` field is of `_MMADDRESS_NODE` type, - // which represents the root. - let vad_root = process.0 + EPROCESS.VadRoot.offset + MM_AVL_TABLE.BalancedRoot.offset; - - Ok(vad_root) - } - - fn vad_root_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - offsets: &v2::Offsets, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - let RTL_AVL_TREE = &offsets._RTL_AVL_TREE; - - // The `RTL_AVL_TREE::Root` field is of pointer type (`_RTL_BALANCED_NODE*`), - // thus we need to dereference it to get the actual node. - let vad_root = vmi.read_va( - registers - .address_context(process.0 + EPROCESS.VadRoot.offset + RTL_AVL_TREE.Root.offset), - registers.address_width(), - )?; - - Ok(vad_root) - } - - /// Retrieves the `VadHint` for a given process. Returns the address of the - /// hint node in the VAD tree. - /// - /// The VAD hint is an optimization used by Windows to speed up VAD lookups. - /// This method returns the address of the hint node in the VAD tree. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// // For Windows 7: - /// return Process->VadRoot->NodeHint; - /// - /// // For Windows 8.1 and later: - /// return Process->VadRoot->Hint; - /// ``` - pub fn vad_hint( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - match &self.offsets.ext { - Some(OffsetsExt::V1(offsets)) => self.vad_hint_v1(vmi, registers, process, offsets), - Some(OffsetsExt::V2(offsets)) => self.vad_hint_v2(vmi, registers, process, offsets), - None => panic!("OffsetsExt not set"), - } - } - - fn vad_hint_v1( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - offsets: &v1::Offsets, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - let MM_AVL_TABLE = &offsets._MM_AVL_TABLE; - - let vad_hint = vmi.read_va( - registers.address_context( - process.0 + EPROCESS.VadRoot.offset + MM_AVL_TABLE.NodeHint.offset, - ), - registers.address_width(), - )?; - - Ok(vad_hint) - } - - fn vad_hint_v2( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - _offsets: &v2::Offsets, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - - let VadHint = EPROCESS - .VadHint - .expect("VadHint is not present in common offsets"); - - let vad_hint = vmi.read_va( - registers.address_context(process.0 + VadHint.offset), - registers.address_width(), - )?; - - Ok(vad_hint) - } - - /// Converts a VAD (Virtual Address Descriptor) to an [`OsRegion`] structure. - /// - /// This method extracts information from a VAD and creates an `OsRegion` - /// object, which includes details about the memory region's address range, - /// protection, and mapping type. - pub fn vad_to_region( - &self, - vmi: &VmiCore, - registers: &::Registers, - vad: Va, - ) -> Result { - let MMVAD = &self.offsets.common._MMVAD; - let SUBSECTION = &self.offsets.common._SUBSECTION; - - let mmvad = self.vad(vmi, registers, vad)?; - let start = Va(mmvad.starting_vpn << 12); - let end = Va((mmvad.ending_vpn + 1) << 12); - - const MM_ZERO_ACCESS: u8 = 0; // this value is not used. - const MM_READONLY: u8 = 1; - const MM_EXECUTE: u8 = 2; - const MM_EXECUTE_READ: u8 = 3; - const MM_READWRITE: u8 = 4; // bit 2 is set if this is writable. - const MM_WRITECOPY: u8 = 5; - const MM_EXECUTE_READWRITE: u8 = 6; - const MM_EXECUTE_WRITECOPY: u8 = 7; - - let protection = match mmvad.protection { - MM_ZERO_ACCESS => MemoryAccess::default(), - MM_READONLY => MemoryAccess::R, - MM_EXECUTE => MemoryAccess::X, - MM_EXECUTE_READ => MemoryAccess::RX, - MM_READWRITE => MemoryAccess::RW, - MM_WRITECOPY => MemoryAccess::RW, // REVIEW: is this correct? - MM_EXECUTE_READWRITE => MemoryAccess::RWX, - MM_EXECUTE_WRITECOPY => MemoryAccess::RWX, // REVIEW: is this correct? - _ => MemoryAccess::default(), - }; - - const VadImageMap: u8 = 2; - - if mmvad.vad_type != VadImageMap { - return Ok(OsRegion { - start, - end, - protection, - kind: OsRegionKind::Private, - }); - } - - let subsection = vmi.read_va( - registers.address_context(vad + MMVAD.Subsection.offset), - registers.address_width(), - )?; - - let control_area = vmi.read_va( - registers.address_context(subsection + SUBSECTION.ControlArea.offset), - registers.address_width(), - )?; - - // Note that filename is allocated from paged pool, - // so this read might fail. - let path = self.control_area_to_filename(vmi, registers, control_area); - - Ok(OsRegion { - start, - end, - protection, - kind: OsRegionKind::Mapped(OsMapped { - path: path.map(Some), - }), - }) - } - - /// Retrieves all memory regions associated with a process's VAD tree. - /// - /// This method traverses the entire VAD tree of a process and converts - /// each VAD into an [`OsRegion`], providing a comprehensive view of the - /// process's virtual address space. - pub fn vad_root_to_regions( - &self, - vmi: &VmiCore, - registers: &::Registers, - vad_root: Va, - ) -> Result, VmiError> { - let mut result = Vec::new(); - - self.enumerate_tree(vmi, registers, vad_root, |vad| { - match self.vad_to_region(vmi, registers, vad) { - Ok(region) => result.push(region), - Err(err) => tracing::warn!(?err, ?vad, "Failed to convert VAD to region"), - } - - true - })?; - - Ok(result) - } - - /// Locates the VAD that encompasses a specific virtual address in a process. - /// - /// This method efficiently searches the VAD tree to find the VAD node that - /// corresponds to the given virtual address within the process's address - /// space. Its functionality is similar to the Windows kernel's internal - /// `MiLocateAddress()` function. - /// - /// Returns virtual address of the matching VAD if found, or `None` if the - /// address is not within any VAD. - /// - pub fn find_process_vad( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError> { - let mut vad_va = self.vad_hint(vmi, registers, process)?; - - if vad_va.is_null() { - return Ok(None); - } - - let vpn = address.0 >> 12; - - let vad = self.vad(vmi, registers, vad_va)?; - if vpn >= vad.starting_vpn && vpn <= vad.ending_vpn { - return Ok(Some(vad_va)); - } - - vad_va = self.vad_root(vmi, registers, process)?; - - while !vad_va.is_null() { - let vad = self.vad(vmi, registers, vad_va)?; - - if vpn < vad.starting_vpn { - vad_va = vad.left_child; - } - else if vpn > vad.ending_vpn { - vad_va = vad.right_child; - } - else { - return Ok(Some(vad_va)); - } - } - - Ok(None) - } - - /// Retrieves the virtual address of the Page Frame Number (PFN) database. - /// - /// The PFN database is a critical data structure in Windows memory management, - /// containing information about each physical page in the system. - /// - /// # Implementation Details - /// - /// The PFN database is located by reading the `MmPfnDatabase` symbol from - /// the kernel image. - fn pfn_database( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let MmPfnDatabase = self.symbols.MmPfnDatabase; - - if let Some(mm_pfn_database) = self.mm_pfn_database.borrow().as_ref() { - return Ok(*mm_pfn_database); - } - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let mm_pfn_database = vmi.read_va( - registers.address_context(kernel_image_base + MmPfnDatabase), - registers.address_width(), - )?; - *self.mm_pfn_database.borrow_mut() = Some(mm_pfn_database); - Ok(mm_pfn_database) - } - - fn modify_pfn_reference_count( - &self, - vmi: &VmiCore, - registers: &::Registers, - pfn: Gfn, - increment: i16, - ) -> Result, VmiError> { - let MMPFN = &self.offsets.common._MMPFN; - - // const ZeroedPageList: u16 = 0; - // const FreePageList: u16 = 1; - const StandbyPageList: u16 = 2; //this list and before make up available pages. - const ModifiedPageList: u16 = 3; - const ModifiedNoWritePageList: u16 = 4; - // const BadPageList: u16 = 5; - const ActiveAndValid: u16 = 6; - // const TransitionPage: u16 = 7; - - let pfn = self.pfn_database(vmi, registers)? + u64::from(pfn) * MMPFN.len() as u64; - - // - // In the _MMPFN structure, the fields are like this: - // - // ```c - // struct _MMPFN { - // ... - // union { - // USHORT ReferenceCount; - // struct { - // UCHAR PageLocation : 3; - // ... - // } e1; - // ... - // } u3; - // }; - // ``` - // - // On the systems tested (Win7 - Win11), the `PageLocation` is right - // after `ReferenceCount`. We can read the value of both fields at once. - // - - debug_assert_eq!(MMPFN.ReferenceCount.size, 2); - debug_assert_eq!( - MMPFN.ReferenceCount.offset + MMPFN.ReferenceCount.size, - MMPFN.PageLocation.offset - ); - debug_assert_eq!(MMPFN.PageLocation.bit_position, 0); - debug_assert_eq!(MMPFN.PageLocation.bit_length, 3); - - let pfn_value = - vmi.read_u32(registers.address_context(pfn + MMPFN.ReferenceCount.offset))?; - let flags = (pfn_value >> 16) as u16; - let ref_count = (pfn_value & 0xFFFF) as u16; - - let page_location = flags & 7; - - tracing::debug!( - %pfn, - ref_count, - flags = %Hex(flags), - page_location, - increment, - "Modifying PFN reference count" - ); - - // - // Make sure the page is good (when coming from hibernate/standby pages - // can be in modified state). - // - - if !matches!( - page_location, - StandbyPageList | ModifiedPageList | ModifiedNoWritePageList | ActiveAndValid - ) { - tracing::warn!( - %pfn, - ref_count, - flags = %Hex(flags), - page_location, - increment, - "Page is not active and valid" - ); - return Ok(None); - } - - if ref_count == 0 { - tracing::warn!( - %pfn, - ref_count, - flags = %Hex(flags), - page_location, - increment, - "Page is not initialized" - ); - return Ok(None); - } - - let new_ref_count = match ref_count.checked_add_signed(increment) { - Some(new_ref_count) => new_ref_count, - None => { - tracing::warn!( - %pfn, - ref_count, - flags = %Hex(flags), - page_location, - increment, - "Page is at maximum reference count" - ); - return Ok(None); - } - }; - - vmi.write_u16( - registers.address_context(pfn + MMPFN.ReferenceCount.offset), - new_ref_count, - )?; - - Ok(Some(new_ref_count)) - } - - /// Increments the reference count of a Page Frame Number (PFN). - /// - /// This method is used to "lock" a physical page by increasing its - /// reference count, preventing it from being paged out or reallocated. - /// - /// Returns the new reference count if successful, or `None` if the - /// operation failed (e.g., if the page is not in a valid state). + /// This method is used to "lock" a physical page by increasing its + /// reference count, preventing it from being paged out or reallocated. + /// + /// Returns the new reference count if successful, or `None` if the + /// operation failed (e.g., if the page is not in a valid state). /// /// # Implementation Details /// @@ -1968,18 +720,13 @@ where /// # Driver: VmiDriver, /// # { /// let _pause_guard = vmi.pause_guard()?; - /// os.lock_pfn(vmi, registers, pfn)?; + /// os.lock_pfn(pfn)?; /// // The VM will automatically resume when `_guard` goes out of scope /// # Ok(()) /// # } /// ``` - pub fn lock_pfn( - &self, - vmi: &VmiCore, - registers: &::Registers, - pfn: Gfn, - ) -> Result, VmiError> { - self.modify_pfn_reference_count(vmi, registers, pfn, 1) + pub fn lock_pfn(vmi: VmiState, pfn: Gfn) -> Result, VmiError> { + Self::modify_pfn_reference_count(vmi, pfn, 1) } /// Decrements the reference count of a Page Frame Number (PFN). @@ -2023,941 +770,145 @@ where /// # Driver: VmiDriver, /// # { /// let _pause_guard = vmi.pause_guard()?; - /// os.unlock_pfn(vmi, registers, pfn)?; + /// os.unlock_pfn(pfn)?; /// // The VM will automatically resume when `_guard` goes out of scope /// # Ok(()) /// # } /// ``` - pub fn unlock_pfn( - &self, - vmi: &VmiCore, - registers: &::Registers, - pfn: Gfn, - ) -> Result, VmiError> { - self.modify_pfn_reference_count(vmi, registers, pfn, -1) + pub fn unlock_pfn(vmi: VmiState, pfn: Gfn) -> Result, VmiError> { + Self::modify_pfn_reference_count(vmi, pfn, -1) } - // endregion: Memory - - // region: Misc - - /// Retrieves the virtual address of the current Kernel Processor Control - /// Region (KPCR). - /// - /// The KPCR is a per-processor data structure in Windows that contains - /// critical information about the current processor state. This method - /// returns the virtual address of the KPCR for the current processor. - pub fn current_kpcr( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Va { - Driver::Architecture::current_kpcr(self, vmi, registers) - } - - /// Extracts information from an exception record at the specified address. - /// - /// This method reads and parses an `EXCEPTION_RECORD` structure from - /// memory, providing detailed information about an exception that has - /// occurred in the system. The returned [`WindowsExceptionRecord`] - /// contains data such as the exception code, flags, and related memory - /// addresses. - pub fn exception_record( - &self, - vmi: &VmiCore, - registers: &::Registers, - address: Va, - ) -> Result { - #[repr(C)] - #[derive(Debug, Copy, Clone, FromBytes, IntoBytes)] - #[allow(non_camel_case_types, non_snake_case)] - struct _EXCEPTION_RECORD { - ExceptionCode: u32, - ExceptionFlags: u32, - ExceptionRecord: u64, - ExceptionAddress: u64, - NumberParameters: u64, - ExceptionInformation: [u64; 15], - } - - let record = vmi.read_struct::<_EXCEPTION_RECORD>(registers.address_context(address))?; - - Ok(WindowsExceptionRecord { - code: record.ExceptionCode, - flags: record.ExceptionFlags, - record: record.ExceptionRecord.into(), - address: record.ExceptionAddress.into(), - information: record.ExceptionInformation - [..u64::min(record.NumberParameters, 15) as usize] - .to_vec(), - }) - } - - /// Retrieves the last status value for the current thread. - /// - /// In Windows, the last status value is typically used to store error codes - /// or success indicators from system calls. This method reads this value - /// from the Thread Environment Block (TEB) of the current thread, providing - /// insight into the outcome of recent operations performed by the thread. - /// - /// Returns `None` if the TEB is not available. - /// - /// # Notes - /// - /// `LastStatusValue` is a `NTSTATUS` value, whereas `LastError` is a Win32 - /// error code. The two values are related but not identical. You can obtain - /// the Win32 error code by calling - /// [`VmiOs::last_error`](crate::VmiOs::last_error). - /// - /// # Implementation Details - /// - /// ```c - /// return NtCurrentTeb()->LastStatusValue; - /// ``` - pub fn last_status( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - let KTHREAD = &self.offsets.common._KTHREAD; - let TEB = &self.offsets.common._TEB; - - let current_thread = self.current_thread(vmi, registers)?; - let teb = vmi.read_va( - registers.address_context(current_thread.0 + KTHREAD.Teb.offset), - registers.address_width(), - )?; - - if teb.is_null() { - return Ok(None); - } - - let result = vmi.read_u32(registers.address_context(teb + TEB.LastStatusValue.offset))?; - Ok(Some(result)) - } - - // endregion: Misc - - // region: Object - - /// Retrieves the object header cookie used for obfuscating object types. - /// Returns `None` if the cookie is not present in the kernel image. - /// - /// # Notes - /// - /// Windows 10 introduced a security feature that obfuscates the type - /// of kernel objects by XORing the `TypeIndex` field in the object header - /// with a random cookie value. This method fetches that cookie, which is - /// essential for correctly interpreting object headers in memory. - /// - /// # Implementation Details - /// - /// The object header cookie is located by reading the `ObHeaderCookie` - /// symbol from the kernel image. - pub fn object_header_cookie( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - if let Some(cookie) = *self.object_header_cookie.borrow() { - return Ok(Some(cookie)); - } - - let ObHeaderCookie = match self.symbols.ObHeaderCookie { - Some(cookie) => cookie, - None => return Ok(None), - }; - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let cookie = vmi.read_u8(registers.address_context(kernel_image_base + ObHeaderCookie))?; - *self.object_header_cookie.borrow_mut() = Some(cookie); - Ok(Some(cookie)) - } - - /// Determines the type of a Windows kernel object. - /// - /// This method analyzes the object header of a given kernel object - /// and returns its type (e.g., Process, Thread, File). It handles the - /// obfuscation introduced by the object header cookie, ensuring accurate - /// type identification even on systems with this security feature enabled. - pub fn object_type( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - ) -> Result, VmiError> { - let ObTypeIndexTable = self.symbols.ObTypeIndexTable; - let OBJECT_HEADER = &self.offsets.common._OBJECT_HEADER; - - let object_header = object - OBJECT_HEADER.Body.offset; - let type_index = - vmi.read_u8(registers.address_context(object_header + OBJECT_HEADER.TypeIndex.offset))?; - - let index = match self.object_header_cookie(vmi, registers)? { - Some(cookie) => { - // - // TypeIndex ^ 2nd least significate byte of OBJECT_HEADER address ^ - // nt!ObHeaderCookie ref: https://medium.com/@ashabdalhalim/a-light-on-windows-10s-object-header-typeindex-value-e8f907e7073a - // - - let salt = (u64::from(object_header) >> 8) as u8; - type_index ^ salt ^ cookie - } - None => type_index, - }; - - let index = index as u64; - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let object_type = vmi.read_va( - registers.address_context( - kernel_image_base + ObTypeIndexTable + index * 8, // REVIEW: replace 8 with registers.address_width()? - ), - registers.address_width(), - )?; - - if let Some(typ) = self.object_type_cache.borrow().get(&object_type) { - return Ok(Some(*typ)); - } - - let object_name = self.read_unicode_string( - vmi, - registers.address_context(object_type + self.offsets.common._OBJECT_TYPE.Name.offset), - )?; - - let typ = match object_name.as_str() { - "ALPC Port" => WindowsObjectType::AlpcPort, - "DebugObject" => WindowsObjectType::DebugObject, - "Device" => WindowsObjectType::Device, - "Directory" => WindowsObjectType::Directory, - "Driver" => WindowsObjectType::Driver, - "Event" => WindowsObjectType::Event, - "File" => WindowsObjectType::File, - "Job" => WindowsObjectType::Job, - "Key" => WindowsObjectType::Key, - "Mutant" => WindowsObjectType::Mutant, - "Port" => WindowsObjectType::Port, - "Process" => WindowsObjectType::Process, - "Section" => WindowsObjectType::Section, - "SymbolicLink" => WindowsObjectType::SymbolicLink, - "Thread" => WindowsObjectType::Thread, - "Timer" => WindowsObjectType::Timer, - "Token" => WindowsObjectType::Token, - "Type" => WindowsObjectType::Type, - _ => return Ok(None), - }; - - self.object_type_cache.borrow_mut().insert(object_type, typ); - - Ok(Some(typ)) - } - - /// Retrieves the name of a named kernel object. - /// - /// Many Windows kernel objects (like mutexes, events, etc.) can have names. - /// This method extracts the name of such an object, if present. It also - /// provides information about the object's containing directory in the - /// object namespace. - pub fn object_name( - &self, - vmi: &VmiCore, - registers: &::Registers, - object: Va, - ) -> Result, VmiError> { - let ObpInfoMaskToOffset = self.symbols.ObpInfoMaskToOffset; - let OBJECT_HEADER = &self.offsets.common._OBJECT_HEADER; - let OBJECT_HEADER_NAME_INFO = &self.offsets.common._OBJECT_HEADER_NAME_INFO; - - let object_header = object - OBJECT_HEADER.Body.offset; - let info_mask = - vmi.read_u8(registers.address_context(object_header + OBJECT_HEADER.InfoMask.offset))?; - - bitflags::bitflags! { - struct InfoFlags: u8 { - const CREATOR_INFO = 0x01; - const NAME_INFO = 0x02; - const HANDLE_INFO = 0x04; - const QUOTA_INFO = 0x08; - const PROCESS_INFO = 0x10; - } - } - - let info_flags = InfoFlags::from_bits_truncate(info_mask); - if !info_flags.contains(InfoFlags::NAME_INFO) { - return Ok(None); - } - - // Offset = ObpInfoMaskToOffset[OBJECT_HEADER->InfoMask & (DesiredHeaderBit | (DesiredHeaderBit-1))] - - let mask = info_mask & (InfoFlags::NAME_INFO.bits() | (InfoFlags::NAME_INFO.bits() - 1)); - let mask = mask as u64; - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let offset = vmi - .read_u8(registers.address_context(kernel_image_base + ObpInfoMaskToOffset + mask))? - as u64; - - let object_header_name_info = object_header - offset; - - let directory = vmi.read_va( - registers.address_context( - object_header_name_info + OBJECT_HEADER_NAME_INFO.Directory.offset, - ), - registers.address_width(), - )?; - - let name = self.read_unicode_string( - vmi, - registers - .address_context(object_header_name_info + OBJECT_HEADER_NAME_INFO.Name.offset), - )?; - - Ok(Some(WindowsObjectName { directory, name })) - } - - /// Converts an `OBJECT_ATTRIBUTES` structure to an object name string. - /// - /// `OBJECT_ATTRIBUTES` is a structure used in many Windows system calls to - /// specify an object. This method interprets that structure and extracts - /// a meaningful name or path for the object. It handles both absolute and - /// relative object names, considering the root directory if specified. - /// - /// Returns `None` if the `_OBJECT_ATTRIBUTES::ObjectName` field is `NULL`. - pub fn object_attributes_to_object_name( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - object_attributes: Va, - ) -> Result, VmiError> { - let OBJECT_ATTRIBUTES = &self.offsets.common._OBJECT_ATTRIBUTES; - - let object_name_address = vmi.read_va( - registers.address_context(object_attributes + OBJECT_ATTRIBUTES.ObjectName.offset), - registers.address_width(), - )?; - - if object_name_address.is_null() { - return Ok(None); - } - - let object_name = - self.read_unicode_string(vmi, registers.address_context(object_name_address))?; - - let root_directory = vmi.read_va( - registers.address_context(object_attributes + OBJECT_ATTRIBUTES.RootDirectory.offset), - registers.address_width(), - )?; - - if root_directory.is_null() { - return Ok(Some(object_name)); - } - - let object = - match self.handle_to_object(vmi, registers, process, u64::from(root_directory))? { - Some(object) => object, - None => return Ok(Some(object_name)), - }; - - let root_name = match object { - WindowsObject::File(file) => Some(file.filename), - WindowsObject::Section(section) => match section.region.kind { - OsRegionKind::Mapped(mapped) => mapped.path?, - _ => None, - }, - }; - - match root_name { - Some(root_name) => Ok(Some(format!("{root_name}\\{object_name}"))), - None => Ok(Some(object_name)), - } - } - - // endregion: Object - - // region: PEB - - /// Retrieves the Process Environment Block (PEB) for a given process. - /// - /// The PEB contains crucial information about a process, including its - /// loaded modules, environment variables, and command line arguments. - pub fn process_peb( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let root = self.process_translation_root(vmi, registers, process)?; - - let address = self.__process_peb_address(vmi, registers, process, root)?; - let current_directory = self.__process_current_directory(vmi, registers, process, root)?; - let dll_path = self.__process_dll_path(vmi, registers, process, root)?; - let image_path_name = self.__process_image_path_name(vmi, registers, process, root)?; - let command_line = self.__process_command_line(vmi, registers, process, root)?; - - Ok(WindowsPeb { - address: address.va, - current_directory, - dll_path, - image_path_name, - command_line, - }) - } - - /// Internal method to get the address of the PEB. - /// - /// This method handles both native (non-WoW64) processes and WoW64 - /// processes, returning the appropriate PEB address based on the - /// process architecture. - fn __process_peb_address( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - - let wow64 = vmi.read_va( - (process.0 + EPROCESS.WoW64Process.offset, root), - registers.address_width(), - )?; - - if wow64.is_null() { - let peb64 = vmi.read_va( - (process.0 + EPROCESS.Peb.offset, root), - registers.address_width(), - )?; - - Ok(WindowsWow64Va::native(peb64)) - } - else { - let peb32 = match &self.offsets.ext { - Some(OffsetsExt::V1(_)) => wow64, - Some(OffsetsExt::V2(v2)) => vmi.read_va( - (wow64 + v2._EWOW64PROCESS.Peb.offset, root), - registers.address_width(), - )?, - None => panic!("OffsetsExt not set"), - }; - - Ok(WindowsWow64Va::x86(peb32)) - } - } - - /// Internal method to retrieve the address of - /// `RTL_USER_PROCESS_PARAMETERS`. - /// - /// This structure contains various process-specific parameters, including - /// the command line, current directory, and DLL search path. - fn __process_rtl_process_parameters( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let address = self.__process_peb_address(vmi, registers, process, root)?; - - match address.kind { - WindowsWow64Kind::Native => { - let PEB = &self.offsets.common._PEB; - - let va = vmi.read_va( - (address.va + PEB.ProcessParameters.offset, root), - registers.address_width(), - )?; - - Ok(WindowsWow64Va::native(va)) - } - WindowsWow64Kind::X86 => { - const PEB32_ProcessParameters_offset: u64 = 0x10; - - let va = vmi.read_va( - (address.va + PEB32_ProcessParameters_offset, root), - registers.address_width(), - )?; - - Ok(WindowsWow64Va::x86(va)) - } - } - } - - /// Gets the current working directory of a process. - /// - /// This method retrieves the full path of the current working directory - /// for the specified process. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NtCurrentPeb()->ProcessParameters; - /// PUNICODE_STRING CurrentDirectory = ProcessParameters->CurrentDirectory; - /// return CurrentDirectory; - /// ``` - pub fn process_current_directory( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let root = self.process_translation_root(vmi, registers, process)?; - self.__process_current_directory(vmi, registers, process, root) - } - - /// Internal method to get the current directory, handling both 32-bit and - /// 64-bit processes. - fn __process_current_directory( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let address = self.__process_rtl_process_parameters(vmi, registers, process, root)?; - - match address.kind { - WindowsWow64Kind::Native => { - self.process_current_directory_native(vmi, root, address.va) - } - WindowsWow64Kind::X86 => self.process_current_directory_32bit(vmi, root, address.va), - } - } - - /// Retrieves the current directory for a native (non-WoW64) process. - fn process_current_directory_native( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - let CURDIR = &self.offsets.common._CURDIR; - let RTL_USER_PROCESS_PARAMETERS = &self.offsets.common._RTL_USER_PROCESS_PARAMETERS; - - self.read_unicode_string( - vmi, - ( - rtl_process_parameters - + RTL_USER_PROCESS_PARAMETERS.CurrentDirectory.offset - + CURDIR.DosPath.offset, - root, - ), - ) - } - - /// Retrieves the current directory for a 32-bit process running under - /// WoW64. - fn process_current_directory_32bit( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - const RTL_USER_PROCESS_PARAMETERS32_CurrentDirectory_offset: u64 = 0x24; - - self.read_unicode_string32( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS32_CurrentDirectory_offset, - root, - ), - ) - } - - /// Gets the DLL search path for a process. - /// - /// This method retrieves the list of directories that the system searches - /// when loading DLLs for the specified process. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NtCurrentPeb()->ProcessParameters; - /// PUNICODE_STRING DllPath = ProcessParameters->DllPath; - /// return DllPath; - /// ``` - pub fn process_dll_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let root = self.process_translation_root(vmi, registers, process)?; - self.__process_dll_path(vmi, registers, process, root) - } - - /// Internal method to get the DLL path, handling both 32-bit and 64-bit - /// processes. - fn __process_dll_path( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let address = self.__process_rtl_process_parameters(vmi, registers, process, root)?; - - match address.kind { - WindowsWow64Kind::Native => self.process_dll_path_native(vmi, root, address.va), - WindowsWow64Kind::X86 => self.process_dll_path_32bit(vmi, root, address.va), - } - } - - /// Retrieves the DLL search path for a native (non-WoW64) process. - fn process_dll_path_native( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - let RTL_USER_PROCESS_PARAMETERS = &self.offsets.common._RTL_USER_PROCESS_PARAMETERS; - - self.read_unicode_string( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS.DllPath.offset, - root, - ), - ) - } - - /// Retrieves the DLL search path for a 32-bit process running under WoW64. - fn process_dll_path_32bit( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - const RTL_USER_PROCESS_PARAMETERS32_DllPath_offset: u64 = 0x30; - - self.read_unicode_string32( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS32_DllPath_offset, - root, - ), - ) - } - - /// Gets the full path of the executable image for a process. - /// - /// This method retrieves the full file system path of the main executable - /// that was used to create the specified process. - /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NtCurrentPeb()->ProcessParameters; - /// PUNICODE_STRING ImagePathName = ProcessParameters->ImagePathName; - /// return ImagePathName; - /// ``` - pub fn process_image_path_name( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let root = self.process_translation_root(vmi, registers, process)?; - self.__process_image_path_name(vmi, registers, process, root) - } - - /// Internal method to get the image path name, handling both 32-bit and - /// 64-bit processes. - fn __process_image_path_name( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let address = self.__process_rtl_process_parameters(vmi, registers, process, root)?; - - match address.kind { - WindowsWow64Kind::Native => self.process_image_path_name_native(vmi, root, address.va), - WindowsWow64Kind::X86 => self.process_image_path_name_32bit(vmi, root, address.va), - } - } - - /// Retrieves the image path name for a native (non-WoW64) process. - fn process_image_path_name_native( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - let RTL_USER_PROCESS_PARAMETERS = &self.offsets.common._RTL_USER_PROCESS_PARAMETERS; - - self.read_unicode_string( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS.ImagePathName.offset, - root, - ), - ) - } - - /// Retrieves the image path name for a 32-bit process running under WoW64. - fn process_image_path_name_32bit( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - const RTL_USER_PROCESS_PARAMETERS32_ImagePathName_offset: u64 = 0x38; - - self.read_unicode_string32( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS32_ImagePathName_offset, - root, - ), - ) + /// Returns the Windows object. + pub fn object<'a>( + vmi: VmiState<'a, Driver, Self>, + va: Va, + ) -> Result, VmiError> { + Ok(WindowsObject::new(vmi, va)) } - /// Gets the command line used to launch a process. + /// Returns the root directory object for the Windows kernel. /// - /// This method retrieves the full command line string, including the - /// executable path and any arguments, used to start the specified process. + /// # Notes /// - /// # Equivalent C pseudo-code + /// The object root directory is cached after the first read. /// - /// ```c - /// PRTL_USER_PROCESS_PARAMETERS ProcessParameters = NtCurrentPeb()->ProcessParameters; - /// PUNICODE_STRING CommandLine = ProcessParameters->CommandLine; - /// return CommandLine; - /// ``` - pub fn process_command_line( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let root = self.process_translation_root(vmi, registers, process)?; - self.__process_command_line(vmi, registers, process, root) - } - - /// Internal method to get the command line, handling both 32-bit and 64-bit - /// processes. - fn __process_command_line( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - root: Pa, - ) -> Result { - let address = self.__process_rtl_process_parameters(vmi, registers, process, root)?; + /// # Implementation Details + /// + /// Corresponds to `ObpRootDirectoryObject` symbol. + pub fn object_root_directory<'a>( + vmi: VmiState<'a, Driver, Self>, + ) -> Result, VmiError> { + let object_root_directory = this!(vmi) + .object_root_directory + .get_or_try_init(|| { + let ObpRootDirectoryObject = + Self::kernel_image_base(vmi)? + symbol!(vmi, ObpRootDirectoryObject); + + vmi.read_va_native(ObpRootDirectoryObject) + }) + .copied()?; - match address.kind { - WindowsWow64Kind::Native => self.process_command_line_native(vmi, root, address.va), - WindowsWow64Kind::X86 => self.process_command_line_32bit(vmi, root, address.va), - } + Ok(WindowsDirectoryObject::new(vmi, object_root_directory)) } - /// Retrieves the command line for a native (non-WoW64) process. - fn process_command_line_native( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - let RTL_USER_PROCESS_PARAMETERS = &self.offsets.common._RTL_USER_PROCESS_PARAMETERS; + /// Returns the object header cookie used for obfuscating object types. + /// Returns `None` if the cookie is not present in the kernel image. + /// + /// # Notes + /// + /// Windows 10 introduced a security feature that obfuscates the type + /// of kernel objects by XORing the `TypeIndex` field in the object header + /// with a random cookie value. This method fetches that cookie, which is + /// essential for correctly interpreting object headers in memory. + /// + /// The cookie is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `ObHeaderCookie` symbol. + pub fn object_header_cookie(vmi: VmiState) -> Result, VmiError> { + let ObHeaderCookie = match symbol!(vmi, ObHeaderCookie) { + Some(cookie) => cookie, + None => return Ok(None), + }; - self.read_unicode_string( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS.CommandLine.offset, - root, - ), - ) + Ok(Some( + this!(vmi) + .object_header_cookie + .get_or_try_init(|| { + let kernel_image_base = Self::kernel_image_base(vmi)?; + vmi.read_u8(kernel_image_base + ObHeaderCookie) + }) + .copied()?, + )) } - /// Retrieves the command line for a 32-bit process running under WoW64. - fn process_command_line_32bit( - &self, - vmi: &VmiCore, - root: Pa, - rtl_process_parameters: Va, - ) -> Result { - const RTL_USER_PROCESS_PARAMETERS32_CommandLine_offset: u64 = 0x40; - - self.read_unicode_string32( - vmi, - ( - rtl_process_parameters + RTL_USER_PROCESS_PARAMETERS32_CommandLine_offset, - root, - ), - ) + /// Returns the Windows object attributes. + pub fn object_attributes<'a>( + vmi: VmiState<'a, Driver, Self>, + object_attributes: Va, + ) -> Result, VmiError> { + Ok(WindowsObjectAttributes::new(vmi, object_attributes)) } - // endregion: PEB - - // region: Process - - /// Extracts the `EPROCESS` structure from a `KTHREAD` structure. + /// Reads string of bytes from an `_ANSI_STRING` structure. /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return Thread->Process; - /// ``` - pub fn process_from_thread( - &self, - vmi: &VmiCore, - registers: &::Registers, - thread: ThreadObject, - ) -> Result { - let KTHREAD = &self.offsets.common._KTHREAD; - - let process = vmi.read_va( - registers.address_context(thread.0 + KTHREAD.Process.offset), - registers.address_width(), - )?; - - Ok(ProcessObject(process)) + /// This method reads a native `_ANSI_STRING` structure which contains + /// an ASCII/ANSI string. The structure is read according to the current + /// OS's architecture (32-bit or 64-bit). + pub fn read_ansi_string_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_ansi_string_bytes_in(vmi, vmi.access_context(va)) } - /// Extracts the `EPROCESS` structure from a `KAPC_STATE` structure. + /// Reads string of bytes from a 32-bit version of `_ANSI_STRING` structure. /// - /// # Equivalent C pseudo-code - /// - /// ```c - /// return Thread->ApcState->Process; - /// ``` - pub fn process_from_thread_apc_state( - &self, - vmi: &VmiCore, - registers: &::Registers, - thread: ThreadObject, - ) -> Result { - let KTHREAD = &self.offsets.common._KTHREAD; - let KAPC_STATE = &self.offsets.common._KAPC_STATE; - - let process = vmi.read_va( - registers - .address_context(thread.0 + KTHREAD.ApcState.offset + KAPC_STATE.Process.offset), - registers.address_width(), - )?; - - Ok(ProcessObject(process)) + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_ansi_string32_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_ansi_string32_bytes_in(vmi, vmi.access_context(va)) } - /// Constructs an [`OsProcess`] from an `_EPROCESS`. - pub fn process_object_to_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; - let KPROCESS = &self.offsets.common._KPROCESS; - - let id = - vmi.read_u32(registers.address_context(process.0 + EPROCESS.UniqueProcessId.offset))?; - - let name = - vmi.read_string(registers.address_context(process.0 + EPROCESS.ImageFileName.offset))?; - - let translation_root = vmi.read_address( - registers.address_context(process.0 + KPROCESS.DirectoryTableBase.offset), - registers.address_width(), - )?; - - Ok(OsProcess { - id: id.into(), - object: process, - name, - translation_root: translation_root.into(), - }) + /// Reads string of bytes from a 64-bit version of `_ANSI_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 64-bit processes where pointers are 64 bits. + pub fn read_ansi_string64_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_ansi_string64_bytes_in(vmi, vmi.access_context(va)) } - // endregion: Process - - // region: String - /// Reads string from an `_ANSI_STRING` structure. /// /// This method reads a native `_ANSI_STRING` structure which contains /// an ASCII/ANSI string. The structure is read according to the current /// OS's architecture (32-bit or 64-bit). - pub fn read_ansi_string( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - // - // `_ANSI_STRING` is unfortunately missing in the PDB symbols. - // However, its layout is same as `_UNICODE_STRING`. - // - - let ANSI_STRING = &self.offsets.common._UNICODE_STRING; - - let string = StructReader::new(vmi, ctx, ANSI_STRING.effective_len())?; - let string_length = string.read(ANSI_STRING.Length)?; - let string_buffer = string.read(ANSI_STRING.Buffer)?; - - ctx.address = string_buffer; - - let mut buffer = vec![0u8; string_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf8_lossy(&buffer).into()) + pub fn read_ansi_string(vmi: VmiState, va: Va) -> Result { + Self::read_ansi_string_in(vmi, vmi.access_context(va)) } /// Reads string from a 32-bit version of `_ANSI_STRING` structure. /// /// This method is specifically for reading `_ANSI_STRING` structures in /// 32-bit processes or WoW64 processes where pointers are 32 bits. - pub fn read_ansi_string32( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - let mut buffer = [0u8; 8]; - vmi.read(ctx, &mut buffer)?; - - let string_length = u16::from_le_bytes([buffer[0], buffer[1]]); - // let string_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); - let string_buffer = u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]); - - ctx.address = string_buffer as u64; - - let mut buffer = vec![0u8; string_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf8_lossy(&buffer).into()) + pub fn read_ansi_string32(vmi: VmiState, va: Va) -> Result { + Self::read_ansi_string32_in(vmi, vmi.access_context(va)) } /// Reads string from a 64-bit version of `_ANSI_STRING` structure. /// /// This method is specifically for reading `_ANSI_STRING` structures in /// 64-bit processes where pointers are 64 bits. - pub fn read_ansi_string64( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - let mut buffer = [0u8; 16]; - vmi.read(ctx, &mut buffer)?; - - let string_length = u16::from_le_bytes([buffer[0], buffer[1]]); - // let string_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); - let string_buffer = u64::from_le_bytes([ - buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], - buffer[15], - ]); - - ctx.address = string_buffer; - - let mut buffer = vec![0u8; string_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf8_lossy(&buffer).into()) + pub fn read_ansi_string64(vmi: VmiState, va: Va) -> Result { + Self::read_ansi_string64_in(vmi, vmi.access_context(va)) } /// Reads string from a `_UNICODE_STRING` structure. @@ -2965,673 +916,515 @@ where /// This method reads a native `_UNICODE_STRING` structure which contains /// a UTF-16 string. The structure is read according to the current OS's /// architecture (32-bit or 64-bit). - pub fn read_unicode_string( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - let UNICODE_STRING = &self.offsets.common._UNICODE_STRING; - - let string = StructReader::new(vmi, ctx, UNICODE_STRING.effective_len())?; - let string_length = string.read(UNICODE_STRING.Length)?; - let string_buffer = string.read(UNICODE_STRING.Buffer)?; - - ctx.address = string_buffer; - - let mut buffer = vec![0u8; string_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf16_lossy( - &buffer - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect::>(), - )) + pub fn read_unicode_string_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_unicode_string_bytes_in(vmi, vmi.access_context(va)) } /// Reads string from a 32-bit version of `_UNICODE_STRING` structure. /// /// This method is specifically for reading `_UNICODE_STRING` structures /// in 32-bit processes or WoW64 processes where pointers are 32 bits. - pub fn read_unicode_string32( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - let mut buffer = [0u8; 8]; - vmi.read(ctx, &mut buffer)?; - - let string_length = u16::from_le_bytes([buffer[0], buffer[1]]); - // let string_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); - let string_buffer = u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]); - - ctx.address = string_buffer as u64; - - let mut buffer = vec![0u8; string_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf16_lossy( - &buffer - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect::>(), - )) + pub fn read_unicode_string32_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_unicode_string32_bytes_in(vmi, vmi.access_context(va)) } /// Reads string from a 64-bit version of `_UNICODE_STRING` structure. /// /// This method is specifically for reading `_UNICODE_STRING` structures /// in 64-bit processes where pointers are 64 bits. - pub fn read_unicode_string64( - &self, - vmi: &VmiCore, - ctx: impl Into, - ) -> Result { - let mut ctx = ctx.into(); - - let mut buffer = [0u8; 16]; - vmi.read(ctx, &mut buffer)?; - - let us_length = u16::from_le_bytes([buffer[0], buffer[1]]); - // let us_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); - let us_buffer = u64::from_le_bytes([ - buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], - buffer[15], - ]); - - ctx.address = us_buffer; - - let mut buffer = vec![0u8; us_length as usize]; - vmi.read(ctx, &mut buffer)?; - - Ok(String::from_utf16_lossy( - &buffer - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect::>(), - )) + pub fn read_unicode_string64_bytes( + vmi: VmiState, + va: Va, + ) -> Result, VmiError> { + Self::read_unicode_string64_bytes_in(vmi, vmi.access_context(va)) } - // endregion: String - - // region: User Address - - /// Returns the lowest user-mode address. - /// - /// This method returns a constant value (0x10000) representing the lowest - /// address that can be used by user-mode applications in Windows. - /// - /// # Notes - /// - /// * Windows creates a `NO_ACCESS` VAD (Virtual Address Descriptor) for the first 64KB - /// of virtual memory. This means the VA range 0-0x10000 is off-limits for usage. - /// * This behavior is consistent across all Windows versions from XP through - /// recent Windows 11, and applies to x86, x64, and ARM64 architectures. - /// * Many Windows APIs leverage this fact to determine whether an input argument - /// is a pointer or not. Here are two notable examples: - /// - /// 1. The `FindResource()` function accepts an `lpName` parameter of type `LPCTSTR`, - /// which can be either: - /// - A pointer to a valid string - /// - A value created by `MAKEINTRESOURCE(ID)` - /// - /// This allows `FindResource()` to accept `WORD` values (unsigned shorts) with - /// a maximum value of 0xFFFF, distinguishing them from valid memory addresses. - /// - /// 2. The `AddAtom()` function similarly accepts an `lpString` parameter of type `LPCTSTR`. - /// This parameter can be: - /// - A pointer to a null-terminated string (max 255 bytes) - /// - An integer atom converted using the `MAKEINTATOM(ID)` macro + /// Reads string from a `_UNICODE_STRING` structure. /// - /// In both cases, the API can distinguish between valid pointers (which will be - /// above 0x10000) and integer values (which will be below 0x10000), allowing - /// for flexible parameter usage without ambiguity. - pub fn lowest_user_address( - &self, - _vmi: &VmiCore, - _registers: &::Registers, - ) -> Result { - Ok(Va(0x10000)) + /// This method reads a native `_UNICODE_STRING` structure which contains + /// a UTF-16 string. The structure is read according to the current OS's + /// architecture (32-bit or 64-bit). + pub fn read_unicode_string(vmi: VmiState, va: Va) -> Result { + Self::read_unicode_string_in(vmi, vmi.access_context(va)) } - /// Retrieves the highest user-mode address. + /// Reads string from a 32-bit version of `_UNICODE_STRING` structure. /// - /// This method reads the highest user-mode address from the Windows kernel. - /// The value is cached after the first read for performance. - pub fn highest_user_address( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - if let Some(highest_user_address) = *self.highest_user_address.borrow() { - return Ok(highest_user_address); - } - - let MmHighestUserAddress = - self.kernel_image_base(vmi, registers)? + self.symbols.MmHighestUserAddress; - - let highest_user_address = vmi.read_va( - registers.address_context(MmHighestUserAddress), - registers.address_width(), - )?; - *self.highest_user_address.borrow_mut() = Some(highest_user_address); - Ok(highest_user_address) + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_unicode_string32(vmi: VmiState, va: Va) -> Result { + Self::read_unicode_string32_in(vmi, vmi.access_context(va)) } - /// Checks if a given address is a valid user-mode address. + /// Reads string from a 64-bit version of `_UNICODE_STRING` structure. /// - /// This method determines whether the provided address falls within - /// the range of valid user-mode addresses in Windows. - pub fn is_valid_user_address( - &self, - vmi: &VmiCore, - registers: &::Registers, - address: Va, - ) -> Result { - let lowest_user_address = self.lowest_user_address(vmi, registers)?; - let highest_user_address = self.highest_user_address(vmi, registers)?; - - Ok(address >= lowest_user_address && address <= highest_user_address) - } - - // endregion: User Address -} - -#[allow(non_snake_case)] -impl VmiOs for WindowsOs -where - Driver: VmiDriver, - Driver::Architecture: Architecture + ArchAdapter, -{ - fn kernel_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - Driver::Architecture::kernel_image_base(self, vmi, registers) - } - - fn kernel_information_string( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { - let NtBuildLab = self.symbols.NtBuildLab; - - if let Some(nt_build_lab) = self.nt_build_lab.borrow().as_ref() { - return Ok(nt_build_lab.clone()); - } - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let nt_build_lab = - vmi.read_string(registers.address_context(kernel_image_base + NtBuildLab))?; - *self.nt_build_lab.borrow_mut() = Some(nt_build_lab.clone()); - Ok(nt_build_lab) - } - - fn kpti_enabled( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { - let KiKvaShadow = self.symbols.KiKvaShadow; - - if let Some(ki_kva_shadow) = self.ki_kva_shadow.borrow().as_ref() { - return Ok(*ki_kva_shadow); - } - - let KiKvaShadow = match KiKvaShadow { - Some(KiKvaShadow) => KiKvaShadow, - None => { - *self.ki_kva_shadow.borrow_mut() = Some(false); - return Ok(false); - } - }; - - let kernel_image_base = self.kernel_image_base(vmi, registers)?; - let ki_kva_shadow = - vmi.read_u8(registers.address_context(kernel_image_base + KiKvaShadow))? != 0; - *self.ki_kva_shadow.borrow_mut() = Some(ki_kva_shadow); - Ok(ki_kva_shadow) + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 64-bit processes where pointers are 64 bits. + pub fn read_unicode_string64(vmi: VmiState, va: Va) -> Result { + Self::read_unicode_string64_in(vmi, vmi.access_context(va)) } - fn modules( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result, VmiError> { - let mut result = Vec::new(); + /// Reads string of bytes from a 32-bit version of `_ANSI_STRING` or + /// `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` or + /// `_UNICODE_STRING` structures in 32-bit processes or WoW64 processes + /// where pointers are 32 bits. + fn read_string32_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + let mut ctx = ctx.into(); - let PsLoadedModuleList = - self.kernel_image_base(vmi, registers)? + self.symbols.PsLoadedModuleList; + let mut buffer = [0u8; 8]; + vmi.read_in(ctx, &mut buffer)?; - let KLDR_DATA_TABLE_ENTRY = &self.offsets.common._KLDR_DATA_TABLE_ENTRY; + let string_length = u16::from_le_bytes([buffer[0], buffer[1]]); - self.enumerate_list(vmi, registers, PsLoadedModuleList, |entry| { - let module_entry = entry - KLDR_DATA_TABLE_ENTRY.InLoadOrderLinks.offset; + if string_length == 0 { + return Ok(Vec::new()); + } - if let Ok(module) = self.kernel_module(vmi, registers, module_entry) { - result.push(module) - } + // let string_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); + let string_buffer = u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]); - true - })?; + if string_buffer == 0 { + tracing::warn!( + addr = %Hex(ctx.address), + len = string_length, + "String buffer is NULL" + ); - Ok(result) - } + return Ok(Vec::new()); + } - fn system_process( - &self, - vmi: &VmiCore, - registers: &<::Architecture as Architecture>::Registers, - ) -> Result { - let PsInitialSystemProcess = - self.kernel_image_base(vmi, registers)? + self.symbols.PsInitialSystemProcess; + ctx.address = string_buffer as u64; - let process = vmi.read_va( - registers.address_context(PsInitialSystemProcess), - registers.address_width(), - )?; + let mut buffer = vec![0u8; string_length as usize]; + vmi.read_in(ctx, &mut buffer)?; - Ok(ProcessObject(process)) + Ok(buffer) } - fn thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - thread: ThreadObject, - ) -> Result { - let ETHREAD = &self.offsets.common._ETHREAD; - let CLIENT_ID = &self.offsets.common._CLIENT_ID; - - let result = vmi.read_u32( - registers - .address_context(thread.0 + ETHREAD.Cid.offset + CLIENT_ID.UniqueThread.offset), - )?; - - Ok(ThreadId(result)) - } + /// Reads string of bytes from a 64-bit version of `_ANSI_STRING` or + /// `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` or + /// `_UNICODE_STRING` structures in 64-bit processes where pointers + /// are 64 bits. + fn read_string64_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + let mut ctx = ctx.into(); - fn process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; + let mut buffer = [0u8; 16]; + vmi.read_in(ctx, &mut buffer)?; - let result = - vmi.read_u32(registers.address_context(process.0 + EPROCESS.UniqueProcessId.offset))?; + let string_length = u16::from_le_bytes([buffer[0], buffer[1]]); - Ok(ProcessId(result)) - } + if string_length == 0 { + return Ok(Vec::new()); + } - fn current_thread( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let KPCR = &self.offsets.common._KPCR; - let KPRCB = &self.offsets.common._KPRCB; + // let string_maximum_length = u16::from_le_bytes([buffer[2], buffer[3]]); + let string_buffer = u64::from_le_bytes([ + buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], + buffer[15], + ]); - let kpcr = self.current_kpcr(vmi, registers); + if string_buffer == 0 { + tracing::warn!( + addr = %Hex(ctx.address), + len = string_length, + "String buffer is NULL" + ); - if kpcr.is_null() { - return Err(VmiError::Other("Invalid KPCR")); + return Ok(Vec::new()); } - let addr = kpcr + KPCR.Prcb.offset + KPRCB.CurrentThread.offset; - let result = vmi.read_va(registers.address_context(addr), registers.address_width())?; + ctx.address = string_buffer; - Ok(ThreadObject(result)) - } + let mut buffer = vec![0u8; string_length as usize]; + vmi.read_in(ctx, &mut buffer)?; - fn current_thread_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let thread = self.current_thread(vmi, registers)?; + Ok(buffer) + } - if thread.is_null() { - return Err(VmiError::Other("Invalid thread")); + /// Reads string of bytes from an `_ANSI_STRING` structure. + /// + /// This method reads a native `_ANSI_STRING` structure which contains + /// an ASCII/ANSI string. The structure is read according to the current + /// OS's architecture (32-bit or 64-bit). + pub fn read_ansi_string_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + match vmi.registers().address_width() { + 4 => Self::read_ansi_string32_bytes_in(vmi, ctx), + 8 => Self::read_ansi_string64_bytes_in(vmi, ctx), + _ => panic!("Unsupported address width"), } + } - self.thread_id(vmi, registers, thread) + /// Reads string of bytes from a 32-bit version of `_ANSI_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_ansi_string32_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + Self::read_string32_in(vmi, ctx) } - fn current_process( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let thread = self.current_thread(vmi, registers)?; + /// Reads string of bytes from a 64-bit version of `_ANSI_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 64-bit processes where pointers are 64 bits. + pub fn read_ansi_string64_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + Self::read_string64_in(vmi, ctx) + } - if thread.is_null() { - return Err(VmiError::Other("Invalid thread")); + /// Reads string from an `_ANSI_STRING` structure. + /// + /// This method reads a native `_ANSI_STRING` structure which contains + /// an ASCII/ANSI string. The structure is read according to the current + /// OS's architecture (32-bit or 64-bit). + pub fn read_ansi_string_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + match vmi.registers().address_width() { + 4 => Self::read_ansi_string32_in(vmi, ctx), + 8 => Self::read_ansi_string64_in(vmi, ctx), + _ => panic!("Unsupported address width"), } + } - self.process_from_thread_apc_state(vmi, registers, thread) + /// Reads string from a 32-bit version of `_ANSI_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_ansi_string32_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + Ok(String::from_utf8_lossy(&Self::read_ansi_string32_bytes_in(vmi, ctx)?).into()) } - fn current_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - let process = self.current_process(vmi, registers)?; + /// Reads string from a 64-bit version of `_ANSI_STRING` structure. + /// + /// This method is specifically for reading `_ANSI_STRING` structures in + /// 64-bit processes where pointers are 64 bits. + pub fn read_ansi_string64_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + Ok(String::from_utf8_lossy(&Self::read_ansi_string64_bytes_in(vmi, ctx)?).into()) + } - if process.is_null() { - return Err(VmiError::Other("Invalid process")); + /// Reads string from a `_UNICODE_STRING` structure. + /// + /// This method reads a native `_UNICODE_STRING` structure which contains + /// a UTF-16 string. The structure is read according to the current OS's + /// architecture (32-bit or 64-bit). + pub fn read_unicode_string_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + match vmi.registers().address_width() { + 4 => Self::read_unicode_string32_bytes_in(vmi, ctx), + 8 => Self::read_unicode_string64_bytes_in(vmi, ctx), + _ => panic!("Unsupported address width"), } - - self.process_id(vmi, registers, process) } - fn processes( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - let mut result = Vec::new(); - - let PsActiveProcessHead = - self.kernel_image_base(vmi, registers)? + self.symbols.PsActiveProcessHead; - let EPROCESS = &self.offsets.common._EPROCESS; - - self.enumerate_list(vmi, registers, PsActiveProcessHead, |entry| { - let process_object = entry - EPROCESS.ActiveProcessLinks.offset; + /// Reads string from a 32-bit version of `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_unicode_string32_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + let buffer = Self::read_string32_in(vmi, ctx)?; - if let Ok(process) = - self.process_object_to_process(vmi, registers, process_object.into()) - { - result.push(process) - } + Ok(buffer + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect::>()) + } - true - })?; + /// Reads string from a 64-bit version of `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 64-bit processes where pointers are 64 bits. + pub fn read_unicode_string64_bytes_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result, VmiError> { + let buffer = Self::read_string64_in(vmi, ctx)?; - Ok(result) + Ok(buffer + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect::>()) } - fn process_parent_process_id( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; + /// Reads string from a `_UNICODE_STRING` structure. + /// + /// This method reads a native `_UNICODE_STRING` structure which contains + /// a UTF-16 string. The structure is read according to the current OS's + /// architecture (32-bit or 64-bit). + pub fn read_unicode_string_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + match vmi.registers().address_width() { + 4 => Self::read_unicode_string32_in(vmi, ctx), + 8 => Self::read_unicode_string64_in(vmi, ctx), + _ => panic!("Unsupported address width"), + } + } - let result = vmi.read_u32( - registers.address_context(process.0 + EPROCESS.InheritedFromUniqueProcessId.offset), - )?; + /// Reads string from a 32-bit version of `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 32-bit processes or WoW64 processes where pointers are 32 bits. + pub fn read_unicode_string32_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + Ok(String::from_utf16_lossy( + &Self::read_unicode_string32_bytes_in(vmi, ctx)?, + )) + } - Ok(ProcessId(result)) + /// Reads string from a 64-bit version of `_UNICODE_STRING` structure. + /// + /// This method is specifically for reading `_UNICODE_STRING` structures + /// in 64-bit processes where pointers are 64 bits. + pub fn read_unicode_string64_in( + vmi: VmiState, + ctx: impl Into, + ) -> Result { + Ok(String::from_utf16_lossy( + &Self::read_unicode_string64_bytes_in(vmi, ctx)?, + )) } - fn process_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; + /// Returns an iterator over a doubly-linked list of `LIST_ENTRY` structures. + /// + /// This method is used to iterate over a doubly-linked list of `LIST_ENTRY` + /// structures in memory. It returns an iterator that yields the virtual + /// addresses of each `LIST_ENTRY` structure in the list. + pub fn linked_list<'a>( + vmi: VmiState<'a, Driver, Self>, + list_head: Va, + offset: u64, + ) -> Result> + 'a, VmiError> { + Ok(ListEntryIterator::new(vmi, list_head, offset)) + } +} - let wow64process = vmi.read_va( - registers.address_context(process.0 + EPROCESS.WoW64Process.offset), - registers.address_width(), - )?; +#[expect(non_snake_case)] +impl VmiOs for WindowsOs +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + type Process<'a> = WindowsProcess<'a, Driver>; + type Thread<'a> = WindowsThread<'a, Driver>; + type Image<'a> = WindowsImage<'a, Driver>; + type Module<'a> = WindowsModule<'a, Driver>; + type Region<'a> = WindowsRegion<'a, Driver>; + type Mapped<'a> = WindowsControlArea<'a, Driver>; - if wow64process.is_null() { - Ok(OsArchitecture::Amd64) - } - else { - Ok(OsArchitecture::X86) - } + fn kernel_image_base(vmi: VmiState) -> Result { + Driver::Architecture::kernel_image_base(vmi) } - fn process_translation_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let KPROCESS = &self.offsets.common._KPROCESS; + fn kernel_information_string(vmi: VmiState) -> Result { + this!(vmi) + .nt_build_lab + .get_or_try_init(|| { + let NtBuildLab = symbol!(vmi, NtBuildLab); - let current_process = self.current_process(vmi, registers)?; + let kernel_image_base = Self::kernel_image_base(vmi)?; + vmi.read_string(kernel_image_base + NtBuildLab) + }) + .cloned() + } - if process == current_process { - return Ok(registers.translation_root(process.0)); - } + /// Checks if Kernel Virtual Address Shadow (KVA Shadow) is enabled. + /// + /// KVA Shadow is a security feature introduced in Windows 10 that + /// mitigates Meltdown and Spectre vulnerabilities by isolating + /// kernel memory from user-mode processes. + /// + /// # Notes + /// + /// This value is cached after the first read. + /// + /// # Implementation Details + /// + /// Corresponds to `KiKvaShadow` symbol. + fn kpti_enabled(vmi: VmiState) -> Result { + this!(vmi) + .ki_kva_shadow + .get_or_try_init(|| { + let KiKvaShadow = symbol!(vmi, KiKvaShadow); - let root = Cr3::from(u64::from(vmi.read_va( - registers.address_context(process.0 + KPROCESS.DirectoryTableBase.offset), - registers.address_width(), - )?)); + let KiKvaShadow = match KiKvaShadow { + Some(KiKvaShadow) => KiKvaShadow, + None => return Ok(false), + }; - Ok(Pa::from(root)) + let kernel_image_base = Self::kernel_image_base(vmi)?; + Ok(vmi.read_u8(kernel_image_base + KiKvaShadow)? != 0) + }) + .copied() } - fn process_user_translation_root( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let KPROCESS = &self.offsets.common._KPROCESS; - let UserDirectoryTableBase = match &KPROCESS.UserDirectoryTableBase { - Some(UserDirectoryTableBase) => UserDirectoryTableBase, - None => return self.process_translation_root(vmi, registers, process), - }; + /// Returns an iterator over all loaded Windows Driver modules. + /// + /// This method returns an iterator over all loaded Windows Driver modules. + /// It reads the `PsLoadedModuleList` symbol from the kernel image and + /// iterates over the linked list of `KLDR_DATA_TABLE_ENTRY` structures + /// representing each loaded module. + fn modules( + vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + let PsLoadedModuleList = Self::kernel_image_base(vmi)? + symbol!(vmi, PsLoadedModuleList); + let KLDR_DATA_TABLE_ENTRY = offset!(vmi, _KLDR_DATA_TABLE_ENTRY); - let root = u64::from(vmi.read_va( - registers.address_context(process.0 + UserDirectoryTableBase.offset), - registers.address_width(), - )?); + Ok(ListEntryIterator::new( + vmi, + PsLoadedModuleList, + KLDR_DATA_TABLE_ENTRY.InLoadOrderLinks.offset(), + ) + .map(move |result| result.map(|entry| WindowsModule::new(vmi, entry)))) + } - if root < Driver::Architecture::PAGE_SIZE { - return self.process_translation_root(vmi, registers, process); - } + /// Returns an iterator over all Windows processes. + /// + /// This method returns an iterator over all Windows processes. It reads the + /// `PsActiveProcessHead` symbol from the kernel image and iterates over the + /// linked list of `EPROCESS` structures representing each process. + fn processes( + vmi: VmiState<'_, Driver, Self>, + ) -> Result, VmiError>> + '_, VmiError> { + let PsActiveProcessHead = Self::kernel_image_base(vmi)? + symbol!(vmi, PsActiveProcessHead); + let EPROCESS = offset!(vmi, _EPROCESS); - Ok(Pa::from(Cr3::from(root))) + Ok(ListEntryIterator::new( + vmi, + PsActiveProcessHead, + EPROCESS.ActiveProcessLinks.offset(), + ) + .map(move |result| result.map(|entry| WindowsProcess::new(vmi, ProcessObject(entry))))) } - fn process_filename( - &self, - vmi: &VmiCore, - registers: &::Registers, + fn process( + vmi: VmiState<'_, Driver, Self>, process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; + ) -> Result, VmiError> { + Ok(WindowsProcess::new(vmi, process)) + } - vmi.read_string(registers.address_context(process.0 + EPROCESS.ImageFileName.offset)) + /// Returns the current process. + fn current_process(vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + Self::current_thread(vmi)?.attached_process() } - fn process_image_base( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result { - let EPROCESS = &self.offsets.common._EPROCESS; + /// Returns the system process. + /// + /// The system process is the first process created by the Windows kernel + /// during system initialization. It is the parent process of all other + /// processes in the system. + fn system_process(vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + let PsInitialSystemProcess = + Self::kernel_image_base(vmi)? + symbol!(vmi, PsInitialSystemProcess); - vmi.read_va( - registers.address_context(process.0 + EPROCESS.SectionBaseAddress.offset), - registers.address_width(), - ) + let process = vmi.read_va_native(PsInitialSystemProcess)?; + Ok(WindowsProcess::new(vmi, ProcessObject(process))) } - fn process_regions( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - ) -> Result, VmiError> { - let vad_root = self.vad_root(vmi, registers, process)?; - self.vad_root_to_regions(vmi, registers, vad_root) + fn thread( + vmi: VmiState<'_, Driver, Self>, + thread: ThreadObject, + ) -> Result, VmiError> { + Ok(WindowsThread::new(vmi, thread)) } - fn process_address_is_valid( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError> { - Driver::Architecture::process_address_is_valid(self, vmi, registers, process, address) - } + /// Returns the current thread. + fn current_thread(vmi: VmiState<'_, Driver, Self>) -> Result, VmiError> { + let KPCR = offset!(vmi, _KPCR); + let KPRCB = offset!(vmi, _KPRCB); - fn find_process_region( - &self, - vmi: &VmiCore, - registers: &::Registers, - process: ProcessObject, - address: Va, - ) -> Result, VmiError> { - let vad = match self.find_process_vad(vmi, registers, process, address)? { - Some(vad) => vad, - None => return Ok(None), - }; + let kpcr = Self::current_kpcr(vmi); - Ok(Some(self.vad_to_region(vmi, registers, vad)?)) - } + if kpcr.is_null() { + return Err(WindowsError::CorruptedStruct("KPCR").into()); + } - fn image_architecture( - &self, - vmi: &VmiCore, - registers: &::Registers, - image_base: Va, - ) -> Result { - let mut data = [0u8; Amd64::PAGE_SIZE as usize]; - vmi.read(registers.address_context(image_base), &mut data)?; - - let pe_magic = optional_header_magic(data.as_ref()) - .map_err(|_| VmiError::Os(PeError::InvalidPeMagic.into()))?; - - match pe_magic { - IMAGE_NT_OPTIONAL_HDR32_MAGIC => Ok(OsArchitecture::X86), - IMAGE_NT_OPTIONAL_HDR64_MAGIC => Ok(OsArchitecture::Amd64), - _ => Ok(OsArchitecture::Unknown), + let addr = kpcr + KPCR.Prcb.offset() + KPRCB.CurrentThread.offset(); + let result = vmi.read_va_native(addr)?; + + if result.is_null() { + return Err(WindowsError::CorruptedStruct("KPCR.Prcb.CurrentThread").into()); } + + Ok(WindowsThread::new(vmi, ThreadObject(result))) } - fn image_exported_symbols( - &self, - vmi: &VmiCore, - registers: &::Registers, - image_base: Va, - ) -> Result, VmiError> { - match self.image_architecture(vmi, registers, image_base)? { - OsArchitecture::Unknown => Err(VmiError::Os(PeError::InvalidPeMagic.into())), - OsArchitecture::X86 => { - tracing::trace!(?image_base, "32-bit PE"); - self.image_exported_symbols_generic::(vmi, registers, image_base) - } - OsArchitecture::Amd64 => { - tracing::trace!(?image_base, "64-bit PE"); - self.image_exported_symbols_generic::(vmi, registers, image_base) - } - } + fn image(vmi: VmiState<'_, Driver, Self>, image_base: Va) -> Result, VmiError> { + Ok(WindowsImage::new(vmi, image_base)) } - fn syscall_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result { - Driver::Architecture::syscall_argument(self, vmi, registers, index) + fn module(vmi: VmiState<'_, Driver, Self>, module: Va) -> Result, VmiError> { + Ok(WindowsModule::new(vmi, module)) } - fn function_argument( - &self, - vmi: &VmiCore, - registers: &::Registers, - index: u64, - ) -> Result { - Driver::Architecture::function_argument(self, vmi, registers, index) + fn region(vmi: VmiState<'_, Driver, Self>, region: Va) -> Result, VmiError> { + Ok(WindowsRegion::new(vmi, region)) } - fn function_return_value( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result { - Driver::Architecture::function_return_value(self, vmi, registers) + fn syscall_argument(vmi: VmiState, index: u64) -> Result { + Driver::Architecture::syscall_argument(vmi, index) } - fn last_error( - &self, - vmi: &VmiCore, - registers: &::Registers, - ) -> Result, VmiError> { - let KTHREAD = &self.offsets.common._KTHREAD; - let TEB = &self.offsets.common._TEB; + fn function_argument(vmi: VmiState, index: u64) -> Result { + Driver::Architecture::function_argument(vmi, index) + } - let current_thread = self.current_thread(vmi, registers)?; - let teb = vmi.read_va( - registers.address_context(current_thread.0 + KTHREAD.Teb.offset), - registers.address_width(), - )?; + fn function_return_value(vmi: VmiState) -> Result { + Driver::Architecture::function_return_value(vmi) + } + + fn last_error(vmi: VmiState) -> Result, VmiError> { + let KTHREAD = offset!(vmi, _KTHREAD); + let TEB = offset!(vmi, _TEB); + + let current_thread = Self::current_thread(vmi)?.object()?; + let teb = vmi.read_va_native(current_thread.0 + KTHREAD.Teb.offset())?; if teb.is_null() { return Ok(None); } - let result = vmi.read_u32(registers.address_context(teb + TEB.LastErrorValue.offset))?; + let result = vmi.read_u32(teb + TEB.LastErrorValue.offset())?; Ok(Some(result)) } } - -impl OsExt for WindowsOs -where - Driver: VmiDriver, - Driver::Architecture: Architecture + ArchAdapter, -{ - fn enumerate_list( - &self, - vmi: &VmiCore, - registers: &::Registers, - list_head: Va, - mut callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError> { - let mut entry = vmi.read_va( - registers.address_context(list_head), - registers.address_width(), - )?; - - while entry != list_head { - if !callback(entry) { - break; - } - - entry = vmi.read_va(registers.address_context(entry), registers.address_width())?; - } - - Ok(()) - } - - fn enumerate_tree( - &self, - vmi: &VmiCore, - registers: &::Registers, - root: Va, - callback: impl FnMut(Va) -> bool, - ) -> Result<(), VmiError> { - match &self.offsets.ext { - Some(OffsetsExt::V1(offsets)) => { - self.enumerate_tree_v1(vmi, registers, root, callback, offsets) - } - Some(OffsetsExt::V2(offsets)) => { - self.enumerate_tree_v2(vmi, registers, root, callback, offsets) - } - None => panic!("OffsetsExt not set"), - } - } -} diff --git a/crates/vmi-os-windows/src/offsets/mod.rs b/crates/vmi-os-windows/src/offsets/mod.rs index 0ed1d82..ed166bf 100644 --- a/crates/vmi-os-windows/src/offsets/mod.rs +++ b/crates/vmi-os-windows/src/offsets/mod.rs @@ -60,10 +60,13 @@ symbols! { NtClose: Option, + ExAllocatePool: u64, ExAllocatePoolWithTag: u64, + ExFreePool: u64, ExFreePoolWithTag: u64, MmGetSystemRoutineAddress: u64, + ObpRootDirectoryObject: u64, ObHeaderCookie: Option, ObTypeIndexTable: u64, ObpInfoMaskToOffset: u64, @@ -84,6 +87,11 @@ offsets! { /// [`WindowsOs`]: crate::WindowsOs #[derive(Debug)] pub struct OffsetsCommon { + struct _LIST_ENTRY { + Flink: Field, // struct _LIST_ENTRY* + Blink: Field, // struct _LIST_ENTRY* + } + struct _EX_FAST_REF { RefCnt: Bitfield, Value: Field, @@ -105,12 +113,12 @@ offsets! { #[isr(alias = "_LDR_DATA_TABLE_ENTRY")] struct _KLDR_DATA_TABLE_ENTRY { - InLoadOrderLinks: Field, // _LIST_ENTRY - DllBase: Field, // PVOID - EntryPoint: Field, // PVOID - SizeOfImage: Field, // ULONG - FullDllName: Field, // _UNICODE_STRING - BaseDllName: Field, // _UNICODE_STRING + InLoadOrderLinks: Field, // _LIST_ENTRY + DllBase: Field, // PVOID + EntryPoint: Field, // PVOID + SizeOfImage: Field, // ULONG + FullDllName: Field, // _UNICODE_STRING + BaseDllName: Field, // _UNICODE_STRING } struct _CLIENT_ID { @@ -128,7 +136,8 @@ offsets! { } struct _HANDLE_TABLE { - TableCode: Field, + NextHandleNeedingPool: Field, // ULONG + TableCode: Field, // ULONG_PTR } struct _OBJECT_ATTRIBUTES { @@ -143,15 +152,43 @@ offsets! { Body: Field, } + struct _OBJECT_DIRECTORY { + HashBuckets: Field, // struct _OBJECT_DIRECTORY_ENTRY* [37] + } + + struct _OBJECT_DIRECTORY_ENTRY { + ChainLink: Field, // struct _OBJECT_DIRECTORY_ENTRY* + Object: Field, // PVOID + HashValue: Field, // ULONG + } + struct _OBJECT_HEADER_NAME_INFO { - Directory: Field, - Name: Field, + Directory: Field, // _OBJECT_DIRECTORY* + Name: Field, // _UNICODE_STRING } struct _OBJECT_TYPE { Name: Field, } + struct _CM_KEY_BODY { + KeyControlBlock: Field, + } + + struct _CM_KEY_CONTROL_BLOCK { + ParentKcb: Field, // _CM_KEY_CONTROL_BLOCK* + NameBlock: Field, // _CM_NAME_CONTROL_BLOCK* + + RealKeyName: Field, // char* + FullKCBName: Field, // _UNICODE_STRING* + } + + struct _CM_NAME_CONTROL_BLOCK { + Compressed: Bitfield, + NameLength: Field, + Name: Field, + } + struct _MMSECTION_FLAGS { Image: Bitfield, File: Bitfield, @@ -179,23 +216,28 @@ offsets! { ApcState: Field, Teb: Field, Process: Field, + ThreadListEntry: Field, // _LIST_ENTRY } struct _ETHREAD { Cid: Field, + ThreadListEntry: Field, // _LIST_ENTRY } struct _KPROCESS { DirectoryTableBase: Field, UserDirectoryTableBase: Option, + ThreadListHead: Field, // _LIST_ENTRY } struct _EPROCESS { UniqueProcessId: Field, - ActiveProcessLinks: Field, + ActiveProcessLinks: Field, // _LIST_ENTRY + SessionProcessLinks: Field, // _LIST_ENTRY SectionBaseAddress: Field, InheritedFromUniqueProcessId: Field, Peb: Field, + Session: Field, // _MM_SESSION_SPACE* ObjectTable: Field, #[isr(alias = "Wow64Process")] WoW64Process: Field, @@ -203,6 +245,7 @@ offsets! { VadRoot: Field, // _MM_AVL_TABLE (Windows 7, contains BalancedRoot at offset 0) // _RTL_AVL_TREE (Windows 10+) VadHint: Option, // PVOID (Windows 10+, _MM_AVL_TABLE.NodeHint on Windows 7) + ThreadListHead: Field, // _LIST_ENTRY } struct _PEB { @@ -227,6 +270,11 @@ offsets! { DosPath: Field, // _UNICODE_STRING } + struct _MM_SESSION_SPACE { + SessionId: Field, // ULONG + ProcessList: Field, // _LIST_ENTRY + } + struct _MMPFN { ReferenceCount: Field, // USHORT @@ -335,6 +383,14 @@ pub struct Offsets { pub ext: Option, } +impl std::ops::Deref for Offsets { + type Target = OffsetsCommon; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + impl Offsets { /// Creates a new `Offsets` instance. pub fn new(profile: &Profile) -> Result { @@ -351,4 +407,9 @@ impl Offsets { Ok(Self { common, ext }) } + + /// Returns the extended offsets. + pub fn ext(&self) -> Option<&OffsetsExt> { + self.ext.as_ref() + } } diff --git a/crates/vmi-os-windows/src/offsets/v1.rs b/crates/vmi-os-windows/src/offsets/v1.rs index 4632ac8..d99f7c8 100644 --- a/crates/vmi-os-windows/src/offsets/v1.rs +++ b/crates/vmi-os-windows/src/offsets/v1.rs @@ -33,8 +33,9 @@ offsets! { } struct _MMADDRESS_NODE { - LeftChild: Field, - RightChild: Field, + Parent: Field, // _MMADDRESS_NODE* + LeftChild: Field, // _MMADDRESS_NODE* + RightChild: Field, // _MMADDRESS_NODE* } } diff --git a/crates/vmi-os-windows/src/offsets/v2.rs b/crates/vmi-os-windows/src/offsets/v2.rs index 32d3dea..e98d234 100644 --- a/crates/vmi-os-windows/src/offsets/v2.rs +++ b/crates/vmi-os-windows/src/offsets/v2.rs @@ -32,6 +32,7 @@ offsets! { struct _RTL_BALANCED_NODE { Left: Field, // _RTL_BALANCED_NODE* Right: Field, // _RTL_BALANCED_NODE* + ParentValue: Field, // ULONG_PTR } struct _MMVAD_FLAGS1 { diff --git a/crates/vmi-os-windows/src/pe/codeview.rs b/crates/vmi-os-windows/src/pe/codeview.rs deleted file mode 100644 index fa6fc1c..0000000 --- a/crates/vmi-os-windows/src/pe/codeview.rs +++ /dev/null @@ -1,166 +0,0 @@ -pub use isr_dl_pdb::CodeView; // re-export the CodeView struct from the isr-dl-pdb crate -use object::{ - endian::LittleEndian as LE, - pe::{ImageDebugDirectory, IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DIRECTORY_ENTRY_DEBUG}, - pod::slice_from_all_bytes, - read::pe::ImageNtHeaders, -}; -use vmi_core::{AddressContext, Architecture as _, VmiCore, VmiDriver, VmiError}; -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; - -use super::PeLite; - -const CV_SIGNATURE_RSDS: u32 = 0x53445352; // 'RSDS' - -#[repr(C)] -#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)] -struct CvInfoPdb70 { - signature: u32, - guid: [u8; 16], - age: u32, - // pdb_file_name: [u8; ???], -} - -pub fn codeview_from_pe( - vmi: &VmiCore, - ctx: impl Into, - pe: &PeLite, -) -> Result, VmiError> -where - Driver: VmiDriver, - Pe: ImageNtHeaders, -{ - let ctx = ctx.into(); - - // - // Check for the debug directory. - // Note that we arbitrarily limit the size of the debug directory - // to 4kb. - // - - let data_dir = match pe.data_directories.get(IMAGE_DIRECTORY_ENTRY_DEBUG) { - Some(data_dir) => data_dir, - None => { - tracing::warn!("No PE debug dir"); - return Ok(None); - } - }; - - if data_dir.virtual_address.get(LE) == 0 { - tracing::warn!("Invalid PE debug dir address"); - return Ok(None); - } - - if data_dir.size.get(LE) == 0 { - tracing::warn!("Invalid PE debug dir size"); - return Ok(None); - } - - if data_dir.size.get(LE) > Driver::Architecture::PAGE_SIZE as u32 { - tracing::warn!("PE debug dir size too large"); - return Ok(None); - } - - // - // Read the debug directory. - // - - let data_dir_address = ctx.va + data_dir.virtual_address.get(LE) as u64; - let data_dir_size = data_dir.size.get(LE) as usize; - - let mut debug_data = vec![0u8; data_dir_size]; - vmi.read((data_dir_address, ctx.root), &mut debug_data)?; - - let debug_dirs = match slice_from_all_bytes::(&debug_data) { - Ok(debug_dirs) => debug_dirs, - Err(_) => { - tracing::warn!("Invalid PE debug dir size"); - return Ok(None); - } - }; - - // - // Find the CodeView debug info. - // - - for debug_dir in debug_dirs { - if debug_dir.typ.get(LE) != IMAGE_DEBUG_TYPE_CODEVIEW { - continue; - } - - if debug_dir.address_of_raw_data.get(LE) == 0 { - tracing::warn!("Invalid CodeView Info address"); - continue; - } - - if debug_dir.size_of_data.get(LE) < size_of::() as u32 { - tracing::warn!("Invalid CodeView Info size"); - continue; - } - - // - // Read the CodeView debug info. - // - - let info_address = ctx.va + debug_dir.address_of_raw_data.get(LE) as u64; - let info_size = debug_dir.size_of_data.get(LE) as usize; - - let mut info_data = vec![0u8; info_size]; - vmi.read((info_address, ctx.root), &mut info_data)?; - - // - // Parse the CodeView debug info. - // Note that the path is located after the `CvInfoPdb70` struct. - // - - let (info, pdb_path) = info_data.split_at(size_of::()); - - let info = match CvInfoPdb70::ref_from_bytes(info) { - Ok(info) => info, - Err(err) => { - tracing::warn!(?err, "Invalid CodeView Info address"); - continue; - } - }; - - if info.signature != CV_SIGNATURE_RSDS { - tracing::warn!("Invalid CodeView signature"); - continue; - } - - // - // Parse the CodeView path. - // Note that the path is supposed to be null-terminated, - // so we need to trim it. - // - - let path = String::from_utf8_lossy(pdb_path) - .trim_end_matches('\0') - .to_string(); - - let guid0 = u32::from_le_bytes(info.guid[0..4].try_into().unwrap()); - let guid1 = u16::from_le_bytes(info.guid[4..6].try_into().unwrap()); - let guid2 = u16::from_le_bytes(info.guid[6..8].try_into().unwrap()); - let guid3 = &info.guid[8..16]; - - let guid = format!( - "{:08x}{:04x}{:04x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:01x}", - guid0, - guid1, - guid2, - guid3[0], - guid3[1], - guid3[2], - guid3[3], - guid3[4], - guid3[5], - guid3[6], - guid3[7], - info.age & 0xf, - ); - - return Ok(Some(CodeView { path, guid })); - } - - Ok(None) -} diff --git a/crates/vmi-os-windows/src/pe/mod.rs b/crates/vmi-os-windows/src/pe/mod.rs index 6e65a9a..81f9c82 100644 --- a/crates/vmi-os-windows/src/pe/mod.rs +++ b/crates/vmi-os-windows/src/pe/mod.rs @@ -1,84 +1,447 @@ -pub(super) mod codeview; +//! Portable Executable (PE) module. +//! +//! This module provides high-level abstractions for parsing and analyzing +//! **Portable Executable (PE)** files, which are the standard executable +//! format for Windows operating systems. + mod error; +pub use isr_dl_pdb::CodeView; // re-export the CodeView struct from the isr-dl-pdb crate +pub use object::pe::{ImageDataDirectory, ImageDebugDirectory, ImageDosHeader, ImageFileHeader}; use object::{ endian::LittleEndian as LE, pe::{ - ImageDataDirectory, ImageDosHeader, ImageNtHeaders32, ImageNtHeaders64, - IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE, + ImageNtHeaders32 as OImageNtHeaders32, ImageNtHeaders64 as OImageNtHeaders64, + ImageOptionalHeader32 as OImageOptionalHeader32, + ImageOptionalHeader64 as OImageOptionalHeader64, IMAGE_DEBUG_TYPE_CODEVIEW, + IMAGE_DOS_SIGNATURE, IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC, + IMAGE_NT_SIGNATURE, IMAGE_NUMBEROF_DIRECTORY_ENTRIES, }, read::{ - pe::{Export, ExportTable, ImageNtHeaders, ImageOptionalHeader}, + pe::{ + optional_header_magic, Export, ExportTable, ImageNtHeaders as OImageNtHeaders, + ImageOptionalHeader as _, + }, ReadRef as _, }, + slice_from_all_bytes, }; +use vmi_core::{os::VmiOsImage, Architecture, VmiDriver, VmiError}; +use zerocopy::{FromBytes, Immutable, KnownLayout}; + +pub use self::error::PeError; +use crate::{ArchAdapter, WindowsImage}; + +/// Portable Executable (PE) NT Headers. +/// +/// Represents the **NT Headers** in a **PE file**, which contain crucial +/// metadata for Windows executables and DLLs. +pub struct ImageNtHeaders { + /// The PE signature. + signature: u32, + + /// The file header. + file_header: ImageFileHeader, -pub use self::{codeview::CodeView, error::PeError}; + /// The optional header. + optional_header: ImageOptionalHeader, +} + +impl ImageNtHeaders { + /// Returns the PE signature. + pub fn signature(&self) -> u32 { + self.signature + } + + /// Return the file header. + pub fn file_header(&self) -> &ImageFileHeader { + &self.file_header + } + + /// Return the optional header. + pub fn optional_header(&self) -> &ImageOptionalHeader { + &self.optional_header + } +} + +impl From for ImageNtHeaders { + fn from(nt_headers: OImageNtHeaders32) -> Self { + Self { + signature: nt_headers.signature.get(LE), + file_header: nt_headers.file_header, + optional_header: ImageOptionalHeader::ImageOptionalHeader32(nt_headers.optional_header), + } + } +} + +impl From for ImageNtHeaders { + fn from(nt_headers: OImageNtHeaders64) -> Self { + Self { + signature: nt_headers.signature.get(LE), + file_header: nt_headers.file_header, + optional_header: ImageOptionalHeader::ImageOptionalHeader64(nt_headers.optional_header), + } + } +} -/// A lightweight Portable Executable (PE) file parser. +/// Portable Executable (PE) Optional Header. /// -/// The generic parameter `Pe` determines whether this handles 32-bit or 64-bit -/// PE files through the [`ImageNtHeaders`] trait. -pub struct PeLite<'a, Pe> +/// Represents the **Optional Header** in a **PE file**, supporting both +/// **32-bit (PE32)** and **64-bit (PE32+)** formats. It contains essential +/// metadata for loading, memory layout, and execution. +pub enum ImageOptionalHeader { + /// 32-bit (PE32) optional header. + ImageOptionalHeader32(OImageOptionalHeader32), + + /// 64-bit (PE32+) optional header. + ImageOptionalHeader64(OImageOptionalHeader64), +} + +macro_rules! impl_image_optional_header_methods { + ( + $( + $( #[$meta:meta] )* + $name:ident: $ty:ty + ),+ $(,)? + ) => { + $( + $( #[$meta] )* + pub fn $name(&self) -> $ty { + match self { + Self::ImageOptionalHeader32(hdr32) => hdr32.$name(), + Self::ImageOptionalHeader64(hdr64) => hdr64.$name(), + } + } + )* + } +} + +impl ImageOptionalHeader { + /// Returns the size of the optional header. + pub fn size(&self) -> usize { + match self { + Self::ImageOptionalHeader32(_) => size_of::(), + Self::ImageOptionalHeader64(_) => size_of::(), + } + } + + impl_image_optional_header_methods!( + /// Returns the PE format identifier (PE32 or PE32+). + magic: u16, + + /// Returns the major linker version. + major_linker_version: u8, + + /// Returns the minor linker version. + minor_linker_version: u8, + + /// Returns the size of the executable code section. + size_of_code: u32, + + /// Returns the size of initialized data (data section). + size_of_initialized_data: u32, + + /// Returns the size of uninitialized data (bss section). + size_of_uninitialized_data: u32, + + /// Returns the relative virtual address (RVA) of the entry point. + /// + /// When the image is loaded, this is the actual memory address. + address_of_entry_point: u32, + + /// Returns the RVA of the start of the code section. + base_of_code: u32, + + /// Returns the RVA of the start of the data section (PE32 only). + base_of_data: Option, + + /// Returns the preferred load address in memory. + /// + /// The actual base address may change due to ASLR or conflicts. + image_base: u64, + + /// Returns the section alignment in memory. + /// This may change when the image is loaded by the OS. + section_alignment: u32, + + /// Returns the section alignment on disk. + file_alignment: u32, + + /// Returns the major operating system version required. + major_operating_system_version: u16, + + /// Returns the minor operating system version required. + minor_operating_system_version: u16, + + /// Returns the major image version. + major_image_version: u16, + + /// Returns the minor image version. + minor_image_version: u16, + + /// Returns the major subsystem version. + major_subsystem_version: u16, + + /// Returns the minor subsystem version. + minor_subsystem_version: u16, + + /// Returns the reserved Windows version value (should be zero). + win32_version_value: u32, + + /// Returns the total size of the image in memory, including headers. + size_of_image: u32, + + /// Returns the size of all headers (DOS header, PE header, section table). + size_of_headers: u32, + + /// Returns the checksum of the image. + check_sum: u32, + + /// Returns the subsystem type (e.g., GUI, console, driver). + subsystem: u16, + + /// Returns the DLL characteristics (e.g., ASLR, DEP, CFG). + dll_characteristics: u16, + + /// Returns the reserved stack size. + size_of_stack_reserve: u64, + + /// Returns the committed stack size. + size_of_stack_commit: u64, + + /// Returns the reserved heap size. + size_of_heap_reserve: u64, + + /// Returns the committed heap size. + size_of_heap_commit: u64, + + /// Returns the reserved loader flags (should be zero). + loader_flags: u32, + + /// Returns the number of data directories in the optional header. + number_of_rva_and_sizes: u32, + ); +} + +/// Portable Executable (PE) Export Directory. +/// +/// Represents the **Export Directory** in a **PE file**, which contains +/// metadata about exported functions, symbols, and addresses. This structure +/// abstracts the export table, storing raw data and directory information. +pub struct PeExportDirectory where - Pe: ImageNtHeaders, + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, { - /// The DOS header from the PE file. - pub dos_header: &'a ImageDosHeader, + _marker: std::marker::PhantomData, - /// The NT headers containing file header and optional header. - pub nt_headers: &'a Pe, + /// The export directory entry. + entry: ImageDataDirectory, - /// Array of data directory entries describing locations of various tables. - pub data_directories: &'a [ImageDataDirectory], + /// The export directory data. + data: Vec, } -/// Type alias for 32-bit PE files. -pub type PeLite32<'a> = PeLite<'a, ImageNtHeaders32>; +impl PeExportDirectory +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + /// Creates a new PE export directory parser. + pub(crate) fn new( + _image: &WindowsImage, + entry: ImageDataDirectory, + data: Vec, + ) -> Self { + Self { + entry, + data, + _marker: std::marker::PhantomData, + } + } + + /// Returns the list of exported symbols. + pub fn exports(&self) -> Result, PeError> { + let export_table = ExportTable::parse(&self.data, self.entry.virtual_address.get(LE)) + .map_err(|_| PeError::InvalidExportTable)?; -/// Type alias for 64-bit PE files. -pub type PeLite64<'a> = PeLite<'a, ImageNtHeaders64>; + export_table + .exports() + .map_err(|_| PeError::InvalidExportTable) + } +} + +/// Portable Executable (PE) Debug Directory. +/// +/// Represents the **Debug Directory** in a **PE file**, which contains +/// debugging information such as symbols, timestamps, and PDB references. +pub struct PeDebugDirectory<'pe, Driver> +where + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, +{ + image: &'pe WindowsImage<'pe, Driver>, + data: Vec, +} -impl<'a, Pe> PeLite<'a, Pe> +impl<'pe, Driver> PeDebugDirectory<'pe, Driver> where - Pe: ImageNtHeaders, + Driver: VmiDriver, + Driver::Architecture: Architecture + ArchAdapter, { - /// Parses a PE file from raw bytes. + /// Creates a new PE debug directory parser. + pub(crate) fn new( + image: &'pe WindowsImage<'pe, Driver>, + _entry: ImageDataDirectory, + data: Vec, + ) -> Self { + Self { image, data } + } + + /// Returns the list of debug directories. + pub fn debug_directories(&self) -> Option<&[ImageDebugDirectory]> { + slice_from_all_bytes::(&self.data).ok() + } + + /// Finds a debug directory by type. /// - /// This method performs all necessary validation of the PE file structure: - /// - Validates DOS header magic and alignment - /// - Validates NT headers signature and magic - /// - Validates optional header size and content - /// - Reads data directories - pub fn parse(data: &'a [u8]) -> Result { - // Parse the DOS header - let dos_header = data - .read_at::(0) - .map_err(|_| PeError::InvalidDosHeaderSizeOrAlignment)?; + /// A debug directory type is represented by `IMAGE_DEBUG_TYPE_*` constants. + pub fn find_debug_directory(&self, typ: u32) -> Option<&ImageDebugDirectory> { + self.debug_directories()?.iter().find(|dir| { + dir.typ.get(LE) == typ + && dir.address_of_raw_data.get(LE) != 0 + && dir.size_of_data.get(LE) != 0 + }) + } - if dos_header.e_magic.get(LE) != IMAGE_DOS_SIGNATURE { - return Err(PeError::InvalidDosMagic); - } + /// Returns the CodeView debug information. + /// + /// The [`CodeView`] debug information is located in the debug directory + /// with type [`IMAGE_DEBUG_TYPE_CODEVIEW`]. + pub fn codeview(&self) -> Result, VmiError> { + const CV_SIGNATURE_RSDS: u32 = 0x53445352; // 'RSDS' - // Parse the NT headers - let mut offset = dos_header.nt_headers_offset() as u64; + #[repr(C)] + #[derive(Debug, FromBytes, Immutable, KnownLayout)] + struct CvInfoPdb70 { + signature: u32, + guid: [u8; 16], + age: u32, + // pdb_file_name: [u8], + } - let nt_headers = data - .read::(&mut offset) - .map_err(|_| PeError::InvalidNtHeadersSizeOrAlignment)?; + let directory = match self.find_debug_directory(IMAGE_DEBUG_TYPE_CODEVIEW) { + Some(directory) => directory, + None => return Ok(None), + }; - if nt_headers.signature() != IMAGE_NT_SIGNATURE { - return Err(PeError::InvalidPeMagic); + if directory.size_of_data.get(LE) < size_of::() as u32 { + tracing::warn!("Invalid CodeView Info size"); + return Ok(None); } - if !nt_headers.is_valid_optional_magic() { - return Err(PeError::InvalidPeOptionalHeaderMagic); + + // + // Read the CodeView debug info. + // + + let info_address = self.image.base_address() + directory.address_of_raw_data.get(LE) as u64; + let info_size = directory.size_of_data.get(LE) as usize; + + let mut info_data = vec![0u8; info_size]; + self.image.vmi.read(info_address, &mut info_data)?; + + // + // Parse the CodeView debug info. + // Note that the path is located after the `CvInfoPdb70` struct. + // + + let (info, pdb_file_name) = info_data.split_at(size_of::()); + + let info = match CvInfoPdb70::ref_from_bytes(info) { + Ok(info) => info, + Err(err) => { + tracing::warn!(?err, "Invalid CodeView Info address"); + return Ok(None); + } + }; + + if info.signature != CV_SIGNATURE_RSDS { + tracing::warn!("Invalid CodeView signature"); + return Ok(None); } - // Read the rest of the optional header, and then read the data directories from - // that. + // + // Parse the CodeView path. + // Note that the path is supposed to be null-terminated, + // so we need to trim it. + // + + let path = String::from_utf8_lossy(pdb_file_name) + .trim_end_matches('\0') + .to_string(); + + let guid0 = u32::from_le_bytes(info.guid[0..4].try_into().unwrap()); + let guid1 = u16::from_le_bytes(info.guid[4..6].try_into().unwrap()); + let guid2 = u16::from_le_bytes(info.guid[6..8].try_into().unwrap()); + let guid3 = &info.guid[8..16]; + + #[rustfmt::skip] + let guid = format!( + concat!( + "{:08x}{:04x}{:04x}", + "{:02x}{:02x}{:02x}{:02x}", + "{:02x}{:02x}{:02x}{:02x}", + "{:01x}" + ), + guid0, guid1, guid2, + guid3[0], guid3[1], guid3[2], guid3[3], + guid3[4], guid3[5], guid3[6], guid3[7], + info.age & 0xf, + ); + + Ok(Some(CodeView { path, guid })) + } +} + +/// Portable Executable (PE) Representation (32-bit & 64-bit). +/// +/// A high-level representation of a **PE file**, supporting both +/// **32-bit (PE32) and 64-bit (PE32+)** formats. It encapsulates +/// the **DOS header, NT headers, and data directories**, providing +/// essential metadata for parsing and analyzing PE binaries. +pub struct Pe { + /// The DOS header. + dos_header: ImageDosHeader, + + /// The NT headers. + nt_headers: ImageNtHeaders, + + /// The data directories. + data_directories: [ImageDataDirectory; IMAGE_NUMBEROF_DIRECTORY_ENTRIES], +} + +impl Pe { + /// Creates a `Pe` instance by parsing raw PE data. + pub fn new(data: &[u8]) -> Result { + let magic = optional_header_magic(data).map_err(|_| PeError::InvalidPeMagic)?; + + let mut offset = 0; + let dos_header = Self::parse_image_dos_header(data, &mut offset)?; + let nt_headers = match magic { + IMAGE_NT_OPTIONAL_HDR32_MAGIC => { + Self::parse_image_nt_headers::(data, &mut offset)? + } + IMAGE_NT_OPTIONAL_HDR64_MAGIC => { + Self::parse_image_nt_headers::(data, &mut offset)? + } + _ => return Err(PeError::InvalidPeMagic), + }; + + // Read the rest of the optional header, and then read + // the data directories from that. let optional_data_size = u64::from(nt_headers.file_header().size_of_optional_header.get(LE)) - .checked_sub(size_of::() as u64) + .checked_sub(nt_headers.optional_header().size() as u64) .ok_or(PeError::PeOptionalHeaderSizeTooSmall)?; let optional_data = data @@ -95,23 +458,65 @@ where Ok(Self { dos_header, nt_headers, - data_directories, + data_directories: std::array::from_fn(|i| { + data_directories + .get(i) + .copied() + .unwrap_or(ImageDataDirectory { + virtual_address: Default::default(), + size: Default::default(), + }) + }), }) } - /// Extracts the export table from the PE file. - /// - /// Reads and parses the export directory from the PE file, returning a vector - /// of all exported symbols. The export table contains information about functions - /// and data that the PE file exposes for use by other modules. - pub fn exports(&self, data: &'a [u8]) -> Result, PeError> { - let entry = self.data_directories[IMAGE_DIRECTORY_ENTRY_EXPORT]; + /// Returns the DOS header. + pub fn dos_header(&self) -> &ImageDosHeader { + &self.dos_header + } - let export_table = ExportTable::parse(data, entry.virtual_address.get(LE)) - .map_err(|_| PeError::InvalidExportTable)?; + /// Returns the NT headers. + pub fn nt_headers(&self) -> &ImageNtHeaders { + &self.nt_headers + } - export_table - .exports() - .map_err(|_| PeError::InvalidExportTable) + /// Returns the data directories. + pub fn data_directories(&self) -> &[ImageDataDirectory] { + &self.data_directories + } + + /// Parses the DOS header. + fn parse_image_dos_header(data: &[u8], offset: &mut u64) -> Result { + let dos_header = data + .read_at::(0) + .map_err(|_| PeError::InvalidDosHeaderSizeOrAlignment)?; + + if dos_header.e_magic.get(LE) != IMAGE_DOS_SIGNATURE { + return Err(PeError::InvalidDosMagic); + } + + *offset = dos_header.nt_headers_offset() as u64; + + Ok(*dos_header) + } + + /// Parses the NT headers. + fn parse_image_nt_headers(data: &[u8], offset: &mut u64) -> Result + where + Pe: OImageNtHeaders + Into, + { + let nt_headers = data + .read::(offset) + .map_err(|_| PeError::InvalidNtHeadersSizeOrAlignment)?; + + if nt_headers.signature() != IMAGE_NT_SIGNATURE { + return Err(PeError::InvalidPeMagic); + } + + if !nt_headers.is_valid_optional_magic() { + return Err(PeError::InvalidPeOptionalHeaderMagic); + } + + Ok((*nt_headers).into()) } } diff --git a/crates/vmi-utils/Cargo.toml b/crates/vmi-utils/Cargo.toml index 54fb872..8043903 100644 --- a/crates/vmi-utils/Cargo.toml +++ b/crates/vmi-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vmi-utils" -version = "0.1.1" +version = "0.2.0" license = "MIT" authors = { workspace = true } edition = { workspace = true } diff --git a/crates/vmi-utils/src/bpm/mod.rs b/crates/vmi-utils/src/bpm/mod.rs index 82d454f..c895a1a 100644 --- a/crates/vmi-utils/src/bpm/mod.rs +++ b/crates/vmi-utils/src/bpm/mod.rs @@ -154,7 +154,7 @@ where Key: KeyType, Tag: TagType, { - #[allow(clippy::new_without_default)] + #[expect(clippy::new_without_default)] /// Creates a new breakpoint manager. pub fn new() -> Self { Self { @@ -195,7 +195,7 @@ where match vmi.translate_address(breakpoint.ctx) { Ok(pa) => self.insert_with_hint(vmi, breakpoint, Some(pa)), - Err(VmiError::PageFault(_)) => self.insert_with_hint(vmi, breakpoint, None), + Err(VmiError::Translation(_)) => self.insert_with_hint(vmi, breakpoint, None), Err(err) => Err(err), } } @@ -252,7 +252,7 @@ where match vmi.translate_address(breakpoint.ctx) { Ok(pa) => self.remove_with_hint(vmi, breakpoint, Some(pa)), - Err(VmiError::PageFault(_)) => self.remove_with_hint(vmi, breakpoint, None), + Err(VmiError::Translation(_)) => self.remove_with_hint(vmi, breakpoint, None), Err(err) => Err(err), } } diff --git a/crates/vmi-utils/src/bridge/mod.rs b/crates/vmi-utils/src/bridge/mod.rs index 4e09344..8d82f8d 100644 --- a/crates/vmi-utils/src/bridge/mod.rs +++ b/crates/vmi-utils/src/bridge/mod.rs @@ -149,7 +149,7 @@ where Os: VmiOs, B: BridgeDispatch, { - /// Create a new bridge. + /// Creates a new bridge. pub fn new(handlers: B) -> Self { Self { handlers, diff --git a/crates/vmi-utils/src/bridge/packet.rs b/crates/vmi-utils/src/bridge/packet.rs index 9f80244..6be4d07 100644 --- a/crates/vmi-utils/src/bridge/packet.rs +++ b/crates/vmi-utils/src/bridge/packet.rs @@ -11,7 +11,7 @@ pub struct BridgePacket { } impl BridgePacket { - /// Create a new packet with the given request and method. + /// Creates a new packet with the given request and method. pub fn new(magic: u32, request: u16, method: u16) -> Self { Self { magic, @@ -24,57 +24,57 @@ impl BridgePacket { } } - /// Set the first value of the packet. + /// Sets the first value of the packet. pub fn with_value1(self, value1: u64) -> Self { Self { value1, ..self } } - /// Set the second value of the packet. + /// Sets the second value of the packet. pub fn with_value2(self, value2: u64) -> Self { Self { value2, ..self } } - /// Set the third value of the packet. + /// Sets the third value of the packet. pub fn with_value3(self, value3: u64) -> Self { Self { value3, ..self } } - /// Set the fourth value of the packet. + /// Sets the fourth value of the packet. pub fn with_value4(self, value4: u64) -> Self { Self { value4, ..self } } - /// Get the magic number of the packet. + /// Returns the magic number of the packet. pub fn magic(&self) -> u32 { self.magic } - /// Get the request of the packet. + /// Returns the request of the packet. pub fn request(&self) -> u16 { self.request } - /// Get the method of the packet. + /// Returns the method of the packet. pub fn method(&self) -> u16 { self.method } - /// Get the first value of the packet. + /// Returns the first value of the packet. pub fn value1(&self) -> u64 { self.value1 } - /// Get the second value of the packet. + /// Returns the second value of the packet. pub fn value2(&self) -> u64 { self.value2 } - /// Get the third value of the packet. + /// Returns the third value of the packet. pub fn value3(&self) -> u64 { self.value3 } - /// Get the fourth value of the packet. + /// Returns the fourth value of the packet. pub fn value4(&self) -> u64 { self.value4 } diff --git a/crates/vmi-utils/src/bridge/response.rs b/crates/vmi-utils/src/bridge/response.rs index 443cb29..982d32c 100644 --- a/crates/vmi-utils/src/bridge/response.rs +++ b/crates/vmi-utils/src/bridge/response.rs @@ -21,7 +21,7 @@ impl Default for BridgeResponse { } impl BridgeResponse { - /// Create a new response with the given value. + /// Creates a new response with the given value. pub fn new(value1: u64) -> Self { Self { value1: Some(value1), @@ -32,37 +32,37 @@ impl BridgeResponse { } } - /// Get the first value of the response. + /// Returns the first value of the response. pub fn value1(&self) -> Option { self.value1 } - /// Get the second value of the response. + /// Returns the second value of the response. pub fn value2(&self) -> Option { self.value2 } - /// Get the third value of the response. + /// Returns the third value of the response. pub fn value3(&self) -> Option { self.value3 } - /// Get the fourth value of the response. + /// Returns the fourth value of the response. pub fn value4(&self) -> Option { self.value4 } - /// Get the result of the response. + /// Returns the result of the response. pub fn result(&self) -> Option<&T> { self.result.as_ref() } - /// Convert the response into a result. + /// Converts the response into a result. pub fn into_result(self) -> Option { self.result } - /// Set the first value of the response. + /// Sets the first value of the response. pub fn with_value1(self, value1: u64) -> Self { Self { value1: Some(value1), @@ -70,7 +70,7 @@ impl BridgeResponse { } } - /// Set the second value of the response. + /// Sets the second value of the response. pub fn with_value2(self, value2: u64) -> Self { Self { value2: Some(value2), @@ -78,7 +78,7 @@ impl BridgeResponse { } } - /// Set the third value of the response. + /// Sets the third value of the response. pub fn with_value3(self, value3: u64) -> Self { Self { value3: Some(value3), @@ -86,7 +86,7 @@ impl BridgeResponse { } } - /// Set the fourth value of the response. + /// Sets the fourth value of the response. pub fn with_value4(self, value4: u64) -> Self { Self { value4: Some(value4), @@ -94,7 +94,7 @@ impl BridgeResponse { } } - /// Set the result of the response. + /// Sets the result of the response. pub fn with_result(self, result: T) -> Self { Self { result: Some(result), diff --git a/crates/vmi-utils/src/injector/macros.rs b/crates/vmi-utils/src/injector/macros.rs index e608274..8eb1ce4 100644 --- a/crates/vmi-utils/src/injector/macros.rs +++ b/crates/vmi-utils/src/injector/macros.rs @@ -8,7 +8,10 @@ pub mod __private { pub use zerocopy::*; } - use vmi_core::{os::OsRegionKind, Va, VmiContext, VmiDriver, VmiError, VmiOs}; + use vmi_core::{ + os::{VmiOsImage as _, VmiOsMapped as _, VmiOsProcess, VmiOsRegion, VmiOsRegionKind}, + Va, VmiDriver, VmiError, VmiOs, VmiState, + }; use super::super::RecipeContext; use crate::injector::recipe::SymbolCache; @@ -65,13 +68,43 @@ pub mod __private { } } + /// Finds a first mapped region with the specified filename in the current + /// process. The filename is case-insensitive. Returns the region if found. + pub fn find_region<'a, Driver>( + process: &impl VmiOsProcess<'a, Driver>, + filename: &str, + ) -> Result>, VmiError> + where + Driver: VmiDriver, + { + for region in process.regions()? { + let region = region?; + + let mapped = match region.kind()? { + VmiOsRegionKind::MappedImage(mapped) => mapped, + _ => continue, + }; + + let path = match mapped.path() { + Ok(Some(path)) => path, + _ => continue, + }; + + if path.to_ascii_lowercase().ends_with(filename) { + return Ok(Some(region)); + } + } + + Ok(None) + } + /// Finds a first mapped region with the specified filename in the current /// process and retrieves the exported symbols from the image. The filename /// is case-insensitive. Returns map of exported symbols and their virtual /// addresses. #[tracing::instrument(skip(vmi))] pub fn exported_symbols( - vmi: &VmiContext<'_, Driver, Os>, + vmi: &VmiState<'_, Driver, Os>, filename: &str, ) -> Result, VmiError> where @@ -79,30 +112,18 @@ pub mod __private { Os: VmiOs, { let current_process = vmi.os().current_process()?; - let regions = vmi.os().process_regions(current_process)?; - - let image = match regions.iter().find(|region| { - let mapped = match ®ion.kind { - OsRegionKind::Mapped(mapped) => mapped, - _ => return false, - }; - - let path = match &mapped.path { - Ok(Some(path)) => path, - _ => return false, - }; - path.to_ascii_lowercase().ends_with(filename) - }) { + let region = match find_region(¤t_process, filename)? { Some(image) => image, None => return Ok(None), }; - let symbols = vmi.os().image_exported_symbols(image.start)?; + let image = vmi.os().image(region.start()?)?; + let symbols = image.exports()?; tracing::trace!( - va = %image.start, - kind = ?image.kind, + va = %region.start()?, + //kind = ?region.kind()?, symbols = symbols.len(), "image found" ); @@ -133,7 +154,7 @@ pub mod __private { /// - `vmi!()` - Access the VMI context /// - `registers!()` - Access the registers /// - `data!()` - Access recipe data -/// - `inj!()` - Inject and call functions +/// - `inject!()` - Inject and call functions /// - `copy_to_stack!()` - Copy data to the stack /// /// [`RecipeContext`]: super::RecipeContext @@ -211,7 +232,7 @@ macro_rules! _private_recipe { /// # Example /// /// ```compile_fail - /// inj! { + /// inject! { /// user32!MessageBoxA( /// 0, // hWnd /// data![text], // lpText @@ -221,7 +242,7 @@ macro_rules! _private_recipe { /// } /// ``` #[expect(unused_macros)] - macro_rules! inj { + macro_rules! inject { ($image:ident!$function:ident($d($d arg:expr),*)) => { $crate::_private_recipe!(@inject ctx, $image!$function($d($d arg),*)) }; @@ -244,7 +265,7 @@ macro_rules! _private_recipe { /// // Allocate a value on the stack to store the output parameter. /// data![bytes_written_ptr] = copy_to_stack!(0u64)?; /// - /// inj! { + /// inject! { /// kernel32!WriteFile( /// data![handle], // hFile /// data![content], // lpBuffer @@ -300,7 +321,7 @@ macro_rules! _private_recipe { // // The parent macro can be invoked as follows: // ``` - // inj! { + // inject! { // kernel32!VirtualAlloc( // 0, // lpAddress // 0x1000, // dwSize diff --git a/crates/vmi-utils/src/injector/os/windows.rs b/crates/vmi-utils/src/injector/os/windows.rs index 62c0479..39b7262 100644 --- a/crates/vmi-utils/src/injector/os/windows.rs +++ b/crates/vmi-utils/src/injector/os/windows.rs @@ -2,8 +2,9 @@ use isr_core::Profile; use isr_macros::{offsets, Field}; use vmi_arch_amd64::{Amd64, ControlRegister, EventMonitor, EventReason, Interrupt, Registers}; use vmi_core::{ - os::ProcessId, Architecture as _, Hex, MemoryAccess, Registers as _, Va, View, VmiContext, - VmiCore, VmiDriver, VmiError, VmiEventResponse, VmiHandler, + os::{ProcessId, VmiOsProcess, VmiOsThread}, + Architecture as _, Hex, MemoryAccess, Registers as _, Va, View, VmiContext, VmiCore, VmiDriver, + VmiError, VmiEventResponse, VmiHandler, VmiVa as _, }; use vmi_os_windows::{WindowsOs, WindowsOsExt as _}; @@ -12,7 +13,6 @@ use super::{ OsAdapter, }; use crate::bridge::{BridgeHandler, BridgePacket}; - // const INVALID_VA: Va = Va(0xffff_ffff_ffff_ffff); const INVALID_VIEW: View = View(0xffff); // const INVALID_TID: ThreadId = ThreadId(0xffff_ffff); @@ -182,7 +182,7 @@ where } } -#[allow(non_snake_case)] +#[expect(non_snake_case)] impl InjectorHandler, T, Bridge> where Driver: VmiDriver, @@ -389,7 +389,7 @@ where // Early exit if the current process is not the target process. // - let current_pid = vmi.os().current_process_id()?; + let current_pid = vmi.os().current_process()?.id()?; if current_pid != self.pid { return Ok(VmiEventResponse::default()); } @@ -400,14 +400,13 @@ where // trap frame of the current thread. // - let current_tid = vmi.os().current_thread_id()?; - - let KTHREAD_TrapFrame = self.offsets._KTHREAD.TrapFrame.offset; - let KTRAP_FRAME_Rsp = self.offsets._KTRAP_FRAME.Rsp.offset; - let KTRAP_FRAME_Rip = self.offsets._KTRAP_FRAME.Rip.offset; + let KTHREAD_TrapFrame = self.offsets._KTHREAD.TrapFrame.offset(); + let KTRAP_FRAME_Rsp = self.offsets._KTRAP_FRAME.Rsp.offset(); + let KTRAP_FRAME_Rip = self.offsets._KTRAP_FRAME.Rip.offset(); let current_thread = vmi.os().current_thread()?; - let current_thread = Va::from(current_thread); + let current_tid = current_thread.id()?; + let current_thread = current_thread.va(); let trap_frame = vmi.read_va(current_thread + KTHREAD_TrapFrame)?; let sp_va = vmi.read_va(trap_frame + KTRAP_FRAME_Rsp)?; @@ -512,7 +511,7 @@ where // Therefore this event might have been triggered by a different process. // - let current_pid = vmi.os().current_process_id()?; + let current_pid = vmi.os().current_process()?.id()?; if current_pid != self.pid { // Too noisy... // tracing::trace!( @@ -527,7 +526,7 @@ where // Early exit if the current thread is not the target thread. // - let current_tid = vmi.os().current_thread_id()?; + let current_tid = vmi.os().current_thread()?.id()?; if Some(current_tid) != self.tid { // Too noisy... // tracing::trace!( @@ -621,12 +620,12 @@ where match self.dispatch(&vmi) { Ok(response) => response, - Err(VmiError::PageFault(pfs)) => { + Err(VmiError::Translation(pfs)) => { let pf = pfs[0]; tracing::debug!(?pf, "injecting page fault"); - let _ = vmi - .inject_interrupt(vmi.event().vcpu_id(), Interrupt::page_fault(pf.address, 0)); + let _ = + vmi.inject_interrupt(vmi.event().vcpu_id(), Interrupt::page_fault(pf.va, 0)); VmiEventResponse::default() } diff --git a/crates/vmi-utils/src/ptm/arch/amd64.rs b/crates/vmi-utils/src/ptm/arch/amd64.rs index 16c218d..91d28e0 100644 --- a/crates/vmi-utils/src/ptm/arch/amd64.rs +++ b/crates/vmi-utils/src/ptm/arch/amd64.rs @@ -3,7 +3,8 @@ use std::collections::{HashMap, HashSet}; use vmi_arch_amd64::{Amd64, PageTableEntry, PageTableLevel}; use vmi_core::{ - AddressContext, Architecture as _, Gfn, MemoryAccess, MemoryAccessOptions, Pa, VcpuId, View, VmiCore, VmiDriver, VmiError + AddressContext, Architecture as _, Gfn, MemoryAccess, MemoryAccessOptions, Pa, VcpuId, View, + VmiCore, VmiDriver, VmiError, }; use super::{ @@ -110,7 +111,12 @@ where /// Monitor a guest frame number for write access. fn monitor(&mut self, vmi: &VmiCore, gfn: Gfn, view: View) -> Result<(), VmiError> { self.monitored_gfns.insert((view, gfn)); - vmi.set_memory_access_with_options(gfn, view, MemoryAccess::R, MemoryAccessOptions::IGNORE_PAGE_WALK_UPDATES) + vmi.set_memory_access_with_options( + gfn, + view, + MemoryAccess::R, + MemoryAccessOptions::IGNORE_PAGE_WALK_UPDATES, + ) } /// Unmonitor a guest frame number. @@ -124,10 +130,8 @@ where match vmi.set_memory_access(gfn, view, MemoryAccess::RW) { Ok(()) => Ok(()), Err(VmiError::ViewNotFound) => { - // // The view was not found. This can happen if the view was // destroyed before unmonitoring. - // tracing::debug!(%gfn, %view, "view not found"); Ok(()) } @@ -406,9 +410,9 @@ where if old_value.present() && new_value.present() && old_value.pfn() != new_value.pfn() { let vas = entry.vas.clone(); - return self - .page_change(vmi, entry_pa, vas, old_value, new_value, view) - .map(Some); + return Ok(Some( + self.page_change(vmi, entry_pa, vas, old_value, new_value, view)?, + )); } else if old_value.present() && !new_value.present() { if old_value.large() { @@ -420,7 +424,7 @@ where } else { let vas = entry.vas.clone(); - return self.page_out(vmi, entry_pa, vas, old_value, view).map(Some); + return Ok(Some(self.page_out(vmi, entry_pa, vas, old_value, view)?)); } } else if !old_value.present() && new_value.present() { @@ -433,7 +437,7 @@ where } else { let vas = entry.vas.clone(); - return self.page_in(vmi, entry_pa, vas, new_value, view).map(Some); + return Ok(Some(self.page_in(vmi, entry_pa, vas, new_value, view)?)); } } diff --git a/crates/vmi-utils/src/ptm/arch/mod.rs b/crates/vmi-utils/src/ptm/arch/mod.rs index 6b1d59b..234f1cf 100644 --- a/crates/vmi-utils/src/ptm/arch/mod.rs +++ b/crates/vmi-utils/src/ptm/arch/mod.rs @@ -15,7 +15,6 @@ where type Impl: PageTableMonitorArchAdapter; } -#[allow(missing_docs)] /// Adapter implementation trait for architecture-specific page table monitor implementations. pub trait PageTableMonitorArchAdapter where diff --git a/docs/vmi-core-os.md b/docs/vmi-core-os.md index 777de7e..f0fec36 100644 --- a/docs/vmi-core-os.md +++ b/docs/vmi-core-os.md @@ -5,23 +5,99 @@ virtual machines. It allows for high-level analysis and manipulation of guest operating systems, abstracting away many of the low-level details of different OS implementations. +## Overview + +This module is at the heart of OS introspection. It defines several traits that +allow users to implement OS-specific logic while offering a consistent interface +to: +- Enumerate and inspect processes and threads. +- Analyze memory regions, including both private and file-backed (mapped) regions. +- Inspect kernel modules and executable images. +- Safely read structured data from guest memory. + +Additionally, a dummy implementation ([`NoOS`]) is provided as a placeholder for +cases where an OS-specific implementation is not available or required. + ## Key Components -- [`VmiOs`]: The core trait for implementing OS-specific introspection - capabilities. -- [`OsProcess`]: A process within the guest OS. -- [`OsRegion`]: A memory region within a process. -- [`ProcessObject`]: An opaque handle to a process in the guest OS. -- [`ThreadObject`]: An opaque handle to a thread in the guest OS. +### Core OS Trait + +- **[`VmiOs`]** + This is the central trait for OS introspection. It defines associated types + for the following: + - **Process**: Represented via the [`VmiOsProcess`] trait. + - **Thread**: Represented via the [`VmiOsThread`] trait. + - **Executable Image**: Represented via the [`VmiOsImage`] trait. + - **Kernel Module**: Represented via the [`VmiOsModule`] trait. + - **Memory Region**: Represented via the [`VmiOsRegion`] trait. + - **Mapped Region**: Represented via the [`VmiOsMapped`] trait. + + In addition to these, it provides methods to retrieve critical OS-specific + information, such as the kernel image base, and whether Kernel Page Table + Isolation (KPTI) is enabled. + +### Process and Thread Introspection + +- **[`VmiOsProcess`]** + + Provides an interface for inspecting guest processes. It offers methods to + obtain the process ID, name, parent process ID, memory translation roots, + and memory regions. + +- **[`ProcessObject`] and [`ProcessId`]** + + Strong types that represent underlying OS process structures such as + `_EPROCESS` on Windows or `task_struct` on Linux. This design minimizes + mistakes by ensuring that process objects are used correctly within the API. + +- **[`VmiOsThread`]** + + Offers methods to inspect thread objects, including obtaining the + thread ID and an associated thread object. + +- **[`ThreadObject`] and [`ThreadId`]** + + Similar to process types, these are strong types representing underlying + OS thread structures (e.g., `_ETHREAD` on Windows). Their strong typing helps + prevent mix-ups with other addresses or identifiers. + +### Memory Region and Mapped Region Introspection + +- **[`VmiOsRegion`]** + + Defines the interface for memory region introspection. Methods include + obtaining the start and end addresses, memory protection details, and the + kind of region (private or mapped). + +- **[`VmiOsRegionKind`]** + + An enum that distinguishes between private and mapped memory regions. + +- **[`VmiOsMapped`]** + + Specializes in introspecting memory regions that are file-backed. It provides + a method to retrieve the backing file’s path. + +### Kernel Modules and Executable Images + +- **[`VmiOsModule`]** + + Offers an abstraction for kernel module introspection, providing methods to + access a module’s base address, size, and name. + +- **[`VmiOsImage`]** + + Defines methods for executable images (binaries, shared libraries) to retrieve + the base address, target architecture, and exported symbols. + +### Additional Components -## Usage +- **[`StructReader`]** -Implementations of `VmiOs` provide methods for introspecting various aspects -of the guest OS, such as enumerating processes, analyzing memory regions, -and extracting OS-specific information. + A utility for safely reading structured data (such as C structs) from guest + memory. -To use OS-aware introspection: +- **[`NoOS`]** -1. Implement the `VmiOs` trait for your specific guest OS. -2. Use the implemented methods to perform high-level analysis of the guest - OS. + A dummy implementation of the `VmiOs` trait for cases where no specific OS + introspection is provided. It serves as a placeholder or for testing purposes. diff --git a/docs/vmi-driver-kdmp.md b/docs/vmi-driver-kdmp.md new file mode 100644 index 0000000..20b5035 --- /dev/null +++ b/docs/vmi-driver-kdmp.md @@ -0,0 +1 @@ +VMI driver for Windows kernel dump. diff --git a/docs/vmi-driver-xen-core-dump.md b/docs/vmi-driver-xen-core-dump.md new file mode 100644 index 0000000..90b7fa0 --- /dev/null +++ b/docs/vmi-driver-xen-core-dump.md @@ -0,0 +1 @@ +VMI driver for Xen core dump. diff --git a/examples/README.md b/examples/README.md index 6631072..2206f8c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,6 +18,10 @@ capabilities, from basic operations to more complex scenarios. Illustrates the usage of the [`BreakpointManager`] and [`PageTableMonitor`] to set and manage breakpoints on Windows systems. +- **[`windows-dump.rs`]** + + Demonstrates how to use the VMI library to analyze a Windows kernel dump file. + - **[`windows-recipe-messagebox.rs`]** A simple example of code injection using a recipe to display a message @@ -31,3 +35,15 @@ capabilities, from basic operations to more complex scenarios. A more complex example showing how to write to a file in chunks and handle potential errors during injection. + + +[`BreakpointManager`]: https://docs.rs/vmi/latest/vmi/utils/bpm/struct.BreakpointManager.html +[`PageTableMonitor`]: https://docs.rs/vmi/latest/vmi/utils/ptm/struct.PageTableMonitor.html + +[`basic.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic.rs +[`basic-process-list.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic-process-list.rs +[`windows-breakpoint-manager.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-breakpoint-manager.rs +[`windows-dump.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-dump.rs +[`windows-recipe-messagebox.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-messagebox.rs +[`windows-recipe-writefile.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile.rs +[`windows-recipe-writefile-advanced.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile-advanced.rs diff --git a/examples/basic-process-list.rs b/examples/basic-process-list.rs index 7aa5be0..7f83704 100644 --- a/examples/basic-process-list.rs +++ b/examples/basic-process-list.rs @@ -1,7 +1,9 @@ use isr::cache::{IsrCache, JsonCodec}; use vmi::{ - arch::amd64::Amd64, driver::xen::VmiXenDriver, os::windows::WindowsOs, VcpuId, VmiCore, - VmiSession, + arch::amd64::Amd64, + driver::xen::VmiXenDriver, + os::{windows::WindowsOs, VmiOsProcess as _}, + VcpuId, VmiCore, VmiSession, }; use xen::XenStore; @@ -23,9 +25,20 @@ fn main() -> Result<(), Box> { // Try to find the kernel information. // This is necessary in order to load the profile. let kernel_info = { + // Pause the VM to get consistent state. let _pause_guard = core.pause_guard()?; + + // Get the register state for the first VCPU. let registers = core.registers(VcpuId(0))?; + // On AMD64 architecture, the kernel is usually found using the + // `MSR_LSTAR` register, which contains the address of the system call + // handler. This register is set by the operating system during boot + // and is left unchanged (unless some rootkits are involved). + // + // Therefore, we can take an arbitrary registers at any point in time + // (as long as the OS has booted and the page tables are set up) and + // use them to find the kernel. WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information") }; @@ -40,11 +53,25 @@ fn main() -> Result<(), Box> { let os = WindowsOs::>::new(&profile)?; let session = VmiSession::new(&core, &os); - // Get the list of processes and print them. + // Pause the VM again to get consistent state. let _pause_guard = session.pause_guard()?; + + // Create a new `VmiState` with the current register. let registers = session.registers(VcpuId(0))?; - let processes = session.os().processes(®isters)?; - println!("Processes: {processes:#?}"); + let vmi = session.with_registers(®isters); + + // Get the list of processes and print them. + for process in vmi.os().processes()? { + let process = process?; + + println!( + "{} [{}] {} (root @ {})", + process.object()?, + process.id()?, + process.name()?, + process.translation_root()? + ); + } Ok(()) } diff --git a/examples/common/mod.rs b/examples/common/mod.rs index e0bb094..beddc57 100644 --- a/examples/common/mod.rs +++ b/examples/common/mod.rs @@ -3,8 +3,10 @@ use isr::{ Profile, }; use vmi::{ - arch::amd64::Amd64, driver::xen::VmiXenDriver, os::windows::WindowsOs, VcpuId, VmiCore, - VmiSession, + arch::amd64::Amd64, + driver::xen::VmiXenDriver, + os::{windows::WindowsOs, VmiOsProcess as _}, + VcpuId, VmiCore, VmiDriver, VmiError, VmiOs, VmiSession, VmiState, }; use xen::XenStore; @@ -39,9 +41,20 @@ pub fn create_vmi_session() -> Result< // Try to find the kernel information. // This is necessary in order to load the profile. let kernel_info = { + // Pause the VCPU to get consistent state. let _pause_guard = core.pause_guard()?; + + // Get the register state for the first VCPU. let registers = core.registers(VcpuId(0))?; + // On AMD64 architecture, the kernel is usually found using the + // `MSR_LSTAR` register, which contains the address of the system call + // handler. This register is set by the operating system during boot + // and is left unchanged (unless some rootkits are involved). + // + // Therefore, we can take an arbitrary registers at any point in time + // (as long as the OS has booted and the page tables are set up) and + // use them to find the kernel. WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information") }; @@ -63,3 +76,22 @@ pub fn create_vmi_session() -> Result< Ok((VmiSession::new(core, os), profile)) } + +pub fn find_process<'a, Driver, Os>( + vmi: &VmiState<'a, Driver, Os>, + name: &str, +) -> Result>, VmiError> +where + Driver: VmiDriver, + Os: VmiOs, +{ + for process in vmi.os().processes()? { + let process = process?; + + if process.name()?.to_lowercase() == name { + return Ok(Some(process)); + } + } + + Ok(None) +} diff --git a/examples/windows-breakpoint-manager.rs b/examples/windows-breakpoint-manager.rs index 7f98158..9676dc5 100644 --- a/examples/windows-breakpoint-manager.rs +++ b/examples/windows-breakpoint-manager.rs @@ -12,15 +12,15 @@ use vmi::{ arch::amd64::{Amd64, EventMonitor, EventReason, ExceptionVector, Interrupt}, driver::xen::VmiXenDriver, os::{ - windows::{WindowsObjectType, WindowsOs, WindowsOsExt as _}, - ProcessObject, + windows::{WindowsOs, WindowsOsExt as _}, + ProcessObject, VmiOsProcess as _, }, utils::{ bpm::{Breakpoint, BreakpointController, BreakpointManager}, ptm::{PageTableMonitor, PageTableMonitorEvent}, }, - MemoryAccess, Va, VcpuId, View, VmiContext, VmiCore, VmiDriver, VmiError, VmiEventResponse, - VmiHandler, VmiSession, + Hex, MemoryAccess, Va, VcpuId, View, VmiContext, VmiCore, VmiDriver, VmiError, + VmiEventResponse, VmiHandler, VmiSession, }; use xen::XenStore; @@ -33,7 +33,7 @@ symbols! { PspInsertProcess: u64, MmCleanProcessAddressSpace: u64, - // symbols! macro also accepts an Option as a value, + // `symbols!` macro also accepts an `Option` as a value, // where `None` means that the symbol is not present in the profile. // MiInsertVad: Option, // MiInsertPrivateVad: Option, @@ -55,17 +55,19 @@ where ptm: PageTableMonitor, } -#[allow(non_snake_case)] +#[expect(non_snake_case)] impl Monitor where Driver: VmiDriver, { pub fn new( - vmi: &VmiSession>, + session: &VmiSession>, profile: &Profile, terminate_flag: Arc, ) -> Result { - // Get the base address of the kernel. + // Capture the current state of the VCPU and get the base address of + // the kernel. + // // This base address is essential to correctly offset monitored // functions. // @@ -76,11 +78,13 @@ where // handler. This register is set by the operating system during // boot and is left unchanged (unless some rootkits are involved). // - // Therefore, what we are doing here is querying the registers - // of the first VCPU and trying to find the kernel image base - // address using them. - let registers = vmi.registers(VcpuId(0))?; - let kernel_image_base = vmi.os().kernel_image_base(®isters)?; + // Therefore, we can take an arbitrary registers at any point + // in time (as long as the OS has booted and the page tables are + // set up) and use them to find the kernel image base. + let registers = session.registers(VcpuId(0))?; + let vmi = session.with_registers(®isters); + + let kernel_image_base = vmi.os().kernel_image_base()?; tracing::info!(%kernel_image_base); // Get the system process. @@ -89,17 +93,15 @@ where // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`. // To monitor page table entries, we need to locate the translation root // of this process. - let system_process = vmi.os().system_process(®isters)?; - tracing::info!(%system_process); + let system_process = vmi.os().system_process()?; + tracing::info!(system_process = %system_process.object()?); // Get the translation root of the system process. // This is effectively "the CR3 of the kernel". // // The translation root is the root of the page table hierarchy (also // known as the Directory Table Base or PML4). - let root = vmi - .os() - .process_translation_root(®isters, system_process)?; + let root = system_process.translation_root()?; tracing::info!(%root); // Load the symbols from the profile. @@ -186,8 +188,8 @@ where let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view) .global() .with_tag("NtCreateFile"); - bpm.insert(vmi, bp_NtCreateFile)?; - ptm.monitor(vmi, cx_NtCreateFile, view, "NtCreateFile")?; + bpm.insert(&vmi, bp_NtCreateFile)?; + ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?; tracing::info!(%va_NtCreateFile); // Insert breakpoint for the `NtWriteFile` function. @@ -196,8 +198,8 @@ where let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view) .global() .with_tag("NtWriteFile"); - bpm.insert(vmi, bp_NtWriteFile)?; - ptm.monitor(vmi, cx_NtWriteFile, view, "NtWriteFile")?; + bpm.insert(&vmi, bp_NtWriteFile)?; + ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?; tracing::info!(%va_NtWriteFile); // Insert breakpoint for the `PspInsertProcess` function. @@ -206,8 +208,8 @@ where let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view) .global() .with_tag("PspInsertProcess"); - bpm.insert(vmi, bp_PspInsertProcess)?; - ptm.monitor(vmi, cx_PspInsertProcess, view, "PspInsertProcess")?; + bpm.insert(&vmi, bp_PspInsertProcess)?; + ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?; // Insert breakpoint for the `MmCleanProcessAddressSpace` function. let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace; @@ -215,9 +217,9 @@ where let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view) .global() .with_tag("MmCleanProcessAddressSpace"); - bpm.insert(vmi, bp_MmCleanProcessAddressSpace)?; + bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?; ptm.monitor( - vmi, + &vmi, cx_MmCleanProcessAddressSpace, view, "MmCleanProcessAddressSpace", @@ -299,8 +301,8 @@ where }; let process = vmi.os().current_process()?; - let process_id = vmi.os().process_id(process)?; - let process_name = vmi.os().process_filename(process)?; + let process_id = process.id()?; + let process_name = process.name()?; tracing::Span::current() .record("pid", process_id.0) .record("process", process_name); @@ -364,19 +366,16 @@ where let ObjectAttributes = Va(vmi.os().function_argument(2)?); - let process = vmi.os().current_process()?; - let path = match vmi - .os() - .object_attributes_to_object_name(process, ObjectAttributes)? - { - Some(path) => path, + let object_attributes = vmi.os().object_attributes(ObjectAttributes)?; + let object_name = match object_attributes.object_name()? { + Some(object_name) => object_name, None => { tracing::warn!(%ObjectAttributes, "No object name found"); return Ok(()); } }; - tracing::info!(%path); + tracing::info!(%object_name); Ok(()) } @@ -403,22 +402,36 @@ where let FileHandle = vmi.os().function_argument(0)?; - let process = vmi.os().current_process()?; + let handle_table_entry = match vmi + .os() + .current_process()? + .handle_table()? + .lookup(FileHandle)? + { + Some(handle_table_entry) => handle_table_entry, + None => { + tracing::warn!(FileHandle = %Hex(FileHandle), "No handle table entry found"); + return Ok(()); + } + }; - let object = match vmi.os().handle_to_object_address(process, FileHandle)? { + let object = match handle_table_entry.object()? { Some(object) => object, None => { - tracing::warn!("No object found for handle"); + tracing::warn!(FileHandle = %Hex(FileHandle), "No object found"); return Ok(()); } }; - if !matches!(vmi.os().object_type(object)?, Some(WindowsObjectType::File)) { - tracing::warn!("Not a file object"); - return Ok(()); - } + let file_object = match object.as_file()? { + Some(file_object) => file_object, + None => { + tracing::warn!(FileHandle = %Hex(FileHandle), "Not a file object"); + return Ok(()); + } + }; - let path = vmi.os().file_object_to_full_path(object)?; + let path = file_object.full_path()?; tracing::info!(%path); Ok(()) @@ -443,30 +456,27 @@ where let NewProcess = vmi.os().function_argument(0)?; let Parent = vmi.os().function_argument(1)?; - let process_object = ProcessObject(Va(NewProcess)); - let process_id = vmi.os().process_id(process_object)?; + let process = vmi.os().process(ProcessObject(Va(NewProcess)))?; + let process_id = process.id()?; - let parent_process_object = ProcessObject(Va(Parent)); - let parent_process_id = vmi.os().process_id(parent_process_object)?; + let parent_process = vmi.os().process(ProcessObject(Va(Parent)))?; + let parent_process_id = parent_process.id()?; // We rely heavily on the 2nd argument to be the parent process object. // If that ever changes, this assertion should catch it. // // So far it is verified that it works for Windows 7 up to Windows 11 // (23H2, build 22631). - debug_assert_eq!( - parent_process_id, - vmi.os().process_parent_process_id(process_object)? - ); + debug_assert_eq!(parent_process_id, process.parent_id()?); - let peb = vmi.os().process_peb(process_object)?; + let peb = process.peb()?.process_parameters()?; - let filename = vmi.os().process_filename(process_object)?; - let image_base = vmi.os().process_image_base(process_object)?; + let name = process.name()?; + let image_base = process.image_base()?; tracing::info!( %process_id, - filename, + name, %image_base, ?peb, ); @@ -488,13 +498,13 @@ where let Process = vmi.os().function_argument(0)?; - let process_object = ProcessObject(Va(Process)); - let process_id = vmi.os().process_id(process_object)?; + let process = vmi.os().process(ProcessObject(Va(Process)))?; + let process_id = process.id()?; - let filename = vmi.os().process_filename(process_object)?; - let image_base = vmi.os().process_image_base(process_object)?; + let name = process.name()?; + let image_base = process.image_base()?; - tracing::info!(%process_id, filename, %image_base); + tracing::info!(%process_id, name, %image_base); Ok(()) } @@ -515,7 +525,7 @@ where // a page fault error. In this case, we inject a page fault interrupt // to the guest. // - // Once the guest handles the page fault, it will try to retry the + // Once the guest handles the page fault, it will retry to execute the // instruction that caused the page fault. if let Err(VmiError::PageFault(pfs)) = result { tracing::warn!(?pfs, "Page fault, injecting"); diff --git a/examples/windows-dump.rs b/examples/windows-dump.rs new file mode 100644 index 0000000..3131329 --- /dev/null +++ b/examples/windows-dump.rs @@ -0,0 +1,441 @@ +//! This example demonstrates how to use the VMI library to analyze a Windows +//! kernel dump file. +//! +//! # Possible log output +//! +//! ```text +//! Kernel Modules: +//! ================================================= +//! Module @ 0xffffd486baa62b80 +//! Base Address: 0xfffff8016e80f000 +//! Size: 17068032 +//! Name: ntoskrnl.exe +//! Full Name: \SystemRoot\system32\ntoskrnl.exe +//! Module @ 0xffffd486baa77050 +//! Base Address: 0xfffff8016fa00000 +//! Size: 24576 +//! Name: hal.dll +//! Full Name: \SystemRoot\system32\hal.dll +//! Module @ 0xffffd486baa77200 +//! Base Address: 0xfffff8016fa10000 +//! Size: 45056 +//! Name: kdcom.dll +//! Full Name: \SystemRoot\system32\kd.dll +//! +//! ... +//! +//! Object Tree (root directory: 0xffffbe8b62c7ae10): +//! ================================================= +//! Mutant: \PendingRenameMutex (Object: 0xffffd486bb6d6bd0) +//! Directory: \ObjectTypes (Object: 0xffffbe8b62c900a0) +//! Type: \ObjectTypes\TmTm (Object: 0xffffd486bab8a220) +//! Type: \ObjectTypes\CpuPartition (Object: 0xffffd486baabdda0) +//! Type: \ObjectTypes\Desktop (Object: 0xffffd486baabc220) +//! +//! ... +//! +//! Process @ 0xffffd486be782080, PID: 1244 +//! Name: svchost.exe +//! Session: 0 +//! Threads: +//! Thread @ 0xffffd486be789080, TID: 1248 +//! Thread @ 0xffffd486be7f0080, TID: 1464 +//! Thread @ 0xffffd486be7ef080, TID: 1504 +//! Thread @ 0xffffd486be81f040, TID: 1548 +//! Thread @ 0xffffd486c0129080, TID: 4896 +//! Thread @ 0xffffd486bfc52080, TID: 4876 +//! Thread @ 0xffffd486bfcf5080, TID: 6832 +//! Thread @ 0xffffd486bf57b080, TID: 3920 +//! Thread @ 0xffffd486c0487080, TID: 6468 +//! Thread @ 0xffffd486c012d080, TID: 4312 +//! Regions: +//! Region @ 0xffffd486be7260d0: 0x000000007ffe0000-0x000000007ffe1000 MemoryAccess(R) Private +//! Region @ 0xffffd486be726260: 0x000000fb1a220000-0x000000fb1a2a0000 MemoryAccess(R | W) Private +//! ... +//! Region @ 0xffffd486be6f92f0: 0x00007ffa62cf0000-0x00007ffa62e9d000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\user32.dll +//! Region @ 0xffffd486be7a4a30: 0x00007ffa62ea0000-0x00007ffa62f46000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\sechost.dll +//! Region @ 0xffffd486be46e140: 0x00007ffa63190000-0x00007ffa63240000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\clbcatq.dll +//! Region @ 0xffffd486be6be530: 0x00007ffa632b0000-0x00007ffa633c7000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\rpcrt4.dll +//! Region @ 0xffffd486be6f87b0: 0x00007ffa63400000-0x00007ffa634a7000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\msvcrt.dll +//! Region @ 0xffffd486be6f60f0: 0x00007ffa63d50000-0x00007ffa63f67000 MemoryAccess(R | W | X) Mapped (Exe): \Windows\System32\ntdll.dll +//! PEB: +//! Current Directory: C:\Windows\system32\ +//! DLL Path: +//! Image Path Name: C:\Windows\system32\svchost.exe +//! Command Line: C:\Windows\system32\svchost.exe -k LocalServiceNetworkRestricted -p +//! Handles: +//! 0004: Object: ffffd486be74cc60 GrantedAccess: 001f0003 Entry: 0xffffbe8b6e74d010 +//! Type: Event, Path: +//! 0008: Object: ffffd486be74c9e0 GrantedAccess: 001f0003 Entry: 0xffffbe8b6e74d020 +//! Type: Event, Path: +//! 000c: Object: ffffbe8b6e620900 GrantedAccess: 00000009 Entry: 0xffffbe8b6e74d030 +//! Type: Key, Path: \REGISTRY\MACHINE\SOFTWARE\SOFTWARE\MICROSOFT\WINDOWS NT\CURRENTVERSION\IMAGE FILE EXECUTION OPTIONS +//! 0010: Object: ffffd486be76f960 GrantedAccess: 00000804 Entry: 0xffffbe8b6e74d040 +//! Type: EtwRegistration, Path: +//! +//! ... +//! ``` + +use isr::cache::{IsrCache, JsonCodec}; +use vmi::{ + arch::amd64::Amd64, + driver::kdmp::VmiKdmpDriver, + os::{ + windows::{WindowsDirectoryObject, WindowsOs, WindowsOsExt, WindowsProcess}, + VmiOsMapped as _, VmiOsModule as _, VmiOsProcess as _, VmiOsRegion as _, VmiOsRegionKind, + VmiOsThread as _, + }, + VcpuId, VmiCore, VmiError, VmiSession, VmiState, VmiVa as _, +}; + +type Arch = Amd64; +type Driver = VmiKdmpDriver; + +fn handle_error(err: VmiError) -> Result { + match err { + VmiError::Translation(pf) => Ok(format!("PF({pf:?})")), + _ => Err(err), + } +} + +// Enumerate processes in the system. +fn enumerate_kernel_modules(vmi: &VmiState>) -> Result<(), VmiError> { + for module in vmi.os().modules()? { + let module = module?; + + let module_va = module.va(); + let base_address = module.base_address()?; // `KLDR_DATA_TABLE_ENTRY.DllBase` + let size = module.size()?; // `KLDR_DATA_TABLE_ENTRY.SizeOfImage` pointer + let name = module.name()?; // `KLDR_DATA_TABLE_ENTRY.BaseDllName` + let full_name = match module.full_name() { + // `KLDR_DATA_TABLE_ENTRY.FullDllName` + Ok(full_name) => full_name, + Err(err) => handle_error(err)?, + }; + + println!("Module @ {module_va}"); + println!(" Base Address: {base_address}"); + println!(" Size: {size}"); + println!(" Name: {name}"); + println!(" Full Name: {full_name}"); + } + + Ok(()) +} + +// Enumerate entries in a `_OBJECT_DIRECTORY`. +fn enumerate_directory_object( + directory_object: &WindowsDirectoryObject, + level: usize, +) -> Result<(), VmiError> { + for object in directory_object.iter()? { + // Print the indentation. + for _ in 0..level { + print!(" "); + } + + // Retrieve the `_OBJECT_DIRECTORY_ENTRY.Object`. + let object = match object { + Ok(object) => object, + Err(err) => { + println!("{}", handle_error(err)?); + continue; + } + }; + + let object_va = object.va(); + + // Determine the object type. + let type_kind = match object.type_kind() { + Ok(Some(typ)) => format!("{typ:?}"), + Ok(None) => String::from(""), + Err(err) => handle_error(err)?, + }; + + print!("{type_kind}: "); + + // Retrieve the full name of the object. + let name = match object.full_path() { + Ok(Some(name)) => name, + Ok(None) => String::from(""), + Err(err) => handle_error(err)?, + }; + + println!("{name} (Object: {object_va})"); + + // If the entry is a directory, recursively enumerate it. + if let Ok(Some(next)) = object.as_directory() { + enumerate_directory_object(&next, level + 1)?; + } + } + + Ok(()) +} + +// Enumerate entries in a `_HANDLE_TABLE`. +fn enumerate_handle_table(process: &WindowsProcess) -> Result<(), VmiError> { + const OBJ_PROTECT_CLOSE: u32 = 0x00000001; + const OBJ_INHERIT: u32 = 0x00000002; + const OBJ_AUDIT_OBJECT_CLOSE: u32 = 0x00000004; + + static LABEL_PROTECTED: [&str; 2] = ["", " (Protected)"]; + static LABEL_INHERIT: [&str; 2] = ["", " (Inherit)"]; + static LABEL_AUDIT: [&str; 2] = ["", " (Audit)"]; + + // Get the handle table from `_EPROCESS.ObjectTable`. + let handle_table = match process.handle_table() { + Ok(handle_table) => handle_table, + Err(err) => { + tracing::error!(?err, "Failed to get handle table"); + return Ok(()); + } + }; + + let handle_table_iterator = match handle_table.iter() { + Ok(iterator) => iterator, + Err(err) => { + println!( + "Failed to get handle table iterator: {}", + handle_error(err)? + ); + return Ok(()); + } + }; + + // Iterate over `_HANDLE_TABLE_ENTRY` items. + for (handle, entry) in handle_table_iterator { + let attributes = match entry.attributes() { + Ok(attributes) => attributes, + Err(err) => { + println!("Failed to get attributes: {}", handle_error(err)?); + continue; + } + }; + + let granted_access = match entry.granted_access() { + Ok(granted_access) => granted_access, + Err(err) => { + println!("Failed to get granted access: {}", handle_error(err)?); + continue; + } + }; + + let object = match entry.object() { + Ok(Some(object)) => object, + Ok(None) => { + // [`WindowsHandleTable::iter`] should only return entries with + // valid objects, so this should not happen. + println!(""); + continue; + } + Err(err) => { + println!("Failed to get object: {}", handle_error(err)?); + continue; + } + }; + + let type_name = match object.type_name() { + Ok(type_name) => type_name, + Err(err) => handle_error(err)?, + }; + + let full_path = match object.full_path() { + Ok(Some(path)) => path, + Ok(None) => String::from(""), + Err(err) => handle_error(err)?, + }; + + println!( + " {:04x}: Object: {:x} GrantedAccess: {:08x}{}{}{} Entry: {}", + handle, + object.va().0, + granted_access, + LABEL_PROTECTED[((attributes & OBJ_PROTECT_CLOSE) != 0) as usize], + LABEL_INHERIT[((attributes & OBJ_INHERIT) != 0) as usize], + LABEL_AUDIT[((attributes & OBJ_AUDIT_OBJECT_CLOSE) != 0) as usize], + entry.va(), + ); + + println!(" Type: {type_name}, Path: {full_path}"); + } + + Ok(()) +} + +// Enumerate VADs in a process. +fn enumerate_regions(process: &WindowsProcess) -> Result<(), VmiError> { + for region in process.regions()? { + let region = region?; + + let region_va = region.va(); + let start = region.start()?; + let end = region.end()?; + let protection = region.protection()?; + let kind = region.kind()?; + + print!(" Region @ {region_va}: {start}-{end} {protection:?}"); + + match &kind { + VmiOsRegionKind::Private => println!(" Private"), + VmiOsRegionKind::MappedImage(mapped) => { + let path = match mapped.path() { + Ok(Some(path)) => path, + Ok(None) => String::from(""), + Err(err) => handle_error(err)?, + }; + + println!(" Mapped (Exe): {path}"); + } + VmiOsRegionKind::MappedData(mapped) => { + let path = match mapped.path() { + Ok(Some(path)) => path, + Ok(None) => String::from(""), + Err(err) => handle_error(err)?, + }; + + println!(" Mapped: {path}"); + } + } + } + + Ok(()) +} + +// Enumerate threads in a process. +fn enumerate_threads(process: &WindowsProcess) -> Result<(), VmiError> { + for thread in process.threads()? { + let thread = thread?; + + let tid = thread.id()?; + let object = thread.object()?; + + println!(" Thread @ {object}, TID: {tid}"); + } + + Ok(()) +} + +// Print process information in a `_PEB.ProcessParameters`. +fn print_process_parameters(process: &WindowsProcess) -> Result<(), VmiError> { + let parameters = match process.peb()?.process_parameters() { + Ok(parameters) => parameters, + Err(err) => { + println!("Failed to get process parameters: {}", handle_error(err)?); + return Ok(()); + } + }; + + let current_directory = match parameters.current_directory() { + Ok(current_directory) => current_directory, + Err(err) => handle_error(err)?, + }; + + let dll_path = match parameters.dll_path() { + Ok(dll_path) => dll_path, + Err(err) => handle_error(err)?, + }; + + let image_path_name = match parameters.image_path_name() { + Ok(image_path_name) => image_path_name, + Err(err) => handle_error(err)?, + }; + + let command_line = match parameters.command_line() { + Ok(command_line) => command_line, + Err(err) => handle_error(err)?, + }; + + println!(" Current Directory: {current_directory}"); + println!(" DLL Path: {dll_path}"); + println!(" Image Path Name: {image_path_name}"); + println!(" Command Line: {command_line}"); + + Ok(()) +} + +// Enumerate processes in the system. +fn enumerate_processes(vmi: &VmiState>) -> Result<(), VmiError> { + for process in vmi.os().processes()? { + let process = process?; + + let pid = process.id()?; // `_EPROCESS.UniqueProcessId` + let object = process.object()?; // `_EPROCESS` pointer + let name = process.name()?; // `_EPROCESS.ImageFileName` + let session = process.session()?; // `_EPROCESS.Session` + + println!("Process @ {object}, PID: {pid}"); + println!(" Name: {name}"); + if let Some(session) = session { + println!(" Session: {}", session.id()?); // `_MM_SESSION_SPACE.SessionId` + } + + println!(" Threads:"); + enumerate_threads(&process)?; + + println!(" Regions:"); + enumerate_regions(&process)?; + + println!(" PEB:"); + print_process_parameters(&process)?; + + println!(" Handles:"); + enumerate_handle_table(&process)?; + } + + Ok(()) +} + +fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_ansi(false) + .init(); + + // First argument is the path to the dump file. + let args = std::env::args().collect::>(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let dump_file = &args[1]; + + // Setup VMI. + let driver = Driver::new(dump_file)?; + let core = VmiCore::new(driver)?; + + let registers = core.registers(VcpuId(0))?; + + // Try to find the kernel information. + // This is necessary in order to load the profile. + let kernel_info = WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information"); + tracing::info!(?kernel_info, "Kernel information"); + + // Load the profile. + // The profile contains offsets to kernel functions and data structures. + let isr = IsrCache::::new("cache")?; + let entry = isr.entry_from_codeview(kernel_info.codeview)?; + let profile = entry.profile()?; + + // Create the VMI session. + tracing::info!("Creating VMI session"); + let os = WindowsOs::::with_kernel_base(&profile, kernel_info.base_address)?; + let session = VmiSession::new(&core, &os); + + let vmi = session.with_registers(®isters); + let root_directory = vmi.os().object_root_directory()?; + + println!("Kernel Modules:"); + println!("================================================="); + enumerate_kernel_modules(&vmi)?; + + println!("Object Tree (root directory: {}):", root_directory.va()); + println!("================================================="); + enumerate_directory_object(&root_directory, 0)?; + + println!("Processes:"); + println!("================================================="); + enumerate_processes(&vmi)?; + + Ok(()) +} diff --git a/examples/windows-recipe-messagebox.rs b/examples/windows-recipe-messagebox.rs index ce085d0..3239ccb 100644 --- a/examples/windows-recipe-messagebox.rs +++ b/examples/windows-recipe-messagebox.rs @@ -22,7 +22,7 @@ mod common; use vmi::{ arch::amd64::Amd64, - os::windows::WindowsOs, + os::{windows::WindowsOs, VmiOsProcess as _}, utils::injector::{recipe, InjectorHandler, Recipe}, VcpuId, VmiDriver, }; @@ -49,7 +49,7 @@ where recipe![ Recipe::<_, WindowsOs, _>::new(data), { - inj! { + inject! { user32!MessageBoxA( 0, // hWnd data![text], // lpText @@ -62,31 +62,39 @@ where } fn main() -> Result<(), Box> { - let (vmi, profile) = common::create_vmi_session()?; + let (session, profile) = common::create_vmi_session()?; - let processes = { - let _pause_guard = vmi.pause_guard()?; + let explorer_pid = { + // This block is used to drop the pause guard after the PID is found. + // If the `session.handle()` would be called with the VM paused, no + // events would be triggered. + let _pause_guard = session.pause_guard()?; - let registers = vmi.registers(VcpuId(0))?; - vmi.os().processes(®isters)? - }; + let registers = session.registers(VcpuId(0))?; + let vmi = session.with_registers(®isters); + + let explorer = match common::find_process(&vmi, "explorer.exe")? { + Some(explorer) => explorer, + None => { + tracing::error!("explorer.exe not found"); + return Ok(()); + } + }; - let explorer = processes - .iter() - .find(|process| process.name.to_lowercase() == "explorer.exe") - .expect("explorer.exe"); + tracing::info!( + pid = %explorer.id()?, + object = %explorer.object()?, + "found explorer.exe" + ); - tracing::info!( - pid = %explorer.id, - object = %explorer.object, - "found explorer.exe" - ); + explorer.id()? + }; - vmi.handle(|vmi| { + session.handle(|session| { InjectorHandler::new( - vmi, + session, &profile, - explorer.id, + explorer_pid, recipe_factory(MessageBox::new( "Hello, World!", "This is a message box from the VMI!", diff --git a/examples/windows-recipe-writefile-advanced.rs b/examples/windows-recipe-writefile-advanced.rs index 88f3cba..cc39ad5 100644 --- a/examples/windows-recipe-writefile-advanced.rs +++ b/examples/windows-recipe-writefile-advanced.rs @@ -52,7 +52,7 @@ mod common; use vmi::{ arch::amd64::Amd64, - os::windows::WindowsOs, + os::{windows::WindowsOs, VmiOsProcess as _}, utils::injector::{recipe, InjectorHandler, Recipe, RecipeControlFlow}, Hex, Va, VcpuId, VmiDriver, }; @@ -150,7 +150,7 @@ where const CREATE_ALWAYS: u64 = 2; const FILE_ATTRIBUTE_NORMAL: u64 = 0x80; - inj! { + inject! { kernel32!CreateFileA( &data![target_path], // lpFileName GENERIC_WRITE, // dwDesiredAccess @@ -197,7 +197,7 @@ where let chunk_size = usize::min(content.len(), data![chunk_size]); let chunk = content[..chunk_size].to_vec(); - inj! { + inject! { kernel32!WriteFile( data![handle], // hFile chunk, // lpBuffer @@ -246,7 +246,7 @@ where // Allocate a value on the stack to store the output parameter. data![bytes_written_ptr] = copy_to_stack!(0u64)?; - inj! { + inject! { kernel32!WriteFile( data![handle], // hFile chunk, // lpBuffer @@ -285,7 +285,7 @@ where "step 4: kernel32!CloseHandle()" ); - inj! { + inject! { kernel32!CloseHandle( data![handle] // hObject ) @@ -295,36 +295,44 @@ where } fn main() -> Result<(), Box> { - let (vmi, profile) = common::create_vmi_session()?; + let (session, profile) = common::create_vmi_session()?; + + let explorer_pid = { + // This block is used to drop the pause guard after the PID is found. + // If the `session.handle()` would be called with the VM paused, no + // events would be triggered. + let _pause_guard = session.pause_guard()?; + + let registers = session.registers(VcpuId(0))?; + let vmi = session.with_registers(®isters); + + let explorer = match common::find_process(&vmi, "explorer.exe")? { + Some(explorer) => explorer, + None => { + tracing::error!("explorer.exe not found"); + return Ok(()); + } + }; - let processes = { - let _pause_guard = vmi.pause_guard()?; + tracing::info!( + pid = %explorer.id()?, + object = %explorer.object()?, + "found explorer.exe" + ); - let registers = vmi.registers(VcpuId(0))?; - vmi.os().processes(®isters)? + explorer.id()? }; - let explorer = processes - .iter() - .find(|process| process.name.to_lowercase() == "explorer.exe") - .expect("explorer.exe"); - - tracing::info!( - pid = %explorer.id, - object = %explorer.object, - "found explorer.exe" - ); - let mut content = Vec::new(); for c in 'A'..='Z' { content.extend((0..2049).map(|_| c as u8).collect::>()); } - vmi.handle(|vmi| { + session.handle(|session| { InjectorHandler::new( - vmi, + session, &profile, - explorer.id, + explorer_pid, recipe_factory(GuestFile::new( "C:\\Users\\John\\Desktop\\test.txt", content, diff --git a/examples/windows-recipe-writefile.rs b/examples/windows-recipe-writefile.rs index 9f06a16..912c08e 100644 --- a/examples/windows-recipe-writefile.rs +++ b/examples/windows-recipe-writefile.rs @@ -27,7 +27,7 @@ mod common; use vmi::{ arch::amd64::Amd64, - os::windows::WindowsOs, + os::{windows::WindowsOs, VmiOsProcess as _}, utils::injector::{recipe, InjectorHandler, Recipe, RecipeControlFlow}, Hex, Va, VcpuId, VmiDriver, }; @@ -110,7 +110,7 @@ where const CREATE_ALWAYS: u64 = 2; const FILE_ATTRIBUTE_NORMAL: u64 = 0x80; - inj! { + inject! { kernel32!CreateFileA( &data![target_path], // lpFileName GENERIC_WRITE, // dwDesiredAccess @@ -152,7 +152,7 @@ where // Allocate a value on the stack to store the output parameter. data![bytes_written_ptr] = copy_to_stack!(0u64)?; - inj! { + inject! { kernel32!WriteFile( data![handle], // hFile data![content], // lpBuffer @@ -190,7 +190,7 @@ where "step 3: kernel32!CloseHandle()" ); - inj! { + inject! { kernel32!CloseHandle( data![handle] // hObject ) @@ -200,31 +200,39 @@ where } fn main() -> Result<(), Box> { - let (vmi, profile) = common::create_vmi_session()?; + let (session, profile) = common::create_vmi_session()?; + + let explorer_pid = { + // This block is used to drop the pause guard after the PID is found. + // If the `session.handle()` would be called with the VM paused, no + // events would be triggered. + let _pause_guard = session.pause_guard()?; + + let registers = session.registers(VcpuId(0))?; + let vmi = session.with_registers(®isters); + + let explorer = match common::find_process(&vmi, "explorer.exe")? { + Some(explorer) => explorer, + None => { + tracing::error!("explorer.exe not found"); + return Ok(()); + } + }; - let processes = { - let _pause_guard = vmi.pause_guard()?; + tracing::info!( + pid = %explorer.id()?, + object = %explorer.object()?, + "found explorer.exe" + ); - let registers = vmi.registers(VcpuId(0))?; - vmi.os().processes(®isters)? + explorer.id()? }; - let explorer = processes - .iter() - .find(|process| process.name.to_lowercase() == "explorer.exe") - .expect("explorer.exe"); - - tracing::info!( - pid = %explorer.id, - object = %explorer.object, - "found explorer.exe" - ); - - vmi.handle(|vmi| { + session.handle(|session| { InjectorHandler::new( - vmi, + session, &profile, - explorer.id, + explorer_pid, recipe_factory(GuestFile::new( "C:\\Users\\John\\Desktop\\test.txt", "Hello, World!".as_bytes(), diff --git a/src/lib.rs b/src/lib.rs index 4e907ff..00f565a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! - [Address Contexts](#address-contexts) //! - [Architecture](#architecture) //! - [Core Components](#core-components) -//! - [Relationship between `VmiCore`, `VmiSession`, and `VmiContext`](#relationship-between-vmicore-vmisession-and-vmicontext) +//! - [Relationship between `VmiCore`, `VmiSession`, `VmiState` and `VmiContext`](#relationship-between-vmicore-vmisession-vmistate-and-vmicontext) //! - [OS-Specific Operations](#os-specific-operations) //! - [Implicit vs. Explicit Registers](#implicit-vs-explicit-registers) //! - [Event Handling](#event-handling) @@ -112,7 +112,7 @@ //! //! ```toml //! [dependencies] -//! vmi = "0.1" +//! vmi = "0.2" //! ``` //! //! Basic usage example: @@ -122,7 +122,7 @@ //! use vmi::{ //! arch::amd64::Amd64, //! driver::xen::VmiXenDriver, -//! os::windows::WindowsOs, +//! os::{windows::WindowsOs, VmiOsProcess as _}, //! VcpuId, VmiCore, VmiSession, //! }; //! use xen::XenDomainId; @@ -135,9 +135,20 @@ //! // Try to find the kernel information. //! // This is necessary in order to load the profile. //! let kernel_info = { +//! // Pause the VCPU to get consistent state. //! let _pause_guard = core.pause_guard()?; +//! +//! // Get the register state for the first VCPU. //! let registers = core.registers(VcpuId(0))?; //! +//! // On AMD64 architecture, the kernel is usually found using the +//! // `MSR_LSTAR` register, which contains the address of the system call +//! // handler. This register is set by the operating system during boot +//! // and is left unchanged (unless some rootkits are involved). +//! // +//! // Therefore, we can take an arbitrary registers at any point in time +//! // (as long as the OS has booted and the page tables are set up) and +//! // use them to find the kernel. //! WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information") //! }; //! @@ -148,14 +159,29 @@ //! let profile = entry.profile()?; //! //! // Create the VMI session. +//! tracing::info!("Creating VMI session"); //! let os = WindowsOs::>::new(&profile)?; //! let session = VmiSession::new(&core, &os); //! -//! // Get the list of processes and print them. +//! // Pause the VM again to get consistent state. //! let _pause_guard = session.pause_guard()?; +//! +//! // Create a new `VmiState` with the current register. //! let registers = session.registers(VcpuId(0))?; -//! let processes = session.os().processes(®isters)?; -//! println!("Processes: {processes:#?}"); +//! let vmi = session.with_registers(®isters); +//! +//! // Get the list of processes and print them. +//! for process in vmi.os().processes()? { +//! let process = process?; +//! +//! println!( +//! "{} [{}] {} (root @ {})", +//! process.object()?, +//! process.id()?, +//! process.name()?, +//! process.translation_root()? +//! ); +//! } //! //! Ok(()) //! } @@ -165,11 +191,11 @@ //! //! But first, you need to install the prerequisites. //! -//! The framework has been tested on Ubuntu 22.04 and Xen 4.19. -//! Note that Xen 4.19 is the minimum version required to use the +//! The framework has been tested on Ubuntu 22.04 and Xen 4.20. +//! Note that Xen 4.20 is the minimum version required to use the //! framework, and it is the current version (at the time of writing). //! -//! Unfortunately, Xen 4.19 is not available in the official Ubuntu +//! Unfortunately, Xen 4.20 is not available in the official Ubuntu //! repositories, so it must be built from source. //! //! This guide assumes you have a fresh Ubuntu 22.04 installation. @@ -196,6 +222,10 @@ //! Illustrates the usage of the [`BreakpointManager`] and //! [`PageTableMonitor`] to set and manage breakpoints on Windows systems. //! +//! - **[`windows-dump.rs`]** +//! +//! Demonstrates how to use the VMI library to analyze a Windows kernel dump file. +//! //! - **[`windows-recipe-messagebox.rs`]** //! //! A simple example of code injection using a recipe to display a message @@ -340,34 +370,41 @@ //! to provide OS-aware operations. This enables high-level introspection //! tasks, but - like `VmiCore` - `VmiSession` does not store register state. //! -//! - [`VmiContext`]: Represents a point-in-time state of the virtual CPU -//! during event handling. Unlike `VmiCore` (and `VmiSession`), `VmiContext` -//! *does* hold the register state at the time of the event, simplifying -//! event handler logic. +//! - [`VmiState`]: Represents a state of the virtual machine at a given moment, +//! combining [`VmiSession`] with [`Architecture::Registers`]. +//! This allows for consistent access to memory, registers, and OS-level +//! abstractions without requiring explicit register state management for +//! every operation. //! -//! It provides access to both `VmiCore` and `VmiOs` functionality within -//! a specific [`VmiEvent`]. +//! - [`VmiContext`]: Represents a point-in-time state of the virtual CPU +//! during event handling, combining [`VmiState`] with a [`VmiEvent`]. //! //! - [`VmiError`]: Represents errors that can occur during VMI operations, //! including translation faults ([`PageFault`]). //! -//! ### Relationship between `VmiCore`, `VmiSession`, and `VmiContext` +//! ### Relationship between `VmiCore`, `VmiSession`, `VmiState` and `VmiContext` //! //! Each of these structures can be implicitly dereferenced down the hierarchy. -//! This means that `VmiContext` implements [`Deref`] to `VmiSession`, -//! which in turn implements `Deref` to `VmiCore`. +//! This means that: +//! +//! - `VmiContext` implements [`Deref`] to `VmiState` +//! - which in turn implements `Deref` to `VmiSession` +//! - which in turn implements `Deref` to `VmiCore`. //! //! This design enables convenient access to lower-level functionality: //! -//! - Access `VmiCore` methods directly from a `VmiSession` or `VmiContext` -//! without explicit dereferencing. +//! - Access `VmiCore` methods directly from a `VmiSession`, `VmiState` or +//! `VmiContext` without explicit dereferencing. //! -//! - Pass a `&VmiContext` to functions expecting a `&VmiSession` +//! - Pass a `&VmiContext` to functions expecting a `&VmiState`, `&VmiSession` //! or `&VmiCore`. //! //! #### OS-Specific Operations //! -//! Both `VmiSession` and `VmiContext` provide access to OS-specific +//! > *Consult the [`os`] module documentation for more information +//! > and examples.* +//! +//! Both `VmiState` and `VmiContext` provide access to OS-specific //! functionality through the [`os()`] method. This method returns a structure //! implementing the [`VmiOs`] trait methods, as well as any additional //! OS-specific operations. @@ -379,9 +416,9 @@ //! for address translation or OS-specific operations) must be explicitly //! provided with the register state. //! -//! `VmiContext`, on the other hand, *does* hold the register state at -//! the time of the event. This difference has important implications for -//! how you interact with these components: +//! `VmiState` and `VmiContext`, on the other hand, *do* hold the register +//! state. This difference has important implications for how you interact with +//! these components: //! //! - With `VmiCore` and `VmiSession`, you must explicitly provide //! the translation root (e.g., `CR3`) when performing memory operations: @@ -404,7 +441,7 @@ //! # Ok::<_, vmi::VmiError>(()) //! ``` //! -//! - With `VmiContext`, register state is managed internally: +//! - With `VmiState` and `VmiContext`, register state is managed internally: //! //! ```rust,no_run //! # use vmi::{ @@ -430,32 +467,34 @@ //! # use vmi::{ //! # arch::amd64::Amd64, //! # driver::xen::VmiXenDriver, -//! # os::windows::WindowsOs, +//! # os::{windows::WindowsOs, VmiOsProcess as _}, //! # Va, VcpuId, VmiSession, //! # }; //! # -//! // let vmi: &VmiSession = ...; -//! # let vmi: &VmiSession, WindowsOs>> = unimplemented!(); -//! let registers = vmi.registers(VcpuId(0))?; -//! let process = vmi.os().current_process(®isters)?; -//! let process_id = vmi.os().process_id(®isters, process)?; +//! // let session: &VmiSession = ...; +//! # let session: &VmiSession, WindowsOs>> = unimplemented!(); +//! let registers = session.registers(VcpuId(0))?; +//! let vmi = session.with_registers(®isters); // Create a new VmiState +//! let process = vmi.os().current_process()?; +//! let process_id = process.id()?; //! # //! # Ok::<_, vmi::VmiError>(()) //! ``` //! -//! - `VmiContext` simplifies this by providing register state implicitly: +//! - `VmiState` and `VmiContext` simplifies this by providing register state +//! implicitly: //! //! ```rust,no_run //! # use vmi::{ //! # arch::amd64::Amd64, //! # driver::xen::VmiXenDriver, -//! # os::windows::WindowsOs, +//! # os::{windows::WindowsOs, VmiOsProcess as _}, //! # Va, VmiContext, //! # }; //! // let vmi: &VmiContext = ...; //! # let vmi: &VmiContext<'_, VmiXenDriver, WindowsOs>> = unimplemented!(); //! let process = vmi.os().current_process()?; -//! let process_id = vmi.os().process_id(process)?; +//! let process_id = process.id()?; //! # //! # Ok::<_, vmi::VmiError>(()) //! ``` @@ -578,7 +617,7 @@ //! [`PageIn`]: crate::utils::ptm::PageTableMonitorEvent::PageIn //! [`PageOut`]: crate::utils::ptm::PageTableMonitorEvent::PageOut //! [`handle_event`]: crate::VmiHandler::handle_event -//! [`os()`]: crate::VmiSession::os() +//! [`os()`]: crate::VmiState::os() //! [physical page lookups]: crate::VmiCore::with_gfn_cache //! [Virtual-to-Physical address translations]: crate::VmiCore::with_v2p_cache //! @@ -598,6 +637,7 @@ //! [`basic.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic.rs //! [`basic-process-list.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/basic-process-list.rs //! [`windows-breakpoint-manager.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-breakpoint-manager.rs +//! [`windows-dump.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-dump.rs //! [`windows-recipe-messagebox.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-messagebox.rs //! [`windows-recipe-writefile.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile.rs //! [`windows-recipe-writefile-advanced.rs`]: https://github.com/vmi-rs/vmi/blob/master/examples/windows-recipe-writefile-advanced.rs @@ -622,12 +662,26 @@ pub mod arch { pub mod driver { //! VMI drivers + #[cfg(feature = "driver-kdmp")] + pub mod kdmp { + #![doc = include_str!("../docs/vmi-driver-kdmp.md")] + + pub use vmi_driver_kdmp::*; + } + #[cfg(feature = "driver-xen")] pub mod xen { #![doc = include_str!("../docs/vmi-driver-xen.md")] pub use vmi_driver_xen::*; } + + #[cfg(feature = "driver-xen-core-dump")] + pub mod xen_core_dump { + #![doc = include_str!("../docs/vmi-driver-xen-core-dump.md")] + + pub use vmi_driver_xen_core_dump::*; + } } pub mod os {