├── README.md ├── pdblister ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── docs │ └── images │ │ └── download.gif └── src │ ├── bin.rs │ ├── lib.rs │ ├── pe.rs │ └── symsrv │ ├── blocking.rs │ ├── mod.rs │ └── nonblocking.rs ├── read_write_driver ├── .gitignore ├── Cargo.toml ├── Makefile.toml ├── build.rs ├── read_write_driver.inx └── src │ ├── cpu.rs │ ├── interrupts.rs │ ├── lib.rs │ ├── locking.rs │ ├── mdl.rs │ ├── shared.rs │ └── uni.rs └── read_write_user ├── .gitignore ├── Cargo.toml └── src ├── main.rs └── uni.rs /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Sample driver + user component to demonstrate writing into arbitrary process memory from Kernel via CR3 manipulation (opposed to the usual KeStackAttachProcess API). 4 | 5 | Note: Only for fun and demonstration 6 | 7 | # Fun 8 | 9 | There's a few fun techniques in this that have been individually useful outside of this demo project. This includes: 10 | 11 | - Halting all other cores/threads on the machine except my own executing code 12 | - Providing windows and alternative x86-generic methods for certain things (e.g. IRQLs v CR8) 13 | - Resolving offsets of unexported structures and union fields at runtime via runtime PDB parsing (instead of hardcoding offsets, etc) 14 | - Modifying arbitrary user process memory from Kernel without KeStackAttachProcess (optionally checking VaSpaceDeleted via PDB-provided offsets) 15 | - Surviving page-faults in >= DISPATCH without try/catch or letting the Kernel log the fault, achieved via IDT hijacking 16 | 17 | This thing is also written in Rust. 18 | 19 | For PDB parsing, I pulled in and modified pdblister https://github.com/microsoft/pdblister to support building as a lib. 20 | 21 | # How to Use 22 | 23 | - Navigate to read_write_driver 24 | - run `cargo make` (sometimes it requires the first run to be done as administrator, you can safely ignore any "missing INF" related errors/warnings that pop up, build process also documented here: https://github.com/microsoft/windows-drivers-rs) 25 | - Copy the driver (e.g for debug builds it'll be `read_write_driver\target\debug\read_write_driver.sys` to your target machine/VM 26 | - Start the driver (e.g. in an administrator cmd prompt run `sc create readwrite binPath= C:\\code\\read_write_driver.sys type= kernel` followed by `sc start readwrite`. Replace the paths with your own) 27 | - Navigate to `read_write_user` and build (e.g. `cargo build` or `cargo build --release`) 28 | - Copy the binary (either `read_write_user\target\debug\read_write_user.exe` or `read_write_user\target\release\read_write_user.exe`) to your target machine/VM 29 | - Find a PID and address in that PID you want to overwrite (e.g. launch notepad.exe, note its pid is 0x1234, attach a debugger and find some address in the target) 30 | - If the address if valid + paged-in, it'll be overwritten with hardcoded sample bytes, if the address is invalid the driver will return an error to our userland process. No BSOD should occur regardless. 31 | - Run the userland process, to run the example that'll leverage runtime PDB parsing add the `--use-symbols` flag, e.g. `read_write_user.exe --pid 0x1234 --address 0x100000 --use-symbols`. The address can be specified in hex (prefixed by `0x`) or in decimal without the prefix. 32 | - If no error was displayed in the userland process, observe the modified bytes at your chosen address. 33 | -------------------------------------------------------------------------------- /pdblister/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .vscode/ 3 | **/*.rs.bk 4 | manifest 5 | symbols 6 | 7 | -------------------------------------------------------------------------------- /pdblister/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle" 36 | version = "1.0.4" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 39 | 40 | [[package]] 41 | name = "anstyle-parse" 42 | version = "0.2.3" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 45 | dependencies = [ 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle-query" 51 | version = "1.0.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 54 | dependencies = [ 55 | "windows-sys 0.52.0", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-wincon" 60 | version = "3.0.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 63 | dependencies = [ 64 | "anstyle", 65 | "windows-sys 0.52.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anyhow" 70 | version = "1.0.75" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 79 | 80 | [[package]] 81 | name = "backtrace" 82 | version = "0.3.69" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 85 | dependencies = [ 86 | "addr2line", 87 | "cc", 88 | "cfg-if", 89 | "libc", 90 | "miniz_oxide", 91 | "object", 92 | "rustc-demangle", 93 | ] 94 | 95 | [[package]] 96 | name = "base64" 97 | version = "0.13.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 100 | 101 | [[package]] 102 | name = "base64" 103 | version = "0.21.5" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "1.3.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 112 | 113 | [[package]] 114 | name = "bitflags" 115 | version = "2.4.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 118 | 119 | [[package]] 120 | name = "bumpalo" 121 | version = "3.14.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 124 | 125 | [[package]] 126 | name = "byteorder" 127 | version = "1.5.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 130 | 131 | [[package]] 132 | name = "bytes" 133 | version = "1.5.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 136 | 137 | [[package]] 138 | name = "cc" 139 | version = "1.0.84" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" 142 | dependencies = [ 143 | "libc", 144 | ] 145 | 146 | [[package]] 147 | name = "cfg-if" 148 | version = "1.0.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 151 | 152 | [[package]] 153 | name = "clap" 154 | version = "4.4.11" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" 157 | dependencies = [ 158 | "clap_builder", 159 | "clap_derive", 160 | ] 161 | 162 | [[package]] 163 | name = "clap_builder" 164 | version = "4.4.11" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" 167 | dependencies = [ 168 | "anstream", 169 | "anstyle", 170 | "clap_lex", 171 | "strsim", 172 | ] 173 | 174 | [[package]] 175 | name = "clap_derive" 176 | version = "4.4.7" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" 179 | dependencies = [ 180 | "heck", 181 | "proc-macro2", 182 | "quote", 183 | "syn", 184 | ] 185 | 186 | [[package]] 187 | name = "clap_lex" 188 | version = "0.6.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" 191 | 192 | [[package]] 193 | name = "colorchoice" 194 | version = "1.0.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 197 | 198 | [[package]] 199 | name = "console" 200 | version = "0.15.7" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" 203 | dependencies = [ 204 | "encode_unicode", 205 | "lazy_static", 206 | "libc", 207 | "unicode-width", 208 | "windows-sys 0.45.0", 209 | ] 210 | 211 | [[package]] 212 | name = "core-foundation" 213 | version = "0.9.4" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 216 | dependencies = [ 217 | "core-foundation-sys", 218 | "libc", 219 | ] 220 | 221 | [[package]] 222 | name = "core-foundation-sys" 223 | version = "0.8.6" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 226 | 227 | [[package]] 228 | name = "encode_unicode" 229 | version = "0.3.6" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 232 | 233 | [[package]] 234 | name = "encoding_rs" 235 | version = "0.8.33" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 238 | dependencies = [ 239 | "cfg-if", 240 | ] 241 | 242 | [[package]] 243 | name = "equivalent" 244 | version = "1.0.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 247 | 248 | [[package]] 249 | name = "errno" 250 | version = "0.3.8" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 253 | dependencies = [ 254 | "libc", 255 | "windows-sys 0.52.0", 256 | ] 257 | 258 | [[package]] 259 | name = "fallible-iterator" 260 | version = "0.2.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "1d9b20bd281f764c9e86776886ab445c4c4f3fd9fee381f581c25aafe5d461f4" 263 | 264 | [[package]] 265 | name = "fastrand" 266 | version = "2.0.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 269 | 270 | [[package]] 271 | name = "fnv" 272 | version = "1.0.7" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 275 | 276 | [[package]] 277 | name = "foreign-types" 278 | version = "0.3.2" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 281 | dependencies = [ 282 | "foreign-types-shared", 283 | ] 284 | 285 | [[package]] 286 | name = "foreign-types-shared" 287 | version = "0.1.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 290 | 291 | [[package]] 292 | name = "form_urlencoded" 293 | version = "1.2.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 296 | dependencies = [ 297 | "percent-encoding", 298 | ] 299 | 300 | [[package]] 301 | name = "futures" 302 | version = "0.3.29" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" 305 | dependencies = [ 306 | "futures-channel", 307 | "futures-core", 308 | "futures-executor", 309 | "futures-io", 310 | "futures-sink", 311 | "futures-task", 312 | "futures-util", 313 | ] 314 | 315 | [[package]] 316 | name = "futures-channel" 317 | version = "0.3.29" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 320 | dependencies = [ 321 | "futures-core", 322 | "futures-sink", 323 | ] 324 | 325 | [[package]] 326 | name = "futures-core" 327 | version = "0.3.29" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 330 | 331 | [[package]] 332 | name = "futures-executor" 333 | version = "0.3.29" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" 336 | dependencies = [ 337 | "futures-core", 338 | "futures-task", 339 | "futures-util", 340 | ] 341 | 342 | [[package]] 343 | name = "futures-io" 344 | version = "0.3.29" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" 347 | 348 | [[package]] 349 | name = "futures-macro" 350 | version = "0.3.29" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" 353 | dependencies = [ 354 | "proc-macro2", 355 | "quote", 356 | "syn", 357 | ] 358 | 359 | [[package]] 360 | name = "futures-sink" 361 | version = "0.3.29" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 364 | 365 | [[package]] 366 | name = "futures-task" 367 | version = "0.3.29" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 370 | 371 | [[package]] 372 | name = "futures-util" 373 | version = "0.3.29" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 376 | dependencies = [ 377 | "futures-channel", 378 | "futures-core", 379 | "futures-io", 380 | "futures-macro", 381 | "futures-sink", 382 | "futures-task", 383 | "memchr", 384 | "pin-project-lite", 385 | "pin-utils", 386 | "slab", 387 | ] 388 | 389 | [[package]] 390 | name = "getrandom" 391 | version = "0.2.11" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 394 | dependencies = [ 395 | "cfg-if", 396 | "libc", 397 | "wasi", 398 | ] 399 | 400 | [[package]] 401 | name = "gimli" 402 | version = "0.28.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 405 | 406 | [[package]] 407 | name = "h2" 408 | version = "0.3.26" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 411 | dependencies = [ 412 | "bytes", 413 | "fnv", 414 | "futures-core", 415 | "futures-sink", 416 | "futures-util", 417 | "http", 418 | "indexmap", 419 | "slab", 420 | "tokio", 421 | "tokio-util", 422 | "tracing", 423 | ] 424 | 425 | [[package]] 426 | name = "hashbrown" 427 | version = "0.14.3" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 430 | 431 | [[package]] 432 | name = "heck" 433 | version = "0.4.1" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 436 | 437 | [[package]] 438 | name = "hermit-abi" 439 | version = "0.3.3" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 442 | 443 | [[package]] 444 | name = "http" 445 | version = "0.2.11" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 448 | dependencies = [ 449 | "bytes", 450 | "fnv", 451 | "itoa", 452 | ] 453 | 454 | [[package]] 455 | name = "http-body" 456 | version = "0.4.6" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 459 | dependencies = [ 460 | "bytes", 461 | "http", 462 | "pin-project-lite", 463 | ] 464 | 465 | [[package]] 466 | name = "httparse" 467 | version = "1.8.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 470 | 471 | [[package]] 472 | name = "httpdate" 473 | version = "1.0.3" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 476 | 477 | [[package]] 478 | name = "hyper" 479 | version = "0.14.27" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 482 | dependencies = [ 483 | "bytes", 484 | "futures-channel", 485 | "futures-core", 486 | "futures-util", 487 | "h2", 488 | "http", 489 | "http-body", 490 | "httparse", 491 | "httpdate", 492 | "itoa", 493 | "pin-project-lite", 494 | "socket2 0.4.10", 495 | "tokio", 496 | "tower-service", 497 | "tracing", 498 | "want", 499 | ] 500 | 501 | [[package]] 502 | name = "hyper-tls" 503 | version = "0.5.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 506 | dependencies = [ 507 | "bytes", 508 | "hyper", 509 | "native-tls", 510 | "tokio", 511 | "tokio-native-tls", 512 | ] 513 | 514 | [[package]] 515 | name = "idna" 516 | version = "0.5.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 519 | dependencies = [ 520 | "unicode-bidi", 521 | "unicode-normalization", 522 | ] 523 | 524 | [[package]] 525 | name = "indexmap" 526 | version = "2.1.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 529 | dependencies = [ 530 | "equivalent", 531 | "hashbrown", 532 | ] 533 | 534 | [[package]] 535 | name = "indicatif" 536 | version = "0.17.7" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" 539 | dependencies = [ 540 | "console", 541 | "instant", 542 | "number_prefix", 543 | "portable-atomic", 544 | "tokio", 545 | "unicode-width", 546 | ] 547 | 548 | [[package]] 549 | name = "instant" 550 | version = "0.1.12" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 553 | dependencies = [ 554 | "cfg-if", 555 | ] 556 | 557 | [[package]] 558 | name = "ipnet" 559 | version = "2.9.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 562 | 563 | [[package]] 564 | name = "itoa" 565 | version = "1.0.10" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 568 | 569 | [[package]] 570 | name = "js-sys" 571 | version = "0.3.66" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" 574 | dependencies = [ 575 | "wasm-bindgen", 576 | ] 577 | 578 | [[package]] 579 | name = "lazy_static" 580 | version = "1.4.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 583 | 584 | [[package]] 585 | name = "libc" 586 | version = "0.2.151" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 589 | 590 | [[package]] 591 | name = "linux-raw-sys" 592 | version = "0.4.12" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" 595 | 596 | [[package]] 597 | name = "lock_api" 598 | version = "0.4.11" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 601 | dependencies = [ 602 | "autocfg", 603 | "scopeguard", 604 | ] 605 | 606 | [[package]] 607 | name = "log" 608 | version = "0.4.20" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 611 | 612 | [[package]] 613 | name = "memchr" 614 | version = "2.6.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 617 | 618 | [[package]] 619 | name = "mime" 620 | version = "0.3.17" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 623 | 624 | [[package]] 625 | name = "miniz_oxide" 626 | version = "0.7.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 629 | dependencies = [ 630 | "adler", 631 | ] 632 | 633 | [[package]] 634 | name = "mio" 635 | version = "0.8.11" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 638 | dependencies = [ 639 | "libc", 640 | "wasi", 641 | "windows-sys 0.48.0", 642 | ] 643 | 644 | [[package]] 645 | name = "native-tls" 646 | version = "0.2.11" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 649 | dependencies = [ 650 | "lazy_static", 651 | "libc", 652 | "log", 653 | "openssl", 654 | "openssl-probe", 655 | "openssl-sys", 656 | "schannel", 657 | "security-framework", 658 | "security-framework-sys", 659 | "tempfile", 660 | ] 661 | 662 | [[package]] 663 | name = "num_cpus" 664 | version = "1.16.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 667 | dependencies = [ 668 | "hermit-abi", 669 | "libc", 670 | ] 671 | 672 | [[package]] 673 | name = "number_prefix" 674 | version = "0.4.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 677 | 678 | [[package]] 679 | name = "object" 680 | version = "0.32.1" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 683 | dependencies = [ 684 | "memchr", 685 | ] 686 | 687 | [[package]] 688 | name = "once_cell" 689 | version = "1.19.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 692 | 693 | [[package]] 694 | name = "openssl" 695 | version = "0.10.61" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" 698 | dependencies = [ 699 | "bitflags 2.4.1", 700 | "cfg-if", 701 | "foreign-types", 702 | "libc", 703 | "once_cell", 704 | "openssl-macros", 705 | "openssl-sys", 706 | ] 707 | 708 | [[package]] 709 | name = "openssl-macros" 710 | version = "0.1.1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 713 | dependencies = [ 714 | "proc-macro2", 715 | "quote", 716 | "syn", 717 | ] 718 | 719 | [[package]] 720 | name = "openssl-probe" 721 | version = "0.1.5" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 724 | 725 | [[package]] 726 | name = "openssl-sys" 727 | version = "0.9.97" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" 730 | dependencies = [ 731 | "cc", 732 | "libc", 733 | "pkg-config", 734 | "vcpkg", 735 | ] 736 | 737 | [[package]] 738 | name = "parking_lot" 739 | version = "0.12.1" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 742 | dependencies = [ 743 | "lock_api", 744 | "parking_lot_core", 745 | ] 746 | 747 | [[package]] 748 | name = "parking_lot_core" 749 | version = "0.9.9" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 752 | dependencies = [ 753 | "cfg-if", 754 | "libc", 755 | "redox_syscall", 756 | "smallvec", 757 | "windows-targets 0.48.5", 758 | ] 759 | 760 | [[package]] 761 | name = "pdb" 762 | version = "0.8.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863" 765 | dependencies = [ 766 | "fallible-iterator", 767 | "scroll", 768 | "uuid", 769 | ] 770 | 771 | [[package]] 772 | name = "pdblister" 773 | version = "0.0.3" 774 | dependencies = [ 775 | "anyhow", 776 | "base64 0.13.1", 777 | "clap", 778 | "futures", 779 | "indicatif", 780 | "mime", 781 | "pdb", 782 | "rand", 783 | "reqwest", 784 | "serde_json", 785 | "thiserror", 786 | "tokio", 787 | "url", 788 | "zerocopy", 789 | "zerocopy-derive", 790 | ] 791 | 792 | [[package]] 793 | name = "percent-encoding" 794 | version = "2.3.1" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 797 | 798 | [[package]] 799 | name = "pin-project-lite" 800 | version = "0.2.13" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 803 | 804 | [[package]] 805 | name = "pin-utils" 806 | version = "0.1.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 809 | 810 | [[package]] 811 | name = "pkg-config" 812 | version = "0.3.27" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 815 | 816 | [[package]] 817 | name = "portable-atomic" 818 | version = "1.6.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 821 | 822 | [[package]] 823 | name = "ppv-lite86" 824 | version = "0.2.17" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 827 | 828 | [[package]] 829 | name = "proc-macro2" 830 | version = "1.0.81" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 833 | dependencies = [ 834 | "unicode-ident", 835 | ] 836 | 837 | [[package]] 838 | name = "quote" 839 | version = "1.0.36" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 842 | dependencies = [ 843 | "proc-macro2", 844 | ] 845 | 846 | [[package]] 847 | name = "rand" 848 | version = "0.8.5" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 851 | dependencies = [ 852 | "libc", 853 | "rand_chacha", 854 | "rand_core", 855 | ] 856 | 857 | [[package]] 858 | name = "rand_chacha" 859 | version = "0.3.1" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 862 | dependencies = [ 863 | "ppv-lite86", 864 | "rand_core", 865 | ] 866 | 867 | [[package]] 868 | name = "rand_core" 869 | version = "0.6.4" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 872 | dependencies = [ 873 | "getrandom", 874 | ] 875 | 876 | [[package]] 877 | name = "redox_syscall" 878 | version = "0.4.1" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 881 | dependencies = [ 882 | "bitflags 1.3.2", 883 | ] 884 | 885 | [[package]] 886 | name = "reqwest" 887 | version = "0.11.22" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" 890 | dependencies = [ 891 | "base64 0.21.5", 892 | "bytes", 893 | "encoding_rs", 894 | "futures-core", 895 | "futures-util", 896 | "h2", 897 | "http", 898 | "http-body", 899 | "hyper", 900 | "hyper-tls", 901 | "ipnet", 902 | "js-sys", 903 | "log", 904 | "mime", 905 | "native-tls", 906 | "once_cell", 907 | "percent-encoding", 908 | "pin-project-lite", 909 | "serde", 910 | "serde_json", 911 | "serde_urlencoded", 912 | "system-configuration", 913 | "tokio", 914 | "tokio-native-tls", 915 | "tower-service", 916 | "url", 917 | "wasm-bindgen", 918 | "wasm-bindgen-futures", 919 | "web-sys", 920 | "winreg", 921 | ] 922 | 923 | [[package]] 924 | name = "rustc-demangle" 925 | version = "0.1.23" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 928 | 929 | [[package]] 930 | name = "rustix" 931 | version = "0.38.28" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" 934 | dependencies = [ 935 | "bitflags 2.4.1", 936 | "errno", 937 | "libc", 938 | "linux-raw-sys", 939 | "windows-sys 0.52.0", 940 | ] 941 | 942 | [[package]] 943 | name = "ryu" 944 | version = "1.0.16" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 947 | 948 | [[package]] 949 | name = "schannel" 950 | version = "0.1.22" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 953 | dependencies = [ 954 | "windows-sys 0.48.0", 955 | ] 956 | 957 | [[package]] 958 | name = "scopeguard" 959 | version = "1.2.0" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 962 | 963 | [[package]] 964 | name = "scroll" 965 | version = "0.11.0" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" 968 | 969 | [[package]] 970 | name = "security-framework" 971 | version = "2.9.2" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 974 | dependencies = [ 975 | "bitflags 1.3.2", 976 | "core-foundation", 977 | "core-foundation-sys", 978 | "libc", 979 | "security-framework-sys", 980 | ] 981 | 982 | [[package]] 983 | name = "security-framework-sys" 984 | version = "2.9.1" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 987 | dependencies = [ 988 | "core-foundation-sys", 989 | "libc", 990 | ] 991 | 992 | [[package]] 993 | name = "serde" 994 | version = "1.0.193" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 997 | dependencies = [ 998 | "serde_derive", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "serde_derive" 1003 | version = "1.0.193" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 1006 | dependencies = [ 1007 | "proc-macro2", 1008 | "quote", 1009 | "syn", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "serde_json" 1014 | version = "1.0.108" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 1017 | dependencies = [ 1018 | "itoa", 1019 | "ryu", 1020 | "serde", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "serde_urlencoded" 1025 | version = "0.7.1" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1028 | dependencies = [ 1029 | "form_urlencoded", 1030 | "itoa", 1031 | "ryu", 1032 | "serde", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "signal-hook-registry" 1037 | version = "1.4.1" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1040 | dependencies = [ 1041 | "libc", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "slab" 1046 | version = "0.4.9" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1049 | dependencies = [ 1050 | "autocfg", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "smallvec" 1055 | version = "1.11.2" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 1058 | 1059 | [[package]] 1060 | name = "socket2" 1061 | version = "0.4.10" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 1064 | dependencies = [ 1065 | "libc", 1066 | "winapi", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "socket2" 1071 | version = "0.5.5" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1074 | dependencies = [ 1075 | "libc", 1076 | "windows-sys 0.48.0", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "strsim" 1081 | version = "0.10.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1084 | 1085 | [[package]] 1086 | name = "syn" 1087 | version = "2.0.55" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" 1090 | dependencies = [ 1091 | "proc-macro2", 1092 | "quote", 1093 | "unicode-ident", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "system-configuration" 1098 | version = "0.5.1" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1101 | dependencies = [ 1102 | "bitflags 1.3.2", 1103 | "core-foundation", 1104 | "system-configuration-sys", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "system-configuration-sys" 1109 | version = "0.5.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1112 | dependencies = [ 1113 | "core-foundation-sys", 1114 | "libc", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "tempfile" 1119 | version = "3.8.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" 1122 | dependencies = [ 1123 | "cfg-if", 1124 | "fastrand", 1125 | "redox_syscall", 1126 | "rustix", 1127 | "windows-sys 0.48.0", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "thiserror" 1132 | version = "1.0.50" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 1135 | dependencies = [ 1136 | "thiserror-impl", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "thiserror-impl" 1141 | version = "1.0.50" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 1144 | dependencies = [ 1145 | "proc-macro2", 1146 | "quote", 1147 | "syn", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "tinyvec" 1152 | version = "1.6.0" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1155 | dependencies = [ 1156 | "tinyvec_macros", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "tinyvec_macros" 1161 | version = "0.1.1" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1164 | 1165 | [[package]] 1166 | name = "tokio" 1167 | version = "1.35.0" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" 1170 | dependencies = [ 1171 | "backtrace", 1172 | "bytes", 1173 | "libc", 1174 | "mio", 1175 | "num_cpus", 1176 | "parking_lot", 1177 | "pin-project-lite", 1178 | "signal-hook-registry", 1179 | "socket2 0.5.5", 1180 | "tokio-macros", 1181 | "windows-sys 0.48.0", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "tokio-macros" 1186 | version = "2.2.0" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1189 | dependencies = [ 1190 | "proc-macro2", 1191 | "quote", 1192 | "syn", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "tokio-native-tls" 1197 | version = "0.3.1" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1200 | dependencies = [ 1201 | "native-tls", 1202 | "tokio", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "tokio-util" 1207 | version = "0.7.10" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1210 | dependencies = [ 1211 | "bytes", 1212 | "futures-core", 1213 | "futures-sink", 1214 | "pin-project-lite", 1215 | "tokio", 1216 | "tracing", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "tower-service" 1221 | version = "0.3.2" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1224 | 1225 | [[package]] 1226 | name = "tracing" 1227 | version = "0.1.40" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1230 | dependencies = [ 1231 | "pin-project-lite", 1232 | "tracing-core", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "tracing-core" 1237 | version = "0.1.32" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1240 | dependencies = [ 1241 | "once_cell", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "try-lock" 1246 | version = "0.2.5" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1249 | 1250 | [[package]] 1251 | name = "unicode-bidi" 1252 | version = "0.3.14" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" 1255 | 1256 | [[package]] 1257 | name = "unicode-ident" 1258 | version = "1.0.12" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1261 | 1262 | [[package]] 1263 | name = "unicode-normalization" 1264 | version = "0.1.22" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1267 | dependencies = [ 1268 | "tinyvec", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "unicode-width" 1273 | version = "0.1.11" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 1276 | 1277 | [[package]] 1278 | name = "url" 1279 | version = "2.5.0" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1282 | dependencies = [ 1283 | "form_urlencoded", 1284 | "idna", 1285 | "percent-encoding", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "utf8parse" 1290 | version = "0.2.1" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1293 | 1294 | [[package]] 1295 | name = "uuid" 1296 | version = "1.6.1" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" 1299 | 1300 | [[package]] 1301 | name = "vcpkg" 1302 | version = "0.2.15" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1305 | 1306 | [[package]] 1307 | name = "want" 1308 | version = "0.3.1" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1311 | dependencies = [ 1312 | "try-lock", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "wasi" 1317 | version = "0.11.0+wasi-snapshot-preview1" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1320 | 1321 | [[package]] 1322 | name = "wasm-bindgen" 1323 | version = "0.2.89" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" 1326 | dependencies = [ 1327 | "cfg-if", 1328 | "wasm-bindgen-macro", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "wasm-bindgen-backend" 1333 | version = "0.2.89" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" 1336 | dependencies = [ 1337 | "bumpalo", 1338 | "log", 1339 | "once_cell", 1340 | "proc-macro2", 1341 | "quote", 1342 | "syn", 1343 | "wasm-bindgen-shared", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "wasm-bindgen-futures" 1348 | version = "0.4.39" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" 1351 | dependencies = [ 1352 | "cfg-if", 1353 | "js-sys", 1354 | "wasm-bindgen", 1355 | "web-sys", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "wasm-bindgen-macro" 1360 | version = "0.2.89" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" 1363 | dependencies = [ 1364 | "quote", 1365 | "wasm-bindgen-macro-support", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "wasm-bindgen-macro-support" 1370 | version = "0.2.89" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" 1373 | dependencies = [ 1374 | "proc-macro2", 1375 | "quote", 1376 | "syn", 1377 | "wasm-bindgen-backend", 1378 | "wasm-bindgen-shared", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "wasm-bindgen-shared" 1383 | version = "0.2.89" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" 1386 | 1387 | [[package]] 1388 | name = "web-sys" 1389 | version = "0.3.66" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" 1392 | dependencies = [ 1393 | "js-sys", 1394 | "wasm-bindgen", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "winapi" 1399 | version = "0.3.9" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1402 | dependencies = [ 1403 | "winapi-i686-pc-windows-gnu", 1404 | "winapi-x86_64-pc-windows-gnu", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "winapi-i686-pc-windows-gnu" 1409 | version = "0.4.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1412 | 1413 | [[package]] 1414 | name = "winapi-x86_64-pc-windows-gnu" 1415 | version = "0.4.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1418 | 1419 | [[package]] 1420 | name = "windows-sys" 1421 | version = "0.45.0" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1424 | dependencies = [ 1425 | "windows-targets 0.42.2", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "windows-sys" 1430 | version = "0.48.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1433 | dependencies = [ 1434 | "windows-targets 0.48.5", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "windows-sys" 1439 | version = "0.52.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1442 | dependencies = [ 1443 | "windows-targets 0.52.0", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "windows-targets" 1448 | version = "0.42.2" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1451 | dependencies = [ 1452 | "windows_aarch64_gnullvm 0.42.2", 1453 | "windows_aarch64_msvc 0.42.2", 1454 | "windows_i686_gnu 0.42.2", 1455 | "windows_i686_msvc 0.42.2", 1456 | "windows_x86_64_gnu 0.42.2", 1457 | "windows_x86_64_gnullvm 0.42.2", 1458 | "windows_x86_64_msvc 0.42.2", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "windows-targets" 1463 | version = "0.48.5" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1466 | dependencies = [ 1467 | "windows_aarch64_gnullvm 0.48.5", 1468 | "windows_aarch64_msvc 0.48.5", 1469 | "windows_i686_gnu 0.48.5", 1470 | "windows_i686_msvc 0.48.5", 1471 | "windows_x86_64_gnu 0.48.5", 1472 | "windows_x86_64_gnullvm 0.48.5", 1473 | "windows_x86_64_msvc 0.48.5", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "windows-targets" 1478 | version = "0.52.0" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1481 | dependencies = [ 1482 | "windows_aarch64_gnullvm 0.52.0", 1483 | "windows_aarch64_msvc 0.52.0", 1484 | "windows_i686_gnu 0.52.0", 1485 | "windows_i686_msvc 0.52.0", 1486 | "windows_x86_64_gnu 0.52.0", 1487 | "windows_x86_64_gnullvm 0.52.0", 1488 | "windows_x86_64_msvc 0.52.0", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "windows_aarch64_gnullvm" 1493 | version = "0.42.2" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1496 | 1497 | [[package]] 1498 | name = "windows_aarch64_gnullvm" 1499 | version = "0.48.5" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1502 | 1503 | [[package]] 1504 | name = "windows_aarch64_gnullvm" 1505 | version = "0.52.0" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1508 | 1509 | [[package]] 1510 | name = "windows_aarch64_msvc" 1511 | version = "0.42.2" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1514 | 1515 | [[package]] 1516 | name = "windows_aarch64_msvc" 1517 | version = "0.48.5" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1520 | 1521 | [[package]] 1522 | name = "windows_aarch64_msvc" 1523 | version = "0.52.0" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1526 | 1527 | [[package]] 1528 | name = "windows_i686_gnu" 1529 | version = "0.42.2" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1532 | 1533 | [[package]] 1534 | name = "windows_i686_gnu" 1535 | version = "0.48.5" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1538 | 1539 | [[package]] 1540 | name = "windows_i686_gnu" 1541 | version = "0.52.0" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1544 | 1545 | [[package]] 1546 | name = "windows_i686_msvc" 1547 | version = "0.42.2" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1550 | 1551 | [[package]] 1552 | name = "windows_i686_msvc" 1553 | version = "0.48.5" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1556 | 1557 | [[package]] 1558 | name = "windows_i686_msvc" 1559 | version = "0.52.0" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1562 | 1563 | [[package]] 1564 | name = "windows_x86_64_gnu" 1565 | version = "0.42.2" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1568 | 1569 | [[package]] 1570 | name = "windows_x86_64_gnu" 1571 | version = "0.48.5" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1574 | 1575 | [[package]] 1576 | name = "windows_x86_64_gnu" 1577 | version = "0.52.0" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1580 | 1581 | [[package]] 1582 | name = "windows_x86_64_gnullvm" 1583 | version = "0.42.2" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1586 | 1587 | [[package]] 1588 | name = "windows_x86_64_gnullvm" 1589 | version = "0.48.5" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1592 | 1593 | [[package]] 1594 | name = "windows_x86_64_gnullvm" 1595 | version = "0.52.0" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1598 | 1599 | [[package]] 1600 | name = "windows_x86_64_msvc" 1601 | version = "0.42.2" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1604 | 1605 | [[package]] 1606 | name = "windows_x86_64_msvc" 1607 | version = "0.48.5" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1610 | 1611 | [[package]] 1612 | name = "windows_x86_64_msvc" 1613 | version = "0.52.0" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1616 | 1617 | [[package]] 1618 | name = "winreg" 1619 | version = "0.50.0" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1622 | dependencies = [ 1623 | "cfg-if", 1624 | "windows-sys 0.48.0", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "zerocopy" 1629 | version = "0.7.32" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 1632 | dependencies = [ 1633 | "byteorder", 1634 | "zerocopy-derive", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "zerocopy-derive" 1639 | version = "0.7.32" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 1642 | dependencies = [ 1643 | "proc-macro2", 1644 | "quote", 1645 | "syn", 1646 | ] 1647 | -------------------------------------------------------------------------------- /pdblister/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pdblister" 3 | description = "Faster version of `symchk /om` for generating PDB manifests of offline machines" 4 | license-file = "LICENSE" 5 | homepage = "https://github.com/microsoft/pdblister" 6 | repository = "https://github.com/microsoft/pdblister" 7 | version = "0.0.3" 8 | authors = ["Gogs ", "Justin Moore "] 9 | edition = "2021" 10 | 11 | [lib] 12 | name = "pdblister" 13 | path = "src/lib.rs" 14 | 15 | [[bin]] 16 | name = "pdblister" 17 | path = "src/bin.rs" 18 | 19 | [dependencies] 20 | anyhow = "1.0" 21 | base64 = "0.13" 22 | clap = { version = "4.4.11", features = ["derive"] } 23 | futures = "0.3" 24 | indicatif = { version = "0.17.2", features = ["tokio"] } 25 | mime = "0.3" 26 | pdb = "0.8.0" 27 | rand = "0.8" 28 | reqwest = "0.11.13" 29 | serde_json = "1.0.87" 30 | thiserror = "1.0.37" 31 | url = "2.2" 32 | zerocopy = { version = "0.7.32", features = ["derive"]} 33 | zerocopy-derive = "0.7.32" 34 | 35 | [dependencies.tokio] 36 | version = "1.24.2" 37 | features = ["full"] 38 | 39 | -------------------------------------------------------------------------------- /pdblister/LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /pdblister/README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This is a tiny **unofficial** project meant to be a quick alternative to symchk for 4 | miscellaneous tasks, such as generating manifests and downloading symbols. This 5 | mimics symchk of the form `symchk /om manifest /r ` but only looks for MZ/PE files. 6 | 7 | Due to symchk doing some weird things it can often crash or get stuck in 8 | infinite loops. Thus this is a stricter (and much faster) alternative. 9 | 10 | The output manifest is compatible with symchk. If you want to use symchk 11 | in lieu of this tool, use `symchk /im manifest /s ` 12 | 13 | ⚠️ Note: This tool is **unstable**! The CLI interface may change at any point, **without warning**. 14 | If you need programmatic stability (e.g. for automation), please pin your install to a specific revision. 15 | 16 | Check out how fast this tool is: 17 | ![](docs/images/download.gif) 18 | 19 | # Quick Start 20 | 21 | ``` 22 | # On your target 23 | > cargo run --release -- manifest C:\Windows\System32 24 | 25 | # On an online machine 26 | > cargo run --release -- download SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols 27 | ``` 28 | 29 | ## Downloading a single PDB file 30 | ``` 31 | > cargo run --release -- download_single SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols C:\Windows\System32\notepad.exe 32 | ``` 33 | 34 | # Future 35 | 36 | Randomizing the order of the files in the manifest would make downloads more 37 | consistant by not having any filesystem locality bias in the files. 38 | 39 | Deduping the files in the manifests could also help, but this isn't a big 40 | deal *shrug* 41 | 42 | We could potentially offer a symchk-compatible subcommand: [#5](https://github.com/microsoft/pdblister/issues/5) 43 | 44 | A "server mode" could be implemented so that other tools written in different languages could take advantage of our functionality: [#7](https://github.com/microsoft/pdblister/issues/7) 45 | 46 | # Performance 47 | 48 | This tool tries to do everything in memory if it can. Lists all files first 49 | then does all the parsing (this has random accesses to files without mapping so 50 | it could be improved, but it doesn't really seem to be an issue, this random 51 | access only occurs if it sees an MZ and PE header and everything is valid). 52 | 53 | It also generates the manifest in memory and dumps it out in one swoop, this is 54 | one large bottleneck original symchk has. 55 | 56 | Then for downloads it chomps through a manifest file asynchronously, at up to 57 | 16 files at the same time! The original `symchk` only peaks at about 3-4 Mbps 58 | of network usage, but this tool saturates my internet connection at 59 | 400 Mbps. 60 | -------------------------------------------------------------------------------- /pdblister/SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /pdblister/docs/images/download.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kharos102/ReadWriteDriverSample/af4399f488c5a9d78c963d0740ef392f261d144b/pdblister/docs/images/download.gif -------------------------------------------------------------------------------- /pdblister/src/bin.rs: -------------------------------------------------------------------------------- 1 | //! This is a tiny project to be a quick alternative to symchk for generating 2 | //! manifests. This mimics symchk of the form `symchk /om manifest /r ` 3 | //! but only looks for MZ/PE files. 4 | //! 5 | //! Due to symchk doing some weird things it can often crash or get stuck in 6 | //! infinite loops. Thus this is a stricter (and much faster) alternative. 7 | //! 8 | //! The output manifest is compatible with symchk and thus symchk is currently 9 | //! used for the actual download. To download symbols after this manifest 10 | //! has been generated use `symchk /im manifest /s ` 11 | #![forbid(unsafe_code)] 12 | 13 | use anyhow::Context; 14 | use clap::{Parser, Subcommand}; 15 | use indicatif::{ProgressBar, ProgressStyle}; 16 | use pdblister::symsrv::SymFileInfo; 17 | use pdblister::{ 18 | connect_servers, download_manifest, get_file_path, get_pdb, get_pdb_path, recursive_listdir, 19 | ManifestEntry, MessageFormat, 20 | }; 21 | use std::io; 22 | use std::path::{Path, PathBuf}; 23 | use std::str::FromStr; 24 | use tokio::fs::DirEntry; 25 | use tokio::io::AsyncWriteExt; 26 | 27 | /// This tool lets you quickly download PDBs from a symbol server 28 | #[derive(Parser)] 29 | #[command(author, version, about)] 30 | struct Args { 31 | #[command(subcommand)] 32 | command: Command, 33 | } 34 | 35 | #[derive(Subcommand, Clone, Debug)] 36 | enum Command { 37 | /// Recursively searches a directory tree and generate a manifest file containing PDB hashes for all executables found 38 | /// 39 | /// This command takes in a filepath to recursively search for files that 40 | /// have a corresponding PDB. This creates a file which is compatible with 41 | /// symchk. 42 | /// 43 | /// For example `pdblister manifest C:\windows` will create `manifest` 44 | /// containing all of the PDB signatures for all of the files in 45 | /// C:\windows. 46 | Manifest { 47 | /// The root of the directory tree to search for PEs 48 | filepath: PathBuf, 49 | /// The destination manifest path 50 | manifest: Option, 51 | }, 52 | /// Downloads all the PDBs specified in the manifest file 53 | Download { 54 | /// The symbol server URL 55 | symsrv: String, 56 | /// The manifest path 57 | manifest: Option, 58 | }, 59 | /// Downloads a PDB file corresponding to a single PE file 60 | DownloadSingle { 61 | /// The symbol server URL 62 | symsrv: String, 63 | /// The PE file path 64 | filepath: PathBuf, 65 | /// The format to print the message in 66 | message_format: MessageFormat, 67 | }, 68 | /// Recursively searches a directory tree and caches all PEs in the current directory in a symbol cache layout 69 | /// 70 | /// This command recursively walks filepath to find all PEs. Any PE file 71 | /// that is found is copied to the local directory 'targetpath' using the 72 | /// layout that symchk.exe uses to store normal files. This is used to 73 | /// create a store of all PEs (such as .dlls), which can be used by a 74 | /// kernel debugger to read otherwise paged out memory by downloading the 75 | /// original PE source file from this filestore. 76 | /// 77 | /// To use this filestore simply merge the contents in with a symbol 78 | /// store/cache path. We keep it separate in this tool just to make it 79 | /// easier to only get PDBs if that's all you really want. 80 | Filestore { 81 | /// The root of the directory tree to search for PEs 82 | filepath: PathBuf, 83 | /// The target directory to stash PEs in 84 | targetpath: PathBuf, 85 | }, 86 | /// Recursively searches a directory tree and caches all PDBs in the current directory in a symbol cache layout 87 | /// 88 | /// This command recursively walks filepath to find all PDBs. Any PDB file 89 | /// that is found is copied to the local directory 'targetpath' using the 90 | /// same layout as symchk.exe. This is used to create a store of all PDBs 91 | /// which can be used by a kernel debugger to resolve symbols. 92 | /// 93 | /// To use this filestore simply merge the contents in with a symbol 94 | /// store/cache path. We keep it separate in this tool just to make it 95 | /// easier to only get PDBs if that's all you really want. 96 | Pdbstore { 97 | /// The root of the directory tree to search for PDBs 98 | filepath: PathBuf, 99 | /// The target directory to stash PDBs in 100 | targetpath: PathBuf, 101 | }, 102 | /// Various information-related subcommands 103 | #[command(subcommand)] 104 | Info(InfoCommand), 105 | } 106 | 107 | #[derive(Subcommand, Clone, Debug)] 108 | enum InfoCommand { 109 | /// Dumps out the hash of the corresponding PDB file for a PE file 110 | Pdbhash { 111 | /// The path to the PE file to dump the PDB hash for 112 | filepath: PathBuf, 113 | }, 114 | } 115 | 116 | async fn run() -> anyhow::Result<()> { 117 | let args = Args::parse(); 118 | 119 | match args.command { 120 | Command::Manifest { filepath, manifest } => { 121 | /* List all files in the directory specified by args[2] */ 122 | let listing: Vec> = 123 | recursive_listdir(filepath).collect().await; 124 | 125 | let pb = ProgressBar::new(listing.len() as u64); 126 | 127 | pb.set_style( 128 | ProgressStyle::default_bar() 129 | .template( 130 | "[{elapsed_precise}] {wide_bar:.cyan/blue} {pos:>7}/{len:7} ({eta}) {msg}", 131 | ) 132 | .unwrap() 133 | .progress_chars("##-"), 134 | ); 135 | 136 | // Map the listing into strings to write into the manifest 137 | let tasks: Vec<_> = listing 138 | .into_iter() 139 | .filter_map(move |e| { 140 | let pb = pb.clone(); 141 | 142 | match e { 143 | Ok(e) => Some(tokio::spawn(async move { 144 | pb.inc(1); 145 | 146 | match get_pdb(&e.path()) { 147 | Ok(manifest_str) => Some(manifest_str), 148 | Err(_) => None, 149 | } 150 | })), 151 | 152 | Err(_) => None, 153 | } 154 | }) 155 | .collect(); 156 | 157 | let manifest_path = manifest.unwrap_or(PathBuf::from("manifest")); 158 | let mut output_file = tokio::fs::File::create(manifest_path) 159 | .await 160 | .context("Failed to create output manifest file")?; 161 | 162 | for task in tasks { 163 | if let Some(e) = task.await.unwrap() { 164 | output_file 165 | .write(format!("{}\n", &e).as_bytes()) 166 | .await 167 | .context("Failed to write to output manifest file")?; 168 | } 169 | } 170 | } 171 | Command::Download { manifest, symsrv } => { 172 | /* Read the entire manifest file into a string */ 173 | let manifest_path = manifest.unwrap_or(PathBuf::from("manifest")); 174 | let buf = tokio::fs::read_to_string(&manifest_path) 175 | .await 176 | .context("failed to read manifest file")?; 177 | 178 | /* Split the file into lines and collect into a vector */ 179 | let mut lines: Vec = buf.lines().map(String::from).collect(); 180 | 181 | /* If there is nothing to download, return out early */ 182 | if lines.is_empty() { 183 | println!("Nothing to download"); 184 | return Ok(()); 185 | } 186 | 187 | println!("Original manifest has {} PDBs", lines.len()); 188 | 189 | lines.sort(); 190 | lines.dedup(); 191 | 192 | println!("Deduped manifest has {} PDBs", lines.len()); 193 | 194 | match download_manifest(&symsrv, lines).await { 195 | Ok(_) => println!("Success!"), 196 | Err(e) => println!("Failed: {:?}", e), 197 | } 198 | } 199 | Command::DownloadSingle { 200 | symsrv, 201 | filepath, 202 | message_format, 203 | } => { 204 | use serde_json::json; 205 | 206 | let result: Result<(&'static str, PathBuf), anyhow::Error> = async { 207 | let servers = connect_servers(&symsrv)?; 208 | 209 | // Resolve the PDB for the executable specified. 210 | let e = ManifestEntry::from_str( 211 | &get_pdb(&filepath).context("failed to resolve PDB hash")?, 212 | ) 213 | .unwrap(); 214 | let info = SymFileInfo::RawHash(e.hash); 215 | 216 | for srv in servers.iter() { 217 | let (message, path) = { 218 | if let Some(p) = srv.find_file(&e.name, &info) { 219 | ("file already cached", p) 220 | } else { 221 | let path = srv 222 | .download_file(&e.name, &info) 223 | .await 224 | .context("failed to download PDB")?; 225 | 226 | ("file successfully downloaded", path) 227 | } 228 | }; 229 | 230 | return Ok((message, path)); 231 | } 232 | 233 | anyhow::bail!("no server returned the PDB file") 234 | } 235 | .await; 236 | 237 | match result { 238 | Ok((message, path)) => match message_format { 239 | MessageFormat::Human => { 240 | println!("{}: {}", message, path.to_string_lossy()) 241 | } 242 | MessageFormat::Json => println!( 243 | "{}", 244 | json!({ 245 | "status": "success", 246 | "message": message, 247 | "path": path.to_str().expect("symbol path was not valid utf-8") 248 | }) 249 | ), 250 | }, 251 | Err(e) => { 252 | match message_format { 253 | MessageFormat::Human => println!("operation failed: {e:?}"), 254 | MessageFormat::Json => println!( 255 | "{}", 256 | json!({ 257 | "status": "failed", 258 | "message": format!("{e:#}"), 259 | }) 260 | ), 261 | } 262 | std::process::exit(1); 263 | } 264 | } 265 | } 266 | Command::Filestore { 267 | filepath, 268 | targetpath, 269 | } => { 270 | /* List all files in the directory specified by args[2] */ 271 | let dir = Path::new(&filepath); 272 | let target = Path::new(&targetpath); 273 | let listing = recursive_listdir(&dir); 274 | 275 | listing 276 | .for_each(|entry| async { 277 | if let Ok(e) = entry { 278 | if let Ok(fsname) = get_file_path(&e.path()) { 279 | let fsname = target.join(&fsname); 280 | 281 | if !fsname.exists() { 282 | let dir = fsname.parent().unwrap(); 283 | tokio::fs::create_dir_all(dir) 284 | .await 285 | .expect("Failed to create filestore directory"); 286 | 287 | if let Err(err) = tokio::fs::copy(&e.path(), fsname).await { 288 | println!("Failed to copy file {:?}: {err:#}", &e.path()); 289 | } 290 | } 291 | } 292 | } 293 | }) 294 | .await; 295 | } 296 | Command::Pdbstore { 297 | filepath, 298 | targetpath, 299 | } => { 300 | /* List all files in the directory specified by args[2] */ 301 | let listing = recursive_listdir(&filepath); 302 | 303 | listing 304 | .for_each(|entry| async { 305 | if let Ok(e) = entry { 306 | if let Ok(fsname) = get_pdb_path(&e.path()) { 307 | let fsname = targetpath.join(&fsname); 308 | 309 | if !fsname.exists() { 310 | let dir = fsname.parent().unwrap(); 311 | tokio::fs::create_dir_all(dir) 312 | .await 313 | .expect("Failed to create filestore directory"); 314 | 315 | if let Err(err) = tokio::fs::copy(&e.path(), fsname).await { 316 | println!("Failed to copy file {:?}: {err:#}", &e.path()); 317 | } 318 | } 319 | } 320 | } 321 | }) 322 | .await; 323 | } 324 | Command::Info(i) => match i { 325 | InfoCommand::Pdbhash { filepath } => { 326 | let pdb = get_pdb(&filepath)?; 327 | println!("{}", pdb); 328 | } 329 | }, 330 | } 331 | 332 | Ok(()) 333 | } 334 | 335 | #[tokio::main] 336 | async fn main() { 337 | run().await.unwrap(); 338 | } 339 | -------------------------------------------------------------------------------- /pdblister/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a tiny project to be a quick alternative to symchk for generating 2 | //! manifests. This mimics symchk of the form `symchk /om manifest /r ` 3 | //! but only looks for MZ/PE files. 4 | //! 5 | //! Due to symchk doing some weird things it can often crash or get stuck in 6 | //! infinite loops. Thus this is a stricter (and much faster) alternative. 7 | //! 8 | //! The output manifest is compatible with symchk and thus symchk is currently 9 | //! used for the actual download. To download symbols after this manifest 10 | //! has been generated use `symchk /im manifest /s ` 11 | #![forbid(unsafe_code)] 12 | 13 | use anyhow::Context; 14 | use clap::{Parser, Subcommand, ValueEnum}; 15 | use indicatif::{MultiProgress, ProgressStyle}; 16 | use symsrv::{SymSrvList, SymSrvSpec}; 17 | 18 | use std::io::SeekFrom; 19 | use std::io::{self, Read, Seek}; 20 | use std::path::{Path, PathBuf}; 21 | use std::str::FromStr; 22 | 23 | use futures::{stream, Stream, StreamExt}; 24 | use indicatif::ProgressBar; 25 | use tokio::{ 26 | fs::{self, DirEntry}, 27 | io::AsyncWriteExt, 28 | }; 29 | 30 | use symsrv::{nonblocking::SymSrv, DownloadError, DownloadStatus, SymFileInfo}; 31 | 32 | pub mod pe; 33 | #[allow(dead_code)] 34 | pub mod symsrv; 35 | 36 | #[derive(Clone, Debug, PartialEq, Eq, ValueEnum)] 37 | pub enum MessageFormat { 38 | Human, 39 | Json, 40 | } 41 | 42 | /// Given a `path`, return a stream of all the files recursively found from 43 | /// that path. 44 | pub fn recursive_listdir( 45 | path: impl Into, 46 | ) -> impl Stream> + Send + 'static { 47 | async fn one_level(path: PathBuf, to_visit: &mut Vec) -> io::Result> { 48 | let mut dir = fs::read_dir(path).await?; 49 | let mut files = Vec::new(); 50 | 51 | while let Some(child) = dir.next_entry().await? { 52 | if child.metadata().await?.is_dir() { 53 | to_visit.push(child.path()); 54 | } else { 55 | files.push(child) 56 | } 57 | } 58 | 59 | Ok(files) 60 | } 61 | 62 | stream::unfold(vec![path.into()], |mut to_visit| async { 63 | let path = to_visit.pop()?; 64 | let file_stream = match one_level(path, &mut to_visit).await { 65 | Ok(files) => stream::iter(files).map(Ok).left_stream(), 66 | Err(e) => stream::once(async { Err(e) }).right_stream(), 67 | }; 68 | 69 | Some((file_stream, to_visit)) 70 | }) 71 | .flatten() 72 | } 73 | 74 | pub fn get_pdb_path>(pdbname: P) -> anyhow::Result { 75 | use pdb::PDB; 76 | 77 | let file_name = Path::new( 78 | pdbname 79 | .as_ref() 80 | .file_name() 81 | .context("no filename component on path")?, 82 | ); 83 | 84 | let f = std::fs::File::open(&pdbname).context("failed to open file")?; 85 | let mut pdb = PDB::open(f).context("failed to parse PDB")?; 86 | 87 | // Query the GUID and age. 88 | let pdbi = pdb 89 | .pdb_information() 90 | .context("failed to find PDB information stream")?; 91 | let dbi = pdb 92 | .debug_information() 93 | .context("failed to find DBI stream")?; 94 | 95 | let guid = pdbi.guid; 96 | let age = dbi.age().unwrap_or(pdbi.age); 97 | 98 | Ok(file_name 99 | .join(format!("{:032X}{:x}", guid.as_u128(), age)) 100 | .join(file_name)) 101 | } 102 | 103 | pub fn get_file_path(filename: &Path) -> anyhow::Result { 104 | let (_, _, pe_header, image_size, _) = pe::parse_pe(filename)?; 105 | 106 | let filename = filename 107 | .file_name() 108 | .context("Failed to get file name")? 109 | .to_str() 110 | .context("Failed to convert file name")?; 111 | 112 | let filestr = format!( 113 | "{}/{:08x}{:x}/{}", 114 | filename, 115 | { pe_header.timestamp }, 116 | image_size, 117 | filename 118 | ); 119 | 120 | /* For hashes 121 | let filestr = format!("{},{:08x}{:x},1", 122 | filename.file_name() 123 | .unwrap().to_str().unwrap(), 124 | pe_header.timestamp, 125 | image_size);*/ 126 | 127 | Ok(filestr) 128 | } 129 | 130 | /// Given a `filename`, attempt to parse out any mention of a PDB file in it. 131 | /// 132 | /// This returns success if it successfully parses the MZ, PE, finds a debug 133 | /// header, matches RSDS signature, and contains a valid reference to a PDB. 134 | /// 135 | /// Returns a string which is the same representation you get from `symchk` 136 | /// when outputting a manifest for the PDB ",,1" 137 | pub fn get_pdb(filename: &Path) -> anyhow::Result { 138 | let (mut fd, mz_header, pe_header, _, num_tables) = pe::parse_pe(filename)?; 139 | 140 | /* Load all the data directories into a vector */ 141 | let mut data_dirs = Vec::new(); 142 | for _ in 0..num_tables { 143 | let datadir: pe::ImageDataDirectory = pe::read_struct(&mut fd)?; 144 | data_dirs.push(datadir); 145 | } 146 | 147 | /* Debug directory is at offset 6, validate we have at least 7 entries */ 148 | if data_dirs.len() < 7 { 149 | anyhow::bail!("No debug data directory"); 150 | } 151 | 152 | /* Grab the debug table */ 153 | let debug_table = data_dirs[6]; 154 | if debug_table.vaddr == 0 || debug_table.size == 0 { 155 | anyhow::bail!("Debug directory not present or zero sized"); 156 | } 157 | 158 | /* Validate debug table size is sane */ 159 | let iddlen = std::mem::size_of::() as u32; 160 | let debug_table_ents = debug_table.size / iddlen; 161 | if (debug_table.size % iddlen) != 0 || debug_table_ents == 0 { 162 | anyhow::bail!("No debug entries or not mod ImageDebugDirectory"); 163 | } 164 | 165 | /* Seek to where the section table should be */ 166 | let section_headers = 167 | mz_header.new_header as u64 + 0x18 + pe_header.optional_header_size as u64; 168 | if fd.seek(SeekFrom::Start(section_headers))? != section_headers { 169 | anyhow::bail!("Failed to seek to section table"); 170 | } 171 | 172 | /* Parse all the sections into a vector */ 173 | let mut sections = Vec::new(); 174 | for _ in 0..pe_header.num_sections { 175 | let sechdr: pe::ImageSectionHeader = pe::read_struct(&mut fd)?; 176 | sections.push(sechdr); 177 | } 178 | 179 | let debug_raw_ptr = { 180 | /* Find the section the debug table belongs to */ 181 | let mut debug_data = None; 182 | for section in §ions { 183 | /* We use raw_data_size instead of vsize as we are not loading the 184 | * file and only care about raw contents in the file. 185 | */ 186 | let secrange = section.vaddr..section.vaddr + section.raw_data_size; 187 | 188 | /* Check if the entire debug table is contained in this sections 189 | * virtual address range. 190 | */ 191 | if secrange.contains(&{ debug_table.vaddr }) 192 | && secrange.contains(&(debug_table.vaddr + debug_table.size - 1)) 193 | { 194 | debug_data = Some(debug_table.vaddr - section.vaddr + section.pointer_to_raw_data); 195 | break; 196 | } 197 | } 198 | 199 | match debug_data { 200 | Some(d) => d as u64, 201 | None => anyhow::bail!("Unable to find debug data"), 202 | } 203 | }; 204 | 205 | /* Seek to where the debug directories should be */ 206 | if fd.seek(SeekFrom::Start(debug_raw_ptr))? != debug_raw_ptr { 207 | anyhow::bail!("Failed to seek to debug directories"); 208 | } 209 | 210 | /* Look through all debug table entries for codeview entries */ 211 | for _ in 0..debug_table_ents { 212 | let de: pe::ImageDebugDirectory = pe::read_struct(&mut fd)?; 213 | 214 | if de.typ == pe::IMAGE_DEBUG_TYPE_CODEVIEW { 215 | /* Seek to where the codeview entry should be */ 216 | let cvo = de.pointer_to_raw_data as u64; 217 | if fd.seek(SeekFrom::Start(cvo))? != cvo { 218 | anyhow::bail!("Failed to seek to codeview entry"); 219 | } 220 | 221 | let cv: pe::CodeviewEntry = pe::read_struct(&mut fd)?; 222 | if &cv.signature != b"RSDS" { 223 | anyhow::bail!("No RSDS signature present in codeview ent"); 224 | } 225 | 226 | /* Calculate theoretical string length based on the size of the 227 | * section vs the size of the header */ 228 | let cv_strlen = de.size_of_data as usize - std::mem::size_of_val(&cv); 229 | 230 | /* Read in the debug path */ 231 | let mut dpath = vec![0u8; cv_strlen]; 232 | fd.read_exact(&mut dpath)?; 233 | 234 | /* PDB strings are utf8 and null terminated, find the first null 235 | * and we will split it there. 236 | */ 237 | if let Some(null_strlen) = dpath.iter().position(|&x| x == 0) { 238 | let dpath = std::str::from_utf8(&dpath[..null_strlen])?; 239 | 240 | /* Further, since this path can be a full path, we get only 241 | * the filename component of this path. 242 | */ 243 | if let Some(pdbfilename) = Path::new(dpath).file_name() { 244 | /* This is the format string used by symchk. 245 | * Original is in SymChkCheckFiles() 246 | * "%s,%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x,1" 247 | */ 248 | let guidstr = format!("{},{:08X}{:04X}{:04X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:x},1", 249 | pdbfilename.to_str().context("Failed to get PDB filename")?, 250 | {cv.guid_a}, {cv.guid_b}, {cv.guid_c}, 251 | {cv.guid_d[0]}, {cv.guid_d[1]}, 252 | {cv.guid_d[2]}, {cv.guid_d[3]}, 253 | {cv.guid_d[4]}, {cv.guid_d[5]}, 254 | {cv.guid_d[6]}, {cv.guid_d[7]}, 255 | {cv.age}); 256 | return Ok(guidstr); 257 | } else { 258 | anyhow::bail!("Could not parse file from RSDS path"); 259 | } 260 | } else { 261 | anyhow::bail!("Failed to find null terminiator in RSDS"); 262 | } 263 | } 264 | } 265 | 266 | anyhow::bail!("Failed to find RSDS codeview directory") 267 | } 268 | 269 | #[allow(dead_code)] 270 | #[derive(Debug, Clone)] 271 | pub struct ManifestEntry { 272 | /// The PDB's name 273 | pub name: String, 274 | /// The hash plus age of the PDB 275 | pub hash: String, 276 | /// The version number (maybe?) 277 | version: u32, 278 | } 279 | 280 | impl FromStr for ManifestEntry { 281 | type Err = anyhow::Error; 282 | 283 | fn from_str(s: &str) -> Result { 284 | let elements = s.split(',').collect::>(); 285 | if elements.len() != 3 { 286 | anyhow::bail!("Invalid manifest line: \"{s}\""); 287 | } 288 | 289 | Ok(Self { 290 | name: elements[0].to_string(), 291 | hash: elements[1].to_string(), 292 | version: u32::from_str(elements[2])?, 293 | }) 294 | } 295 | } 296 | 297 | /// Attempt to connect to the servers described in the server string. 298 | pub fn connect_servers(srvstr: &str) -> anyhow::Result> { 299 | let srvlist = SymSrvList::from_str(srvstr).context("failed to parse server list")?; 300 | 301 | match srvlist 302 | .0 303 | .iter() 304 | .map(|s| SymSrv::connect(s.clone()).map_err(|e| (s.clone(), e))) 305 | .collect::, (SymSrvSpec, anyhow::Error)>>() 306 | { 307 | Ok(srv) => Ok(srv.into_boxed_slice()), 308 | Err((s, e)) => Err(e.context(format!("failed to connect to server {s}"))), 309 | } 310 | } 311 | 312 | pub async fn download_manifest(srvstr: &str, files: Vec) -> anyhow::Result<()> { 313 | let servers = connect_servers(srvstr)?; 314 | 315 | // http://patshaughnessy.net/2020/1/20/downloading-100000-files-using-async-rust 316 | // The following code is based off of the above blog post. 317 | let m = MultiProgress::new(); 318 | 319 | // Create a progress bar. 320 | let pb = m.add(ProgressBar::new(files.len() as u64)); 321 | pb.set_style( 322 | ProgressStyle::default_bar() 323 | .template("[{elapsed_precise}] {wide_bar:.cyan/blue} {pos:>10}/{len:10} ({eta}) {msg}") 324 | .unwrap() 325 | .progress_chars("█▉▊▋▌▍▎▏ "), 326 | ); 327 | 328 | // Set up our asynchronous code block. 329 | // This block will be lazily executed when something awaits on it, such as the tokio thread pool below. 330 | let queries = futures::stream::iter( 331 | // Map the files vector using a closure, such that it's converted from a Vec 332 | // into a Vec> 333 | files.into_iter().map(|line| { 334 | // Take explicit references to a few variables and move them into the async block. 335 | let servers = &servers; 336 | let pb = pb.clone(); 337 | let m = &m; 338 | 339 | async move { 340 | pb.inc(1); 341 | 342 | let e = ManifestEntry::from_str(&line).unwrap(); 343 | let info = SymFileInfo::RawHash(e.hash); 344 | 345 | for srv in servers.iter() { 346 | if srv.find_file(&e.name, &info).is_some() { 347 | return Ok(DownloadStatus::AlreadyExists); 348 | } 349 | 350 | match srv.download_file_progress(&e.name, &info, m).await { 351 | Ok(_) => return Ok(DownloadStatus::DownloadedOk), 352 | Err(_e) => {} 353 | }; 354 | } 355 | 356 | Err(DownloadError::FileNotFound) 357 | } 358 | }), 359 | ) 360 | .buffer_unordered(32) 361 | .collect::>>(); 362 | 363 | // N.B: The buffer_unordered bit above allows us to feed in 64 requests at a time to tokio. 364 | // That way we don't exhaust system resources in the networking stack or filesystem. 365 | let output = queries.await; 366 | 367 | pb.finish(); 368 | 369 | let mut ok = 0u64; 370 | let mut ok_exists = 0u64; 371 | let mut err = 0u64; 372 | 373 | // Collect output results. 374 | output.iter().for_each(|x| match x { 375 | Err(_) => { 376 | err += 1; 377 | } 378 | 379 | Ok(s) => match s { 380 | DownloadStatus::AlreadyExists => ok_exists += 1, 381 | DownloadStatus::DownloadedOk => ok += 1, 382 | }, 383 | }); 384 | 385 | println!("{} files failed to download", err); 386 | println!("{} files already downloaded", ok_exists); 387 | println!("{} files downloaded successfully", ok); 388 | 389 | Ok(()) 390 | } 391 | -------------------------------------------------------------------------------- /pdblister/src/pe.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality for parsing MZ/PE files 2 | use std::io::{self, Read, Seek, SeekFrom}; 3 | use std::path::Path; 4 | 5 | use zerocopy::{AsBytes, FromBytes}; 6 | use zerocopy_derive::FromZeroes; 7 | 8 | #[repr(C, packed)] 9 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 10 | pub struct MZHeader { 11 | pub signature: [u8; 2], 12 | pub last_page_bytes: u16, 13 | pub num_pages: u16, 14 | pub num_relocations: u16, 15 | pub header_size: u16, 16 | pub min_memory: u16, 17 | pub max_memory: u16, 18 | pub initial_ss: u16, 19 | pub initial_sp: u16, 20 | pub checksum: u16, 21 | pub entry: u32, 22 | pub ptr_relocation: u16, 23 | pub overlay: u16, 24 | pub reserved: [u8; 32], 25 | pub new_header: u32, 26 | } 27 | 28 | #[repr(C, packed)] 29 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 30 | pub struct PEHeader { 31 | pub signature: [u8; 4], 32 | pub machine: u16, 33 | pub num_sections: u16, 34 | pub timestamp: u32, 35 | pub ptr_symtable: u32, 36 | pub num_smtable: u32, 37 | pub optional_header_size: u16, 38 | pub characteristics: u16, 39 | } 40 | 41 | const IMAGE_FILE_MACHINE_I386: u16 = 0x014c; 42 | const IMAGE_FILE_MACHINE_IA64: u16 = 0x0200; 43 | const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; 44 | 45 | #[repr(C, packed)] 46 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 47 | pub struct WindowsPEHeader32 { 48 | pub magic: u16, 49 | pub linker_major_version: u8, 50 | pub linker_minor_version: u8, 51 | pub size_of_code: u32, 52 | pub size_of_initialized_data: u32, 53 | pub size_of_uninitialized_data: u32, 54 | pub entry: u32, 55 | pub code_base: u32, 56 | pub data_base: u32, 57 | pub image_base: u32, 58 | pub section_align: u32, 59 | pub file_align: u32, 60 | pub major_os_version: u16, 61 | pub minor_os_version: u16, 62 | pub major_image_version: u16, 63 | pub minor_image_version: u16, 64 | pub major_subsystem_version: u16, 65 | pub minor_subsystem_version: u16, 66 | pub win32_version: u32, 67 | pub size_of_image: u32, 68 | pub size_of_headers: u32, 69 | pub checksum: u32, 70 | pub subsystem: u16, 71 | pub dll_characteristics: u16, 72 | pub size_of_stack_reserve: u32, 73 | pub size_of_stack_commit: u32, 74 | pub size_of_heap_reserve: u32, 75 | pub size_of_heap_commit: u32, 76 | pub loader_flags: u32, 77 | pub num_tables: u32, 78 | } 79 | 80 | #[repr(C, packed)] 81 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 82 | pub struct WindowsPEHeader64 { 83 | pub magic: u16, 84 | pub linker_major_version: u8, 85 | pub linker_minor_version: u8, 86 | pub size_of_code: u32, 87 | pub size_of_initialized_data: u32, 88 | pub size_of_uninitialized_data: u32, 89 | pub entry: u32, 90 | pub code_base: u32, 91 | pub image_base: u64, 92 | pub section_align: u32, 93 | pub file_align: u32, 94 | pub major_os_version: u16, 95 | pub minor_os_version: u16, 96 | pub major_image_version: u16, 97 | pub minor_image_version: u16, 98 | pub major_subsystem_version: u16, 99 | pub minor_subsystem_version: u16, 100 | pub win32_version: u32, 101 | pub size_of_image: u32, 102 | pub size_of_headers: u32, 103 | pub checksum: u32, 104 | pub subsystem: u16, 105 | pub dll_characteristics: u16, 106 | pub size_of_stack_reserve: u64, 107 | pub size_of_stack_commit: u64, 108 | pub size_of_heap_reserve: u64, 109 | pub size_of_heap_commit: u64, 110 | pub loader_flags: u32, 111 | pub num_tables: u32, 112 | } 113 | 114 | #[repr(C, packed)] 115 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 116 | pub struct ImageDataDirectory { 117 | pub vaddr: u32, 118 | pub size: u32, 119 | } 120 | 121 | #[repr(C, packed)] 122 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 123 | pub struct ImageSectionHeader { 124 | pub name: [u8; 8], 125 | pub vsize: u32, 126 | pub vaddr: u32, 127 | pub raw_data_size: u32, 128 | pub pointer_to_raw_data: u32, 129 | pub pointer_to_relocations: u32, 130 | pub pointer_to_line_numbers: u32, 131 | pub number_of_relocations: u16, 132 | pub number_of_line_numbers: u16, 133 | pub characteristics: u32, 134 | } 135 | 136 | #[repr(C, packed)] 137 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 138 | pub struct ImageDebugDirectory { 139 | pub characteristics: u32, 140 | pub timestamp: u32, 141 | pub major_version: u16, 142 | pub minor_version: u16, 143 | pub typ: u32, 144 | pub size_of_data: u32, 145 | pub address_of_raw_data: u32, 146 | pub pointer_to_raw_data: u32, 147 | } 148 | 149 | #[repr(C, packed)] 150 | #[derive(Clone, Copy, AsBytes, FromBytes, FromZeroes)] 151 | pub struct CodeviewEntry { 152 | pub signature: [u8; 4], // RSDS 153 | pub guid_a: u32, 154 | pub guid_b: u16, 155 | pub guid_c: u16, 156 | pub guid_d: [u8; 8], 157 | pub age: u32, 158 | } 159 | 160 | pub const IMAGE_DEBUG_TYPE_CODEVIEW: u32 = 2; 161 | 162 | /// Read a structure from a file stream, directly interpreting the raw bytes 163 | /// of the file as T. 164 | pub fn read_struct(fd: &mut std::fs::File) -> io::Result { 165 | let mut ret: T = T::new_zeroed(); 166 | fd.read_exact(ret.as_bytes_mut())?; 167 | 168 | Ok(ret) 169 | } 170 | 171 | pub fn parse_pe(filename: &Path) -> anyhow::Result<(std::fs::File, MZHeader, PEHeader, u32, u32)> { 172 | let mut fd = std::fs::File::open(filename)?; 173 | 174 | /* Check for an MZ header */ 175 | let mz_header: MZHeader = read_struct(&mut fd)?; 176 | if &mz_header.signature != b"MZ" { 177 | anyhow::bail!("No MZ header present"); 178 | } 179 | 180 | /* Seek to where the PE header should be */ 181 | if fd.seek(SeekFrom::Start(mz_header.new_header as u64))? != mz_header.new_header as u64 { 182 | anyhow::bail!("Failed to seek to PE header"); 183 | } 184 | 185 | /* Check for a PE header */ 186 | let pe_header: PEHeader = read_struct(&mut fd)?; 187 | if &pe_header.signature != b"PE\0\0" { 188 | anyhow::bail!("No PE header present"); 189 | } 190 | 191 | /* Grab the number of tables from the bitness-specific table */ 192 | let (image_size, num_tables) = match pe_header.machine { 193 | IMAGE_FILE_MACHINE_I386 => { 194 | let opthdr: WindowsPEHeader32 = read_struct(&mut fd)?; 195 | (opthdr.size_of_image, opthdr.num_tables) 196 | } 197 | IMAGE_FILE_MACHINE_IA64 | IMAGE_FILE_MACHINE_AMD64 => { 198 | let opthdr: WindowsPEHeader64 = read_struct(&mut fd)?; 199 | (opthdr.size_of_image, opthdr.num_tables) 200 | } 201 | _ => anyhow::bail!("Unsupported PE machine type"), 202 | }; 203 | 204 | Ok((fd, mz_header, pe_header, image_size, num_tables)) 205 | } 206 | -------------------------------------------------------------------------------- /pdblister/src/symsrv/blocking.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use super::{nonblocking, DownloadError, SymFileInfo, SymSrvSpec}; 4 | 5 | use tokio::runtime::Runtime; 6 | 7 | #[derive(Debug)] 8 | pub struct SymSrv { 9 | inner: nonblocking::SymSrv, 10 | rt: Runtime, 11 | } 12 | 13 | impl SymSrv { 14 | pub fn new(spec: SymSrvSpec) -> anyhow::Result { 15 | Ok(Self { 16 | inner: nonblocking::SymSrv::connect(spec)?, 17 | rt: tokio::runtime::Builder::new_current_thread() 18 | .enable_all() 19 | .build()?, 20 | }) 21 | } 22 | 23 | /// Attempt to find a single file in the symbol store associated with this context. 24 | /// 25 | /// If the file is found, its cache path will be returned. 26 | pub fn find_file(&self, name: &str, info: &SymFileInfo) -> Option { 27 | self.inner.find_file(name, info) 28 | } 29 | 30 | /// Download and cache a single file in the symbol store associated with this context, 31 | /// and then return its path on the local system. 32 | pub fn download_file(&self, name: &str, info: &SymFileInfo) -> Result { 33 | self.rt.block_on(self.inner.download_file(name, info)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pdblister/src/symsrv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blocking; 2 | pub mod nonblocking; 3 | 4 | use std::{path::PathBuf, str::FromStr}; 5 | use thiserror::Error; 6 | 7 | /// Information about a symbol file resource. 8 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 9 | pub enum SymFileInfo { 10 | Exe(ExeInfo), 11 | Pdb(PdbInfo), 12 | /// A raw symsrv-compatible hash. 13 | RawHash(String), 14 | } 15 | 16 | impl ToString for SymFileInfo { 17 | fn to_string(&self) -> String { 18 | // The middle component of the resource's path on a symbol. 19 | match self { 20 | SymFileInfo::Exe(i) => i.to_string(), 21 | SymFileInfo::Pdb(i) => i.to_string(), 22 | SymFileInfo::RawHash(h) => h.clone(), 23 | } 24 | } 25 | } 26 | 27 | /// Executable file information relevant to a symbol server. 28 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 29 | pub struct ExeInfo { 30 | pub timestamp: u32, 31 | pub size: u32, 32 | } 33 | 34 | impl ToString for ExeInfo { 35 | fn to_string(&self) -> String { 36 | format!("{:08x}{:x}", self.timestamp, self.size) 37 | } 38 | } 39 | 40 | /// PDB file information relevant to a symbol server. 41 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 42 | pub struct PdbInfo { 43 | pub guid: u128, 44 | pub age: u32, 45 | } 46 | 47 | impl ToString for PdbInfo { 48 | fn to_string(&self) -> String { 49 | format!("{:032X}{:x}", self.guid, self.age) 50 | } 51 | } 52 | 53 | #[derive(Error, Debug)] 54 | pub enum DownloadError { 55 | /// Server returned a 404 error. Try the next one. 56 | #[error("server returned 404 not found")] 57 | FileNotFound, 58 | 59 | #[error("error requesting file")] 60 | Request(#[from] reqwest::Error), 61 | 62 | #[error(transparent)] 63 | Other(#[from] anyhow::Error), 64 | } 65 | 66 | #[derive(Debug, Clone, PartialEq, Eq)] 67 | pub enum DownloadStatus { 68 | /// The symbol file already exists in the filesystem. 69 | AlreadyExists, 70 | /// The symbol file was successfully downloaded from the remote server. 71 | DownloadedOk, 72 | } 73 | 74 | /// A symbol server, defined by the user with the syntax `SRV**`. 75 | #[derive(Debug, Clone, PartialEq, Eq)] 76 | pub struct SymSrvSpec { 77 | /// The base URL for a symbol server, e.g: `https://msdl.microsoft.com/download/symbols` 78 | pub server_url: String, 79 | /// The base path for the local symbol cache, e.g: `C:\Symcache` 80 | pub cache_path: PathBuf, 81 | } 82 | 83 | impl FromStr for SymSrvSpec { 84 | type Err = anyhow::Error; 85 | 86 | fn from_str(srv: &str) -> Result { 87 | // Split the path out by asterisks. 88 | let directives: Vec<&str> = srv.split('*').collect(); 89 | 90 | // Ensure that the path starts with `SRV*` - the only form we currently support. 91 | match directives.first() { 92 | // Simply exit the match statement if the directive is "SRV" 93 | Some(x) => { 94 | if x.eq_ignore_ascii_case("SRV") { 95 | if directives.len() != 3 { 96 | anyhow::bail!("Unsupported server string form; only 'SRV**' supported"); 97 | } 98 | 99 | // Alright, the directive is of the proper form. Return the server and filepath. 100 | return Ok(SymSrvSpec { 101 | server_url: directives[2].to_string(), 102 | cache_path: directives[1].into(), 103 | }); 104 | } 105 | } 106 | 107 | None => { 108 | anyhow::bail!("Unsupported server string form; only 'SRV**' supported"); 109 | } 110 | }; 111 | 112 | anyhow::bail!( 113 | "Unsupported server string form; only 'SRV**' supported" 114 | ); 115 | } 116 | } 117 | 118 | impl std::fmt::Display for SymSrvSpec { 119 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 120 | write!(f, "SRV*{}*{}", self.cache_path.display(), self.server_url) 121 | } 122 | } 123 | 124 | /// A list of symbol servers, defined by the user with a semicolon-separated list. 125 | #[derive(Debug, Clone, PartialEq, Eq)] 126 | pub struct SymSrvList(pub Box<[SymSrvSpec]>); 127 | 128 | impl FromStr for SymSrvList { 129 | type Err = anyhow::Error; 130 | 131 | fn from_str(s: &str) -> Result { 132 | let server_list: Vec<&str> = s.split(';').collect(); 133 | if server_list.is_empty() { 134 | anyhow::bail!("Invalid server string"); 135 | } 136 | 137 | let vec = server_list 138 | .into_iter() 139 | .map(|symstr| symstr.parse::()) 140 | .collect::>>()?; 141 | 142 | Ok(SymSrvList(vec.into_boxed_slice())) 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod test { 148 | use super::*; 149 | 150 | #[test] 151 | fn symsrv_spec() { 152 | assert_eq!( 153 | SymSrvSpec::from_str("SRV*C:\\Symbols*https://msdl.microsoft.com/download/symbols") 154 | .unwrap(), 155 | SymSrvSpec { 156 | server_url: "https://msdl.microsoft.com/download/symbols".to_string(), 157 | cache_path: "C:\\Symbols".into(), 158 | } 159 | ); 160 | 161 | assert_eq!( 162 | SymSrvSpec::from_str("srv*C:\\Symbols*https://msdl.microsoft.com/download/symbols") 163 | .unwrap(), 164 | SymSrvSpec { 165 | server_url: "https://msdl.microsoft.com/download/symbols".to_string(), 166 | cache_path: "C:\\Symbols".into(), 167 | } 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /pdblister/src/symsrv/nonblocking.rs: -------------------------------------------------------------------------------- 1 | // #![allow(unknown_lints)] 2 | // #![warn(clippy::all)] 3 | // #![allow(clippy::needless_return)] 4 | 5 | use std::path::PathBuf; 6 | 7 | extern crate futures; 8 | extern crate indicatif; 9 | extern crate reqwest; 10 | extern crate tokio; 11 | 12 | use crate::{DownloadError, DownloadStatus, SymFileInfo, SymSrvSpec}; 13 | 14 | use anyhow::Context; 15 | use indicatif::{MultiProgress, ProgressBar}; 16 | 17 | use tokio::io::AsyncWriteExt; 18 | 19 | mod style { 20 | use indicatif::ProgressStyle; 21 | 22 | pub fn bar() -> ProgressStyle { 23 | ProgressStyle::default_bar() 24 | .template( 25 | "[{elapsed_precise}] {bar:.cyan/blue} {bytes:>12}/{total_bytes:12} {wide_msg}", 26 | ) 27 | .unwrap() 28 | .progress_chars("█▉▊▋▌▍▎▏ ") 29 | } 30 | 31 | pub fn spinner() -> ProgressStyle { 32 | ProgressStyle::default_bar() 33 | .template("[{elapsed_precise}] {spinner} {bytes_per_sec:>10} {wide_msg}") 34 | .unwrap() 35 | } 36 | } 37 | 38 | enum RemoteFileType { 39 | /// HTTP-accessible URL (with a response already received) 40 | Url(reqwest::Response), 41 | /// Path on a network share 42 | Path(String), 43 | } 44 | 45 | /// Attempt to download a single resource from a single symbol server. 46 | async fn download_single( 47 | client: &reqwest::Client, 48 | srv: &SymSrvSpec, 49 | mp: Option<&MultiProgress>, 50 | name: &str, 51 | hash: &str, 52 | ) -> Result<(DownloadStatus, PathBuf), DownloadError> { 53 | // e.g: "ntkrnlmp.pdb/32C1A669D5FFEFD41091F636CFDB6E991" 54 | let file_rel_folder = format!("{}/{}", name, hash); 55 | 56 | // The name of the file on the local filesystem 57 | let file_name = srv.cache_path.join(&file_rel_folder).join(name); 58 | // The path to the file's folder on the remote server 59 | let file_folder_url = format!("{}/{}", srv.server_url, file_rel_folder); 60 | 61 | // Attempt to remove any existing temporary files first. 62 | // Silently ignore failures since we don't care if this fails. 63 | let file_name_tmp = file_name.with_extension("pdb.tmp"); 64 | let _ = tokio::fs::remove_file(&file_name_tmp).await; 65 | 66 | // Check to see if the file already exists. If so, skip it. 67 | if std::path::Path::new(&file_name).exists() { 68 | return Ok((DownloadStatus::AlreadyExists, file_name.into())); 69 | } 70 | 71 | // Attempt to retrieve the file. 72 | let remote_file = { 73 | let pdb_req = client 74 | .get::<&str>(&format!("{}/{}", file_folder_url, name)) 75 | .send() 76 | .await?; 77 | if pdb_req.status().is_success() { 78 | if let Some(mime) = pdb_req.headers().get(reqwest::header::CONTENT_TYPE) { 79 | let mime = mime 80 | .to_str() 81 | .expect("Content-Type header not a valid string") 82 | .parse::() 83 | .expect("Content-Type header not a valid MIME type"); 84 | 85 | if mime.subtype() == mime::HTML { 86 | // Azure DevOps will do this if the authentication header isn't correct... 87 | panic!( 88 | "Server {} returned an invalid Content-Type of {mime}", 89 | srv.server_url 90 | ); 91 | } 92 | } 93 | 94 | RemoteFileType::Url(pdb_req) 95 | } else { 96 | // Try a `file.ptr` redirection URL 97 | let fileptr_req = client 98 | .get::<&str>(&format!("{}/file.ptr", file_folder_url)) 99 | .send() 100 | .await?; 101 | if !fileptr_req.status().is_success() { 102 | // Attempt another server instead 103 | Err(DownloadError::FileNotFound)?; 104 | } 105 | 106 | let url = fileptr_req 107 | .text() 108 | .await 109 | .context("failed to get file.ptr contents")?; 110 | 111 | // FIXME: Would prefer not to unwrap the iterator results... 112 | let mut url_iter = url.split(':'); 113 | let url_type = url_iter.next().unwrap(); 114 | let url = url_iter.next().unwrap(); 115 | 116 | match url_type { 117 | "PATH" => RemoteFileType::Path(url.to_string()), 118 | "MSG" => return Err(DownloadError::FileNotFound), // Try another server. 119 | typ => { 120 | unimplemented!( 121 | "Unknown symbol redirection pointer type {typ}!\n{url_type}:{url}" 122 | ); 123 | } 124 | } 125 | } 126 | }; 127 | 128 | // Create the directory tree. 129 | tokio::fs::create_dir_all(srv.cache_path.join(file_rel_folder)) 130 | .await 131 | .context("failed to create symbol directory tree")?; 132 | 133 | match remote_file { 134 | RemoteFileType::Url(mut res) => { 135 | // N.B: If the server sends us a content-length header, use it to display a progress bar. 136 | // Otherwise, just display a spinner progress bar. 137 | // TODO: Should have the library user provide a trait that allows us to create a progress bar 138 | // in abstract 139 | let dl_pb = if let Some(m) = mp { 140 | let dl_pb = match res.content_length() { 141 | Some(len) => { 142 | let dl_pb = m.add(ProgressBar::new(len)); 143 | dl_pb.set_style(style::bar()); 144 | 145 | dl_pb 146 | } 147 | 148 | None => { 149 | let dl_pb = m.add(ProgressBar::new_spinner()); 150 | dl_pb.set_style(style::spinner()); 151 | dl_pb.enable_steady_tick(std::time::Duration::from_millis(5)); 152 | 153 | dl_pb 154 | } 155 | }; 156 | 157 | dl_pb.set_message(format!("{}/{}", hash, name)); 158 | Some(dl_pb) 159 | } else { 160 | None 161 | }; 162 | 163 | // Create the output file. 164 | let mut file = tokio::fs::File::create(&file_name_tmp) 165 | .await 166 | .context("failed to create output pdb")?; 167 | 168 | // N.B: We use this in lieu of tokio::io::copy so we can update the download progress. 169 | while let Some(chunk) = res.chunk().await.context("failed to download pdb chunk")? { 170 | if let Some(dl_pb) = &dl_pb { 171 | dl_pb.inc(chunk.len() as u64); 172 | } 173 | 174 | file.write(&chunk) 175 | .await 176 | .context("failed to write pdb chunk")?; 177 | } 178 | 179 | // Rename the temporary copy to the final name 180 | tokio::fs::rename(&file_name_tmp, &file_name) 181 | .await 182 | .context("failed to rename pdb")?; 183 | 184 | Ok((DownloadStatus::DownloadedOk, file_name.into())) 185 | } 186 | 187 | RemoteFileType::Path(path) => { 188 | // Attempt to open the file via the filesystem. 189 | let mut remote_file = tokio::fs::File::open(path) 190 | .await 191 | .context("failed to open remote file")?; 192 | let metadata = remote_file 193 | .metadata() 194 | .await 195 | .context("failed to fetch remote metadata")?; 196 | 197 | let dl_pb = if let Some(m) = mp { 198 | let dl_pb = m.add(ProgressBar::new(metadata.len())); 199 | dl_pb.set_style(style::bar()); 200 | 201 | dl_pb.set_message(format!("{}/{}", hash, name)); 202 | 203 | Some(dl_pb) 204 | } else { 205 | None 206 | }; 207 | 208 | // Create the output file. 209 | let mut file = tokio::fs::File::create(&file_name_tmp) 210 | .await 211 | .context("failed to create output pdb")?; 212 | 213 | if let Some(dl_pb) = dl_pb { 214 | tokio::io::copy(&mut dl_pb.wrap_async_read(remote_file), &mut file) 215 | .await 216 | .context("failed to copy pdb")?; 217 | } else { 218 | tokio::io::copy(&mut remote_file, &mut file) 219 | .await 220 | .context("failed to copy pdb")?; 221 | } 222 | 223 | // Rename the temporary copy to the final name 224 | tokio::fs::rename(&file_name_tmp, &file_name) 225 | .await 226 | .context("failed to rename pdb")?; 227 | 228 | Ok((DownloadStatus::DownloadedOk, file_name.into())) 229 | } 230 | } 231 | } 232 | 233 | /// Connect to Azure and authenticate requests using a PAT. 234 | /// 235 | /// Reference: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows 236 | fn connect_pat(token: &str) -> anyhow::Result { 237 | use reqwest::header; 238 | 239 | // N.B: According to ADO documentation, the token needs to be preceded by an arbitrary 240 | // string followed by a colon. The arbitrary string can be empty. 241 | let b64 = base64::encode(format!(":{token}")); 242 | 243 | let mut headers = header::HeaderMap::new(); 244 | let auth_value = header::HeaderValue::from_str(&format!("Basic {b64}"))?; 245 | headers.insert(header::AUTHORIZATION, auth_value); 246 | 247 | Ok(reqwest::Client::builder() 248 | .default_headers(headers) 249 | .https_only(true) 250 | .build()?) 251 | } 252 | 253 | fn connect_server(srv: &SymSrvSpec) -> anyhow::Result { 254 | // Determine if the URL is a known URL that requires OAuth2 authorization. 255 | use url::{Host, Url}; 256 | 257 | let url = Url::parse(&srv.server_url) 258 | .context(format!("invalid server URL: \"{}\"", &srv.server_url))?; 259 | match url.host() { 260 | Some(Host::Domain(d)) => { 261 | match d { 262 | // Azure DevOps 263 | d if d.ends_with("artifacts.visualstudio.com") || d.ends_with("dev.azure.com") => { 264 | // Try and find the PAT for ADO from URL basic authentication. 265 | let pat = url 266 | .password() 267 | .map(|p| p.to_string()) 268 | .context("ADO requires a PAT for authentication")?; 269 | 270 | Ok(connect_pat(&pat)?) 271 | } 272 | 273 | _ => { 274 | // Unknown URL; return a fresh client. 275 | Ok(reqwest::Client::new()) 276 | } 277 | } 278 | } 279 | Some(Host::Ipv4(_) | Host::Ipv6(_)) | None => { 280 | // Just return a new client. 281 | Ok(reqwest::Client::new()) 282 | } 283 | } 284 | } 285 | 286 | #[derive(Debug, Clone)] 287 | pub struct SymSrv { 288 | spec: SymSrvSpec, 289 | client: reqwest::Client, 290 | } 291 | 292 | impl SymSrv { 293 | /// Attempt to connect to the specified symbol server. 294 | pub fn connect(spec: SymSrvSpec) -> anyhow::Result { 295 | Ok(Self { 296 | client: connect_server(&spec)?, 297 | spec, 298 | }) 299 | } 300 | 301 | /// Retrieve the associated server specification from this connection. 302 | pub fn spec(&self) -> SymSrvSpec { 303 | self.spec.clone() 304 | } 305 | 306 | /// Attempt to find a single file in the symbol store associated with this context. 307 | /// 308 | /// If the file is found, its cache path will be returned. 309 | pub fn find_file(&self, name: &str, info: &SymFileInfo) -> Option { 310 | let hash = info.to_string(); 311 | 312 | // The file should be in each cache directory under the following path: 313 | // "///" 314 | let path = PathBuf::from(&self.spec.cache_path) 315 | .join(name) 316 | .join(hash) 317 | .join(name); 318 | 319 | path.exists().then_some(path) 320 | } 321 | 322 | /// Download and cache a single file in the symbol store associated with this context, 323 | /// and then return its path on the local system. 324 | pub async fn download_file( 325 | &self, 326 | name: &str, 327 | info: &SymFileInfo, 328 | ) -> Result { 329 | let hash = info.to_string(); 330 | 331 | download_single(&self.client, &self.spec, None, name, &hash) 332 | .await 333 | .map(|r| r.1) 334 | } 335 | 336 | /// Download (displaying progress) and cache a single file in the symbol store associated with this context, 337 | /// and then return its path on the local system. 338 | pub async fn download_file_progress( 339 | &self, 340 | name: &str, 341 | info: &SymFileInfo, 342 | mp: &MultiProgress, 343 | ) -> Result { 344 | let hash = info.to_string(); 345 | 346 | download_single(&self.client, &self.spec, Some(mp), name, &hash) 347 | .await 348 | .map(|r| r.1) 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /read_write_driver/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /read_write_driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "read_write_driver" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Christopher Vella "] 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wdk = "0.2.0" 10 | wdk-alloc = "0.2.0" 11 | wdk-panic = "0.2.0" 12 | wdk-sys = "0.2.0" 13 | lazy_static = "1.4.0" 14 | 15 | [dependencies.iced-x86] 16 | version = "1.21.0" 17 | default-features = false 18 | features = ["decoder", "no_std"] 19 | 20 | [build-dependencies] 21 | wdk-build = "0.2.0" 22 | 23 | [lib] 24 | crate-type = ["cdylib"] 25 | 26 | [profile.release] 27 | panic = "abort" 28 | debug = true 29 | 30 | [profile.dev] 31 | panic = "abort" 32 | 33 | [package.metadata.wdk] 34 | 35 | 36 | -------------------------------------------------------------------------------- /read_write_driver/Makefile.toml: -------------------------------------------------------------------------------- 1 | extend = "target/rust-driver-makefile.toml" 2 | 3 | [env] 4 | CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true 5 | 6 | [config] 7 | load_script = ''' 8 | #!@rust 9 | //! ```cargo 10 | //! [dependencies] 11 | //! wdk-build = "0.2.0" 12 | //! ``` 13 | #![allow(unused_doc_comments)] 14 | 15 | wdk_build::cargo_make::load_rust_driver_makefile()? 16 | ''' 17 | -------------------------------------------------------------------------------- /read_write_driver/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), wdk_build::ConfigError> { 2 | wdk_build::Config::from_env_auto()?.configure_binary_build(); 3 | Ok(()) 4 | } 5 | -------------------------------------------------------------------------------- /read_write_driver/read_write_driver.inx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kharos102/ReadWriteDriverSample/af4399f488c5a9d78c963d0740ef392f261d144b/read_write_driver/read_write_driver.inx -------------------------------------------------------------------------------- /read_write_driver/src/cpu.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | pub unsafe fn rdtsc() -> u64 { 4 | let high_bits: u32; 5 | let low_bits: u32; 6 | asm!("rdtsc", out("edx") high_bits, out("eax") low_bits); 7 | ((high_bits as u64) << 32) | low_bits as u64 8 | } 9 | -------------------------------------------------------------------------------- /read_write_driver/src/interrupts.rs: -------------------------------------------------------------------------------- 1 | use crate::mdl::{build_mdl, make_mdl_pages_writeable, MyMDL}; 2 | use alloc::sync::Arc; 3 | use alloc::vec; 4 | use alloc::vec::Vec; 5 | use core::arch::{asm, global_asm}; 6 | use core::cell::UnsafeCell; 7 | use core::hint::black_box; 8 | use core::ops::{Deref, DerefMut}; 9 | use core::ptr::addr_of_mut; 10 | use core::sync::atomic::Ordering::SeqCst; 11 | use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize}; 12 | pub(crate) static PAGE_FAULT_HIT: AtomicBool = AtomicBool::new(false); 13 | 14 | pub(crate) static PREVIOUS_PAGE_FAULT_HANDLER: AtomicUsize = AtomicUsize::new(0); 15 | 16 | pub(crate) static PROBING_ADDRESS: AtomicU64 = AtomicU64::new(0); 17 | 18 | pub(crate) static mut CACHED_IDT: Option>> = None; 19 | 20 | static mut JUNK: u64 = 0; 21 | 22 | extern "C" { 23 | fn page_fault_handler(); 24 | } 25 | 26 | // Interrupt lock will guard the provided raw pointer by only allowing access 27 | // when going through our `lock()` function that disables interrupts with the `cli` instruction 28 | // and provides an InterruptGuard that re-enables interrupts with the `sti` instruction when dropped. 29 | pub(crate) struct InterruptLock { 30 | value: UnsafeCell, 31 | } 32 | 33 | impl InterruptLock { 34 | pub(crate) fn new(value: T) -> Self { 35 | InterruptLock { 36 | value: UnsafeCell::new(value), 37 | } 38 | } 39 | 40 | pub(crate) fn lock(&self) -> InterruptGuard { 41 | // Disable interrupts 42 | unsafe { 43 | asm!("cli"); 44 | } 45 | InterruptGuard { cell: self } 46 | } 47 | } 48 | 49 | pub struct InterruptGuard<'a, T> { 50 | cell: &'a InterruptLock, 51 | } 52 | 53 | impl<'a, T> Drop for InterruptGuard<'a, T> { 54 | fn drop(&mut self) { 55 | // Re-enable interrupts 56 | unsafe { 57 | asm!("sti"); 58 | } 59 | } 60 | } 61 | 62 | impl<'a, T> Deref for InterruptGuard<'a, T> { 63 | type Target = T; 64 | 65 | fn deref(&self) -> &Self::Target { 66 | unsafe { &*self.cell.value.get() } 67 | } 68 | } 69 | 70 | impl<'a, T> DerefMut for InterruptGuard<'a, T> { 71 | fn deref_mut(&mut self) -> &mut Self::Target { 72 | unsafe { &mut *self.cell.value.get() } 73 | } 74 | } 75 | 76 | #[derive(Copy, Clone)] 77 | #[repr(C, packed(1))] 78 | pub(crate) struct IdtEntry64Raw { 79 | offset_1: u16, 80 | selector: u16, 81 | ist: u8, 82 | types_attr: u8, 83 | offset_2: u16, 84 | offset_3: u32, 85 | reserved: u32, 86 | } 87 | 88 | pub(crate) struct IdtEntry64 { 89 | raw_entry: InterruptLock<*mut IdtEntry64Raw>, 90 | vector: u64, 91 | } 92 | 93 | impl IdtEntry64 { 94 | fn replace_handler_address(&self, handler: &HandlerAddress) { 95 | let handler_address = handler.0; 96 | let raw_entry = self.raw_entry.lock(); 97 | 98 | // Get the old handler address from the previous entry 99 | let raw_entry_copy = unsafe { core::ptr::read_volatile(*raw_entry) }; 100 | 101 | let old_handler_address = { 102 | let offset_1 = raw_entry_copy.offset_1 as u64; 103 | let offset_2 = raw_entry_copy.offset_2 as u64; 104 | let offset_3 = raw_entry_copy.offset_3 as u64; 105 | offset_1 | (offset_2 << 16) | (offset_3 << 32) 106 | }; 107 | 108 | let new_entry = IdtEntry64Raw { 109 | offset_1: handler_address as u16, 110 | selector: raw_entry_copy.selector, 111 | ist: raw_entry_copy.ist, 112 | types_attr: raw_entry_copy.types_attr, 113 | offset_2: (handler_address >> 16) as u16, 114 | offset_3: (handler_address >> 32) as u32, 115 | reserved: raw_entry_copy.reserved, 116 | }; 117 | 118 | // Replace the handler address in the IDT entry with the provided handler address. 119 | unsafe { 120 | // First, flush the cache containing the raw_entry address using clflush 121 | // Call this for every 8 bytes of the raw_entry address (based on size of IdtEntry64Raw) 122 | asm!("wbinvd"); 123 | // Synchronize 124 | asm!("mfence"); 125 | core::ptr::write_volatile(*raw_entry, new_entry); 126 | } 127 | 128 | // Store the old handler address in our atomic global 129 | PREVIOUS_PAGE_FAULT_HANDLER.store(old_handler_address as usize, SeqCst); 130 | } 131 | } 132 | 133 | #[repr(C)] 134 | pub struct ExceptionStackFrame { 135 | pub instruction_pointer: u64, 136 | pub code_segment: u64, 137 | pub cpu_flags: u64, 138 | pub stack_pointer: u64, 139 | pub stack_segment: u64, 140 | } 141 | 142 | #[derive(Copy, Clone, Eq, PartialEq)] 143 | pub(crate) enum InterruptVector { 144 | PageFault = 14, 145 | } 146 | 147 | #[derive(Copy, Clone, Eq, PartialEq)] 148 | pub(crate) struct PageFaultVector(InterruptVector); 149 | 150 | impl PageFaultVector { 151 | fn new() -> Self { 152 | PageFaultVector(InterruptVector::PageFault) 153 | } 154 | } 155 | impl Default for PageFaultVector { 156 | fn default() -> Self { 157 | PageFaultVector::new() 158 | } 159 | } 160 | 161 | #[derive(Copy, Clone)] 162 | struct HandlerAddress(u64); 163 | 164 | #[derive(Copy, Clone)] 165 | pub(crate) enum InterruptHandler { 166 | PageFaultHandler(unsafe extern "C" fn(), PageFaultVector), 167 | Unknown(u64), 168 | } 169 | 170 | impl PartialEq for InterruptHandler { 171 | fn eq(&self, other: &Self) -> bool { 172 | match (self, other) { 173 | ( 174 | InterruptHandler::PageFaultHandler(handler, vector), 175 | InterruptHandler::PageFaultHandler(other_handler, other_vector), 176 | ) => { 177 | handler as *const _ as u64 == other_handler as *const _ as u64 178 | && vector == other_vector 179 | } 180 | (InterruptHandler::Unknown(address), InterruptHandler::Unknown(other_address)) => { 181 | address == other_address 182 | } 183 | _ => false, 184 | } 185 | } 186 | } 187 | 188 | impl InterruptHandler { 189 | fn handler_address(&self) -> HandlerAddress { 190 | match self { 191 | InterruptHandler::PageFaultHandler(handler, _) => unsafe { 192 | *(handler as *const _ as *const HandlerAddress) 193 | }, 194 | InterruptHandler::Unknown(address) => HandlerAddress(*address), 195 | } 196 | } 197 | 198 | fn vector(&self) -> InterruptVector { 199 | match self { 200 | InterruptHandler::PageFaultHandler(_, vector) => vector.0, 201 | InterruptHandler::Unknown(_) => panic!("Unknown interrupt handler"), 202 | } 203 | } 204 | } 205 | 206 | #[derive(Debug)] 207 | pub(crate) struct SegmentTable { 208 | original_base: AtomicU64, 209 | base: AtomicU64, 210 | limit: u16, 211 | } 212 | 213 | /// Generic Segment Table Format 214 | #[repr(C, packed(1))] 215 | #[derive(Debug)] 216 | pub(crate) struct SegmentTableRaw { 217 | pub(crate) limit: u16, 218 | pub(crate) base: u64, 219 | } 220 | 221 | impl SegmentTable { 222 | fn replace_interrupt_handler(&self, handler: InterruptHandler, vector: u64) { 223 | // Get the address of the IDT entry we need to replace based on the interrupt vector 224 | // from the provided InterruptHandler 225 | let idt_entry = self.get_idt_entry_for_vector(vector); 226 | 227 | // Replace the handler address in the IDT entry with the provided handler address 228 | idt_entry.replace_handler_address(&handler.handler_address()); 229 | } 230 | 231 | fn get_idt_entry_for_vector(&self, vector: u64) -> Arc { 232 | // Calculate the address of the IDT entry for the provided interrupt vector 233 | // by multiplying the vector by the size of an IdtEntry64 234 | let idt_base = self.base.load(SeqCst) as *mut IdtEntry64Raw; 235 | let idt_target_entry = unsafe { idt_base.add(vector as usize) }; 236 | // calculate the last valid entry to the IDT based on the IDT limit 237 | let idt_last_entry = 238 | unsafe { (idt_base as *mut u8).add(self.limit as usize) } as *mut IdtEntry64Raw; 239 | // Ensure the target entry is within the IDT limits 240 | if idt_target_entry > idt_last_entry { 241 | panic!("IDT entry for vector {} is out of bounds", vector as usize); 242 | } else { 243 | let new_entry = Arc::new(IdtEntry64 { 244 | raw_entry: InterruptLock::new(idt_target_entry), 245 | vector, 246 | }); 247 | 248 | new_entry 249 | } 250 | } 251 | } 252 | 253 | #[inline] 254 | pub(crate) fn idt() -> Arc { 255 | let mut table = SegmentTableRaw { base: 0, limit: 0 }; 256 | unsafe { 257 | asm!("sidt [{}]", in(reg) &mut table); 258 | } 259 | 260 | if let Some(cached_idt) = unsafe { &CACHED_IDT } { 261 | // If we find an existing entry with the same original base, return it. 262 | // We check this as we may be executing on different cores, and each core has its 263 | // own IDT. 264 | for entry in cached_idt.iter() { 265 | if entry.original_base.load(SeqCst) == table.base { 266 | return entry.clone(); 267 | } 268 | } 269 | } 270 | // No match, create a new entry 271 | let seg = SegmentTable { 272 | original_base: AtomicU64::new(table.base), 273 | base: AtomicU64::new(table.base), 274 | limit: table.limit, 275 | }; 276 | 277 | unsafe { 278 | // Add the entry to the global cache 279 | let arc_entry = Arc::new(seg); 280 | if let Some(cached_idt) = &mut CACHED_IDT { 281 | cached_idt.push(arc_entry.clone()); 282 | } else { 283 | CACHED_IDT = Some(vec![arc_entry.clone()]); 284 | } 285 | 286 | arc_entry 287 | } 288 | } 289 | 290 | pub(crate) struct PageFaultInterruptHandlerManager { 291 | current_handler_address: InterruptHandler, 292 | current_vector: u64, 293 | previous_handler_address: Option, 294 | mdl: MyMDL, 295 | pub(crate) core_id: u64, 296 | } 297 | 298 | impl PageFaultInterruptHandlerManager { 299 | pub(crate) fn new(mdl: MyMDL, core: u64) -> Self { 300 | // Get the address of the current interrupt handler by reading it from the IDT 301 | let current_handler_idt = idt().get_idt_entry_for_vector(InterruptVector::PageFault as u64); 302 | // Construct the address of the entry 303 | let current_handler_address = unsafe { 304 | let raw_entry = current_handler_idt.raw_entry.lock(); 305 | let offset_1 = (*(*raw_entry)).offset_1 as u64; 306 | let offset_2 = (*(*raw_entry)).offset_2 as u64; 307 | let offset_3 = (*(*raw_entry)).offset_3 as u64; 308 | HandlerAddress(offset_1 | (offset_2 << 16) | (offset_3 << 32)) 309 | }; 310 | 311 | // Return the manager 312 | PageFaultInterruptHandlerManager { 313 | current_handler_address: InterruptHandler::Unknown(current_handler_address.0), 314 | current_vector: InterruptVector::PageFault as u64, 315 | previous_handler_address: None, 316 | mdl, 317 | core_id: core, 318 | } 319 | } 320 | 321 | pub(crate) fn install_interrupt_handler(&mut self) { 322 | let handler = 323 | InterruptHandler::PageFaultHandler(page_fault_handler, PageFaultVector::new()); 324 | // Only replace the handler if it's different from the current handler 325 | if self.current_handler_address != handler { 326 | // Set previous 327 | self.previous_handler_address = Some(self.current_handler_address.handler_address().0); 328 | // Replace the current handler with the new handler 329 | let idt = idt(); 330 | 331 | idt.replace_interrupt_handler(handler, self.current_vector); 332 | // Update the current handler address 333 | self.current_handler_address = handler; 334 | } else { 335 | panic!("Handler already installed"); 336 | } 337 | } 338 | 339 | pub(crate) fn restore_interrupt_handler(&mut self) { 340 | if let Some(previous_handler_address) = self.previous_handler_address { 341 | let previous_handler = InterruptHandler::Unknown(previous_handler_address); 342 | let idt = idt(); 343 | idt.replace_interrupt_handler(previous_handler, self.current_vector); 344 | self.current_handler_address = previous_handler; 345 | self.previous_handler_address = None; 346 | PAGE_FAULT_HIT.store(false, SeqCst); 347 | } else { 348 | unimplemented!(); 349 | } 350 | } 351 | 352 | pub(crate) fn page_in_idt(&self) { 353 | let idt = idt(); 354 | let idt_base = idt.base.load(SeqCst) as *mut IdtEntry64Raw; 355 | let idt_last_entry = unsafe { (idt_base as *mut u8).add(idt.limit as usize) }; 356 | 357 | for entry in ((idt_base as u64)..(idt_last_entry as u64)) 358 | .step_by(core::mem::size_of::()) 359 | { 360 | let entry = entry as *const IdtEntry64Raw; 361 | let res = unsafe { core::ptr::read_volatile(entry) }; 362 | black_box(res); 363 | } 364 | } 365 | } 366 | 367 | pub(crate) fn pagein_unprotect_idt() -> MyMDL { 368 | // Get the IDT and read every entry up to the limit to ensure they are paged in 369 | let idt = idt(); 370 | let idt_base = idt.base.load(SeqCst) as *mut IdtEntry64Raw; 371 | unsafe { 372 | // Get an MDL that describes the entire IDT table 373 | let mdl = build_mdl(idt_base as u64, idt.limit as u64).unwrap(); 374 | // Mark the entire MDL as RWX 375 | make_mdl_pages_writeable(&mdl); 376 | 377 | // Modify the base with the one from our MDL 378 | idt.base.store(mdl.mapped_base, SeqCst); 379 | let idt_base = idt.base.load(SeqCst) as *mut IdtEntry64Raw; 380 | let idt_last_entry = (idt_base as *mut u8).add(idt.limit as usize); 381 | 382 | for entry in ((idt_base as u64)..(idt_last_entry as u64)) 383 | .step_by(core::mem::size_of::()) 384 | { 385 | let entry = entry as *const IdtEntry64Raw; 386 | let res = core::ptr::read_volatile(entry); 387 | black_box(res); 388 | } 389 | 390 | mdl 391 | } 392 | } 393 | 394 | #[no_mangle] 395 | pub unsafe extern "C" fn get_probing_address() -> u64 { 396 | return PROBING_ADDRESS.load(SeqCst); 397 | } 398 | 399 | #[no_mangle] 400 | pub unsafe extern "C" fn get_junk_pointer() -> *mut u64 { 401 | addr_of_mut!(JUNK) 402 | } 403 | 404 | #[no_mangle] 405 | pub unsafe extern "C" fn set_page_fault_hit() { 406 | PAGE_FAULT_HIT.store(true, SeqCst); 407 | } 408 | 409 | #[no_mangle] 410 | pub unsafe extern "C" fn get_previous_handler() -> u64 { 411 | return PREVIOUS_PAGE_FAULT_HANDLER.load(SeqCst) as u64; 412 | } 413 | 414 | // global_asm block of our page fault handler 415 | global_asm! { 416 | r#" 417 | .section .text 418 | .align 16 419 | .extern get_probing_address 420 | .extern get_previous_handler 421 | .extern set_page_fault_hit 422 | .global page_fault_handler 423 | page_fault_handler: 424 | // Check if the faulted address matches our global probing address 425 | push rax // Original rax 426 | pushfq // Original rflags 427 | push rcx // Original rcx 428 | push rdx // Original rdx 429 | push r8 // Original r8 430 | push r9 // Original r9 431 | push r10 // Original r10 432 | push r11 // Original r11 433 | // backup XMM0-XMM5 434 | sub rsp, 16 435 | movdqu [rsp], xmm0 436 | sub rsp, 16 437 | movdqu [rsp], xmm1 438 | sub rsp, 16 439 | movdqu [rsp], xmm2 440 | sub rsp, 16 441 | movdqu [rsp], xmm3 442 | sub rsp, 16 443 | movdqu [rsp], xmm4 444 | sub rsp, 16 445 | movdqu [rsp], xmm5 446 | call get_previous_handler 447 | push rax // get_previous_handler result 448 | call get_probing_address 449 | // Check if the address is a match, if not then jmp to the previous handler 450 | // If so, then spinloop for now 451 | push rdx 452 | mov rdx, cr2 453 | cmp rax, rdx 454 | pop rdx 455 | jne .Lprevious_handler 456 | .Lprobed_address: 457 | call get_junk_pointer 458 | mov rdx, rax 459 | push rdx // save rdx (junk pointer) 460 | call set_page_fault_hit 461 | pop rdx // restore junk pointer 462 | // Restore the previous handler 463 | pop rax // pop the previous handler from stack 464 | // Restore all saved registers except rax 465 | movdqu xmm5, [rsp] 466 | add rsp, 16 467 | movdqu xmm4, [rsp] 468 | add rsp, 16 469 | movdqu xmm3, [rsp] 470 | add rsp, 16 471 | movdqu xmm2, [rsp] 472 | add rsp, 16 473 | movdqu xmm1, [rsp] 474 | add rsp, 16 475 | movdqu xmm0, [rsp] 476 | add rsp, 16 477 | pop r11 478 | pop r10 479 | pop r9 480 | pop r8 481 | // Skip over rdx, as we've modified it 482 | add rsp, 8 483 | pop rcx 484 | popfq 485 | pop rax // restore rax 486 | // "pop" off the error code 487 | add rsp, 8 488 | iretq 489 | .Lprevious_handler: 490 | // Restore the previous handler 491 | pop rax // pop the previous handler from stack 492 | // Restore all saved registers except rax 493 | movdqu xmm5, [rsp] 494 | add rsp, 16 495 | movdqu xmm4, [rsp] 496 | add rsp, 16 497 | movdqu xmm3, [rsp] 498 | add rsp, 16 499 | movdqu xmm2, [rsp] 500 | add rsp, 16 501 | movdqu xmm1, [rsp] 502 | add rsp, 16 503 | movdqu xmm0, [rsp] 504 | add rsp, 16 505 | pop r11 506 | pop r10 507 | pop r9 508 | pop r8 509 | pop rdx 510 | pop rcx 511 | popfq 512 | // Enter the previous handler 513 | call rax 514 | // Restore rax 515 | pop rax 516 | // return 517 | ret 518 | // mov rax into -8 of the current stack pointer 519 | //mov QWORD PTR [rsp - 16], rax 520 | // Restore rax 521 | //pop rax 522 | //jmp [rsp - 24] 523 | "# 524 | } 525 | -------------------------------------------------------------------------------- /read_write_driver/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(new_uninit)] 2 | #![feature(negative_impls)] 3 | #![no_std] 4 | 5 | extern crate alloc; 6 | #[cfg(not(test))] 7 | extern crate wdk_panic; 8 | 9 | use alloc::vec; 10 | use alloc::vec::Vec; 11 | use core::sync::atomic::Ordering::SeqCst; 12 | use core::{arch::asm, ffi::c_void, sync::atomic::AtomicUsize}; 13 | 14 | #[cfg(not(test))] 15 | use wdk_alloc::WDKAllocator; 16 | use wdk_sys::{ 17 | ntddk::KeQueryActiveProcessorCount, DEVICE_OBJECT, DRIVER_OBJECT, NTSTATUS, NT_SUCCESS, 18 | PCUNICODE_STRING, 19 | }; 20 | 21 | use crate::interrupts::{PageFaultInterruptHandlerManager, PAGE_FAULT_HIT, PROBING_ADDRESS}; 22 | use crate::locking::{ 23 | CoreLock, CorePin, QueuedLock, CORES_CHECKED_IN, CORE_LOCK_HELD, RELEASE_CORES, 24 | }; 25 | 26 | #[cfg(not(test))] 27 | #[global_allocator] 28 | static GLOBAL_ALLOCATOR: WDKAllocator = WDKAllocator; 29 | 30 | pub(crate) mod uni; 31 | 32 | mod cpu; 33 | mod interrupts; 34 | mod locking; 35 | mod mdl; 36 | pub(crate) mod shared; 37 | 38 | // Exposed device name used for user<>driver communication 39 | const NT_DEVICE_NAME_PATH: &str = "\\Device\\ReadWriteDevice"; 40 | const DOS_DEVICE_NAME_PATH: &str = "\\DosDevices\\ReadWriteDevice"; 41 | 42 | /// Number of logical cores (NUM_LOGICAL_CORES) on the system using OnceLock and KeQueryActiveProcessorCount 43 | static NUM_LOGICAL_CORES: AtomicUsize = AtomicUsize::new(0); 44 | 45 | static PAGE_FAULT_MANAGER: QueuedLock> = 46 | QueuedLock::new(vec![]); 47 | 48 | /// Wrapper around the PEPROCESS handle to ensure it is dereferenced when dropped 49 | struct PeProcessHandle { 50 | handle: wdk_sys::PEPROCESS, 51 | } 52 | 53 | impl Drop for PeProcessHandle { 54 | fn drop(&mut self) { 55 | unsafe { 56 | wdk_sys::ntddk::ObfDereferenceObject(self.handle as *mut c_void); 57 | } 58 | } 59 | } 60 | 61 | #[derive(Copy, Clone)] 62 | enum IrqlMethod { 63 | RaiseIrql, 64 | RaiseIrqlDirect, 65 | LowerIrql, 66 | LowerIrqlDirect, 67 | } 68 | 69 | /// Wrapper around an old IRQL value (after a call to raise IRQL) that will lower the IRQL 70 | /// to its original value when dropped. 71 | struct IrqlGuard { 72 | old_irql: u8, 73 | method: IrqlMethod, 74 | } 75 | 76 | impl Drop for IrqlGuard { 77 | fn drop(&mut self) { 78 | unsafe { 79 | match self.method { 80 | IrqlMethod::RaiseIrql => wdk_sys::ntddk::KeLowerIrql(self.old_irql), 81 | IrqlMethod::RaiseIrqlDirect => { 82 | asm!("mov cr8, {}", in(reg) self.old_irql as u64) 83 | } 84 | IrqlMethod::LowerIrql => { 85 | let _ = wdk_sys::ntddk::KfRaiseIrql(self.old_irql); 86 | } 87 | IrqlMethod::LowerIrqlDirect => { 88 | asm!("mov cr8, {}", in(reg) self.old_irql as u64) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | /// Errors that can occur when handling IOCTL requests, can be converted to NTSTATUS 96 | enum IoctlError { 97 | InvalidBufferSize, 98 | InvalidPid, 99 | InvalidCr3, 100 | VaSpaceDeleted, 101 | InvalidAddress, 102 | } 103 | 104 | impl IoctlError { 105 | fn status(&self) -> NTSTATUS { 106 | match self { 107 | IoctlError::InvalidBufferSize => wdk_sys::STATUS_INVALID_BUFFER_SIZE, 108 | IoctlError::InvalidPid => wdk_sys::STATUS_INVALID_PARAMETER, 109 | IoctlError::InvalidCr3 => wdk_sys::STATUS_INVALID_PARAMETER, 110 | IoctlError::VaSpaceDeleted => wdk_sys::STATUS_INVALID_PARAMETER, 111 | IoctlError::InvalidAddress => wdk_sys::STATUS_ACCESS_VIOLATION, 112 | } 113 | } 114 | } 115 | 116 | /// Raise the IRQL to DISPATCH_LEVEL and return an IrqlGuard that will lower the IRQL when dropped. 117 | /// The method used can be via the Windows API or by directly modifying cr8. 118 | fn raise_irql_to_dispatch(method: IrqlMethod) -> IrqlGuard { 119 | let old_irql = match method { 120 | IrqlMethod::RaiseIrql => unsafe { 121 | wdk_sys::ntddk::KfRaiseIrql(wdk_sys::DISPATCH_LEVEL as u8) 122 | }, 123 | IrqlMethod::RaiseIrqlDirect => unsafe { 124 | let old_irql: u64; 125 | asm!("mov {}, cr8", out(reg) old_irql); 126 | asm!("mov cr8, {}", in(reg) wdk_sys::DISPATCH_LEVEL as u64); 127 | old_irql as u8 128 | }, 129 | _ => { 130 | panic!("Invalid method for raise_irql_to_dispatch") 131 | } 132 | }; 133 | 134 | IrqlGuard { old_irql, method } 135 | } 136 | 137 | fn lower_irql_to_passive(method: IrqlMethod) -> IrqlGuard { 138 | let old_irql = match method { 139 | IrqlMethod::LowerIrql => unsafe { 140 | let old_irql = wdk_sys::ntddk::KeGetCurrentIrql(); 141 | wdk_sys::ntddk::KeLowerIrql(wdk_sys::PASSIVE_LEVEL as u8); 142 | old_irql as u8 143 | }, 144 | IrqlMethod::LowerIrqlDirect => unsafe { 145 | let old_irql: u64; 146 | asm!("mov {}, cr8", out(reg) old_irql); 147 | asm!("mov cr8, {}", in(reg) wdk_sys::PASSIVE_LEVEL as u64); 148 | old_irql as u8 149 | }, 150 | _ => { 151 | panic!("Invalid method for lower_irql_to_passive") 152 | } 153 | }; 154 | 155 | IrqlGuard { old_irql, method } 156 | } 157 | 158 | #[export_name = "DriverEntry"] // WDF expects a symbol with the name DriverEntry 159 | pub unsafe extern "system" fn driver_entry( 160 | driver: &mut DRIVER_OBJECT, 161 | _registry_path: PCUNICODE_STRING, 162 | ) -> NTSTATUS { 163 | // Device names for NT and Dos in unicode, as required by the kernel APIs 164 | let nt_device_name = uni::str_to_unicode(NT_DEVICE_NAME_PATH); 165 | let dos_device_name = uni::str_to_unicode(DOS_DEVICE_NAME_PATH); 166 | // Device object handle to be filled by IoCreateDevice 167 | let mut device_obj: *mut DEVICE_OBJECT = core::ptr::null_mut(); 168 | 169 | let status = { 170 | let mut nt_device_name_uni = nt_device_name.to_unicode(); 171 | wdk_sys::ntddk::IoCreateDevice( 172 | driver, 173 | 0, 174 | &mut nt_device_name_uni, 175 | wdk_sys::FILE_DEVICE_UNKNOWN, 176 | wdk_sys::FILE_DEVICE_SECURE_OPEN, 177 | 0, 178 | &mut device_obj, 179 | ) 180 | }; 181 | if !NT_SUCCESS(status) { 182 | // Fail 183 | return status; 184 | } 185 | // Set required MajorFunction handlers to permit DeviceIoControl calls from user 186 | driver.MajorFunction[wdk_sys::IRP_MJ_CREATE as usize] = Some(device_create_close); 187 | driver.MajorFunction[wdk_sys::IRP_MJ_CLOSE as usize] = Some(device_create_close); 188 | driver.MajorFunction[wdk_sys::IRP_MJ_DEVICE_CONTROL as usize] = Some(device_ioctl_handler); 189 | // Support unloading the driver 190 | driver.DriverUnload = Some(device_unload); 191 | 192 | // Create symbolic link to device, required for user mode to access the device 193 | let status = { 194 | let mut nt_device_name_uni = nt_device_name.to_unicode(); 195 | let mut dos_device_name_uni = dos_device_name.to_unicode(); 196 | wdk_sys::ntddk::IoCreateSymbolicLink(&mut dos_device_name_uni, &mut nt_device_name_uni) 197 | }; 198 | if !NT_SUCCESS(status) { 199 | // If failed, cleanup device object and return 200 | wdk_sys::ntddk::IoDeleteDevice(device_obj); 201 | return status; 202 | } 203 | 204 | // Initialize the number of logical cores 205 | { 206 | let core_count = unsafe { KeQueryActiveProcessorCount(core::ptr::null_mut()) as usize }; 207 | NUM_LOGICAL_CORES.store(core_count, SeqCst); 208 | } 209 | 210 | // At this point we have a device and a symbolic link, return success 211 | wdk_sys::STATUS_SUCCESS 212 | } 213 | 214 | /// Entry point to handle DeviceIoControl calls from user. Expects a ReadWriteIoctl struct as input. 215 | pub unsafe extern "C" fn device_ioctl_handler( 216 | _dev_obj: *mut DEVICE_OBJECT, 217 | irp: *mut wdk_sys::IRP, 218 | ) -> NTSTATUS { 219 | // Get the control code and input buffer from the IRP 220 | let irp = &mut *irp; 221 | let stack = &*irp 222 | .Tail 223 | .Overlay 224 | .__bindgen_anon_2 225 | .__bindgen_anon_1 226 | .CurrentStackLocation; 227 | let control_code = stack.Parameters.DeviceIoControl.IoControlCode; 228 | // Get input buffer and len 229 | let input_buffer = irp.AssociatedIrp.SystemBuffer; 230 | let input_buffer_len = stack.Parameters.DeviceIoControl.InputBufferLength as usize; 231 | 232 | // Check for our supported IOCTL_REQUEST command or return invalid device request 233 | match control_code { 234 | shared::IOCTL_REQUEST => { 235 | // Matched on our IOCTL_REQUEST command. Check input buffer len and handle the request. 236 | // The input buffer should be at least the size of the ReadWriteIoctl struct 237 | return if input_buffer_len < core::mem::size_of::() { 238 | // Input buffer is too small, return invalid buffer size 239 | irp.IoStatus.__bindgen_anon_1.Status = wdk_sys::STATUS_INVALID_BUFFER_SIZE; 240 | irp.IoStatus.Information = 0; 241 | wdk_sys::ntddk::IofCompleteRequest(irp, 0); 242 | wdk_sys::STATUS_INVALID_BUFFER_SIZE 243 | } else { 244 | // Input buffer is large enough, handle the request. 245 | // First, cast the input buffer to a ReadWriteIoctl struct 246 | let ioctl_request = &*(input_buffer as *const shared::ReadWriteIoctl); 247 | // Attempt to handle the request 248 | match handle_ioctl_request(ioctl_request, input_buffer_len) { 249 | Ok(_) => { 250 | // Handled the request successfully, return success 251 | irp.IoStatus.__bindgen_anon_1.Status = wdk_sys::STATUS_SUCCESS; 252 | irp.IoStatus.Information = 0; 253 | wdk_sys::ntddk::IofCompleteRequest(irp, 0); 254 | wdk_sys::STATUS_SUCCESS 255 | } 256 | Err(e) => { 257 | // Failed to handle the request, return invalid parameter 258 | irp.IoStatus.__bindgen_anon_1.Status = e.status(); 259 | irp.IoStatus.Information = 0; 260 | wdk_sys::ntddk::IofCompleteRequest(irp, 0); 261 | wdk_sys::STATUS_INVALID_PARAMETER 262 | } 263 | } 264 | }; 265 | } 266 | _ => { 267 | // Unsupported IOCTL command, return invalid device request 268 | irp.IoStatus.__bindgen_anon_1.Status = wdk_sys::STATUS_INVALID_DEVICE_REQUEST; 269 | irp.IoStatus.Information = 0; 270 | wdk_sys::ntddk::IofCompleteRequest(irp, 0); 271 | wdk_sys::STATUS_INVALID_DEVICE_REQUEST 272 | } 273 | } 274 | } 275 | 276 | enum CheckWriteMethod { 277 | WindowsApi, 278 | CustomPageFaultHandler, 279 | } 280 | 281 | fn check_and_write( 282 | write_request: WriteRequest, 283 | method: CheckWriteMethod, 284 | ) -> Result<(), IoctlError> { 285 | match method { 286 | CheckWriteMethod::WindowsApi => check_and_write_windows(write_request), 287 | CheckWriteMethod::CustomPageFaultHandler => check_and_write_page_fault(write_request), 288 | } 289 | } 290 | 291 | fn check_and_write_page_fault(write_request: WriteRequest) -> Result<(), IoctlError> { 292 | let mut global_manager_lock = PAGE_FAULT_MANAGER.lock(); 293 | // Install our custom page fault handler 294 | let handler_manager = { 295 | // Temporarily lower IRQL to support page faults 296 | let _irql_guard = lower_irql_to_passive(IrqlMethod::LowerIrqlDirect); 297 | 298 | let mut handler_manager = None; 299 | // Check if we have one already, if so return a mutable reference 300 | // Attempt to find a manager with the same core_id as us 301 | let current_core = 302 | unsafe { wdk_sys::ntddk::KeGetCurrentProcessorNumberEx(core::ptr::null_mut()) }; 303 | 304 | for manager in global_manager_lock.iter_mut() { 305 | if manager.core_id == current_core as u64 { 306 | // Ensure things are still paged in 307 | manager.page_in_idt(); 308 | handler_manager = Some(manager); 309 | break; 310 | } 311 | } 312 | 313 | if handler_manager.is_none() { 314 | // Pagein IDT 315 | let mdl = interrupts::pagein_unprotect_idt(); 316 | // If we don't have one, create a new one and store it 317 | let manager = PageFaultInterruptHandlerManager::new(mdl, current_core as u64); 318 | // Add the manager to the global vector 319 | global_manager_lock.push(manager); 320 | // Set handler_manager to the newly created manager 321 | handler_manager = Some(global_manager_lock.last_mut().unwrap()); 322 | } 323 | 324 | handler_manager.unwrap() 325 | }; 326 | handler_manager.install_interrupt_handler(); 327 | 328 | let mut return_value = Ok(()); 329 | // Attempt to write the buffer to the target process 330 | // For each byte we write, check the global PAGE_FAULT_HIT flag to see if a page fault occurred 331 | // If a page fault occurred, return an error 332 | for i in 0..write_request.buffer.len() { 333 | let address = write_request.untrusted_address + i; 334 | PROBING_ADDRESS.store(address as u64, SeqCst); 335 | unsafe { 336 | asm!( 337 | "mov [rdx], {}", 338 | in(reg_byte) write_request.buffer[i], 339 | in("rdx") address as u64, 340 | ); 341 | } 342 | if PAGE_FAULT_HIT.load(SeqCst) { 343 | return_value = Err(IoctlError::InvalidAddress); 344 | break; 345 | } 346 | } 347 | 348 | PROBING_ADDRESS.store(0, SeqCst); 349 | 350 | // Restore the original page fault handler 351 | handler_manager.restore_interrupt_handler(); 352 | 353 | return_value 354 | } 355 | fn check_and_write_windows(write_request: WriteRequest) -> Result<(), IoctlError> { 356 | let buffer_len = write_request.buffer.len(); 357 | let address = write_request.untrusted_address; 358 | // For every byte we wish to access, we need to ensure the address is accessible. As we've guaranteed all other cores are frozen and 359 | // our thread cannot be task-switched, we can trust the result of the following checks as there is no opportunity for another thread 360 | // to modify the state of the target process. 361 | // Loop through each address we intend to touch (starting at the provided address, and ending at the provided address + buffer_len) and ensure the 362 | // result of a call to MmIsAddressValid is true. If it is not, return an error. 363 | for i in 0..buffer_len { 364 | let address = address + i; 365 | if unsafe { wdk_sys::ntddk::MmIsAddressValid(address as *mut c_void) } != 1 { 366 | return Err(IoctlError::InvalidAddress); 367 | } 368 | } 369 | 370 | // Copy buffer to address as requested by user. 371 | unsafe { 372 | core::ptr::copy_nonoverlapping( 373 | write_request.buffer.as_ptr(), 374 | address as *mut u8, 375 | buffer_len, 376 | ); 377 | } 378 | 379 | Ok(()) 380 | } 381 | 382 | fn switch_cr3(new_cr3: usize) -> Cr3SwitchGuard { 383 | let previous_cr3 = unsafe { 384 | let previous_cr3: usize; 385 | asm!( 386 | "mov {}, cr3", 387 | out(reg) previous_cr3, 388 | ); 389 | asm!( 390 | "mov cr3, {}", 391 | in(reg) new_cr3, 392 | ); 393 | previous_cr3 394 | }; 395 | 396 | Cr3SwitchGuard { previous_cr3 } 397 | } 398 | 399 | struct Cr3SwitchGuard { 400 | previous_cr3: usize, 401 | } 402 | 403 | impl Drop for Cr3SwitchGuard { 404 | fn drop(&mut self) { 405 | unsafe { 406 | asm!( 407 | "mov cr3, {}", 408 | in(reg) self.previous_cr3, 409 | ); 410 | } 411 | } 412 | } 413 | 414 | #[derive(Clone)] 415 | struct WriteRequest<'a> { 416 | untrusted_address: usize, 417 | buffer: &'a [u8], 418 | } 419 | 420 | /// Attempt to handle the ReadWriteIoctl request. This function will attempt to locate the target process 421 | /// by PID, and then copy the provided buffer to the target process's memory. 422 | fn handle_ioctl_request( 423 | ioctl_request: &shared::ReadWriteIoctl, 424 | buffer_len_max: usize, 425 | ) -> Result<(), IoctlError> { 426 | let header = &ioctl_request.header; 427 | let address = header.address; 428 | let buffer_len = header.buffer_len; 429 | 430 | // Pin the executing thread to the current core, as we mess with IDTs later 431 | // we want to ensure we're not task-switched to another core with a separate IDT. 432 | // This will auto-revert when dropped (end of function) 433 | let _core_pin = CorePin::new(); 434 | 435 | // If we're attempting to write 0 bytes, we can just return success now 436 | if buffer_len == 0 { 437 | return Ok(()); 438 | } 439 | // Validate that the entire ReadWriteIoctl request (incl. dynamic buffer) is within the input buffer max bounds 440 | if core::mem::size_of::() 441 | .checked_add(buffer_len) 442 | .ok_or(IoctlError::InvalidBufferSize)? 443 | > buffer_len_max 444 | { 445 | // The size of the provided ioctl_request (including the dynamic buffer portion) exceeds the bounds of the provided input buffer, 446 | // if we continued to parse the request we'd be reading out of bounds. Return an error. 447 | return Err(IoctlError::InvalidBufferSize); 448 | } 449 | // The dynamic buffer portion is of a dynamic length, so we need to convert it to a slice 450 | // using the provided buffer_len. 451 | let buffer = 452 | unsafe { core::slice::from_raw_parts(&ioctl_request.buffer as *const u8, buffer_len) }; 453 | // Get the KPROCESS from the target_pid if it exists, or return an error 454 | let process = match process_from_pid(header.target_pid) { 455 | Some(process) => process, 456 | None => return Err(IoctlError::InvalidPid), 457 | }; 458 | 459 | // Get offset 0x28 from the KPROCESS which is the DirectoryTableBase / cr3 460 | // Attempt to get it from the provided symbols first, if not use the default offset 461 | let cr3 = unsafe { 462 | let cr3_offset = header.symbols.directory_table_base.unwrap_or_else(|| 0x28); 463 | let cr3_ptr = (process.handle as *const u8).add(cr3_offset) as *const usize; 464 | *cr3_ptr 465 | }; 466 | 467 | // Check if our obtained cr3 is likely to be a valid cr3 468 | if !is_likely_cr3(cr3) { 469 | return Err(IoctlError::InvalidCr3); 470 | } 471 | 472 | { 473 | // Freeze all other cores on the system to prevent any other threads from modifying the target process 474 | let _lock = freeze_all_cores(); 475 | { 476 | // Raise our IRQL to prevent task-switching 477 | let _irql_guard = raise_irql_to_dispatch(IrqlMethod::RaiseIrqlDirect); 478 | // Only proceed if the VA space isn't deleted. This check is only performed if we have 479 | // the symbol offset. 480 | if let Some(va_space_deleted_s) = &header.symbols.va_space_deleted { 481 | let va_space_deleted = unsafe { 482 | let va_space_deleted_ptr = 483 | (process.handle as *const u8).add(va_space_deleted_s.offset) as *const u8; 484 | let va_space_deleted = ((*(va_space_deleted_ptr as *const u32)) 485 | >> va_space_deleted_s.bit_pos) 486 | & 0b1; 487 | va_space_deleted as usize 488 | }; 489 | if va_space_deleted != 0 { 490 | // Return out if the VA space is deleted 491 | return Err(IoctlError::VaSpaceDeleted); 492 | } 493 | } 494 | // Modify our CR3 to the targets, backing up our original cr3 495 | // and auto-restoring on drop 496 | let _cr3_guard = switch_cr3(cr3); 497 | 498 | let write_request = WriteRequest { 499 | untrusted_address: address, 500 | buffer, 501 | }; 502 | 503 | check_and_write(write_request, CheckWriteMethod::CustomPageFaultHandler)?; 504 | 505 | // irql_guard and cr3_guard will be dropped (or dropped already if check_and_write 506 | // threw an error), auto lowering the irql and restoring the cr3 507 | } 508 | 509 | // _lock will be dropped, auto releasing the cores 510 | } 511 | 512 | // Success 513 | Ok(()) 514 | } 515 | 516 | /// Freeze all cores on the system. Returns a `CoreLock` which will release the cores when dropped. 517 | fn freeze_all_cores() -> CoreLock { 518 | // Ensure the cores are not already locked, if not swap the lock to true 519 | if CORE_LOCK_HELD.swap(true, SeqCst) { 520 | panic!("Cores are already locked"); 521 | } 522 | // Create a vector to store the allocated DPCs for cleanup 523 | let mut allocated_dpcs = Vec::with_capacity(NUM_LOGICAL_CORES.load(SeqCst)); 524 | 525 | for _core in 0..(NUM_LOGICAL_CORES.load(SeqCst) - 1) { 526 | let tag = 0x647772; 527 | // Create a DPC for the core 528 | // The DPC should be allocated from NonPagedPool, allocate it using ExAllocatePool2 529 | unsafe { 530 | let buffer = wdk_sys::ntddk::ExAllocatePool2( 531 | wdk_sys::POOL_FLAG_NON_PAGED, 532 | core::mem::size_of::() as u64, 533 | tag, 534 | ) as *mut wdk_sys::KDPC; 535 | 536 | // Assert the buffer is non-null 537 | assert_ne!(buffer, core::ptr::null_mut()); 538 | // Write a default KDPC to the buffer 539 | core::ptr::write(buffer, wdk_sys::KDPC::default()); 540 | 541 | // Store the allocated DPC for cleanup 542 | allocated_dpcs.push(buffer); 543 | }; 544 | } 545 | 546 | { 547 | // Temporarily raise to dispatch to prevent being rescheduled onto a different core 548 | let _irql_guard = raise_irql_to_dispatch(IrqlMethod::RaiseIrqlDirect); 549 | // Don't freeze the current core 550 | let current_core = 551 | unsafe { wdk_sys::ntddk::KeGetCurrentProcessorNumberEx(core::ptr::null_mut()) } 552 | as usize; 553 | 554 | // Clone the allocated_dpcs as we're going to temporarily pop them 555 | let mut allocated_dpcs_clone = allocated_dpcs.clone(); 556 | // Create a DPC for NUM_LOGICAL_CORES - 1 (skipping the current core) 557 | for core in 0..NUM_LOGICAL_CORES.load(SeqCst) { 558 | // If its the current core, skip 559 | if core == current_core { 560 | continue; 561 | } 562 | // If its not, pop a DPC out of the allocated_dpcs_clone 563 | let dpc = allocated_dpcs_clone.pop().expect("Failed to pop DPC"); 564 | // Initialize the DPC 565 | unsafe { 566 | wdk_sys::ntddk::KeInitializeDpc(dpc, Some(freeze_core), core::ptr::null_mut()); 567 | } 568 | // Set the target processor for the DPC 569 | unsafe { 570 | wdk_sys::ntddk::KeSetTargetProcessorDpc(dpc, core as i8); 571 | // Queue the DPC 572 | wdk_sys::ntddk::KeInsertQueueDpc(dpc, core::ptr::null_mut(), core::ptr::null_mut()); 573 | } 574 | } 575 | } 576 | 577 | // Wait for all other cores to check in to ensure they are frozen before we proceed further 578 | while CORES_CHECKED_IN.load(SeqCst) < NUM_LOGICAL_CORES.load(SeqCst) - 1 { 579 | // Spin/wait 580 | } 581 | // All cores have checked in, return the CoreLock 582 | CoreLock::new(allocated_dpcs) 583 | } 584 | 585 | /// Freezes a core by raising the IRQL to DISPATCH_LEVEL, checking in the core and waiting for the release signal. 586 | #[inline(never)] 587 | unsafe extern "C" fn freeze_core( 588 | _kdpc: *mut wdk_sys::KDPC, 589 | _deferred_context: *mut c_void, 590 | _system_argument1: *mut c_void, 591 | _system_argument2: *mut c_void, 592 | ) { 593 | // Raise IRQL to DISPATCH_LEVEL to prevent task-switching, ensuring our thread is the only one running on the core 594 | let _irql_guard = raise_irql_to_dispatch(IrqlMethod::RaiseIrqlDirect); 595 | // Check in the core to indicate it is frozen 596 | CORES_CHECKED_IN.fetch_add(1, SeqCst); 597 | // Wait for the release signal 598 | while !RELEASE_CORES.load(SeqCst) { 599 | // Spin/wait 600 | } 601 | // Release signal received, check-out the core as we will no longer freeze this core 602 | CORES_CHECKED_IN.fetch_sub(1, SeqCst); 603 | 604 | // irql_guard will be dropped, auto lowering the irql 605 | } 606 | 607 | /// Attempt to locate the process by PID. If found, return the a wrapper to it, otherwise return None. 608 | fn process_from_pid(pid: u32) -> Option { 609 | let mut handle: wdk_sys::PEPROCESS = core::ptr::null_mut(); 610 | let status = 611 | unsafe { wdk_sys::ntddk::PsLookupProcessByProcessId(pid as *mut c_void, &mut handle) }; 612 | if NT_SUCCESS(status) { 613 | Some(PeProcessHandle { handle }) 614 | } else { 615 | None 616 | } 617 | } 618 | 619 | /// Unload the driver. This function will delete the symbolic link and the device object if it exists. 620 | pub unsafe extern "C" fn device_unload(driver_obj: *mut DRIVER_OBJECT) { 621 | // Check if any cores are currently frozen, this should never happen. 622 | // If they are frozen, wait a little to see if they unfreeze and if not, panic. 623 | if CORES_CHECKED_IN.load(SeqCst) > 0 { 624 | for _ in 0..1000 { 625 | // Spin 626 | } 627 | panic!("Cores are still frozen while attempting to unload the driver"); 628 | } 629 | 630 | // Delete symbolic link 631 | let dos_device_name = uni::str_to_unicode(DOS_DEVICE_NAME_PATH); 632 | let mut dos_device_name_uni = dos_device_name.to_unicode(); 633 | let _status = wdk_sys::ntddk::IoDeleteSymbolicLink(&mut dos_device_name_uni); 634 | 635 | // If we have a device, delete it 636 | if !(*driver_obj).DeviceObject.is_null() { 637 | wdk_sys::ntddk::IoDeleteDevice((*driver_obj).DeviceObject); 638 | } 639 | } 640 | 641 | /// Return whether the provided address/usize appears to be on a page boundary 642 | fn is_page_aligned(address: usize) -> bool { 643 | address & 0xFFF == 0 644 | } 645 | 646 | /// Checks whether the provided value appears to be a valid CR3. 647 | /// This only guesses by performing a few checks that apply to how CR3 values are 648 | /// used on Windows, but are not strictly guaranteed to be true. 649 | fn is_likely_cr3(cr3: usize) -> bool { 650 | // Check if the value is page aligned, less than 0x0000_FFFF_FFFF_FFFF, and non-zero 651 | const CR3_EXPECTED_MAX: usize = 0x0000_FFFF_FFFF_FFFF; 652 | is_page_aligned(cr3) && cr3 < CR3_EXPECTED_MAX && cr3 != 0 653 | } 654 | 655 | /// Create and close handlers for the device object. These are required to permit DeviceIoControl calls from user. 656 | /// We allow create and close to pass through without any action. 657 | pub unsafe extern "C" fn device_create_close( 658 | _dev_obj: *mut DEVICE_OBJECT, 659 | irp: *mut wdk_sys::IRP, 660 | ) -> NTSTATUS { 661 | let irp = &mut *irp; 662 | irp.IoStatus.__bindgen_anon_1.Status = wdk_sys::STATUS_SUCCESS; 663 | irp.IoStatus.Information = 0; 664 | wdk_sys::ntddk::IofCompleteRequest(irp, 0); 665 | 666 | wdk_sys::STATUS_SUCCESS 667 | } 668 | -------------------------------------------------------------------------------- /read_write_driver/src/locking.rs: -------------------------------------------------------------------------------- 1 | // no_std QueuedLock struct that wraps a T and provides a lock() method that 2 | // atomically locks the T and returns a QueuedLockGuard that unlocks the T when it 3 | // goes out of scope. Uses Atomics and a ticket approach to obtain the lock 4 | // and ensure callers are provided access in the order they requested it. 5 | 6 | use crate::cpu::rdtsc; 7 | use alloc::vec::Vec; 8 | use core::cell::UnsafeCell; 9 | use core::ffi::c_void; 10 | use core::panic::Location; 11 | use core::sync::atomic::Ordering::SeqCst; 12 | use core::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize}; 13 | use lazy_static::lazy_static; 14 | use wdk_sys::ntddk::{KeRevertToUserAffinityThreadEx, KeSetSystemAffinityThreadEx}; 15 | 16 | /// Flag indicating if the cores should be released 17 | pub static RELEASE_CORES: AtomicBool = AtomicBool::new(false); 18 | 19 | /// Number of cores currently checked-in / on hold when freezing cores 20 | pub static CORES_CHECKED_IN: AtomicUsize = AtomicUsize::new(0); 21 | 22 | /// Flag indicating if the global core lock is held, used when freezing cores 23 | pub static CORE_LOCK_HELD: AtomicBool = AtomicBool::new(false); 24 | 25 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 26 | pub enum LockError { 27 | CoreAlreadyPinned, 28 | CoreNotPinned, 29 | } 30 | 31 | lazy_static! { 32 | static ref PINNED_CORES: [QueuedLock; 64] = { 33 | let pinned_cores = core::array::from_fn(|_| { 34 | QueuedLock::new(PinnedCore { 35 | core_id: AtomicUsize::new(0), 36 | is_pinned: AtomicBool::new(false), 37 | }) 38 | }); 39 | 40 | pinned_cores 41 | }; 42 | } 43 | 44 | struct PinnedCore { 45 | core_id: AtomicUsize, 46 | is_pinned: AtomicBool, 47 | } 48 | 49 | pub struct QueuedLock { 50 | ticket: AtomicUsize, 51 | serving: AtomicUsize, 52 | caller_location: AtomicUsize, 53 | data: UnsafeCell, 54 | } 55 | 56 | unsafe impl Send for QueuedLock {} 57 | unsafe impl Sync for QueuedLock {} 58 | 59 | pub struct QueuedLockGuard<'a, T> { 60 | lock: &'a QueuedLock, 61 | } 62 | 63 | impl QueuedLock { 64 | pub const fn new(data: T) -> Self { 65 | // Get the current core and pin 66 | Self { 67 | ticket: AtomicUsize::new(0), 68 | serving: AtomicUsize::new(0), 69 | caller_location: AtomicUsize::new(0), 70 | data: UnsafeCell::new(data), 71 | } 72 | } 73 | 74 | #[track_caller] 75 | pub fn lock(&self) -> QueuedLockGuard { 76 | let ticket = self.ticket.fetch_add(1, SeqCst); 77 | let mut fast_threshold: usize = 20_000; 78 | let mut timeout: u64 = 0; 79 | while self.serving.load(SeqCst) != ticket { 80 | if fast_threshold > 0 { 81 | fast_threshold -= 1; 82 | continue; 83 | } else { 84 | if timeout == 0 { 85 | timeout = unsafe { rdtsc() } + 3_000_000_000 * 5; 86 | } else { 87 | if unsafe { rdtsc() } > timeout { 88 | panic!("Lock timeout"); 89 | } 90 | let caller_location = self.caller_location.load(SeqCst); 91 | if caller_location > 0 { 92 | let caller_location = caller_location as *const Location<'static>; 93 | panic!("Lock timeout with previous holder at: {:?}", unsafe { 94 | &*caller_location 95 | }); 96 | } else { 97 | panic!("Lock timeout with unknown previous holder"); 98 | } 99 | } 100 | } 101 | } 102 | // track location 103 | self.caller_location 104 | .store(Location::caller() as *const _ as usize, SeqCst); 105 | QueuedLockGuard { lock: self } 106 | } 107 | } 108 | 109 | impl Drop for QueuedLockGuard<'_, T> { 110 | fn drop(&mut self) { 111 | self.lock.serving.fetch_add(1, SeqCst); 112 | } 113 | } 114 | 115 | impl core::ops::Deref for QueuedLockGuard<'_, T> { 116 | type Target = T; 117 | 118 | fn deref(&self) -> &Self::Target { 119 | unsafe { &*self.lock.data.get() } 120 | } 121 | } 122 | 123 | impl core::ops::DerefMut for QueuedLockGuard<'_, T> { 124 | fn deref_mut(&mut self) -> &mut Self::Target { 125 | unsafe { &mut *self.lock.data.get() } 126 | } 127 | } 128 | 129 | pub struct CorePin { 130 | previous_affinity: AtomicU64, 131 | } 132 | 133 | // Impl new on CorePin that will pin the current thread to the current core 134 | impl CorePin { 135 | pub fn new() -> Result { 136 | let current_core = unsafe { 137 | wdk_sys::ntddk::KeGetCurrentProcessorNumberEx(core::ptr::null_mut()) as usize 138 | }; 139 | let previous_affinity = pin_to_core(current_core)?; 140 | Ok(CorePin { 141 | previous_affinity: AtomicU64::new(previous_affinity), 142 | }) 143 | } 144 | } 145 | 146 | // Impl drop on CorePin that will restore the previous affinity when dropped 147 | impl Drop for CorePin { 148 | fn drop(&mut self) { 149 | unpin_core(self.previous_affinity.load(SeqCst)).expect("Failed to unpin core"); 150 | } 151 | } 152 | 153 | /// CoreLock is a struct that is held when the cores are frozen, and released when the cores are unfrozen. 154 | /// Cores are frozen by scheduling a DPC on each core to raise the IRQL to DISPATCH_LEVEL, and then checked in. 155 | /// We track all allocated DPCs so that we can free them when the CoreLock is dropped. 156 | pub struct CoreLock { 157 | allocated_dpcs: Vec<*mut wdk_sys::KDPC>, 158 | } 159 | 160 | impl CoreLock { 161 | pub fn new(allocated_dpcs: Vec<*mut wdk_sys::KDPC>) -> Self { 162 | CoreLock { allocated_dpcs } 163 | } 164 | } 165 | 166 | // Impl drop for corelock that will set the release cores flag to true, wait for 167 | // all cores to check out, and then reset the core lock held flag. 168 | impl Drop for CoreLock { 169 | fn drop(&mut self) { 170 | // Set the release flag 171 | RELEASE_CORES.store(true, SeqCst); 172 | // Wait for all cores to check out 173 | while CORES_CHECKED_IN.load(SeqCst) > 0 { 174 | // Spin 175 | } 176 | // All cores have checked out, reset the release flag 177 | RELEASE_CORES.store(false, SeqCst); 178 | 179 | // Reset the core lock held flag 180 | CORE_LOCK_HELD.store(false, SeqCst); 181 | 182 | // Free the allocated DPCs 183 | for dpc in self.allocated_dpcs.iter() { 184 | unsafe { 185 | wdk_sys::ntddk::ExFreePool(*dpc as *mut c_void); 186 | } 187 | } 188 | } 189 | } 190 | 191 | pub fn pin_to_core(core_id: usize) -> Result { 192 | let target_core_lock = &PINNED_CORES[core_id]; 193 | let target_core = target_core_lock.lock(); 194 | if target_core 195 | .is_pinned 196 | .compare_exchange(false, true, SeqCst, SeqCst) 197 | .is_err() 198 | { 199 | return Err(LockError::CoreAlreadyPinned); 200 | } 201 | target_core.core_id.store(core_id, SeqCst); 202 | let previous_affinity = unsafe { KeSetSystemAffinityThreadEx(1 << core_id) }; 203 | Ok(previous_affinity) 204 | } 205 | 206 | pub fn unpin_core(previous_affinity: u64) -> Result<(), LockError> { 207 | // We can only unpin the current core 208 | let core_id = 209 | unsafe { wdk_sys::ntddk::KeGetCurrentProcessorNumberEx(core::ptr::null_mut()) as usize }; 210 | let target_core_lock = &PINNED_CORES[core_id]; 211 | let target_core = target_core_lock.lock(); 212 | if target_core 213 | .is_pinned 214 | .compare_exchange(true, false, SeqCst, SeqCst) 215 | .is_err() 216 | { 217 | return Err(LockError::CoreNotPinned); 218 | } 219 | unsafe { 220 | KeRevertToUserAffinityThreadEx(previous_affinity); 221 | } 222 | Ok(()) 223 | } 224 | -------------------------------------------------------------------------------- /read_write_driver/src/mdl.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::null_mut; 2 | use wdk_sys::ntddk::{ 3 | IoAllocateMdl, IoFreeMdl, MmBuildMdlForNonPagedPool, MmMapLockedPagesSpecifyCache, 4 | MmProtectMdlSystemAddress, 5 | }; 6 | use wdk_sys::_MEMORY_CACHING_TYPE::MmNonCached; 7 | use wdk_sys::_MM_PAGE_PRIORITY::HighPagePriority; 8 | use wdk_sys::_MODE::KernelMode; 9 | use wdk_sys::{BOOLEAN, KPROCESSOR_MODE, PAGE_EXECUTE_READWRITE, PMDL, ULONG}; 10 | 11 | pub(crate) struct MyMDL { 12 | mdl: PMDL, 13 | pub(crate) mapped_base: u64, 14 | } 15 | 16 | impl Drop for MyMDL { 17 | fn drop(&mut self) { 18 | unsafe { 19 | IoFreeMdl(self.mdl); 20 | } 21 | } 22 | } 23 | 24 | // Build an MDL for the provided base address + size using IoAllocateMdl 25 | pub(crate) fn build_mdl(base: u64, size: u64) -> Result { 26 | let mdl = unsafe { 27 | IoAllocateMdl( 28 | base as *mut _, 29 | size as u32, 30 | BOOLEAN::from(false), 31 | BOOLEAN::from(false), 32 | null_mut(), 33 | ) 34 | }; 35 | if mdl.is_null() { 36 | return Err(()); 37 | } 38 | let mapped_base = unsafe { 39 | MmBuildMdlForNonPagedPool(mdl); 40 | // Lock the mdl with MmMapLockedPagesSpecifyCache 41 | MmMapLockedPagesSpecifyCache( 42 | mdl, 43 | KernelMode as KPROCESSOR_MODE, 44 | MmNonCached, 45 | null_mut(), 46 | 1, 47 | HighPagePriority as ULONG, 48 | ) as u64 49 | }; 50 | 51 | Ok(MyMDL { mdl, mapped_base }) 52 | } 53 | 54 | // Makes the MDL pages writeable using MmProtectMdlSystemAddress 55 | pub(crate) fn make_mdl_pages_writeable(mdl: &MyMDL) { 56 | let result = unsafe { MmProtectMdlSystemAddress(mdl.mdl, PAGE_EXECUTE_READWRITE) }; 57 | if result != 0 { 58 | panic!( 59 | "MmProtectMdlSystemAddress failed with error code {:#x}", 60 | result 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /read_write_driver/src/shared.rs: -------------------------------------------------------------------------------- 1 | /// IOCTL code for the ReadWriteIoctl request. Uses METHOD_BUFFERED and FILE_ANY_ACCESS. 2 | pub(crate) const IOCTL_REQUEST: u32 = 0x222004; 3 | 4 | /// Struct to be used as the input buffer for the IOCTL_REQUEST command. 5 | /// Specifies the target process ID, the address to read/write, and the buffer to read/write. 6 | /// Comprised of a ReadWriteIoctlHeader and a dynamic-length buffer (size provided by ReadWriteIoctlHeader.buffer_len). 7 | #[repr(C)] 8 | pub(crate) struct ReadWriteIoctl { 9 | pub(crate) header: ReadWriteIoctlHeader, 10 | pub(crate) buffer: [u8; 0], 11 | } 12 | 13 | /// Header portion of the ReadWriteIoctl struct. Contains the target process ID, the address to read/write, and the buffer length. 14 | #[repr(C)] 15 | pub(crate) struct ReadWriteIoctlHeader { 16 | pub(crate) target_pid: u32, 17 | pub(crate) address: usize, 18 | pub(crate) symbols: IoctlSymbolOffsets, 19 | pub(crate) buffer_len: usize, 20 | } 21 | 22 | #[repr(C)] 23 | #[derive(Debug)] 24 | pub(crate) struct IoctlSymbolOffsets { 25 | pub(crate) directory_table_base: Option, 26 | pub(crate) va_space_deleted: Option, 27 | } 28 | 29 | #[repr(C)] 30 | #[derive(Debug)] 31 | pub(crate) struct VaSpaceDeleted { 32 | pub(crate) offset: usize, 33 | pub(crate) bit_pos: usize, 34 | } 35 | -------------------------------------------------------------------------------- /read_write_driver/src/uni.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use wdk_sys::UNICODE_STRING; 3 | 4 | /// A wrapper around a Vec that represents a unicode string 5 | pub(crate) struct OwnedUnicodeString { 6 | buffer: Vec, 7 | _phantompinned: core::marker::PhantomPinned, 8 | } 9 | 10 | impl OwnedUnicodeString { 11 | /// Convert the OwnedUnicodeString to a UNICODE_STRING. 12 | /// SAFETY: `self` must be pinned and remain valid for the lifetime of the UNICODE_STRING. 13 | pub(crate) fn to_unicode(&self) -> UNICODE_STRING { 14 | // Note: we subtract 2 from the length to account for the u16 null terminator, as the length field is the length of the string minus the null terminator. 15 | UNICODE_STRING { 16 | Length: ((self.buffer.len() * core::mem::size_of::()) - 2) as u16, 17 | MaximumLength: (self.buffer.len() * core::mem::size_of::()) as u16, 18 | Buffer: self.buffer.as_ptr() as *mut u16, 19 | } 20 | } 21 | } 22 | 23 | /// Creates a new OwnedUnicodeString from a rust string. The string is converted to a wide string and null-terminated. 24 | pub(crate) fn str_to_unicode(s: &str) -> OwnedUnicodeString { 25 | // Convert the rust string to a wide string 26 | let mut wide_string: Vec = s.encode_utf16().collect(); 27 | wide_string.push(0); // Null terminate the string 28 | OwnedUnicodeString { 29 | buffer: wide_string, 30 | _phantompinned: core::marker::PhantomPinned, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /read_write_user/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /read_write_user/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "read_write_user" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Christopher Vella "] 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.5.1", features=["derive"] } 10 | clap-num = "1.1.1" 11 | pdblister = {path = "../pdblister"} 12 | anyhow = "1.0.82" 13 | pdb = "0.8.0" 14 | tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] } 15 | 16 | [dependencies.windows] 17 | version = "0.52" 18 | features = [ 19 | "Data_Xml_Dom", 20 | "Win32_Foundation", 21 | "Win32_Security", 22 | "Win32_System_Threading", 23 | "Win32_UI_WindowsAndMessaging", 24 | "Win32_Storage_FileSystem", 25 | "Win32_System_IO", 26 | ] 27 | -------------------------------------------------------------------------------- /read_write_user/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use std::marker::PhantomData; 3 | use std::path::PathBuf; 4 | use std::str::FromStr; 5 | 6 | use crate::shared::IoctlSymbolOffsets; 7 | use anyhow::Error; 8 | use clap::Parser; 9 | use clap_num::maybe_hex; 10 | use pdb::{FallibleIterator, RawString, TypeFinder}; 11 | use pdblister::symsrv::SymFileInfo; 12 | use pdblister::{connect_servers, get_pdb, ManifestEntry}; 13 | use windows::Win32::System::IO::DeviceIoControl; 14 | use windows::Win32::{ 15 | Foundation::{GENERIC_READ, GENERIC_WRITE}, 16 | Storage::FileSystem::{CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, OPEN_EXISTING}, 17 | }; 18 | 19 | pub mod uni; 20 | 21 | // Shared types and constants between the kernel driver and user-mode application 22 | #[path = "..\\..\\read_write_driver\\src\\shared.rs"] 23 | pub mod shared; 24 | 25 | #[derive(Parser, Debug)] 26 | #[command(version, about, long_about = None)] 27 | struct Args { 28 | /// Target PID to write into 29 | #[arg(short, long)] 30 | pid: u32, 31 | 32 | /// Address to write into, may be in decimal or hex (when prefixed with 0x) 33 | #[arg(short, long, value_parser=maybe_hex::)] 34 | address: usize, 35 | 36 | /// Determines if we download and parse NT symbols to resolve offsets for the driver 37 | #[arg(short, long, action)] 38 | use_symbols: bool, 39 | } 40 | 41 | // Path to our kernel device 42 | const DEVICE_PATH: &str = "\\\\.\\ReadWriteDevice"; 43 | 44 | #[tokio::main] 45 | async fn main() { 46 | let args = Args::parse(); 47 | // Hardcoded bytes we'll write into the target process as a test. This may be replaced with a dynamic buffer. 48 | let buffer = [0x01, 0x03, 0x03, 0x07]; 49 | // Send the IOCTL request to the kernel device to write the buffer into the target process at the specified address 50 | write_ioctl_buffer(&args, &buffer).await; 51 | } 52 | 53 | async fn get_nt_symbols() -> Result { 54 | let filepath = "C:\\Windows\\System32\\ntoskrnl.exe"; 55 | // get user temp path 56 | let temp_path = std::env::temp_dir(); 57 | // Get env var NT_SYMBOL_PATH if it exists, otherwise hardcode https://msdl.microsoft.com/download/symbols 58 | let sym_path = match std::env::var("_NT_SYMBOL_PATH") { 59 | Ok(val) => val, 60 | Err(_) => format!( 61 | "srv*{}*https://msdl.microsoft.com/download/symbols", 62 | temp_path.display() 63 | ) 64 | .to_string(), 65 | }; 66 | let result: Result<(&'static str, PathBuf), anyhow::Error> = async { 67 | let servers = connect_servers(&sym_path)?; 68 | 69 | // Resolve the PDB for the executable specified. 70 | let e = ManifestEntry::from_str( 71 | &get_pdb((&filepath).as_ref()).context("failed to resolve PDB hash")?, 72 | ) 73 | .unwrap(); 74 | let info = SymFileInfo::RawHash(e.hash); 75 | 76 | for srv in servers.iter() { 77 | let (message, path) = { 78 | if let Some(p) = srv.find_file(&e.name, &info) { 79 | ("file already cached", p) 80 | } else { 81 | let path = srv 82 | .download_file(&e.name, &info) 83 | .await 84 | .context("failed to download PDB")?; 85 | 86 | ("file successfully downloaded", path) 87 | } 88 | }; 89 | 90 | return Ok((message, path)); 91 | } 92 | 93 | anyhow::bail!("no server returned the PDB file") 94 | } 95 | .await; 96 | 97 | match result { 98 | Ok((_message, path)) => { 99 | let mut va_space_deleted = None; 100 | let mut va_space_idx = None; 101 | let mut directory_table_base = None; 102 | let mut bit_pos = None; 103 | let file = std::fs::File::open(path)?; 104 | let mut pdb = pdb::PDB::open(file)?; 105 | 106 | let type_information = pdb.type_information()?; 107 | let mut type_finder = type_information.finder(); 108 | let mut iter = type_information.iter(); 109 | 'loop1: while let Some(typ) = iter.next()? { 110 | // build the type finder as we go 111 | type_finder.update(&iter); 112 | 113 | // parse the type record 114 | match typ.parse() { 115 | Ok(pdb::TypeData::Class(pdb::ClassType { 116 | name: _, 117 | properties: _, 118 | fields: Some(fields), 119 | .. 120 | })) => { 121 | // this Type describes a class-like type with fields 122 | // `fields` is a TypeIndex which refers to a FieldList 123 | // To find information about the fields, find and parse that Type 124 | match type_finder.find(fields)?.parse()? { 125 | pdb::TypeData::FieldList(list) => { 126 | find_from_fieldlist( 127 | &mut type_finder, 128 | &list, 129 | &mut va_space_deleted, 130 | &mut va_space_idx, 131 | &mut directory_table_base, 132 | &mut bit_pos, 133 | ); 134 | if va_space_deleted.is_some() 135 | && directory_table_base.is_some() 136 | && bit_pos.is_some() 137 | { 138 | break 'loop1; 139 | } 140 | } 141 | _ => {} 142 | } 143 | } 144 | Ok(pdb::TypeData::Bitfield(bitfield)) => { 145 | let underlying_type = bitfield.underlying_type; 146 | if underlying_type.0 == 0x1334 { 147 | println!("bitfield: {:#x?}", bitfield); 148 | } 149 | } 150 | Ok(pdb::TypeData::Union(uniont)) => { 151 | if uniont.fields.0 == 0x1334 152 | || uniont.name == RawString::from("ProcessFlags") 153 | { 154 | println!("union: {:#x?}", uniont); 155 | } 156 | } 157 | Ok(_) => { 158 | // ignore everything that's not a class-like type 159 | } 160 | Err(pdb::Error::UnimplementedTypeKind(_)) => { 161 | // found an unhandled type record 162 | // this probably isn't fatal in most use cases 163 | } 164 | Err(e) => { 165 | // other error, probably is worth failing 166 | return Err(Error::from(e)); 167 | } 168 | } 169 | } 170 | 171 | if let (Some(va_space_deleted), Some(directory_table_base), Some(bit_pos)) = 172 | (va_space_deleted, directory_table_base, bit_pos) 173 | { 174 | let va_space_deleted = shared::VaSpaceDeleted { 175 | offset: va_space_deleted as usize, 176 | bit_pos: bit_pos as usize, 177 | }; 178 | return Ok(shared::IoctlSymbolOffsets { 179 | va_space_deleted: Some(va_space_deleted), 180 | directory_table_base: Some(directory_table_base as usize), 181 | }); 182 | } else { 183 | anyhow::bail!("Failed to find required symbols"); 184 | } 185 | } 186 | Err(e) => { 187 | return Err(e); 188 | } 189 | } 190 | } 191 | 192 | fn find_from_fieldlist( 193 | type_finder: &mut TypeFinder, 194 | list: &pdb::FieldList, 195 | va_space_deleted: &mut Option, 196 | va_space_idx: &mut Option, 197 | directory_table_base: &mut Option, 198 | bit_pos: &mut Option, 199 | ) { 200 | for field in &list.fields { 201 | if let pdb::TypeData::Member(member) = field { 202 | if member.name == RawString::from("DirectoryTableBase") { 203 | *directory_table_base = Some(member.offset as usize); 204 | if va_space_deleted.is_some() && bit_pos.is_some() { 205 | break; 206 | } 207 | } else if member.name == RawString::from("VaSpaceDeleted") { 208 | *va_space_deleted = Some(member.offset as usize); 209 | println!("va_space_index:{:#x}", member.field_type.0); 210 | *va_space_idx = Some(member.field_type.0 as usize); 211 | 212 | let newt = type_finder 213 | .find(member.field_type) 214 | .unwrap() 215 | .parse() 216 | .unwrap(); 217 | if let pdb::TypeData::Bitfield(bitfield) = newt { 218 | *bit_pos = Some(bitfield.position as usize); 219 | } 220 | 221 | if directory_table_base.is_some() && bit_pos.is_some() { 222 | break; 223 | } 224 | } 225 | } 226 | 227 | if let Some(more_fields) = list.continuation { 228 | let new_list = type_finder.find(more_fields).unwrap().parse().unwrap(); 229 | if let pdb::TypeData::FieldList(nlist) = new_list { 230 | // A FieldList can be split across multiple records 231 | find_from_fieldlist( 232 | type_finder, 233 | &nlist, 234 | va_space_deleted, 235 | va_space_idx, 236 | directory_table_base, 237 | bit_pos, 238 | ); 239 | } else { 240 | panic!("Expected FieldList"); 241 | } 242 | } 243 | } 244 | } 245 | 246 | /// Write the bytes from `buffer` into the target process with PID `pid` at the address `address`, using our kernel device. 247 | async fn write_ioctl_buffer(args: &Args, buffer: &[u8]) { 248 | // Create a file handle to the device, 249 | let device_path = uni::owned_string_from_str(DEVICE_PATH); 250 | let device_handle = unsafe { 251 | CreateFileW( 252 | device_path.as_pcwstr(), 253 | GENERIC_READ.0 | GENERIC_WRITE.0, 254 | FILE_SHARE_READ, 255 | None, 256 | OPEN_EXISTING, 257 | FILE_ATTRIBUTE_NORMAL, 258 | None, 259 | ) 260 | .unwrap() 261 | }; 262 | let symbols = match args.use_symbols { 263 | true => { 264 | // Panic if we fail to get the symbols when requested 265 | get_nt_symbols().await.unwrap() 266 | } 267 | false => IoctlSymbolOffsets { 268 | va_space_deleted: None, 269 | directory_table_base: None, 270 | }, 271 | }; 272 | 273 | // Create the IOCTL request 274 | // Determine size of the entire request, which is the size of the header plus the size of the dynamic buffer 275 | let req_size = core::mem::size_of::() + buffer.len(); 276 | // Create a new dynamic-sized buffer to hold the request 277 | let mut req_buffer: VLS = VLS::new(req_size); 278 | unsafe { 279 | // Get a mutable reference to the request buffer for initialization 280 | let req = req_buffer.as_mut(); 281 | // Write the header of the request 282 | *req = shared::ReadWriteIoctl { 283 | header: shared::ReadWriteIoctlHeader { 284 | target_pid: args.pid, 285 | address: args.address, 286 | symbols: symbols, 287 | buffer_len: buffer.len(), 288 | }, 289 | buffer: [0; 0], 290 | }; 291 | // Copy our dynamic buffer into the request from the location of the `buffer` field, this initializes the buffer in our ReadWriteIoctl struct 292 | std::ptr::copy_nonoverlapping(buffer.as_ptr(), (*req).buffer.as_mut_ptr(), buffer.len()); 293 | } 294 | // Send the ioctl request and ensure it was successful (or panic if it wasn't) 295 | let mut bytes_written = 0; 296 | unsafe { 297 | DeviceIoControl( 298 | device_handle, 299 | shared::IOCTL_REQUEST, 300 | Some(req_buffer.as_mut() as *mut _ as _), 301 | req_size as u32, 302 | None, 303 | 0, 304 | Some(&mut bytes_written), 305 | None, 306 | ) 307 | .unwrap(); 308 | } 309 | } 310 | 311 | /// Variable-length buffer of dynamic type T, allows allocating a dynamic-sized buffer and treating it as a type T 312 | #[derive(Default)] 313 | pub struct VLS { 314 | v: Vec, 315 | _phantom: PhantomData, 316 | } 317 | 318 | // Implement deref and deref_mut to allow treating the VLS as a type T 319 | impl core::ops::Deref for VLS { 320 | type Target = T; 321 | 322 | fn deref(&self) -> &T { 323 | unsafe { core::mem::transmute(self.v.as_ptr()) } 324 | } 325 | } 326 | 327 | impl core::ops::DerefMut for VLS { 328 | fn deref_mut(&mut self) -> &mut T { 329 | unsafe { core::mem::transmute(self.v.as_mut_ptr()) } 330 | } 331 | } 332 | 333 | // If T is Copy, we can implement From for VLS to allow creating a VLS from a value of type T 334 | impl From for VLS { 335 | fn from(val: T) -> Self { 336 | let mut ret = Self::new(core::mem::size_of_val(&val)); 337 | ret.as_slice_mut()[0] = val; 338 | ret 339 | } 340 | } 341 | 342 | impl VLS { 343 | /// Create a VLS with a specified byte size. 344 | pub fn new(size: usize) -> Self { 345 | let v = vec![0u8; size]; 346 | Self { 347 | v, 348 | _phantom: PhantomData::default(), 349 | } 350 | } 351 | 352 | /// Return an array slice of type T, guaranteed to not overflow the bounds 353 | /// of the allocated data 354 | pub fn as_slice(&self) -> &[T] { 355 | unsafe { 356 | core::slice::from_raw_parts( 357 | self.v.as_ptr() as *const T, 358 | self.v.len() / core::mem::size_of::(), 359 | ) 360 | } 361 | } 362 | 363 | /// Return a mutable array slice of type T, guaranteed to not overflow the 364 | /// bounds of the allocated data 365 | pub fn as_slice_mut(&mut self) -> &mut [T] { 366 | unsafe { 367 | core::slice::from_raw_parts_mut( 368 | self.v.as_mut_ptr() as *mut T, 369 | self.v.len() / core::mem::size_of::(), 370 | ) 371 | } 372 | } 373 | 374 | /// Returns a raw mut pointer to T 375 | pub fn as_mut(&mut self) -> *mut T { 376 | self.v.as_mut_ptr() as *mut T 377 | } 378 | 379 | /// Returns a byte slice of the underlying data 380 | pub fn as_bytes(&self) -> &[u8] { 381 | &self.v 382 | } 383 | } 384 | 385 | // tests 386 | #[cfg(test)] 387 | mod tests { 388 | use super::*; 389 | 390 | #[tokio::test] 391 | async fn test_sym() { 392 | let syms = get_nt_symbols().await.unwrap(); 393 | assert!(syms.va_space_deleted.is_some()); 394 | assert!(syms.directory_table_base.is_some()); 395 | println!("Symbols: {:#x?}", syms); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /read_write_user/src/uni.rs: -------------------------------------------------------------------------------- 1 | use windows::core::PCWSTR; 2 | 3 | pub struct OwnedString { 4 | _s: String, 5 | buf: Vec, 6 | _phantom_pinned: std::marker::PhantomPinned, 7 | } 8 | 9 | impl OwnedString { 10 | pub fn as_pcwstr(&self) -> PCWSTR { 11 | PCWSTR::from_raw(self.buf.as_ptr() as _) 12 | } 13 | } 14 | 15 | pub fn owned_string_from_str(s: &str) -> OwnedString { 16 | let mut buf = Vec::with_capacity(s.encode_utf16().count() + 1); 17 | buf.extend(s.encode_utf16()); 18 | buf.push(0); 19 | OwnedString { 20 | _s: s.to_string(), 21 | buf, 22 | _phantom_pinned: std::marker::PhantomPinned, 23 | } 24 | } 25 | --------------------------------------------------------------------------------