etke / checksec.rs Goto Github PK
View Code? Open in Web Editor NEWFast multi-platform (ELF/PE/MachO) binary checksec written in Rust.
License: Apache License 2.0
Fast multi-platform (ELF/PE/MachO) binary checksec written in Rust.
License: Apache License 2.0
It would be nice to have a more readable output for scanning directories/multiple binaries.
Some ideas:
The TODO section of the ReadMe mentions Rpath RW, probably taken from the output of checksec.sh.
From an ELF perspective RPATH and RUNPATH have no RW(read-write) attribute.
checksec.sh checks whether the respective directory is writable, which is not that interesting, since it is fine for /usr/lib/foo to be writable by root.
One could maybe divide R(UN)PATH into some sections:
It seems that the clang CFI check is wrongly implemented, as it only iterates over the ELF .dynsym section and looks for the ".cfi" string. However, based on my dummy binaries the hardened *.cfi functions are located in the .symtab section.
readelf -s cfi_icall bin -> primary !
Symbol table '.dynsym' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (2)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
7: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 53 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.c
2: 0000000000000358 32 OBJECT LOCAL DEFAULT 3 __abi_tag
3: 0000000000000000 0 FILE LOCAL DEFAULT ABS init.c
4: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
5: 0000000000001080 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones
6: 00000000000010b0 0 FUNC LOCAL DEFAULT 14 register_tm_clones
7: 00000000000010f0 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux
8: 0000000000004058 1 OBJECT LOCAL DEFAULT 25 completed.0
9: 0000000000003de0 0 OBJECT LOCAL DEFAULT 20 __do_global_dtor[...]
10: 0000000000001140 0 FUNC LOCAL DEFAULT 14 frame_dummy
11: 0000000000003dd8 0 OBJECT LOCAL DEFAULT 19 __frame_dummy_in[...]
12: 0000000000000000 0 FILE LOCAL DEFAULT ABS
13: 0000000000001438 24 FUNC LOCAL DEFAULT 14 bad_int_arg
14: 0000000000001310 57 FUNC LOCAL DEFAULT 14 bad_int_arg.cfi
15: 0000000000004038 32 OBJECT LOCAL DEFAULT 24 f
16: 0000000000001350 65 FUNC LOCAL DEFAULT 14 float_arg
17: 0000000000001430 24 FUNC LOCAL DEFAULT 14 int_arg
18: 00000000000012e0 43 FUNC LOCAL DEFAULT 14 int_arg.cfi
19: 0000000000001440 24 FUNC LOCAL DEFAULT 14 not_entry_point
20: 00000000000013a0 137 FUNC LOCAL DEFAULT 14 not_entry_point.cfi
[...]
$ checksec -f cfi_icall
ELF64: | Canary: false CFI: **true** SafeStack: false Fortify: false Fortified: 0 NX: true PIE: Full Relro: Partial RPATH: None RUNPATH: None | File: cfi_icall
$ checksec -f cfi_icall
ELF64: | Canary: false CFI: **false** SafeStack: false Fortify: false Fortified: 0 NX: true PIE: Full Relro: Partial RPATH: None RUNPATH: None | File: cfi_icall
Just replace the iterator over the dynsyms with an iterator over the .syms
The tool misreports the issue reported here: slimm609/checksec.sh#161
Maybe RELRO should be checked only for got and reported as "GOT RELRO"?
I've encountered a potential issue with the has_arc() and has_canary() functions in the macho.rs file. These functions are intended to check for the presence of specific keywords in the imports of a Mach-O binary: _objc_release for has_arc(), which should return true if ARC is being used, and either ___stack_chk_fail or ___stack_chk_guard for has_canary(), which should return true if stack protection is enabled.
When provided with a Mach-O binary that has ARC and stack canaries properly applied, the has_arc()
function is expected to return true
if the _objc_release
symbol is present
$ nm ~/Desktop/HelloWorld | grep _objc_release
U _objc_release
and has_canary()
should return true
if stack protection symbols are present.
$ nm ~/Desktop/hello_canary
U ___stack_chk_fail
U ___stack_chk_guard
0000000100000000 T __mh_execute_header
0000000100003f2c T _main
U _read
Despite supplying a Mach-O file that I've verified to have ARC enabled, the has_arc()
function returned false
. Upon further inspection, it appears that the list of imports retrieved within the function is empty. This same behavior is observed with the has_canary()
function, suggesting that both functions may consistently return false
regardless of the actual contents of the Mach-O binary.
$ ./checksec -f ~/Desktop/hello_canary
MachO64: |
ARC: false
Canary: false
...
| File: /Users/cpuu/Desktop/hello_canary
To further investigate the issue, I added additional print statements to the has_arc() function to trace the flow and check where it might be failing. Below is the modified version of the has_arc() function:
fn has_arc(&self) -> bool {
println!("Starting has_arc check...");
match self.imports() {
Ok(imports) => {
println!("Successfully retrieved imports. Number of imports: {}", imports.len());
if imports.is_empty() {
println!("No imports found. Exiting check.");
}
for import in &imports {
println!("Import name: {}", import.name);
if import.name == "_objc_release" {
println!("Found '_objc_release' import. ARC is used. Exiting check.");
return true;
}
}
println!("'_objc_release' import not found. ARC is not used.");
false
},
Err(e) => {
println!("Error while getting imports: {:?}", e);
false
}
}
}
When running the above code with a Mach-O binary that is known to have ARC enabled, the output was as follows:
Starting has_arc check...
Successfully retrieved imports. Number of imports: 0
No imports found. Exiting check.
'_objc_release' import not found. ARC is not used.
This output indicates that the imports() call is successful, but it retrieves an empty list of imports, leading to the function incorrectly reporting that ARC is not used. Given that I have verified the binary has ARC enabled through other means, this result seems to be inaccurate.
I have verified the presence of ARC and stack canaries in my Mach-O binary using both otool
and nm
command-line tools, which show the expected symbols. This leads me to believe there might be a discrepancy in how the macho.rs
file handles the parsing or detection of these symbols.
I am wondering if there could be an underlying issue with how imports are being retrieved or if there's an assumption made by the checker that doesn't hold true for all Mach-O binaries.
Any insights or suggestions on this matter would be greatly appreciated.
checksec.rs seems to be the only checksec tool that also checks Mach-O files. I stumbled upon this tool due to this check. However, the stack canaries check looks up symbols related to Objective-C code, which is also suggested in guides such as the Mobile Security Testing Guide. According to what I read on the Internet, Swift code has a per default built-in stack protection. For Swift libraries, the stack canary is shown to be disabled. It is misleading, but I don't know how to implement a better check. Maybe the tool could call them ObjC Stack canaries to be a bit more specific. Maybe the tool could add a heuristic check, if the library is Swift-based, Objective-C-based or mixed. Maybe it is more clear than.
It would be convenient for development to have more binaries to test in test/binaries, especially some PE and MachO ones.
error: attributes are not yet allowed on if expressions
--> /home/User/.cargo/registry/src/github.com-1ecc6299db9ec823/sysinfo-0.14.5/src/utils.rs:27:5
|
27 | #[allow(clippy::identity_conversion)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^error: aborting due to previous error
error: could not compile sysinfo.
warning: build failed, waiting for other jobs to finish...
error: build failed
Same error above is thrown when I tried to build/install using cargo build --release
and cargo install checksec
I am using cargo v1.41.0, Ubuntu 18.04 x86_64.
Hey! Would it be possible to bump checksec
's goblin
dependency to the latest version (v0.5) and release on crates.io?
Currently get an error when attempting to use goblin
v0.5 and checksec
v0.0.8 (with goblin
v0.2) simultaneously:
error[E0308]: mismatched types
--> src/cli/cli.rs:197:76
|
197 | CustomElfCheckSecResults(ElfCheckSecResults::parse(&elf))
| ^^^^ expected struct `goblin::elf::Elf`, found a different struct `goblin::elf::Elf`
|
= note: expected reference `&goblin::elf::Elf<'_>` (struct `goblin::elf::Elf`)
found reference `&goblin::elf::Elf<'_>` (struct `goblin::elf::Elf`)
= note: perhaps two different versions of crate `goblin` are being used?
Thank you for your work on this crate.
Hi, I would like to know if this project is discontinued or just not maintained anymore ? Maybe that should be mentioned in the README.md
This issue is for an extremely specific quirk that isn’t really a problem in practice, but bringing it to your attention on the off chance other library API users ran into the same thing.
Currently use this crate as a dependency in a CLI tool, the tool accepts a --no-color
flag to turn off colored output. Original rationale was to support UNIX piping, assuming ANSI escape sequences would cause issues. But it turns out the colored
crate uses the atty
crate to automatically detect when a file descriptor isn’t a terminal and disable coloring. This means checksec
has no problem at all with UNIX piping if colored output is enabled at compile-time.
Regardless, I’m slightly annoyed that I can’t get checksec
to toggle color at runtime based on a parameter. It’s technically possible if I re-implement all formatting rules for every field member using the “new type pattern” (which I already half do here to get multi-line output), but that’s a bit painful.
Do you think it’s worth supporting coloring as a run-time option instead of compile-time configuration?
Downloaded and compiled latest checksec.rs
$ pwd
/root/CS/checksec.rs
$
$ target/debug/checksec --version
checksec 0.0.8
$
Compiled a sample C binary
$
$ cat ~/CS/hello.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
$
$ gcc ~/CS/hello.c -o ~/CS/hello
$
$ file ~/CS/hello
/root/CS/hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3fa10253da53dcc98a69539b2486e4ed1c620171, not stripped
$
$ ~/CS/hello
Hello World
$
Tried using check.rs against this binary, got the following parsing error
$
$ target/debug/checksec --file ~/CS/hello
Can not parse binary file /root/CS/hello: Malformed entity: Section 122 size (4195304) + addr (96) is out of bounds. Overflowed: false
$
More details about the ELF file below, for context I am using a RHEL8 machine for this example
$
$ readelf -h ~/CS/hello
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4004a0
Start of program headers: 64 (bytes into file)
Start of section headers: 15512 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
$
GCC version used
gcc (GCC) 8.4.1 20200928
Strangely everything works fine for existing system binaries but not for freshly compiled binaries,
Which makes me wonder whats the case default gcc compilation of a simple binary (not special flags given)
$ ./target/debug/checksec -f /usr/bin/ls
ELF64: | Canary: true CFI: false SafeStack: false Fortify: true Fortified: 5 NX: true PIE: Full Relro: Partial RPATH: None RUNPATH: None | File: /usr/bin/ls
$
$ ./target/debug/checksec -f /usr/bin/ps
ELF64: | Canary: true CFI: false SafeStack: false Fortify: true Fortified: 5 NX: true PIE: Full Relro: Partial RPATH: None RUNPATH: None | File: /usr/bin/ps
$
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.