From b6690b1cd4c023d4efd3e6f419c730230e7e8a68 Mon Sep 17 00:00:00 2001 From: Koutheir Attouchi Date: Sat, 10 Feb 2024 17:05:16 -0500 Subject: [PATCH] Replace unmaintained dependencies. Human readable errors. --- Cargo.lock | 533 ++++++++++++++++++++++++++------ Cargo.toml | 23 +- README.md | 49 +-- src/archive.rs | 26 +- src/cmdline.docopt | 47 --- src/cmdline.rs | 216 +++++++------ src/command-line-after-help.txt | 27 ++ src/elf.rs | 19 +- src/elf/needed_libc.rs | 35 ++- src/errors.rs | 14 +- src/main.rs | 94 +++--- src/options.rs | 102 ++++-- src/options/status.rs | 60 +--- src/parser.rs | 16 +- src/pe.rs | 24 +- src/ui.rs | 19 +- 16 files changed, 847 insertions(+), 457 deletions(-) delete mode 100644 src/cmdline.docopt create mode 100644 src/command-line-after-help.txt diff --git a/Cargo.lock b/Cargo.lock index 0f5a619..1d77a6d 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,69 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -19,24 +82,107 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "binary-security-check" -version = "1.2.14" +version = "1.2.15" dependencies = [ - "docopt", + "clap", + "flexi_logger", "goblin", - "lazy_static", "log", - "memmap", + "memmap2", "memoffset", + "once_cell", "rayon", "regex", "scroll", - "serde", - "serde_derive", - "simplelog", "termcolor", "thiserror", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.0", +] + +[[package]] +name = "clap" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -63,31 +209,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] -name = "deranged" -version = "0.3.11" +name = "either" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] -name = "docopt" -version = "1.1.1" +name = "flexi_logger" +version = "0.27.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" +checksum = "469e584c031833564840fb0cdbce99bdfe946fd45480a188545e73a76f45461c" dependencies = [ + "chrono", + "glob", + "is-terminal", "lazy_static", + "log", + "nu-ansi-term", "regex", - "serde", - "strsim", + "thiserror", ] [[package]] -name = "either" -version = "1.10.0" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "goblin" @@ -101,10 +248,59 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.10" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "lazy_static" @@ -131,13 +327,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] -name = "memmap" -version = "0.7.0" +name = "memmap2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", - "winapi", ] [[package]] @@ -150,31 +345,34 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "nu-ansi-term" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] [[package]] -name = "num_threads" -version = "0.1.6" +name = "num-traits" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ - "libc", + "autocfg", ] [[package]] -name = "plain" -version = "0.2.3" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" @@ -263,42 +461,11 @@ dependencies = [ "syn", ] -[[package]] -name = "serde" -version = "1.0.196" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.196" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "simplelog" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" @@ -313,9 +480,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -341,43 +508,70 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.3.34" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", + "cfg-if", + "wasm-bindgen-macro", ] [[package]] -name = "time-core" -version = "0.1.2" +name = "wasm-bindgen-backend" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] [[package]] -name = "time-macros" -version = "0.2.17" +name = "wasm-bindgen-macro" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ - "num-conv", - "time-core", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "wasm-bindgen-macro-support" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "winapi" @@ -409,3 +603,144 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index b7ea440..ead479a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ [package] name = "binary-security-check" -version = "1.2.14" +version = "1.2.15" authors = ["Koutheir Attouchi "] license = "MIT" description = "Analyzer of security features in executable binaries" @@ -37,17 +37,24 @@ incremental = false overflow-checks = true [dependencies] -docopt = { version = "1.1" } thiserror = { version = "1.0" } goblin = { version = "0.8" } -lazy_static = { version = "1.4" } +once_cell = { version = "1.19" } log = { version = "0.4" } -memmap = { version = "0.7" } +memmap2 = { version = "0.9" } rayon = { version = "1.8" } regex = { version = "1.10" } scroll = { version = "0.12" } -serde = { version = "1.0" } -serde_derive = { version = "1.0" } -simplelog = { version = "0.12" } -termcolor = { version = "1.1" } +flexi_logger = { version = "0.27" } +termcolor = { version = "1.4" } memoffset = { version = "0.9" } + +clap = { version = "4.5", features = [ + "color", + "help", + "usage", + "error-context", + "suggestions", + "derive", + "cargo", +] } diff --git a/README.md b/README.md index ccde1ff..59e93ed 100755 --- a/README.md +++ b/README.md @@ -87,29 +87,37 @@ The status of the security feature in the binary is indicated by a letter before For example, `!ASLR` means the binary does not support Address Space Layout Randomization. -## Command line +## Usage ``` -Usage: - binary-security-check [-v] [-c COLOR] [(-s DIR | -l FILE | -i SPEC | -n)] ... - binary-security-check (-h | --help) - binary-security-check --version +Usage: binary-security-check [OPTIONS] ... + +Arguments: + ... + Binary files to analyze Options: - -c COLOR, --color=COLOR Use color in standard output. Either 'auto' or - 'always' or 'never' [default: auto]. - -s DIR, --sysroot=DIR Set system root for finding the corresponding - C runtime library. - -l FILE, --libc=FILE Set the path of the C runtime library. - -i SPEC, --libc-spec=SPEC Use an internal list of checked functions as - specified by a specification. - -n, --no-libc Assume that input files do not use any C runtime libraries. - -v, --verbose Verbose logging. - -h, --help Show this screen. - --version Show version. - -If specified, then SPEC can be one of the following versions of the Linux -Standard Base specifications: + -v, --verbose + Verbose logging + -c, --color + Use color in standard output [default: auto] [possible values: auto, always, never] + -l, --libc + Path of the C runtime library file + -s, --sysroot + Path of the system root for finding the corresponding C runtime library + -i, --libc-spec + Use an internal list of checked functions as specified by a specification + [possible values: lsb1, lsb1dot1, lsb1dot2, lsb1dot3, lsb2, lsb2dot0dot1, lsb2dot1, lsb3, + lsb3dot1, lsb3dot2, lsb4, lsb4dot1, lsb5] + -n, --no-libc + Assume that input files do not use any C runtime libraries + -h, --help + Print help + -V, --version + Print version + +If --libc-spec is specified, then its value can be one of the following versions +of the Linux Standard Base specifications: - lsb1: LSB 1.0.0. - lsb1dot1: LSB 1.1.0. - lsb1dot2: LSB 1.2.0. @@ -132,7 +140,8 @@ following directories: - /usr/lib64/ - /lib32/ - /usr/lib32/ -The tools `readelf` and `ldd` can be used to help find the path of the C library + +The tools "readelf" and "ldd" can be used to help find the path of the C library needed by the analyzed files, which is given by the --libc parameter. ``` diff --git a/src/archive.rs b/src/archive.rs index 62b1b4a..fb3f9d8 100755 --- a/src/archive.rs +++ b/src/archive.rs @@ -11,8 +11,11 @@ use crate::options::status::DisplayInColorTerm; use crate::options::{BinarySecurityOption, ELFStackProtectionOption}; use crate::parser::BinaryParser; -pub fn analyze_binary(parser: &BinaryParser) -> Result>> { - let has_stack_protection = ELFStackProtectionOption.check(parser)?; +pub(crate) fn analyze_binary( + parser: &BinaryParser, + options: &crate::cmdline::Options, +) -> Result>> { + let has_stack_protection = ELFStackProtectionOption.check(parser, options)?; Ok(vec![has_stack_protection]) } @@ -22,13 +25,13 @@ pub(crate) fn has_stack_protection( ) -> Result { let bytes = parser.bytes(); for member_name in archive.members() { - let buffer = archive - .extract(member_name, bytes) - .map_err(|source| Error::Goblin1 { - operation: "goblin::archive::Archive", - param1: member_name.into(), - source, - })?; + let buffer = + archive + .extract(member_name, bytes) + .map_err(|source| Error::ExtractArchiveMember { + member: member_name.into(), + source, + })?; let r = member_has_stack_protection(member_name, buffer)?; if r { @@ -43,10 +46,7 @@ pub(crate) fn has_stack_protection( fn member_has_stack_protection(member_name: &str, bytes: &[u8]) -> Result { use goblin::Object; - let obj = Object::parse(bytes).map_err(|source| Error::Goblin { - operation: "goblin::Object::parse", - source, - })?; + let obj = Object::parse(bytes).map_err(|source| Error::ParseFile { source })?; if let Object::Elf(elf) = obj { // elf.is_object_file() diff --git a/src/cmdline.docopt b/src/cmdline.docopt deleted file mode 100644 index 4112d62..0000000 --- a/src/cmdline.docopt +++ /dev/null @@ -1,47 +0,0 @@ -{0} version {2}. -{1}, by {3}. - -Usage: - {0} [-v] [-c COLOR] [(-s DIR | -l FILE | -i SPEC | -n)] ... - {0} (-h | --help) - {0} --version - -Options: - -c COLOR, --color=COLOR Use color in standard output. Either 'auto' or - 'always' or 'never' [default: auto]. - -s DIR, --sysroot=DIR Set system root for finding the corresponding - C runtime library. - -l FILE, --libc=FILE Set the path of the C runtime library. - -i SPEC, --libc-spec=SPEC Use an internal list of checked functions as - specified by a specification. - -n, --no-libc Assume that input files do not use any C runtime libraries. - -v, --verbose Verbose logging. - -h, --help Show this screen. - --version Show version. - -If specified, then SPEC can be one of the following versions of the Linux -Standard Base specifications: -- lsb1: LSB 1.0.0. -- lsb1dot1: LSB 1.1.0. -- lsb1dot2: LSB 1.2.0. -- lsb1dot3: LSB 1.3.0. -- lsb2: LSB 2.0.0. -- lsb2dot0dot1: LSB 2.0.1. -- lsb2dot1: LSB 2.1.0. -- lsb3: LSB 3.0.0. -- lsb3dot1: LSB 3.1.0. -- lsb3dot2: LSB 3.2.0. -- lsb4: LSB 4.0.0. -- lsb4dot1: LSB 4.1.0. -- lsb5: LSB 5.0.0. - -By default, this tool tries to automatically locate the C library in the -following directories: -- /lib/ -- /usr/lib/ -- /lib64/ -- /usr/lib64/ -- /lib32/ -- /usr/lib32/ -The tools `readelf` and `ldd` can be used to help find the path of the C library -needed by the analyzed files, which is given by the --libc parameter. diff --git a/src/cmdline.rs b/src/cmdline.rs index 3a25ceb..6c15b48 100755 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -7,132 +7,142 @@ use core::fmt; use std::path::PathBuf; -use docopt::Docopt; -use serde_derive::Deserialize; - use crate::elf; -#[derive(Debug, Deserialize)] -pub struct Args { - pub flag_verbose: bool, - pub flag_sysroot: Option, - pub flag_libc: Option, - pub flag_libc_spec: Option, - pub flag_no_libc: bool, - pub flag_color: UseColor, - pub arg_file: Vec, -} - -lazy_static::lazy_static! { - pub static ref ARGS: Args = parse_command_line(); +const HELP_TEMPLATE: &str = "{before-help}{about-with-newline} +{usage-heading} {usage} + +{all-args}{after-help} +\u{1b}[1m\u{1b}[4mAuthors:\u{1b}[24m\u{1b}[22m +{tab}{author-with-newline}"; + +#[derive(Debug, clap::Parser)] +#[command( + author, + version, + about, + next_line_help = true, + help_template = HELP_TEMPLATE, + after_help = include_str!("command-line-after-help.txt"), +)] +pub(crate) struct Options { + /// Verbose logging. + #[arg(short = 'v', long, global = true, default_value_t = false)] + pub(crate) verbose: bool, + + /// Use color in standard output. + #[arg(short = 'c', long, global = true, value_enum, default_value_t = UseColor::Auto)] + pub(crate) color: UseColor, + + /// Path of the C runtime library file. + #[arg(short = 'l', long, conflicts_with_all = ["sysroot", "libc_spec", "no_libc"])] + pub(crate) libc: Option, + + /// Path of the system root for finding the corresponding C runtime library. + #[arg(short = 's', long, conflicts_with_all = ["libc", "libc_spec", "no_libc"])] + pub(crate) sysroot: Option, + + /// Use an internal list of checked functions as specified by a specification. + #[arg(short = 'i', long, value_enum, conflicts_with_all = ["libc", "sysroot", "no_libc"])] + pub(crate) libc_spec: Option, + + /// Assume that input files do not use any C runtime libraries. + #[arg(short = 'n', long, default_value_t = false, conflicts_with_all = ["libc", "sysroot", "libc_spec"])] + pub(crate) no_libc: bool, + + /// Binary files to analyze. + #[arg(required = true, value_hint = clap::ValueHint::FilePath)] + pub(crate) input_files: Vec, } -static PKG_NAME: &str = env!("CARGO_PKG_NAME"); -static PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); -static PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); -static PKG_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); - -fn parse_command_line() -> Args { - let usage = format!( - include_str!("cmdline.docopt"), - PKG_NAME, PKG_DESCRIPTION, PKG_VERSION, PKG_AUTHORS - ); - - let version = format!("{PKG_NAME} version {PKG_VERSION}"); - - Docopt::new(usage) - .and_then(|d| d.help(true).version(Some(version)).deserialize()) - .unwrap_or_else(|e| e.exit()) -} - -#[allow(non_camel_case_types)] -#[derive(Debug, Copy, Clone, Deserialize)] -pub enum UseColor { - auto, - always, - never, +#[derive(Debug, Copy, Clone, clap::ValueEnum)] +pub(crate) enum UseColor { + Auto, + Always, + Never, } impl From for termcolor::ColorChoice { fn from(other: UseColor) -> Self { match other { - UseColor::auto => termcolor::ColorChoice::Auto, - UseColor::always => termcolor::ColorChoice::Always, - UseColor::never => termcolor::ColorChoice::Never, + UseColor::Auto => termcolor::ColorChoice::Auto, + UseColor::Always => termcolor::ColorChoice::Always, + UseColor::Never => termcolor::ColorChoice::Never, } } } // If this changes, then update the command line reference. -#[allow(non_camel_case_types)] -#[derive(Debug, Copy, Clone, Deserialize)] -pub enum LibCSpec { - lsb1, - lsb1dot1, - lsb1dot2, - lsb1dot3, - lsb2, - lsb2dot0dot1, - lsb2dot1, - lsb3, - lsb3dot1, - lsb3dot2, - lsb4, - lsb4dot1, - lsb5, +#[derive(Debug, Copy, Clone, clap::ValueEnum)] +pub(crate) enum LibCSpec { + LSB1, + LSB1dot1, + LSB1dot2, + LSB1dot3, + LSB2, + LSB2dot0dot1, + LSB2dot1, + LSB3, + LSB3dot1, + LSB3dot2, + LSB4, + LSB4dot1, + LSB5, } impl fmt::Display for LibCSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - LibCSpec::lsb1 - | LibCSpec::lsb1dot1 - | LibCSpec::lsb1dot2 - | LibCSpec::lsb1dot3 - | LibCSpec::lsb2 - | LibCSpec::lsb2dot0dot1 - | LibCSpec::lsb2dot1 - | LibCSpec::lsb3 - | LibCSpec::lsb3dot1 - | LibCSpec::lsb3dot2 - | LibCSpec::lsb4 - | LibCSpec::lsb4dot1 - | LibCSpec::lsb5 => write!(f, "Linux Standard Base ")?, - } - - match *self { - LibCSpec::lsb1 => write!(f, "1.0.0"), - LibCSpec::lsb1dot1 => write!(f, "1.1.0"), - LibCSpec::lsb1dot2 => write!(f, "1.2.0"), - LibCSpec::lsb1dot3 => write!(f, "1.3.0"), - LibCSpec::lsb2 => write!(f, "2.0.0"), - LibCSpec::lsb2dot0dot1 => write!(f, "2.0.1"), - LibCSpec::lsb2dot1 => write!(f, "2.1.0"), - LibCSpec::lsb3 => write!(f, "3.0.0"), - LibCSpec::lsb3dot1 => write!(f, "3.1.0"), - LibCSpec::lsb3dot2 => write!(f, "3.2.0"), - LibCSpec::lsb4 => write!(f, "4.0.0"), - LibCSpec::lsb4dot1 => write!(f, "4.1.0"), - LibCSpec::lsb5 => write!(f, "5.0.0"), - } + let spec_name = match *self { + LibCSpec::LSB1 + | LibCSpec::LSB1dot1 + | LibCSpec::LSB1dot2 + | LibCSpec::LSB1dot3 + | LibCSpec::LSB2 + | LibCSpec::LSB2dot0dot1 + | LibCSpec::LSB2dot1 + | LibCSpec::LSB3 + | LibCSpec::LSB3dot1 + | LibCSpec::LSB3dot2 + | LibCSpec::LSB4 + | LibCSpec::LSB4dot1 + | LibCSpec::LSB5 => "Linux Standard Base", + }; + + let spec_version = match *self { + LibCSpec::LSB1 => "1.0.0", + LibCSpec::LSB1dot1 => "1.1.0", + LibCSpec::LSB1dot2 => "1.2.0", + LibCSpec::LSB1dot3 => "1.3.0", + LibCSpec::LSB2 => "2.0.0", + LibCSpec::LSB2dot0dot1 => "2.0.1", + LibCSpec::LSB2dot1 => "2.1.0", + LibCSpec::LSB3 => "3.0.0", + LibCSpec::LSB3dot1 => "3.1.0", + LibCSpec::LSB3dot2 => "3.2.0", + LibCSpec::LSB4 => "4.0.0", + LibCSpec::LSB4dot1 => "4.1.0", + LibCSpec::LSB5 => "5.0.0", + }; + + write!(f, "{spec_name} {spec_version}") } } impl LibCSpec { - pub fn get_functions_with_checked_versions(self) -> &'static [&'static str] { + pub(crate) fn get_functions_with_checked_versions(self) -> &'static [&'static str] { match self { - LibCSpec::lsb1 - | LibCSpec::lsb1dot1 - | LibCSpec::lsb1dot2 - | LibCSpec::lsb1dot3 - | LibCSpec::lsb2 - | LibCSpec::lsb2dot0dot1 - | LibCSpec::lsb2dot1 - | LibCSpec::lsb3 - | LibCSpec::lsb3dot1 - | LibCSpec::lsb3dot2 => &[], - - LibCSpec::lsb4 | LibCSpec::lsb4dot1 | LibCSpec::lsb5 => { + LibCSpec::LSB1 + | LibCSpec::LSB1dot1 + | LibCSpec::LSB1dot2 + | LibCSpec::LSB1dot3 + | LibCSpec::LSB2 + | LibCSpec::LSB2dot0dot1 + | LibCSpec::LSB2dot1 + | LibCSpec::LSB3 + | LibCSpec::LSB3dot1 + | LibCSpec::LSB3dot2 => &[], + + LibCSpec::LSB4 | LibCSpec::LSB4dot1 | LibCSpec::LSB5 => { elf::checked_functions::LSB_4_0_0_FUNCTIONS_WITH_CHECKED_VERSIONS } } diff --git a/src/command-line-after-help.txt b/src/command-line-after-help.txt new file mode 100644 index 0000000..3c89739 --- /dev/null +++ b/src/command-line-after-help.txt @@ -0,0 +1,27 @@ +If --libc-spec is specified, then its value can be one of the following versions +of the Linux Standard Base specifications: +- lsb1: LSB 1.0.0. +- lsb1dot1: LSB 1.1.0. +- lsb1dot2: LSB 1.2.0. +- lsb1dot3: LSB 1.3.0. +- lsb2: LSB 2.0.0. +- lsb2dot0dot1: LSB 2.0.1. +- lsb2dot1: LSB 2.1.0. +- lsb3: LSB 3.0.0. +- lsb3dot1: LSB 3.1.0. +- lsb3dot2: LSB 3.2.0. +- lsb4: LSB 4.0.0. +- lsb4dot1: LSB 4.1.0. +- lsb5: LSB 5.0.0. + +By default, this tool tries to automatically locate the C library in the +following directories: +- /lib/ +- /usr/lib/ +- /lib64/ +- /usr/lib64/ +- /lib32/ +- /usr/lib32/ + +The tools "readelf" and "ldd" can be used to help find the path of the C library +needed by the analyzed files, which is given by the --libc parameter. diff --git a/src/elf.rs b/src/elf.rs index f865f69..a24b066 100755 --- a/src/elf.rs +++ b/src/elf.rs @@ -11,7 +11,6 @@ use std::collections::HashSet; use log::{debug, log_enabled, warn}; -use crate::cmdline::ARGS; use crate::errors::Result; use crate::options::status::{ASLRCompatibilityLevel, DisplayInColorTerm}; use crate::options::{ @@ -23,12 +22,15 @@ use crate::parser::BinaryParser; use self::checked_functions::function_is_checked_version; use self::needed_libc::NeededLibC; -pub fn analyze_binary(parser: &BinaryParser) -> Result>> { +pub(crate) fn analyze_binary( + parser: &BinaryParser, + options: &crate::cmdline::Options, +) -> Result>> { let supports_address_space_layout_randomization = - AddressSpaceLayoutRandomizationOption.check(parser)?; - let has_stack_protection = ELFStackProtectionOption.check(parser)?; - let read_only_after_reloc = ELFReadOnlyAfterRelocationsOption.check(parser)?; - let immediate_bind = ELFImmediateBindingOption.check(parser)?; + AddressSpaceLayoutRandomizationOption.check(parser, options)?; + let has_stack_protection = ELFStackProtectionOption.check(parser, options)?; + let read_only_after_reloc = ELFReadOnlyAfterRelocationsOption.check(parser, options)?; + let immediate_bind = ELFImmediateBindingOption.check(parser, options)?; let mut result = vec![ supports_address_space_layout_randomization, @@ -37,8 +39,9 @@ pub fn analyze_binary(parser: &BinaryParser) -> Result Result { - if let Some(path) = ARGS.flag_libc.as_ref() { + pub(crate) fn find_needed_by_executable( + elf: &goblin::elf::Elf, + options: &crate::cmdline::Options, + ) -> Result { + if let Some(path) = &options.libc { Self::open_elf_for_architecture(path, elf) } else { elf.libraries @@ -61,7 +64,7 @@ impl NeededLibC { // Only consider libraries whose pattern is known. .filter(|needed_lib| KNOWN_LIBC_PATTERN.is_match(needed_lib)) // Parse the library. - .map(|lib| Self::open_compatible_libc(lib, elf)) + .map(|lib| Self::open_compatible_libc(lib, elf, options)) // Return the first that can be successfully parsed. .find(Result::is_ok) // Or return an error in case nothing is found or nothing can be parsed. @@ -69,13 +72,17 @@ impl NeededLibC { } } - fn open_compatible_libc(file_name: impl AsRef, elf: &goblin::elf::Elf) -> Result { + fn open_compatible_libc( + file_name: impl AsRef, + elf: &goblin::elf::Elf, + options: &crate::cmdline::Options, + ) -> Result { KNOWN_LIBC_FILE_LOCATIONS .iter() // For each known libc file location, parse the libc file. .map(|known_location| { Self::open_elf_for_architecture( - Self::get_libc_path(known_location, &file_name), + Self::get_libc_path(known_location, &file_name, options), elf, ) }) @@ -85,8 +92,12 @@ impl NeededLibC { .unwrap_or_else(|| Err(Error::NotFoundNeededLibC(file_name.as_ref().into()))) } - fn get_libc_path(location: impl AsRef, file_name: impl AsRef) -> PathBuf { - let mut path = if let Some(sysroot) = ARGS.flag_sysroot.as_ref() { + fn get_libc_path( + location: impl AsRef, + file_name: impl AsRef, + options: &crate::cmdline::Options, + ) -> PathBuf { + let mut path = if let Some(sysroot) = options.sysroot.as_ref() { let mut p = PathBuf::from(sysroot).into_os_string(); p.push(location.as_ref()); PathBuf::from(p) @@ -199,11 +210,7 @@ static KNOWN_LIBC_FILE_LOCATIONS: &[&str] = &[ "/usr/lib32", ]; -lazy_static::lazy_static! { - static ref KNOWN_LIBC_PATTERN: Regex = init_known_libc_pattern(); -} - -fn init_known_libc_pattern() -> Regex { +static KNOWN_LIBC_PATTERN: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { RegexBuilder::new(r"\blib(c|bionic)\b[^/]+$") .case_insensitive(true) .multi_line(false) @@ -211,4 +218,4 @@ fn init_known_libc_pattern() -> Regex { .unicode(true) .build() .expect("Invalid static regular expression.") -} +}); diff --git a/src/errors.rs b/src/errors.rs index 7473775..8397509 100755 --- a/src/errors.rs +++ b/src/errors.rs @@ -10,7 +10,7 @@ pub(crate) type Result = core::result::Result; #[derive(Debug, thiserror::Error)] pub(crate) enum Error { - #[error("{operation}({path}) failed")] + #[error("failed to {operation}. Path: {path}")] IO1 { operation: &'static str, path: PathBuf, @@ -20,19 +20,17 @@ pub(crate) enum Error { //backtrace: Backtrace, }, - #[error("{operation}() failed")] - Goblin { - operation: &'static str, + #[error("failed to parse file")] + ParseFile { #[source] source: goblin::error::Error, // Add this when `Backtrace` becomes stable. //backtrace: Backtrace, }, - #[error("{operation}({param1}) failed")] - Goblin1 { - operation: &'static str, - param1: String, + #[error("failed to extract archive member: {member}")] + ExtractArchiveMember { + member: String, #[source] source: goblin::error::Error, // Add this when `Backtrace` becomes stable. diff --git a/src/main.rs b/src/main.rs index 64d8641..fe04a6b 100755 --- a/src/main.rs +++ b/src/main.rs @@ -41,23 +41,32 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::process::ExitCode; -use log::{debug, error}; +use clap::Parser; +use flexi_logger::{FlexiLoggerError, LoggerHandle}; +use log::{debug, error, trace}; use rayon::prelude::*; -use crate::cmdline::{UseColor, ARGS}; +use crate::cmdline::UseColor; use crate::errors::{Error, Result}; use crate::parser::BinaryParser; use crate::ui::ColorBuffer; fn main() -> ExitCode { - lazy_static::initialize(&ARGS); - let _ignored = init_logging().or_else(|r| -> Result<()> { - eprintln!("Error: {}", format_error(&r)); - Ok(()) - }); + let options = cmdline::Options::parse(); + + let _log_handle = match init_logger(&options) { + Ok(h) => h, + + Err(err) => { + eprintln!("Error: {}", format_error(&err)); + return ExitCode::FAILURE; + } + }; + + trace!("{:?}", &options); let mut exit_code = 0_u8; - match run() { + match run(options) { Ok((successes, errors)) => { // Print successful results. for (path, color_buffer) in successes { @@ -86,17 +95,18 @@ fn main() -> ExitCode { ExitCode::from(exit_code) } -type SuccessResults<'args> = Vec<(&'args PathBuf, ColorBuffer)>; -type ErrorResults<'args> = Vec<(&'args PathBuf, Error)>; +type SuccessResults = Vec<(PathBuf, ColorBuffer)>; +type ErrorResults = Vec<(PathBuf, Error)>; -fn run<'args>() -> Result<(SuccessResults<'args>, ErrorResults<'args>)> { +fn run(mut options: cmdline::Options) -> Result<(SuccessResults, ErrorResults)> { use rayon::iter::Either; - let icb_stdout = ColorBuffer::for_stdout(); + let icb_stdout = ColorBuffer::for_stdout(options.color); + + let input_files = core::mem::take(&mut options.input_files); - let result: (Vec<_>, Vec<_>) = ARGS - .arg_file - .iter() + let result: (Vec<_>, Vec<_>) = input_files + .into_iter() // Zip one color buffer with each file to process. .zip(iter::repeat(icb_stdout)) // Collect all inputs before starting processing. @@ -104,7 +114,7 @@ fn run<'args>() -> Result<(SuccessResults<'args>, ErrorResults<'args>)> { .into_par_iter() // Process each file. .map(|(path, mut out)| { - let r = process_file(path, &mut out.color_buffer); + let r = process_file(&path, &mut out.color_buffer, &options); (path, out, r) }) .partition_map(|(path, out, result)| match result { @@ -129,33 +139,34 @@ fn format_error(mut r: &dyn std::error::Error) -> String { text } -fn init_logging() -> Result<()> { - use simplelog::{ColorChoice, Config, LevelFilter, SimpleLogger, TermLogger, TerminalMode}; - - let log_level = if ARGS.flag_verbose { - LevelFilter::Debug - } else { - LevelFilter::Info +fn init_logger(options: &cmdline::Options) -> std::result::Result { + use flexi_logger::{ + colored_default_format, default_format, AdaptiveFormat, LogSpecification, Logger, }; - let log_config = Config::default(); - - match ARGS.flag_color { - UseColor::never => SimpleLogger::init(log_level, log_config)?, + let log_spec = LogSpecification::builder() + .default(if options.verbose { + log::LevelFilter::Trace + } else { + log::LevelFilter::Info + }) + .build(); - UseColor::auto | UseColor::always => TermLogger::init( - log_level, - log_config, - TerminalMode::Stderr, - ColorChoice::Auto, - )?, - } + let logger = Logger::with(log_spec).use_utc(); + let logger = match options.color { + UseColor::Auto => logger.adaptive_format_for_stderr(AdaptiveFormat::Default), + UseColor::Always => logger.format_for_stderr(colored_default_format), + UseColor::Never => logger.format_for_stderr(default_format), + }; - debug!("{:?}", *ARGS); - Ok(()) + logger.start() } -fn process_file(path: &impl AsRef, color_buffer: &mut termcolor::Buffer) -> Result<()> { +fn process_file( + path: &impl AsRef, + color_buffer: &mut termcolor::Buffer, + options: &cmdline::Options, +) -> Result<()> { use goblin::Object; let parser = BinaryParser::open(path.as_ref())?; @@ -163,12 +174,12 @@ fn process_file(path: &impl AsRef, color_buffer: &mut termcolor::Buffer) - let results = match parser.object() { Object::Elf(_elf) => { debug!("Binary file format is 'ELF'."); - elf::analyze_binary(&parser) + elf::analyze_binary(&parser, options) } Object::PE(_pe) => { debug!("Binary file format is 'PE'."); - pe::analyze_binary(&parser) + pe::analyze_binary(&parser, options) } Object::Mach(_mach) => { @@ -181,7 +192,7 @@ fn process_file(path: &impl AsRef, color_buffer: &mut termcolor::Buffer) - Object::Archive(_archive) => { debug!("Binary file format is 'Archive'."); - archive::analyze_binary(&parser) + archive::analyze_binary(&parser, options) } Object::Unknown(_magic) => Err(Error::UnknownBinaryFormat(path.as_ref().into())), @@ -200,6 +211,7 @@ fn process_file(path: &impl AsRef, color_buffer: &mut termcolor::Buffer) - } } - writeln!(color_buffer).map_err(|r| Error::from_io1(r, "writeln", "standard output stream"))?; + writeln!(color_buffer) + .map_err(|r| Error::from_io1(r, "write line", "standard output stream"))?; Ok(()) } diff --git a/src/options.rs b/src/options.rs index 7bbc204..2a9fcd7 100755 --- a/src/options.rs +++ b/src/options.rs @@ -15,8 +15,12 @@ use self::status::{ DisplayInColorTerm, ELFFortifySourceStatus, PEControlFlowGuardLevel, YesNoUnknownStatus, }; -pub trait BinarySecurityOption<'t> { - fn check(&self, parser: &BinaryParser) -> Result>; +pub(crate) trait BinarySecurityOption<'t> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result>; } struct PEDllCharacteristicsBitOption { @@ -27,7 +31,11 @@ struct PEDllCharacteristicsBitOption { } impl<'t> BinarySecurityOption<'t> for PEDllCharacteristicsBitOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { if let goblin::Object::PE(pe) = parser.object() { if let Some(bit_is_set) = pe::dll_characteristics_bit_is_set(pe, self.mask_name, self.mask) @@ -46,7 +54,11 @@ impl<'t> BinarySecurityOption<'t> for PEDllCharacteristicsBitOption { pub(crate) struct PEHasCheckSumOption; impl<'t> BinarySecurityOption<'t> for PEHasCheckSumOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::PE(pe) = parser.object() { pe::has_check_sum(pe) } else { @@ -69,7 +81,11 @@ impl<'t> BinarySecurityOption<'t> for DataExecutionPreventionOption { /// When DEP is supported, a virtual memory page can be marked as non-executable (NX), in which /// case trying to execute any code from that pages will raise an exception, and likely crash /// the application, instead of running arbitrary code. - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result> { if let goblin::Object::PE(_pe) = parser.object() { PEDllCharacteristicsBitOption { name: "DATA-EXEC-PREVENT", @@ -77,7 +93,7 @@ impl<'t> BinarySecurityOption<'t> for DataExecutionPreventionOption { mask: pe::IMAGE_DLLCHARACTERISTICS_NX_COMPAT, present: true, } - .check(parser) + .check(parser, options) } else { Ok(Box::new(YesNoUnknownStatus::unknown("DATA-EXEC-PREVENT"))) } @@ -93,14 +109,18 @@ impl<'t> BinarySecurityOption<'t> for PERunsOnlyInAppContainerOption { /// This option indicates whether the executable must be run in the `AppContainer` /// process-isolation environment, such as a Universal Windows Platform (UWP) or Windows /// Phone 8.x app. - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result> { PEDllCharacteristicsBitOption { name: "RUNS-IN-APP-CONTAINER", mask_name: "IMAGE_DLLCHARACTERISTICS_APPCONTAINER", mask: pe::IMAGE_DLLCHARACTERISTICS_APPCONTAINER, present: true, } - .check(parser) + .check(parser, options) } } @@ -110,7 +130,11 @@ pub(crate) struct RequiresIntegrityCheckOption; impl<'t> BinarySecurityOption<'t> for RequiresIntegrityCheckOption { /// Returns whether the operating system must to verify the digital signature of this executable /// at load time. - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result> { if let goblin::Object::PE(_pe) = parser.object() { PEDllCharacteristicsBitOption { name: "VERIFY-DIGITAL-CERT", @@ -118,7 +142,7 @@ impl<'t> BinarySecurityOption<'t> for RequiresIntegrityCheckOption { mask: pe::IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY, present: true, } - .check(parser) + .check(parser, options) } else { Ok(Box::new(YesNoUnknownStatus::unknown("VERIFY-DIGITAL-CERT"))) } @@ -137,14 +161,18 @@ impl<'t> BinarySecurityOption<'t> for PEEnableManifestHandlingOption { /// application manifest for the newly created process. The new process will not have a default /// activation context, even if there is a manifest inside the executable or placed in the same /// directory as the executable with name `executable-name.exe.manifest`. - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result> { PEDllCharacteristicsBitOption { name: "CONSIDER-MANIFEST", mask_name: "IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", mask: pe::IMAGE_DLLCHARACTERISTICS_NO_ISOLATION, present: false, } - .check(parser) + .check(parser, options) } } @@ -152,7 +180,11 @@ impl<'t> BinarySecurityOption<'t> for PEEnableManifestHandlingOption { pub(crate) struct PEControlFlowGuardOption; impl<'t> BinarySecurityOption<'t> for PEControlFlowGuardOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::PE(pe) = parser.object() { pe::supports_control_flow_guard(pe) } else { @@ -166,7 +198,11 @@ impl<'t> BinarySecurityOption<'t> for PEControlFlowGuardOption { pub(crate) struct PEHandlesAddressesLargerThan2GBOption; impl<'t> BinarySecurityOption<'t> for PEHandlesAddressesLargerThan2GBOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::PE(pe) = parser.object() { YesNoUnknownStatus::new( "HANDLES-ADDR-GT-2GB", @@ -188,7 +224,11 @@ impl<'t> BinarySecurityOption<'t> for AddressSpaceLayoutRandomizationOption { /// When ASLR is supported, the executable should be randomly re-based at load time, enabling /// virtual address allocation randomization, which affects the virtual memory location of heaps, /// stacks, and other operating system allocations. - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { match parser.object() { goblin::Object::PE(pe) => Ok(Box::new(pe::supports_aslr(pe))), goblin::Object::Elf(elf_obj) => Ok(Box::new(elf::supports_aslr(elf_obj))), @@ -201,7 +241,11 @@ impl<'t> BinarySecurityOption<'t> for AddressSpaceLayoutRandomizationOption { pub(crate) struct PESafeStructuredExceptionHandlingOption; impl<'t> BinarySecurityOption<'t> for PESafeStructuredExceptionHandlingOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::PE(pe) = parser.object() { YesNoUnknownStatus::new( "SAFE-SEH", @@ -218,7 +262,11 @@ impl<'t> BinarySecurityOption<'t> for PESafeStructuredExceptionHandlingOption { pub(crate) struct ELFReadOnlyAfterRelocationsOption; impl<'t> BinarySecurityOption<'t> for ELFReadOnlyAfterRelocationsOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::Elf(elf) = parser.object() { YesNoUnknownStatus::new( "READ-ONLY-RELOC", @@ -235,7 +283,11 @@ impl<'t> BinarySecurityOption<'t> for ELFReadOnlyAfterRelocationsOption { pub(crate) struct ELFStackProtectionOption; impl<'t> BinarySecurityOption<'t> for ELFStackProtectionOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = match parser.object() { goblin::Object::Elf(elf_obj) => { YesNoUnknownStatus::new("STACK-PROT", elf::has_stack_protection(elf_obj)) @@ -256,7 +308,11 @@ impl<'t> BinarySecurityOption<'t> for ELFStackProtectionOption { pub(crate) struct ELFImmediateBindingOption; impl<'t> BinarySecurityOption<'t> for ELFImmediateBindingOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + _options: &crate::cmdline::Options, + ) -> Result> { let r = if let goblin::Object::Elf(elf) = parser.object() { YesNoUnknownStatus::new("IMMEDIATE-BIND", elf::requires_immediate_binding(elf)) } else { @@ -277,12 +333,16 @@ impl ELFFortifySourceOption { } impl<'t> BinarySecurityOption<'t> for ELFFortifySourceOption { - fn check(&self, parser: &BinaryParser) -> Result> { + fn check( + &self, + parser: &BinaryParser, + options: &crate::cmdline::Options, + ) -> Result> { if let goblin::Object::Elf(elf) = parser.object() { let libc = if let Some(spec) = self.libc_spec { NeededLibC::from_spec(spec) } else { - NeededLibC::find_needed_by_executable(elf)? + NeededLibC::find_needed_by_executable(elf, options)? }; let result = ELFFortifySourceStatus::new(libc, elf)?; diff --git a/src/options/status.rs b/src/options/status.rs index ab90c04..daeb4f6 100755 --- a/src/options/status.rs +++ b/src/options/status.rs @@ -53,19 +53,12 @@ impl DisplayInColorTerm for YesNoUnknownStatus { }; wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) - .map_err(|r| { - Error::from_io1( - r, - "termcolor::WriteColor::set_color", - "standard output stream", - ) - })?; + .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; write!(wc, "{}{}", marker, self.name) .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; - wc.reset().map_err(|r| { - Error::from_io1(r, "termcolor::WriteColor::reset", "standard output stream") - }) + wc.reset() + .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) } } @@ -92,19 +85,12 @@ impl DisplayInColorTerm for PEControlFlowGuardLevel { }; wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) - .map_err(|r| { - Error::from_io1( - r, - "termcolor::WriteColor::set_color", - "standard output stream", - ) - })?; + .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; write!(wc, "{marker}CONTROL-FLOW-GUARD") .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; - wc.reset().map_err(|r| { - Error::from_io1(r, "termcolor::WriteColor::reset", "standard output stream") - }) + wc.reset() + .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) } } @@ -147,19 +133,12 @@ impl DisplayInColorTerm for ASLRCompatibilityLevel { }; wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) - .map_err(|r| { - Error::from_io1( - r, - "termcolor::WriteColor::set_color", - "standard output stream", - ) - })?; + .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; write!(wc, "{marker}{text}") .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; - wc.reset().map_err(|r| { - Error::from_io1(r, "termcolor::WriteColor::reset", "standard output stream") - }) + wc.reset() + .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) } } @@ -246,22 +225,15 @@ impl DisplayInColorTerm for Pin> { (false, false) => (MARKER_MAYBE, COLOR_UNKNOWN), }; - let set_color_err = |r| { - Error::from_io1( - r, - "termcolor::WriteColor::set_color", - "standard output stream", - ) - }; + let set_color_err = |r| Error::from_io1(r, "set color", "standard output stream"); wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) .map_err(set_color_err)?; write!(wc, "{marker}FORTIFY-SOURCE") .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; - wc.reset().map_err(|r| { - Error::from_io1(r, "termcolor::WriteColor::reset", "standard output stream") - })?; + wc.reset() + .map_err(|r| Error::from_io1(r, "reset", "standard output stream"))?; write!(wc, "(").map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; @@ -284,10 +256,10 @@ impl DisplayInColorTerm for Pin> { separator = ","; } - wc.reset().map_err(|r| { - Error::from_io1(r, "termcolor::WriteColor::reset", "standard output stream") - })?; - writeln!(wc, ")").map_err(|r| Error::from_io1(r, "writeln", "standard output stream"))?; + wc.reset() + .map_err(|r| Error::from_io1(r, "reset", "standard output stream"))?; + writeln!(wc, ")") + .map_err(|r| Error::from_io1(r, "write line", "standard output stream"))?; Ok(()) } } diff --git a/src/parser.rs b/src/parser.rs index 90335c0..22d01ec 100755 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,7 +11,7 @@ use std::fs; use std::path::Path; use log::debug; -use memmap::{Mmap, MmapOptions}; +use memmap2::{Mmap, MmapOptions}; use crate::errors::{Error, Result}; @@ -22,14 +22,14 @@ pub(crate) struct BinaryParser { } impl BinaryParser { - pub fn open(path: impl AsRef) -> Result>> { + pub(crate) fn open(path: impl AsRef) -> Result>> { debug!("Opening binary file '{}'.", path.as_ref().display()); - let file = fs::File::open(&path) - .map_err(|r| Error::from_io1(r, "std::fs::File::open", path.as_ref()))?; + let file = + fs::File::open(&path).map_err(|r| Error::from_io1(r, "open file", path.as_ref()))?; debug!("Mapping binary file '{}'.", path.as_ref().display()); let bytes = unsafe { MmapOptions::new().map(&file) } - .map_err(|r| Error::from_io1(r, "memmap::MmapOptions::map", path.as_ref()))?; + .map_err(|r| Error::from_io1(r, "map file", path.as_ref()))?; let mut result = Box::pin(Self { bytes, @@ -52,10 +52,8 @@ impl BinaryParser { unsafe { ptr::NonNull::from(&result.bytes).as_ptr().as_ref().unwrap() }; debug!("Parsing binary file '{}'.", path.as_ref().display()); - let object = goblin::Object::parse(bytes_ref).map_err(|source| Error::Goblin { - operation: "goblin::Object::parse", - source, - })?; + let object = + goblin::Object::parse(bytes_ref).map_err(|source| Error::ParseFile { source })?; result.as_mut().set_object(Some(object)); Ok(result) diff --git a/src/pe.rs b/src/pe.rs index ec1e782..80ea01d 100755 --- a/src/pe.rs +++ b/src/pe.rs @@ -21,19 +21,23 @@ use crate::options::{ }; use crate::parser::BinaryParser; -pub fn analyze_binary(parser: &BinaryParser) -> Result>> { - let has_checksum = PEHasCheckSumOption.check(parser)?; - let supports_data_execution_prevention = DataExecutionPreventionOption.check(parser)?; - let runs_only_in_app_container = PERunsOnlyInAppContainerOption.check(parser)?; - let enable_manifest_handling = PEEnableManifestHandlingOption.check(parser)?; - let requires_integrity_check = RequiresIntegrityCheckOption.check(parser)?; - let supports_control_flow_guard = PEControlFlowGuardOption.check(parser)?; +pub(crate) fn analyze_binary( + parser: &BinaryParser, + options: &crate::cmdline::Options, +) -> Result>> { + let has_checksum = PEHasCheckSumOption.check(parser, options)?; + let supports_data_execution_prevention = + DataExecutionPreventionOption.check(parser, options)?; + let runs_only_in_app_container = PERunsOnlyInAppContainerOption.check(parser, options)?; + let enable_manifest_handling = PEEnableManifestHandlingOption.check(parser, options)?; + let requires_integrity_check = RequiresIntegrityCheckOption.check(parser, options)?; + let supports_control_flow_guard = PEControlFlowGuardOption.check(parser, options)?; let handles_addresses_larger_than_2_gigabytes = - PEHandlesAddressesLargerThan2GBOption.check(parser)?; + PEHandlesAddressesLargerThan2GBOption.check(parser, options)?; let supports_address_space_layout_randomization = - AddressSpaceLayoutRandomizationOption.check(parser)?; + AddressSpaceLayoutRandomizationOption.check(parser, options)?; let supports_safe_structured_exception_handling = - PESafeStructuredExceptionHandlingOption.check(parser)?; + PESafeStructuredExceptionHandlingOption.check(parser, options)?; Ok(vec![ has_checksum, diff --git a/src/ui.rs b/src/ui.rs index aa981ad..a876178 100755 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,7 +6,7 @@ use std::sync::Arc; -use crate::cmdline::ARGS; +use crate::cmdline::UseColor; use crate::errors::{Error, Result}; /// A color buffer that can should be written-to from a single thread. @@ -19,9 +19,8 @@ pub(crate) struct ColorBuffer { } impl ColorBuffer { - pub fn for_stdout() -> Self { - let buffer_writer = - termcolor::BufferWriter::stdout(termcolor::ColorChoice::from(ARGS.flag_color)); + pub(crate) fn for_stdout(use_color: UseColor) -> Self { + let buffer_writer = termcolor::BufferWriter::stdout(use_color.into()); let color_buffer = buffer_writer.buffer(); Self { @@ -30,14 +29,10 @@ impl ColorBuffer { } } - pub fn print(&self) -> Result<()> { - self.buffer_writer.print(&self.color_buffer).map_err(|r| { - Error::from_io1( - r, - "termcolor::BufferWriter::print", - "standard output stream", - ) - })?; + pub(crate) fn print(&self) -> Result<()> { + self.buffer_writer + .print(&self.color_buffer) + .map_err(|r| Error::from_io1(r, "print", "standard output stream"))?; Ok(()) } }