├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Dockerfile.build ├── Makefile ├── README.md ├── demo ├── .gitignore ├── Makefile ├── demo.c ├── demo2.c └── demo3.c ├── harness ├── Makefile ├── harness └── harness.c └── src ├── analyzed_value.rs ├── lib.rs ├── main.rs ├── mandrake.rs ├── mandrake_output.rs ├── syscalls.csv ├── syscalls.rs └── visibility_configuration.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | testing 3 | /build 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 (Jan 28, 2022) 2 | * Initial release 3 | 4 | # 0.1.1 (Feb 1, 2022) 5 | * Added a Plaintext output format 6 | * Don't trace into the new process created by `sys_execve` by default 7 | 8 | # 0.1.2 (Mar 9, 2022) 9 | * Handle `SIGCHLD` cleanly 10 | * Parse socket structures in syscalls 11 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.0.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 30 | 31 | [[package]] 32 | name = "base64" 33 | version = "0.12.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "1.2.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 42 | 43 | [[package]] 44 | name = "bstr" 45 | version = "0.2.17" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 48 | dependencies = [ 49 | "lazy_static", 50 | "memchr", 51 | "regex-automata", 52 | "serde", 53 | ] 54 | 55 | [[package]] 56 | name = "byteorder" 57 | version = "1.4.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 60 | 61 | [[package]] 62 | name = "cc" 63 | version = "1.0.72" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 66 | 67 | [[package]] 68 | name = "cfg-if" 69 | version = "0.1.10" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 78 | 79 | [[package]] 80 | name = "clap" 81 | version = "3.0.6" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "1957aa4a5fb388f0a0a73ce7556c5b42025b874e5cdc2c670775e346e97adec0" 84 | dependencies = [ 85 | "atty", 86 | "bitflags", 87 | "clap_derive", 88 | "indexmap", 89 | "lazy_static", 90 | "os_str_bytes", 91 | "strsim", 92 | "termcolor", 93 | "textwrap", 94 | ] 95 | 96 | [[package]] 97 | name = "clap-num" 98 | version = "1.0.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "ea589d838669bcc85ffc6126784601e61064951d206dde84895ff82a2ef8f056" 101 | dependencies = [ 102 | "num-traits", 103 | ] 104 | 105 | [[package]] 106 | name = "clap_derive" 107 | version = "3.0.6" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" 110 | dependencies = [ 111 | "heck", 112 | "proc-macro-error", 113 | "proc-macro2", 114 | "quote", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "csv" 120 | version = "1.1.6" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 123 | dependencies = [ 124 | "bstr", 125 | "csv-core", 126 | "itoa 0.4.8", 127 | "ryu", 128 | "serde", 129 | ] 130 | 131 | [[package]] 132 | name = "csv-core" 133 | version = "0.1.10" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 136 | dependencies = [ 137 | "memchr", 138 | ] 139 | 140 | [[package]] 141 | name = "getrandom" 142 | version = "0.2.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 145 | dependencies = [ 146 | "cfg-if 1.0.0", 147 | "libc", 148 | "wasi", 149 | ] 150 | 151 | [[package]] 152 | name = "hashbrown" 153 | version = "0.11.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 156 | 157 | [[package]] 158 | name = "heck" 159 | version = "0.4.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 162 | 163 | [[package]] 164 | name = "hermit-abi" 165 | version = "0.1.19" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 168 | dependencies = [ 169 | "libc", 170 | ] 171 | 172 | [[package]] 173 | name = "hex" 174 | version = "0.4.3" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 177 | 178 | [[package]] 179 | name = "iced-x86" 180 | version = "1.11.3" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "6d907dfc273d9ed0da940e8145ceb77759056a1fb2110b44b83d1b203cddb906" 183 | dependencies = [ 184 | "lazy_static", 185 | "static_assertions", 186 | ] 187 | 188 | [[package]] 189 | name = "indexmap" 190 | version = "1.8.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 193 | dependencies = [ 194 | "autocfg", 195 | "hashbrown", 196 | ] 197 | 198 | [[package]] 199 | name = "iter-read" 200 | version = "0.3.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "4ea34c173b290f2449bb0489ae1024c2cf8b1c1dd1cc58a2e105df7f5d759f5d" 203 | 204 | [[package]] 205 | name = "itoa" 206 | version = "0.4.8" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 209 | 210 | [[package]] 211 | name = "itoa" 212 | version = "1.0.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 215 | 216 | [[package]] 217 | name = "lazy_static" 218 | version = "1.4.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 221 | 222 | [[package]] 223 | name = "libc" 224 | version = "0.2.112" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 227 | 228 | [[package]] 229 | name = "linked-hash-map" 230 | version = "0.5.4" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 233 | 234 | [[package]] 235 | name = "mandrake" 236 | version = "0.1.1" 237 | dependencies = [ 238 | "base64", 239 | "byteorder", 240 | "clap", 241 | "clap-num", 242 | "csv", 243 | "hex", 244 | "iced-x86", 245 | "lazy_static", 246 | "nix 0.20.2", 247 | "regex", 248 | "serde", 249 | "serde-pickle", 250 | "serde_json", 251 | "serde_yaml", 252 | "simple-error", 253 | "spawn-ptrace", 254 | "tempfile", 255 | ] 256 | 257 | [[package]] 258 | name = "memchr" 259 | version = "2.4.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 262 | 263 | [[package]] 264 | name = "memoffset" 265 | version = "0.6.5" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 268 | dependencies = [ 269 | "autocfg", 270 | ] 271 | 272 | [[package]] 273 | name = "nix" 274 | version = "0.18.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" 277 | dependencies = [ 278 | "bitflags", 279 | "cc", 280 | "cfg-if 0.1.10", 281 | "libc", 282 | ] 283 | 284 | [[package]] 285 | name = "nix" 286 | version = "0.20.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" 289 | dependencies = [ 290 | "bitflags", 291 | "cc", 292 | "cfg-if 1.0.0", 293 | "libc", 294 | "memoffset", 295 | ] 296 | 297 | [[package]] 298 | name = "num-bigint" 299 | version = "0.4.3" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 302 | dependencies = [ 303 | "autocfg", 304 | "num-integer", 305 | "num-traits", 306 | ] 307 | 308 | [[package]] 309 | name = "num-integer" 310 | version = "0.1.44" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 313 | dependencies = [ 314 | "autocfg", 315 | "num-traits", 316 | ] 317 | 318 | [[package]] 319 | name = "num-traits" 320 | version = "0.2.14" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 323 | dependencies = [ 324 | "autocfg", 325 | ] 326 | 327 | [[package]] 328 | name = "os_str_bytes" 329 | version = "6.0.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 332 | dependencies = [ 333 | "memchr", 334 | ] 335 | 336 | [[package]] 337 | name = "ppv-lite86" 338 | version = "0.2.16" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 341 | 342 | [[package]] 343 | name = "proc-macro-error" 344 | version = "1.0.4" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 347 | dependencies = [ 348 | "proc-macro-error-attr", 349 | "proc-macro2", 350 | "quote", 351 | "syn", 352 | "version_check", 353 | ] 354 | 355 | [[package]] 356 | name = "proc-macro-error-attr" 357 | version = "1.0.4" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 360 | dependencies = [ 361 | "proc-macro2", 362 | "quote", 363 | "version_check", 364 | ] 365 | 366 | [[package]] 367 | name = "proc-macro2" 368 | version = "1.0.36" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 371 | dependencies = [ 372 | "unicode-xid", 373 | ] 374 | 375 | [[package]] 376 | name = "quote" 377 | version = "1.0.14" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" 380 | dependencies = [ 381 | "proc-macro2", 382 | ] 383 | 384 | [[package]] 385 | name = "rand" 386 | version = "0.8.4" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 389 | dependencies = [ 390 | "libc", 391 | "rand_chacha", 392 | "rand_core", 393 | "rand_hc", 394 | ] 395 | 396 | [[package]] 397 | name = "rand_chacha" 398 | version = "0.3.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 401 | dependencies = [ 402 | "ppv-lite86", 403 | "rand_core", 404 | ] 405 | 406 | [[package]] 407 | name = "rand_core" 408 | version = "0.6.3" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 411 | dependencies = [ 412 | "getrandom", 413 | ] 414 | 415 | [[package]] 416 | name = "rand_hc" 417 | version = "0.3.1" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 420 | dependencies = [ 421 | "rand_core", 422 | ] 423 | 424 | [[package]] 425 | name = "redox_syscall" 426 | version = "0.2.10" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 429 | dependencies = [ 430 | "bitflags", 431 | ] 432 | 433 | [[package]] 434 | name = "regex" 435 | version = "1.5.4" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 438 | dependencies = [ 439 | "aho-corasick", 440 | "memchr", 441 | "regex-syntax", 442 | ] 443 | 444 | [[package]] 445 | name = "regex-automata" 446 | version = "0.1.10" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 449 | 450 | [[package]] 451 | name = "regex-syntax" 452 | version = "0.6.25" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 455 | 456 | [[package]] 457 | name = "remove_dir_all" 458 | version = "0.5.3" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 461 | dependencies = [ 462 | "winapi", 463 | ] 464 | 465 | [[package]] 466 | name = "ryu" 467 | version = "1.0.9" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 470 | 471 | [[package]] 472 | name = "serde" 473 | version = "1.0.133" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" 476 | dependencies = [ 477 | "serde_derive", 478 | ] 479 | 480 | [[package]] 481 | name = "serde-pickle" 482 | version = "1.1.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "47b0d075918cbf8475cedcae8e85844f437e2a7c70d54a2f405fab29bb6b2536" 485 | dependencies = [ 486 | "byteorder", 487 | "iter-read", 488 | "num-bigint", 489 | "num-traits", 490 | "serde", 491 | ] 492 | 493 | [[package]] 494 | name = "serde_derive" 495 | version = "1.0.133" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" 498 | dependencies = [ 499 | "proc-macro2", 500 | "quote", 501 | "syn", 502 | ] 503 | 504 | [[package]] 505 | name = "serde_json" 506 | version = "1.0.74" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" 509 | dependencies = [ 510 | "itoa 1.0.1", 511 | "ryu", 512 | "serde", 513 | ] 514 | 515 | [[package]] 516 | name = "serde_yaml" 517 | version = "0.8.23" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" 520 | dependencies = [ 521 | "indexmap", 522 | "ryu", 523 | "serde", 524 | "yaml-rust", 525 | ] 526 | 527 | [[package]] 528 | name = "simple-error" 529 | version = "0.2.3" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" 532 | 533 | [[package]] 534 | name = "spawn-ptrace" 535 | version = "0.1.2" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "2b572503c81aa40e1e61954bca89b863c344e0f310fd51f308d933255bf4f756" 538 | dependencies = [ 539 | "nix 0.18.0", 540 | ] 541 | 542 | [[package]] 543 | name = "static_assertions" 544 | version = "1.1.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 547 | 548 | [[package]] 549 | name = "strsim" 550 | version = "0.10.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 553 | 554 | [[package]] 555 | name = "syn" 556 | version = "1.0.85" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" 559 | dependencies = [ 560 | "proc-macro2", 561 | "quote", 562 | "unicode-xid", 563 | ] 564 | 565 | [[package]] 566 | name = "tempfile" 567 | version = "3.2.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 570 | dependencies = [ 571 | "cfg-if 1.0.0", 572 | "libc", 573 | "rand", 574 | "redox_syscall", 575 | "remove_dir_all", 576 | "winapi", 577 | ] 578 | 579 | [[package]] 580 | name = "termcolor" 581 | version = "1.1.2" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 584 | dependencies = [ 585 | "winapi-util", 586 | ] 587 | 588 | [[package]] 589 | name = "textwrap" 590 | version = "0.14.2" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 593 | 594 | [[package]] 595 | name = "unicode-xid" 596 | version = "0.2.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 599 | 600 | [[package]] 601 | name = "version_check" 602 | version = "0.9.4" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 605 | 606 | [[package]] 607 | name = "wasi" 608 | version = "0.10.2+wasi-snapshot-preview1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 611 | 612 | [[package]] 613 | name = "winapi" 614 | version = "0.3.9" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 617 | dependencies = [ 618 | "winapi-i686-pc-windows-gnu", 619 | "winapi-x86_64-pc-windows-gnu", 620 | ] 621 | 622 | [[package]] 623 | name = "winapi-i686-pc-windows-gnu" 624 | version = "0.4.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 627 | 628 | [[package]] 629 | name = "winapi-util" 630 | version = "0.1.5" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 633 | dependencies = [ 634 | "winapi", 635 | ] 636 | 637 | [[package]] 638 | name = "winapi-x86_64-pc-windows-gnu" 639 | version = "0.4.0" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 642 | 643 | [[package]] 644 | name = "yaml-rust" 645 | version = "0.4.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 648 | dependencies = [ 649 | "linked-hash-map", 650 | ] 651 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mandrake" 3 | authors = ["Ron Bowes "] 4 | version = "0.1.2" 5 | edition = "2021" 6 | description = "Mandrake is an open-source machine code analyzer / instrumenter" 7 | homepage = "https://github.com/counterhack/mandrake" 8 | repository = "https://github.com/counterhack/mandrake" 9 | license = "mit" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | nix = "~0.20.0" 15 | spawn-ptrace = "~0.1.2" 16 | tempfile = "~3.2.0" 17 | byteorder = "~1.4.3" 18 | iced-x86 = "~1.11.3" 19 | hex = "~0.4.2" 20 | simple-error = "~0.2.1" 21 | clap = { version = "~3.0.6", features = ["derive"] } 22 | clap-num = "~1.0.0" 23 | 24 | # Serialize formats 25 | serde = { version = "~1.0.110", features = ["derive"] } 26 | serde_json = "~1.0.53" 27 | serde_yaml = "~0.8.23" 28 | serde-pickle = "~1.1.0" 29 | base64 = "~0.12.3" 30 | 31 | # Used to load syscall data 32 | lazy_static = "~1.4.0" 33 | csv = "~1.1.6" 34 | 35 | # Used to read syscall file 36 | regex = "~1.5.4" 37 | 38 | [profile.release] 39 | # strip = "debuginfo" 40 | panic = 'abort' 41 | lto = true 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | MAINTAINER "Ron Bowes" 3 | 4 | # Copy binaries 5 | RUN mkdir /app 6 | COPY ./build/mandrake /app/mandrake 7 | RUN chmod +x /app/mandrake 8 | 9 | RUN mkdir -p /app/harness 10 | COPY ./build/harness /app/harness/harness 11 | RUN chmod +x /app/harness/harness 12 | 13 | # Set up user 14 | RUN useradd -m mandrake 15 | USER mandrake 16 | WORKDIR /app 17 | 18 | # Environment 19 | ENTRYPOINT ["/app/mandrake"] 20 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM rust:latest 2 | MAINTAINER "Ron Bowes" 3 | 4 | # Install all the tools 5 | #RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install build-essential cargo -y 6 | 7 | # We're going to mount the source here 8 | RUN mkdir /src 9 | WORKDIR /src 10 | 11 | CMD ["make", "indocker"] 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Use a container name based on the folder name (probably "build-mandrake") 2 | BUILD?=$(shell basename `pwd`)-build 3 | EXECUTE?=$(shell basename `pwd`)-execute 4 | 5 | # This kicks up docker to build the rest 6 | # (Probably shouldn't be customized) 7 | all: src/*.rs 8 | docker build . -t ${BUILD} -f Dockerfile.build 9 | docker run --rm -v ${PWD}:/src --env UID=$(shell id -u) --env GID=$(shell id -g) -ti ${BUILD} 10 | 11 | run: all 12 | docker build . -t ${EXECUTE} 13 | 14 | @echo "" 15 | @echo "To execute, run:" 16 | @echo "" 17 | @echo "docker run --rm -ti ${EXECUTE} --help" 18 | 19 | # This runs inside Docker, customize this part! 20 | indocker: 21 | # Build the binary 22 | cargo build --release 23 | mkdir -p build/ 24 | cp target/release/mandrake build/ 25 | strip build/mandrake 26 | 27 | # Build the harness 28 | cd harness && make 29 | cp harness/harness build/ 30 | strip build/harness 31 | 32 | # Fix ownership, because Docker 33 | chown -R ${UID}:${GID} . 34 | 35 | clean: 36 | rm -rf target build 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A binary analysis / instrumentation library for Rust. 2 | 3 | # Author 4 | 5 | [Ron Bowes](https://www.skullsecurity.org/) from Counter Hack 6 | 7 | License: MIT 8 | 9 | # Acknowledgements 10 | 11 | Thanks to folks: 12 | 13 | * Daniel Pendolino, for helping me with the initial idea 14 | * Josh Wright, for helping with testing and language 15 | * Ed Skoudis, for giving me permission to release this open source 16 | * Counter Hack and SANS in general, for giving me the time I needed to write this, as well as encouragement 17 | * Ryan Chapman, for their amazing [syscall table](https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/) that I parsed into [/src/syscalls.csv](a CSV file) 18 | 19 | # Purpose 20 | 21 | Mandrake is a framework for executing and instrumenting machine code or ELF 22 | binaries. It can execute a full binary or a block of hex-encoded machine code, 23 | saving the output results as JSON or YAML. 24 | 25 | The goal of Mandrake is to help analysts understand or evaluate unknown code. 26 | While most disassembly tools (such as `ndisasm`) will do a great job of showing 27 | what each instruction and opcode do, Mandrake goes a step further and 28 | *executes* the code, showing what actually ran. 29 | 30 | This means that packed, self-modified, and looping code can be analyzed much 31 | more easily, since you'll see very clearly which syscalls are being performed! 32 | 33 | *Warning: This DOES run the code on your machine, using 34 | [ptrace](https://man7.org/linux/man-pages/man2/ptrace.2.html). You probably 35 | don't want to analyze malicious code on a production system!* 36 | 37 | # Installation 38 | 39 | *This must be run on an x64-based Linux system!* 40 | 41 | The best way to execute `mandrake` is to check it out from 42 | [the GitHub repo](https://github.com/counterhack/mandrake), then build + run 43 | with either `cargo` (the Rust toolchain) or `docker`. 44 | 45 | ## Executing with cargo 46 | 47 | To run with `cargo`, you need the `edition2021` edition of Rust (which usually 48 | means installing [rustup](https://rustup.rs/) (or using the `rust:latest` 49 | Docker image). Probably distros will start including that edition in their 50 | repos eventually, and I hate depending on it, but some of the dependencies I 51 | pull in require it. 52 | 53 | Once you have `cargo` and `edition2021`, you can build and run from source: 54 | 55 | ``` 56 | $ git clone https://github.com/CounterHack/mandrake.git 57 | $ cd mandrake 58 | $ mandrake --help 59 | ``` 60 | 61 | ## Installing with docker 62 | 63 | Alternatively, we include a `Makefile` that just uses the `rust:latest` Docker 64 | container. You can run `make` to use that: 65 | 66 | ``` 67 | $ make run 68 | docker build . -t mandrake-build -f Dockerfile.build 69 | 70 | [...] 71 | 72 | Successfully tagged mandrake-execute:latest 73 | 74 | To execute, run: 75 | 76 | docker run --rm -ti mandrake-execute --help 77 | ``` 78 | 79 | Or you can use an interactive Docker environment directly: 80 | 81 | ``` 82 | $ docker run -ti -v $PWD:/src rust:latest /bin/bash 83 | root@6764c399bc84:/# cd src 84 | root@6764c399bc84:/src# mandrake --help 85 | ``` 86 | 87 | ## Building a Binary 88 | 89 | We have included a Dockerfile to build binary releases. To build a release, 90 | execute `make` in the source directory. That will use `docker` to build 91 | releases in the `build/` directory. 92 | 93 | Once those are built, you should be able to execute `build/mandrake` with no 94 | extra dependencies (besides the hardness, which will also be compiled into 95 | `build/`. 96 | 97 | *We plan to do proper binary releases but have not yet. By the time this is 98 | public, we'll have a link here.* 99 | 100 | # Usage 101 | 102 | To use this, the simplest way is to check out the source, install the Rust 103 | build environment (i.e., `cargo`), and just use it right from the source tree. 104 | So far, we haven't really done any fancy releases. 105 | 106 | For the remainder of this README, we will assume you are executing using a 107 | `mandrake` binary. You can just as easily use `cargo run --` anywhere you see 108 | `mandrake`. 109 | 110 | Mandrake has two modes, implemented as subcommands - `code` and `elf`. Run 111 | Mandrake with `--help` to see the full options: 112 | 113 | ``` 114 | $ mandrake --help 115 | $ mandrake code --help 116 | $ mandrake elf --help 117 | ``` 118 | 119 | ## Analyzing Raw Code 120 | 121 | To use Mandrake to analyze raw machine code, you need two things: 122 | 123 | * The `harness` executable - you'll get this when you check out the codebase, but you can also get it [directly from GitHub](https://github.com/CounterHack/mandrake/blob/main/harness/harness) 124 | * The hex-encoded machine code 125 | 126 | How you get hex-encoded machine code is sort of up to you, but if you want 127 | something simple to test, try `c3` (`ret`) or `4831c048ffc0c3` 128 | (`xor rax, rax` / `inc rax` / `ret`) - aka, `return 1`. 129 | 130 | Here is an example: 131 | 132 | ``` 133 | $ mandrake --snippit-length 4 code 'c3' 134 | 135 | { 136 | "success": true, 137 | "pid": 1046429, 138 | "history": [ 139 | { 140 | "rdx": { 141 | "value": 0, 142 | "memory": null, 143 | "as_instruction": null, 144 | "as_string": null 145 | }, 146 | "rip": { 147 | "value": 322371584, 148 | "memory": [ 149 | 195 150 | ], 151 | "as_instruction": "ret", 152 | "as_string": null 153 | }, 154 | [...] 155 | } 156 | ], 157 | "stdout": "", 158 | "stderr": "", 159 | "exit_reason": "Process exited cleanly with exit code 0", 160 | "exit_code": 0 161 | } 162 | ``` 163 | 164 | This example also demonstrates how to use a custom path to the `harness`: 165 | 166 | ``` 167 | $ mandrake --snippit-length 4 code --harness=./harness/harness '4831c048ffc0c3' 168 | 169 | { 170 | "success": true, 171 | "pid": 1053809, 172 | "history": [ 173 | { 174 | "rbx": { 175 | "value": 0, 176 | "memory": null, 177 | "as_instruction": null, 178 | "as_string": null 179 | }, 180 | [...] 181 | "rip": { 182 | "value": 322371590, 183 | "memory": [ 184 | 195 185 | ], 186 | "as_instruction": "ret", 187 | "as_string": null 188 | }, 189 | "rdi": { 190 | "value": 0, 191 | "memory": null, 192 | "as_instruction": null, 193 | "as_string": null 194 | } 195 | } 196 | ], 197 | "stdout": "", 198 | "stderr": "", 199 | "exit_reason": "Process exited cleanly with exit code 1", 200 | "exit_code": 1 201 | } 202 | ``` 203 | 204 | If you are testing shellcode that crashes, Mandrake will handle that 205 | gracefully. This example runs the shellcode `push 0x41414141` / `ret`, which 206 | will crash at `0x41414141`: 207 | 208 | ``` 209 | $ mandrake --snippit-length 4 code --harness=./harness/harness '6841414141c3' 210 | { 211 | "success": true, 212 | "pid": 1054409, 213 | "history": [ 214 | { 215 | [...] 216 | } 217 | ], 218 | "stdout": "", 219 | "stderr": "", 220 | "exit_reason": "Execution crashed with a segmentation fault (SIGSEGV) @ 0x41414141", 221 | "exit_code": null 222 | } 223 | ``` 224 | 225 | Mandrake can also capture standard output: 226 | 227 | ``` 228 | $ mandrake --snippit-length 4 code 'e80d00000048656c6c6f20576f726c64210048c7c00100000048c7c7010000005e48c7c20c0000000f05c3' 229 | { 230 | "success": true, 231 | "pid": 1055334, 232 | "history": [ 233 | [...] 234 | ], 235 | "stdout": "Hello World!", 236 | "stderr": "", 237 | "exit_reason": "Process exited cleanly with exit code 12", 238 | "exit_code": 12 239 | ``` 240 | 241 | ## Analyzing Elf Files 242 | 243 | In addition to shellcode, we can also instrument an ELF (Linux) binary! We 244 | haven't used ELF binaries as much as shellcode, so this isn't as well tested 245 | and hardy. Your mileage may vary! 246 | 247 | The biggest thing to know is that, in an ELF binary, there's gonna be A LOT 248 | more junk, especially if you call out to libc functions. It might also run 249 | REALLLLY slow if you trace through all the libc code. 250 | 251 | If you can modify the ELF binary, you can trigger the logger by adding an `int 252 | 3` instruction in front of the code that you want to instrument. To turn the 253 | debugger back off again, add an `int 3` AFTER the code that you want to 254 | instrument. (I don't love doing it that way, but otherwise it takes a LONG time 255 | to run.) 256 | 257 | > NOTE: If you have an `int 3` within the code you want to instrument, you're 258 | > gonna have a bad time (sorry, I wish I could think of a better way!) 259 | 260 | Here's an example of something you might want to instrument: 261 | 262 | ``` 263 | $ cat demo.c 264 | #include 265 | #include 266 | 267 | int main(int argc, char *argv[]) 268 | { 269 | int i = 0; 270 | char buffer[16]; 271 | 272 | asm("int 3"); 273 | 274 | asm("nop"); 275 | asm("nop"); 276 | asm("nop"); 277 | 278 | asm("int 3"); 279 | 280 | return 0; 281 | } 282 | 283 | $ gcc -o demo -O0 -masm=intel --no-pie demo.c 284 | ``` 285 | 286 | When you execute it in Mandrake, you will see the three `nop` instructions: 287 | 288 | ``` 289 | $ mandrake --snippit-length 4 elf ./demo 290 | { 291 | "success": true, 292 | "pid": 1121316, 293 | "history": [ 294 | { 295 | "rip": { 296 | "value": 93824992235867, 297 | "memory": [ 298 | 144 299 | ], 300 | "as_instruction": "nop", 301 | "as_string": null 302 | } 303 | [...] 304 | ``` 305 | 306 | But if there are libc calls, things can get a bit big! Here's another example: 307 | 308 | ``` 309 | $ cat demo2.c 310 | #include 311 | #include 312 | 313 | int main(int argc, char *argv[]) 314 | { 315 | int i = 0; 316 | char buffer[16]; 317 | 318 | asm("int 3"); 319 | asm("nop"); 320 | 321 | strcpy(buffer, argv[1]); 322 | printf("%s\n", buffer); 323 | 324 | asm("nop"); 325 | asm("int 3"); 326 | 327 | return 0; 328 | } 329 | 330 | $ gcc -o demo2 -O0 -masm=intel --no-pie demo2.c 331 | ``` 332 | 333 | If we try to instrument `demo2`, we quickly run into our execution cap: 334 | 335 | ``` 336 | $ mandrake --snippit-length 4 elf ./demo2 abc 337 | 338 | [...] 339 | "exit_reason": "Execution stopped at instruction cap (max instructions: 128)", 340 | ``` 341 | 342 | We can raise that, but we end up with a whole lot of output: 343 | 344 | ``` 345 | $ mandrake --max-instructions 10000 --snippit-length 4 elf ./demo2 abc 346 | [...] 347 | { 348 | "instructions_executed": 3209, 349 | "success": true, 350 | ``` 351 | 352 | > Maybe you're okay with looking through 3209 instructions, but I sure don't 353 | > want to! 354 | 355 | The best you can do is probably to turn off ASLR, then filter down to simply 356 | the binary you want to see. Here's how I do that: 357 | 358 | Ensure your binary is compiled with `--no-pie`, then turn off ASLR, 359 | execute it, and have a look at the starting address: 360 | 361 | ``` 362 | $ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 363 | $ mandrake --max-instructions 1 --snippit-length 4 elf ./demo2 abc 364 | [...] 365 | { 366 | "starting_address": 93824992235899, 367 | "instructions_executed": 1, 368 | ``` 369 | 370 | That value is `0x55555555517b` in hex. It might vary for you, so don't use 371 | this command directly if you're following along! 372 | 373 | By default, Mandrake masks out the last 4 nibbles, meaning effectively the 374 | address is 0x555555550000 when compared. The mask can be changed with 375 | `--hidden-mask` if you want, but we don't need to: 376 | 377 | ``` 378 | $ mandrake --output-format=json --snippit-length 4 elf ./demo2 --visible-address 0x0000555555550000 379 | [...] 380 | { 381 | "starting_address": 93824992235899, 382 | "instructions_executed": 3209, 383 | "success": true, 384 | [...] 385 | ``` 386 | 387 | Note that while 3209 instructions are executed, the results only contain 13 388 | entries! 389 | 390 | ## What do I do with all that JSON? 391 | 392 | Well, you can also output with `--output-format=YAML`. :) 393 | 394 | We can actually support any type that [Serde](https://serde.rs/) supports, 395 | please file a bug or send a patch if you'd like Pickle or something. 396 | 397 | 398 | 399 | But to answer the question.. I dunno! At Counter Hack, we wrapped a web 400 | interface around it to teach shellcoding. I bet there are a lot more cool 401 | things you can do, though, use your imagination! 402 | 403 | # Build 404 | 405 | If you have the Rust toolchain (`cargo`), you don't really need to build it! 406 | It'll automatically build when you `cargo run`. 407 | 408 | But if you don't want to install `cargo`, fear not! You can just run `make` in 409 | the root folder, and it should build you a binary release using a Docker 410 | environment (requires Docker). 411 | 412 | The build files are copies into the build/ folder when complete. 413 | 414 | # Appendix: Usage 415 | 416 | This is just the output of `--help`. Be warned - I might forget to update this, 417 | run the actual application for up-to-date help! 418 | 419 | ``` 420 | $ mandrake --help 421 | Mandrake 0.1.1 422 | Ron Bowes 423 | Mandrake is an open-source machine code analyzer / instrumenter 424 | 425 | USAGE: 426 | mandrake [OPTIONS] 427 | 428 | OPTIONS: 429 | --follow-exec-syscalls 430 | Enable to follow exec syscalls (usually not desirable, because exec starts a process 431 | from scratch and following that is very slow) 432 | 433 | -h, --help 434 | Print help information 435 | 436 | -i, --max-instructions 437 | The maximum number of instructions to read before stopping (to prevent infinite loops) 438 | [default: 128] 439 | 440 | --ignore-stderr 441 | Don't save output from stderr 442 | 443 | --ignore-stdout 444 | Don't save output from stdout 445 | 446 | -m, --minimum-viable-string 447 | The number of consecutive ASCII bytes to be considered a string [default: 6] 448 | 449 | -o, --output-format 450 | The output format ("JSON", "YAML", "Plaintext", or "Pickle") [default: JSON] 451 | 452 | -s, --snippit-length 453 | The amount of context memory to read [default: 64] 454 | 455 | -V, --version 456 | Print version information 457 | 458 | SUBCOMMANDS: 459 | code Analyze raw machine code using a harness 460 | elf Analyze an ELF file (Linux executable) 461 | help Print this message or the help of the given subcommand(s) 462 | ``` 463 | 464 | ``` 465 | $ mandrake code --help 466 | mandrake-code 0.1.1 467 | Ron Bowes 468 | Analyze raw machine code using a harness 469 | 470 | USAGE: 471 | mandrake code [OPTIONS] 472 | 473 | ARGS: 474 | The code, as a hex string (eg: "4831C0C3") 475 | 476 | OPTIONS: 477 | -h, --help Print help information 478 | --harness The path to the required harness [default: ./harness/harness] 479 | -V, --version Print version information 480 | ``` 481 | 482 | ``` 483 | $ mandrake elf --help 484 | mandrake-elf 0.1.1 485 | Ron Bowes 486 | Analyze an ELF file (Linux executable) 487 | 488 | USAGE: 489 | mandrake elf [OPTIONS] [ARGS]... 490 | 491 | ARGS: 492 | The ELF executable 493 | ... The argument(s) to pass to the ELF executable 494 | 495 | OPTIONS: 496 | -h, --help 497 | Print help information 498 | 499 | --hidden-address 500 | Hide instructions that match this address (ANDed with the --hidden-mask) 501 | 502 | --hidden-mask 503 | ANDed with the --hidden-address before comparing - by default, 0xFFFFFFFFFFFF0000 504 | 505 | -V, --version 506 | Print version information 507 | 508 | --visible-address 509 | Only show instructions that match this address (ANDed with the --visible-mask) 510 | 511 | --visible-mask 512 | ANDed with the --visible-address before comparing - by default, 0xFFFFFFFFFFFF0000 513 | ``` 514 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | demo 2 | demo2 3 | demo3 4 | -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | all: demo demo2 demo3 2 | 3 | demo: demo.c 4 | gcc -o demo -O0 -masm=intel -fno-stack-protector --no-pie demo.c 5 | 6 | demo2: demo2.c 7 | gcc -o demo2 -O0 -masm=intel -fno-stack-protector --no-pie demo2.c 8 | 9 | demo3: demo3.c 10 | gcc -o demo3 -O0 -masm=intel -fno-stack-protector --no-pie demo3.c 11 | 12 | clean: 13 | rm -f demo demo2 demo3 *.o 14 | -------------------------------------------------------------------------------- /demo/demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | int i = 0; 7 | char buffer[16]; 8 | 9 | asm("int 3"); 10 | 11 | asm("nop"); 12 | asm("nop"); 13 | asm("nop"); 14 | 15 | asm("int 3"); 16 | 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /demo/demo2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | int i = 0; 7 | char buffer[16]; 8 | 9 | asm("int 3"); 10 | asm("nop"); 11 | 12 | strcpy(buffer, argv[1]); 13 | printf("%s\n", buffer); 14 | 15 | asm("nop"); 16 | asm("int 3"); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /demo/demo3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | int i = 0; 7 | char buffer[16]; 8 | 9 | gets(buffer); 10 | printf("%s\n", buffer); 11 | 12 | asm("int 3"); 13 | asm("nop"); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /harness/Makefile: -------------------------------------------------------------------------------- 1 | all: harness 2 | 3 | harness: harness.c 4 | gcc -o harness -masm=intel harness.c 5 | chown ${UID}:${GID} harness 6 | 7 | clean: 8 | rm -f harness *.o 9 | -------------------------------------------------------------------------------- /harness/harness: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CounterHack/mandrake/4a3b251be330a97bee2507a983bce3b96b5bdceb/harness/harness -------------------------------------------------------------------------------- /harness/harness.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char *argv[]){ 9 | if(argc != 2) { 10 | printf("Usage: %s \n", argv[0]); 11 | exit(1); 12 | } 13 | 14 | // Note: It's important that this uses 0x13370000 * 0xFFFF0000, because 15 | // The `mandrake` binary requires that 16 | unsigned char *a = mmap((void*)0x13370000, strlen(argv[1]) / 2, PROT_EXEC |PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); 17 | int i; 18 | for(i = 0; i < strlen(argv[1]); i += 2) { 19 | sscanf(argv[1] + i, "%2hhx", (char*)&a[i / 2]); 20 | } 21 | 22 | /* Give it 10 seconds to run before killing the process with SIGALRM */ 23 | alarm(10); 24 | asm("mov rax, %0\n" 25 | "xor rbx, rbx\n" 26 | "xor rcx, rcx\n" 27 | "xor rdx, rdx\n" 28 | "xor rsi, rsi\n" 29 | "xor rdi, rdi\n" 30 | "xor rbp, rbp\n" 31 | 32 | // This triggers the debugger 33 | "int 0x03\n" 34 | 35 | // Jump to the user's code - if they return, it'll return to the exit code 36 | "call rax\n" 37 | 38 | // This turns off the debugger 39 | "int 0x03\n" 40 | 41 | // rax (exit code) is already set to the value returned by the code 42 | "mov rdi, rax\n" // Set rdi (exit_code) to whatever the function returned 43 | "mov rax, 60\n" // Set the syscall to 60 (sys_exit) 44 | "syscall\n" 45 | : :"r"(a)); 46 | } 47 | -------------------------------------------------------------------------------- /src/analyzed_value.rs: -------------------------------------------------------------------------------- 1 | //! Reads a block of memory and translates it into something useful. 2 | //! 3 | //! Always, we store the [`u64`] value. Then we try to read the memory pointed 4 | //! at by it. We store some amount of memory based on what the caller wants, 5 | //! then try to parse it either as an instruction or a string. That may or 6 | //! may not work, and it may or may not produce valid output - we do what we 7 | //! can! 8 | use std::fmt; 9 | 10 | use byteorder::{LittleEndian, WriteBytesExt}; 11 | use iced_x86::{Decoder, DecoderOptions, Formatter, NasmFormatter}; 12 | use nix::sys::ptrace::{read, AddressType}; 13 | use nix::unistd::Pid; 14 | use serde::{Serialize, Deserialize}; 15 | 16 | use crate::syscalls::{SyscallEntry, SYSCALLS}; 17 | 18 | // We initially read this much so we can look for strings and code 19 | const INITIAL_SNIPPIT_LENGTH: usize = 128; 20 | 21 | const MAX_SYSCALL_MEMORY_SNIPPIT: usize = 8; 22 | 23 | /// A serializable, analyzed value. 24 | /// 25 | /// Be careful changing this! Things that consume Mandrake's output depend on 26 | /// the structure not changing. 27 | #[derive(Serialize, Deserialize, Clone, Debug)] 28 | pub struct AnalyzedValue { 29 | // The value 30 | pub value: u64, 31 | 32 | // The memory as a stream of bytes 33 | pub memory: Option>, 34 | 35 | // The decoded instruction, if possible 36 | pub as_instruction: Option, 37 | 38 | // A decoded string (UTF-8), if possible 39 | pub as_string: Option, 40 | 41 | // Keep track of what's an instruction pointer (for nicer output) 42 | pub is_instruction_pointer: bool, 43 | 44 | // Extra info, if we have any 45 | pub extra: Option>, 46 | } 47 | 48 | impl AnalyzedValue { 49 | fn syscall_param(pid: Pid, s: &SyscallEntry, r: &AnalyzedValue) -> String { 50 | if s.is_array { 51 | // Ensure it's a pointer 52 | if r.value != 0 { 53 | // Create a vector of the arguments 54 | let mut out: Vec = Vec::new(); 55 | 56 | // Loop through the arguments 57 | for i in 0.. { 58 | // Get the address of the next potential string 59 | let addr = Self::get_memory_as_u64(pid, r.value + (i * 8)); 60 | 61 | // Break on invalid memory 62 | let addr = match addr { 63 | Some(a) => a, 64 | None => break, 65 | }; 66 | 67 | // Break on NUL pointer 68 | if addr == 0 { 69 | break; 70 | } 71 | 72 | // Get the string there 73 | let a = Self::new(pid, addr, false, 0, 0); 74 | 75 | // Break if there's no string 76 | let as_string = match a.as_string { 77 | Some(as_string) => as_string, 78 | None => break, 79 | }; 80 | 81 | // Add it to the list and continue 82 | out.push(format!("\"{}\"", as_string)); 83 | } 84 | 85 | format!("[{}]", out.join(", ")) 86 | } else { 87 | "(Empty array)".to_string() 88 | } 89 | } else if s.is_string { 90 | match &r.as_string { 91 | Some(s) => format!("`{}`", &s), 92 | None => format!("Invalid string: 0x{:08x}", r.value), 93 | } 94 | } else if s.field_type == "struct sockaddr" { 95 | let data = Self::new(pid, r.value, false, 10, 0); 96 | match data.memory { 97 | Some(m) => { 98 | if m[0] == 2 && m[1] == 0 { 99 | let port = (m[2] as u16) << 8 | (m[3] as u16); 100 | let ip = format!("{}.{}.{}.{}", m[4], m[5], m[6], m[7]); 101 | 102 | format!("IPv4 address: `{}:{}`", ip, port) 103 | } else { 104 | format!("Unknown sockaddr type (not AF_INET): 0x{:04x}", ((m[1] as u16) << 8) | (m[0] as u16)) 105 | } 106 | }, 107 | None => format!("Invalid sockaddr pointer: 0x{:08x}", r.value), 108 | } 109 | } else if s.is_pointer { 110 | if r.value == 0 { 111 | "(nil)".to_string() 112 | } else { 113 | match &r.memory { 114 | Some(mem) => format!("`{}...`", hex::encode(&mem[..MAX_SYSCALL_MEMORY_SNIPPIT])), 115 | None => format!("Invalid memory pointer: 0x{:08x}", r.value), 116 | } 117 | } 118 | } else { 119 | format!("`0x{:08x}`", r.value) 120 | } 121 | } 122 | 123 | pub fn syscall_info(pid: Pid, rax: &AnalyzedValue, rdi: &AnalyzedValue, rsi: &AnalyzedValue, rdx: &AnalyzedValue, r10: &AnalyzedValue, r8: &AnalyzedValue, r9: &AnalyzedValue) -> Vec { 124 | match SYSCALLS.get(&rax.value) { 125 | Some(s) => { 126 | let mut out = vec![format!("Syscall: `{}`", s.name)]; // The syscall number 127 | 128 | if let Some(param) = &s.rdi { 129 | out.push(format!("{} (rdi) = {}", param.field_name, Self::syscall_param(pid, ¶m, rdi))); 130 | } 131 | 132 | if let Some(param) = &s.rsi { 133 | out.push(format!("{} (rsi) = {}", param.field_name, Self::syscall_param(pid, ¶m, rsi))); 134 | } 135 | 136 | if let Some(param) = &s.rdx { 137 | out.push(format!("{} (rdx) = {}", param.field_name, Self::syscall_param(pid, ¶m, rdx))); 138 | } 139 | 140 | if let Some(param) = &s.r10 { 141 | out.push(format!("{} (r10) = {}", param.field_name, Self::syscall_param(pid, ¶m, r10))); 142 | } 143 | 144 | if let Some(param) = &s.r8 { 145 | out.push(format!("{} (r8) = {}", param.field_name, Self::syscall_param(pid, ¶m, r8))); 146 | } 147 | 148 | if let Some(param) = &s.r9 { 149 | out.push(format!("{} (r9) = {}", param.field_name, Self::syscall_param(pid, ¶m, r9))); 150 | } 151 | 152 | out 153 | }, 154 | None => vec![format!("Unknown syscall: `{}`", rax.value)], 155 | } 156 | } 157 | 158 | pub fn new(pid: Pid, value: u64, is_instruction_pointer: bool, snippit_length: usize, minimum_viable_string: usize) -> Self { 159 | // Figure out the longest value we need 160 | let bytes_to_get: usize = std::cmp::max(INITIAL_SNIPPIT_LENGTH, snippit_length); 161 | 162 | let mut data = match Self::get_memory(pid, value, bytes_to_get) { 163 | Some(data) => data, 164 | None => { 165 | // If we can't get memory, just return the value 166 | return AnalyzedValue { 167 | value: value, 168 | memory: None, 169 | as_instruction: None, 170 | as_string: None, 171 | is_instruction_pointer: is_instruction_pointer, 172 | extra: None, 173 | }; 174 | } 175 | }; 176 | 177 | // Try and decode from assembly - decode with the full data length 178 | let mut decoder = Decoder::with_ip(64, &data, value as u64, DecoderOptions::NONE); 179 | let as_instruction = match decoder.can_decode() { 180 | true => { 181 | let mut output = String::new(); 182 | let decoded = decoder.decode(); 183 | 184 | if is_instruction_pointer { 185 | data.truncate(decoded.len()); 186 | } 187 | NasmFormatter::new().format(&decoded, &mut output); 188 | 189 | if output == "(bad)" { 190 | None 191 | } else { 192 | Some(output) 193 | } 194 | } 195 | false => None, 196 | }; 197 | 198 | // Try and interpret as a string - this is also done with the full-length value 199 | let string_data: Vec = data.clone().into_iter().take_while(|d| *d != 0).collect(); 200 | let as_string = match std::str::from_utf8(&string_data) { 201 | Ok(s) => { 202 | if s.len() > minimum_viable_string { 203 | Some(s.to_string()) 204 | } else { 205 | None 206 | } 207 | }, 208 | Err(_) => None, 209 | }; 210 | 211 | // Truncate it to the actual size they asked for (after checking for instructions) 212 | data.truncate(snippit_length); 213 | 214 | Self { 215 | value: value, 216 | memory: Some(data), 217 | as_instruction: as_instruction, 218 | as_string: as_string, 219 | is_instruction_pointer: is_instruction_pointer, 220 | 221 | // We need all the registers to figure out syscall details, so mark 222 | // this as None for now 223 | extra: None, 224 | } 225 | } 226 | 227 | fn get_memory(pid: Pid, addr: u64, snippit_length: usize) -> Option> { 228 | let mut data: Vec = vec![]; 229 | 230 | for i in 0..((snippit_length + 7) / 8) { 231 | let this_chunk = match read(pid, (addr as usize + (i * 8)) as AddressType) { 232 | Ok(chunk) => chunk, 233 | // If the memory isn't readable, just return None 234 | Err(_e) => return None, 235 | }; 236 | 237 | // I don't think this can actually fail 238 | data.write_i64::(this_chunk).unwrap(); 239 | } 240 | 241 | Some(data) 242 | } 243 | 244 | fn get_memory_as_u64(pid: Pid, addr: u64) -> Option { 245 | match read(pid, addr as AddressType) { 246 | Ok(d) => Some(d as u64), 247 | Err(_e) => None, 248 | } 249 | } 250 | 251 | } 252 | 253 | impl fmt::Display for AnalyzedValue { 254 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 255 | if self.is_instruction_pointer { 256 | write!(f, "0x{:08x} {}", self.value, self.as_instruction.as_ref().unwrap_or(&"(bad)".to_string())) 257 | } else { 258 | write!(f, "0x{:08x}", self.value) 259 | } 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analyzed_value; 2 | pub mod mandrake_output; 3 | pub mod mandrake; 4 | pub mod visibility_configuration; 5 | pub mod syscalls; 6 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::path::Path; 3 | use std::str::FromStr; 4 | 5 | use simple_error::{SimpleError, bail}; 6 | use clap::Parser; 7 | use clap_num::maybe_hex; 8 | 9 | // Import from the library 10 | use mandrake::mandrake::Mandrake; 11 | use mandrake::visibility_configuration::VisibilityConfiguration; 12 | 13 | #[derive(Debug)] 14 | enum OutputFormat { 15 | JSON, 16 | YAML, 17 | PLAINTEXT, 18 | PICKLE, 19 | } 20 | 21 | impl FromStr for OutputFormat { 22 | type Err = SimpleError; 23 | 24 | fn from_str(input: &str) -> Result { 25 | match &input.to_lowercase()[..] { 26 | "json" => Ok(OutputFormat::JSON), 27 | "yaml" => Ok(OutputFormat::YAML), 28 | "pickle" => Ok(OutputFormat::PICKLE), 29 | "plaintext" | "text" => Ok(OutputFormat::PLAINTEXT), 30 | 31 | _ => bail!("Unknown format: {}", input), 32 | } 33 | } 34 | } 35 | 36 | impl fmt::Display for OutputFormat { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | match self { 39 | Self::JSON => write!(f, "JSON"), 40 | Self::YAML => write!(f, "YAML"), 41 | Self::PICKLE => write!(f, "PICKLE"), 42 | Self::PLAINTEXT => write!(f, "PLAINTEXT"), 43 | } 44 | } 45 | } 46 | 47 | 48 | #[derive(Parser, Debug)] 49 | #[clap(about, version, author)] 50 | struct Elf { 51 | #[clap(flatten)] 52 | visibility_configuration: VisibilityConfiguration, 53 | 54 | /// Standard in, encoded as hex (eg, "4141414141") 55 | #[clap(long)] 56 | stdin_data: Option, 57 | 58 | /// The ELF executable 59 | elf: String, 60 | 61 | /// The argument(s) to pass to the ELF executable 62 | args: Vec, 63 | } 64 | 65 | #[derive(Parser, Debug)] 66 | #[clap(about, version, author)] 67 | struct Code { 68 | /// The code, as a hex string (eg: "4831C0C3") 69 | code: String, 70 | 71 | /// The path to the required harness 72 | #[clap(long, default_value_t = String::from("./harness/harness"))] 73 | harness: String, 74 | 75 | /// If set, doesn't hide instructions executed outside of the harness 76 | /// (helpful if, say, you're analyzing shellcode that allocates memory) 77 | #[clap(long)] 78 | show_everything: bool, 79 | } 80 | 81 | #[derive(clap::Subcommand, Debug)] 82 | enum Action { 83 | /// Analyze raw machine code using a harness 84 | Code(Code), 85 | 86 | /// Analyze an ELF file (Linux executable) 87 | Elf(Elf), 88 | } 89 | 90 | /// Mandrake is an open-source machine code analyzer / instrumenter written in Rust. 91 | #[derive(Parser, Debug)] 92 | #[clap(name = "Mandrake", about, version, author)] 93 | struct Args { 94 | /// The output format ("JSON", "YAML", "Plaintext", or "Pickle") 95 | #[clap(short, long, default_value_t = OutputFormat::JSON)] 96 | output_format: OutputFormat, 97 | 98 | /// The amount of context memory to read 99 | #[clap(short, long, default_value_t = 64, parse(try_from_str=maybe_hex))] 100 | snippit_length: usize, 101 | 102 | /// The number of consecutive ASCII bytes to be considered a string 103 | #[clap(short, long, default_value_t = 6, parse(try_from_str=maybe_hex))] 104 | minimum_viable_string: usize, 105 | 106 | /// The maximum number of instructions to read before stopping (to prevent infinite loops) 107 | #[clap(short='i', long, default_value_t = 1024, parse(try_from_str=maybe_hex))] 108 | max_instructions: usize, 109 | 110 | /// Don't save output from stdout 111 | #[clap(long)] 112 | ignore_stdout: bool, 113 | 114 | /// Don't save output from stderr 115 | #[clap(long)] 116 | ignore_stderr: bool, 117 | 118 | /// Enable to follow exec syscalls (usually not desirable, because exec starts a process from scratch and following that is very slow) 119 | #[clap(long)] 120 | follow_exec_syscalls: bool, 121 | 122 | #[clap(subcommand)] 123 | action: Action, 124 | } 125 | 126 | /// Main intentially does not return an error. 127 | /// 128 | /// That means that we're sorta forced to handle all errors cleanly (or 129 | /// panic :) ). 130 | fn main() { 131 | // Parse the commandline options 132 | let args = Args::parse(); 133 | 134 | // Create an instance of Mandrake with the configurations 135 | let mandrake = Mandrake::new( 136 | args.snippit_length, 137 | args.minimum_viable_string, 138 | Some(args.max_instructions), 139 | args.ignore_stdout, 140 | args.ignore_stderr, 141 | args.follow_exec_syscalls, 142 | ); 143 | 144 | // Check which subcommand they ran 145 | let result = match args.action { 146 | Action::Code(code_args) => { 147 | match hex::decode(code_args.code) { 148 | Ok(code) => mandrake.analyze_code(code, &Path::new(&code_args.harness), code_args.show_everything), 149 | Err(e) => Err(SimpleError::new(format!("Could not decode hex: {}", e))), 150 | } 151 | }, 152 | Action::Elf(elf_args) => { 153 | mandrake.analyze_elf(&Path::new(&elf_args.elf), elf_args.stdin_data, elf_args.args, &elf_args.visibility_configuration) 154 | }, 155 | }; 156 | 157 | // Handle errors somewhat more cleanly than just bailing 158 | match result { 159 | Ok(r) => match args.output_format { 160 | OutputFormat::JSON => println!("{}", serde_json::to_string_pretty(&r).unwrap()), 161 | OutputFormat::YAML => println!("{}", serde_yaml::to_string(&r).unwrap()), 162 | OutputFormat::PICKLE => { 163 | println!("import base64"); 164 | println!("import pickle"); 165 | println!(); 166 | println!("pickle.loads(base64.b64decode(\"{}\"))", base64::encode(serde_pickle::to_vec(&r, Default::default()).unwrap())); 167 | }, 168 | OutputFormat::PLAINTEXT => { 169 | for entry in r.history { 170 | match entry.get("rip") { 171 | Some(entry) => { 172 | println!("{}", entry); 173 | }, 174 | None => { 175 | eprintln!("Missing rip in entry"); 176 | }, 177 | } 178 | } 179 | 180 | if let Some(stdout) = r.stdout { 181 | if stdout != "" { 182 | println!(); 183 | println!("Stdout: {}", stdout); 184 | } 185 | } 186 | 187 | if let Some(stderr) = r.stderr { 188 | if stderr != "" { 189 | println!(); 190 | println!("stderr: {}", stderr); 191 | } 192 | } 193 | }, 194 | }, 195 | Err(e) => eprintln!("Execution failed: {}", e.to_string()), 196 | }; 197 | } 198 | -------------------------------------------------------------------------------- /src/mandrake.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::process::{Command, Stdio, Child}; 3 | use std::collections::HashMap; 4 | use std::path::Path; 5 | 6 | use nix::sys::ptrace::{getregs, step, cont, kill}; 7 | use nix::sys::signal::Signal; 8 | use nix::sys::wait::{wait, WaitStatus}; 9 | use nix::unistd::Pid; 10 | 11 | use simple_error::{bail, SimpleResult, SimpleError}; 12 | use spawn_ptrace::CommandPtraceSpawn; 13 | 14 | use crate::analyzed_value::AnalyzedValue; 15 | use crate::mandrake_output::MandrakeOutput; 16 | use crate::visibility_configuration::VisibilityConfiguration; 17 | 18 | /// Represents the mandrake configuration. 19 | #[derive(Debug)] 20 | pub struct Mandrake { 21 | snippit_length: usize, 22 | minimum_viable_string: usize, 23 | max_logged_instructions: Option, 24 | capture_stdout: bool, 25 | capture_stderr: bool, 26 | follow_exec: bool, 27 | } 28 | 29 | const EXECVE_NUM: u64 = 59; 30 | 31 | /// Performs a wait() then cont(). 32 | /// 33 | /// Waits for the current operation to complete (which is a step), then 34 | /// continues execution 35 | fn resume_execution(pid: Pid) -> SimpleResult<()> { 36 | wait() 37 | .map_err(|e| SimpleError::new(&format!("Couldn't step over breakpoint: {}", e)))?; 38 | 39 | cont(pid, None) 40 | .map_err(|e| SimpleError::new(&format!("Couldn't resume execution after breakpoint: {}", e)))?; 41 | 42 | Ok(()) 43 | } 44 | 45 | impl Mandrake { 46 | pub fn new(snippit_length: usize, minimum_viable_string: usize, max_logged_instructions: Option, ignore_stdout: bool, ignore_stderr: bool, follow_exec: bool) -> Self { 47 | Self { 48 | snippit_length: snippit_length, 49 | minimum_viable_string: minimum_viable_string, 50 | max_logged_instructions: max_logged_instructions, 51 | capture_stdout: !ignore_stdout, 52 | capture_stderr: !ignore_stderr, 53 | follow_exec: follow_exec, 54 | } 55 | } 56 | 57 | fn go(&self, child: Child, visibility: &VisibilityConfiguration) -> SimpleResult { 58 | // Build a state then loop, one instruction at a time, till this ends 59 | let mut result = MandrakeOutput::new(child.id()); 60 | let pid = Pid::from_raw(child.id() as i32); 61 | 62 | // This flag is set when a call to execve is made, and we want to stop 63 | // tracing. The new process creation causes debugging to turn back on, 64 | // and we don't want that. 65 | let mut completed = false; 66 | 67 | loop { 68 | match wait() { 69 | Ok(WaitStatus::Exited(_, code)) => { 70 | result.exit_reason = Some(format!("Process exited cleanly with exit code {}", code)); 71 | result.exit_code = Some(code); 72 | break; 73 | } 74 | Ok(WaitStatus::Stopped(_, sig)) => { 75 | // Get rip when it crashes 76 | let regs = self.get_registers_from_pid(pid) 77 | .map_err(|e| SimpleError::new(format!("Couldn't read registers: {}", e)))?; 78 | 79 | // Get the value for RIP, die if it's missing (shouldn't happen) 80 | let rip = match regs.get("rip") { 81 | Some(rip) => rip, 82 | None => bail!("rip is missing from the register list!"), 83 | }; 84 | 85 | match sig { 86 | // Do nothing, this is the happy call 87 | Signal::SIGTRAP => { 88 | // No matter what, step past the instruction 89 | step(pid, None) 90 | .map_err(|e| SimpleError::new(&format!("Couldn't step through code: {}", e)))?; 91 | 92 | // If we're already finished, just keep going 93 | if completed { 94 | resume_execution(pid)?; 95 | continue; 96 | } 97 | 98 | // If we get an int3, it means we want to stop logging (ie, continue) 99 | if let Some(instruction) = &rip.as_instruction { 100 | // Toggle "following" for "int 3" 101 | if instruction == "int3" { 102 | // Waiting for the step() to finish before continuing is important 103 | resume_execution(pid)?; 104 | 105 | // Continue so it's not logged 106 | continue; 107 | } 108 | 109 | // Toggle following on exec, unless the user turned that off 110 | if !self.follow_exec && instruction == "syscall" { 111 | // Check the syscall num (based on rax) 112 | let syscall_num = match regs.get("rax") { 113 | Some(rax) => rax, 114 | None => bail!("rax is missing from the register list!"), 115 | }; 116 | 117 | // sys_execve 118 | if syscall_num.value == EXECVE_NUM { 119 | // Skip all future checks 120 | completed = true; 121 | 122 | // Resume, but don't skip the output (the user wants to see the exec!) 123 | resume_execution(pid)?; 124 | } 125 | } 126 | } 127 | 128 | // Count the instructions 129 | result.instructions_executed += 1; 130 | 131 | // Count the actual instructions executed (even if they're invisible) 132 | if let Some(max_instructions) = self.max_logged_instructions { 133 | if result.instructions_executed >= max_instructions { 134 | result.exit_reason = Some(format!("Execution stopped at instruction cap (max instructions: {})", max_instructions)); 135 | break; 136 | } 137 | } 138 | 139 | // Check if we're supposed to see this 140 | if !visibility.is_visible(rip.value) { 141 | continue; 142 | } 143 | 144 | // If we don't have a first address, save the current address 145 | if result.starting_address.is_none() { 146 | result.starting_address = Some(rip.value); 147 | } 148 | 149 | result.history.push(regs); 150 | 151 | continue; 152 | }, 153 | 154 | // Check for the special timeout symbol (since we set alarm() in the harness) 155 | Signal::SIGALRM => { result.exit_reason = Some(format!("Execution timed out (SIGALRM) @ {}", rip)); break; }, 156 | 157 | // Try and catch other obvious problems 158 | Signal::SIGABRT => { result.exit_reason = Some(format!("Execution crashed with an abort (SIGABRT) @ {}", rip)); break; } 159 | Signal::SIGBUS => { result.exit_reason = Some(format!("Execution crashed with a bus error (bad memory access) (SIGBUS) @ {}", rip)); break; } 160 | Signal::SIGFPE => { result.exit_reason = Some(format!("Execution crashed with a floating point error (SIGFPE) @ {}", rip)); break; } 161 | Signal::SIGILL => { result.exit_reason = Some(format!("Execution crashed with an illegal instruction (SIGILL) @ {}", rip)); break; }, 162 | Signal::SIGKILL => { result.exit_reason = Some(format!("Execution was killed (SIGKILL) @ {}", rip)); break; }, 163 | Signal::SIGSEGV => { result.exit_reason = Some(format!("Execution crashed with a segmentation fault (SIGSEGV) @ {}", rip)); break; }, 164 | Signal::SIGTERM => { result.exit_reason = Some(format!("Execution was terminated (SIGTERM) @ {}", rip)); break; }, 165 | Signal::SIGCHLD => { result.exit_reason = Some(format!("Execution ended when child process ended (SIGCHLD)")); break; }, 166 | 167 | _ => { result.exit_reason = Some(format!("Execution stopped by unexpected signal: {}", sig)); break; } 168 | }; 169 | 170 | }, 171 | Ok(s) => bail!("Unexpected stop reason: {:?}", s), 172 | Err(e) => bail!("Unexpected wait() error: {:?}", e), 173 | }; 174 | } 175 | 176 | // I don't know why, but this fixes a random timeout that sometimes breaks 177 | // this :-/ 178 | // 179 | // As of 2022-01, I have no idea if this is still needed or if the bug 180 | // this fixed is long-gone, but I'm too afraid to try because the bug 181 | // was always sporadic :) 182 | println!(""); 183 | 184 | // Whatever situation we're in, we need to make sure the process is dead 185 | // (We discard errors here, because we don't really care if it was already 186 | // killed or failed to kill or whatever) 187 | match kill(pid) { 188 | Ok(_) => (), 189 | Err(_) => (), 190 | }; 191 | 192 | // If we made it here, grab the stdout + stderr 193 | if self.capture_stdout { 194 | let mut stdout: Vec = vec![]; 195 | child.stdout 196 | .ok_or_else(|| SimpleError::new(format!("Couldn't get a handle to stdout")))? 197 | .read_to_end(&mut stdout) 198 | .map_err(|e| SimpleError::new(format!("Failed while trying to read stdout: {}", e)))?; 199 | 200 | result.stdout = Some(String::from_utf8_lossy(&stdout).to_string()); 201 | } 202 | 203 | if self.capture_stderr { 204 | let mut stderr: Vec = vec![]; 205 | child.stderr 206 | .ok_or_else(|| SimpleError::new(format!("Couldn't get a handle to stderr")))? 207 | .read_to_end(&mut stderr) 208 | .map_err(|e| SimpleError::new(format!("Failed while trying to read stderr: {}", e)))?; 209 | result.stderr = Some(String::from_utf8_lossy(&stderr).to_string()); 210 | } 211 | 212 | Ok(result) 213 | } 214 | 215 | fn get_registers_from_pid(&self, pid: Pid) -> SimpleResult> { 216 | // Try and get the registers 217 | let regs = match getregs(pid) { 218 | Ok(r) => r, 219 | Err(e) => bail!("Couldn't read registers: {}", e), 220 | }; 221 | 222 | // Analyze and save each one 223 | let mut out: HashMap = vec![ 224 | ("rip".to_string(), AnalyzedValue::new(pid, regs.rip, true, self.snippit_length, self.minimum_viable_string)), 225 | ("rax".to_string(), AnalyzedValue::new(pid, regs.rax, false, self.snippit_length, self.minimum_viable_string)), 226 | ("rbx".to_string(), AnalyzedValue::new(pid, regs.rbx, false, self.snippit_length, self.minimum_viable_string)), 227 | ("rcx".to_string(), AnalyzedValue::new(pid, regs.rcx, false, self.snippit_length, self.minimum_viable_string)), 228 | ("rdx".to_string(), AnalyzedValue::new(pid, regs.rdx, false, self.snippit_length, self.minimum_viable_string)), 229 | ("rsi".to_string(), AnalyzedValue::new(pid, regs.rsi, false, self.snippit_length, self.minimum_viable_string)), 230 | ("rdi".to_string(), AnalyzedValue::new(pid, regs.rdi, false, self.snippit_length, self.minimum_viable_string)), 231 | ("rbp".to_string(), AnalyzedValue::new(pid, regs.rbp, false, self.snippit_length, self.minimum_viable_string)), 232 | ("rsp".to_string(), AnalyzedValue::new(pid, regs.rsp, false, self.snippit_length, self.minimum_viable_string)), 233 | 234 | // I guess we should do the boring registers, too... 235 | ("r8".to_string(), AnalyzedValue::new(pid, regs.r8, false, self.snippit_length, self.minimum_viable_string)), 236 | ("r9".to_string(), AnalyzedValue::new(pid, regs.r9, false, self.snippit_length, self.minimum_viable_string)), 237 | ("r10".to_string(), AnalyzedValue::new(pid, regs.r10, false, self.snippit_length, self.minimum_viable_string)), 238 | ("r11".to_string(), AnalyzedValue::new(pid, regs.r11, false, self.snippit_length, self.minimum_viable_string)), 239 | ("r12".to_string(), AnalyzedValue::new(pid, regs.r12, false, self.snippit_length, self.minimum_viable_string)), 240 | ("r13".to_string(), AnalyzedValue::new(pid, regs.r13, false, self.snippit_length, self.minimum_viable_string)), 241 | ("r14".to_string(), AnalyzedValue::new(pid, regs.r14, false, self.snippit_length, self.minimum_viable_string)), 242 | ("r15".to_string(), AnalyzedValue::new(pid, regs.r15, false, self.snippit_length, self.minimum_viable_string)), 243 | ].into_iter().collect(); 244 | 245 | // Handle syscalls - this needs to come after because we need all values 246 | if let Some(rip) = &out.get("rip") { 247 | if let Some(instruction) = &rip.as_instruction { 248 | if instruction == "syscall" { 249 | // Load + clone registers before getting a mutable instance of 250 | // rip (Rust smartly doesn't let us read and write a variable 251 | // at the same time!) 252 | let rax = out.get("rax").ok_or_else(|| SimpleError::new(format!("Could not read value of rax")))?.clone(); 253 | let rdi = out.get("rdi").ok_or_else(|| SimpleError::new(format!("Could not read value of rdi")))?.clone(); 254 | let rsi = out.get("rsi").ok_or_else(|| SimpleError::new(format!("Could not read value of rsi")))?.clone(); 255 | let rdx = out.get("rdx").ok_or_else(|| SimpleError::new(format!("Could not read value of rdx")))?.clone(); 256 | let r10 = out.get("r10").ok_or_else(|| SimpleError::new(format!("Could not read value of r10")))?.clone(); 257 | let r8 = out.get("r8" ).ok_or_else(|| SimpleError::new(format!("Could not read value of r8" )))?.clone(); 258 | let r9 = out.get("r9" ).ok_or_else(|| SimpleError::new(format!("Could not read value of r9" )))?.clone(); 259 | 260 | // This gets a mutable handle to `out` - that means we can't 261 | // read from `out` within this block! 262 | out.get_mut("rip").map(|rip| { 263 | rip.extra = Some(AnalyzedValue::syscall_info(pid, &rax, &rdi, &rsi, &rdx, &r10, &r8, &r9)); 264 | }); 265 | } 266 | } 267 | } 268 | 269 | Ok(out) 270 | } 271 | 272 | pub fn analyze_code(&self, code: Vec, harness_path: &Path, show_everything: bool) -> SimpleResult { 273 | if !harness_path.exists() { 274 | bail!("Could not find the execution harness: {:?} - use --harness to specify the path to the 'harness' executable (which is available on https://github.com/counterhack)", harness_path); 275 | } 276 | 277 | let child = Command::new(harness_path) 278 | .arg(hex::encode(code)) 279 | .stdout(Stdio::piped()) 280 | .stderr(Stdio::piped()) 281 | .spawn_ptrace() 282 | .map_err(|e| SimpleError::new(format!("Could not execute testing harness: {}", e)))?; 283 | 284 | // Get a pid structure 285 | let pid = Pid::from_raw(child.id() as i32); 286 | 287 | // Find the first breakpiont 288 | cont(pid, None).map_err(|e| SimpleError::new(format!("Couldn't resume execution: {}", e)))?; 289 | wait().map_err(|e| SimpleError::new(format!("Failed while waiting for process to resume: {}", e)))?; 290 | 291 | // Step over it - this will perform the call() and move us to the start of 292 | // the user's code 293 | step(pid, None).map_err(|e| SimpleError::new(format!("Failed to stop into the shellcode: {}", e)))?; 294 | 295 | // At this point, we can proceed to normal analysis 296 | match show_everything { 297 | false => self.go(child, &VisibilityConfiguration::full_visibility()), 298 | true => self.go(child, &VisibilityConfiguration::harness_visibility()), 299 | } 300 | } 301 | 302 | pub fn analyze_elf(&self, binary: &Path, stdin: Option, args: Vec, visibility: &VisibilityConfiguration) -> SimpleResult { 303 | // Decode the stdin before starting the command, so we don't start the 304 | // process if the stdin is badly encoded 305 | let stdin = match stdin { 306 | Some(stdin) => Some(hex::decode(stdin).map_err(|e| SimpleError::new(format!("Could not parse --stdin-data as a hex string: {}", e)))?), 307 | None => None, 308 | }; 309 | 310 | // This spawns the process and calls waitpid(), so it reaches the first 311 | // system call (execve) 312 | let mut command = Command::new(binary); 313 | command.stdout(Stdio::piped()); 314 | command.stderr(Stdio::piped()); 315 | 316 | match stdin { 317 | // If there's a stdin, use it 318 | Some(_) => command.stdin(Stdio::piped()), 319 | // If there's no stdin, close it 320 | None => command.stdin(Stdio::null()), 321 | }; 322 | 323 | for arg in args { 324 | command.arg(arg); 325 | } 326 | 327 | let mut child = command.spawn_ptrace() 328 | .map_err(|e| SimpleError::new(format!("Could not execute testing harness: {}", e)))?; 329 | 330 | if let Some(stdin) = stdin { 331 | child.stdin.take() 332 | .ok_or_else(|| SimpleError::new(format!("Couldn't get a handle to stdin")))? 333 | .write_all(&stdin) 334 | .map_err(|e| SimpleError::new(format!("Failed while trying to write to stdin: {}", e)))?; 335 | } 336 | 337 | // Find the first breakpiont 338 | let pid = Pid::from_raw(child.id() as i32); 339 | cont(pid, None) 340 | .map_err(|e| SimpleError::new(format!("Couldn't resume execution: {}", e)))?; 341 | 342 | self.go(child, visibility) 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/mandrake_output.rs: -------------------------------------------------------------------------------- 1 | ///! Just a simple, serializable data structure that represents the output. 2 | 3 | use std::collections::HashMap; 4 | 5 | use serde::{Serialize, Deserialize}; 6 | 7 | use crate::analyzed_value::AnalyzedValue; 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug)] 10 | pub struct MandrakeOutput { 11 | pub starting_address: Option, 12 | pub instructions_executed: usize, 13 | 14 | pub success: bool, 15 | pub pid: u32, 16 | pub history: Vec>, 17 | pub stdout: Option, 18 | pub stderr: Option, 19 | pub exit_reason: Option, 20 | pub exit_code: Option, 21 | } 22 | 23 | impl MandrakeOutput { 24 | pub fn new(pid: u32) -> Self { 25 | MandrakeOutput { 26 | starting_address: None, 27 | instructions_executed: 0, 28 | 29 | success: true, 30 | pid: pid, 31 | history: vec![], 32 | stdout: None, 33 | stderr: None, 34 | exit_reason: None, 35 | exit_code: None, 36 | } 37 | } 38 | 39 | pub fn print(&self) { 40 | // I'm hoping that the to-json part can't fail 41 | println!("{}", serde_json::to_string_pretty(self).unwrap()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/syscalls.csv: -------------------------------------------------------------------------------- 1 | 0,sys_read,unsigned int fd,char *buf,size_t count 2 | 1,sys_write,unsigned int fd,const char *buf,size_t count 3 | 2,sys_open,const char *filename,int flags,int mode 4 | 3,sys_close,unsigned int fd 5 | 4,sys_stat,const char *filename,struct stat *statbuf 6 | 5,sys_fstat,unsigned int fd,struct stat *statbuf 7 | 6,sys_lstat,fconst char *filename,struct stat *statbuf 8 | 7,sys_poll,struct poll_fd *ufds,unsigned int nfds,long timeout_msecs 9 | 8,sys_lseek,unsigned int fd,off_t offset,unsigned int origin 10 | 9,sys_mmap,unsigned long addr,unsigned long len,unsigned long prot,unsigned long flags,unsigned long fd,unsigned long off 11 | 10,sys_mprotect,unsigned long start,size_t len,unsigned long prot 12 | 11,sys_munmap,unsigned long addr,size_t len 13 | 12,sys_brk,unsigned long brk 14 | 13,sys_rt_sigaction,int sig,const struct sigaction *act,struct sigaction *oact,size_t sigsetsize 15 | 14,sys_rt_sigprocmask,int how,sigset_t *nset,sigset_t *oset,size_t sigsetsize 16 | 15,sys_rt_sigreturn,unsigned long __unused 17 | 16,sys_ioctl,unsigned int fd,unsigned int cmd,unsigned long arg 18 | 17,sys_pread64,unsigned long fd,char *buf,size_t count,loff_t pos 19 | 18,sys_pwrite64,unsigned int fd,const char *buf,size_t count,loff_t pos 20 | 19,sys_readv,unsigned long fd,const struct iovec *vec,unsigned long vlen 21 | 20,sys_writev,unsigned long fd,const struct iovec *vec,unsigned long vlen 22 | 21,sys_access,const char *filename,int mode 23 | 22,sys_pipe,int *filedes 24 | 23,sys_select,int n,fd_set *inp,fd_set *outp,fd_set *exp,struct timeval *tvp 25 | 24,sys_sched_yield 26 | 25,sys_mremap,unsigned long addr,unsigned long old_len,unsigned long new_len,unsigned long flags,unsigned long new_addr 27 | 26,sys_msync,unsigned long start,size_t len,int flags 28 | 27,sys_mincore,unsigned long start,size_t len,unsigned char *vec 29 | 28,sys_madvise,unsigned long start,size_t len_in,int behavior 30 | 29,sys_shmget,key_t key,size_t size,int shmflg 31 | 30,sys_shmat,int shmid,char *shmaddr,int shmflg 32 | 31,sys_shmctl,int shmid,int cmd,struct shmid_ds *buf 33 | 32,sys_dup,unsigned int fildes 34 | 33,sys_dup2,unsigned int oldfd,unsigned int newfd 35 | 34,sys_pause 36 | 35,sys_nanosleep,struct timespec *rqtp,struct timespec *rmtp 37 | 36,sys_getitimer,int which,struct itimerval *value 38 | 37,sys_alarm,unsigned int seconds 39 | 38,sys_setitimer,int which,struct itimerval *value,struct itimerval *ovalue 40 | 39,sys_getpid 41 | 40,sys_sendfile,int out_fd,int in_fd,off_t *offset,size_t count 42 | 41,sys_socket,int family,int type,int protocol 43 | 42,sys_connect,int fd,struct sockaddr *uservaddr,int addrlen 44 | 43,sys_accept,int fd,struct sockaddr *upeer_sockaddr,int *upeer_addrlen 45 | 44,sys_sendto,int fd,void *buff,size_t len,unsigned flags,struct sockaddr *addr,int addr_len 46 | 45,sys_recvfrom,int fd,void *ubuf,size_t size,unsigned flags,struct sockaddr *addr,int *addr_len 47 | 46,sys_sendmsg,int fd,struct msghdr *msg,unsigned flags 48 | 47,sys_recvmsg,int fd,struct msghdr *msg,unsigned int flags 49 | 48,sys_shutdown,int fd,int how 50 | 49,sys_bind,int fd,struct sockaddr *umyaddr,int addrlen 51 | 50,sys_listen,int fd,int backlog 52 | 51,sys_getsockname,int fd,struct sockaddr *usockaddr,int *usockaddr_len 53 | 52,sys_getpeername,int fd,struct sockaddr *usockaddr,int *usockaddr_len 54 | 53,sys_socketpair,int family,int type,int protocol,int *usockvec 55 | 54,sys_setsockopt,int fd,int level,int optname,char *optval,int optlen 56 | 55,sys_getsockopt,int fd,int level,int optname,char *optval,int *optlen 57 | 56,sys_clone,unsigned long clone_flags,unsigned long newsp,void *parent_tid,void *child_tid,unsigned int tid 58 | 57,sys_fork 59 | 58,sys_vfork 60 | 59,sys_execve,const char *filename,const char *const argv[],const char *const envp[] 61 | 60,sys_exit,int error_code 62 | 61,sys_wait4,pid_t upid,int *stat_addr,int options,struct rusage *ru 63 | 62,sys_kill,pid_t pid,int sig 64 | 63,sys_uname,struct old_utsname *name 65 | 64,sys_semget,key_t key,int nsems,int semflg 66 | 65,sys_semop,int semid,struct sembuf *tsops,unsigned nsops 67 | 66,sys_semctl,int semid,int semnum,int cmd,union semun arg 68 | 67,sys_shmdt,char *shmaddr 69 | 68,sys_msgget,key_t key,int msgflg 70 | 69,sys_msgsnd,int msqid,struct msgbuf *msgp,size_t msgsz,int msgflg 71 | 70,sys_msgrcv,int msqid,struct msgbuf *msgp,size_t msgsz,long msgtyp,int msgflg 72 | 71,sys_msgctl,int msqid,int cmd,struct msqid_ds *buf 73 | 72,sys_fcntl,unsigned int fd,unsigned int cmd,unsigned long arg 74 | 73,sys_flock,unsigned int fd,unsigned int cmd 75 | 74,sys_fsync,unsigned int fd 76 | 75,sys_fdatasync,unsigned int fd 77 | 76,sys_truncate,const char *path,long length 78 | 77,sys_ftruncate,unsigned int fd,unsigned long length 79 | 78,sys_getdents,unsigned int fd,struct linux_dirent *dirent,unsigned int count 80 | 79,sys_getcwd,char *buf,unsigned long size 81 | 80,sys_chdir,const char *filename 82 | 81,sys_fchdir,unsigned int fd 83 | 82,sys_rename,const char *oldname,const char *newname 84 | 83,sys_mkdir,const char *pathname,int mode 85 | 84,sys_rmdir,const char *pathname 86 | 85,sys_creat,const char *pathname,int mode 87 | 86,sys_link,const char *oldname,const char *newname 88 | 87,sys_unlink,const char *pathname 89 | 88,sys_symlink,const char *oldname,const char *newname 90 | 89,sys_readlink,const char *path,char *buf,int bufsiz 91 | 90,sys_chmod,const char *filename,mode_t mode 92 | 91,sys_fchmod,unsigned int fd,mode_t mode 93 | 92,sys_chown,const char *filename,uid_t user,gid_t group 94 | 93,sys_fchown,unsigned int fd,uid_t user,gid_t group 95 | 94,sys_lchown,const char *filename,uid_t user,gid_t group 96 | 95,sys_umask,int mask 97 | 96,sys_gettimeofday,struct timeval *tv,struct timezone *tz 98 | 97,sys_getrlimit,unsigned int resource,struct rlimit *rlim 99 | 98,sys_getrusage,int who,struct rusage *ru 100 | 99,sys_sysinfo,struct sysinfo *info 101 | 100,sys_times,struct tms *tbuf 102 | 101,sys_ptrace,long request,long pid,unsigned long addr,unsigned long data 103 | 102,sys_getuid 104 | 103,sys_syslog,int type,char *buf,int len 105 | 104,sys_getgid 106 | 105,sys_setuid,uid_t uid 107 | 106,sys_setgid,gid_t gid 108 | 107,sys_geteuid 109 | 108,sys_getegid 110 | 109,sys_setpgid,pid_t pid,pid_t pgid 111 | 110,sys_getppid 112 | 111,sys_getpgrp 113 | 112,sys_setsid 114 | 113,sys_setreuid,uid_t ruid,uid_t euid 115 | 114,sys_setregid,gid_t rgid,gid_t egid 116 | 115,sys_getgroups,int gidsetsize,gid_t *grouplist 117 | 116,sys_setgroups,int gidsetsize,gid_t *grouplist 118 | 117,sys_setresuid,uid_t *ruid,uid_t *euid,uid_t *suid 119 | 118,sys_getresuid,uid_t *ruid,uid_t *euid,uid_t *suid 120 | 119,sys_setresgid,gid_t rgid,gid_t egid,gid_t sgid 121 | 120,sys_getresgid,gid_t *rgid,gid_t *egid,gid_t *sgid 122 | 121,sys_getpgid,pid_t pid 123 | 122,sys_setfsuid,uid_t uid 124 | 123,sys_setfsgid,gid_t gid 125 | 124,sys_getsid,pid_t pid 126 | 125,sys_capget,cap_user_header_t header,cap_user_data_t dataptr 127 | 126,sys_capset,cap_user_header_t header,const cap_user_data_t data 128 | 127,sys_rt_sigpending,sigset_t *set,size_t sigsetsize 129 | 128,sys_rt_sigtimedwait,const sigset_t *uthese,siginfo_t *uinfo,const struct timespec *uts,size_t sigsetsize 130 | 129,sys_rt_sigqueueinfo,pid_t pid,int sig,siginfo_t *uinfo 131 | 130,sys_rt_sigsuspend,sigset_t *unewset,size_t sigsetsize 132 | 131,sys_sigaltstack,const stack_t *uss,stack_t *uoss 133 | 132,sys_utime,char *filename,struct utimbuf *times 134 | 133,sys_mknod,const char *filename,umode_t mode,unsigned dev 135 | 134,sys_uselib,NOT IMPLEMENTED 136 | 135,sys_personality,unsigned int personality 137 | 136,sys_ustat,unsigned dev,struct ustat *ubuf 138 | 137,sys_statfs,const char *pathname,struct statfs *buf 139 | 138,sys_fstatfs,unsigned int fd,struct statfs *buf 140 | 139,sys_sysfs,int option,unsigned long arg1,unsigned long arg2 141 | 140,sys_getpriority,int which,int who 142 | 141,sys_setpriority,int which,int who,int niceval 143 | 142,sys_sched_setparam,pid_t pid,struct sched_param *param 144 | 143,sys_sched_getparam,pid_t pid,struct sched_param *param 145 | 144,sys_sched_setscheduler,pid_t pid,int policy,struct sched_param *param 146 | 145,sys_sched_getscheduler,pid_t pid 147 | 146,sys_sched_get_priority_max,int policy 148 | 147,sys_sched_get_priority_min,int policy 149 | 148,sys_sched_rr_get_interval,pid_t pid,struct timespec *interval 150 | 149,sys_mlock,unsigned long start,size_t len 151 | 150,sys_munlock,unsigned long start,size_t len 152 | 151,sys_mlockall,int flags 153 | 152,sys_munlockall 154 | 153,sys_vhangup 155 | 154,sys_modify_ldt,int func,void *ptr,unsigned long bytecount 156 | 155,sys_pivot_root,const char *new_root,const char *put_old 157 | 156,sys__sysctl,struct __sysctl_args *args 158 | 157,sys_prctl,int option,unsigned long arg2,unsigned long arg3,unsigned long arg4,void *unknown,unsigned long arg5 159 | 158,sys_arch_prctl,struct task_struct *task,int code,unsigned long *addr 160 | 159,sys_adjtimex,struct timex *txc_p 161 | 160,sys_setrlimit,unsigned int resource,struct rlimit *rlim 162 | 161,sys_chroot,const char *filename 163 | 162,sys_sync 164 | 163,sys_acct,const char *name 165 | 164,sys_settimeofday,struct timeval *tv,struct timezone *tz 166 | 165,sys_mount,char *dev_name,char *dir_name,char *type,unsigned long flags,void *data 167 | 166,sys_umount2,const char *target,int flags 168 | 167,sys_swapon,const char *specialfile,int swap_flags 169 | 168,sys_swapoff,const char *specialfile 170 | 169,sys_reboot,int magic1,int magic2,unsigned int cmd,void *arg 171 | 170,sys_sethostname,char *name,int len 172 | 171,sys_setdomainname,char *name,int len 173 | 172,sys_iopl,unsigned int level,struct pt_regs *regs 174 | 173,sys_ioperm,unsigned long from,unsigned long num,int turn_on 175 | 175,sys_init_module,void *umod,unsigned long len,const char *uargs 176 | 176,sys_delete_module,const chat *name_user,unsigned int flags 177 | 179,sys_quotactl,unsigned int cmd,const char *special,qid_t id,void *addr 178 | 180,sys_nfsservctl,NOT IMPLEMENTED 179 | 181,sys_getpmsg,NOT IMPLEMENTED 180 | 182,sys_putpmsg,NOT IMPLEMENTED 181 | 183,sys_afs_syscall,NOT IMPLEMENTED 182 | 184,sys_tuxcall,NOT IMPLEMENTED 183 | 185,sys_security,NOT IMPLEMENTED 184 | 186,sys_gettid 185 | 187,sys_readahead,int fd,loff_t offset,size_t count 186 | 188,sys_setxattr,const char *pathname,const char *name,const void *value,size_t size,int flags 187 | 189,sys_lsetxattr,const char *pathname,const char *name,const void *value,size_t size,int flags 188 | 190,sys_fsetxattr,int fd,const char *name,const void *value,size_t size,int flags 189 | 191,sys_getxattr,const char *pathname,const char *name,void *value,size_t size 190 | 192,sys_lgetxattr,const char *pathname,const char *name,void *value,size_t size 191 | 193,sys_fgetxattr,int fd,const har *name,void *value,size_t size 192 | 194,sys_listxattr,const char *pathname,char *list,size_t size 193 | 195,sys_llistxattr,const char *pathname,char *list,size_t size 194 | 196,sys_flistxattr,int fd,char *list,size_t size 195 | 197,sys_removexattr,const char *pathname,const char *name 196 | 198,sys_lremovexattr,const char *pathname,const char *name 197 | 199,sys_fremovexattr,int fd,const char *name 198 | 200,sys_tkill,pid_t pid,ing sig 199 | 201,sys_time,time_t *tloc 200 | 202,sys_futex,u32 *uaddr,int op,u32 val,struct timespec *utime,u32 *uaddr2,u32 val3 201 | 203,sys_sched_setaffinity,pid_t pid,unsigned int len,unsigned long *user_mask_ptr 202 | 204,sys_sched_getaffinity,pid_t pid,unsigned int len,unsigned long *user_mask_ptr 203 | 205,sys_set_thread_area,NOT IMPLEMENTED. Use arch_prctl 204 | 206,sys_io_setup,unsigned nr_events,aio_context_t *ctxp 205 | 207,sys_io_destroy,aio_context_t ctx 206 | 208,sys_io_getevents,aio_context_t ctx_id,long min_nr,long nr,struct io_event *events 207 | 209,sys_io_submit,aio_context_t ctx_id,long nr,struct iocb **iocbpp 208 | 210,sys_io_cancel,aio_context_t ctx_id,struct iocb *iocb,struct io_event *result 209 | 211,sys_get_thread_area,NOT IMPLEMENTED. Use arch_prctl 210 | 212,sys_lookup_dcookie,u64 cookie64,long buf,long len 211 | 213,sys_epoll_create,int size 212 | 214,sys_epoll_ctl_old,NOT IMPLEMENTED 213 | 215,sys_epoll_wait_old,NOT IMPLEMENTED 214 | 216,sys_remap_file_pages,unsigned long start,unsigned long size,unsigned long prot,unsigned long pgoff,unsigned long flags 215 | 217,sys_getdents64,unsigned int fd,struct linux_dirent64 *dirent,unsigned int count 216 | 218,sys_set_tid_address,int *tidptr 217 | 219,sys_restart_syscall 218 | 220,sys_semtimedop,int semid,struct sembuf *tsops,unsigned nsops,const struct timespec *timeout 219 | 221,sys_fadvise64,int fd,loff_t offset,size_t len,int advice 220 | 222,sys_timer_create,const clockid_t which_clock,struct sigevent *timer_event_spec,timer_t *created_timer_id 221 | 223,sys_timer_settime,timer_t timer_id,int flags,const struct itimerspec *new_setting,struct itimerspec *old_setting 222 | 224,sys_timer_gettime,timer_t timer_id,struct itimerspec *setting 223 | 225,sys_timer_getoverrun,timer_t timer_id 224 | 226,sys_timer_delete,timer_t timer_id 225 | 227,sys_clock_settime,const clockid_t which_clock,const struct timespec *tp 226 | 228,sys_clock_gettime,const clockid_t which_clock,struct timespec *tp 227 | 229,sys_clock_getres,const clockid_t which_clock,struct timespec *tp 228 | 230,sys_clock_nanosleep,const clockid_t which_clock,int flags,const struct timespec *rqtp,struct timespec *rmtp 229 | 231,sys_exit_group,int error_code 230 | 232,sys_epoll_wait,int epfd,struct epoll_event *events,int maxevents,int timeout 231 | 233,sys_epoll_ctl,int epfd,int op,int fd,struct epoll_event *event 232 | 234,sys_tgkill,pid_t tgid,pid_t pid,int sig 233 | 235,sys_utimes,char *filename,struct timeval *utimes 234 | 236,sys_vserver,NOT IMPLEMENTED 235 | 237,sys_mbind,unsigned long start,unsigned long len,unsigned long mode,unsigned long *nmask,unsigned long maxnode,unsigned flags 236 | 238,sys_set_mempolicy,int mode,unsigned long *nmask,unsigned long maxnode 237 | 239,sys_get_mempolicy,int *policy,unsigned long *nmask,unsigned long maxnode,unsigned long addr,unsigned long flags 238 | 240,sys_mq_open,const char *u_name,int oflag,mode_t mode,struct mq_attr *u_attr 239 | 241,sys_mq_unlink,const char *u_name 240 | 242,sys_mq_timedsend,mqd_t mqdes,const char *u_msg_ptr,size_t msg_len,unsigned int msg_prio,const stuct timespec *u_abs_timeout 241 | 243,sys_mq_timedreceive,mqd_t mqdes,char *u_msg_ptr,size_t msg_len,unsigned int *u_msg_prio,const struct timespec *u_abs_timeout 242 | 244,sys_mq_notify,mqd_t mqdes,const struct sigevent *u_notification 243 | 245,sys_mq_getsetattr,mqd_t mqdes,const struct mq_attr *u_mqstat,struct mq_attr *u_omqstat 244 | 246,sys_kexec_load,unsigned long entry,unsigned long nr_segments,struct kexec_segment *segments,unsigned long flags 245 | 247,sys_waitid,int which,pid_t upid,struct siginfo *infop,int options,struct rusage *ru 246 | 248,sys_add_key,const char *_type,const char *_description,const void *_payload,size_t plen 247 | 249,sys_request_key,const char *_type,const char *_description,const char *_callout_info,key_serial_t destringid 248 | 250,sys_keyctl,int option,unsigned long arg2,unsigned long arg3,unsigned long arg4,unsigned long arg5 249 | 251,sys_ioprio_set,int which,int who,int ioprio 250 | 252,sys_ioprio_get,int which,int who 251 | 253,sys_inotify_init 252 | 254,sys_inotify_add_watch,int fd,const char *pathname,u32 mask 253 | 255,sys_inotify_rm_watch,int fd,__s32 wd 254 | 256,sys_migrate_pages,pid_t pid,unsigned long maxnode,const unsigned long *old_nodes,const unsigned long *new_nodes 255 | 257,sys_openat,int dfd,const char *filename,int flags,int mode 256 | 258,sys_mkdirat,int dfd,const char *pathname,int mode 257 | 259,sys_mknodat,int dfd,const char *filename,int mode,unsigned dev 258 | 260,sys_fchownat,int dfd,const char *filename,uid_t user,gid_t group,int flag 259 | 261,sys_futimesat,int dfd,const char *filename,struct timeval *utimes 260 | 262,sys_newfstatat,int dfd,const char *filename,struct stat *statbuf,int flag 261 | 263,sys_unlinkat,int dfd,const char *pathname,int flag 262 | 264,sys_renameat,int oldfd,const char *oldname,int newfd,const char *newname 263 | 265,sys_linkat,int oldfd,const char *oldname,int newfd,const char *newname,int flags 264 | 266,sys_symlinkat,const char *oldname,int newfd,const char *newname 265 | 267,sys_readlinkat,int dfd,const char *pathname,char *buf,int bufsiz 266 | 268,sys_fchmodat,int dfd,const char *filename,mode_t mode 267 | 269,sys_faccessat,int dfd,const char *filename,int mode 268 | 270,sys_pselect6,int n,fd_set *inp,fd_set *outp,fd_set *exp,struct timespec *tsp,void *sig 269 | 271,sys_ppoll,struct pollfd *ufds,unsigned int nfds,struct timespec *tsp,const sigset_t *sigmask,size_t sigsetsize 270 | 272,sys_unshare,unsigned long unshare_flags 271 | 273,sys_set_robust_list,struct robust_list_head *head,size_t len 272 | 274,sys_get_robust_list,int pid,struct robust_list_head **head_ptr,size_t *len_ptr 273 | 275,sys_splice,int fd_in,loff_t *off_in,int fd_out,loff_t *off_out,size_t len,unsigned int flags 274 | 276,sys_tee,int fdin,int fdout,size_t len,unsigned int flags 275 | 277,sys_sync_file_range,long fd,loff_t offset,loff_t bytes,long flags 276 | 278,sys_vmsplice,int fd,const struct iovec *iov,unsigned long nr_segs,unsigned int flags 277 | 279,sys_move_pages,pid_t pid,unsigned long nr_pages,const void **pages,const int *nodes,int *status,int flags 278 | 280,sys_utimensat,int dfd,const char *filename,struct timespec *utimes,int flags 279 | 281,sys_epoll_pwait,int epfd,struct epoll_event *events,int maxevents,int timeout,const sigset_t *sigmask,size_t sigsetsize 280 | 282,sys_signalfd,int ufd,sigset_t *user_mask,size_t sizemask 281 | 283,sys_timerfd_create,int clockid,int flags 282 | 284,sys_eventfd,unsigned int count 283 | 285,sys_fallocate,long fd,long mode,loff_t offset,loff_t len 284 | 286,sys_timerfd_settime,int ufd,int flags,const struct itimerspec *utmr,struct itimerspec *otmr 285 | 287,sys_timerfd_gettime,int ufd,struct itimerspec *otmr 286 | 288,sys_accept4,int fd,struct sockaddr *upeer_sockaddr,int *upeer_addrlen,int flags 287 | 289,sys_signalfd4,int ufd,sigset_t *user_mask,size_t sizemask,int flags 288 | 290,sys_eventfd2,unsigned int count,int flags 289 | 291,sys_epoll_create1,int flags 290 | 292,sys_dup3,unsigned int oldfd,unsigned int newfd,int flags 291 | 293,sys_pipe2,int *filedes,int flags 292 | 294,sys_inotify_init1,int flags 293 | 295,sys_preadv,unsigned long fd,const struct iovec *vec,unsigned long vlen,unsigned long pos_l,unsigned long pos_h 294 | 296,sys_pwritev,unsigned long fd,const struct iovec *vec,unsigned long vlen,unsigned long pos_l,unsigned long pos_h 295 | 297,sys_rt_tgsigqueueinfo,pid_t tgid,pid_t pid,int sig,siginfo_t *uinfo 296 | 298,sys_perf_event_open,struct perf_event_attr *attr_uptr,pid_t pid,int cpu,int group_fd,unsigned long flags 297 | 299,sys_recvmmsg,int fd,struct msghdr *mmsg,unsigned int vlen,unsigned int flags,struct timespec *timeout 298 | 300,sys_fanotify_init,unsigned int flags,unsigned int event_f_flags 299 | 301,sys_fanotify_mark,long fanotify_fd,long flags,__u64 mask,long dfd,long pathname 300 | 302,sys_prlimit64,pid_t pid,unsigned int resource,const struct rlimit64 *new_rlim,struct rlimit64 *old_rlim 301 | 303,sys_name_to_handle_at,int dfd,const char *name,struct file_handle *handle,int *mnt_id,int flag 302 | 304,sys_open_by_handle_at,int dfd,const char *name,struct file_handle *handle,int *mnt_id,int flags 303 | 305,sys_clock_adjtime,clockid_t which_clock,struct timex *tx 304 | 306,sys_syncfs,int fd 305 | 307,sys_sendmmsg,int fd,struct mmsghdr *mmsg,unsigned int vlen,unsigned int flags 306 | 308,sys_setns,int fd,int nstype 307 | 309,sys_getcpu,unsigned *cpup,unsigned *nodep,struct getcpu_cache *unused 308 | 310,sys_process_vm_readv,pid_t pid,const struct iovec *lvec,unsigned long liovcnt,const struct iovec *rvec,unsigned long riovcnt,unsigned long flags 309 | 311,sys_process_vm_writev,pid_t pid,const struct iovec *lvec,unsigned long liovcnt,const struct iovcc *rvec,unsigned long riovcnt,unsigned long flags 310 | 312,sys_kcmp,pid_t pid1,pid_t pid2,int type,unsigned long idx1,unsigned long idx2 311 | 313,sys_finit_module,int fd,const char __user *uargs,int flags 312 | 314,sys_sched_setattr,pid_t pid,struct sched_attr __user *attr,unsigned int flags 313 | 315,sys_sched_getattr,pid_t pid,struct sched_attr __user *attr,unsigned int size,unsigned int flags 314 | 316,sys_renameat2,int olddfd,const char __user *oldname,int newdfd,const char __user *newname,unsigned int flags 315 | 317,sys_seccomp,unsigned int op,unsigned int flags,const char __user *uargs 316 | 318,sys_getrandom,char __user *buf,size_t count,unsigned int flags 317 | 319,sys_memfd_create,const char __user *uname_ptr,unsigned int flags 318 | 320,sys_kexec_file_load,int kernel_fd,int initrd_fd,unsigned long cmdline_len,const char __user *cmdline_ptr,unsigned long flags 319 | 321,sys_bpf,int cmd,union bpf_attr *attr,unsigned int size 320 | 322,stub_execveat,int dfd,const char __user *filename,const char __user *const __user *argv,const char __user *const __user *envp,int flags 321 | 323,userfaultfd,int flags 322 | 324,membarrier,int cmd,int flags 323 | 325,mlock2,unsigned long start,size_t len,int flags 324 | 326,copy_file_range,int fd_in,loff_t __user *off_in,int fd_out,loff_t __user * off_out,size_t len,unsigned int flags 325 | 327,preadv2,unsigned long fd,const struct iovec __user *vec,unsigned long vlen,unsigned long pos_l,unsigned long pos_h,int flags 326 | 328,pwritev2,unsigned long fd,const struct iovec __user *vec,unsigned long vlen,unsigned long pos_l,unsigned long pos_h,int flags 327 | 329,pkey_mprotect 328 | 330,pkey_alloc 329 | 331,pkey_free 330 | 332,statx 331 | 333,io_pgetevents 332 | 334,rseq 333 | 335,pkey_mprotect 334 | -------------------------------------------------------------------------------- /src/syscalls.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lazy_static::lazy_static; 4 | use regex::Regex; 5 | use simple_error::SimpleError; 6 | 7 | /// A single syscall parameter 8 | #[derive(Debug)] 9 | pub struct SyscallEntry { 10 | pub field_type: String, 11 | pub is_string: bool, 12 | pub is_pointer: bool, 13 | pub field_name: String, 14 | pub is_array: bool, 15 | } 16 | 17 | impl SyscallEntry { 18 | /// Parse a syscall parameter from a string-based definition 19 | pub fn new(syscall_param: &str) -> Self { 20 | // Match with everything before the identifier, then the identifier 21 | // type 0+ * identifier optional [] 22 | let re = Regex::new(r"^(.*?) (\**)([a-zA-Z0-9_-]*)(\[\])?$").unwrap(); 23 | 24 | if let Some(out) = re.captures(syscall_param) { 25 | let out = SyscallEntry { 26 | field_type: out.get(1).unwrap().as_str().to_string(), 27 | is_string: out.get(1).unwrap().as_str().contains("char"), 28 | is_pointer: out.get(2).unwrap().as_str().contains('*'), 29 | field_name: out.get(3).unwrap().as_str().to_string(), 30 | is_array: match &out.get(4) { 31 | Some(a) => a.as_str() == "[]", 32 | None => false, 33 | }, 34 | }; 35 | 36 | out 37 | } else { 38 | panic!("Could not parse syscall parameter: {}", syscall_param); 39 | } 40 | } 41 | } 42 | 43 | /// Defines a syscall. 44 | /// 45 | /// This is populated from the `syscalls.csv` file, which is loaded at compile- 46 | /// time. That file, in turn, is based on Ryan Chapman's blog: 47 | /// 48 | /// https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ 49 | #[derive(Debug)] 50 | pub struct Syscall { 51 | pub name: String, 52 | pub rdi: Option, 53 | pub rsi: Option, 54 | pub rdx: Option, 55 | pub r10: Option, 56 | pub r8: Option, 57 | pub r9: Option, 58 | } 59 | 60 | lazy_static! { 61 | /// Enumerations comment 62 | pub static ref SYSCALLS: HashMap = { 63 | let mut out: HashMap = HashMap::new(); 64 | 65 | let mut rdr = csv::ReaderBuilder::new() 66 | .has_headers(false) 67 | .flexible(true) 68 | .from_reader(include_str!("./syscalls.csv").as_bytes()); 69 | 70 | for result in rdr.records() { 71 | let record = result.map_err(|e| { 72 | SimpleError::new(format!("Couldn't read CSV: {}", e)) 73 | }).unwrap(); 74 | 75 | let rax: u64 = record.get(0).ok_or( 76 | SimpleError::new("Error reading the CSV file") 77 | ).unwrap().parse().map_err(|e| { 78 | SimpleError::new(format!("Couldn't parse first CSV field as integer: {}", e)) 79 | }).unwrap(); 80 | 81 | if out.contains_key(&rax) { 82 | panic!("Duplicate key in syscall CSV: {}", rax); 83 | } 84 | 85 | let syscall = Syscall { 86 | name: record.get(1).unwrap().to_string(), 87 | rdi: record.get(2).map(|r| SyscallEntry::new(r)), 88 | rsi: record.get(3).map(|r| SyscallEntry::new(r)), 89 | rdx: record.get(4).map(|r| SyscallEntry::new(r)), 90 | r10: record.get(5).map(|r| SyscallEntry::new(r)), 91 | r8: record.get(6).map(|r| SyscallEntry::new(r)), 92 | r9: record.get(7).map(|r| SyscallEntry::new(r)), 93 | }; 94 | 95 | out.insert(rax, syscall); 96 | } 97 | 98 | out 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /src/visibility_configuration.rs: -------------------------------------------------------------------------------- 1 | //! A data structure to configure address visibility. 2 | //! 3 | //! Basically, we need a way to show/hide different addresses, otherwise we get 4 | //! potentially overwhelmed in libc and stuff. So we can either show or hide 5 | //! certain addresses. 6 | //! 7 | //! When the harness loads code, it always loads it to `0x13370000`, so we 8 | //! added a convenience function to always set that address. 9 | //! 10 | //! When an ELF is executed, by default everything is shown, but we implement 11 | //! `clap` parsers so the user can pass in whatever they like on the 12 | //! commandline. 13 | 14 | use clap::Parser; 15 | use clap_num::maybe_hex; 16 | 17 | const DEFAULT_MASK: u64 = 0xFFFFFFFFFFFF0000; 18 | 19 | #[derive(Parser, Debug)] 20 | pub struct VisibilityConfiguration { 21 | /// Hide instructions that match this address (ANDed with the --hidden-mask) 22 | #[clap(long, parse(try_from_str=maybe_hex))] 23 | hidden_address: Option, 24 | 25 | /// ANDed with the --hidden-address before comparing - by default, 0xFFFFFFFFFFFF0000 26 | #[clap(long, parse(try_from_str=maybe_hex))] 27 | hidden_mask: Option, 28 | 29 | /// Only show instructions that match this address (ANDed with the --visible-mask) 30 | #[clap(long, parse(try_from_str=maybe_hex))] 31 | visible_address: Option, 32 | 33 | /// ANDed with the --visible-address before comparing - by default, 0xFFFFFFFFFFFF0000 34 | #[clap(long, parse(try_from_str=maybe_hex))] 35 | visible_mask: Option, 36 | } 37 | 38 | impl VisibilityConfiguration { 39 | /// Visibility settings when using the harness 40 | /// 41 | /// The harness always loads code to `0x13370000`. 42 | pub fn harness_visibility() -> Self { 43 | Self { 44 | hidden_address: None, 45 | hidden_mask: None, 46 | visible_address: Some(0x13370000), 47 | visible_mask: Some(0xFFFF0000), 48 | } 49 | } 50 | 51 | /// Settings where everything is visible 52 | pub fn full_visibility() -> Self { 53 | Self { 54 | hidden_address: None, 55 | hidden_mask: None, 56 | visible_address: None, 57 | visible_mask: None, 58 | } 59 | } 60 | 61 | pub fn is_visible(&self, address: u64) -> bool { 62 | // Suppress addresses that match the hidden_address / hidden_mask, if set 63 | if let Some(hidden_address) = self.hidden_address { 64 | let mask = self.hidden_mask.unwrap_or(DEFAULT_MASK); 65 | 66 | if (address & mask) == hidden_address { 67 | return false; 68 | } 69 | } 70 | 71 | // Suppress addresses that don't match the visible_address / visible_mask 72 | if let Some(visible_address) = self.visible_address { 73 | let mask = self.visible_mask.unwrap_or(DEFAULT_MASK); 74 | 75 | if (address & mask) != visible_address { 76 | return false; 77 | } 78 | } 79 | 80 | true 81 | } 82 | } 83 | --------------------------------------------------------------------------------