Home Is This App Safe ? Guide to macOS Binary Verification
Post
Cancel

Is This App Safe ? Guide to macOS Binary Verification

With macOS increasingly targeted by malware, verifying the authenticity and security of applications is more crucial than ever. Whether you’re a cybersecurity researcher, malware analyst, or a macOS power user, this guide will equip you with practical techniques to ensure the integrity and security of macOS applications.

1. Check the Code signature

Use the codesign command to verify the authenticity and integrity of the binary:

1
codesign -dv --verbose=4 /path/to/binary

2. Check if the Notarization Ticket Exists and is Stapled

Apple notarization ensures that the binary has been scanned for security issues. Verify notarization using:

1
spctl -a -vvv -t execute /path/to/binary

What is a Stapled Notarization Ticket in macOS? In macOS, stapling refers to embedding a notarization ticket inside an app bundle. This allows the app to be verified offline by Gatekeeper without requiring an internet connection.

A notarized app may not necessarily have the notarization ticket stapled. To check:

1
stapler validate /path/to/App.app

Expected Output (If Stapled)

1
2
Processing: /path/to/App.app
The validate action worked!

If the ticket is not stapled, you’ll see:

1
2
Processing: /path/to/App.app
Error: ticket not found

What If an App is Notarized But Not Stapled?

  1. The app can still run on macOS, but Gatekeeper will verify the notarization online.
  2. If offline, macOS might flag it as untrusted.

To automate notarization and stapling verification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import subprocess

def check_notarization(app_path):
    result = subprocess.run(["spctl", "-a", "-t", "execute", "-vv", app_path], capture_output=True, text=True)
    if "source=Notarized Developer ID" in result.stdout:
        print(f"[+] {app_path} is notarized.")
    else:
        print(f"[!] {app_path} is NOT notarized.")

def check_stapled(app_path):
    result = subprocess.run(["stapler", "validate", app_path], capture_output=True, text=True)
    if "worked" in result.stdout:
        print(f"[+] {app_path} has a notarization ticket stapled.")
    else:
        print(f"[!] {app_path} does NOT have a stapled ticket.")

# Example usage
app_bundle = "/path/to/App.app"
check_notarization(app_bundle)
check_stapled(app_bundle)

3. Analyze the Binary’s Hash and Reputation

1
shasum -a 256 /path/to/binary

4. Check the Entire App Bundle for Suspicious Attributes

1
find /path/to/App.app -exec xattr -l {} +

5. Check the dylibs Used

1
otool -L /path/to/App.app/Contents/MacOS/executable

6. Check Load Commands

1
otool -l /path/to/App.app/Contents/MacOS/executable

Look for LC_LOAD_DYLIB and LC_MAIN.

7. Check Frameworks Used

1
find /path/to/App.app/Contents/Frameworks -type f -name "*.dylib"

8. Check Imports and Exports

1
nm -gU /path/to/App.app/Contents/MacOS/executable

9. Run Dynamically and Observe Behavior

Tools: dtruss – Trace system calls:

1
sudo dtruss -p $(pgrep AppName)

fs_usage – Monitor file system activity:

1
sudo fs_usage -w -f filesys

sudo opensnoop – Track file opens:

1
sudo opensnoop -n AppName

sudo lsof -p – Check open files:

1
sudo lsof -p $(pgrep AppName)

10. Check Logging (ES, Unified, Crash Logs, Sysdiagnose)

  1. Endpoint Security (ES) logs: Requires a custom agent using Apple’s ES framework.

  2. Unified Logging:
    1
    
    log stream --predicate 'processImagePath contains "AppName"'
    
  3. Crash logs
    1
    
    ls -lt ~/Library/Logs/DiagnosticReports/
    
  4. Sysdiagnose:
    1
    
    sudo sysdiagnose -f ~/Desktop/
    

    11. Check the Entitlements of the App Bundle or Executable

    1
    
    codesign -d --entitlements :- /path/to/App.app/Contents/MacOS/executable
    

    Look for com.apple.security.automation.apple-events, com.apple.security.network.client, etc.

12. Check Sandbox Results

If the app runs inside macOS sandbox:

1
sandbox-exec -n default /path/to/App.app/Contents/MacOS/executable

13. Check EXIF Data (If App Contains Images or Metadata)

14. Go Through Packet Capture After Running

Start packet capture before running:

1
sudo tcpdump -i any -nn port 443 or port 80 -w traffic.pcap

Analyze with Wireshark or tshark:

1
tshark -r traffic.pcap -Y "http.request or tls.handshake"

15. Verifying Apple-Signed Binaries Using Anchor Apple Requirement

To verify whether a flagged library is legitimately signed by Apple, we first obtain a static code reference using SecStaticCodeCreateWithPath(). Next, we compile the requirement string "anchor apple" with SecRequirementCreateWithString(). Finally, we pass the static code reference to SecStaticCodeCheckValidity(). If the API returns errSecSuccess, the library is confirmed to be Apple-signed, indicating a false positive detection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <Security/Security.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>

void checkAnchorApple(const char *binaryPath) {
    CFURLRef binaryURL = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)binaryPath, strlen(binaryPath), false);
    if (!binaryURL) {
        printf("[-] Failed to create CFURL for binary.\n");
        return;
    }

    SecStaticCodeRef staticCode = NULL;
    if (SecStaticCodeCreateWithPath(binaryURL, kSecCSDefaultFlags, &staticCode) != errSecSuccess) {
        printf("[-] Failed to create static code reference.\n");
        CFRelease(binaryURL);
        return;
    }

    SecRequirementRef requirement = NULL;
    if (SecRequirementCreateWithString(CFSTR("anchor apple"), kSecCSDefaultFlags, &requirement) != errSecSuccess) {
        printf("[-] Failed to create requirement reference.\n");
        CFRelease(staticCode);
        CFRelease(binaryURL);
        return;
    }

    OSStatus status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirement);
    if (status == errSecSuccess) {
        printf("[+] The binary/library is signed by Apple (meets 'anchor apple' requirement).\n");
    } else {
        printf("[-] The binary/library is NOT signed by Apple (or verification failed).\n");
    }

    CFRelease(requirement);
    CFRelease(staticCode);
    CFRelease(binaryURL);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s /path/to/binary\n", argv[0]);
        return 1;
    }

    checkAnchorApple(argv[1]);
    return 0;
}

Example Usage

Compile and run the program:

1
2
clang -o check_anchor_apple check_anchor_apple.c -framework Security -framework CoreFoundation
./check_anchor_apple /System/Library/Frameworks/AppKit.framework/AppKit

Expected Output (For Apple-Signed Binaries):

1
[+] The binary/library is signed by Apple (meets 'anchor apple' requirement).

Expected Output (For Third-Party or Untrusted Binaries):

1
[-] The binary/library is NOT signed by Apple (or verification failed).

Thank you

This post is licensed under CC BY 4.0 by the author.

MacOS Malware:Anti-Analysis Tactics and Bypass Techniques

-