|
| 1 | +// |
| 2 | +// KSJailbreak.h |
| 3 | +// |
| 4 | +// Created by Karl Stenerud on 10.02.21. |
| 5 | +// Copyright (c) 2012 Karl Stenerud. All rights reserved. |
| 6 | +// |
| 7 | +// |
| 8 | +// Robust Enough Jailbreak Detection |
| 9 | +// --------------------------------- |
| 10 | +// |
| 11 | +// Perfect jailbreak detection, like perfect copy protection, is a fool's errand. |
| 12 | +// But perfection isn't necessary for our purposes. We just need to make it tricky enough |
| 13 | +// that only a complicated per-app targeted tweak would work. Once an app gets popular |
| 14 | +// enough to warrant the time and effort of a targeted tweak, they'll need custom |
| 15 | +// jailbreak detection code anyway for the inevitable cat-and-mouse game. |
| 16 | +// |
| 17 | +// This code operates on the following anti-anti-jailbreak-detection principles: |
| 18 | +// |
| 19 | +// - Functions can be patched by a general tweak, but syscalls cannot. |
| 20 | +// - "environ" is a global variable, which cannot easily be pre-manipulated without |
| 21 | +// potential breakage elsewhere. |
| 22 | +// |
| 23 | +// We check the following things: |
| 24 | +// |
| 25 | +// - Can we create a file in /tmp? (/tmp has perms 777, but sandboxed apps can't see it) |
| 26 | +// - Does Cydia's MobileSubstrate library exist? (used for tweaks and cracks) |
| 27 | +// - Does /etc/apt exist? (Debian's apt is used for non-app-store app distribution) |
| 28 | +// - Does the ENV contain an "insert libraries" directive? (used to override functions) |
| 29 | +// |
| 30 | +// To guard against function overrides, we use syscalls for some of the checks, with a |
| 31 | +// graceful fallback to libc functions if we're on an unknown architecture. We also |
| 32 | +// stick to very basic and old syscalls that have remained stable for decades. |
| 33 | +// |
| 34 | + |
| 35 | +#ifndef HDR_KSJailbreak_h |
| 36 | +#define HDR_KSJailbreak_h |
| 37 | + |
| 38 | +#include <dirent.h> |
| 39 | +#include <fcntl.h> |
| 40 | +#include <stdbool.h> |
| 41 | +#include <stdio.h> |
| 42 | +#include <sys/types.h> |
| 43 | +#include <unistd.h> |
| 44 | +#include <TargetConditionals.h> |
| 45 | + |
| 46 | +// The global environ variable must be imported this way. |
| 47 | +// See: https://opensource.apple.com/source/Libc/Libc-1439.40.11/man/FreeBSD/environ.7 |
| 48 | +extern char **environ; |
| 49 | + |
| 50 | +static inline bool ksj_local_is_insert_libraries_env_var(const char* str) { |
| 51 | + if (str == NULL) { |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + // DYLD_INSERT_LIBRARIES lets you override functions by loading other libraries first. |
| 56 | + // This is a common technique used for defeating detection. |
| 57 | + // See: https://opensource.apple.com/source/dyld/dyld-832.7.3/doc/man/man1/dyld.1 |
| 58 | + const char insert[] = "DYLD_INSERT_LIBRARIES"; |
| 59 | + if (strlen(str) < sizeof(insert)) { |
| 60 | + return false; |
| 61 | + } |
| 62 | + return __builtin_memcmp(str, insert, sizeof(insert)) == 0; |
| 63 | +} |
| 64 | + |
| 65 | +// Note: The following are written as macros to force them always inline so that there |
| 66 | +// are no function calls (which could be patched out). There's enough repetition |
| 67 | +// going on that the compiler might refuse to inline if these were functions. |
| 68 | + |
| 69 | +// Reminder: Always follow proper macro hygene to prevent unwanted side effects: |
| 70 | +// - Surround macros in a do { ... } while(0) construct to prevent leakage. |
| 71 | +// - Surround parameters in parentheses when accessing them. |
| 72 | +// - Do not access a parameter more than once. |
| 73 | +// - Make local copies of input parameters to help enforce their types. |
| 74 | +// - Use pointers for output parameters, with labels that identify them as such. |
| 75 | +// - Beware of global consts or defines bleeding through. |
| 76 | + |
| 77 | +#if TARGET_CPU_ARM64 && !TARGET_OS_OSX |
| 78 | +#define KSCRASH_HAS_CUSTOM_SYSCALL 1 |
| 79 | + |
| 80 | +// ARM64 3-parameter syscall |
| 81 | +// Writes -1 to *(pResult) on failure, or the actual result on success. |
| 82 | +// |
| 83 | +// Implementation Details: |
| 84 | +// - Syscall# is in x16, params in x0, x1, x2, and return in x0. |
| 85 | +// - Carry bit is cleared on success, set on failure (we copy the carry bit to x3). |
| 86 | +// - We must also inform the compiler that memory and condition codes may get clobbered. |
| 87 | +#define ksj_syscall3(call_num, param0, param1, param2, pResult) do { \ |
| 88 | + register uintptr_t call asm("x16") = (uintptr_t)(call_num); \ |
| 89 | + register uintptr_t p0 asm("x0") = (uintptr_t)(param0); \ |
| 90 | + register uintptr_t p1 asm("x1") = (uintptr_t)(param1); \ |
| 91 | + register uintptr_t p2 asm("x2") = (uintptr_t)(param2); \ |
| 92 | + register uintptr_t carry_bit asm("x3") = 0; \ |
| 93 | + asm volatile("svc #0x80\n" \ |
| 94 | + "mov x3, #0\n" \ |
| 95 | + "adc x3, x3, x3\n" \ |
| 96 | + : "=r"(carry_bit), "=r"(p0),"=r"(p1) \ |
| 97 | + : "r"(p0), "r"(p1), "r"(p2), "r"(call), "r"(carry_bit) \ |
| 98 | + : "memory", "cc"); \ |
| 99 | + if(carry_bit == 1) { \ |
| 100 | + *(pResult) = -1; \ |
| 101 | + } else {\ |
| 102 | + *(pResult) = (int)p0; \ |
| 103 | + } \ |
| 104 | +} while(0) |
| 105 | + |
| 106 | +#elif TARGET_CPU_X86_64 && defined(__GCC_ASM_FLAG_OUTPUTS__) && !TARGET_OS_OSX |
| 107 | +#define KSCRASH_HAS_CUSTOM_SYSCALL 1 |
| 108 | + |
| 109 | +// X86_64 3-parameter syscall |
| 110 | +// Writes -1 to *(pResult) on failure, or the actual result on success. |
| 111 | +// |
| 112 | +// Implementation Details: |
| 113 | +// - Syscall# is in RAX, params in RDI, RSI, RDX, and return in RAX. |
| 114 | +// - Carry bit is cleared on success, set on failure. |
| 115 | +// - We must also inform the compiler that memory, rcx, r11 may get clobbered. |
| 116 | +// The "=@ccc" constraint requires __GCC_ASM_FLAG_OUTPUTS__, not available in Xcode 10 |
| 117 | +// https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#index-asm-flag-output-operands |
| 118 | +#define ksj_syscall3(call_num, param0, param1, param2, pResult) do { \ |
| 119 | + register uintptr_t rax = (uintptr_t)(call_num) | (2<<24); \ |
| 120 | + register uintptr_t p0 = (uintptr_t)(param0); \ |
| 121 | + register uintptr_t p1 = (uintptr_t)(param1); \ |
| 122 | + register uintptr_t p2 = (uintptr_t)(param2); \ |
| 123 | + register uintptr_t carry_bit = 0; \ |
| 124 | + asm volatile( \ |
| 125 | + "syscall" \ |
| 126 | + : "=@ccc"(carry_bit), "+a"(rax) \ |
| 127 | + : "D" (p0), "S" (p1), "d" (p2) \ |
| 128 | + : "memory", "rcx", "r11"); \ |
| 129 | + if(carry_bit == 1) { \ |
| 130 | + *(pResult) = -1; \ |
| 131 | + } else { \ |
| 132 | + *(pResult) = (int)rax; \ |
| 133 | + } \ |
| 134 | +} while(0) |
| 135 | + |
| 136 | +#else |
| 137 | +#define KSCRASH_HAS_CUSTOM_SYSCALL 0 |
| 138 | + |
| 139 | +// Unhandled architecture. |
| 140 | +// We fall back to the libc functions in this case, mimicing the syscall-like API. |
| 141 | +// See definitions below. |
| 142 | + |
| 143 | +#endif /* TARGET_CPU_XYZ */ |
| 144 | + |
| 145 | + |
| 146 | +#if KSCRASH_HAS_CUSTOM_SYSCALL |
| 147 | + |
| 148 | +// See: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/kern/syscalls.master |
| 149 | +#define KSCRASH_SYSCALL_OPEN 5 |
| 150 | +#define ksj_syscall_open(path, flags, mode, pResult) ksj_syscall3(KSCRASH_SYSCALL_OPEN, (uintptr_t)path, flags, mode, pResult) |
| 151 | + |
| 152 | +#else |
| 153 | + |
| 154 | +#define ksj_syscall_open(path, flags, mode, pResult) do {*(pResult) = open(path, flags, mode);} while(0) |
| 155 | + |
| 156 | +#endif /* KSCRASH_HAS_CUSTOM_SYSCALL */ |
| 157 | + |
| 158 | + |
| 159 | +/** |
| 160 | + * Get this device's jailbreak status. |
| 161 | + * Stores nonzero in *(pIsJailbroken) if the device is jailbroken, 0 otherwise. |
| 162 | + * Note: Implemented as a macro to force it inline always. |
| 163 | + */ |
| 164 | +#if !TARGET_OS_SIMULATOR && !TARGET_OS_OSX && KSCRASH_HAS_SYSCALL |
| 165 | +#define get_jailbreak_status(pIsJailbroken) do { \ |
| 166 | + int fd = 0; \ |
| 167 | + \ |
| 168 | + bool tmp_file_is_accessible = false; \ |
| 169 | + bool mobile_substrate_exists = false; \ |
| 170 | + bool etc_apt_exists = false; \ |
| 171 | + bool has_insert_libraries = false; \ |
| 172 | + \ |
| 173 | + const char* test_write_file = "/tmp/bugsnag-check.txt"; \ |
| 174 | + remove(test_write_file); \ |
| 175 | + ksj_syscall_open(test_write_file, O_CREAT, 0644, &fd); \ |
| 176 | + if(fd > 0) { \ |
| 177 | + close(fd); \ |
| 178 | + tmp_file_is_accessible = true; \ |
| 179 | + } else { \ |
| 180 | + ksj_syscall_open(test_write_file, O_RDONLY, 0, &fd); \ |
| 181 | + if(fd > 0) { \ |
| 182 | + close(fd); \ |
| 183 | + tmp_file_is_accessible = true; \ |
| 184 | + } \ |
| 185 | + } \ |
| 186 | + remove(test_write_file); \ |
| 187 | + \ |
| 188 | + const char* mobile_substrate_path = "/Library/MobileSubstrate/MobileSubstrate.dylib"; \ |
| 189 | + ksj_syscall_open(mobile_substrate_path, O_RDONLY, 0, &fd); \ |
| 190 | + if(fd > 0) { \ |
| 191 | + close(fd); \ |
| 192 | + mobile_substrate_exists = true; \ |
| 193 | + } \ |
| 194 | + \ |
| 195 | + const char* etc_apt_path = "/etc/apt"; \ |
| 196 | + DIR *dirp = opendir(etc_apt_path); \ |
| 197 | + if(dirp) { \ |
| 198 | + etc_apt_exists = true; \ |
| 199 | + closedir(dirp); \ |
| 200 | + } \ |
| 201 | + \ |
| 202 | + for(int i = 0; environ[i] != NULL; i++) { \ |
| 203 | + if(ksj_local_is_insert_libraries_env_var(environ[i])) { \ |
| 204 | + has_insert_libraries = true; \ |
| 205 | + break; \ |
| 206 | + } \ |
| 207 | + } \ |
| 208 | + \ |
| 209 | + *(pIsJailbroken) = tmp_file_is_accessible || \ |
| 210 | + mobile_substrate_exists || \ |
| 211 | + etc_apt_exists || \ |
| 212 | + has_insert_libraries; \ |
| 213 | +} while(0) |
| 214 | + |
| 215 | +#else |
| 216 | + |
| 217 | +// "/tmp" is accessible on the simulator, which makes the JB test come back positive, so |
| 218 | +// report false on the simulator. |
| 219 | +#define get_jailbreak_status(pIsJailbroken) do { *(pIsJailbroken) = 0; } while(0) |
| 220 | + |
| 221 | +#endif /* !TARGET_OS_SIMULATOR */ |
| 222 | + |
| 223 | + |
| 224 | +#endif /* HDR_KSJailbreak_h */ |
0 commit comments