├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── src ├── archive.rs ├── cmdline.rs ├── command-line-after-help.txt ├── elf.rs ├── elf │ ├── checked_functions.rs │ └── needed_libc.rs ├── errors.rs ├── main.rs ├── options.rs ├── options │ └── status.rs ├── parser.rs ├── pe.rs └── ui.rs └── tools └── x86_64-unknown-freebsd-clang.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # cargo build --release --target=x86_64-unknown-linux-musl 2 | [target.x86_64-unknown-linux-musl] 3 | linker = "ld.lld" 4 | 5 | # cargo build --release --target=aarch64-unknown-linux-musl 6 | [target.aarch64-unknown-linux-musl] 7 | linker = "ld.lld" 8 | 9 | # cargo build --release --target=armv7-unknown-linux-musleabihf 10 | [target.armv7-unknown-linux-musleabihf] 11 | linker = "ld.lld" 12 | 13 | # cargo build --release --target=x86_64-pc-windows-gnu 14 | [target.x86_64-pc-windows-gnu] 15 | linker = "x86_64-w64-mingw32-gcc" 16 | runner = "wine-stable" 17 | 18 | # rustup target add x86_64-unknown-freebsd 19 | # cargo build --release --target=x86_64-unknown-freebsd 20 | [target.x86_64-unknown-freebsd] 21 | linker = "tools/x86_64-unknown-freebsd-clang.sh" 22 | rustflags = ["-Clink-arg=--target=x86_64-unknown-freebsd"] 23 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build-x86_64-unknown-linux-musl: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Update packages data base 20 | run: sudo apt-get update --quiet 21 | 22 | - name: Install C/C++ toolchains 23 | run: sudo apt-get install --quiet --no-install-recommends --yes dialog clang lld musl-dev 24 | 25 | - name: Install rustc target 26 | run: rustup target add x86_64-unknown-linux-musl 27 | 28 | - name: Build 29 | run: cargo build --verbose --target=x86_64-unknown-linux-musl 30 | 31 | - name: Test 32 | run: cargo test --verbose --target=x86_64-unknown-linux-musl 33 | 34 | build-aarch64-unknown-linux-musl: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - name: Update packages data base 41 | run: sudo apt-get update --quiet 42 | 43 | - name: Install C/C++ toolchains 44 | run: sudo apt-get install --quiet --no-install-recommends --yes dialog clang lld musl-dev 45 | 46 | - name: Install rustc target 47 | run: rustup target add aarch64-unknown-linux-musl 48 | 49 | - name: Build 50 | run: cargo build --verbose --target=aarch64-unknown-linux-musl 51 | 52 | build-armv7-unknown-linux-musleabihf: 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v2 57 | 58 | - name: Update packages data base 59 | run: sudo apt-get update --quiet 60 | 61 | - name: Install C/C++ toolchains 62 | run: sudo apt-get install --quiet --no-install-recommends --yes dialog clang lld musl-dev 63 | 64 | - name: Install rustc target 65 | run: rustup target add armv7-unknown-linux-musleabihf 66 | 67 | - name: Build 68 | run: cargo build --verbose --target=armv7-unknown-linux-musleabihf 69 | 70 | build-x86_64-pc-windows-gnu: 71 | runs-on: ubuntu-latest 72 | 73 | steps: 74 | - uses: actions/checkout@v2 75 | 76 | - name: Update packages data base 77 | run: sudo apt-get update --quiet 78 | 79 | - name: Upgrade existing packages 80 | run: sudo apt-get upgrade --quiet --yes 81 | 82 | - name: Install C/C++ toolchains 83 | run: sudo apt-get install --quiet --yes --no-install-recommends dialog clang lld mingw-w64 wine-stable wine64 84 | 85 | - name: Install rustc target 86 | run: rustup target add x86_64-pc-windows-gnu 87 | 88 | - name: Build 89 | run: cargo build --verbose --target=x86_64-pc-windows-gnu 90 | 91 | - name: Test 92 | run: cargo test --verbose --target=x86_64-pc-windows-gnu 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files and executables: 2 | /target/ 3 | 4 | # Backup files generated by rustfmt: 5 | **/*.rs.bk 6 | 7 | # Project files: 8 | /.vscode/ 9 | /*.code-workspace 10 | -------------------------------------------------------------------------------- /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 = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "android-tzdata" 16 | version = "0.1.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 19 | 20 | [[package]] 21 | name = "android_system_properties" 22 | version = "0.1.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.13" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle" 45 | version = "1.0.6" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 48 | 49 | [[package]] 50 | name = "anstyle-parse" 51 | version = "0.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 54 | dependencies = [ 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-query" 60 | version = "1.0.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 63 | dependencies = [ 64 | "windows-sys 0.52.0", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle-wincon" 69 | version = "3.0.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 72 | dependencies = [ 73 | "anstyle", 74 | "windows-sys 0.52.0", 75 | ] 76 | 77 | [[package]] 78 | name = "arrayvec" 79 | version = "0.7.4" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 82 | 83 | [[package]] 84 | name = "autocfg" 85 | version = "1.1.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 88 | 89 | [[package]] 90 | name = "binary-security-check" 91 | version = "1.3.1" 92 | dependencies = [ 93 | "clap", 94 | "dynamic-loader-cache", 95 | "flexi_logger", 96 | "goblin", 97 | "log", 98 | "memmap2", 99 | "once_cell", 100 | "rayon", 101 | "regex", 102 | "scroll", 103 | "termcolor", 104 | "thiserror", 105 | ] 106 | 107 | [[package]] 108 | name = "bumpalo" 109 | version = "3.15.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" 112 | 113 | [[package]] 114 | name = "cc" 115 | version = "1.0.90" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "1.0.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 124 | 125 | [[package]] 126 | name = "chrono" 127 | version = "0.4.35" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" 130 | dependencies = [ 131 | "android-tzdata", 132 | "iana-time-zone", 133 | "num-traits", 134 | "windows-targets 0.52.4", 135 | ] 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "4.5.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" 142 | dependencies = [ 143 | "clap_builder", 144 | "clap_derive", 145 | ] 146 | 147 | [[package]] 148 | name = "clap_builder" 149 | version = "4.5.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 152 | dependencies = [ 153 | "anstream", 154 | "anstyle", 155 | "clap_lex", 156 | "strsim", 157 | ] 158 | 159 | [[package]] 160 | name = "clap_derive" 161 | version = "4.5.3" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" 164 | dependencies = [ 165 | "heck", 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "clap_lex" 173 | version = "0.7.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 176 | 177 | [[package]] 178 | name = "colorchoice" 179 | version = "1.0.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 182 | 183 | [[package]] 184 | name = "core-foundation-sys" 185 | version = "0.8.6" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 188 | 189 | [[package]] 190 | name = "crossbeam-deque" 191 | version = "0.8.5" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 194 | dependencies = [ 195 | "crossbeam-epoch", 196 | "crossbeam-utils", 197 | ] 198 | 199 | [[package]] 200 | name = "crossbeam-epoch" 201 | version = "0.9.18" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 204 | dependencies = [ 205 | "crossbeam-utils", 206 | ] 207 | 208 | [[package]] 209 | name = "crossbeam-utils" 210 | version = "0.8.19" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 213 | 214 | [[package]] 215 | name = "dynamic-loader-cache" 216 | version = "0.1.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "5b8c10501bef83cfef6e19406ea04e09e8ba53a015ce22732bc106939dac7aed" 219 | dependencies = [ 220 | "arrayvec", 221 | "memmap2", 222 | "memoffset", 223 | "nom", 224 | "static_assertions", 225 | "thiserror", 226 | ] 227 | 228 | [[package]] 229 | name = "either" 230 | version = "1.10.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 233 | 234 | [[package]] 235 | name = "flexi_logger" 236 | version = "0.28.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "f248c29a6d4bc5d065c9e9068d858761a0dcd796759f7801cc14db35db23abd8" 239 | dependencies = [ 240 | "chrono", 241 | "glob", 242 | "is-terminal", 243 | "log", 244 | "nu-ansi-term", 245 | "regex", 246 | "thiserror", 247 | ] 248 | 249 | [[package]] 250 | name = "glob" 251 | version = "0.3.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 254 | 255 | [[package]] 256 | name = "goblin" 257 | version = "0.8.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887" 260 | dependencies = [ 261 | "log", 262 | "plain", 263 | "scroll", 264 | ] 265 | 266 | [[package]] 267 | name = "heck" 268 | version = "0.5.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 271 | 272 | [[package]] 273 | name = "hermit-abi" 274 | version = "0.3.9" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 277 | 278 | [[package]] 279 | name = "iana-time-zone" 280 | version = "0.1.60" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 283 | dependencies = [ 284 | "android_system_properties", 285 | "core-foundation-sys", 286 | "iana-time-zone-haiku", 287 | "js-sys", 288 | "wasm-bindgen", 289 | "windows-core", 290 | ] 291 | 292 | [[package]] 293 | name = "iana-time-zone-haiku" 294 | version = "0.1.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 297 | dependencies = [ 298 | "cc", 299 | ] 300 | 301 | [[package]] 302 | name = "is-terminal" 303 | version = "0.4.12" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 306 | dependencies = [ 307 | "hermit-abi", 308 | "libc", 309 | "windows-sys 0.52.0", 310 | ] 311 | 312 | [[package]] 313 | name = "js-sys" 314 | version = "0.3.69" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 317 | dependencies = [ 318 | "wasm-bindgen", 319 | ] 320 | 321 | [[package]] 322 | name = "libc" 323 | version = "0.2.153" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 326 | 327 | [[package]] 328 | name = "log" 329 | version = "0.4.21" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 332 | 333 | [[package]] 334 | name = "memchr" 335 | version = "2.7.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 338 | 339 | [[package]] 340 | name = "memmap2" 341 | version = "0.9.4" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 344 | dependencies = [ 345 | "libc", 346 | ] 347 | 348 | [[package]] 349 | name = "memoffset" 350 | version = "0.9.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 353 | dependencies = [ 354 | "autocfg", 355 | ] 356 | 357 | [[package]] 358 | name = "minimal-lexical" 359 | version = "0.2.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 362 | 363 | [[package]] 364 | name = "nom" 365 | version = "7.1.3" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 368 | dependencies = [ 369 | "memchr", 370 | "minimal-lexical", 371 | ] 372 | 373 | [[package]] 374 | name = "nu-ansi-term" 375 | version = "0.49.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" 378 | dependencies = [ 379 | "windows-sys 0.48.0", 380 | ] 381 | 382 | [[package]] 383 | name = "num-traits" 384 | version = "0.2.18" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 387 | dependencies = [ 388 | "autocfg", 389 | ] 390 | 391 | [[package]] 392 | name = "once_cell" 393 | version = "1.19.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 396 | 397 | [[package]] 398 | name = "plain" 399 | version = "0.2.3" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 402 | 403 | [[package]] 404 | name = "proc-macro2" 405 | version = "1.0.79" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 408 | dependencies = [ 409 | "unicode-ident", 410 | ] 411 | 412 | [[package]] 413 | name = "quote" 414 | version = "1.0.35" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 417 | dependencies = [ 418 | "proc-macro2", 419 | ] 420 | 421 | [[package]] 422 | name = "rayon" 423 | version = "1.10.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 426 | dependencies = [ 427 | "either", 428 | "rayon-core", 429 | ] 430 | 431 | [[package]] 432 | name = "rayon-core" 433 | version = "1.12.1" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 436 | dependencies = [ 437 | "crossbeam-deque", 438 | "crossbeam-utils", 439 | ] 440 | 441 | [[package]] 442 | name = "regex" 443 | version = "1.10.4" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 446 | dependencies = [ 447 | "aho-corasick", 448 | "memchr", 449 | "regex-automata", 450 | "regex-syntax", 451 | ] 452 | 453 | [[package]] 454 | name = "regex-automata" 455 | version = "0.4.6" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 458 | dependencies = [ 459 | "aho-corasick", 460 | "memchr", 461 | "regex-syntax", 462 | ] 463 | 464 | [[package]] 465 | name = "regex-syntax" 466 | version = "0.8.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 469 | 470 | [[package]] 471 | name = "scroll" 472 | version = "0.12.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 475 | dependencies = [ 476 | "scroll_derive", 477 | ] 478 | 479 | [[package]] 480 | name = "scroll_derive" 481 | version = "0.12.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" 484 | dependencies = [ 485 | "proc-macro2", 486 | "quote", 487 | "syn", 488 | ] 489 | 490 | [[package]] 491 | name = "static_assertions" 492 | version = "1.1.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 495 | 496 | [[package]] 497 | name = "strsim" 498 | version = "0.11.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 501 | 502 | [[package]] 503 | name = "syn" 504 | version = "2.0.53" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" 507 | dependencies = [ 508 | "proc-macro2", 509 | "quote", 510 | "unicode-ident", 511 | ] 512 | 513 | [[package]] 514 | name = "termcolor" 515 | version = "1.4.1" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 518 | dependencies = [ 519 | "winapi-util", 520 | ] 521 | 522 | [[package]] 523 | name = "thiserror" 524 | version = "1.0.58" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" 527 | dependencies = [ 528 | "thiserror-impl", 529 | ] 530 | 531 | [[package]] 532 | name = "thiserror-impl" 533 | version = "1.0.58" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" 536 | dependencies = [ 537 | "proc-macro2", 538 | "quote", 539 | "syn", 540 | ] 541 | 542 | [[package]] 543 | name = "unicode-ident" 544 | version = "1.0.12" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 547 | 548 | [[package]] 549 | name = "utf8parse" 550 | version = "0.2.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 553 | 554 | [[package]] 555 | name = "wasm-bindgen" 556 | version = "0.2.92" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 559 | dependencies = [ 560 | "cfg-if", 561 | "wasm-bindgen-macro", 562 | ] 563 | 564 | [[package]] 565 | name = "wasm-bindgen-backend" 566 | version = "0.2.92" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 569 | dependencies = [ 570 | "bumpalo", 571 | "log", 572 | "once_cell", 573 | "proc-macro2", 574 | "quote", 575 | "syn", 576 | "wasm-bindgen-shared", 577 | ] 578 | 579 | [[package]] 580 | name = "wasm-bindgen-macro" 581 | version = "0.2.92" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 584 | dependencies = [ 585 | "quote", 586 | "wasm-bindgen-macro-support", 587 | ] 588 | 589 | [[package]] 590 | name = "wasm-bindgen-macro-support" 591 | version = "0.2.92" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "syn", 598 | "wasm-bindgen-backend", 599 | "wasm-bindgen-shared", 600 | ] 601 | 602 | [[package]] 603 | name = "wasm-bindgen-shared" 604 | version = "0.2.92" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 607 | 608 | [[package]] 609 | name = "winapi" 610 | version = "0.3.9" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 613 | dependencies = [ 614 | "winapi-i686-pc-windows-gnu", 615 | "winapi-x86_64-pc-windows-gnu", 616 | ] 617 | 618 | [[package]] 619 | name = "winapi-i686-pc-windows-gnu" 620 | version = "0.4.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 623 | 624 | [[package]] 625 | name = "winapi-util" 626 | version = "0.1.6" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 629 | dependencies = [ 630 | "winapi", 631 | ] 632 | 633 | [[package]] 634 | name = "winapi-x86_64-pc-windows-gnu" 635 | version = "0.4.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 638 | 639 | [[package]] 640 | name = "windows-core" 641 | version = "0.52.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 644 | dependencies = [ 645 | "windows-targets 0.52.4", 646 | ] 647 | 648 | [[package]] 649 | name = "windows-sys" 650 | version = "0.48.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 653 | dependencies = [ 654 | "windows-targets 0.48.5", 655 | ] 656 | 657 | [[package]] 658 | name = "windows-sys" 659 | version = "0.52.0" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 662 | dependencies = [ 663 | "windows-targets 0.52.4", 664 | ] 665 | 666 | [[package]] 667 | name = "windows-targets" 668 | version = "0.48.5" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 671 | dependencies = [ 672 | "windows_aarch64_gnullvm 0.48.5", 673 | "windows_aarch64_msvc 0.48.5", 674 | "windows_i686_gnu 0.48.5", 675 | "windows_i686_msvc 0.48.5", 676 | "windows_x86_64_gnu 0.48.5", 677 | "windows_x86_64_gnullvm 0.48.5", 678 | "windows_x86_64_msvc 0.48.5", 679 | ] 680 | 681 | [[package]] 682 | name = "windows-targets" 683 | version = "0.52.4" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 686 | dependencies = [ 687 | "windows_aarch64_gnullvm 0.52.4", 688 | "windows_aarch64_msvc 0.52.4", 689 | "windows_i686_gnu 0.52.4", 690 | "windows_i686_msvc 0.52.4", 691 | "windows_x86_64_gnu 0.52.4", 692 | "windows_x86_64_gnullvm 0.52.4", 693 | "windows_x86_64_msvc 0.52.4", 694 | ] 695 | 696 | [[package]] 697 | name = "windows_aarch64_gnullvm" 698 | version = "0.48.5" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 701 | 702 | [[package]] 703 | name = "windows_aarch64_gnullvm" 704 | version = "0.52.4" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 707 | 708 | [[package]] 709 | name = "windows_aarch64_msvc" 710 | version = "0.48.5" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 713 | 714 | [[package]] 715 | name = "windows_aarch64_msvc" 716 | version = "0.52.4" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 719 | 720 | [[package]] 721 | name = "windows_i686_gnu" 722 | version = "0.48.5" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 725 | 726 | [[package]] 727 | name = "windows_i686_gnu" 728 | version = "0.52.4" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 731 | 732 | [[package]] 733 | name = "windows_i686_msvc" 734 | version = "0.48.5" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 737 | 738 | [[package]] 739 | name = "windows_i686_msvc" 740 | version = "0.52.4" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 743 | 744 | [[package]] 745 | name = "windows_x86_64_gnu" 746 | version = "0.48.5" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 749 | 750 | [[package]] 751 | name = "windows_x86_64_gnu" 752 | version = "0.52.4" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 755 | 756 | [[package]] 757 | name = "windows_x86_64_gnullvm" 758 | version = "0.48.5" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 761 | 762 | [[package]] 763 | name = "windows_x86_64_gnullvm" 764 | version = "0.52.4" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 767 | 768 | [[package]] 769 | name = "windows_x86_64_msvc" 770 | version = "0.48.5" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 773 | 774 | [[package]] 775 | name = "windows_x86_64_msvc" 776 | version = "0.52.4" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 779 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2024 Koutheir Attouchi. 2 | # See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | # 4 | # Licensed under the MIT license. This file may not be copied, modified, 5 | # or distributed except according to those terms. 6 | 7 | [package] 8 | name = "binary-security-check" 9 | version = "1.3.1" 10 | authors = ["Koutheir Attouchi "] 11 | license = "MIT" 12 | description = "Analyzer of security features in executable binaries" 13 | edition = "2021" 14 | readme = "README.md" 15 | documentation = "https://docs.rs/binary-security-check" 16 | homepage = "https://codeberg.org/koutheir/binary-security-check.git" 17 | repository = "https://codeberg.org/koutheir/binary-security-check.git" 18 | categories = ["command-line-utilities", "development-tools", "visualization"] 19 | 20 | keywords = [ 21 | "security", 22 | "aslr", 23 | "stack-overflow", 24 | "control-flow-guard", 25 | "fortify-source", 26 | ] 27 | 28 | # The release profile, used for `cargo build --release`. 29 | [profile.release] 30 | opt-level = 3 31 | debug = false 32 | rpath = false 33 | lto = true 34 | debug-assertions = false 35 | codegen-units = 1 36 | panic = 'unwind' 37 | incremental = false 38 | overflow-checks = true 39 | 40 | [dependencies] 41 | thiserror = { version = "1.0" } 42 | goblin = { version = "0.8" } 43 | once_cell = { version = "1.19" } 44 | log = { version = "0.4" } 45 | memmap2 = { version = "0.9" } 46 | rayon = { version = "1.10" } 47 | regex = { version = "1.10" } 48 | scroll = { version = "0.12" } 49 | flexi_logger = { version = "0.28" } 50 | termcolor = { version = "1.4" } 51 | dynamic-loader-cache = { version = "0.1" } 52 | 53 | clap = { version = "4.5", features = [ 54 | "color", 55 | "help", 56 | "usage", 57 | "error-context", 58 | "suggestions", 59 | "derive", 60 | "cargo", 61 | ] } 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2024 Koutheir Attouchi. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/binary-security-check.svg)](https://crates.io/crates/binary-security-check) 2 | [![license](https://img.shields.io/github/license/koutheir/binary-security-check?color=black)](https://raw.githubusercontent.com/koutheir/binary-security-check/master/LICENSE.txt) 3 | 4 | # Analyzer of security features in executable binaries 5 | 6 | `binary-security-check` is a command line utility that analyzes executable 7 | binaries looking for features that make the executable more secure, 8 | or less prone to some vulnerabilities. 9 | 10 | ## Installation instructions 11 | 12 | In order to use this tool on your computer, you need to build it from sources: 13 | 14 | 1. If you don't have a [Rust](https://www.rust-lang.org/) toolchain installed, 15 | then [install one](https://www.rust-lang.org/tools/install). 16 | I recommend installing the latest stable toolchain for your computer. 17 | 18 | 2. Install a C toolchain for your computer. For example on Debian Linux: 19 | ``` 20 | sudo apt-get install build-essential 21 | ``` 22 | 23 | 3. Build the sources: 24 | ``` 25 | cargo install binary-security-check 26 | ``` 27 | 28 | 4. You should be able to run the tool directly: 29 | ``` 30 | binary-security-check -h 31 | ``` 32 | 33 | ## Supported formats 34 | 35 | Different executable formats are currently supported: 36 | 37 | - `ELF` format in 32-bits and 64-bits variants. 38 | It is used, for instance, in Linux and BSD executable programs and shared libraries. 39 | These files usually have either no extension, or the `.so` extension. 40 | - `Archive` format, used in static libraries storing object files. 41 | It is used, for example, in Linux and Windows static libraries. 42 | These files usually have one of the following extensions: `.a`, `.lib`, etc. 43 | - `PE32` format (32-bits variant) and `PE32+` format (64-bits variant) used by 44 | Windows executable programs and shared libraries. 45 | These files usually have one of the following extensions: `.exe`, `.scr`, `.dll`, `.sys`, etc. 46 | 16-bits executable binaries are not supported. 47 | 48 | ## Reported security features: 49 | 50 | The list of security features analyzed by `binary-security-check` depends on the analyzed format. 51 | Each security feature has a keyword identifying it in the report. 52 | 53 | For the `ELF` format, the analyzed features are: 54 | 55 | - Address Space Layout Randomization: `ASLR` option. 56 | - Stack smashing protection: `STACK-PROT` option. 57 | - Executable pages become read-only after relocation: `READ-ONLY-RELOC` option. 58 | - Imported symbols are bound immediately during the loading of the binary: `IMMEDIATE-BIND` option. 59 | - Potentially unsafe C library functions calls are replaced with more secure variants: `FORTIFY-SOURCE` option. 60 | 61 | For the `Archive` format, the analyzed features are: 62 | 63 | - Stack smashing protection: `STACK-PROT` option. 64 | 65 | For `PE32` and `PE32+` formats, the analyzed features are: 66 | 67 | - Address Space Layout Randomization: `ASLR`, `ASLR-EXPENSIVE`, `ASLR-LOW-ENTROPY-LT-2GB`, `ASLR-LOW-ENTROPY`, `ASLR-LT-2GB` options. 68 | - Data Execution Prevention: `DATA-EXEC-PREVENT` option. 69 | - Control Flow Guard: `CONTROL-FLOW-GUARD` option. 70 | - Handling of addresses larger than 2 Gigabytes: `HANDLES-ADDR-GT-2GB` option. 71 | - Executable has a check sum of its data: `CHECKSUM` option. 72 | - Only allow running inside AppContainer: `RUNS-IN-APP-CONTAINER` option. 73 | - Integrity verification is required based on digital signature: `VERIFY-DIGITAL-CERT` option. 74 | - Manifest files must be considered when loading executable: `CONSIDER-MANIFEST` option. 75 | - Safe Structured Exception Handling: `SAFE-SEH` option. 76 | 77 | ## Reporting format 78 | 79 | The program can analyze multiple binary files. 80 | For each file, it displays the file path, and the status of the checked security features. 81 | 82 | The status of the security feature in the binary is indicated by a letter before the keyword: 83 | - `+` means the feature is present/supported. 84 | - `!` means the feature is absent/unsupported. 85 | - `~` means the feature is probably present/supported. 86 | - `?` means the feature status is unknown. 87 | 88 | For example, `!ASLR` means the binary does not support Address Space Layout Randomization. 89 | 90 | ## Usage 91 | 92 | ``` 93 | Usage: binary-security-check [OPTIONS] ... 94 | 95 | Arguments: 96 | ... 97 | Binary files to analyze 98 | 99 | Options: 100 | -v, --verbose 101 | Verbose logging 102 | -c, --color 103 | Use color in standard output [default: auto] [possible values: auto, always, never] 104 | -l, --libc 105 | Path of the C runtime library file 106 | -s, --sysroot 107 | Path of the system root for finding the corresponding C runtime library 108 | -i, --libc-spec 109 | Use an internal list of checked functions as specified by a specification 110 | [possible values: lsb1, lsb1dot1, lsb1dot2, lsb1dot3, lsb2, lsb2dot0dot1, lsb2dot1, lsb3, 111 | lsb3dot1, lsb3dot2, lsb4, lsb4dot1, lsb5] 112 | -n, --no-libc 113 | Assume that input files do not use any C runtime libraries 114 | -h, --help 115 | Print help 116 | -V, --version 117 | Print version 118 | 119 | If --libc-spec is specified, then its value can be one of the following versions 120 | of the Linux Standard Base specifications: 121 | - lsb1: LSB 1.0.0. 122 | - lsb1dot1: LSB 1.1.0. 123 | - lsb1dot2: LSB 1.2.0. 124 | - lsb1dot3: LSB 1.3.0. 125 | - lsb2: LSB 2.0.0. 126 | - lsb2dot0dot1: LSB 2.0.1. 127 | - lsb2dot1: LSB 2.1.0. 128 | - lsb3: LSB 3.0.0. 129 | - lsb3dot1: LSB 3.1.0. 130 | - lsb3dot2: LSB 3.2.0. 131 | - lsb4: LSB 4.0.0. 132 | - lsb4dot1: LSB 4.1.0. 133 | - lsb5: LSB 5.0.0. 134 | 135 | By default, this tool tries to automatically locate the C library in the 136 | following directories: 137 | - /lib/ 138 | - /usr/lib/ 139 | - /lib64/ 140 | - /usr/lib64/ 141 | - /lib32/ 142 | - /usr/lib32/ 143 | 144 | The tools "readelf" and "ldd" can be used to help find the path of the C library 145 | needed by the analyzed files, which is given by the --libc parameter. 146 | ``` 147 | 148 | ## Miscellaneous features 149 | 150 | - Runs on multiple platforms, including Linux, FreeBSD and Windows. 151 | - Supports all binary executable formats independently of which platform is used to run the tool. 152 | - Operates in parallel when sensible. 153 | - Output colored text. 154 | - Support multiple ways to identify binary's dependent C library (if there is one), 155 | including Linux Standard Base (LSB) specifications. 156 | - Designed to be easily extensible. 157 | 158 | # License 159 | 160 | Copyright 2018-2024 Koutheir Attouchi. See the `LICENSE.txt` file 161 | at the top-level directory of this distribution. 162 | Licensed under the MIT license. 163 | This file may not be copied, modified, or distributed except according to those terms. 164 | -------------------------------------------------------------------------------- /src/archive.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use log::{debug, warn}; 8 | 9 | use crate::errors::{Error, Result}; 10 | use crate::options::status::DisplayInColorTerm; 11 | use crate::options::{BinarySecurityOption, ELFStackProtectionOption}; 12 | use crate::parser::BinaryParser; 13 | 14 | pub(crate) fn analyze_binary( 15 | parser: &BinaryParser, 16 | options: &crate::cmdline::Options, 17 | ) -> Result>> { 18 | let has_stack_protection = ELFStackProtectionOption.check(parser, options)?; 19 | Ok(vec![has_stack_protection]) 20 | } 21 | 22 | pub(crate) fn has_stack_protection( 23 | parser: &BinaryParser, 24 | archive: &goblin::archive::Archive, 25 | ) -> Result { 26 | let bytes = parser.bytes(); 27 | for member_name in archive.members() { 28 | let buffer = 29 | archive 30 | .extract(member_name, bytes) 31 | .map_err(|source| Error::ExtractArchiveMember { 32 | member: member_name.into(), 33 | source, 34 | })?; 35 | 36 | let r = member_has_stack_protection(member_name, buffer)?; 37 | if r { 38 | return Ok(true); 39 | } 40 | } 41 | Ok(false) 42 | } 43 | 44 | /// - [`__stack_chk_fail`](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---stack-chk-fail-1.html). 45 | /// - `__stack_chk_fail_local` is present in `libc` when it is stack-protected. 46 | fn member_has_stack_protection(member_name: &str, bytes: &[u8]) -> Result { 47 | use goblin::Object; 48 | 49 | let obj = Object::parse(bytes).map_err(|source| Error::ParseFile { source })?; 50 | 51 | if let Object::Elf(elf) = obj { 52 | // elf.is_object_file() 53 | debug!("Format of archive member '{}' is 'ELF'.", member_name); 54 | // `r` is `true` if any named function or an unspecified-type symbol is 55 | // named '__stack_chk_fail_local' or '__stack_chk_fail'. 56 | let r = elf 57 | .syms 58 | .iter() 59 | .filter_map(|symbol| crate::elf::symbol_is_named_function_or_unspecified(&elf, &symbol)) 60 | .any(|name| name == "__stack_chk_fail" || name == "__stack_chk_fail_local"); 61 | 62 | if r { 63 | debug!("Found function symbol '__stack_chk_fail_local' or '__stack_chk_fail' inside symbols section of member '{}'.", member_name); 64 | } 65 | Ok(r) 66 | } else { 67 | warn!("Format of archive member '{}' is not 'ELF'.", member_name); 68 | Err(Error::UnexpectedBinaryFormat { 69 | expected: "ELF", 70 | name: member_name.into(), 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/cmdline.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use core::fmt; 8 | use std::path::PathBuf; 9 | 10 | use crate::elf; 11 | 12 | const HELP_TEMPLATE: &str = "{before-help}{about-with-newline} 13 | {usage-heading} {usage} 14 | 15 | {all-args}{after-help} 16 | \u{1b}[1m\u{1b}[4mAuthors:\u{1b}[24m\u{1b}[22m 17 | {tab}{author-with-newline}"; 18 | 19 | #[derive(Debug, clap::Parser)] 20 | #[command( 21 | author, 22 | version, 23 | about, 24 | next_line_help = true, 25 | help_template = HELP_TEMPLATE, 26 | after_help = include_str!("command-line-after-help.txt"), 27 | )] 28 | pub(crate) struct Options { 29 | /// Verbose logging. 30 | #[arg(short = 'v', long, global = true, default_value_t = false)] 31 | pub(crate) verbose: bool, 32 | 33 | /// Use color in standard output. 34 | #[arg(short = 'c', long, global = true, value_enum, default_value_t = UseColor::Auto)] 35 | pub(crate) color: UseColor, 36 | 37 | /// Path of the C runtime library file. 38 | #[arg(short = 'l', long, conflicts_with_all = ["sysroot", "libc_spec", "no_libc"])] 39 | pub(crate) libc: Option, 40 | 41 | /// Path of the system root for finding the corresponding C runtime library. 42 | #[arg(short = 's', long, conflicts_with_all = ["libc", "libc_spec", "no_libc"])] 43 | pub(crate) sysroot: Option, 44 | 45 | /// Use an internal list of checked functions as specified by a specification. 46 | #[arg(short = 'i', long, value_enum, conflicts_with_all = ["libc", "sysroot", "no_libc"])] 47 | pub(crate) libc_spec: Option, 48 | 49 | /// Assume that input files do not use any C runtime libraries. 50 | #[arg(short = 'n', long, default_value_t = false, conflicts_with_all = ["libc", "sysroot", "libc_spec"])] 51 | pub(crate) no_libc: bool, 52 | 53 | /// Binary files to analyze. 54 | #[arg(required = true, value_hint = clap::ValueHint::FilePath)] 55 | pub(crate) input_files: Vec, 56 | } 57 | 58 | #[derive(Debug, Copy, Clone, clap::ValueEnum)] 59 | pub(crate) enum UseColor { 60 | Auto, 61 | Always, 62 | Never, 63 | } 64 | 65 | impl From for termcolor::ColorChoice { 66 | fn from(other: UseColor) -> Self { 67 | match other { 68 | UseColor::Auto => termcolor::ColorChoice::Auto, 69 | UseColor::Always => termcolor::ColorChoice::Always, 70 | UseColor::Never => termcolor::ColorChoice::Never, 71 | } 72 | } 73 | } 74 | 75 | // If this changes, then update the command line reference. 76 | #[derive(Debug, Copy, Clone, clap::ValueEnum)] 77 | pub(crate) enum LibCSpec { 78 | LSB1, 79 | LSB1dot1, 80 | LSB1dot2, 81 | LSB1dot3, 82 | LSB2, 83 | LSB2dot0dot1, 84 | LSB2dot1, 85 | LSB3, 86 | LSB3dot1, 87 | LSB3dot2, 88 | LSB4, 89 | LSB4dot1, 90 | LSB5, 91 | } 92 | 93 | impl fmt::Display for LibCSpec { 94 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 95 | let spec_name = match *self { 96 | LibCSpec::LSB1 97 | | LibCSpec::LSB1dot1 98 | | LibCSpec::LSB1dot2 99 | | LibCSpec::LSB1dot3 100 | | LibCSpec::LSB2 101 | | LibCSpec::LSB2dot0dot1 102 | | LibCSpec::LSB2dot1 103 | | LibCSpec::LSB3 104 | | LibCSpec::LSB3dot1 105 | | LibCSpec::LSB3dot2 106 | | LibCSpec::LSB4 107 | | LibCSpec::LSB4dot1 108 | | LibCSpec::LSB5 => "Linux Standard Base", 109 | }; 110 | 111 | let spec_version = match *self { 112 | LibCSpec::LSB1 => "1.0.0", 113 | LibCSpec::LSB1dot1 => "1.1.0", 114 | LibCSpec::LSB1dot2 => "1.2.0", 115 | LibCSpec::LSB1dot3 => "1.3.0", 116 | LibCSpec::LSB2 => "2.0.0", 117 | LibCSpec::LSB2dot0dot1 => "2.0.1", 118 | LibCSpec::LSB2dot1 => "2.1.0", 119 | LibCSpec::LSB3 => "3.0.0", 120 | LibCSpec::LSB3dot1 => "3.1.0", 121 | LibCSpec::LSB3dot2 => "3.2.0", 122 | LibCSpec::LSB4 => "4.0.0", 123 | LibCSpec::LSB4dot1 => "4.1.0", 124 | LibCSpec::LSB5 => "5.0.0", 125 | }; 126 | 127 | write!(f, "{spec_name} {spec_version}") 128 | } 129 | } 130 | 131 | impl LibCSpec { 132 | pub(crate) fn get_functions_with_checked_versions(self) -> &'static [&'static str] { 133 | match self { 134 | LibCSpec::LSB1 135 | | LibCSpec::LSB1dot1 136 | | LibCSpec::LSB1dot2 137 | | LibCSpec::LSB1dot3 138 | | LibCSpec::LSB2 139 | | LibCSpec::LSB2dot0dot1 140 | | LibCSpec::LSB2dot1 141 | | LibCSpec::LSB3 142 | | LibCSpec::LSB3dot1 143 | | LibCSpec::LSB3dot2 => &[], 144 | 145 | LibCSpec::LSB4 | LibCSpec::LSB4dot1 | LibCSpec::LSB5 => { 146 | elf::checked_functions::LSB_4_0_0_FUNCTIONS_WITH_CHECKED_VERSIONS 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/command-line-after-help.txt: -------------------------------------------------------------------------------- 1 | If --libc-spec is specified, then its value can be one of the following versions 2 | of the Linux Standard Base specifications: 3 | - lsb1: LSB 1.0.0. 4 | - lsb1dot1: LSB 1.1.0. 5 | - lsb1dot2: LSB 1.2.0. 6 | - lsb1dot3: LSB 1.3.0. 7 | - lsb2: LSB 2.0.0. 8 | - lsb2dot0dot1: LSB 2.0.1. 9 | - lsb2dot1: LSB 2.1.0. 10 | - lsb3: LSB 3.0.0. 11 | - lsb3dot1: LSB 3.1.0. 12 | - lsb3dot2: LSB 3.2.0. 13 | - lsb4: LSB 4.0.0. 14 | - lsb4dot1: LSB 4.1.0. 15 | - lsb5: LSB 5.0.0. 16 | 17 | By default, this tool tries to automatically locate the C library in the 18 | following directories: 19 | - /lib/ 20 | - /usr/lib/ 21 | - /lib64/ 22 | - /usr/lib64/ 23 | - /lib32/ 24 | - /usr/lib32/ 25 | 26 | The tools "readelf" and "ldd" can be used to help find the path of the C library 27 | needed by the analyzed files, which is given by the --libc parameter. 28 | -------------------------------------------------------------------------------- /src/elf.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | pub(crate) mod checked_functions; 8 | pub(crate) mod needed_libc; 9 | 10 | use std::collections::HashSet; 11 | 12 | use log::{debug, log_enabled, warn}; 13 | 14 | use crate::errors::Result; 15 | use crate::options::status::{ASLRCompatibilityLevel, DisplayInColorTerm}; 16 | use crate::options::{ 17 | AddressSpaceLayoutRandomizationOption, BinarySecurityOption, ELFFortifySourceOption, 18 | ELFImmediateBindingOption, ELFReadOnlyAfterRelocationsOption, ELFStackProtectionOption, 19 | }; 20 | use crate::parser::BinaryParser; 21 | 22 | use self::checked_functions::function_is_checked_version; 23 | use self::needed_libc::NeededLibC; 24 | 25 | pub(crate) fn analyze_binary( 26 | parser: &BinaryParser, 27 | options: &crate::cmdline::Options, 28 | ) -> Result>> { 29 | let supports_address_space_layout_randomization = 30 | AddressSpaceLayoutRandomizationOption.check(parser, options)?; 31 | let has_stack_protection = ELFStackProtectionOption.check(parser, options)?; 32 | let read_only_after_reloc = ELFReadOnlyAfterRelocationsOption.check(parser, options)?; 33 | let immediate_bind = ELFImmediateBindingOption.check(parser, options)?; 34 | 35 | let mut result = vec![ 36 | supports_address_space_layout_randomization, 37 | has_stack_protection, 38 | read_only_after_reloc, 39 | immediate_bind, 40 | ]; 41 | 42 | if !options.no_libc { 43 | let fortify_source = 44 | ELFFortifySourceOption::new(options.libc_spec).check(parser, options)?; 45 | result.push(fortify_source); 46 | } 47 | 48 | Ok(result) 49 | } 50 | 51 | pub(crate) fn get_libc_functions_by_protection<'t>( 52 | elf: &goblin::elf::Elf, 53 | libc_ref: &'t NeededLibC, 54 | ) -> (HashSet<&'t str>, HashSet<&'t str>) { 55 | let imported_functions = elf 56 | .dynsyms 57 | .iter() 58 | .filter_map(|symbol| dynamic_symbol_is_named_imported_function(elf, &symbol)); 59 | 60 | let mut protected_functions = HashSet::<&str>::default(); 61 | let mut unprotected_functions = HashSet::<&str>::default(); 62 | for imported_function in imported_functions { 63 | if function_is_checked_version(imported_function) { 64 | if let Some(unchecked_function) = libc_ref.exports_function(imported_function) { 65 | protected_functions.insert(unchecked_function); 66 | } else { 67 | warn!( 68 | "Checked function '{}' is not exported by the C runtime library. This might indicate a C runtime mismatch.", 69 | imported_function 70 | ); 71 | } 72 | } else if let Some(unchecked_function) = 73 | libc_ref.exports_checked_version_of_function(imported_function) 74 | { 75 | unprotected_functions.insert(unchecked_function); 76 | } 77 | } 78 | 79 | (protected_functions, unprotected_functions) 80 | } 81 | 82 | /// [`ET_EXEC`, `ET_DYN`, `PT_PHDR`](http://refspecs.linux-foundation.org/elf/TIS1.1.pdf). 83 | pub(crate) fn supports_aslr(elf: &goblin::elf::Elf) -> ASLRCompatibilityLevel { 84 | debug!( 85 | "Header type is 'ET_{}'.", 86 | goblin::elf::header::et_to_str(elf.header.e_type) 87 | ); 88 | 89 | match elf.header.e_type { 90 | goblin::elf::header::ET_EXEC => { 91 | // Position-dependent executable. 92 | ASLRCompatibilityLevel::Unsupported 93 | } 94 | 95 | goblin::elf::header::ET_DYN => { 96 | if log_enabled!(log::Level::Debug) { 97 | if elf 98 | .program_headers 99 | .iter() 100 | .any(|ph| ph.p_type == goblin::elf::program_header::PT_PHDR) 101 | { 102 | // Position-independent executable. 103 | debug!("Found type 'PT_PHDR' inside program headers section."); 104 | } else if let Some(dynamic_section) = elf.dynamic.as_ref() { 105 | let dynamic_section_flags_include_pie = dynamic_section.dyns.iter().any(|e| { 106 | (e.d_tag == goblin::elf::dynamic::DT_FLAGS_1) && ((e.d_val & DF_1_PIE) != 0) 107 | }); 108 | 109 | if dynamic_section_flags_include_pie { 110 | // Position-independent executable. 111 | debug!("Bit 'DF_1_PIE' is set in tag 'DT_FLAGS_1' inside dynamic linking information."); 112 | } else { 113 | // Shared library. 114 | debug!("Binary is a shared library with dynamic linking information."); 115 | } 116 | } else { 117 | // Shared library. 118 | debug!("Binary is a shared library without dynamic linking information."); 119 | } 120 | } 121 | 122 | ASLRCompatibilityLevel::Supported 123 | } 124 | 125 | _ => { 126 | debug!("Position-independence could not be determined."); 127 | ASLRCompatibilityLevel::Unknown 128 | } 129 | } 130 | } 131 | 132 | /// [PT_GNU_RELRO](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html). 133 | pub(crate) fn becomes_read_only_after_relocations(elf: &goblin::elf::Elf) -> bool { 134 | let r = elf 135 | .program_headers 136 | .iter() 137 | .any(|ph| ph.p_type == goblin::elf::program_header::PT_GNU_RELRO); 138 | 139 | if r { 140 | debug!("Found type 'PT_GNU_RELRO' inside program headers section."); 141 | } 142 | r 143 | } 144 | 145 | /// [`__stack_chk_fail`](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---stack-chk-fail-1.html). 146 | pub(crate) fn has_stack_protection(elf: &goblin::elf::Elf) -> bool { 147 | let r = elf 148 | .dynsyms 149 | .iter() 150 | // Consider only named functions, and focus on their names. 151 | .filter_map(|symbol| dynamic_symbol_is_named_function(elf, &symbol)) 152 | // Check if any function name corresponds to '__stack_chk_fail'. 153 | .any(|name| name == "__stack_chk_fail"); 154 | 155 | if r { 156 | debug!("Found function symbol '__stack_chk_fail' inside dynamic symbols section."); 157 | } 158 | r 159 | } 160 | 161 | /// Visibility is specified by binding type. 162 | const STV_DEFAULT: u8 = 0; 163 | /// Defined by processor supplements. 164 | //const STV_INTERNAL: u8 = 1; 165 | /// Not visible to other components. 166 | //const STV_HIDDEN: u8 = 2; 167 | /// Visible in other components but not preemptable. 168 | //const STV_PROTECTED: u8 = 3; 169 | 170 | pub(crate) fn dynamic_symbol_is_named_exported_function<'elf>( 171 | elf: &'elf goblin::elf::Elf, 172 | symbol: &goblin::elf::sym::Sym, 173 | ) -> Option<&'elf str> { 174 | // Visibility must be STV_DEFAULT. 175 | if symbol.st_other == STV_DEFAULT { 176 | // Type must be STT_FUNC or STT_GNU_IFUNC. 177 | let st_type = symbol.st_type(); 178 | if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC { 179 | // Binding must be STB_GLOBAL or BSF_WEAK or STB_GNU_UNIQUE. 180 | // Value must not be zero. 181 | let st_bind = symbol.st_bind(); 182 | if (st_bind == goblin::elf::sym::STB_GLOBAL 183 | || st_bind == goblin::elf::sym::STB_WEAK 184 | || st_bind == goblin::elf::sym::STB_GNU_UNIQUE) 185 | && (symbol.st_value != 0) 186 | { 187 | return elf 188 | .dynstrtab 189 | .get_at(symbol.st_name) 190 | .filter(|name| !name.is_empty()); // Only consider non-empty names. 191 | } 192 | } 193 | } 194 | None 195 | } 196 | 197 | /// Position Independent Executable. 198 | pub(crate) const DF_1_PIE: u64 = 0x08_00_00_00; 199 | 200 | pub(crate) fn symbol_is_named_function_or_unspecified<'elf>( 201 | elf: &'elf goblin::elf::Elf, 202 | symbol: &goblin::elf::sym::Sym, 203 | ) -> Option<&'elf str> { 204 | // Type must be STT_FUNC or STT_GNU_IFUNC or STT_NOTYPE. 205 | let st_type = symbol.st_type(); 206 | if st_type == goblin::elf::sym::STT_FUNC 207 | || st_type == goblin::elf::sym::STT_GNU_IFUNC 208 | || st_type == goblin::elf::sym::STT_NOTYPE 209 | { 210 | elf.strtab 211 | .get_at(symbol.st_name) 212 | .filter(|name| !name.is_empty()) // Only consider non-empty names. 213 | } else { 214 | None 215 | } 216 | } 217 | 218 | fn dynamic_symbol_is_named_function<'elf>( 219 | elf: &'elf goblin::elf::Elf, 220 | symbol: &goblin::elf::sym::Sym, 221 | ) -> Option<&'elf str> { 222 | // Type must be STT_FUNC or STT_GNU_IFUNC. 223 | let st_type = symbol.st_type(); 224 | if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC { 225 | elf.dynstrtab 226 | .get_at(symbol.st_name) 227 | .filter(|name| !name.is_empty()) // Only consider non-empty names. 228 | } else { 229 | None 230 | } 231 | } 232 | 233 | fn dynamic_symbol_is_named_imported_function<'elf>( 234 | elf: &'elf goblin::elf::Elf, 235 | symbol: &goblin::elf::sym::Sym, 236 | ) -> Option<&'elf str> { 237 | // Type must be STT_FUNC or STT_GNU_IFUNC. 238 | let st_type = symbol.st_type(); 239 | if st_type == goblin::elf::sym::STT_FUNC || st_type == goblin::elf::sym::STT_GNU_IFUNC { 240 | // Binding must be STB_GLOBAL or BSF_WEAK or STB_GNU_UNIQUE. 241 | // Value must be zero. 242 | let st_bind = symbol.st_bind(); 243 | if (st_bind == goblin::elf::sym::STB_GLOBAL 244 | || st_bind == goblin::elf::sym::STB_WEAK 245 | || st_bind == goblin::elf::sym::STB_GNU_UNIQUE) 246 | && (symbol.st_value == 0) 247 | { 248 | return elf 249 | .dynstrtab 250 | .get_at(symbol.st_name) 251 | .filter(|name| !name.is_empty()); // Only consider non-empty names. 252 | } 253 | } 254 | None 255 | } 256 | 257 | /// - [`DT_BIND_NOW`](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dynamicsection.html). 258 | /// - [`DF_BIND_NOW`, `DF_1_NOW`](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/libc-ddefs.html). 259 | pub(crate) fn requires_immediate_binding(elf: &goblin::elf::Elf) -> bool { 260 | elf.dynamic 261 | // We want to reference the data in `elf.dynamic`, not move it. 262 | .as_ref() 263 | .and_then(|dli| { 264 | // We have dynamic linking information. 265 | // Find the first entry that requires immediate binding. 266 | dli.dyns 267 | .iter() 268 | .find(|dyn_entry| dynamic_linking_info_entry_requires_immediate_binding(dyn_entry)) 269 | }) 270 | .is_some() 271 | } 272 | 273 | fn dynamic_linking_info_entry_requires_immediate_binding( 274 | dyn_entry: &goblin::elf::dynamic::Dyn, 275 | ) -> bool { 276 | match dyn_entry.d_tag { 277 | goblin::elf::dynamic::DT_BIND_NOW => { 278 | debug!("Found tag 'DT_BIND_NOW' inside dynamic linking information."); 279 | true 280 | } 281 | 282 | goblin::elf::dynamic::DT_FLAGS => { 283 | let r = (dyn_entry.d_val & goblin::elf::dynamic::DF_BIND_NOW) != 0; 284 | if r { 285 | debug!("Bit 'DF_BIND_NOW' is set in tag 'DT_FLAGS' inside dynamic linking information."); 286 | } 287 | r 288 | } 289 | 290 | goblin::elf::dynamic::DT_FLAGS_1 => { 291 | let r = (dyn_entry.d_val & goblin::elf::dynamic::DF_1_NOW) != 0; 292 | if r { 293 | debug!( 294 | "Bit 'DF_1_NOW' is set in tag 'DT_FLAGS_1' inside dynamic linking information." 295 | ); 296 | } 297 | r 298 | } 299 | 300 | _ => false, 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/elf/checked_functions.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | #[derive(Debug, Eq, PartialEq, Hash)] 8 | pub(crate) struct CheckedFunction { 9 | checked_name: String, 10 | } 11 | 12 | impl CheckedFunction { 13 | pub(crate) fn from_checked_name(checked_name: &str) -> Self { 14 | Self { 15 | checked_name: String::from(checked_name), 16 | } 17 | } 18 | 19 | pub(crate) fn from_unchecked_name(unchecked_name: &str) -> Self { 20 | Self { 21 | checked_name: format!("__{unchecked_name}_chk"), 22 | } 23 | } 24 | 25 | pub(crate) fn _get_checked_name(&self) -> &str { 26 | &self.checked_name 27 | } 28 | 29 | pub(crate) fn get_unchecked_name(&self) -> &str { 30 | &self.checked_name[2..self.checked_name.len() - 4] 31 | } 32 | } 33 | 34 | /// [Functions prefixed by `__` and suffixed by `_chk`](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/libc.html). 35 | pub(crate) fn function_is_checked_version(name: &str) -> bool { 36 | name.starts_with("__") && name.ends_with("_chk") 37 | } 38 | 39 | /// - [LSB 4.0.0](http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/libc.html). 40 | /// - [LSB 4.1.0](http://refspecs.linux-foundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/libc.html). 41 | /// - [LSB 5.0.0](http://refspecs.linux-foundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/libc.html). 42 | pub(crate) static LSB_4_0_0_FUNCTIONS_WITH_CHECKED_VERSIONS: &[&str] = &[ 43 | "confstr", 44 | "fgets", 45 | "fgets_unlocked", 46 | "fgetws", 47 | "fgetws_unlocked", 48 | "fprintf", 49 | "fwprintf", 50 | "getcwd", 51 | "getgroups", 52 | "gethostname", 53 | "getlogin_r", 54 | "mbsnrtowcs", 55 | "mbsrtowcs", 56 | "mbstowcs", 57 | "memcpy", 58 | "memmove", 59 | "mempcpy", 60 | "memset", 61 | "pread64", 62 | "pread", 63 | "printf", 64 | "read", 65 | "readlink", 66 | "realpath", 67 | "recv", 68 | "recvfrom", 69 | "snprintf", 70 | "sprintf", 71 | "stpcpy", 72 | "stpncpy", 73 | "strcat", 74 | "strcpy", 75 | "strncat", 76 | "strncpy", 77 | "swprintf", 78 | "syslog", 79 | "ttyname_r", 80 | "vfprintf", 81 | "vfwprintf", 82 | "vprintf", 83 | "vsnprintf", 84 | "vsprintf", 85 | "vswprintf", 86 | "vsyslog", 87 | "vwprintf", 88 | "wcpcpy", 89 | "wcpncpy", 90 | "wcrtomb", 91 | "wcscat", 92 | "wcscpy", 93 | "wcsncat", 94 | "wcsncpy", 95 | "wcsnrtombs", 96 | "wcsrtombs", 97 | "wcstombs", 98 | "wctomb", 99 | "wmemcpy", 100 | "wmemmove", 101 | "wmempcpy", 102 | "wmemset", 103 | "wprintf", 104 | ]; 105 | -------------------------------------------------------------------------------- /src/elf/needed_libc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use std::collections::HashSet; 8 | use std::path::{Path, PathBuf}; 9 | use std::sync::OnceLock; 10 | 11 | use log::{debug, log_enabled}; 12 | use regex::{Regex, RegexBuilder}; 13 | 14 | use super::checked_functions::{function_is_checked_version, CheckedFunction}; 15 | use crate::cmdline::LibCSpec; 16 | use crate::errors::{Error, Result}; 17 | use crate::parser::BinaryParser; 18 | 19 | #[derive(Debug)] 20 | pub(crate) struct LibCResolver { 21 | sys_root: PathBuf, 22 | ld_so_cache: Option, 23 | } 24 | 25 | static LIBC_RESOLVER: OnceLock> = OnceLock::new(); 26 | 27 | impl LibCResolver { 28 | pub(crate) fn get(options: &crate::cmdline::Options) -> Result<&'static Self> { 29 | let mut first_err = None; 30 | 31 | let r = LIBC_RESOLVER.get_or_init(|| match Self::new(options) { 32 | Ok(r) => Some(r), 33 | 34 | Err(err) => { 35 | first_err = Some(err); 36 | None 37 | } 38 | }); 39 | 40 | if let Some(err) = first_err { 41 | Err(err) 42 | } else { 43 | r.as_ref().ok_or_else(|| { 44 | let err = std::io::ErrorKind::InvalidData.into(); 45 | Error::from_io1(err, "load linker cache", "") 46 | }) 47 | } 48 | } 49 | 50 | fn new(options: &crate::cmdline::Options) -> Result { 51 | let ld_so_cache = if options.sysroot.is_none() { 52 | Some(dynamic_loader_cache::Cache::load()?) 53 | } else { 54 | None 55 | }; 56 | 57 | let sys_root = options.sysroot.as_deref().unwrap_or_else(|| Path::new("/")); 58 | 59 | Ok(Self { 60 | sys_root: sys_root.into(), 61 | ld_so_cache, 62 | }) 63 | } 64 | 65 | pub(crate) fn find_needed_by_executable(&self, elf: &goblin::elf::Elf) -> Result { 66 | elf.libraries 67 | .iter() 68 | // Only consider libraries whose pattern is known. 69 | .filter(|needed_lib| KNOWN_LIBC_PATTERN.is_match(needed_lib)) 70 | // Parse the library. 71 | .map(|&lib| self.open_compatible_libc(elf, Path::new(lib))) 72 | // Return the first that can be successfully parsed. 73 | .find(Result::is_ok) 74 | // Or return an error in case nothing is found or nothing can be parsed. 75 | .unwrap_or(Err(Error::UnrecognizedNeededLibC)) 76 | } 77 | 78 | fn open_compatible_libc(&self, elf: &goblin::elf::Elf, file_name: &Path) -> Result { 79 | debug!("Looking for libc '{}'.", file_name.display()); 80 | 81 | if let Some(ld_so_cache) = self.ld_so_cache.as_ref() { 82 | let found_in_ld_so_cache = ld_so_cache 83 | .iter()? 84 | .filter_map(dynamic_loader_cache::Result::ok) 85 | .filter_map(|e| (e.file_name == file_name).then_some(e.full_path)) 86 | // For each known libc file location, parse the libc file. 87 | .map(|path| NeededLibC::open_elf_for_architecture(path, elf)) 88 | // Return the first that can be successfully parsed. 89 | .find(Result::is_ok); 90 | 91 | if let Some(libc) = found_in_ld_so_cache { 92 | return libc; 93 | } 94 | } 95 | 96 | KNOWN_LIB_DIRS 97 | .iter() 98 | .flat_map(|&lib| { 99 | KNOWN_PREFIXES 100 | .iter() 101 | .map(move |&prefix| self.sys_root.join(prefix).join(lib).join(file_name)) 102 | }) 103 | // For each known libc file location, parse the libc file. 104 | .map(|path| NeededLibC::open_elf_for_architecture(path, elf)) 105 | // Return the first that can be successfully parsed. 106 | .find(Result::is_ok) 107 | // Or return an error in case nothing is found or nothing can be parsed. 108 | .unwrap_or_else(|| Err(Error::NotFoundNeededLibC(file_name.into()))) 109 | } 110 | } 111 | 112 | pub(crate) struct NeededLibC { 113 | checked_functions: HashSet, 114 | } 115 | 116 | impl NeededLibC { 117 | pub(crate) fn from_spec(spec: LibCSpec) -> Self { 118 | let functions_with_checked_versions = spec.get_functions_with_checked_versions(); 119 | 120 | if log_enabled!(log::Level::Debug) { 121 | debug!("C runtime library is assumed to conform to {}.", spec); 122 | 123 | let mut text = String::default(); 124 | let mut iter = functions_with_checked_versions.iter(); 125 | if let Some(name) = iter.next() { 126 | text.push_str(name); 127 | for name in iter { 128 | text.push(' '); 129 | text.push_str(name); 130 | } 131 | } else { 132 | text.push_str("(none)"); 133 | } 134 | debug!( 135 | "Functions with checked versions, presumably exported by the C runtime library: {}.", 136 | text 137 | ); 138 | } 139 | 140 | Self { 141 | checked_functions: functions_with_checked_versions 142 | .iter() 143 | .map(|name| CheckedFunction::from_unchecked_name(name)) 144 | .collect(), 145 | } 146 | } 147 | 148 | pub(crate) fn open_elf_for_architecture( 149 | path: impl AsRef, 150 | other_elf: &goblin::elf::Elf, 151 | ) -> Result { 152 | let parser = BinaryParser::open(&path)?; 153 | 154 | match parser.object() { 155 | goblin::Object::Elf(elf) => { 156 | if elf.header.e_machine == other_elf.header.e_machine { 157 | debug!( 158 | "C runtime library file format is 'ELF'. Resolved to '{}'.", 159 | path.as_ref().display() 160 | ); 161 | 162 | Ok(Self { 163 | checked_functions: Self::get_checked_functions_elf(elf), 164 | }) 165 | } else { 166 | Err(Error::UnexpectedBinaryArchitecture(path.as_ref().into())) 167 | } 168 | } 169 | 170 | goblin::Object::Unknown(magic) => Err(Error::UnsupportedBinaryFormat { 171 | format: format!("Magic: 0x{magic:016X}"), 172 | path: path.as_ref().into(), 173 | }), 174 | 175 | goblin::Object::PE(_) | goblin::Object::Mach(_) | goblin::Object::Archive(_) => { 176 | Err(Error::UnexpectedBinaryFormat { 177 | expected: "ELF", 178 | name: path.as_ref().into(), 179 | }) 180 | } 181 | 182 | _ => Err(Error::UnsupportedBinaryFormat { 183 | format: "Unknown".into(), 184 | path: path.as_ref().into(), 185 | }), 186 | } 187 | } 188 | 189 | fn get_checked_functions_elf(elf: &goblin::elf::Elf) -> HashSet { 190 | let checked_functions = elf 191 | .dynsyms 192 | .iter() 193 | // Consider only named exported functions, and focus on their name. 194 | .filter_map(|symbol| { 195 | crate::elf::dynamic_symbol_is_named_exported_function(elf, &symbol) 196 | }) 197 | // Consider only functions that are checked versions of libc functions. 198 | .filter(|name| function_is_checked_version(name)) 199 | // Make up a new `CheckedFunction` for each found function. 200 | .map(CheckedFunction::from_checked_name) 201 | .collect::>(); 202 | 203 | if log_enabled!(log::Level::Debug) { 204 | let mut text = String::default(); 205 | let mut iter = checked_functions.iter(); 206 | if let Some(name) = iter.next() { 207 | text.push_str(name.get_unchecked_name()); 208 | for name in iter { 209 | text.push(' '); 210 | text.push_str(name.get_unchecked_name()); 211 | } 212 | } else { 213 | text.push_str("(none)"); 214 | } 215 | debug!( 216 | "Functions with checked versions, exported by the C runtime library: {}.", 217 | text 218 | ); 219 | } 220 | checked_functions 221 | } 222 | 223 | pub(crate) fn exports_function<'this>(&'this self, checked_name: &str) -> Option<&'this str> { 224 | self.checked_functions 225 | .get(&CheckedFunction::from_checked_name(checked_name)) 226 | .map(CheckedFunction::get_unchecked_name) 227 | } 228 | 229 | pub(crate) fn exports_checked_version_of_function<'this>( 230 | &'this self, 231 | unchecked_name: &str, 232 | ) -> Option<&'this str> { 233 | self.checked_functions 234 | .get(&CheckedFunction::from_unchecked_name(unchecked_name)) 235 | .map(CheckedFunction::get_unchecked_name) 236 | } 237 | } 238 | 239 | // If this changes, then update the command line reference. 240 | static KNOWN_PREFIXES: &[&str] = &["", "usr"]; 241 | static KNOWN_LIB_DIRS: &[&str] = &["lib", "lib64", "lib32"]; 242 | 243 | static KNOWN_LIBC_PATTERN: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { 244 | RegexBuilder::new(r"\blib(c|bionic)\b[^/]+$") 245 | .case_insensitive(true) 246 | .multi_line(false) 247 | .dot_matches_new_line(false) 248 | .unicode(true) 249 | .build() 250 | .expect("Invalid static regular expression.") 251 | }); 252 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use std::path::PathBuf; 8 | 9 | pub(crate) type Result = core::result::Result; 10 | 11 | #[derive(Debug, thiserror::Error)] 12 | pub(crate) enum Error { 13 | #[error("failed to {operation}. Path: {path}")] 14 | IO1 { 15 | operation: &'static str, 16 | path: PathBuf, 17 | #[source] 18 | source: std::io::Error, 19 | // Add this when `Backtrace` becomes stable. 20 | //backtrace: Backtrace, 21 | }, 22 | 23 | #[error("failed to parse file")] 24 | ParseFile { 25 | #[source] 26 | source: goblin::error::Error, 27 | // Add this when `Backtrace` becomes stable. 28 | //backtrace: Backtrace, 29 | }, 30 | 31 | #[error("failed to extract archive member: {member}")] 32 | ExtractArchiveMember { 33 | member: String, 34 | #[source] 35 | source: goblin::error::Error, 36 | // Add this when `Backtrace` becomes stable. 37 | //backtrace: Backtrace, 38 | }, 39 | 40 | #[error("logging initialization failed")] 41 | LogInitialization(#[from] log::SetLoggerError), 42 | 43 | #[error("binary format of file '{0}' is not recognized")] 44 | UnknownBinaryFormat(PathBuf), 45 | 46 | #[error("binary format of '{name}' is not {expected}")] 47 | UnexpectedBinaryFormat { 48 | expected: &'static str, 49 | name: PathBuf, 50 | }, 51 | 52 | #[error("architecture of '{0}' is unexpected")] 53 | UnexpectedBinaryArchitecture(PathBuf), 54 | 55 | #[error("binary format '{format}' of file '{path}' is recognized but unsupported")] 56 | UnsupportedBinaryFormat { format: String, path: PathBuf }, 57 | 58 | #[error("dependent C runtime library is not recognized. Consider specifying --sysroot, --libc, --libc-spec or --no-libc")] 59 | UnrecognizedNeededLibC, 60 | 61 | #[error("dependent C runtime library '{0}' was not found")] 62 | NotFoundNeededLibC(PathBuf), 63 | 64 | #[error(transparent)] 65 | FromBytesWithNul(#[from] core::ffi::FromBytesWithNulError), 66 | 67 | #[error(transparent)] 68 | FromBytesUntilNul(#[from] core::ffi::FromBytesUntilNulError), 69 | 70 | #[error(transparent)] 71 | Scroll(#[from] scroll::Error), 72 | 73 | #[error(transparent)] 74 | DynamicLoaderCache(#[from] dynamic_loader_cache::Error), 75 | } 76 | 77 | impl Error { 78 | pub(crate) fn from_io1( 79 | source: std::io::Error, 80 | operation: &'static str, 81 | path: impl Into, 82 | ) -> Self { 83 | Self::IO1 { 84 | operation, 85 | path: path.into(), 86 | source, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | #![doc = include_str!("../README.md")] 8 | #![warn(unsafe_op_in_unsafe_fn)] 9 | #![warn(clippy::all, clippy::pedantic)] 10 | //#![warn(clippy::restriction)] 11 | #![allow( 12 | clippy::upper_case_acronyms, 13 | clippy::unnecessary_wraps, 14 | clippy::missing_docs_in_private_items, 15 | clippy::print_stderr, 16 | clippy::print_stdout, 17 | clippy::implicit_return, 18 | clippy::separated_literal_suffix, 19 | clippy::question_mark_used, 20 | clippy::mod_module_files, 21 | clippy::expect_used, 22 | clippy::module_name_repetitions, 23 | clippy::unwrap_in_result, 24 | clippy::min_ident_chars, 25 | clippy::single_char_lifetime_names, 26 | clippy::single_call_fn, 27 | clippy::absolute_paths, 28 | clippy::similar_names 29 | )] 30 | 31 | mod archive; 32 | mod cmdline; 33 | mod elf; 34 | mod errors; 35 | mod options; 36 | mod parser; 37 | mod pe; 38 | mod ui; 39 | 40 | use core::iter; 41 | use std::io::Write; 42 | use std::path::{Path, PathBuf}; 43 | use std::process::ExitCode; 44 | 45 | use clap::Parser; 46 | use flexi_logger::{FlexiLoggerError, LoggerHandle}; 47 | use log::{debug, error, trace}; 48 | use rayon::prelude::*; 49 | 50 | use crate::cmdline::UseColor; 51 | use crate::errors::{Error, Result}; 52 | use crate::parser::BinaryParser; 53 | use crate::ui::ColorBuffer; 54 | 55 | fn main() -> ExitCode { 56 | let options = cmdline::Options::parse(); 57 | 58 | let _log_handle = match init_logger(&options) { 59 | Ok(h) => h, 60 | 61 | Err(err) => { 62 | eprintln!("Error: {}", format_error(&err)); 63 | return ExitCode::FAILURE; 64 | } 65 | }; 66 | 67 | trace!("{:?}", &options); 68 | 69 | let mut exit_code = 0_u8; 70 | match run(options) { 71 | Ok((successes, errors)) => { 72 | // Print successful results. 73 | for (path, color_buffer) in successes { 74 | print!("{}: ", path.display()); 75 | if color_buffer.print().is_err() { 76 | exit_code = 1; 77 | break; 78 | } 79 | } 80 | 81 | // Print errors related to files. 82 | if exit_code == 0 { 83 | for (path, error) in errors { 84 | exit_code = 1; 85 | error!("{}: {}", path.display(), format_error(&error)); 86 | } 87 | } 88 | } 89 | 90 | Err(error) => { 91 | exit_code = 1; 92 | error!("{}", format_error(&error)); 93 | } 94 | } 95 | 96 | ExitCode::from(exit_code) 97 | } 98 | 99 | type SuccessResults = Vec<(PathBuf, ColorBuffer)>; 100 | type ErrorResults = Vec<(PathBuf, Error)>; 101 | 102 | fn run(mut options: cmdline::Options) -> Result<(SuccessResults, ErrorResults)> { 103 | use rayon::iter::Either; 104 | 105 | let icb_stdout = ColorBuffer::for_stdout(options.color); 106 | 107 | let input_files = core::mem::take(&mut options.input_files); 108 | 109 | let result: (Vec<_>, Vec<_>) = input_files 110 | .into_iter() 111 | // Zip one color buffer with each file to process. 112 | .zip(iter::repeat(icb_stdout)) 113 | // Collect all inputs before starting processing. 114 | .collect::>() 115 | .into_par_iter() 116 | // Process each file. 117 | .map(|(path, mut out)| { 118 | let r = process_file(&path, &mut out.color_buffer, &options); 119 | (path, out, r) 120 | }) 121 | .partition_map(|(path, out, result)| match result { 122 | // On success, retain the path and output buffer, discard the result. 123 | Ok(()) => Either::Left((path, out)), 124 | // On error, retain the path and error, discard the output buffer. 125 | Err(r) => Either::Right((path, r)), 126 | }); 127 | 128 | Ok(result) 129 | } 130 | 131 | fn format_error(mut r: &dyn std::error::Error) -> String { 132 | use core::fmt::Write; 133 | 134 | // Format the error as a message. 135 | let mut text = format!("{r}."); 136 | while let Some(source) = r.source() { 137 | let _ignored = write!(&mut text, " {source}."); 138 | r = source; 139 | } 140 | text 141 | } 142 | 143 | fn init_logger(options: &cmdline::Options) -> std::result::Result { 144 | use flexi_logger::{ 145 | colored_default_format, default_format, AdaptiveFormat, LogSpecification, Logger, 146 | }; 147 | 148 | let log_spec = LogSpecification::builder() 149 | .default(if options.verbose { 150 | log::LevelFilter::Trace 151 | } else { 152 | log::LevelFilter::Info 153 | }) 154 | .build(); 155 | 156 | let logger = Logger::with(log_spec).use_utc(); 157 | let logger = match options.color { 158 | UseColor::Auto => logger.adaptive_format_for_stderr(AdaptiveFormat::Default), 159 | UseColor::Always => logger.format_for_stderr(colored_default_format), 160 | UseColor::Never => logger.format_for_stderr(default_format), 161 | }; 162 | 163 | logger.start() 164 | } 165 | 166 | fn process_file( 167 | path: &impl AsRef, 168 | color_buffer: &mut termcolor::Buffer, 169 | options: &cmdline::Options, 170 | ) -> Result<()> { 171 | use goblin::Object; 172 | 173 | let parser = BinaryParser::open(path.as_ref())?; 174 | 175 | let results = match parser.object() { 176 | Object::Elf(_elf) => { 177 | debug!("Binary file format is 'ELF'."); 178 | elf::analyze_binary(&parser, options) 179 | } 180 | 181 | Object::PE(_pe) => { 182 | debug!("Binary file format is 'PE'."); 183 | pe::analyze_binary(&parser, options) 184 | } 185 | 186 | Object::Mach(_mach) => { 187 | debug!("Binary file format is 'MACH'."); 188 | Err(Error::UnsupportedBinaryFormat { 189 | format: "MACH".into(), 190 | path: path.as_ref().into(), 191 | }) 192 | } 193 | 194 | Object::Archive(_archive) => { 195 | debug!("Binary file format is 'Archive'."); 196 | archive::analyze_binary(&parser, options) 197 | } 198 | 199 | Object::Unknown(_magic) => Err(Error::UnknownBinaryFormat(path.as_ref().into())), 200 | 201 | _ => Err(Error::UnknownBinaryFormat(path.as_ref().into())), 202 | }?; 203 | 204 | // Print results in the color buffer. 205 | let mut iter = results.into_iter(); 206 | if let Some(first) = iter.next() { 207 | first.as_ref().display_in_color_term(color_buffer)?; 208 | for opt in iter { 209 | write!(color_buffer, " ") 210 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 211 | opt.as_ref().display_in_color_term(color_buffer)?; 212 | } 213 | } 214 | 215 | writeln!(color_buffer) 216 | .map_err(|r| Error::from_io1(r, "write line", "standard output stream"))?; 217 | Ok(()) 218 | } 219 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | pub(crate) mod status; 8 | 9 | use crate::elf::needed_libc::{LibCResolver, NeededLibC}; 10 | use crate::errors::Result; 11 | use crate::parser::BinaryParser; 12 | use crate::{archive, cmdline, elf, pe}; 13 | 14 | use self::status::{ 15 | DisplayInColorTerm, ELFFortifySourceStatus, PEControlFlowGuardLevel, YesNoUnknownStatus, 16 | }; 17 | 18 | pub(crate) trait BinarySecurityOption<'t> { 19 | fn check( 20 | &self, 21 | parser: &BinaryParser, 22 | options: &crate::cmdline::Options, 23 | ) -> Result>; 24 | } 25 | 26 | struct PEDllCharacteristicsBitOption { 27 | name: &'static str, 28 | mask_name: &'static str, 29 | mask: u16, 30 | present: bool, 31 | } 32 | 33 | impl<'t> BinarySecurityOption<'t> for PEDllCharacteristicsBitOption { 34 | fn check( 35 | &self, 36 | parser: &BinaryParser, 37 | _options: &crate::cmdline::Options, 38 | ) -> Result> { 39 | if let goblin::Object::PE(pe) = parser.object() { 40 | if let Some(bit_is_set) = 41 | pe::dll_characteristics_bit_is_set(pe, self.mask_name, self.mask) 42 | { 43 | return Ok(Box::new(YesNoUnknownStatus::new( 44 | self.name, 45 | bit_is_set == self.present, 46 | ))); 47 | } 48 | } 49 | Ok(Box::new(YesNoUnknownStatus::unknown(self.name))) 50 | } 51 | } 52 | 53 | #[derive(Default)] 54 | pub(crate) struct PEHasCheckSumOption; 55 | 56 | impl<'t> BinarySecurityOption<'t> for PEHasCheckSumOption { 57 | fn check( 58 | &self, 59 | parser: &BinaryParser, 60 | _options: &crate::cmdline::Options, 61 | ) -> Result> { 62 | let r = if let goblin::Object::PE(pe) = parser.object() { 63 | pe::has_check_sum(pe) 64 | } else { 65 | None 66 | }; 67 | 68 | Ok(Box::new(r.map_or_else( 69 | || YesNoUnknownStatus::unknown("CHECKSUM"), 70 | |r| YesNoUnknownStatus::new("CHECKSUM", r), 71 | ))) 72 | } 73 | } 74 | 75 | #[derive(Default)] 76 | pub(crate) struct DataExecutionPreventionOption; 77 | 78 | impl<'t> BinarySecurityOption<'t> for DataExecutionPreventionOption { 79 | /// Returns information about support of Data Execution Prevention (DEP) in the executable. 80 | /// 81 | /// When DEP is supported, a virtual memory page can be marked as non-executable (NX), in which 82 | /// case trying to execute any code from that pages will raise an exception, and likely crash 83 | /// the application, instead of running arbitrary code. 84 | fn check( 85 | &self, 86 | parser: &BinaryParser, 87 | options: &crate::cmdline::Options, 88 | ) -> Result> { 89 | if let goblin::Object::PE(_pe) = parser.object() { 90 | PEDllCharacteristicsBitOption { 91 | name: "DATA-EXEC-PREVENT", 92 | mask_name: "IMAGE_DLLCHARACTERISTICS_NX_COMPAT", 93 | mask: pe::IMAGE_DLLCHARACTERISTICS_NX_COMPAT, 94 | present: true, 95 | } 96 | .check(parser, options) 97 | } else { 98 | Ok(Box::new(YesNoUnknownStatus::unknown("DATA-EXEC-PREVENT"))) 99 | } 100 | } 101 | } 102 | 103 | #[derive(Default)] 104 | pub(crate) struct PERunsOnlyInAppContainerOption; 105 | 106 | impl<'t> BinarySecurityOption<'t> for PERunsOnlyInAppContainerOption { 107 | /// Returns information about the requirement to run this executable inside `AppContainer`. 108 | /// 109 | /// This option indicates whether the executable must be run in the `AppContainer` 110 | /// process-isolation environment, such as a Universal Windows Platform (UWP) or Windows 111 | /// Phone 8.x app. 112 | fn check( 113 | &self, 114 | parser: &BinaryParser, 115 | options: &crate::cmdline::Options, 116 | ) -> Result> { 117 | PEDllCharacteristicsBitOption { 118 | name: "RUNS-IN-APP-CONTAINER", 119 | mask_name: "IMAGE_DLLCHARACTERISTICS_APPCONTAINER", 120 | mask: pe::IMAGE_DLLCHARACTERISTICS_APPCONTAINER, 121 | present: true, 122 | } 123 | .check(parser, options) 124 | } 125 | } 126 | 127 | #[derive(Default)] 128 | pub(crate) struct RequiresIntegrityCheckOption; 129 | 130 | impl<'t> BinarySecurityOption<'t> for RequiresIntegrityCheckOption { 131 | /// Returns whether the operating system must to verify the digital signature of this executable 132 | /// at load time. 133 | fn check( 134 | &self, 135 | parser: &BinaryParser, 136 | options: &crate::cmdline::Options, 137 | ) -> Result> { 138 | if let goblin::Object::PE(_pe) = parser.object() { 139 | PEDllCharacteristicsBitOption { 140 | name: "VERIFY-DIGITAL-CERT", 141 | mask_name: "IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY", 142 | mask: pe::IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY, 143 | present: true, 144 | } 145 | .check(parser, options) 146 | } else { 147 | Ok(Box::new(YesNoUnknownStatus::unknown("VERIFY-DIGITAL-CERT"))) 148 | } 149 | } 150 | } 151 | 152 | #[derive(Default)] 153 | pub(crate) struct PEEnableManifestHandlingOption; 154 | 155 | impl<'t> BinarySecurityOption<'t> for PEEnableManifestHandlingOption { 156 | /// Returns whether the operating system is allowed to consider manifest files when loading 157 | /// this executable. 158 | /// 159 | /// Enabling this causes the operating system to do manifest lookup and loads. 160 | /// When isolation is disabled for an executable, the Windows loader will not attempt to find an 161 | /// application manifest for the newly created process. The new process will not have a default 162 | /// activation context, even if there is a manifest inside the executable or placed in the same 163 | /// directory as the executable with name `executable-name.exe.manifest`. 164 | fn check( 165 | &self, 166 | parser: &BinaryParser, 167 | options: &crate::cmdline::Options, 168 | ) -> Result> { 169 | PEDllCharacteristicsBitOption { 170 | name: "CONSIDER-MANIFEST", 171 | mask_name: "IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", 172 | mask: pe::IMAGE_DLLCHARACTERISTICS_NO_ISOLATION, 173 | present: false, 174 | } 175 | .check(parser, options) 176 | } 177 | } 178 | 179 | #[derive(Default)] 180 | pub(crate) struct PEControlFlowGuardOption; 181 | 182 | impl<'t> BinarySecurityOption<'t> for PEControlFlowGuardOption { 183 | fn check( 184 | &self, 185 | parser: &BinaryParser, 186 | _options: &crate::cmdline::Options, 187 | ) -> Result> { 188 | let r = if let goblin::Object::PE(pe) = parser.object() { 189 | pe::supports_control_flow_guard(pe) 190 | } else { 191 | PEControlFlowGuardLevel::Unknown 192 | }; 193 | Ok(Box::new(r)) 194 | } 195 | } 196 | 197 | #[derive(Default)] 198 | pub(crate) struct PEHandlesAddressesLargerThan2GBOption; 199 | 200 | impl<'t> BinarySecurityOption<'t> for PEHandlesAddressesLargerThan2GBOption { 201 | fn check( 202 | &self, 203 | parser: &BinaryParser, 204 | _options: &crate::cmdline::Options, 205 | ) -> Result> { 206 | let r = if let goblin::Object::PE(pe) = parser.object() { 207 | YesNoUnknownStatus::new( 208 | "HANDLES-ADDR-GT-2GB", 209 | pe::handles_addresses_larger_than_2_gigabytes(pe), 210 | ) 211 | } else { 212 | YesNoUnknownStatus::unknown("HANDLES-ADDR-GT-2GB") 213 | }; 214 | Ok(Box::new(r)) 215 | } 216 | } 217 | 218 | #[derive(Default)] 219 | pub(crate) struct AddressSpaceLayoutRandomizationOption; 220 | 221 | impl<'t> BinarySecurityOption<'t> for AddressSpaceLayoutRandomizationOption { 222 | /// Returns the level of support of Address Space Layout Randomization (ASLR). 223 | /// 224 | /// When ASLR is supported, the executable should be randomly re-based at load time, enabling 225 | /// virtual address allocation randomization, which affects the virtual memory location of heaps, 226 | /// stacks, and other operating system allocations. 227 | fn check( 228 | &self, 229 | parser: &BinaryParser, 230 | _options: &crate::cmdline::Options, 231 | ) -> Result> { 232 | match parser.object() { 233 | goblin::Object::PE(pe) => Ok(Box::new(pe::supports_aslr(pe))), 234 | goblin::Object::Elf(elf_obj) => Ok(Box::new(elf::supports_aslr(elf_obj))), 235 | _ => Ok(Box::new(YesNoUnknownStatus::unknown("ASLR"))), 236 | } 237 | } 238 | } 239 | 240 | #[derive(Default)] 241 | pub(crate) struct PESafeStructuredExceptionHandlingOption; 242 | 243 | impl<'t> BinarySecurityOption<'t> for PESafeStructuredExceptionHandlingOption { 244 | fn check( 245 | &self, 246 | parser: &BinaryParser, 247 | _options: &crate::cmdline::Options, 248 | ) -> Result> { 249 | let r = if let goblin::Object::PE(pe) = parser.object() { 250 | YesNoUnknownStatus::new( 251 | "SAFE-SEH", 252 | pe::has_safe_structured_exception_handlers(parser, pe), 253 | ) 254 | } else { 255 | YesNoUnknownStatus::unknown("SAFE-SEH") 256 | }; 257 | Ok(Box::new(r)) 258 | } 259 | } 260 | 261 | #[derive(Default)] 262 | pub(crate) struct ELFReadOnlyAfterRelocationsOption; 263 | 264 | impl<'t> BinarySecurityOption<'t> for ELFReadOnlyAfterRelocationsOption { 265 | fn check( 266 | &self, 267 | parser: &BinaryParser, 268 | _options: &crate::cmdline::Options, 269 | ) -> Result> { 270 | let r = if let goblin::Object::Elf(elf) = parser.object() { 271 | YesNoUnknownStatus::new( 272 | "READ-ONLY-RELOC", 273 | elf::becomes_read_only_after_relocations(elf), 274 | ) 275 | } else { 276 | YesNoUnknownStatus::unknown("READ-ONLY-RELOC") 277 | }; 278 | Ok(Box::new(r)) 279 | } 280 | } 281 | 282 | #[derive(Default)] 283 | pub(crate) struct ELFStackProtectionOption; 284 | 285 | impl<'t> BinarySecurityOption<'t> for ELFStackProtectionOption { 286 | fn check( 287 | &self, 288 | parser: &BinaryParser, 289 | _options: &crate::cmdline::Options, 290 | ) -> Result> { 291 | let r = match parser.object() { 292 | goblin::Object::Elf(elf_obj) => { 293 | YesNoUnknownStatus::new("STACK-PROT", elf::has_stack_protection(elf_obj)) 294 | } 295 | 296 | goblin::Object::Archive(archive) => { 297 | let r = archive::has_stack_protection(parser, archive)?; 298 | YesNoUnknownStatus::new("STACK-PROT", r) 299 | } 300 | 301 | _ => YesNoUnknownStatus::unknown("STACK-PROT"), 302 | }; 303 | Ok(Box::new(r)) 304 | } 305 | } 306 | 307 | #[derive(Default)] 308 | pub(crate) struct ELFImmediateBindingOption; 309 | 310 | impl<'t> BinarySecurityOption<'t> for ELFImmediateBindingOption { 311 | fn check( 312 | &self, 313 | parser: &BinaryParser, 314 | _options: &crate::cmdline::Options, 315 | ) -> Result> { 316 | let r = if let goblin::Object::Elf(elf) = parser.object() { 317 | YesNoUnknownStatus::new("IMMEDIATE-BIND", elf::requires_immediate_binding(elf)) 318 | } else { 319 | YesNoUnknownStatus::unknown("IMMEDIATE-BIND") 320 | }; 321 | Ok(Box::new(r)) 322 | } 323 | } 324 | 325 | pub(crate) struct ELFFortifySourceOption { 326 | libc_spec: Option, 327 | } 328 | 329 | impl ELFFortifySourceOption { 330 | pub(crate) fn new(libc_spec: Option) -> Self { 331 | Self { libc_spec } 332 | } 333 | } 334 | 335 | impl<'t> BinarySecurityOption<'t> for ELFFortifySourceOption { 336 | fn check( 337 | &self, 338 | parser: &BinaryParser, 339 | options: &crate::cmdline::Options, 340 | ) -> Result> { 341 | if let goblin::Object::Elf(elf) = parser.object() { 342 | let libc = if let Some(spec) = self.libc_spec { 343 | NeededLibC::from_spec(spec) 344 | } else if let Some(path) = &options.libc { 345 | NeededLibC::open_elf_for_architecture(path, elf)? 346 | } else { 347 | LibCResolver::get(options)?.find_needed_by_executable(elf)? 348 | }; 349 | 350 | let result = ELFFortifySourceStatus::new(libc, elf)?; 351 | Ok(Box::new(result)) 352 | } else { 353 | Ok(Box::new(YesNoUnknownStatus::unknown("FORTIFY-SOURCE"))) 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/options/status.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use core::marker::PhantomPinned; 8 | use core::pin::Pin; 9 | use core::ptr::NonNull; 10 | use std::collections::HashSet; 11 | 12 | use crate::elf; 13 | use crate::elf::needed_libc::NeededLibC; 14 | use crate::errors::{Error, Result}; 15 | 16 | pub(crate) const MARKER_GOOD: char = '+'; 17 | pub(crate) const MARKER_BAD: char = '!'; 18 | pub(crate) const MARKER_MAYBE: char = '~'; 19 | pub(crate) const MARKER_UNKNOWN: char = '?'; 20 | 21 | pub(crate) const COLOR_GOOD: termcolor::Color = termcolor::Color::Green; 22 | pub(crate) const COLOR_BAD: termcolor::Color = termcolor::Color::Red; 23 | pub(crate) const COLOR_UNKNOWN: termcolor::Color = termcolor::Color::Yellow; 24 | 25 | pub(crate) trait DisplayInColorTerm { 26 | fn display_in_color_term(&self, wc: &mut dyn termcolor::WriteColor) -> Result<()>; 27 | } 28 | 29 | pub(crate) struct YesNoUnknownStatus { 30 | name: &'static str, 31 | status: Option, 32 | } 33 | 34 | impl YesNoUnknownStatus { 35 | pub(crate) fn new(name: &'static str, yes_or_no: bool) -> Self { 36 | Self { 37 | name, 38 | status: Some(yes_or_no), 39 | } 40 | } 41 | 42 | pub(crate) fn unknown(name: &'static str) -> Self { 43 | Self { name, status: None } 44 | } 45 | } 46 | 47 | impl DisplayInColorTerm for YesNoUnknownStatus { 48 | fn display_in_color_term(&self, wc: &mut dyn termcolor::WriteColor) -> Result<()> { 49 | let (marker, color) = match self.status { 50 | Some(true) => (MARKER_GOOD, COLOR_GOOD), 51 | Some(false) => (MARKER_BAD, COLOR_BAD), 52 | None => (MARKER_UNKNOWN, COLOR_UNKNOWN), 53 | }; 54 | 55 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) 56 | .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; 57 | 58 | write!(wc, "{}{}", marker, self.name) 59 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 60 | wc.reset() 61 | .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) 62 | } 63 | } 64 | 65 | /// [Control Flow Guard](https://docs.microsoft.com/en-us/cpp/build/reference/guard-enable-guard-checks). 66 | pub(crate) enum PEControlFlowGuardLevel { 67 | /// Control Flow Guard support is unknown. 68 | Unknown, 69 | /// Control Flow Guard is unsupported. 70 | Unsupported, 71 | /// Control Flow Guard is supported, but cannot take effect. 72 | /// This is usually because the executable cannot be relocated at runtime. 73 | Ineffective, 74 | /// Control Flow Guard is supported. 75 | Supported, 76 | } 77 | 78 | impl DisplayInColorTerm for PEControlFlowGuardLevel { 79 | fn display_in_color_term(&self, wc: &mut dyn termcolor::WriteColor) -> Result<()> { 80 | let (marker, color) = match *self { 81 | PEControlFlowGuardLevel::Unknown => (MARKER_UNKNOWN, COLOR_UNKNOWN), 82 | PEControlFlowGuardLevel::Unsupported => (MARKER_BAD, COLOR_BAD), 83 | PEControlFlowGuardLevel::Ineffective => (MARKER_MAYBE, COLOR_UNKNOWN), 84 | PEControlFlowGuardLevel::Supported => (MARKER_GOOD, COLOR_GOOD), 85 | }; 86 | 87 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) 88 | .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; 89 | 90 | write!(wc, "{marker}CONTROL-FLOW-GUARD") 91 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 92 | wc.reset() 93 | .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) 94 | } 95 | } 96 | 97 | pub(crate) enum ASLRCompatibilityLevel { 98 | /// Address Space Layout Randomization support is unknown. 99 | Unknown, 100 | /// Address Space Layout Randomization is unsupported. 101 | Unsupported, 102 | /// Address Space Layout Randomization is supported, but might be expensive. 103 | /// This usually happens when an executable has a preferred base address explicitly specified. 104 | Expensive, 105 | /// Address Space Layout Randomization is supported, but with a low entropy, and only in 106 | /// addresses below 2 Gigabytes. 107 | SupportedLowEntropyBelow2G, 108 | /// Address Space Layout Randomization is supported, but with a low entropy. 109 | SupportedLowEntropy, 110 | /// Address Space Layout Randomization is supported with high entropy, but only in addresses 111 | /// below 2 Gigabytes. 112 | SupportedBelow2G, 113 | /// Address Space Layout Randomization is supported (with high entropy for PE32/PE32+). 114 | Supported, 115 | } 116 | 117 | impl DisplayInColorTerm for ASLRCompatibilityLevel { 118 | fn display_in_color_term(&self, wc: &mut dyn termcolor::WriteColor) -> Result<()> { 119 | let (marker, color, text) = match *self { 120 | ASLRCompatibilityLevel::Unknown => (MARKER_UNKNOWN, COLOR_UNKNOWN, "ASLR"), 121 | ASLRCompatibilityLevel::Unsupported => (MARKER_BAD, COLOR_BAD, "ASLR"), 122 | ASLRCompatibilityLevel::Expensive => (MARKER_MAYBE, COLOR_UNKNOWN, "ASLR-EXPENSIVE"), 123 | ASLRCompatibilityLevel::SupportedLowEntropyBelow2G => { 124 | (MARKER_MAYBE, COLOR_UNKNOWN, "ASLR-LOW-ENTROPY-LT-2GB") 125 | } 126 | ASLRCompatibilityLevel::SupportedLowEntropy => { 127 | (MARKER_MAYBE, COLOR_UNKNOWN, "ASLR-LOW-ENTROPY") 128 | } 129 | ASLRCompatibilityLevel::SupportedBelow2G => { 130 | (MARKER_MAYBE, COLOR_UNKNOWN, "ASLR-LT-2GB") 131 | } 132 | ASLRCompatibilityLevel::Supported => (MARKER_GOOD, COLOR_GOOD, "ASLR"), 133 | }; 134 | 135 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) 136 | .map_err(|r| Error::from_io1(r, "set color", "standard output stream"))?; 137 | 138 | write!(wc, "{marker}{text}") 139 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 140 | wc.reset() 141 | .map_err(|r| Error::from_io1(r, "reset", "standard output stream")) 142 | } 143 | } 144 | 145 | pub(crate) struct ELFFortifySourceStatus { 146 | libc: NeededLibC, 147 | protected_functions: HashSet<&'static str>, 148 | unprotected_functions: HashSet<&'static str>, 149 | _pin: PhantomPinned, 150 | } 151 | 152 | impl ELFFortifySourceStatus { 153 | pub(crate) fn new(libc: NeededLibC, elf_object: &goblin::elf::Elf) -> Result>> { 154 | let mut result = Box::pin(Self { 155 | libc, 156 | protected_functions: HashSet::default(), 157 | unprotected_functions: HashSet::default(), 158 | _pin: PhantomPinned, 159 | }); 160 | 161 | // SAFETY: 162 | // `result` is now allocated, initialized and pinned on the heap. 163 | // Its location is therefore stable, and we can store references to it 164 | // in other places. 165 | // 166 | // Construct a reference to `result.libc` that lives for the 'static 167 | // life time: 168 | // &ref => pointer => 'static ref 169 | // 170 | // This is safe because the `Drop` implementation drops the fields 171 | // `Self::protected_functions` and `Self::unprotected_functions` 172 | // before the field `Self::libc`. 173 | let libc_ref: &'static NeededLibC = 174 | unsafe { NonNull::from(&result.libc).as_ptr().as_ref().unwrap() }; 175 | 176 | let (prot_fn, unprot_fn) = elf::get_libc_functions_by_protection(elf_object, libc_ref); 177 | 178 | // SAFETY: Storing to the field `protected_functions` does not move `result`. 179 | unsafe { Pin::get_unchecked_mut(result.as_mut()) }.protected_functions = prot_fn; 180 | 181 | // SAFETY: Storing to the field `unprotected_functions` does not move `result`. 182 | unsafe { Pin::get_unchecked_mut(result.as_mut()) }.unprotected_functions = unprot_fn; 183 | 184 | Ok(result) 185 | } 186 | 187 | fn drop_pinned(mut self: Pin<&mut Self>) { 188 | // SAFETY: Drop fields `protected_functions` and `unprotected_functions` 189 | // before field `libc` is dropped. 190 | let this = Pin::as_mut(&mut self); 191 | 192 | // SAFETY: Calling `HashSet::clear()` does not move `this`. 193 | let this = unsafe { Pin::get_unchecked_mut(this) }; 194 | 195 | this.protected_functions.clear(); 196 | this.unprotected_functions.clear(); 197 | } 198 | } 199 | 200 | impl Drop for ELFFortifySourceStatus { 201 | fn drop(&mut self) { 202 | // SAFETY: All instances of `Self` are pinned. 203 | unsafe { Pin::new_unchecked(self) }.drop_pinned(); 204 | } 205 | } 206 | 207 | impl DisplayInColorTerm for Pin> { 208 | fn display_in_color_term(&self, wc: &mut dyn termcolor::WriteColor) -> Result<()> { 209 | let no_protected_functions = self.protected_functions.is_empty(); 210 | let no_unprotected_functions = self.unprotected_functions.is_empty(); 211 | 212 | let (marker, color) = match (no_protected_functions, no_unprotected_functions) { 213 | // Neither protected not unprotected functions are used. The binary can still be secure, 214 | // if it does not use these functions. 215 | (true, true) => (MARKER_UNKNOWN, COLOR_UNKNOWN), 216 | // Only unprotected functions are used. 217 | (true, false) => (MARKER_BAD, COLOR_BAD), 218 | // Only protected functions are used. 219 | (false, true) => (MARKER_GOOD, COLOR_GOOD), 220 | // Both protected and unprotected functions are used. This usually indicates a compiler 221 | // that, through static analysis, proves that some usage of the unprotected functions 222 | // is actually safe, and for those instances, does not call the protected functions. 223 | // It can also indicate that multiple object files have been compiled with different 224 | // compiler flags (with and without `FORTIFY_SOURCE`) then linked together. 225 | (false, false) => (MARKER_MAYBE, COLOR_UNKNOWN), 226 | }; 227 | 228 | let set_color_err = |r| Error::from_io1(r, "set color", "standard output stream"); 229 | 230 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(color))) 231 | .map_err(set_color_err)?; 232 | 233 | write!(wc, "{marker}FORTIFY-SOURCE") 234 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 235 | wc.reset() 236 | .map_err(|r| Error::from_io1(r, "reset", "standard output stream"))?; 237 | 238 | write!(wc, "(").map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 239 | 240 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(COLOR_GOOD))) 241 | .map_err(set_color_err)?; 242 | 243 | let mut separator = ""; 244 | for &name in &self.protected_functions { 245 | write!(wc, "{separator}{MARKER_GOOD}{name}") 246 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 247 | separator = ","; 248 | } 249 | 250 | wc.set_color(termcolor::ColorSpec::new().set_fg(Some(COLOR_BAD))) 251 | .map_err(set_color_err)?; 252 | 253 | for &name in &self.unprotected_functions { 254 | write!(wc, "{separator}{MARKER_BAD}{name}") 255 | .map_err(|r| Error::from_io1(r, "write", "standard output stream"))?; 256 | separator = ","; 257 | } 258 | 259 | wc.reset() 260 | .map_err(|r| Error::from_io1(r, "reset", "standard output stream"))?; 261 | writeln!(wc, ")") 262 | .map_err(|r| Error::from_io1(r, "write line", "standard output stream"))?; 263 | Ok(()) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use core::marker::PhantomPinned; 8 | use core::pin::Pin; 9 | use core::ptr; 10 | use std::fs; 11 | use std::path::Path; 12 | 13 | use log::debug; 14 | use memmap2::{Mmap, MmapOptions}; 15 | 16 | use crate::errors::{Error, Result}; 17 | 18 | pub(crate) struct BinaryParser { 19 | bytes: Mmap, 20 | object: Option>, 21 | _pin: PhantomPinned, 22 | } 23 | 24 | impl BinaryParser { 25 | pub(crate) fn open(path: impl AsRef) -> Result>> { 26 | debug!("Opening binary file '{}'.", path.as_ref().display()); 27 | let file = 28 | fs::File::open(&path).map_err(|r| Error::from_io1(r, "open file", path.as_ref()))?; 29 | 30 | let bytes = unsafe { MmapOptions::new().map(&file) } 31 | .map_err(|r| Error::from_io1(r, "map file", path.as_ref()))?; 32 | 33 | let mut result = Box::pin(Self { 34 | bytes, 35 | object: None, 36 | _pin: PhantomPinned, 37 | }); 38 | 39 | // SAFETY: 40 | // `result` is now allocated, initialized and pinned on the heap. 41 | // Its location is therefore stable, and we can store references to it 42 | // in other places. 43 | // 44 | // Construct a reference to `result.bytes` that lives for the 'static 45 | // life time: 46 | // &ref => pointer => 'static ref 47 | // 48 | // This is safe because the `Drop` implementation drops `Self::object` 49 | // before `Self::bytes`. 50 | let bytes_ref: &'static Mmap = 51 | unsafe { ptr::NonNull::from(&result.bytes).as_ptr().as_ref().unwrap() }; 52 | 53 | debug!("Parsing binary file '{}'.", path.as_ref().display()); 54 | let object = 55 | goblin::Object::parse(bytes_ref).map_err(|source| Error::ParseFile { source })?; 56 | 57 | result.as_mut().set_object(Some(object)); 58 | Ok(result) 59 | } 60 | 61 | pub(crate) fn object(&self) -> &goblin::Object { 62 | // SAFETY: All instances of `Self` that are created and still in scope 63 | // must have `Some(_)` in the `object` field. 64 | self.object.as_ref().unwrap() 65 | } 66 | 67 | pub(crate) fn bytes(&self) -> &[u8] { 68 | &self.bytes 69 | } 70 | 71 | fn set_object(mut self: Pin<&mut Self>, object: Option>) { 72 | let this = Pin::as_mut(&mut self); 73 | 74 | // SAFETY: Storing to the field `object` does not move `this`. 75 | unsafe { Pin::get_unchecked_mut(this) }.object = object; 76 | } 77 | 78 | fn drop_pinned(self: Pin<&mut Self>) { 79 | // SAFETY: Drop `object` before `bytes` is dropped. 80 | self.set_object(None); 81 | } 82 | } 83 | 84 | impl Drop for BinaryParser { 85 | fn drop(&mut self) { 86 | // SAFETY: All instances of `Self` are pinned. 87 | unsafe { Pin::new_unchecked(self) }.drop_pinned(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/pe.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use core::mem::{offset_of, size_of}; 8 | 9 | use goblin::pe::section_table::{IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ}; 10 | use log::debug; 11 | use scroll::Pread; 12 | 13 | use crate::errors::Result; 14 | use crate::options::status::{ASLRCompatibilityLevel, DisplayInColorTerm, PEControlFlowGuardLevel}; 15 | use crate::options::{ 16 | AddressSpaceLayoutRandomizationOption, BinarySecurityOption, DataExecutionPreventionOption, 17 | PEControlFlowGuardOption, PEEnableManifestHandlingOption, 18 | PEHandlesAddressesLargerThan2GBOption, PEHasCheckSumOption, PERunsOnlyInAppContainerOption, 19 | PESafeStructuredExceptionHandlingOption, RequiresIntegrityCheckOption, 20 | }; 21 | use crate::parser::BinaryParser; 22 | 23 | pub(crate) fn analyze_binary( 24 | parser: &BinaryParser, 25 | options: &crate::cmdline::Options, 26 | ) -> Result>> { 27 | let has_checksum = PEHasCheckSumOption.check(parser, options)?; 28 | let supports_data_execution_prevention = 29 | DataExecutionPreventionOption.check(parser, options)?; 30 | let runs_only_in_app_container = PERunsOnlyInAppContainerOption.check(parser, options)?; 31 | let enable_manifest_handling = PEEnableManifestHandlingOption.check(parser, options)?; 32 | let requires_integrity_check = RequiresIntegrityCheckOption.check(parser, options)?; 33 | let supports_control_flow_guard = PEControlFlowGuardOption.check(parser, options)?; 34 | let handles_addresses_larger_than_2_gigabytes = 35 | PEHandlesAddressesLargerThan2GBOption.check(parser, options)?; 36 | let supports_address_space_layout_randomization = 37 | AddressSpaceLayoutRandomizationOption.check(parser, options)?; 38 | let supports_safe_structured_exception_handling = 39 | PESafeStructuredExceptionHandlingOption.check(parser, options)?; 40 | 41 | Ok(vec![ 42 | has_checksum, 43 | supports_data_execution_prevention, 44 | runs_only_in_app_container, 45 | enable_manifest_handling, 46 | requires_integrity_check, 47 | supports_control_flow_guard, 48 | handles_addresses_larger_than_2_gigabytes, 49 | supports_address_space_layout_randomization, 50 | supports_safe_structured_exception_handling, 51 | ]) 52 | } 53 | 54 | pub(crate) const IMAGE_DLLCHARACTERISTICS_NX_COMPAT: u16 = 0x0100; 55 | pub(crate) const IMAGE_DLLCHARACTERISTICS_APPCONTAINER: u16 = 0x1000; 56 | pub(crate) const IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY: u16 = 0x0080; 57 | pub(crate) const IMAGE_DLLCHARACTERISTICS_NO_ISOLATION: u16 = 0x0200; 58 | pub(crate) const IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE: u16 = 0x0040; 59 | pub(crate) const IMAGE_DLLCHARACTERISTICS_GUARD_CF: u16 = 0x4000; 60 | pub(crate) const IMAGE_FILE_LARGE_ADDRESS_AWARE: u16 = 0x0020; 61 | pub(crate) const IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA: u16 = 0x0020; 62 | pub(crate) const IMAGE_FILE_RELOCS_STRIPPED: u16 = 0x0001; 63 | pub(crate) const RDATA_CHARACTERISTICS: u32 = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; 64 | pub(crate) const PDATA_CHARACTERISTICS: u32 = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; 65 | 66 | #[repr(C)] 67 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] 68 | #[allow(non_snake_case)] 69 | pub(crate) struct ImageLoadConfigCodeIntegrity { 70 | Flags: u16, 71 | Catalog: u16, 72 | CatalogOffset: u32, 73 | Reserved: u32, 74 | } 75 | 76 | #[repr(C)] 77 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] 78 | #[allow(non_snake_case)] 79 | pub(crate) struct ImageLoadConfigDirectory32 { 80 | Size: u32, 81 | TimeDateStamp: u32, 82 | MajorVersion: u16, 83 | MinorVersion: u16, 84 | GlobalFlagsClear: u32, 85 | GlobalFlagsSet: u32, 86 | CriticalSectionDefaultTimeout: u32, 87 | DeCommitFreeBlockThreshold: u32, 88 | DeCommitTotalFreeThreshold: u32, 89 | LockPrefixTable: u32, 90 | MaximumAllocationSize: u32, 91 | VirtualMemoryThreshold: u32, 92 | ProcessHeapFlags: u32, 93 | ProcessAffinityMask: u32, 94 | CSDVersion: u16, 95 | DependentLoadFlags: u16, 96 | EditList: u32, 97 | SecurityCookie: u32, 98 | SEHandlerTable: u32, 99 | pub(crate) SEHandlerCount: u32, 100 | GuardCFCheckFunctionPointer: u32, 101 | GuardCFDispatchFunctionPointer: u32, 102 | GuardCFFunctionTable: u32, 103 | GuardCFFunctionCount: u32, 104 | GuardFlags: u32, 105 | CodeIntegrity: ImageLoadConfigCodeIntegrity, 106 | GuardAddressTakenIatEntryTable: u32, 107 | GuardAddressTakenIatEntryCount: u32, 108 | GuardLongJumpTargetTable: u32, 109 | GuardLongJumpTargetCount: u32, 110 | DynamicValueRelocTable: u32, 111 | CHPEMetadataPointer: u32, 112 | GuardRFFailureRoutine: u32, 113 | GuardRFFailureRoutineFunctionPointer: u32, 114 | DynamicValueRelocTableOffset: u32, 115 | DynamicValueRelocTableSection: u16, 116 | Reserved2: u16, 117 | GuardRFVerifyStackPointerFunctionPointer: u32, 118 | HotPatchTableOffset: u32, 119 | Reserved3: u32, 120 | EnclaveConfigurationPointer: u32, 121 | } 122 | 123 | #[repr(C)] 124 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] 125 | #[allow(non_snake_case)] 126 | pub(crate) struct ImageLoadConfigDirectory64 { 127 | Size: u32, 128 | TimeDateStamp: u32, 129 | MajorVersion: u16, 130 | MinorVersion: u16, 131 | GlobalFlagsClear: u32, 132 | GlobalFlagsSet: u32, 133 | CriticalSectionDefaultTimeout: u32, 134 | DeCommitFreeBlockThreshold: u64, 135 | DeCommitTotalFreeThreshold: u64, 136 | LockPrefixTable: u64, 137 | MaximumAllocationSize: u64, 138 | VirtualMemoryThreshold: u64, 139 | ProcessAffinityMask: u64, 140 | ProcessHeapFlags: u32, 141 | CSDVersion: u16, 142 | DependentLoadFlags: u16, 143 | EditList: u64, 144 | SecurityCookie: u64, 145 | SEHandlerTable: u64, 146 | pub(crate) SEHandlerCount: u64, 147 | GuardCFCheckFunctionPointer: u64, 148 | GuardCFDispatchFunctionPointer: u64, 149 | GuardCFFunctionTable: u64, 150 | GuardCFFunctionCount: u64, 151 | GuardFlags: u32, 152 | CodeIntegrity: ImageLoadConfigCodeIntegrity, 153 | GuardAddressTakenIatEntryTable: u64, 154 | GuardAddressTakenIatEntryCount: u64, 155 | GuardLongJumpTargetTable: u64, 156 | GuardLongJumpTargetCount: u64, 157 | DynamicValueRelocTable: u64, 158 | CHPEMetadataPointer: u64, 159 | GuardRFFailureRoutine: u64, 160 | GuardRFFailureRoutineFunctionPointer: u64, 161 | DynamicValueRelocTableOffset: u32, 162 | DynamicValueRelocTableSection: u16, 163 | Reserved2: u16, 164 | GuardRFVerifyStackPointerFunctionPointer: u64, 165 | HotPatchTableOffset: u32, 166 | Reserved3: u32, 167 | EnclaveConfigurationPointer: u64, 168 | } 169 | 170 | #[allow(non_camel_case_types)] 171 | pub(crate) type ImageLoadConfigDirectory_Size_Type = u32; 172 | #[allow(non_camel_case_types)] 173 | pub(crate) type ImageLoadConfigDirectory32_SEHandlerCount_Type = u32; 174 | #[allow(non_camel_case_types)] 175 | pub(crate) type ImageLoadConfigDirectory64_SEHandlerCount_Type = u64; 176 | 177 | pub(crate) fn dll_characteristics_bit_is_set( 178 | pe: &goblin::pe::PE, 179 | mask_name: &'static str, 180 | mask: u16, 181 | ) -> Option { 182 | pe.header.optional_header.map(|optional_header| { 183 | let r = (optional_header.windows_fields.dll_characteristics & mask) != 0; 184 | debug!( 185 | "Bit '{}' is {} in 'DllCharacteristics' inside optional Windows header.", 186 | mask_name, 187 | if r { "set" } else { "cleared" } 188 | ); 189 | r 190 | }) 191 | } 192 | 193 | /// Returns the level of support of Control Flow Guard (CFG). 194 | /// 195 | /// When CFG is supported, the compiler analyzes the control flow by examining all indirect 196 | /// calls for possible target addresses. The compiler inserts code to verify the target address 197 | /// of an indirect call instruction is in the list of known target addresses at runtime. 198 | /// Operating systems that support CFG stop a program that fails a CFG runtime check. This makes 199 | /// it more difficult for an attacker to execute malicious code by using data corruption to 200 | /// change a call target. 201 | pub(crate) fn supports_control_flow_guard(pe: &goblin::pe::PE) -> PEControlFlowGuardLevel { 202 | if let Some(optional_header) = pe.header.optional_header { 203 | if (optional_header.windows_fields.dll_characteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF) 204 | == 0 205 | { 206 | PEControlFlowGuardLevel::Unsupported 207 | } else { 208 | debug!("Bit 'IMAGE_DLLCHARACTERISTICS_GUARD_CF' is set in 'DllCharacteristics' inside optional Windows header."); 209 | 210 | if (optional_header.windows_fields.dll_characteristics 211 | & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) 212 | == 0 213 | { 214 | PEControlFlowGuardLevel::Ineffective 215 | } else { 216 | debug!("Bit 'IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE' is set in 'DllCharacteristics' inside optional Windows header."); 217 | PEControlFlowGuardLevel::Supported 218 | } 219 | } 220 | } else { 221 | PEControlFlowGuardLevel::Unknown 222 | } 223 | } 224 | 225 | pub(crate) fn has_check_sum(pe: &goblin::pe::PE) -> Option { 226 | pe.header 227 | .optional_header 228 | .map(|header| header.windows_fields.check_sum != 0) 229 | } 230 | 231 | /// Returns whether the executable can handle addresses larger than 2 Gigabytes. 232 | pub(crate) fn handles_addresses_larger_than_2_gigabytes(pe: &goblin::pe::PE) -> bool { 233 | let r = (pe.header.coff_header.characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0; 234 | if r { 235 | debug!( 236 | "Bit 'IMAGE_FILE_LARGE_ADDRESS_AWARE' is set in 'Characteristics' inside COFF header." 237 | ); 238 | } 239 | r 240 | } 241 | 242 | pub(crate) fn supports_aslr(pe: &goblin::pe::PE) -> ASLRCompatibilityLevel { 243 | if (pe.header.coff_header.characteristics & IMAGE_FILE_RELOCS_STRIPPED) != 0 { 244 | // Base relocation information are absent. The loader cannot relocate the image. 245 | debug!("Bit 'IMAGE_FILE_RELOCS_STRIPPED' is set in 'Characteristics' inside COFF header."); 246 | ASLRCompatibilityLevel::Unsupported 247 | } else if let Some(optional_header) = pe.header.optional_header { 248 | if (optional_header.windows_fields.dll_characteristics 249 | & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) 250 | == 0 251 | { 252 | // The executable has a preferred address. ASLR will probably not be used, as it might 253 | // be expensive to relocate the executable. 254 | ASLRCompatibilityLevel::Expensive 255 | } else { 256 | let handles_addresses_larger_than_2_gigabytes = 257 | (pe.header.coff_header.characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0; 258 | 259 | if (optional_header.windows_fields.dll_characteristics 260 | & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) 261 | != 0 262 | { 263 | debug!("Bit 'IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA' is set in 'DllCharacteristics' inside optional Windows header."); 264 | 265 | if handles_addresses_larger_than_2_gigabytes { 266 | // High entropy ASLR. 267 | ASLRCompatibilityLevel::Supported 268 | } else { 269 | // High entropy ASLR, but below 2G. 270 | ASLRCompatibilityLevel::SupportedBelow2G 271 | } 272 | } else if handles_addresses_larger_than_2_gigabytes { 273 | if pe.is_64 { 274 | // Low entropy ASLR. 275 | ASLRCompatibilityLevel::SupportedLowEntropy 276 | } else { 277 | // ASLR supported in 32-bits even beyond 2G. 278 | ASLRCompatibilityLevel::Supported 279 | } 280 | } else if pe.is_64 { 281 | // Low entropy ASLR and below 2G. 282 | ASLRCompatibilityLevel::SupportedLowEntropyBelow2G 283 | } else { 284 | // ASLR supported in 32-bits, but below 2G. 285 | ASLRCompatibilityLevel::SupportedBelow2G 286 | } 287 | } 288 | } else { 289 | ASLRCompatibilityLevel::Unknown 290 | } 291 | } 292 | 293 | /// Returns information about support of Safe Structured Exception Handlers (`SafeSEH`). 294 | /// 295 | /// When `SafeSEH` is supported, the executable has a table of safe exception handlers. This table 296 | /// specifies for the operating system which exception handlers are valid for the image. 297 | /// 298 | /// `SafeSEH` is optional only on x86 targets. Other architectures, such as x64 and ARM, always 299 | /// store all exception handlers in the PDATA section. 300 | pub(crate) fn has_safe_structured_exception_handlers( 301 | parser: &BinaryParser, 302 | pe: &goblin::pe::PE, 303 | ) -> bool { 304 | match has_safe_seh_handlers(parser, pe) { 305 | Some(true) => true, 306 | Some(false) | None => has_pdata_section(pe), 307 | } 308 | } 309 | 310 | /// Returns `true` if the executable has a `PDATA` (`.pdata`) section, where all exception handlers 311 | /// are stored. 312 | fn has_pdata_section(pe: &goblin::pe::PE) -> bool { 313 | pe.sections.iter().any(|section| { 314 | if (section.characteristics & PDATA_CHARACTERISTICS) == PDATA_CHARACTERISTICS { 315 | // If this section name is valid UTF-8, then `r` will be `true` if the name equals 316 | // `.pdata`, and false otherwise. For non UTF-8-valid names, `r` will be `false`. 317 | let r = section.name().map(|name| name == ".pdata").unwrap_or(false); 318 | if r { 319 | debug!("Section '.pdata' found in the executable."); 320 | } 321 | r 322 | } else { 323 | false 324 | } 325 | }) 326 | } 327 | 328 | /// Returns `Some(true)` if the executable has an image load configuration directory, in which 329 | /// at least one `SafeSEH` handler is referenced. 330 | /// 331 | /// This returns `Some(false)` if the executable has an image load configuration directory, 332 | /// in which no `SafeSEH` handlers are referenced. It returns `None` in all other cases. 333 | fn has_safe_seh_handlers(parser: &BinaryParser, pe: &goblin::pe::PE) -> Option { 334 | pe.header 335 | .optional_header 336 | // If we actually have an optional header, get its load configuration table. 337 | .and_then(|optional_header| { 338 | optional_header 339 | .data_directories 340 | .get_load_config_table() 341 | .copied() 342 | }) 343 | // Continue only if the load configuration table has some bytes. 344 | .filter(|load_config_table| load_config_table.size > 0) 345 | .and_then(|load_config_table| { 346 | debug!("Reference to Image load configuration directory found in the executable."); 347 | 348 | let load_config_table_end = load_config_table 349 | .virtual_address 350 | .saturating_add(load_config_table.size); 351 | 352 | pe.sections 353 | .iter() 354 | // Find the `.rdata` section that has the bytes of this load configuration table. 355 | .find(|§ion| { 356 | (section.characteristics & RDATA_CHARACTERISTICS) == RDATA_CHARACTERISTICS 357 | && (load_config_table.virtual_address >= section.virtual_address) 358 | && (load_config_table_end 359 | <= section.virtual_address.saturating_add(section.virtual_size)) 360 | }) 361 | // We still need `load_config_table`, so carry it forward to the next steps. 362 | .map(|section| (section, load_config_table)) 363 | }) 364 | // Find out if the load configuration table references some safe structured exception 365 | // handlers. The section is needed to read the bytes of the load configuration table. 366 | .and_then(|(section, load_config_table)| { 367 | image_load_configuration_directory_has_safe_seh_handlers( 368 | parser, 369 | pe, 370 | section, 371 | load_config_table, 372 | ) 373 | }) 374 | } 375 | 376 | fn image_load_configuration_directory_has_safe_seh_handlers( 377 | parser: &BinaryParser, 378 | pe: &goblin::pe::PE, 379 | section: &goblin::pe::section_table::SectionTable, 380 | load_config_table: goblin::pe::data_directories::DataDirectory, 381 | ) -> Option { 382 | debug!("Image load configuration directory found in the executable."); 383 | 384 | // Based on the architecture of the PE32/PE32+ file, find out relatively where and exactly 385 | // how large is the data representing the number of safe structured exception handlers. 386 | let (offset_of_se_handler_count, size_of_se_handler_count) = if pe.is_64 { 387 | ( 388 | offset_of!(ImageLoadConfigDirectory64, SEHandlerCount), 389 | size_of::(), 390 | ) 391 | } else { 392 | ( 393 | offset_of!(ImageLoadConfigDirectory32, SEHandlerCount), 394 | size_of::(), 395 | ) 396 | }; 397 | 398 | // Convert virtual addresses into file offsets. 399 | let config_table_offset_in_section = load_config_table 400 | .virtual_address 401 | .saturating_sub(section.virtual_address); 402 | let config_table_offset_in_file = (section.pointer_to_raw_data as usize) 403 | .saturating_add(config_table_offset_in_section as usize); 404 | let se_handler_count_offset_in_file = 405 | config_table_offset_in_file.saturating_add(offset_of_se_handler_count); 406 | 407 | parser 408 | .bytes() 409 | .pread_with::(config_table_offset_in_file, scroll::LE) 410 | .ok() 411 | // Only continue if the load configuration table size is big enough to read the number of 412 | // safe structured exception handlers. 413 | .filter(|load_config_directory_size| { 414 | (*load_config_directory_size as usize) 415 | >= offset_of_se_handler_count.saturating_add(size_of_se_handler_count) 416 | }) 417 | .and_then(|_load_config_directory_size| { 418 | debug!("Image load configuration directory defines 'SEHandlerCount'."); 419 | 420 | if pe.is_64 { 421 | // Read the number of safe structured exception handlers in a PE32+ executable. 422 | parser 423 | .bytes() 424 | .pread_with::( 425 | se_handler_count_offset_in_file, 426 | scroll::LE, 427 | ) 428 | } else { 429 | // Read the number of safe structured exception handlers in a PE32 executable. 430 | parser 431 | .bytes() 432 | .pread_with::( 433 | se_handler_count_offset_in_file, 434 | scroll::LE, 435 | ) 436 | // To unify the comparison below, convert the count into the same type as in 437 | // the PE32+ executable. 438 | .map(ImageLoadConfigDirectory64_SEHandlerCount_Type::from) 439 | } 440 | .ok() 441 | }) 442 | // Return `Some(true)` if the load configuration table references a least one safe 443 | // structured exception handler. 444 | .map(|se_handler_count| { 445 | debug!( 446 | "Image load configuration directory defines {} structured exceptions handlers.", 447 | se_handler_count 448 | ); 449 | se_handler_count > 0 450 | }) 451 | } 452 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 Koutheir Attouchi. 2 | // See the "LICENSE.txt" file at the top-level directory of this distribution. 3 | // 4 | // Licensed under the MIT license. This file may not be copied, modified, 5 | // or distributed except according to those terms. 6 | 7 | use std::sync::Arc; 8 | 9 | use crate::cmdline::UseColor; 10 | use crate::errors::{Error, Result}; 11 | 12 | /// A color buffer that can should be written-to from a single thread. 13 | /// If cloned and given to another thread, then both threads can write to their own color buffer 14 | /// without synchronizing, and later a joining thread can perform the synchronization and write 15 | /// all cloned color buffers. 16 | pub(crate) struct ColorBuffer { 17 | buffer_writer: Arc, 18 | pub(crate) color_buffer: termcolor::Buffer, 19 | } 20 | 21 | impl ColorBuffer { 22 | pub(crate) fn for_stdout(use_color: UseColor) -> Self { 23 | let buffer_writer = termcolor::BufferWriter::stdout(use_color.into()); 24 | let color_buffer = buffer_writer.buffer(); 25 | 26 | Self { 27 | buffer_writer: Arc::new(buffer_writer), 28 | color_buffer, 29 | } 30 | } 31 | 32 | pub(crate) fn print(&self) -> Result<()> { 33 | self.buffer_writer 34 | .print(&self.color_buffer) 35 | .map_err(|r| Error::from_io1(r, "print", "standard output stream"))?; 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl Clone for ColorBuffer { 41 | fn clone(&self) -> Self { 42 | Self { 43 | // Increment the reference count of the `BufferWriter`. 44 | buffer_writer: Arc::clone(&self.buffer_writer), 45 | // Create a new buffer linked to `buffer_writer`. 46 | color_buffer: self.buffer_writer.buffer(), 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tools/x86_64-unknown-freebsd-clang.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SYS_ROOT="$TOOLCHAINS_ROOT/x86_64-unknown-freebsd/sysroot" 4 | if [ ! -d "$SYS_ROOT" ]; then 5 | >&2 echo "Toolchain root not found: $TOOLCHAINS_ROOT" 6 | exit 1 7 | fi 8 | 9 | exec clang "--sysroot=$SYS_ROOT" "$@" 10 | --------------------------------------------------------------------------------