Back to Blog
iOS Security
Jan 03, 2026
15 min read

8KSec - WhereAmIReally

My hands-on iOS solution

8KSec - WhereAmIReally

WhereIamReally

WhereAmIReally is an iOS app that only reveals the flag if you’re in the right place "physically". It checks your GPS coordinates against a geofenced area and validates the authenticity of your location before granting access.

1. Investigating the application

A. Info.plist

  • Analyzing the Info.plist file for configurations.
Info.plist

B. Investigating the application binaries

  • Checking the binaries for hardcoded strings or hints.
Binaries

C. Investigating Frameworks

Frameworks

2. Jailbreak Detection Analysis

A. Identifying the Check

  • Locating the jailbreak detection mechanism.
Jailbreak Detection

B. The Jailbreak Class

  • Analyzing the specific class responsible for the checks.
Jailbreak Class

3. Bypassing and Solution

A. Bypassing the Jailbreak Check

  • Successfully bypassing the restriction using Frida or other tools.
Jailbreak Bypassed
/**
 * Frida Script: Jailbreak Bypass and Location Check Tracing (iOS)
 *
 * This script combines the user's existing jailbreak bypass logic with a method
 * tracer to identify the function that checks the location for the CTF flag.
 *
 * TARGET CLASS: WhereAmIReally.LocationManager (Swift Class)
 */

if (ObjC.available) {
    
    // =========================================================================
    // SECTION 1: JAILBREAK BYPASS LOGIC (USER MUST PASTE THEIR CODE HERE)
    // =========================================================================
    
    console.log("[*] Executing Jailbreak Bypass Logic...");
    
    try {
        // ---------------------------------------------------------------------
        // !!! PASTE YOUR EXISTING JAILBREAK BYPASS CODE BELOW THIS LINE !!!
        // ---------------------------------------------------------------------
     	/*
 * safe_plus_with_anti_exit.js
 * Combines SAFE+ IOSSecuritySuite/HookKit bypass + anti-exit hardening.
 *
 * Start in "discover" (logs only). Switch to "bypass" once you see stable logs:
 * frida -U -f com.your.bundle --pause -l safe_plus_with_anti_exit.js
 * % rpc.exports.setmode("discover"); % %resume
 * # then:
 * % rpc.exports.setmode("bypass")
 *
 * Use only on apps/devices you own or have authorization to test.
 */

var MODE = "bypass"; // "discover" | "bypass"
var VERBOSE = true;

// Coverage toggles (safe by default)
const ENABLE_FILES     = true;   // stat/lstat/access/fopen
const ENABLE_ENV       = true;   // getenv
const ENABLE_PTRACE    = true;   // ptrace(PT_DENY_ATTACH)
const ENABLE_SYSCTLBN  = true;   // sysctlbyname (success + zero-fill for suspicious)
const ENABLE_SYSCTL    = true;   // sysctl MIB (success + zero-fill for KERN_PROC)
const ENABLE_UIA       = true;   // UIApplication.canOpenURL
const ENABLE_FM        = true;   // NSFileManager.fileExistsAtPath
const ENABLE_OPEN      = true;   // open/creat -> redirect writes to /dev/null
const ENABLE_MKDIR     = true;   // mkdir write-probe -> success
const ENABLE_UNLINK    = true;   // unlink write-probe -> success
const ENABLE_STATFS    = true;   // statfs /var/jb neutralization
const ENABLE_CSOPS     = true;   // csops
const ENABLE_CSOPSAUD  = true;   // csops_audittoken
const ENABLE_DLSYM     = true;   // dlsym symbol hunting blocklist
const ENABLE_CONNECT   = true;   // connect() frida port scan block
const ENABLE_DBG       = true;   // isatty/ioctl debugger probes
const ENABLE_SBCHK     = true;   // sandbox_check -> allow
const ENABLE_PROCINFO  = true;   // proc_pidinfo / proc_pidpath
const ENABLE_GETPPID   = true;   // getppid -> 1 (launchd)

// Anti-exit hardening (ON)
const ENABLE_ANTI_EXIT = true;   // exit/_exit/_Exit/abort/raise/kill/pthread_kill/__pthread_kill etc.
const ENABLE_ANTI_ASSERT = true; // __assert_rtn/__stack_chk_fail/os_crash/abort_with_payload
const ENABLE_ANTI_OBJC_THROW = true; // objc_exception_throw swallow
const ENABLE_ANTI_SWIFT   = true; // swift_fatalError etc. best-effort
const ENABLE_ANTI_CXX     = false; // __cxa_throw/rethrow/etc. (aggressive; default OFF)

// Risky (keep OFF unless you need log-only visibility)
const ENABLE_DLOPEN   = false;  // dlopen
const ENABLE_DYLD     = false;  // _dyld_get_image_name spoof/log
const ENABLE_SEC      = false;  // SecStaticCodeCheckValidity force success
const ENABLE_MEMCMP   = false;  // global memcmp forcing

// Indicators
const HIDE_PATHS = [
  "/Applications/Cydia.app",
  "/Library/MobileSubstrate",
  "/Library/MobileSubstrate/DynamicLibraries",
  "/etc/apt",
  "/private/var/lib/apt",
  "/var/lib/dpkg",
  "/usr/sbin/sshd", "/usr/bin/ssh", "/bin/bash",
  "/var/jb"
];

const WRITE_PROBE_PATHS = [
  "/private/jailbreak.txt",
  "/private/test.txt",
  "/private/test_jb.txt",
  "/private/var/tmp/jb_check.txt",
  "/var/jb/test.txt"
];

const HIDE_SCHEMES = ["cydia://","sileo://","undecimus://","filza://","zbra://"];
const HIDE_ENVS    = ["DYLD_INSERT_LIBRARIES","LD_PRELOAD","OBJC_DISABLE_INITIALIZE_FORK_SAFETY","FRIDA"];
const HIDE_SYMBOLS = ["frida","MSHookFunction","MSFindSymbol","fishhook","hookkit","dobby","substrate","frida_agent_main"];
const FRIDA_PORTS  = new Set([27042, 27043]);

function log(){ if (VERBOSE) console.log("[SAFE+X] " + Array.from(arguments).join(" ")); }
function dlog(){ if (MODE==="discover") console.log("[DISCOVER] " + Array.from(arguments).join(" ")); }

rpc.exports = {
  setmode: m => { MODE = m; log("MODE:", MODE); },
  setverbose: v => { VERBOSE = !!v; },
  enable: g => { globalThis[g] = true; log("ENABLED:", g); },
  disable: g => { globalThis[g] = false; log("DISABLED:", g); }
};

function hookExport(name, cond, onEnter, onLeave){
  if (!cond) return;
  const addr = Module.findExportByName(null, name);
  if (!addr) { log("not found:", name); return; }
  Interceptor.attach(addr, { onEnter: onEnter || function(){}, onLeave: onLeave || function(){} });
  log("hooked", name);
}
function replaceExport(name, cond, cbRetType, cbArgTypes, handler){
  if (!cond) return;
  const addr = Module.findExportByName(null, name);
  if (!addr) { log("not found:", name); return; }
  const cb = new NativeCallback(handler, cbRetType, cbArgTypes);
  Interceptor.replace(addr, cb);
  log("replaced", name);
}

function isIndicatorPath(p){
  if (!p) return false;
  return HIDE_PATHS.some(h => p.indexOf(h) !== -1);
}
function isWriteProbePath(p){
  if (!p) return false;
  if (WRITE_PROBE_PATHS.some(x => p === x)) return true;
  if (p.startsWith("/private/") || p.startsWith("/var/jb/")) return true;
  return false;
}

/*** libc helpers for /dev/null redirection ***/
const openPtr  = Module.findExportByName(null, "open");
const openFn   = openPtr ? new NativeFunction(openPtr, 'int', ['pointer','int']) : null;
const devNullP = Memory.allocUtf8String("/dev/null");

/*** ObjC ***/
if (ObjC.available){
  try {
    if (ENABLE_FM){
      const FM = ObjC.classes.NSFileManager;
      if (FM && FM["- fileExistsAtPath:"]){
        Interceptor.attach(FM["- fileExistsAtPath:"].implementation, {
          onEnter(args){ try{ this.p = new ObjC.Object(args[2]).toString(); }catch(_){ this.p=null; } },
          onLeave(ret){
            dlog("NSFileManager.fileExistsAtPath", this.p);
            if (MODE==="bypass" && this.p && isIndicatorPath(this.p)) ret.replace(ptr(0));
          }
        });
        log("hooked ObjC NSFileManager");
      }
    }
    if (ENABLE_UIA){
      const UIA = ObjC.classes.UIApplication;
      if (UIA && UIA["- canOpenURL:"]){
        Interceptor.attach(UIA["- canOpenURL:"].implementation, {
          onEnter(args){ try{ this.u = new ObjC.Object(args[2]).absoluteString().toString(); }catch(_){ this.u=null; } },
          onLeave(ret){
            dlog("UIApplication.canOpenURL", this.u);
            if (MODE==="bypass" && this.u && HIDE_SCHEMES.some(s => this.u.indexOf(s)!==-1)) ret.replace(ptr(0));
          }
        });
        log("hooked ObjC UIApplication.canOpenURL");
      }
    }
    // Private: -[UIApplication terminateWithSuccess]
    try {
      const UIApp = ObjC.classes.UIApplication;
      if (UIApp && UIApp["- terminateWithSuccess"]) {
        Interceptor.attach(UIApp["- terminateWithSuccess"].implementation, {
          onEnter(){ dlog("UIApplication terminateWithSuccess", "called"); },
          onLeave(ret){ if (MODE==="bypass") ret.replace(ptr(0)); }
        });
        log("hooked UIApplication.terminateWithSuccess");
      }
    } catch(_) {}
  } catch(e){ log("ObjC hook error:", e); }
}

/*** libc & friends ***/
// Classic file probes
["stat","lstat","access","fopen"].forEach(fn => {
  hookExport(fn, ENABLE_FILES,
    function(args){ try{ this.p = Memory.readUtf8String(args[0]); dlog(fn+" enter", this.p); }catch(_){ this.p = null; } },
    function(ret){
      if (MODE!=="bypass" || !this.p) return;
      if (isIndicatorPath(this.p)){
        if (fn==="fopen") ret.replace(ptr(0)); else ret.replace(ptr(-1));
        log(fn, "hide", this.p);
      }
    }
  );
});

// Writes -> /dev/null or success fakes
hookExport("open", ENABLE_OPEN,
  function(args){
    try{
      this.p = Memory.readUtf8String(args[0]);
      this.flags = args[1].toInt32();
      dlog("open", this.p + " flags=" + this.flags);
    }catch(_){ this.p=null; this.flags=0; }
  },
  function(ret){
    if (MODE!=="bypass" || !this.p) return;
    const O_CREAT = 0x0200;
    const isWrite = (this.flags & 0x0001) || (this.flags & 0x0002) || (this.flags & O_CREAT);
    if (isWrite && isWriteProbePath(this.p) && openFn) {
      const newFlags = (this.flags & ~O_CREAT) | 0x0001; // O_WRONLY
      ret.replace(ptr(openFn(devNullP, newFlags)));
      log("open redirected WRITE probe -> /dev/null", this.p);
    }
  }
);

hookExport("creat", ENABLE_OPEN,
  function(args){ try{ this.p = Memory.readUtf8String(args[0]); dlog("creat", this.p); }catch(_){ this.p=null; } },
  function(ret){
    if (MODE==="bypass" && this.p && isWriteProbePath(this.p) && openFn){
      ret.replace(ptr(openFn(devNullP, 0x0001)));
      log("creat redirected -> /dev/null", this.p);
    }
  }
);

hookExport("mkdir", ENABLE_MKDIR,
  function(args){ try{ this.p = Memory.readUtf8String(args[0]); dlog("mkdir", this.p); }catch(_){ this.p=null; } },
  function(ret){
    if (MODE==="bypass" && this.p && isWriteProbePath(this.p)){
      ret.replace(ptr(0)); // success
      log("mkdir fake success", this.p);
    }
  }
);

hookExport("unlink", ENABLE_UNLINK,
  function(args){ try{ this.p = Memory.readUtf8String(args[0]); dlog("unlink", this.p); }catch(_){ this.p=null; } },
  function(ret){
    if (MODE==="bypass" && this.p && isWriteProbePath(this.p)){
      ret.replace(ptr(0)); // success
      log("unlink fake success", this.p);
    }
  }
);

// getenv
hookExport("getenv", ENABLE_ENV,
  function(args){ try{ this.name = Memory.readUtf8String(args[0]); dlog("getenv", this.name); }catch(_){ this.name = null; } },
  function(ret){ if (MODE==="bypass" && this.name && HIDE_ENVS.includes(this.name)) ret.replace(ptr(0)); }
);

// ptrace
hookExport("ptrace", ENABLE_PTRACE,
  function(args){ try{ this.req = args[0].toInt32(); dlog("ptrace", this.req); }catch(_){ this.req=null; } },
  function(ret){ if (MODE==="bypass" && this.req===31 /* PT_DENY_ATTACH */) ret.replace(ptr(0)); }
);

/*** sysctlbyname: success + zero-fill for suspicious keys ***/
hookExport("sysctlbyname", ENABLE_SYSCTLBN,
  function (args) {
    try {
      this.key = Memory.readUtf8String(args[0]);
      this.oldp = args[1];
      this.oldlenp = args[2];
      dlog("sysctlbyname", this.key);
    } catch (_) { this.key=null; this.oldp=ptr(0); this.oldlenp=ptr(0); }
  },
  function (retval) {
    if (MODE !== "bypass" || !this.key) return;
    const sus = /(jail|security|kern\.proc|proc_pid|proc_pidinfo|debug|trace|sandbox)/i;
    if (sus.test(this.key)) {
      try {
        if (!this.oldp.isNull()) {
          let n = 0;
          if (!this.oldlenp.isNull()) {
            n = Process.pointerSize === 8 ? Number(Memory.readU64(this.oldlenp))
                                          : Memory.readU32(this.oldlenp);
          }
          if (n > 0) {
            Memory.protect(this.oldp, n, 'rw-');
            Memory.writeByteArray(this.oldp, new Uint8Array(n));
          }
        }
        retval.replace(ptr(0)); // success
        log("sysctlbyname neutralized -> success", this.key);
      } catch(e) {
        retval.replace(ptr(-1));
        log("sysctlbyname fallback -1", this.key, e);
      }
    }
  }
);

/*** sysctl (MIB): success + zero-fill for KERN_PROC queries ***/
hookExport("sysctl", ENABLE_SYSCTL,
  function (args) {
    try {
      this.namePtr  = args[0]; this.namelen = args[1].toUInt32();
      this.oldp     = args[2]; this.oldlenp = args[3];
      this.mib = [];
      for (let i=0;i=2 && this.mib[0]===CTL_KERN && this.mib[1]===KERN_PROC;
      if (looksProcQuery) {
        if (!this.oldp.isNull()) {
          let n = 0;
          if (!this.oldlenp.isNull()) n = Process.pointerSize===8 ? Number(Memory.readU64(this.oldlenp)) : Memory.readU32(this.oldlenp);
          if (n>0){ Memory.protect(this.oldp, n, 'rw-'); Memory.writeByteArray(this.oldp, new Uint8Array(n)); }
        }
        retval.replace(ptr(0));
        log("sysctl MIB neutralized -> success", JSON.stringify(this.mib));
      }
    } catch(e){ log("sysctl MIB handling error", e); }
  }
);

// statfs — neutralize /var/jb mount probes
hookExport("statfs", ENABLE_STATFS,
  function(args){ try{ this.p = Memory.readUtf8String(args[0]); dlog("statfs", this.p); }catch(_){ this.p=null; } },
  function(ret){ if (MODE==="bypass" && this.p && this.p.indexOf("/var/jb")!==-1){ ret.replace(ptr(0)); log("statfs neutralized", this.p);} }
);

// csops / csops_audittoken — zero out buffers + success
hookExport("csops", ENABLE_CSOPS,
  function(args){ try{ this.useraddr=args[2]; this.usersize=args[3].toInt32(); dlog("csops", "size="+this.usersize); }catch(_){ } },
  function(ret){ if (MODE==="bypass"){ try{ if(!this.useraddr.isNull() && this.usersize>0){ Memory.protect(this.useraddr,this.usersize,'rw-'); Memory.writeByteArray(this.useraddr,new Uint8Array(this.usersize)); } }catch(_){ } ret.replace(ptr(0)); log("csops neutralized"); } }
);
hookExport("csops_audittoken", ENABLE_CSOPSAUD,
  function(args){ try{ this.useraddr=args[2]; this.usersize=args[3].toInt32(); dlog("csops_audittoken","size="+this.usersize);}catch(_){ } },
  function(ret){ if (MODE==="bypass"){ try{ if(!this.useraddr.isNull() && this.usersize>0){ Memory.protect(this.useraddr,this.usersize,'rw-'); Memory.writeByteArray(this.useraddr,new Uint8Array(this.usersize)); } }catch(_){ } ret.replace(ptr(0)); log("csops_audittoken neutralized"); } }
);

// dlsym — hide suspicious symbol lookups
hookExport("dlsym", ENABLE_DLSYM,
  function(args){ try{ this.sym = Memory.readUtf8String(args[1]); dlog("dlsym", this.sym); }catch(_){ this.sym=null; } },
  function(ret){ if (MODE==="bypass" && this.sym && HIDE_SYMBOLS.some(s=>this.sym.indexOf(s)!==-1)){ ret.replace(ptr(0)); log("dlsym hide", this.sym); } }
);

// connect — block obvious Frida port scans
hookExport("connect", ENABLE_CONNECT,
  function(args){
    try{
      this.sockaddr=args[1];
      var family=Memory.readU16(this.sockaddr.add(1)); // AF
      if (family===2){ var port=Memory.readU16(this.sockaddr.add(2)); this.port=((port&0xff)<<8)|((port>>8)&0xff); dlog("connect AF_INET port", this.port); }
      else this.port=0;
    } catch(_){ this.port=0; }
  },
  function(ret){ if (MODE==="bypass" && this.port && FRIDA_PORTS.has(this.port)){ ret.replace(ptr(-1)); log("connect block port", this.port); } }
);

// sandbox_check — allow
hookExport("sandbox_check", ENABLE_SBCHK,
  function(args){ try{ this.op=args[1].toInt32(); dlog("sandbox_check op", this.op);}catch(_){ this.op=0; } },
  function(ret){ if (MODE==="bypass"){ ret.replace(ptr(0)); log("sandbox_check allowed"); } }
);

// proc_pidinfo / proc_pidpath — success + zeroed data
hookExport("proc_pidinfo", ENABLE_PROCINFO,
  function(args){ try{ this.buffer=args[3]; this.bufsize=args[4].toInt32(); dlog("proc_pidinfo size", this.bufsize);}catch(_){ } },
  function(ret){ if (MODE==="bypass"){ try{ if(!this.buffer.isNull()&&this.bufsize>0){ Memory.protect(this.buffer,this.bufsize,'rw-'); Memory.writeByteArray(this.buffer,new Uint8Array(this.bufsize)); } }catch(_){ } ret.replace(ptr(this.bufsize)); log("proc_pidinfo neutralized"); } }
);
hookExport("proc_pidpath", ENABLE_PROCINFO,
  function(args){ try{ this.buffer=args[1]; this.bufsize=args[2].toUInt32(); dlog("proc_pidpath size", this.bufsize);}catch(_){ } },
  function(ret){ if (MODE==="bypass"){ try{ if(!this.buffer.isNull()&&this.bufsize>0){ Memory.protect(this.buffer,this.bufsize,'rw-'); Memory.writeByteArray(this.buffer,new Uint8Array(this.bufsize)); } }catch(_){ } ret.replace(ptr(0)); log("proc_pidpath neutralized"); } }
);

// getppid — pretend parent is launchd (1)
hookExport("getppid", ENABLE_GETPPID, null, function(ret){ if (MODE==="bypass"){ ret.replace(ptr(1)); log("getppid -> 1"); } });

// risky (log-only)
hookExport("dlopen", ENABLE_DLOPEN, function(args){ try{ dlog("dlopen", Memory.readUtf8String(args[0])); }catch(_){ } });
if (ENABLE_DYLD){ hookExport("_dyld_get_image_name", true, function(args){ this.idx=args[0].toUInt32(); }, function(ret){ try{ dlog("_dyld_get_image_name", Memory.readUtf8String(ret)); }catch(_){ } }); }
hookExport("SecStaticCodeCheckValidity", ENABLE_SEC, null, function(ret){ if (MODE==="bypass") ret.replace(ptr(0)); });
hookExport("memcmp", ENABLE_MEMCMP, function(args){ this.len=args[2].toInt32(); dlog("memcmp len", this.len); }, function(ret){});

// ==================== Anti-exit hardening ====================
(function installAntiExit(){
  if (!ENABLE_ANTI_EXIT && !ENABLE_ANTI_ASSERT && !ENABLE_ANTI_OBJC_THROW && !ENABLE_ANTI_SWIFT && !ENABLE_ANTI_CXX) return;

  const libc = null;
  function find(name){ const a=Module.findExportByName(libc, name); if(!a) log("not found:", name); return a; }
  function replaceVoid(name){
    const addr=find(name); if(!addr) return;
    const cb=new NativeCallback(function(){ log(name,"blocked"); }, 'void', []);
    Interceptor.replace(addr, cb); log("anti-exit: replaced", name);
  }
  function replaceInt(name, argTypes, handler){
    const addr=find(name); if(!addr) return;
    const cb=new NativeCallback(handler, 'int', argTypes);
    Interceptor.replace(addr, cb); log("anti-exit: replaced", name);
  }
  function attach(name, onEnter, onLeave){
    const addr=find(name); if(!addr) return;
    Interceptor.attach(addr,{onEnter:onEnter||function(){},onLeave:onLeave||function(){}});
    log("anti-exit: hooked", name);
  }

  const SIG = { ABRT:6, KILL:9, TRAP:5, STOP:17 };
  const deadly = new Set([SIG.KILL, SIG.ABRT, SIG.TRAP, SIG.STOP]);

  if (ENABLE_ANTI_EXIT){
    replaceVoid("exit");
    replaceVoid("_exit");
    replaceVoid("_Exit");
    replaceVoid("abort");

    const getpidA = find("getpid");
    const getpidF = getpidA ? new NativeFunction(getpidA,'int',[]) : null;

    replaceInt("raise", ['int'], function(sig){ if(deadly.has(sig)){ log("raise blocked", sig); return 0; } log("raise swallow", sig); return 0; });
    replaceInt("kill", ['int','int'], function(pid,sig){
      try{ const self=getpidF?getpidF():-1; if((self===-1||pid===self)&&deadly.has(sig)){ log("kill blocked pid",pid,"sig",sig); return 0; } }catch(_){}
      log("kill neutralized pid",pid,"sig",sig); return 0;
    });
    replaceInt("pthread_kill", ['pointer','int'], function(th,sig){ if(deadly.has(sig)){ log("pthread_kill blocked", sig); return 0; } log("pthread_kill swallow", sig); return 0; });
    replaceInt("__pthread_kill", ['int','int'], function(tid,sig){ if(deadly.has(sig)){ log("__pthread_kill blocked", sig); return 0; } log("__pthread_kill swallow", sig); return 0; });
  }

  if (ENABLE_ANTI_ASSERT){
    attach("__assert_rtn", function(args){
      try{
        const func=Memory.readUtf8String(args[0]); const file=Memory.readUtf8String(args[1]);
        const line=args[2].toInt32(); const expr=Memory.readUtf8String(args[3]);
        log("__assert_rtn blocked", func, file+":"+line, expr);
      }catch(_){ log("__assert_rtn blocked"); }
    }, function(ret){ ret.replace(ptr(0)); });

    replaceVoid("__stack_chk_fail");
    replaceVoid("os_crash");
    replaceInt("abort_with_payload", ['uint','uint64','pointer','uint64','pointer','uint32'], function(){ log("abort_with_payload blocked"); return 0; });
  }

  if (ENABLE_ANTI_OBJC_THROW){
    attach("objc_exception_throw",
      function(args){ try{ const exc=new ObjC.Object(args[0]); log("objc_exception_throw blocked", exc.toString()); }catch(_){ log("objc_exception_throw blocked"); } },
      function(ret){ ret.replace(ptr(0)); }
    );
  }

  if (ENABLE_ANTI_SWIFT){
    replaceVoid("swift_fatalError");
    replaceVoid("swift_reportError");
    replaceVoid("_swift_runtime_on_report");
  }

  if (ENABLE_ANTI_CXX){
    // Aggressive: uncomment only if needed; can mask real logic.
    // replaceVoid("__cxa_throw");
    // replaceVoid("__cxa_rethrow");
    // replaceVoid("__cxa_bad_cast");
    // replaceVoid("__cxa_call_terminate");
  }
})();
console.log("[*] SAFE+X loaded. MODE =", MODE, "| Anti-exit enabled =", ENABLE_ANTI_EXIT);

        // ---------------------------------------------------------------------
        // !!! PASTE YOUR EXISTING JAILBREAK BYPASS CODE ABOVE THIS LINE !!!
        // ---------------------------------------------------------------------
        
        console.log("[+] Jailbreak Bypass Hooks Loaded.");
        
    } catch (e) {
        console.log(`[ERROR] Failed to load Jailbreak Bypass hooks: ${e.message}`);
    }

    // =========================================================================
    // SECTION 2: LOCATION CHECK TRACING LOGIC (Targeting Swift Class)
    // =========================================================================
    
    // The class name is the mangled Swift name: WhereAmIReally.LocationManager
    const TARGET_CLASS_NAME = "WhereAmIReally.LocationManager";
    const TargetClass = ObjC.classes[TARGET_CLASS_NAME]; // Swift classes are exposed via ObjC.classes

    if (TargetClass) {
        console.log(`[*] Found Target Class: ${TARGET_CLASS_NAME}. Starting method tracing...`);

        // Swift methods are not always exposed via $ownMethods. We will use a more generic approach.
        // We will iterate over all methods and hook them.
        
        // Use the Objective-C runtime to get the class methods
        const methods = TargetClass.$ownMethods;

        methods.forEach(methodName => {
            try {
                const method = TargetClass[methodName];
                
                Interceptor.attach(method.implementation, {
                    onEnter: function(args) {
                        // Log the method call
                        console.log(`[TRACE] Called: ${methodName}`);
                        
                        // Heuristic to highlight potential location check functions
                        if (methodName.includes("location") || methodName.includes("coordinate") || methodName.includes("check") || methodName.includes("flag")) {
                            console.log(`[!!!] POTENTIAL TARGET METHOD: ${methodName}`);
                        }
                    },
                    onLeave: function(retval) {
                        // Optionally log return value if it's a boolean (0 or 1)
                        if (retval.toInt32() === 0 || retval.toInt32() === 1) {
                            console.log(`[TRACE] Returned: ${retval.toInt32()}`);
                        }
                    }
                });
            } catch (e) {
                // console.log(`[-] Could not hook method ${methodName}: ${e.message}`);
            }
        });

        console.log(`[*] Tracing ${methods.length} methods in ${TARGET_CLASS_NAME}.`);
        console.log("[*] Now interact with the app (e.g., trigger a location update) and watch the output.");

    } else {
        // Fallback for mangled name if the direct name fails
        const MANGLED_CLASS_NAME = "_TtC14WhereAmIReally15LocationManager";
        const MangledClass = ObjC.classes[MANGLED_CLASS_NAME];
        
        if (MangledClass) {
            console.log(`[WARNING] Direct class name failed. Using mangled name: ${MANGLED_CLASS_NAME}.`);
            // The logic for tracing the mangled class would be the same as above.
            // For simplicity, we will ask the user to confirm the correct name.
            console.log("[HINT] Please try to use the correct name 'WhereAmIReally.LocationManager' or the mangled name in the script.");
        } else {
            console.log(`[ERROR] Target Class "${TARGET_CLASS_NAME}" (and mangled name) not found.`);
            console.log("[HINT] Please ensure the class name is correct. Try 'frida-trace -i \"*LocationManager*\"' to confirm.");
        }
    }

} else {
    console.log("[-] Objective-C Runtime not available. This script is for iOS/macOS targets.");
}

B. The Solution

After bypassing the checks, we retrieve the final flag/solution.

Solution

'use strict';

/**
 * inject_force_location.js
 *
 * - Forces authorization status (authorizedAlways)
 * - Disables CLLocationSourceInformation.isSimulatedBySoftware checks
 * - Hooks the app's Swift LocationManager '- locationManager:didUpdateLocations:'
 * and replaces the locations array with a single forged CLLocation(LAT, LON)
 *
 * Edit FAKE_LAT / FAKE_LON below to the coordinates you want the app to see.
 */

setImmediate(function () {
  // ---------- CONFIG ----------
  const FAKE_LAT = 49.0666666667;
  const FAKE_LON = 2.3250000000;

  // ---------- UTIL ----------
  function safeLog(...args) {
    try { console.log('[FRIDA] ' + args.join(' ')); } catch (_) {}
  }

  if (!ObjC.available) {
    safeLog('ObjC runtime not available — aborting');
    return;
  }

  // ---------- ANTI-SIMULATION: hook CLLocationSourceInformation.isSimulatedBySoftware ----------
  try {
    const CLSrcInfo = ObjC.classes.CLLocationSourceInformation;
    if (CLSrcInfo && CLSrcInfo['- isSimulatedBySoftware']) {
      Interceptor.attach(CLSrcInfo['- isSimulatedBySoftware'].implementation, {
        onEnter: function () { /* no-op */ },
        onLeave: function (retval) {
          try { retval.replace(ptr(0)); } catch (e) { /* ignore */ }
        }
      });
      safeLog('anti-simulation hook installed (isSimulatedBySoftware -> FALSE)');
    } else {
      safeLog('CLLocationSourceInformation or selector not found');
    }
  } catch (e) {
    safeLog('anti-simulation hook error:', e);
  }

  // ---------- FORCE AUTHORIZATION STATUS ----------
  // +[CLLocationManager authorizationStatus] -> force to kCLAuthorizationStatusAuthorizedAlways (3)
  try {
    const CLM = ObjC.classes.CLLocationManager;
    if (CLM && CLM['+ authorizationStatus']) {
      Interceptor.attach(CLM['+ authorizationStatus'].implementation, {
        onEnter: function () { /* no-op */ },
        onLeave: function (retval) {
          try { retval.replace(ptr(3)); } catch (e) { /* ignore */ }
        }
      });
      safeLog('CLLocationManager.authorizationStatus forced to "authorizedAlways" (3)');
    } else {
      safeLog('CLLocationManager.authorizationStatus not found');
    }
  } catch (e) {
    safeLog('authorization hook error:', e);
  }

  // ---------- HELPER: build a CLLocation instance ----------
  function buildCLLocation(lat, lon) {
    try {
      const CLLocation = ObjC.classes.CLLocation;
      // initWithLatitude:longitude:
      return CLLocation.alloc().initWithLatitude_longitude_(lat, lon);
    } catch (e) {
      safeLog('buildCLLocation error:', e);
      return null;
    }
  }

  // ---------- HOOK: Swift LocationManager's -locationManager:didUpdateLocations: ----------
  // Common Swift-mangled class name observed earlier: _TtC14WhereAmIReally15LocationManager
  // We'll try that name first, fallback to enumerating classes that match both tokens.
  try {
    let targetClassName = '_TtC14WhereAmIReally15LocationManager';
    let targetClass = ObjC.classes[targetClassName];

    if (!targetClass) {
      // fallback enumeration: find a class that contains both substrings
      const all = ObjC.enumerateLoadedClassesSync();
      for (const k in all) {
        if (k.indexOf('WhereAmIReally') !== -1 && k.indexOf('LocationManager') !== -1) {
          targetClassName = k;
          targetClass = ObjC.classes[k];
          break;
        }
      }
    }

    if (!targetClass) {
      safeLog('Swift LocationManager class not found (tried default + enumeration). Will not install delegate hook.');
      return;
    }

    const selector = '- locationManager:didUpdateLocations:';
    if (!targetClass[selector]) {
      safeLog(`Selector ${selector} not found on ${targetClassName}`);
      return;
    }

    // Attach and replace the locations arg (args[3]) with an NSArray containing our fake CLLocation
    Interceptor.attach(targetClass[selector].implementation, {
      onEnter: function (args) {
        try {
          const fakeLoc = buildCLLocation(FAKE_LAT, FAKE_LON);
          if (!fakeLoc) {
            safeLog('failed to create fake CLLocation; skipping injection');
            return;
          }

          const NSArray = ObjC.classes.NSArray;
          const injectedArray = NSArray.arrayWithObject_(fakeLoc);
          // Replace the original locations parameter with our injected NSArray
          args[3] = injectedArray.handle;

          safeLog(`injected fake location into ${targetClassName} ${selector} -> ${FAKE_LAT.toFixed(6)}, ${FAKE_LON.toFixed(6)}`);
        } catch (e) {
          safeLog('error during delegate injection:', e);
        }
      }
    });

    safeLog(`hook installed: ${targetClassName} ${selector}`);
  } catch (e) {
    safeLog('delegate hook install error:', e);
  }

}); // setImmediate