├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── engine.rs ├── fs_monitor.rs ├── fs_scan.rs ├── main.rs └── report.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v2 17 | 18 | - name: Install stable toolchain with clippy available 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | components: clippy 25 | 26 | - name: Run clippy 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: clippy 30 | args: -- -Dwarnings 31 | 32 | - name: Run tests 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /yara-rules 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "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 0.3.9", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bindgen" 33 | version = "0.60.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" 36 | dependencies = [ 37 | "bitflags", 38 | "cexpr", 39 | "clang-sys", 40 | "env_logger 0.9.0", 41 | "lazy_static", 42 | "lazycell", 43 | "log", 44 | "peeking_take_while", 45 | "proc-macro2", 46 | "quote", 47 | "regex", 48 | "rustc-hash", 49 | "shlex", 50 | "which", 51 | ] 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "1.3.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 58 | 59 | [[package]] 60 | name = "bstr" 61 | version = "0.2.17" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 64 | dependencies = [ 65 | "memchr", 66 | ] 67 | 68 | [[package]] 69 | name = "cc" 70 | version = "1.0.73" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 73 | 74 | [[package]] 75 | name = "cexpr" 76 | version = "0.6.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 79 | dependencies = [ 80 | "nom", 81 | ] 82 | 83 | [[package]] 84 | name = "cfg-if" 85 | version = "0.1.10" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 88 | 89 | [[package]] 90 | name = "cfg-if" 91 | version = "1.0.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 94 | 95 | [[package]] 96 | name = "clang-sys" 97 | version = "1.3.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" 100 | dependencies = [ 101 | "glob", 102 | "libc", 103 | "libloading", 104 | ] 105 | 106 | [[package]] 107 | name = "clap" 108 | version = "3.2.17" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" 111 | dependencies = [ 112 | "atty", 113 | "bitflags", 114 | "clap_derive", 115 | "clap_lex", 116 | "indexmap", 117 | "once_cell", 118 | "strsim", 119 | "termcolor", 120 | "textwrap", 121 | ] 122 | 123 | [[package]] 124 | name = "clap_derive" 125 | version = "3.2.17" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" 128 | dependencies = [ 129 | "heck", 130 | "proc-macro-error", 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "clap_lex" 138 | version = "0.2.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 141 | dependencies = [ 142 | "os_str_bytes", 143 | ] 144 | 145 | [[package]] 146 | name = "crossbeam-utils" 147 | version = "0.8.11" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" 150 | dependencies = [ 151 | "cfg-if 1.0.0", 152 | "once_cell", 153 | ] 154 | 155 | [[package]] 156 | name = "either" 157 | version = "1.7.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" 160 | 161 | [[package]] 162 | name = "env_logger" 163 | version = "0.7.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 166 | dependencies = [ 167 | "atty", 168 | "humantime 1.3.0", 169 | "log", 170 | "regex", 171 | "termcolor", 172 | ] 173 | 174 | [[package]] 175 | name = "env_logger" 176 | version = "0.9.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 179 | dependencies = [ 180 | "atty", 181 | "humantime 2.1.0", 182 | "log", 183 | "regex", 184 | "termcolor", 185 | ] 186 | 187 | [[package]] 188 | name = "filetime" 189 | version = "0.2.17" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" 192 | dependencies = [ 193 | "cfg-if 1.0.0", 194 | "libc", 195 | "redox_syscall", 196 | "windows-sys", 197 | ] 198 | 199 | [[package]] 200 | name = "fnv" 201 | version = "1.0.7" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 204 | 205 | [[package]] 206 | name = "fs_extra" 207 | version = "1.2.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" 210 | 211 | [[package]] 212 | name = "fsevent" 213 | version = "0.4.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 216 | dependencies = [ 217 | "bitflags", 218 | "fsevent-sys", 219 | ] 220 | 221 | [[package]] 222 | name = "fsevent-sys" 223 | version = "2.0.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 226 | dependencies = [ 227 | "libc", 228 | ] 229 | 230 | [[package]] 231 | name = "fuchsia-zircon" 232 | version = "0.3.3" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 235 | dependencies = [ 236 | "bitflags", 237 | "fuchsia-zircon-sys", 238 | ] 239 | 240 | [[package]] 241 | name = "fuchsia-zircon-sys" 242 | version = "0.3.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 245 | 246 | [[package]] 247 | name = "glob" 248 | version = "0.3.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 251 | 252 | [[package]] 253 | name = "globset" 254 | version = "0.4.9" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" 257 | dependencies = [ 258 | "aho-corasick", 259 | "bstr", 260 | "fnv", 261 | "log", 262 | "regex", 263 | ] 264 | 265 | [[package]] 266 | name = "globwalk" 267 | version = "0.8.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" 270 | dependencies = [ 271 | "bitflags", 272 | "ignore", 273 | "walkdir", 274 | ] 275 | 276 | [[package]] 277 | name = "hashbrown" 278 | version = "0.12.3" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 281 | 282 | [[package]] 283 | name = "heck" 284 | version = "0.4.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 287 | 288 | [[package]] 289 | name = "hermit-abi" 290 | version = "0.1.19" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 293 | dependencies = [ 294 | "libc", 295 | ] 296 | 297 | [[package]] 298 | name = "humantime" 299 | version = "1.3.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 302 | dependencies = [ 303 | "quick-error", 304 | ] 305 | 306 | [[package]] 307 | name = "humantime" 308 | version = "2.1.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 311 | 312 | [[package]] 313 | name = "ignore" 314 | version = "0.4.18" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" 317 | dependencies = [ 318 | "crossbeam-utils", 319 | "globset", 320 | "lazy_static", 321 | "log", 322 | "memchr", 323 | "regex", 324 | "same-file", 325 | "thread_local", 326 | "walkdir", 327 | "winapi-util", 328 | ] 329 | 330 | [[package]] 331 | name = "indexmap" 332 | version = "1.9.1" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 335 | dependencies = [ 336 | "autocfg", 337 | "hashbrown", 338 | ] 339 | 340 | [[package]] 341 | name = "inotify" 342 | version = "0.7.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 345 | dependencies = [ 346 | "bitflags", 347 | "inotify-sys", 348 | "libc", 349 | ] 350 | 351 | [[package]] 352 | name = "inotify-sys" 353 | version = "0.1.5" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 356 | dependencies = [ 357 | "libc", 358 | ] 359 | 360 | [[package]] 361 | name = "iovec" 362 | version = "0.1.4" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 365 | dependencies = [ 366 | "libc", 367 | ] 368 | 369 | [[package]] 370 | name = "itoa" 371 | version = "1.0.3" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 374 | 375 | [[package]] 376 | name = "kernel32-sys" 377 | version = "0.2.2" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 380 | dependencies = [ 381 | "winapi 0.2.8", 382 | "winapi-build", 383 | ] 384 | 385 | [[package]] 386 | name = "lazy_static" 387 | version = "1.4.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 390 | 391 | [[package]] 392 | name = "lazycell" 393 | version = "1.3.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 396 | 397 | [[package]] 398 | name = "libc" 399 | version = "0.2.132" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 402 | 403 | [[package]] 404 | name = "libloading" 405 | version = "0.7.3" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 408 | dependencies = [ 409 | "cfg-if 1.0.0", 410 | "winapi 0.3.9", 411 | ] 412 | 413 | [[package]] 414 | name = "log" 415 | version = "0.4.17" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 418 | dependencies = [ 419 | "cfg-if 1.0.0", 420 | ] 421 | 422 | [[package]] 423 | name = "memchr" 424 | version = "2.5.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 427 | 428 | [[package]] 429 | name = "minimal-lexical" 430 | version = "0.2.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 433 | 434 | [[package]] 435 | name = "mio" 436 | version = "0.6.23" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 439 | dependencies = [ 440 | "cfg-if 0.1.10", 441 | "fuchsia-zircon", 442 | "fuchsia-zircon-sys", 443 | "iovec", 444 | "kernel32-sys", 445 | "libc", 446 | "log", 447 | "miow", 448 | "net2", 449 | "slab", 450 | "winapi 0.2.8", 451 | ] 452 | 453 | [[package]] 454 | name = "mio-extras" 455 | version = "2.0.6" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 458 | dependencies = [ 459 | "lazycell", 460 | "log", 461 | "mio", 462 | "slab", 463 | ] 464 | 465 | [[package]] 466 | name = "miow" 467 | version = "0.2.2" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 470 | dependencies = [ 471 | "kernel32-sys", 472 | "net2", 473 | "winapi 0.2.8", 474 | "ws2_32-sys", 475 | ] 476 | 477 | [[package]] 478 | name = "net2" 479 | version = "0.2.37" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 482 | dependencies = [ 483 | "cfg-if 0.1.10", 484 | "libc", 485 | "winapi 0.3.9", 486 | ] 487 | 488 | [[package]] 489 | name = "nom" 490 | version = "7.1.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 493 | dependencies = [ 494 | "memchr", 495 | "minimal-lexical", 496 | ] 497 | 498 | [[package]] 499 | name = "notify" 500 | version = "4.0.17" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" 503 | dependencies = [ 504 | "bitflags", 505 | "filetime", 506 | "fsevent", 507 | "fsevent-sys", 508 | "inotify", 509 | "libc", 510 | "mio", 511 | "mio-extras", 512 | "walkdir", 513 | "winapi 0.3.9", 514 | ] 515 | 516 | [[package]] 517 | name = "num_cpus" 518 | version = "1.13.1" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 521 | dependencies = [ 522 | "hermit-abi", 523 | "libc", 524 | ] 525 | 526 | [[package]] 527 | name = "once_cell" 528 | version = "1.13.1" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" 531 | 532 | [[package]] 533 | name = "os_str_bytes" 534 | version = "6.3.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 537 | 538 | [[package]] 539 | name = "peeking_take_while" 540 | version = "0.1.2" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 543 | 544 | [[package]] 545 | name = "pretty_env_logger" 546 | version = "0.4.0" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 549 | dependencies = [ 550 | "env_logger 0.7.1", 551 | "log", 552 | ] 553 | 554 | [[package]] 555 | name = "proc-macro-error" 556 | version = "1.0.4" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 559 | dependencies = [ 560 | "proc-macro-error-attr", 561 | "proc-macro2", 562 | "quote", 563 | "syn", 564 | "version_check", 565 | ] 566 | 567 | [[package]] 568 | name = "proc-macro-error-attr" 569 | version = "1.0.4" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 572 | dependencies = [ 573 | "proc-macro2", 574 | "quote", 575 | "version_check", 576 | ] 577 | 578 | [[package]] 579 | name = "proc-macro2" 580 | version = "1.0.43" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 583 | dependencies = [ 584 | "unicode-ident", 585 | ] 586 | 587 | [[package]] 588 | name = "quick-error" 589 | version = "1.2.3" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 592 | 593 | [[package]] 594 | name = "quote" 595 | version = "1.0.21" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 598 | dependencies = [ 599 | "proc-macro2", 600 | ] 601 | 602 | [[package]] 603 | name = "redox_syscall" 604 | version = "0.2.16" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 607 | dependencies = [ 608 | "bitflags", 609 | ] 610 | 611 | [[package]] 612 | name = "regex" 613 | version = "1.6.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 616 | dependencies = [ 617 | "aho-corasick", 618 | "memchr", 619 | "regex-syntax", 620 | ] 621 | 622 | [[package]] 623 | name = "regex-syntax" 624 | version = "0.6.27" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 627 | 628 | [[package]] 629 | name = "rustc-hash" 630 | version = "1.1.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 633 | 634 | [[package]] 635 | name = "ryu" 636 | version = "1.0.11" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 639 | 640 | [[package]] 641 | name = "same-file" 642 | version = "1.0.6" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 645 | dependencies = [ 646 | "winapi-util", 647 | ] 648 | 649 | [[package]] 650 | name = "sauron" 651 | version = "0.1.0" 652 | dependencies = [ 653 | "clap", 654 | "log", 655 | "notify", 656 | "num_cpus", 657 | "pretty_env_logger", 658 | "serde", 659 | "serde_json", 660 | "threadpool", 661 | "walkdir", 662 | "yara", 663 | "yara-sys", 664 | ] 665 | 666 | [[package]] 667 | name = "serde" 668 | version = "1.0.143" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" 671 | dependencies = [ 672 | "serde_derive", 673 | ] 674 | 675 | [[package]] 676 | name = "serde_derive" 677 | version = "1.0.143" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" 680 | dependencies = [ 681 | "proc-macro2", 682 | "quote", 683 | "syn", 684 | ] 685 | 686 | [[package]] 687 | name = "serde_json" 688 | version = "1.0.83" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" 691 | dependencies = [ 692 | "itoa", 693 | "ryu", 694 | "serde", 695 | ] 696 | 697 | [[package]] 698 | name = "shlex" 699 | version = "1.1.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 702 | 703 | [[package]] 704 | name = "slab" 705 | version = "0.4.7" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 708 | dependencies = [ 709 | "autocfg", 710 | ] 711 | 712 | [[package]] 713 | name = "strsim" 714 | version = "0.10.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 717 | 718 | [[package]] 719 | name = "syn" 720 | version = "1.0.99" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 723 | dependencies = [ 724 | "proc-macro2", 725 | "quote", 726 | "unicode-ident", 727 | ] 728 | 729 | [[package]] 730 | name = "termcolor" 731 | version = "1.1.3" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 734 | dependencies = [ 735 | "winapi-util", 736 | ] 737 | 738 | [[package]] 739 | name = "textwrap" 740 | version = "0.15.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 743 | 744 | [[package]] 745 | name = "thiserror" 746 | version = "1.0.32" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 749 | dependencies = [ 750 | "thiserror-impl", 751 | ] 752 | 753 | [[package]] 754 | name = "thiserror-impl" 755 | version = "1.0.32" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 758 | dependencies = [ 759 | "proc-macro2", 760 | "quote", 761 | "syn", 762 | ] 763 | 764 | [[package]] 765 | name = "thread_local" 766 | version = "1.1.4" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 769 | dependencies = [ 770 | "once_cell", 771 | ] 772 | 773 | [[package]] 774 | name = "threadpool" 775 | version = "1.8.1" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 778 | dependencies = [ 779 | "num_cpus", 780 | ] 781 | 782 | [[package]] 783 | name = "unicode-ident" 784 | version = "1.0.3" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 787 | 788 | [[package]] 789 | name = "version_check" 790 | version = "0.9.4" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 793 | 794 | [[package]] 795 | name = "walkdir" 796 | version = "2.3.2" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 799 | dependencies = [ 800 | "same-file", 801 | "winapi 0.3.9", 802 | "winapi-util", 803 | ] 804 | 805 | [[package]] 806 | name = "which" 807 | version = "4.2.5" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" 810 | dependencies = [ 811 | "either", 812 | "lazy_static", 813 | "libc", 814 | ] 815 | 816 | [[package]] 817 | name = "winapi" 818 | version = "0.2.8" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 821 | 822 | [[package]] 823 | name = "winapi" 824 | version = "0.3.9" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 827 | dependencies = [ 828 | "winapi-i686-pc-windows-gnu", 829 | "winapi-x86_64-pc-windows-gnu", 830 | ] 831 | 832 | [[package]] 833 | name = "winapi-build" 834 | version = "0.1.1" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 837 | 838 | [[package]] 839 | name = "winapi-i686-pc-windows-gnu" 840 | version = "0.4.0" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 843 | 844 | [[package]] 845 | name = "winapi-util" 846 | version = "0.1.5" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 849 | dependencies = [ 850 | "winapi 0.3.9", 851 | ] 852 | 853 | [[package]] 854 | name = "winapi-x86_64-pc-windows-gnu" 855 | version = "0.4.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 858 | 859 | [[package]] 860 | name = "windows-sys" 861 | version = "0.36.1" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 864 | dependencies = [ 865 | "windows_aarch64_msvc", 866 | "windows_i686_gnu", 867 | "windows_i686_msvc", 868 | "windows_x86_64_gnu", 869 | "windows_x86_64_msvc", 870 | ] 871 | 872 | [[package]] 873 | name = "windows_aarch64_msvc" 874 | version = "0.36.1" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 877 | 878 | [[package]] 879 | name = "windows_i686_gnu" 880 | version = "0.36.1" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 883 | 884 | [[package]] 885 | name = "windows_i686_msvc" 886 | version = "0.36.1" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 889 | 890 | [[package]] 891 | name = "windows_x86_64_gnu" 892 | version = "0.36.1" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 895 | 896 | [[package]] 897 | name = "windows_x86_64_msvc" 898 | version = "0.36.1" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 901 | 902 | [[package]] 903 | name = "ws2_32-sys" 904 | version = "0.2.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 907 | dependencies = [ 908 | "winapi 0.2.8", 909 | "winapi-build", 910 | ] 911 | 912 | [[package]] 913 | name = "yara" 914 | version = "0.15.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "d074589e5e73b199e7fb93da14c84df792f02bcc61dbd1acbd0b967e25d06a17" 917 | dependencies = [ 918 | "bitflags", 919 | "lazy_static", 920 | "thiserror", 921 | "yara-sys", 922 | ] 923 | 924 | [[package]] 925 | name = "yara-sys" 926 | version = "0.15.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "a098b42118ac2afb401ce097116239beba0259cabedf2b2794b9b9e04f2e3b88" 929 | dependencies = [ 930 | "bindgen", 931 | "cc", 932 | "fs_extra", 933 | "globwalk", 934 | ] 935 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sauron" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Minimalistic cross-platform filesystem monitor and malware scanner using YARA rules." 6 | authors = ["Simone Margaritelli "] 7 | license = "GPL-3.0" 8 | readme = "README.md" 9 | repository = "https://github.com/evilsocket/sauron" 10 | 11 | [dependencies] 12 | clap = {version = "3.2.17", features = ["derive"]} 13 | log = "0.4.17" 14 | notify = "4.0.17" 15 | num_cpus = "1.13.1" 16 | pretty_env_logger = "0.4.0" 17 | serde = { version = "1.0.143", features = ["derive"] } 18 | serde_json = "1.0.83" 19 | threadpool = "1.8.1" 20 | walkdir = "2.3.2" 21 | yara = { version = "0.15.0" } 22 | yara-sys = { version = "0.15.0", features = ["vendored"]} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | ========================== 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. <> 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this license 9 | document, but changing it is not allowed. 10 | 11 | ## Preamble 12 | 13 | The GNU General Public License is a free, copyleft license for software and other 14 | kinds of works. 15 | 16 | The licenses for most software and other practical works are designed to take away 17 | your freedom to share and change the works. By contrast, the GNU General Public 18 | License is intended to guarantee your freedom to share and change all versions of a 19 | program--to make sure it remains free software for all its users. We, the Free 20 | Software Foundation, use the GNU General Public License for most of our software; it 21 | applies also to any other work released this way by its authors. You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not price. Our General 25 | Public Licenses are designed to make sure that you have the freedom to distribute 26 | copies of free software (and charge for them if you wish), that you receive source 27 | code or can get it if you want it, that you can change the software or use pieces of 28 | it in new free programs, and that you know you can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you these rights or 31 | asking you to surrender the rights. Therefore, you have certain responsibilities if 32 | you distribute copies of the software, or if you modify it: responsibilities to 33 | respect the freedom of others. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a fee, 36 | you must pass on to the recipients the same freedoms that you received. You must make 37 | sure that they, too, receive or can get the source code. And you must show them these 38 | terms so they know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 41 | copyright on the software, and (2) offer you this License giving you legal permission 42 | to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains that there is 45 | no warranty for this free software. For both users' and authors' sake, the GPL 46 | requires that modified versions be marked as changed, so that their problems will not 47 | be attributed erroneously to authors of previous versions. 48 | 49 | Some devices are designed to deny users access to install or run modified versions of 50 | the software inside them, although the manufacturer can do so. This is fundamentally 51 | incompatible with the aim of protecting users' freedom to change the software. The 52 | systematic pattern of such abuse occurs in the area of products for individuals to 53 | use, which is precisely where it is most unacceptable. Therefore, we have designed 54 | this version of the GPL to prohibit the practice for those products. If such problems 55 | arise substantially in other domains, we stand ready to extend this provision to 56 | those domains in future versions of the GPL, as needed to protect the freedom of 57 | users. 58 | 59 | Finally, every program is threatened constantly by software patents. States should 60 | not allow patents to restrict development and use of software on general-purpose 61 | computers, but in those that do, we wish to avoid the special danger that patents 62 | applied to a free program could make it effectively proprietary. To prevent this, the 63 | GPL assures that patents cannot be used to render the program non-free. 64 | 65 | The precise terms and conditions for copying, distribution and modification follow. 66 | 67 | ## TERMS AND CONDITIONS 68 | 69 | ### 0. Definitions. 70 | 71 | “This License” refers to version 3 of the GNU General Public License. 72 | 73 | “Copyright” also means copyright-like laws that apply to other kinds of 74 | works, such as semiconductor masks. 75 | 76 | “The Program” refers to any copyrightable work licensed under this 77 | License. Each licensee is addressed as “you”. “Licensees” and 78 | “recipients” may be individuals or organizations. 79 | 80 | To “modify” a work means to copy from or adapt all or part of the work in 81 | a fashion requiring copyright permission, other than the making of an exact copy. The 82 | resulting work is called a “modified version” of the earlier work or a 83 | work “based on” the earlier work. 84 | 85 | A “covered work” means either the unmodified Program or a work based on 86 | the Program. 87 | 88 | To “propagate” a work means to do anything with it that, without 89 | permission, would make you directly or secondarily liable for infringement under 90 | applicable copyright law, except executing it on a computer or modifying a private 91 | copy. Propagation includes copying, distribution (with or without modification), 92 | making available to the public, and in some countries other activities as well. 93 | 94 | To “convey” a work means any kind of propagation that enables other 95 | parties to make or receive copies. Mere interaction with a user through a computer 96 | network, with no transfer of a copy, is not conveying. 97 | 98 | An interactive user interface displays “Appropriate Legal Notices” to the 99 | extent that it includes a convenient and prominently visible feature that (1) 100 | displays an appropriate copyright notice, and (2) tells the user that there is no 101 | warranty for the work (except to the extent that warranties are provided), that 102 | licensees may convey the work under this License, and how to view a copy of this 103 | License. If the interface presents a list of user commands or options, such as a 104 | menu, a prominent item in the list meets this criterion. 105 | 106 | ### 1. Source Code. 107 | 108 | The “source code” for a work means the preferred form of the work for 109 | making modifications to it. “Object code” means any non-source form of a 110 | work. 111 | 112 | A “Standard Interface” means an interface that either is an official 113 | standard defined by a recognized standards body, or, in the case of interfaces 114 | specified for a particular programming language, one that is widely used among 115 | developers working in that language. 116 | 117 | The “System Libraries” of an executable work include anything, other than 118 | the work as a whole, that (a) is included in the normal form of packaging a Major 119 | Component, but which is not part of that Major Component, and (b) serves only to 120 | enable use of the work with that Major Component, or to implement a Standard 121 | Interface for which an implementation is available to the public in source code form. 122 | A “Major Component”, in this context, means a major essential component 123 | (kernel, window system, and so on) of the specific operating system (if any) on which 124 | the executable work runs, or a compiler used to produce the work, or an object code 125 | interpreter used to run it. 126 | 127 | The “Corresponding Source” for a work in object code form means all the 128 | source code needed to generate, install, and (for an executable work) run the object 129 | code and to modify the work, including scripts to control those activities. However, 130 | it does not include the work's System Libraries, or general-purpose tools or 131 | generally available free programs which are used unmodified in performing those 132 | activities but which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for the work, and 134 | the source code for shared libraries and dynamically linked subprograms that the work 135 | is specifically designed to require, such as by intimate data communication or 136 | control flow between those subprograms and other parts of the work. 137 | 138 | The Corresponding Source need not include anything that users can regenerate 139 | automatically from other parts of the Corresponding Source. 140 | 141 | The Corresponding Source for a work in source code form is that same work. 142 | 143 | ### 2. Basic Permissions. 144 | 145 | All rights granted under this License are granted for the term of copyright on the 146 | Program, and are irrevocable provided the stated conditions are met. This License 147 | explicitly affirms your unlimited permission to run the unmodified Program. The 148 | output from running a covered work is covered by this License only if the output, 149 | given its content, constitutes a covered work. This License acknowledges your rights 150 | of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not convey, without 153 | conditions so long as your license otherwise remains in force. You may convey covered 154 | works to others for the sole purpose of having them make modifications exclusively 155 | for you, or provide you with facilities for running those works, provided that you 156 | comply with the terms of this License in conveying all material for which you do not 157 | control copyright. Those thus making or running the covered works for you must do so 158 | exclusively on your behalf, under your direction and control, on terms that prohibit 159 | them from making any copies of your copyrighted material outside their relationship 160 | with you. 161 | 162 | Conveying under any other circumstances is permitted solely under the conditions 163 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 164 | 165 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 166 | 167 | No covered work shall be deemed part of an effective technological measure under any 168 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 169 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 170 | of such measures. 171 | 172 | When you convey a covered work, you waive any legal power to forbid circumvention of 173 | technological measures to the extent such circumvention is effected by exercising 174 | rights under this License with respect to the covered work, and you disclaim any 175 | intention to limit operation or modification of the work as a means of enforcing, 176 | against the work's users, your or third parties' legal rights to forbid circumvention 177 | of technological measures. 178 | 179 | ### 4. Conveying Verbatim Copies. 180 | 181 | You may convey verbatim copies of the Program's source code as you receive it, in any 182 | medium, provided that you conspicuously and appropriately publish on each copy an 183 | appropriate copyright notice; keep intact all notices stating that this License and 184 | any non-permissive terms added in accord with section 7 apply to the code; keep 185 | intact all notices of the absence of any warranty; and give all recipients a copy of 186 | this License along with the Program. 187 | 188 | You may charge any price or no price for each copy that you convey, and you may offer 189 | support or warranty protection for a fee. 190 | 191 | ### 5. Conveying Modified Source Versions. 192 | 193 | You may convey a work based on the Program, or the modifications to produce it from 194 | the Program, in the form of source code under the terms of section 4, provided that 195 | you also meet all of these conditions: 196 | 197 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 198 | relevant date. 199 | * **b)** The work must carry prominent notices stating that it is released under this 200 | License and any conditions added under section 7. This requirement modifies the 201 | requirement in section 4 to “keep intact all notices”. 202 | * **c)** You must license the entire work, as a whole, under this License to anyone who 203 | comes into possession of a copy. This License will therefore apply, along with any 204 | applicable section 7 additional terms, to the whole of the work, and all its parts, 205 | regardless of how they are packaged. This License gives no permission to license the 206 | work in any other way, but it does not invalidate such permission if you have 207 | separately received it. 208 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 209 | Notices; however, if the Program has interactive interfaces that do not display 210 | Appropriate Legal Notices, your work need not make them do so. 211 | 212 | A compilation of a covered work with other separate and independent works, which are 213 | not by their nature extensions of the covered work, and which are not combined with 214 | it such as to form a larger program, in or on a volume of a storage or distribution 215 | medium, is called an “aggregate” if the compilation and its resulting 216 | copyright are not used to limit the access or legal rights of the compilation's users 217 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 218 | does not cause this License to apply to the other parts of the aggregate. 219 | 220 | ### 6. Conveying Non-Source Forms. 221 | 222 | You may convey a covered work in object code form under the terms of sections 4 and 223 | 5, provided that you also convey the machine-readable Corresponding Source under the 224 | terms of this License, in one of these ways: 225 | 226 | * **a)** Convey the object code in, or embodied in, a physical product (including a 227 | physical distribution medium), accompanied by the Corresponding Source fixed on a 228 | durable physical medium customarily used for software interchange. 229 | * **b)** Convey the object code in, or embodied in, a physical product (including a 230 | physical distribution medium), accompanied by a written offer, valid for at least 231 | three years and valid for as long as you offer spare parts or customer support for 232 | that product model, to give anyone who possesses the object code either (1) a copy of 233 | the Corresponding Source for all the software in the product that is covered by this 234 | License, on a durable physical medium customarily used for software interchange, for 235 | a price no more than your reasonable cost of physically performing this conveying of 236 | source, or (2) access to copy the Corresponding Source from a network server at no 237 | charge. 238 | * **c)** Convey individual copies of the object code with a copy of the written offer to 239 | provide the Corresponding Source. This alternative is allowed only occasionally and 240 | noncommercially, and only if you received the object code with such an offer, in 241 | accord with subsection 6b. 242 | * **d)** Convey the object code by offering access from a designated place (gratis or for 243 | a charge), and offer equivalent access to the Corresponding Source in the same way 244 | through the same place at no further charge. You need not require recipients to copy 245 | the Corresponding Source along with the object code. If the place to copy the object 246 | code is a network server, the Corresponding Source may be on a different server 247 | (operated by you or a third party) that supports equivalent copying facilities, 248 | provided you maintain clear directions next to the object code saying where to find 249 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 250 | you remain obligated to ensure that it is available for as long as needed to satisfy 251 | these requirements. 252 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 253 | other peers where the object code and Corresponding Source of the work are being 254 | offered to the general public at no charge under subsection 6d. 255 | 256 | A separable portion of the object code, whose source code is excluded from the 257 | Corresponding Source as a System Library, need not be included in conveying the 258 | object code work. 259 | 260 | A “User Product” is either (1) a “consumer product”, which 261 | means any tangible personal property which is normally used for personal, family, or 262 | household purposes, or (2) anything designed or sold for incorporation into a 263 | dwelling. In determining whether a product is a consumer product, doubtful cases 264 | shall be resolved in favor of coverage. For a particular product received by a 265 | particular user, “normally used” refers to a typical or common use of 266 | that class of product, regardless of the status of the particular user or of the way 267 | in which the particular user actually uses, or expects or is expected to use, the 268 | product. A product is a consumer product regardless of whether the product has 269 | substantial commercial, industrial or non-consumer uses, unless such uses represent 270 | the only significant mode of use of the product. 271 | 272 | “Installation Information” for a User Product means any methods, 273 | procedures, authorization keys, or other information required to install and execute 274 | modified versions of a covered work in that User Product from a modified version of 275 | its Corresponding Source. The information must suffice to ensure that the continued 276 | functioning of the modified object code is in no case prevented or interfered with 277 | solely because modification has been made. 278 | 279 | If you convey an object code work under this section in, or with, or specifically for 280 | use in, a User Product, and the conveying occurs as part of a transaction in which 281 | the right of possession and use of the User Product is transferred to the recipient 282 | in perpetuity or for a fixed term (regardless of how the transaction is 283 | characterized), the Corresponding Source conveyed under this section must be 284 | accompanied by the Installation Information. But this requirement does not apply if 285 | neither you nor any third party retains the ability to install modified object code 286 | on the User Product (for example, the work has been installed in ROM). 287 | 288 | The requirement to provide Installation Information does not include a requirement to 289 | continue to provide support service, warranty, or updates for a work that has been 290 | modified or installed by the recipient, or for the User Product in which it has been 291 | modified or installed. Access to a network may be denied when the modification itself 292 | materially and adversely affects the operation of the network or violates the rules 293 | and protocols for communication across the network. 294 | 295 | Corresponding Source conveyed, and Installation Information provided, in accord with 296 | this section must be in a format that is publicly documented (and with an 297 | implementation available to the public in source code form), and must require no 298 | special password or key for unpacking, reading or copying. 299 | 300 | ### 7. Additional Terms. 301 | 302 | “Additional permissions” are terms that supplement the terms of this 303 | License by making exceptions from one or more of its conditions. Additional 304 | permissions that are applicable to the entire Program shall be treated as though they 305 | were included in this License, to the extent that they are valid under applicable 306 | law. If additional permissions apply only to part of the Program, that part may be 307 | used separately under those permissions, but the entire Program remains governed by 308 | this License without regard to the additional permissions. 309 | 310 | When you convey a copy of a covered work, you may at your option remove any 311 | additional permissions from that copy, or from any part of it. (Additional 312 | permissions may be written to require their own removal in certain cases when you 313 | modify the work.) You may place additional permissions on material, added by you to a 314 | covered work, for which you have or can give appropriate copyright permission. 315 | 316 | Notwithstanding any other provision of this License, for material you add to a 317 | covered work, you may (if authorized by the copyright holders of that material) 318 | supplement the terms of this License with terms: 319 | 320 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 321 | sections 15 and 16 of this License; or 322 | * **b)** Requiring preservation of specified reasonable legal notices or author 323 | attributions in that material or in the Appropriate Legal Notices displayed by works 324 | containing it; or 325 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 326 | modified versions of such material be marked in reasonable ways as different from the 327 | original version; or 328 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 329 | material; or 330 | * **e)** Declining to grant rights under trademark law for use of some trade names, 331 | trademarks, or service marks; or 332 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 333 | who conveys the material (or modified versions of it) with contractual assumptions of 334 | liability to the recipient, for any liability that these contractual assumptions 335 | directly impose on those licensors and authors. 336 | 337 | All other non-permissive additional terms are considered “further 338 | restrictions” within the meaning of section 10. If the Program as you received 339 | it, or any part of it, contains a notice stating that it is governed by this License 340 | along with a term that is a further restriction, you may remove that term. If a 341 | license document contains a further restriction but permits relicensing or conveying 342 | under this License, you may add to a covered work material governed by the terms of 343 | that license document, provided that the further restriction does not survive such 344 | relicensing or conveying. 345 | 346 | If you add terms to a covered work in accord with this section, you must place, in 347 | the relevant source files, a statement of the additional terms that apply to those 348 | files, or a notice indicating where to find the applicable terms. 349 | 350 | Additional terms, permissive or non-permissive, may be stated in the form of a 351 | separately written license, or stated as exceptions; the above requirements apply 352 | either way. 353 | 354 | ### 8. Termination. 355 | 356 | You may not propagate or modify a covered work except as expressly provided under 357 | this License. Any attempt otherwise to propagate or modify it is void, and will 358 | automatically terminate your rights under this License (including any patent licenses 359 | granted under the third paragraph of section 11). 360 | 361 | However, if you cease all violation of this License, then your license from a 362 | particular copyright holder is reinstated (a) provisionally, unless and until the 363 | copyright holder explicitly and finally terminates your license, and (b) permanently, 364 | if the copyright holder fails to notify you of the violation by some reasonable means 365 | prior to 60 days after the cessation. 366 | 367 | Moreover, your license from a particular copyright holder is reinstated permanently 368 | if the copyright holder notifies you of the violation by some reasonable means, this 369 | is the first time you have received notice of violation of this License (for any 370 | work) from that copyright holder, and you cure the violation prior to 30 days after 371 | your receipt of the notice. 372 | 373 | Termination of your rights under this section does not terminate the licenses of 374 | parties who have received copies or rights from you under this License. If your 375 | rights have been terminated and not permanently reinstated, you do not qualify to 376 | receive new licenses for the same material under section 10. 377 | 378 | ### 9. Acceptance Not Required for Having Copies. 379 | 380 | You are not required to accept this License in order to receive or run a copy of the 381 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 382 | using peer-to-peer transmission to receive a copy likewise does not require 383 | acceptance. However, nothing other than this License grants you permission to 384 | propagate or modify any covered work. These actions infringe copyright if you do not 385 | accept this License. Therefore, by modifying or propagating a covered work, you 386 | indicate your acceptance of this License to do so. 387 | 388 | ### 10. Automatic Licensing of Downstream Recipients. 389 | 390 | Each time you convey a covered work, the recipient automatically receives a license 391 | from the original licensors, to run, modify and propagate that work, subject to this 392 | License. You are not responsible for enforcing compliance by third parties with this 393 | License. 394 | 395 | An “entity transaction” is a transaction transferring control of an 396 | organization, or substantially all assets of one, or subdividing an organization, or 397 | merging organizations. If propagation of a covered work results from an entity 398 | transaction, each party to that transaction who receives a copy of the work also 399 | receives whatever licenses to the work the party's predecessor in interest had or 400 | could give under the previous paragraph, plus a right to possession of the 401 | Corresponding Source of the work from the predecessor in interest, if the predecessor 402 | has it or can get it with reasonable efforts. 403 | 404 | You may not impose any further restrictions on the exercise of the rights granted or 405 | affirmed under this License. For example, you may not impose a license fee, royalty, 406 | or other charge for exercise of rights granted under this License, and you may not 407 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 408 | that any patent claim is infringed by making, using, selling, offering for sale, or 409 | importing the Program or any portion of it. 410 | 411 | ### 11. Patents. 412 | 413 | A “contributor” is a copyright holder who authorizes use under this 414 | License of the Program or a work on which the Program is based. The work thus 415 | licensed is called the contributor's “contributor version”. 416 | 417 | A contributor's “essential patent claims” are all patent claims owned or 418 | controlled by the contributor, whether already acquired or hereafter acquired, that 419 | would be infringed by some manner, permitted by this License, of making, using, or 420 | selling its contributor version, but do not include claims that would be infringed 421 | only as a consequence of further modification of the contributor version. For 422 | purposes of this definition, “control” includes the right to grant patent 423 | sublicenses in a manner consistent with the requirements of this License. 424 | 425 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 426 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 427 | import and otherwise run, modify and propagate the contents of its contributor 428 | version. 429 | 430 | In the following three paragraphs, a “patent license” is any express 431 | agreement or commitment, however denominated, not to enforce a patent (such as an 432 | express permission to practice a patent or covenant not to sue for patent 433 | infringement). To “grant” such a patent license to a party means to make 434 | such an agreement or commitment not to enforce a patent against the party. 435 | 436 | If you convey a covered work, knowingly relying on a patent license, and the 437 | Corresponding Source of the work is not available for anyone to copy, free of charge 438 | and under the terms of this License, through a publicly available network server or 439 | other readily accessible means, then you must either (1) cause the Corresponding 440 | Source to be so available, or (2) arrange to deprive yourself of the benefit of the 441 | patent license for this particular work, or (3) arrange, in a manner consistent with 442 | the requirements of this License, to extend the patent license to downstream 443 | recipients. “Knowingly relying” means you have actual knowledge that, but 444 | for the patent license, your conveying the covered work in a country, or your 445 | recipient's use of the covered work in a country, would infringe one or more 446 | identifiable patents in that country that you have reason to believe are valid. 447 | 448 | If, pursuant to or in connection with a single transaction or arrangement, you 449 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 450 | license to some of the parties receiving the covered work authorizing them to use, 451 | propagate, modify or convey a specific copy of the covered work, then the patent 452 | license you grant is automatically extended to all recipients of the covered work and 453 | works based on it. 454 | 455 | A patent license is “discriminatory” if it does not include within the 456 | scope of its coverage, prohibits the exercise of, or is conditioned on the 457 | non-exercise of one or more of the rights that are specifically granted under this 458 | License. You may not convey a covered work if you are a party to an arrangement with 459 | a third party that is in the business of distributing software, under which you make 460 | payment to the third party based on the extent of your activity of conveying the 461 | work, and under which the third party grants, to any of the parties who would receive 462 | the covered work from you, a discriminatory patent license (a) in connection with 463 | copies of the covered work conveyed by you (or copies made from those copies), or (b) 464 | primarily for and in connection with specific products or compilations that contain 465 | the covered work, unless you entered into that arrangement, or that patent license 466 | was granted, prior to 28 March 2007. 467 | 468 | Nothing in this License shall be construed as excluding or limiting any implied 469 | license or other defenses to infringement that may otherwise be available to you 470 | under applicable patent law. 471 | 472 | ### 12. No Surrender of Others' Freedom. 473 | 474 | If conditions are imposed on you (whether by court order, agreement or otherwise) 475 | that contradict the conditions of this License, they do not excuse you from the 476 | conditions of this License. If you cannot convey a covered work so as to satisfy 477 | simultaneously your obligations under this License and any other pertinent 478 | obligations, then as a consequence you may not convey it at all. For example, if you 479 | agree to terms that obligate you to collect a royalty for further conveying from 480 | those to whom you convey the Program, the only way you could satisfy both those terms 481 | and this License would be to refrain entirely from conveying the Program. 482 | 483 | ### 13. Use with the GNU Affero General Public License. 484 | 485 | Notwithstanding any other provision of this License, you have permission to link or 486 | combine any covered work with a work licensed under version 3 of the GNU Affero 487 | General Public License into a single combined work, and to convey the resulting work. 488 | The terms of this License will continue to apply to the part which is the covered 489 | work, but the special requirements of the GNU Affero General Public License, section 490 | 13, concerning interaction through a network will apply to the combination as such. 491 | 492 | ### 14. Revised Versions of this License. 493 | 494 | The Free Software Foundation may publish revised and/or new versions of the GNU 495 | General Public License from time to time. Such new versions will be similar in spirit 496 | to the present version, but may differ in detail to address new problems or concerns. 497 | 498 | Each version is given a distinguishing version number. If the Program specifies that 499 | a certain numbered version of the GNU General Public License “or any later 500 | version” applies to it, you have the option of following the terms and 501 | conditions either of that numbered version or of any later version published by the 502 | Free Software Foundation. If the Program does not specify a version number of the GNU 503 | General Public License, you may choose any version ever published by the Free 504 | Software Foundation. 505 | 506 | If the Program specifies that a proxy can decide which future versions of the GNU 507 | General Public License can be used, that proxy's public statement of acceptance of a 508 | version permanently authorizes you to choose that version for the Program. 509 | 510 | Later license versions may give you additional or different permissions. However, no 511 | additional obligations are imposed on any author or copyright holder as a result of 512 | your choosing to follow a later version. 513 | 514 | ### 15. Disclaimer of Warranty. 515 | 516 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 517 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 518 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 519 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 520 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 521 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 522 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 523 | 524 | ### 16. Limitation of Liability. 525 | 526 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 527 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 528 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 529 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 530 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 531 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 532 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 533 | POSSIBILITY OF SUCH DAMAGES. 534 | 535 | ### 17. Interpretation of Sections 15 and 16. 536 | 537 | If the disclaimer of warranty and limitation of liability provided above cannot be 538 | given local legal effect according to their terms, reviewing courts shall apply local 539 | law that most closely approximates an absolute waiver of all civil liability in 540 | connection with the Program, unless a warranty or assumption of liability accompanies 541 | a copy of the Program in return for a fee. 542 | 543 | END OF TERMS AND CONDITIONS 544 | 545 | ## How to Apply These Terms to Your New Programs 546 | 547 | If you develop a new program, and you want it to be of the greatest possible use to 548 | the public, the best way to achieve this is to make it free software which everyone 549 | can redistribute and change under these terms. 550 | 551 | To do so, attach the following notices to the program. It is safest to attach them 552 | to the start of each source file to most effectively state the exclusion of warranty; 553 | and each file should have at least the “copyright” line and a pointer to 554 | where the full notice is found. 555 | 556 | 557 | Copyright (C) 558 | 559 | This program is free software: you can redistribute it and/or modify 560 | it under the terms of the GNU General Public License as published by 561 | the Free Software Foundation, either version 3 of the License, or 562 | (at your option) any later version. 563 | 564 | This program is distributed in the hope that it will be useful, 565 | but WITHOUT ANY WARRANTY; without even the implied warranty of 566 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 567 | GNU General Public License for more details. 568 | 569 | You should have received a copy of the GNU General Public License 570 | along with this program. If not, see . 571 | 572 | Also add information on how to contact you by electronic and paper mail. 573 | 574 | If the program does terminal interaction, make it output a short notice like this 575 | when it starts in an interactive mode: 576 | 577 | Copyright (C) 578 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 579 | This is free software, and you are welcome to redistribute it 580 | under certain conditions; type 'show c' for details. 581 | 582 | The hypothetical commands 'show w' and 'show c' should show the appropriate parts of 583 | the General Public License. Of course, your program's commands might be different; 584 | for a GUI interface, you would use an “about box”. 585 | 586 | You should also get your employer (if you work as a programmer) or school, if any, to 587 | sign a “copyright disclaimer” for the program, if necessary. For more 588 | information on this, and how to apply and follow the GNU GPL, see 589 | <>. 590 | 591 | The GNU General Public License does not permit incorporating your program into 592 | proprietary programs. If your program is a subroutine library, you may consider it 593 | more useful to permit linking proprietary applications with the library. If this is 594 | what you want to do, use the GNU Lesser General Public License instead of this 595 | License. But first, please read 596 | <>. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sauron is a minimalistic, YARA based malware scanner with realtime filesystem monitoring written in Rust. 2 | 3 | ## Features 4 | 5 | * Realtime scan of created and modified files supporting Linux `inotify`, macOS `FSEvents`, Windows `ReadDirectoryChanges` and polling for other platforms. 6 | * YARA engine complete support. 7 | * Single scan mode to scan a folder, report results and exit. 8 | * Parallel scanning using a configurable thread pool. 9 | * Log, text and JSON reporting. 10 | 11 | ### Known Limitations 12 | 13 | Due to the filesystem monitoring mechanism, Sauron is extremely lightweight and non invasive as more sophisticated AV solutions, however this comes with the following limitations: 14 | 15 | * Scanning files with an exclusive lock by other processes will likely fail with a `Permission Denied` error. 16 | * Malicious files creation and execution won't be blocked but just reported. 17 | * [Fileless malware](https://en.wikipedia.org/wiki/Fileless_malware) won't be detected. 18 | * Detected files won't be linked to originating processes. 19 | 20 | ## Building 21 | 22 | ```sh 23 | cargo build --release 24 | ``` 25 | 26 | ### Dependencies 27 | 28 | Your system must have `libssl-dev` installed. For Ubuntu-derivatives this can be installed via `sudo apt install libssl-dev`. 29 | 30 | ## Running 31 | 32 | Assuming you have your YARA rules in `./yara-rules` (you can find [plenty of free rules](https://github.com/InQuest/awesome-yara) online): 33 | 34 | ```sh 35 | sudo ./target/release/sauron --rules ./yara-rules 36 | ``` 37 | 38 | ![screenshot](https://i.imgur.com/Dw5N9RR.png) 39 | 40 | ## Single Scan 41 | 42 | Alternatively you can perform a one-time recursive scan of the specified folder using the `--scan` argument: 43 | 44 | ```sh 45 | sudo ./target/release/sauron --rules ./yara-rules --scan --root /path/to/scan 46 | ``` 47 | 48 | You can specify which file extensions to scan (all by default) with the `--ext` argument: 49 | 50 | ```sh 51 | sudo ./target/release/sauron \ 52 | --rules ./yara-rules \ 53 | --scan \ 54 | --root /path/to/scan \ 55 | --ext exe \ 56 | --ext elf \ 57 | --ext doc \ 58 | --ext docx 59 | ``` 60 | 61 | ## Reporting 62 | 63 | Various options are available for reporting: 64 | 65 | * `--report-clean` will also report clean files. 66 | * `--report-errors` explicitly report errors (reported as debug logs by default). 67 | * `--report-output ` will write scan reports to a file. 68 | * `--report-json` if `--report-output` is passed, write as JSON instead of text. 69 | 70 | ## Other options 71 | 72 | Run `sauron --help` for the complete list of options. 73 | 74 | ## License 75 | 76 | This project is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and it is released under the GPL3 license. 77 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::time::{Instant, SystemTime, UNIX_EPOCH}; 3 | 4 | use serde::Serialize; 5 | use walkdir::WalkDir; 6 | use yara::{Compiler, Rules}; 7 | 8 | pub type Error = String; 9 | pub type Tag = String; 10 | 11 | #[derive(Clone, Debug, Serialize)] 12 | pub struct Detection { 13 | pub path: PathBuf, 14 | pub size: u64, 15 | pub scanned_at: u64, 16 | pub time: f32, 17 | pub error: Option, 18 | pub detected: bool, 19 | pub tags: Vec, 20 | } 21 | 22 | pub struct Configuration { 23 | pub data_path: String, 24 | pub timeout: i32, 25 | } 26 | 27 | pub struct Engine { 28 | config: Configuration, 29 | rules: Rules, 30 | } 31 | 32 | impl Engine { 33 | pub fn new(config: Configuration) -> Result { 34 | log::info!("initializing yara engine from '{}' ...", &config.data_path); 35 | 36 | // create YARA compiler 37 | let mut compiler = Compiler::new().map_err(|e| e.to_string())?; 38 | let mut num_rules = 0; 39 | 40 | // a single yara file has been passed as argument 41 | if config.data_path.ends_with(".yar") { 42 | log::debug!("loading {} ...", &config.data_path); 43 | compiler = compiler 44 | .add_rules_file(&config.data_path) 45 | .map_err(|e| format!("could not load {:?}: {:?}", &config.data_path, e))?; 46 | num_rules += 1; 47 | } else { 48 | // loop rules folder and load each .yar file 49 | for entry in WalkDir::new(&config.data_path) 50 | .follow_links(true) 51 | .into_iter() 52 | .filter_map(|e| e.ok()) 53 | { 54 | let f_name = entry.path().to_string_lossy(); 55 | if f_name.ends_with(".yar") { 56 | log::debug!("loading {} ...", &f_name); 57 | compiler = compiler 58 | .add_rules_file(&*f_name) 59 | .map_err(|e| format!("could not load {:?}: {:?}", f_name, e))?; 60 | num_rules += 1; 61 | } 62 | } 63 | } 64 | 65 | // compile all rules 66 | log::debug!("compiling {} rules ...", num_rules); 67 | 68 | let start = Instant::now(); 69 | 70 | let rules = compiler.compile_rules().map_err(|e| e.to_string())?; 71 | 72 | log::info!("{} rules compiled in {:?}", num_rules, start.elapsed()); 73 | 74 | Ok(Engine { config, rules }) 75 | } 76 | 77 | pub fn scan(&self, path: &PathBuf) -> Detection { 78 | let mut detected = false; 79 | let mut tags = vec![]; 80 | let mut error: Option = None; 81 | let mut size: u64 = 0; 82 | let mut time: f32 = 0.0; 83 | // make path absolute 84 | let path = match std::fs::canonicalize(path) { 85 | Ok(p) => p, 86 | Err(e) => { 87 | error = Some(format!("can't canonicalize {:?}: {:?}", path, e)); 88 | path.clone() 89 | } 90 | }; 91 | let scanned_at = SystemTime::now() 92 | .duration_since(UNIX_EPOCH) 93 | .unwrap() 94 | .as_secs(); 95 | 96 | if path.is_file() || path.is_symlink() { 97 | // get file metadata 98 | match std::fs::metadata(&path) { 99 | Ok(data) => { 100 | // skip empty files 101 | size = data.len(); 102 | if size == 0 { 103 | log::trace!("ignoring empty file {:?}", &path); 104 | } else { 105 | let start = Instant::now(); 106 | 107 | // scan this file with the loaded YARA rules 108 | match self.rules.scan_file(&path, self.config.timeout) { 109 | Ok(matches) => { 110 | if !matches.is_empty() { 111 | detected = true; 112 | for rule in matches { 113 | tags.push(rule.identifier.to_string()); 114 | } 115 | } 116 | } 117 | Err(e) => error = Some(format!("can't scan {:?}: {:?}", &path, e)), 118 | } 119 | 120 | let elapsed = start.elapsed(); 121 | time = elapsed.as_secs_f32(); 122 | 123 | log::debug!("{:?} - {} bytes scanned in {:?} ", &path, size, elapsed); 124 | } 125 | } 126 | Err(e) => error = Some(format!("can't get metadata for {:?}: {:?}", &path, e)), 127 | } 128 | } 129 | 130 | Detection { 131 | path, 132 | size, 133 | scanned_at, 134 | time, 135 | detected, 136 | tags, 137 | error, 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/fs_monitor.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::channel; 2 | use std::sync::{Arc, Mutex}; 3 | use std::time::Duration; 4 | 5 | use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; 6 | use threadpool::ThreadPool; 7 | 8 | use crate::engine::Engine; 9 | use crate::report::Report; 10 | use crate::Arguments; 11 | 12 | pub(crate) fn start(args: Arguments, engine: Engine, report: Report) -> Result<(), String> { 13 | // create a recursive filesystem monitor for the root path 14 | log::info!("initializing filesystem monitor for '{}' ...", &args.root); 15 | 16 | let (tx, rx) = channel(); 17 | let mut watcher = watcher(tx, Duration::ZERO).map_err(|e| e.to_string())?; 18 | 19 | watcher 20 | .watch(&args.root, RecursiveMode::Recursive) 21 | .map_err(|e| e.to_string())?; 22 | 23 | log::info!("initializing pool with {} workers ...", args.workers); 24 | 25 | let pool = ThreadPool::new(args.workers); 26 | 27 | log::info!("running ..."); 28 | 29 | let engine = Arc::new(engine); 30 | let report = Arc::new(Mutex::new(report)); 31 | 32 | // receive filesystem events 33 | loop { 34 | match rx.recv() { 35 | Ok(event) => match event { 36 | // we're interested in files creation and modification 37 | DebouncedEvent::Create(path) 38 | | DebouncedEvent::NoticeWrite(path) 39 | | DebouncedEvent::Write(path) 40 | | DebouncedEvent::Rename(_, path) => { 41 | // if it's a file and it exists 42 | if path.is_file() && path.exists() { 43 | // create thread safe references 44 | let engine = engine.clone(); 45 | let report = report.clone(); 46 | // submit scan job to the threads pool 47 | pool.execute(move || { 48 | // perform the scanning 49 | let res = engine.scan(&path); 50 | // handle reporting 51 | if let Ok(mut report) = report.lock() { 52 | if let Err(e) = report.report(res) { 53 | log::error!("reporting error: {:?}", e); 54 | } 55 | } 56 | }); 57 | } 58 | } 59 | 60 | // ignored events 61 | DebouncedEvent::NoticeRemove(path) => { 62 | log::trace!("ignoring remove event for {:?}", path); 63 | } 64 | DebouncedEvent::Chmod(path) => { 65 | log::trace!("ignoring chmod event for {:?}", path); 66 | } 67 | DebouncedEvent::Remove(path) => { 68 | log::trace!("ignoring remove event for {:?}", path); 69 | } 70 | // error events 71 | DebouncedEvent::Rescan => { 72 | log::debug!("rescan"); 73 | } 74 | DebouncedEvent::Error(error, maybe_path) => { 75 | log::error!("error for {:?}: {:?}", maybe_path, error); 76 | } 77 | }, 78 | Err(e) => log::error!("filesystem monitoring error: {:?}", e), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/fs_scan.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | use std::sync::{Arc, Mutex}; 3 | use std::time::Instant; 4 | 5 | use threadpool::ThreadPool; 6 | use walkdir::WalkDir; 7 | 8 | use crate::engine::Engine; 9 | use crate::report::Report; 10 | use crate::Arguments; 11 | 12 | pub(crate) fn start(args: Arguments, engine: Engine, report: Report) -> Result<(), String> { 13 | log::info!("initializing pool with {} workers ...", args.workers); 14 | 15 | let pool = ThreadPool::new(args.workers); 16 | 17 | log::info!("scanning {} ...", &args.root); 18 | 19 | let engine = Arc::new(engine); 20 | let report = Arc::new(Mutex::new(report)); 21 | let start = Instant::now(); 22 | let num_scanned = Arc::new(AtomicU32::new(0)); 23 | let num_detected = Arc::new(AtomicU32::new(0)); 24 | 25 | for entry in WalkDir::new(&args.root) 26 | .follow_links(true) 27 | .into_iter() 28 | .filter_map(|e| e.ok()) 29 | { 30 | let f_path = entry.path(); 31 | let mut do_scan = args.ext.is_empty(); // init to true if not extensions were passed 32 | 33 | // do we have to filter by file extension? 34 | if !do_scan { 35 | if let Some(ext) = f_path.extension() { 36 | for filter_ext in &args.ext { 37 | if filter_ext.to_lowercase() == *ext.to_string_lossy().to_lowercase() { 38 | do_scan = true; 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | 45 | if do_scan { 46 | // create thread-safe references 47 | let an_engine = engine.clone(); 48 | let f_path = f_path.to_path_buf(); 49 | let num_scanned = num_scanned.clone(); 50 | let num_detected = num_detected.clone(); 51 | let report = report.clone(); 52 | 53 | // submit scan job to the threads pool 54 | pool.execute(move || { 55 | // perform the scanning 56 | let res = an_engine.scan(&f_path); 57 | if res.detected { 58 | num_detected.fetch_add(1, Ordering::SeqCst); 59 | } 60 | num_scanned.fetch_add(1, Ordering::SeqCst); 61 | 62 | // handle reporting 63 | if let Ok(mut report) = report.lock() { 64 | if let Err(e) = report.report(res) { 65 | log::error!("reporting error: {:?}", e); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | 72 | pool.join(); 73 | 74 | log::info!( 75 | "{:?} files scanned in {:?}, {:?} positive detections", 76 | num_scanned, 77 | start.elapsed(), 78 | num_detected 79 | ); 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | mod engine; 4 | mod fs_monitor; 5 | mod fs_scan; 6 | mod report; 7 | 8 | #[derive(Parser, Default, Debug, Clone)] 9 | #[clap( 10 | about = "Minimalistic cross-platform filesystem monitor and malware scanner using YARA rules." 11 | )] 12 | struct Arguments { 13 | /// Root path of the filesystem to monitor. 14 | #[clap(long, default_value = "/")] 15 | root: String, 16 | /// Path of YARA rules to use. 17 | #[clap(long)] 18 | rules: String, 19 | /// Number of worker threads used for scanning. 20 | #[clap(long, default_value_t = num_cpus::get() * 2)] 21 | workers: usize, 22 | /// Scan timeout in seconds. 23 | #[clap(long, default_value_t = 30)] 24 | scan_timeout: i32, 25 | /// Perform a scan of every file in the specified root folder and exit. 26 | #[clap(long, takes_value = false)] 27 | scan: bool, 28 | /// Only scan files with the specified extension if --scan is used, can be passed multiple times. 29 | #[clap(long)] 30 | ext: Vec, 31 | /// Report clean files along with malware detections. 32 | #[clap(long, takes_value = false)] 33 | report_clean: bool, 34 | /// Report scan errors. 35 | #[clap(long, takes_value = false)] 36 | report_errors: bool, 37 | /// Write scanning report to this file. 38 | #[clap(long)] 39 | report_output: Option, 40 | /// If --report-output is passed, write as JSON instead of text. 41 | #[clap(long, takes_value = false)] 42 | report_json: bool, 43 | } 44 | 45 | fn main() -> Result<(), String> { 46 | pretty_env_logger::init(); 47 | 48 | let args = Arguments::parse(); 49 | 50 | // initialize the scan engine 51 | let config = engine::Configuration { 52 | data_path: args.rules.clone(), 53 | timeout: args.scan_timeout, 54 | }; 55 | let engine = engine::Engine::new(config)?; 56 | 57 | // initialize the reporting engine 58 | let report = report::Report::setup(&args)?; 59 | 60 | if args.scan { 61 | // perform a scan of the root folder and exit 62 | fs_scan::start(args, engine, report) 63 | } else { 64 | // monitor the filesystem 65 | fs_monitor::start(args, engine, report) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/report.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::prelude::*; 3 | use std::io::SeekFrom; 4 | 5 | use crate::engine::Detection; 6 | use crate::Arguments; 7 | 8 | type Error = String; 9 | 10 | pub(crate) struct Report { 11 | args: Arguments, 12 | output: Option, 13 | detections: Vec, 14 | } 15 | 16 | impl Report { 17 | pub fn setup(args: &Arguments) -> Result { 18 | let args = args.clone(); 19 | let mut output = None; 20 | let detections = vec![]; 21 | 22 | if let Some(output_file_name) = &args.report_output { 23 | output = Some( 24 | OpenOptions::new() 25 | .write(true) 26 | .create_new(true) 27 | .open(output_file_name) 28 | .map_err(|e| format!("can't create {:?}: {:?}", output_file_name, e))?, 29 | ); 30 | } 31 | 32 | Ok(Self { 33 | args, 34 | output, 35 | detections, 36 | }) 37 | } 38 | 39 | fn write_to_file_if_needed( 40 | &mut self, 41 | detection: Detection, 42 | message: String, 43 | to_json: Option, 44 | ) -> Result<(), Error> { 45 | // if file reporting is enabled 46 | if let Some(output) = &mut self.output { 47 | let mut data = String::new(); 48 | 49 | if self.args.report_json && to_json.is_some() { 50 | // JSON reporting is enabled and we have a detection to report 51 | self.detections.push(detection); 52 | // reset file 53 | output.set_len(0).map_err(|e| e.to_string())?; 54 | output.seek(SeekFrom::Start(0)).map_err(|e| e.to_string())?; 55 | // serialize detections array, using format instead of whole object serialization 56 | // in order to borrow unmutable references to self 57 | data = format!( 58 | "{{\"detections\":{}}}", 59 | serde_json::to_string(&self.detections).map_err(|e| e.to_string())? 60 | ); 61 | } else if !message.is_empty() { 62 | // plain text reporting 63 | data = format!("{}\n", &message); 64 | } 65 | 66 | // any data at all to write? 67 | if !data.is_empty() { 68 | // write to file 69 | output 70 | .write_all(data.as_bytes()) 71 | .map_err(|e| e.to_string())?; 72 | // flush 73 | output.flush().map_err(|e| e.to_string())?; 74 | } 75 | } 76 | 77 | Ok(()) 78 | } 79 | 80 | pub fn report(&mut self, detection: Detection) -> Result<(), Error> { 81 | let mut message = String::new(); 82 | let mut to_json: Option = None; 83 | 84 | if let Some(error) = &detection.error { 85 | log::debug!("{:?}", &error); 86 | 87 | if self.args.report_errors { 88 | message = error.to_owned(); 89 | to_json = Some(detection.clone()); 90 | 91 | log::error!("{}", &message); 92 | } 93 | } else if detection.detected { 94 | message = format!( 95 | "!!! MALWARE DETECTION: '{}' detected as '{:?}'", 96 | detection.path.to_string_lossy(), 97 | detection.tags.join(", ") 98 | ); 99 | to_json = Some(detection.clone()); 100 | 101 | log::warn!("{}", &message); 102 | } else if self.args.report_clean { 103 | message = format!("{} - clean", detection.path.to_string_lossy()); 104 | to_json = Some(detection.clone()); 105 | 106 | log::info!("{}", &message); 107 | } 108 | 109 | self.write_to_file_if_needed(detection, message, to_json) 110 | } 111 | } 112 | --------------------------------------------------------------------------------