etke / checksec.rs

Fast multi-platform (ELF/PE/MachO) binary checksec written in Rust.
Apache License 2.0
96 stars 12 forks source link

Issues with has_arc() and has_canary() Functions Not Detecting Symbols in Mach-O Files #44

Open cpuu opened 10 months ago

cpuu commented 10 months ago

Environment

Description

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_chkfail or stack_chk_guard for has_canary(), which should return true if stack protection is enabled.

Expected Behavior

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

Actual Behavior

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

Investigation

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.

Additional Information

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.

titison commented 10 months ago

For me the has_arc and has_canary functions are able to find imports (and return true when the _objc_release symbol is found). But i am running on Linux Mint 21 and not Mac currently.

You said you are using Goblin 0.5.2. Are you using the official 0.0.9 release or did you checkout this git? (Because the current state on git requires at least goblin 0.6.0). If you are using the official release, please try the current state on git.

If the problems persist, could you provide further information on the executables you were checking?

EDIT: Are you using swift? Maybe its related to this issue #6

titison commented 10 months ago

I looked into this further and the problem seems to be that checksec uses the imports() function which only returns imports known to dyld. However, this info is not present for all MachO Files.

A potential fix would be to change from the imports function to the symbols() function. Replace the corresponding functions with:

    fn has_arc(&self) -> bool {
        for i in self.symbols() {
            if let Ok((symbol,_)) = i {
                if symbol == "_objc_release" {
                    return true;
                }
            }
        }
        false
    }
    fn has_canary(&self) -> bool {
        for i in self.symbols() {
            if let Ok((symbol,_)) = i {
                match symbol {
                    "___stack_chk_fail" | "___stack_chk_guard" => return true,
                    _ => continue,
                }
            }
        }
        false
    }