├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── screenshot.png └── src ├── constraint.rs ├── int.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.peekieboi 3 | -------------------------------------------------------------------------------- /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 = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cc" 19 | version = "1.0.73" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "clipboard-win" 31 | version = "4.4.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" 34 | dependencies = [ 35 | "error-code", 36 | "str-buf", 37 | "winapi", 38 | ] 39 | 40 | [[package]] 41 | name = "dirs-next" 42 | version = "2.0.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 45 | dependencies = [ 46 | "cfg-if", 47 | "dirs-sys-next", 48 | ] 49 | 50 | [[package]] 51 | name = "dirs-sys-next" 52 | version = "0.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 55 | dependencies = [ 56 | "libc", 57 | "redox_users", 58 | "winapi", 59 | ] 60 | 61 | [[package]] 62 | name = "endian-type" 63 | version = "0.1.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 66 | 67 | [[package]] 68 | name = "errno" 69 | version = "0.2.8" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 72 | dependencies = [ 73 | "errno-dragonfly", 74 | "libc", 75 | "winapi", 76 | ] 77 | 78 | [[package]] 79 | name = "errno-dragonfly" 80 | version = "0.1.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 83 | dependencies = [ 84 | "cc", 85 | "libc", 86 | ] 87 | 88 | [[package]] 89 | name = "error-code" 90 | version = "2.3.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 93 | dependencies = [ 94 | "libc", 95 | "str-buf", 96 | ] 97 | 98 | [[package]] 99 | name = "fd-lock" 100 | version = "3.0.5" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca" 103 | dependencies = [ 104 | "cfg-if", 105 | "rustix", 106 | "windows-sys", 107 | ] 108 | 109 | [[package]] 110 | name = "getrandom" 111 | version = "0.2.6" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 114 | dependencies = [ 115 | "cfg-if", 116 | "libc", 117 | "wasi", 118 | ] 119 | 120 | [[package]] 121 | name = "io-lifetimes" 122 | version = "0.6.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504" 125 | 126 | [[package]] 127 | name = "libc" 128 | version = "0.2.126" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 131 | 132 | [[package]] 133 | name = "libprocmem" 134 | version = "0.1.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "2402273fe03fd8e2d11cb8153f86a24699eef50a49409205013ffcd38313d66a" 137 | 138 | [[package]] 139 | name = "linux-raw-sys" 140 | version = "0.0.46" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" 143 | 144 | [[package]] 145 | name = "log" 146 | version = "0.4.17" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 149 | dependencies = [ 150 | "cfg-if", 151 | ] 152 | 153 | [[package]] 154 | name = "memchr" 155 | version = "2.5.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 158 | 159 | [[package]] 160 | name = "memoffset" 161 | version = "0.6.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 164 | dependencies = [ 165 | "autocfg", 166 | ] 167 | 168 | [[package]] 169 | name = "mempeek" 170 | version = "0.1.4" 171 | dependencies = [ 172 | "libprocmem", 173 | "quoted_strings", 174 | "rustyline", 175 | ] 176 | 177 | [[package]] 178 | name = "nibble_vec" 179 | version = "0.1.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 182 | dependencies = [ 183 | "smallvec", 184 | ] 185 | 186 | [[package]] 187 | name = "nix" 188 | version = "0.23.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" 191 | dependencies = [ 192 | "bitflags", 193 | "cc", 194 | "cfg-if", 195 | "libc", 196 | "memoffset", 197 | ] 198 | 199 | [[package]] 200 | name = "proc-macro2" 201 | version = "1.0.39" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 204 | dependencies = [ 205 | "unicode-ident", 206 | ] 207 | 208 | [[package]] 209 | name = "quote" 210 | version = "1.0.18" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 213 | dependencies = [ 214 | "proc-macro2", 215 | ] 216 | 217 | [[package]] 218 | name = "quoted_strings" 219 | version = "0.1.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "f1c6d56aa6564abc425bab3c1035089117da5218bffb3f9fb3d0066912f32a2c" 222 | 223 | [[package]] 224 | name = "radix_trie" 225 | version = "0.2.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 228 | dependencies = [ 229 | "endian-type", 230 | "nibble_vec", 231 | ] 232 | 233 | [[package]] 234 | name = "redox_syscall" 235 | version = "0.2.13" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 238 | dependencies = [ 239 | "bitflags", 240 | ] 241 | 242 | [[package]] 243 | name = "redox_users" 244 | version = "0.4.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 247 | dependencies = [ 248 | "getrandom", 249 | "redox_syscall", 250 | "thiserror", 251 | ] 252 | 253 | [[package]] 254 | name = "rustix" 255 | version = "0.34.8" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "2079c267b8394eb529872c3cf92e181c378b41fea36e68130357b52493701d2e" 258 | dependencies = [ 259 | "bitflags", 260 | "errno", 261 | "io-lifetimes", 262 | "libc", 263 | "linux-raw-sys", 264 | "winapi", 265 | ] 266 | 267 | [[package]] 268 | name = "rustyline" 269 | version = "9.1.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039" 272 | dependencies = [ 273 | "bitflags", 274 | "cfg-if", 275 | "clipboard-win", 276 | "dirs-next", 277 | "fd-lock", 278 | "libc", 279 | "log", 280 | "memchr", 281 | "nix", 282 | "radix_trie", 283 | "scopeguard", 284 | "smallvec", 285 | "unicode-segmentation", 286 | "unicode-width", 287 | "utf8parse", 288 | "winapi", 289 | ] 290 | 291 | [[package]] 292 | name = "scopeguard" 293 | version = "1.1.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 296 | 297 | [[package]] 298 | name = "smallvec" 299 | version = "1.8.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 302 | 303 | [[package]] 304 | name = "str-buf" 305 | version = "1.0.6" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 308 | 309 | [[package]] 310 | name = "syn" 311 | version = "1.0.95" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "unicode-ident", 318 | ] 319 | 320 | [[package]] 321 | name = "thiserror" 322 | version = "1.0.31" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 325 | dependencies = [ 326 | "thiserror-impl", 327 | ] 328 | 329 | [[package]] 330 | name = "thiserror-impl" 331 | version = "1.0.31" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 334 | dependencies = [ 335 | "proc-macro2", 336 | "quote", 337 | "syn", 338 | ] 339 | 340 | [[package]] 341 | name = "unicode-ident" 342 | version = "1.0.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 345 | 346 | [[package]] 347 | name = "unicode-segmentation" 348 | version = "1.9.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 351 | 352 | [[package]] 353 | name = "unicode-width" 354 | version = "0.1.9" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 357 | 358 | [[package]] 359 | name = "utf8parse" 360 | version = "0.2.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 363 | 364 | [[package]] 365 | name = "wasi" 366 | version = "0.10.2+wasi-snapshot-preview1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 369 | 370 | [[package]] 371 | name = "winapi" 372 | version = "0.3.9" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 375 | dependencies = [ 376 | "winapi-i686-pc-windows-gnu", 377 | "winapi-x86_64-pc-windows-gnu", 378 | ] 379 | 380 | [[package]] 381 | name = "winapi-i686-pc-windows-gnu" 382 | version = "0.4.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 385 | 386 | [[package]] 387 | name = "winapi-x86_64-pc-windows-gnu" 388 | version = "0.4.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 391 | 392 | [[package]] 393 | name = "windows-sys" 394 | version = "0.30.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a" 397 | dependencies = [ 398 | "windows_aarch64_msvc", 399 | "windows_i686_gnu", 400 | "windows_i686_msvc", 401 | "windows_x86_64_gnu", 402 | "windows_x86_64_msvc", 403 | ] 404 | 405 | [[package]] 406 | name = "windows_aarch64_msvc" 407 | version = "0.30.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" 410 | 411 | [[package]] 412 | name = "windows_i686_gnu" 413 | version = "0.30.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" 416 | 417 | [[package]] 418 | name = "windows_i686_msvc" 419 | version = "0.30.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" 422 | 423 | [[package]] 424 | name = "windows_x86_64_gnu" 425 | version = "0.30.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" 428 | 429 | [[package]] 430 | name = "windows_x86_64_msvc" 431 | version = "0.30.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" 434 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mempeek" 3 | version = "0.1.5" 4 | edition = "2021" 5 | license = "BSD-2-Clause" 6 | description = "A command line tool that resembles a debugger as well as Cheat Engine, to search for values in memory" 7 | documentation = "https://docs.rs/mempeek" 8 | repository = "https://github.com/gamozolabs/mempeek" 9 | keywords = ["proc", "mem", "maps"] 10 | categories = ["os::linux-apis", "command-line-utilities"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | libprocmem = "0.1" 16 | quoted_strings = "0.1.0" 17 | rustyline = "9.1" 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, gamozolabs 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This is a small command-line tool designed to peek around memory of a running 4 | Linux process. It also provides filtering mechanisms similar to Cheat Engine 5 | to scan for memory of certain values. 6 | 7 | It uses `rustyline` to maintain a history of command line arguments which is 8 | persisted in the `.peekieboi` file. Allowing "up-arrow" to work across 9 | different runs of the tool! 10 | 11 | # Installing 12 | 13 | Simply run `cargo install mempeek` to install this tool! Then invoke it by 14 | running `mempeek ` 15 | 16 | # Commands 17 | 18 | ## Expression support 19 | 20 | I've added extremely basic support for expressions of various radix as well as 21 | add, subtract, multiply, and divide. No support for parenthesis (yet). 22 | 23 | This allows you to use an expression like `0x13370000+0o100*4` in any argument 24 | to a command which expects a constant value. _The default radix for numbers 25 | is 16, thus, hex unless you use an `0b`, `0o`, or `0d` prefix_ 26 | 27 | ## Types 28 | 29 | Types may be one of the following: 30 | 31 | - `b` - `u8` 32 | - `w` - `u16` 33 | - `d` - `u32` 34 | - `q` - `u64` 35 | - `B` - `i8` 36 | - `W` - `i16` 37 | - `D` - `i32` 38 | - `Q` - `i64` 39 | - `f` - `f32` 40 | - `F` - `f64` 41 | 42 | ## Constraints 43 | 44 | Constraints may be any one of the following: 45 | 46 | - `=[val]` - Equal to `[val]` 47 | - `![val]` - Not equal to `[val]` 48 | - `>[val]` - Greater than `[val]` 49 | - `>=[val]` - Greater than or equal to `[val]` 50 | - `<[val]` - Less than `[val]` 51 | - `<=[val]` - Less than or equal to `[val]` 52 | 53 | Currently this only supports a few commands 54 | 55 | ## `q` | `exit` | `quit` 56 | 57 | Exit the program 58 | 59 | ## `h ` 60 | 61 | Get the results from a previous memory scan. Takes the query index of the query 62 | to retrieve. Optionally, you can use `l` in place of the query index to get the 63 | most recent query results 64 | 65 | ## `s[bwdqBWDQfF] [constraints]` 66 | 67 | Scan memory for a value of a given type starting at `addr` for `length` bytes 68 | using `constraints` 69 | 70 | ## `u[bwdqBWDQfFo] [constraints]` 71 | 72 | Using the address list from a previous query, interpret the pointed-to-value as 73 | the specified type `o` implies that the update should use the type of the 74 | original query. 75 | 76 | ## `d[bwdqBWDQfF] []` 77 | 78 | Dump memory interpreted as a given type for a given number of bytes 79 | 80 | ## `ss ` 81 | 82 | Search for a `string` in a region of memory specified by `addr` and `length` 83 | (in bytes) 84 | 85 | ## `m` 86 | 87 | Dump memory regions and their permissions. 88 | 89 | # Example 90 | 91 | ![Example of mempeek](/screenshot.png) 92 | 93 | _Green values in the dump output indicate that the value is a valid pointer 94 | when cast to a `u64`_ 95 | 96 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/mempeek/4fbd53d9bd17682827436c046e29cb03a959a634/screenshot.png -------------------------------------------------------------------------------- /src/constraint.rs: -------------------------------------------------------------------------------- 1 | ///! Basic constraints 2 | 3 | use crate::{Error, Result, Value}; 4 | 5 | /// Different constraints we can apply on a value 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum Constraint { 8 | /// `=` 9 | Equal(Value), 10 | 11 | /// `!` 12 | NotEqual(Value), 13 | 14 | /// `>` 15 | Greater(Value), 16 | 17 | /// `>=` 18 | GreaterEqual(Value), 19 | 20 | /// `<` 21 | Less(Value), 22 | 23 | /// `<=` 24 | LessEqual(Value), 25 | } 26 | 27 | impl Constraint { 28 | /// Replace the value with a new one 29 | pub fn update_val(&mut self, val: Value) { 30 | match self { 31 | Self::Greater(ref mut rhs) | 32 | Self::GreaterEqual(ref mut rhs) | 33 | Self::Less(ref mut rhs) | 34 | Self::LessEqual(ref mut rhs) | 35 | Self::Equal(ref mut rhs) => *rhs = val, 36 | Self::NotEqual(ref mut rhs) => *rhs = val, 37 | } 38 | } 39 | 40 | /// Check a condition 41 | pub fn check(&self, lhs: Value) -> bool { 42 | match *self { 43 | Self::Greater(rhs) => lhs > rhs, 44 | Self::GreaterEqual(rhs) => lhs >= rhs, 45 | Self::Less(rhs) => lhs < rhs, 46 | Self::LessEqual(rhs) => lhs <= rhs, 47 | Self::Equal(rhs) => lhs == rhs, 48 | Self::NotEqual(rhs) => lhs != rhs, 49 | } 50 | } 51 | 52 | /// Create a new constraint from `s` using `val` as the type for the value 53 | pub fn from_str_value(s: &str, val: Option) -> Result { 54 | if s.starts_with("=") { 55 | if let Some(mut val) = val { 56 | val.update_str(&s[1..])?; 57 | Ok(Constraint::Equal(val)) 58 | } else { 59 | Ok(Constraint::Equal(Value::U8(0))) 60 | } 61 | } else if s.starts_with("!") { 62 | if let Some(mut val) = val { 63 | val.update_str(&s[1..])?; 64 | Ok(Constraint::NotEqual(val)) 65 | } else { 66 | Ok(Constraint::NotEqual(Value::U8(0))) 67 | } 68 | } else if s.starts_with(">=") { 69 | if let Some(mut val) = val { 70 | val.update_str(&s[2..])?; 71 | Ok(Constraint::GreaterEqual(val)) 72 | } else { 73 | Ok(Constraint::GreaterEqual(Value::U8(0))) 74 | } 75 | } else if s.starts_with(">") { 76 | if let Some(mut val) = val { 77 | val.update_str(&s[1..])?; 78 | Ok(Constraint::Greater(val)) 79 | } else { 80 | Ok(Constraint::Greater(Value::U8(0))) 81 | } 82 | } else if s.starts_with("<=") { 83 | if let Some(mut val) = val { 84 | val.update_str(&s[2..])?; 85 | Ok(Constraint::LessEqual(val)) 86 | } else { 87 | Ok(Constraint::LessEqual(Value::U8(0))) 88 | } 89 | } else if s.starts_with("<") { 90 | if let Some(mut val) = val { 91 | val.update_str(&s[1..])?; 92 | Ok(Constraint::Less(val)) 93 | } else { 94 | Ok(Constraint::Less(Value::U8(0))) 95 | } 96 | } else { 97 | Err(Error::InvalidConstraint) 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/int.rs: -------------------------------------------------------------------------------- 1 | //! Integer parsing helper libraries 2 | 3 | use crate::Error; 4 | 5 | macro_rules! impl_expr { 6 | ($name:ident, $name_int:ident, $ty:ty) => { 7 | pub fn $name(s: &str) -> crate::Result<$ty> { 8 | #[derive(Clone, Copy, Debug)] 9 | enum Expr { 10 | Add, 11 | Sub, 12 | Mul, 13 | Div, 14 | Val($ty), 15 | } 16 | 17 | // Split expression into components 18 | let mut components = Vec::new(); 19 | 20 | let mut cur = String::new(); 21 | for chr in s.chars() { 22 | if matches!(chr, '+' | '-' | '*' | '/') { 23 | // Push the current component 24 | components.push(Expr::Val($name_int(&cur)?)); 25 | match chr { 26 | '+' => components.push(Expr::Add), 27 | '-' => components.push(Expr::Sub), 28 | '*' => components.push(Expr::Mul), 29 | '/' => components.push(Expr::Div), 30 | _ => unreachable!(), 31 | } 32 | cur.clear(); 33 | continue; 34 | } 35 | 36 | cur.push(chr); 37 | } 38 | 39 | // Flush remaining data 40 | if !cur.is_empty() { 41 | components.push(Expr::Val($name_int(&cur)?)); 42 | } 43 | 44 | let mut ii = 0; 45 | while ii < components.len().saturating_sub(2) { 46 | let (left, op, right) = ( 47 | components[ii + 0], 48 | components[ii + 1], 49 | components[ii + 2], 50 | ); 51 | 52 | let res = match (left, op, right) { 53 | (Expr::Val(l), Expr::Mul, Expr::Val(r)) => 54 | l.wrapping_mul(r), 55 | (Expr::Val(l), Expr::Div, Expr::Val(r)) => 56 | l.wrapping_div(r), 57 | _ => { 58 | ii += 1; 59 | continue; 60 | } 61 | }; 62 | 63 | // Replace the expression with the result 64 | components[ii] = Expr::Val(res); 65 | 66 | // Remove the operation and right side as we've "consumed" them 67 | components.drain(ii + 1..ii + 3); 68 | } 69 | 70 | let mut ii = 0; 71 | while ii < components.len().saturating_sub(2) { 72 | let (left, op, right) = ( 73 | components[ii + 0], 74 | components[ii + 1], 75 | components[ii + 2], 76 | ); 77 | 78 | let res = match (left, op, right) { 79 | (Expr::Val(l), Expr::Add, Expr::Val(r)) => 80 | l.wrapping_add(r), 81 | (Expr::Val(l), Expr::Sub, Expr::Val(r)) => 82 | l.wrapping_sub(r), 83 | _ => { 84 | ii += 1; 85 | continue; 86 | } 87 | }; 88 | 89 | // Replace the expression with the result 90 | components[ii] = Expr::Val(res); 91 | 92 | // Remove the operation and right side as we've "consumed" them 93 | components.drain(ii + 1..ii + 3); 94 | } 95 | 96 | match components.as_slice() { 97 | [Expr::Val(ret)] => Ok(*ret), 98 | _ => Err(Error::InvalidExpression), 99 | } 100 | } 101 | } 102 | } 103 | 104 | macro_rules! int_parse { 105 | ($name:ident, $name_int:ident, $ty:ty) => { 106 | /// Parse an integer with optional base override prefix 107 | pub fn $name_int(mut s: &str) -> crate::Result<$ty> { 108 | // Default base 109 | let mut base = 16; 110 | 111 | // Invert sign 112 | let mut inv = false; 113 | 114 | // Check for a prefix 115 | match s.get(0..3) { 116 | Some("-0x" | "-0X") => { base = 16; s = &s[3..]; inv = true; } 117 | Some("-0d" | "-0D") => { base = 10; s = &s[3..]; inv = true; } 118 | Some("-0o" | "-0O") => { base = 8; s = &s[3..]; inv = true; } 119 | Some("-0b" | "-0B") => { base = 2; s = &s[3..]; inv = true; } 120 | _ => { 121 | // Check for a prefix 122 | match s.get(0..2) { 123 | Some("0x" | "0X") => { base = 16; s = &s[2..]; } 124 | Some("0d" | "0D") => { base = 10; s = &s[2..]; } 125 | Some("0o" | "0O") => { base = 8; s = &s[2..]; } 126 | Some("0b" | "0B") => { base = 2; s = &s[2..]; } 127 | _ => {} 128 | } 129 | } 130 | } 131 | 132 | // Parse the integer 133 | <$ty>::from_str_radix(s, base).map_err(crate::Error::ParseSigned) 134 | .map(|x| if inv { -x } else { x }) 135 | } 136 | 137 | impl_expr!($name, $name_int, $ty); 138 | } 139 | } 140 | 141 | macro_rules! uint_parse { 142 | ($name:ident, $name_int:ident, $ty:ty) => { 143 | /// Parse an integer with optional base override prefix 144 | pub fn $name_int(mut s: &str) -> crate::Result<$ty> { 145 | // Default base 146 | let mut base = 16; 147 | 148 | // Check for a prefix 149 | match s.get(0..2) { 150 | Some("0x" | "0X") => { base = 16; s = &s[2..]; } 151 | Some("0d" | "0D") => { base = 10; s = &s[2..]; } 152 | Some("0o" | "0O") => { base = 8; s = &s[2..]; } 153 | Some("0b" | "0B") => { base = 2; s = &s[2..]; } 154 | _ => {} 155 | } 156 | 157 | // Parse the integer 158 | <$ty>::from_str_radix(s, base).map_err(crate::Error::ParseUnsigned) 159 | } 160 | 161 | impl_expr!($name, $name_int, $ty); 162 | } 163 | } 164 | 165 | uint_parse!(parse_u8, parse_u8_int, u8); 166 | uint_parse!(parse_u16, parse_u16_int, u16); 167 | uint_parse!(parse_u32, parse_u32_int, u32); 168 | uint_parse!(parse_u64, parse_u64_int, u64); 169 | uint_parse!(parse_usize, parse_usize_int, usize); 170 | int_parse!(parse_i8, parse_i8_int, i8); 171 | int_parse!(parse_i16, parse_i16_int, i16); 172 | int_parse!(parse_i32, parse_i32_int, i32); 173 | int_parse!(parse_i64, parse_i64_int, i64); 174 | int_parse!(parse_isize, parse_isize_int, isize); 175 | 176 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(array_chunks)] 2 | 3 | pub mod int; 4 | pub mod constraint; 5 | 6 | use std::str::FromStr; 7 | use crate::int::*; 8 | use crate::constraint::*; 9 | use libprocmem::Memory; 10 | use rustyline::Editor; 11 | use rustyline::error::ReadlineError; 12 | use quoted_strings::QuotedParts; 13 | 14 | /// Wrapper for `Result` 15 | type Result = std::result::Result; 16 | 17 | /// Error type 18 | #[derive(Debug)] 19 | pub enum Error { 20 | /// Error interacting with libprocmem 21 | Memory(libprocmem::Error), 22 | 23 | /// Failed to parse a signed value 24 | ParseSigned(std::num::ParseIntError), 25 | 26 | /// Failed to parse an unsigned value 27 | ParseUnsigned(std::num::ParseIntError), 28 | 29 | /// Failed to read command 30 | Readline(ReadlineError), 31 | 32 | /// Integer truncation happened when converting a `u64` to a `usize` 33 | TooBig, 34 | 35 | /// Failed to parse a floating point value 36 | ParseFloat(std::num::ParseFloatError), 37 | 38 | /// Invalid constraint 39 | InvalidConstraint, 40 | 41 | /// Got an invalid PID on the command line 42 | InvalidPid(std::num::ParseIntError), 43 | 44 | /// An invalid expression was used 45 | /// 46 | /// Currently we just support add, sub, mul, and div. No spaces. Numbers 47 | /// can be any base (with the correct override) 48 | InvalidExpression, 49 | } 50 | 51 | /// Different values 52 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 53 | pub enum Value { 54 | F32(f32), 55 | F64(f64), 56 | U8(u8), 57 | U16(u16), 58 | U32(u32), 59 | U64(u64), 60 | I8(i8), 61 | I16(i16), 62 | I32(i32), 63 | I64(i64), 64 | } 65 | 66 | impl Value { 67 | /// Create a new (zero) value with the format specified by `chr` 68 | pub fn default_from_letter(chr: char) -> Value { 69 | // Get the value 70 | match chr { 71 | 'f' => Value::F32(0.), 72 | 'F' => Value::F64(0.), 73 | 'b' => Value::U8(0), 74 | 'w' => Value::U16(0), 75 | 'd' => Value::U32(0), 76 | 'q' => Value::U64(0), 77 | 'B' => Value::I8(0), 78 | 'W' => Value::I16(0), 79 | 'D' => Value::I32(0), 80 | 'Q' => Value::I64(0), 81 | _ => unreachable!(), 82 | } 83 | } 84 | 85 | /// Interpret the value as a `u64` through casting 86 | pub fn as_u64(&self) -> u64 { 87 | match self { 88 | Self::F32(x) => x.to_bits() as u64, 89 | Self::F64(x) => x.to_bits(), 90 | Self::U8 (x) => *x as u64, 91 | Self::U16(x) => *x as u64, 92 | Self::U32(x) => *x as u64, 93 | Self::U64(x) => *x, 94 | Self::I8 (x) => *x as u64, 95 | Self::I16(x) => *x as u64, 96 | Self::I32(x) => *x as u64, 97 | Self::I64(x) => *x as u64, 98 | } 99 | } 100 | 101 | /// Get number of bytes per `self` 102 | pub fn bytes(&self) -> usize { 103 | match self { 104 | Self::F32(_) => 4, 105 | Self::F64(_) => 8, 106 | Self::U8 (_) => 1, 107 | Self::U16(_) => 2, 108 | Self::U32(_) => 4, 109 | Self::U64(_) => 8, 110 | Self::I8 (_) => 1, 111 | Self::I16(_) => 2, 112 | Self::I32(_) => 4, 113 | Self::I64(_) => 8, 114 | } 115 | } 116 | 117 | /// Get number of bytes per `self` when `Display`ed 118 | pub fn display(&self) -> usize { 119 | match self { 120 | Self::F32(_) => 25, 121 | Self::F64(_) => 25, 122 | Self::U8 (_) => 2, 123 | Self::U16(_) => 4, 124 | Self::U32(_) => 8, 125 | Self::U64(_) => 16, 126 | Self::I8 (_) => 4, 127 | Self::I16(_) => 6, 128 | Self::I32(_) => 11, 129 | Self::I64(_) => 21, 130 | } 131 | } 132 | 133 | /// Update value from little-endian bytes 134 | pub fn from_le_bytes(&mut self, bytes: &[u8]) { 135 | match self { 136 | Self::F32(ref mut val) => 137 | *val = f32::from_le_bytes(bytes.try_into().unwrap()), 138 | Self::F64(ref mut val) => 139 | *val = f64::from_le_bytes(bytes.try_into().unwrap()), 140 | Self::U8 (ref mut val) => 141 | *val = u8::from_le_bytes(bytes.try_into().unwrap()), 142 | Self::U16(ref mut val) => 143 | *val = u16::from_le_bytes(bytes.try_into().unwrap()), 144 | Self::U32(ref mut val) => 145 | *val = u32::from_le_bytes(bytes.try_into().unwrap()), 146 | Self::U64(ref mut val) => 147 | *val = u64::from_le_bytes(bytes.try_into().unwrap()), 148 | Self::I8 (ref mut val) => 149 | *val = i8::from_le_bytes(bytes.try_into().unwrap()), 150 | Self::I16(ref mut val) => 151 | *val = i16::from_le_bytes(bytes.try_into().unwrap()), 152 | Self::I32(ref mut val) => 153 | *val = i32::from_le_bytes(bytes.try_into().unwrap()), 154 | Self::I64(ref mut val) => 155 | *val = i64::from_le_bytes(bytes.try_into().unwrap()), 156 | } 157 | } 158 | 159 | /// Update `self` to a new value of the same type from `s` 160 | pub fn update_str(&mut self, s: &str) -> Result<()> { 161 | match self { 162 | Self::F32(ref mut val) => { 163 | *val = f32::from_str(s).map_err(Error::ParseFloat)?; 164 | } 165 | Self::F64(ref mut val) => { 166 | *val = f64::from_str(s).map_err(Error::ParseFloat)?; 167 | } 168 | Self::U8 (ref mut val) => *val = parse_u8 (s)?, 169 | Self::U16(ref mut val) => *val = parse_u16(s)?, 170 | Self::U32(ref mut val) => *val = parse_u32(s)?, 171 | Self::U64(ref mut val) => *val = parse_u64(s)?, 172 | Self::I8 (ref mut val) => *val = parse_i8 (s)?, 173 | Self::I16(ref mut val) => *val = parse_i16(s)?, 174 | Self::I32(ref mut val) => *val = parse_i32(s)?, 175 | Self::I64(ref mut val) => *val = parse_i64(s)?, 176 | } 177 | 178 | Ok(()) 179 | } 180 | } 181 | 182 | impl std::fmt::Display for Value { 183 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 184 | match self { 185 | Self::F32(val) => 186 | f.write_fmt(format_args!("{:25.6}", val)), 187 | Self::F64(val) => 188 | f.write_fmt(format_args!("{:25.6}", val)), 189 | Self::U8 (val) => f.write_fmt(format_args!("{:02x}", val)), 190 | Self::U16(val) => f.write_fmt(format_args!("{:04x}", val)), 191 | Self::U32(val) => f.write_fmt(format_args!("{:08x}", val)), 192 | Self::U64(val) => f.write_fmt(format_args!("{:016x}", val)), 193 | Self::I8 (val) => f.write_fmt(format_args!("{:4}", val)), 194 | Self::I16(val) => f.write_fmt(format_args!("{:6}", val)), 195 | Self::I32(val) => f.write_fmt(format_args!("{:11}", val)), 196 | Self::I64(val) => f.write_fmt(format_args!("{:21}", val)), 197 | } 198 | } 199 | } 200 | 201 | impl std::fmt::LowerHex for Value { 202 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 203 | match self { 204 | Self::F32(val) => 205 | f.write_fmt(format_args!("{:08x}", val.to_bits())), 206 | Self::F64(val) => 207 | f.write_fmt(format_args!("{:016x}", val.to_bits())), 208 | Self::U8 (val) => f.write_fmt(format_args!("{:02x}", val)), 209 | Self::U16(val) => f.write_fmt(format_args!("{:04x}", val)), 210 | Self::U32(val) => f.write_fmt(format_args!("{:08x}", val)), 211 | Self::U64(val) => f.write_fmt(format_args!("{:016x}", val)), 212 | Self::I8 (val) => f.write_fmt(format_args!("{:02x}", val)), 213 | Self::I16(val) => f.write_fmt(format_args!("{:04x}", val)), 214 | Self::I32(val) => f.write_fmt(format_args!("{:08x}", val)), 215 | Self::I64(val) => f.write_fmt(format_args!("{:016x}", val)), 216 | } 217 | } 218 | } 219 | 220 | /// A memory scanner! 221 | struct Scan { 222 | /// Memory 223 | memory: Memory, 224 | 225 | /// Searches which yielded results 226 | matches: Vec<(Vec, Vec<(u64, Value)>)>, 227 | } 228 | 229 | impl Scan { 230 | /// Handle a command to the scanner 231 | fn handle_command(&mut self, command: &[String]) -> Result<()> { 232 | // Nothing to do 233 | if command.len() == 0 { return Ok(()); } 234 | 235 | match command[0].as_str() { 236 | "exit" | "q" | "quit" => { 237 | // Exit the program 238 | std::process::exit(0); 239 | } 240 | "h" => { 241 | // History, displays old query results 242 | if command.len() != 2 { 243 | println!("h "); 244 | return Ok(()); 245 | } 246 | 247 | // Get the query number 248 | let query = if command[1] == "l" { 249 | // 'l' is a shortcut for "last" command 250 | self.matches.len() - 1 251 | } else { 252 | parse_usize(&command[1])? 253 | }; 254 | if let Some((constraints, matches)) = self.matches.get(query) { 255 | for &(addr, old) in matches.iter() { 256 | // Read the new value 257 | let tmp = 258 | self.memory.read_slice::(addr, old.bytes()) 259 | .map_err(Error::Memory)?; 260 | 261 | // Get the new value with the same typing as the old 262 | // value 263 | let mut new = old; 264 | new.from_le_bytes(&tmp); 265 | 266 | if old != new { 267 | println!("{:016x} query: {} -> {}", 268 | addr, old, new); 269 | } else { 270 | println!("{:016x} query: {}", addr, old); 271 | } 272 | } 273 | 274 | println!( 275 | "Query #{} had {} matches with constraints:\n{:#?}", 276 | query, matches.len(), constraints); 277 | } else { 278 | println!("No matching query"); 279 | } 280 | } 281 | "m" => { 282 | // Print address maps 283 | if let Ok(maps) = self.memory.query_address_space() { 284 | for region in maps { 285 | println!("{:016x}-{:016x} {}{}{}", 286 | region.base, region.end, 287 | if region.r { "r" } else { " " }, 288 | if region.w { "w" } else { " " }, 289 | if region.x { "x" } else { " " }); 290 | } 291 | } else { 292 | println!("Failed to query address map."); 293 | } 294 | } 295 | "uo" | 296 | "ub" | "uw" | "ud" | "uq" | 297 | "uB" | "uW" | "uD" | "uQ" | "uf" | "uF" => { 298 | // Re-query only the addresses of a previous query with new 299 | // constraints 300 | if command.len() < 3 { 301 | println!("u[bwdqBWDQfF] [constraints]"); 302 | return Ok(()); 303 | } 304 | 305 | // Create value associated with type 306 | let value = if command[0] == "uo" { 307 | None 308 | } else { 309 | Some(Value::default_from_letter( 310 | command[0].as_bytes()[1] as char)) 311 | }; 312 | 313 | // Create list of constraints 314 | let mut constraints = Vec::new(); 315 | for constraint in &command[2..] { 316 | constraints.push( 317 | Constraint::from_str_value(constraint, value)?); 318 | } 319 | 320 | // Get query ID 321 | let query = if command[1] == "l" { 322 | self.matches.len() - 1 323 | } else { 324 | parse_usize(&command[1])? 325 | }; 326 | if let Some((_, old_matches)) = self.matches.get(query) { 327 | // Search through old query matches 328 | let mut matches = Vec::new(); 329 | for &(addr, old_value) in old_matches { 330 | // Check if we have a concrete value, if not, compare 331 | // against the last observed value 332 | let mut value = if let Some(value) = value { 333 | value 334 | } else { 335 | for constraint in constraints.iter_mut() { 336 | constraint.update_val(old_value); 337 | } 338 | 339 | old_value 340 | }; 341 | 342 | // Read the new value 343 | let tmp = self.memory.read_slice::( 344 | addr, value.bytes()) 345 | .map_err(Error::Memory)?; 346 | value.from_le_bytes(&tmp); 347 | 348 | // Check constraints 349 | if constraints.iter().all(|x| x.check(value)) { 350 | matches.push((addr, value)); 351 | } 352 | } 353 | 354 | if !matches.is_empty() { 355 | println!("Got {} matches, saving as query #{:x}", 356 | matches.len(), self.matches.len()); 357 | if matches.len() < 100 { 358 | for (addr, value) in &matches { 359 | println!("{:016x} {}", addr, value); 360 | } 361 | } 362 | self.matches.push((constraints, matches)); 363 | } else { 364 | println!("No matches."); 365 | } 366 | } else { 367 | println!("No matching query"); 368 | } 369 | } 370 | "ss" => { 371 | // Search for a string 372 | if command.len() != 4 { 373 | println!("ss "); 374 | return Ok(()); 375 | } 376 | 377 | // Get the address and size 378 | let addr = parse_u64(&command[1])?; 379 | let size = parse_usize(&command[2])?; 380 | 381 | // Read the memory 382 | let tmp = self.memory.read_slice::(addr, size) 383 | .map_err(Error::Memory)?; 384 | 385 | // String search 386 | tmp.windows(command[3].len()).enumerate() 387 | .for_each(|(ii, window)| { 388 | if window == command[3].as_bytes() { 389 | println!("{:016x} {}", addr + ii as u64, command[3]); 390 | } 391 | }); 392 | } 393 | "sb" | "sw" | "sd" | "sq" | 394 | "sB" | "sW" | "sD" | "sQ" | "sf" | "sF" => { 395 | // Search for a value 396 | if command.len() <= 2 { 397 | println!("s[bwdqBWDQfF] [constraints]"); 398 | return Ok(()); 399 | } 400 | 401 | // Create value associated with type 402 | let value = 403 | Value::default_from_letter( 404 | command[0].as_bytes()[1] as char); 405 | 406 | // Create list of constraints 407 | let mut constraints = Vec::new(); 408 | for constraint in &command[3..] { 409 | constraints.push( 410 | Constraint::from_str_value(constraint, Some(value))?); 411 | } 412 | 413 | // Get the address and size 414 | let addr = parse_u64(&command[1])?; 415 | let size = parse_usize(&command[2])?; 416 | let end = addr + size as u64; 417 | 418 | let mut matches = Vec::new(); 419 | for mapping in self.memory.query_address_space() 420 | .map_err(Error::Memory)? { 421 | // Skip ranges not in bounds 422 | if mapping.end <= addr || mapping.base >= end { 423 | continue; 424 | } 425 | 426 | let start = addr.max(mapping.base); 427 | let end = end.min(mapping.end); 428 | self.search_memory(value, start, (end - start) as usize, 429 | &constraints, &mut matches)?; 430 | } 431 | 432 | // If we got matches, save them off 433 | if !matches.is_empty() { 434 | println!("Got {} matches, saving as query #{:x}", 435 | matches.len(), self.matches.len()); 436 | if matches.len() < 100 { 437 | for (addr, value) in &matches { 438 | println!("{:016x} {}", addr, value); 439 | } 440 | } 441 | self.matches.push((constraints, matches)); 442 | } else { 443 | println!("No matches."); 444 | } 445 | } 446 | "db" | "dw" | "dd" | "dq" | 447 | "dB" | "dW" | "dD" | "dQ" | "df" | "dF" =>{ 448 | // Display memory 449 | if !matches!(command.len(), 2 | 3) { 450 | println!("d[bwdqBWDQfF] []"); 451 | return Ok(()); 452 | } 453 | 454 | // Get the letter used with this command and use it to create a 455 | // dummy expected value 456 | let mut value = 457 | Value::default_from_letter( 458 | command[0].as_bytes()[1] as char); 459 | 460 | // Parse integer address 461 | let addr = parse_u64(&command[1])?; 462 | 463 | // Determine number of bytes to read 464 | let bytes = if let Some(x) = command.get(2) { 465 | parse_usize(x)? 466 | } else { 467 | 64 468 | }; 469 | 470 | // Read memory 471 | let tmp = self.memory.read_slice::(addr, bytes) 472 | .map_err(Error::Memory)?; 473 | 474 | // Print the new line header 475 | print!("\x1b[0;34m{:016x}\x1b[0m: ", addr); 476 | 477 | // Display all the values 478 | let mut output_used = 0; 479 | let mut iter = tmp.chunks_exact( 480 | value.bytes()).map(|x| Some(x)).chain( 481 | std::iter::repeat(None)).enumerate().peekable(); 482 | while let Some((ii, val)) = iter.next() { 483 | if let Some(val) = val { 484 | // Print the value 485 | value.from_le_bytes(val); 486 | 487 | // Convert the value to a `u64` and try to use it as 488 | // an address to see if it is a pointer 489 | let maybe_addr = value.as_u64(); 490 | let valid_ptr = 491 | self.memory.read::(maybe_addr).is_ok(); 492 | 493 | if valid_ptr { 494 | print!("\x1b[0;32m{}\x1b[0m ", value); 495 | } else { 496 | print!("{} ", value); 497 | } 498 | } else { 499 | // Clean 500 | for _ in 0..value.display() { 501 | print!("?"); 502 | } 503 | print!(" "); 504 | } 505 | 506 | // Update output used for this line 507 | output_used += 1; 508 | let vals_per_line = 16 / value.bytes(); 509 | if output_used == vals_per_line { 510 | // Before we make the newline, print the ASCII 511 | let ascii = ii / vals_per_line * vals_per_line * 512 | value.bytes(); 513 | for byte in tmp[ascii..].iter().take(16) { 514 | if byte.is_ascii_graphic() { 515 | print!("{}", *byte as char); 516 | } else { 517 | print!("."); 518 | } 519 | } 520 | println!(); 521 | 522 | // If we have nothing more to print after this, we're 523 | // done 524 | if matches!(iter.peek(), Some((_, None))) { 525 | return Ok(()); 526 | } 527 | 528 | // Print the new line header 529 | print!("\x1b[34m{:016x}\x1b[0m: ", 530 | addr + (ii as u64 + 1) * value.bytes() as u64); 531 | 532 | // Update state 533 | output_used = 0; 534 | } 535 | } 536 | } 537 | _ => { 538 | println!("Unknown command: {:?}", command); 539 | } 540 | } 541 | 542 | Ok(()) 543 | } 544 | 545 | fn search_memory(&mut self, mut value: Value, addr: u64, size: usize, 546 | constraints: &[Constraint], 547 | matches: &mut Vec<(u64, Value)>) -> Result<()> { 548 | // Read the memory 549 | let tmp = self.memory.read_slice::(addr, size) 550 | .map_err(Error::Memory)?; 551 | // Go through memory 552 | for (ii, chunk) in tmp.chunks_exact(value.bytes()).enumerate(){ 553 | // Update value 554 | value.from_le_bytes(chunk); 555 | 556 | // Check constraints 557 | if constraints.iter().all(|x| x.check(value)) { 558 | let addr = addr + ii as u64 * value.bytes() as u64; 559 | matches.push((addr, value)); 560 | } 561 | } 562 | 563 | Ok(()) 564 | } 565 | } 566 | 567 | fn main() -> Result<()> { 568 | // Get the arguments 569 | let args = std::env::args().collect::>(); 570 | if args.len() != 2 { 571 | println!("Usage: peek "); 572 | return Ok(()); 573 | } 574 | 575 | // Get the PID 576 | let pid = usize::from_str_radix(&args[1], 10) 577 | .map_err(Error::InvalidPid)?; 578 | 579 | // Create a readline handler 580 | let mut rl = Editor::<()>::new(); 581 | let _ = rl.load_history(".peekieboi"); 582 | 583 | // Create a memory scanner 584 | let mut scan = Scan { 585 | memory: Memory::pid(pid).map_err(Error::Memory)?, 586 | matches: Vec::new(), 587 | }; 588 | 589 | // Wait for commands 590 | loop { 591 | // Get command 592 | let command = match rl.readline(">> ") { 593 | Ok(x) => x, 594 | Err(ReadlineError::Interrupted) => { 595 | // Ctrl+c 596 | break; 597 | } 598 | Err(x) => return Err(Error::Readline(x)), 599 | }; 600 | rl.add_history_entry(command.as_str()); 601 | let _ = rl.save_history(".peekieboi"); 602 | 603 | // Split command 604 | let command = QuotedParts::from(command.trim()).collect::>(); 605 | if let Err(err) = scan.handle_command(&command) { 606 | println!("Failed to execute command: {:?}", err); 607 | } 608 | } 609 | 610 | Ok(()) 611 | } 612 | 613 | --------------------------------------------------------------------------------