Overview

These vulnerabilities were disclosed at Black Hat Europe 2022 in the talk Knockout Win Against TCC - 20+ NEW Ways to Bypass Your MacOS Privacy Mechanisms. The technique relied on an SQLite environment variable respected by libsqlite3.dylib which made apps using the standard SQLite system API log all the SQL queries. As such queries may contain sensitive user data normally protected by the TCC - I started researching all the problematic occurrences.

Exploitation

The exploitation here is really simple. I figured out that this technique may be used against applications like: Contacts, Mail, Notes, or iMessage. All these apps run as your user so to set the SQLITE_AUTO_TRACE environment variable - you can use the open -b BUNDLE_ID --env SQLITE_AUTO_TRACE=1 or use launchctl setenv SQLITE_AUTO_TRACE 1 to set this for all the apps.

Take a look at some examples:

Contacts

img

Mail

img

iMessage

img

Notes

img

Decoded version:

img

Fix

Now I’d like to share my recently discovered trick. After the fix, we usually decompile the binary file we expect to have the patch. And what do we see? Barely readable code like the below:

[...]
loc_18723febc:
 r21 = 0x1;
 r2 = 0x6;
    if (strncasecmp("syslog", r19, 0x6) == 0x0) {
 r21 = 0x1;
 r2 = 0x6;
            if (CPU_FLAGS & E) {
 r21 = 0x1;
 r2 = 0x6;
 r21 = 0x1;
 }
 r2 = 0x6;
 }
 r2 = 0x6;
 r20 = r21 << 0x1;
 r0 = getenv("SQLITE_AUTO_TRACE");
 r19 = r0;
    if (r0 != 0x0) goto loc_18723ff38;

loc_18723fef0:
 r2 = 0x6;
    goto loc_18723ffe0;

loc_18723ffe0:
    if ((r21 | 0x0) == 0x1) {
 r0 = dyld_process_is_restricted();
            if (r0 == 0x0) {
 *(int128_t *)0x1d669bdc0 = r20;
 *(int128_t *)0x1d669bdc4 = r19;
 r29 = saved_fp;
 r30 = stack[-8];
 r20 = var_10;
 r19 = stack[-24];
 r22 = var_20;
 r21 = stack[-40];
 r31 = r31 + 0x30;
[...]

Of course, an experienced reverse engineer will immediately see that now the libsqlite3.dylib before turning on the logging mechanism for SQLITE_AUTO_TRACE env var checks if the process is recstricted using dyld_process_is_restricted(). But… look what happens when you copy and paste the decompiled C code to ChatGPT 4o.

int enableAutoLogging() {
    // Variable declarations
    int autoProfileEnabled = 0;
    int autoTraceEnabled = 0;
    const char *profileEnv;
    const char *traceEnv;

    // Check the environment variable for auto profiling
 profileEnv = getenv("SQLITE_AUTO_PROFILE");
    if (profileEnv != NULL) {
        if (strncasecmp("1", profileEnv, 1) == 0 || strncasecmp("stderr", profileEnv, 6) == 0 || strncasecmp("syslog", profileEnv, 6) == 0) {
 autoProfileEnabled = 1;
 }
 }

    // Check the environment variable for auto tracing
 traceEnv = getenv("SQLITE_AUTO_TRACE");
    if (traceEnv != NULL) {
        if (strncasecmp("1", traceEnv, 1) == 0 || strncasecmp("stderr", traceEnv, 6) == 0 || strncasecmp("syslog", traceEnv, 6) == 0) {
 autoTraceEnabled = 1;
 }
 }

    // If auto logging is enabled, check if the process is restricted
    if (autoProfileEnabled || autoTraceEnabled) {
        if (dyld_process_is_restricted() == 0) {
            // Enable auto logging
            // Assume `enable_auto_logging` is a function that enables the logging
            enable_auto_logging(autoProfileEnabled, autoTraceEnabled);
 } else {
            // Process is restricted, log a warning message
            sqlite3_log(28, "Auto logging environment variable set, but process is restricted. Ignoring.");
 }
 }

    return 0;
}

Much better, right?