Back to Blog
iOS SecurityJan 03, 202615 min read
8KSec - WhereAmIReally
My hands-on iOS solution
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.
B. Investigating the application binaries
- Checking the binaries for hardcoded strings or hints.
C. Investigating Frameworks
2. Jailbreak Detection Analysis
A. Identifying the Check
- Locating the jailbreak detection mechanism.
B. The Jailbreak Class
- Analyzing the specific class responsible for the checks.
3. Bypassing and Solution
A. Bypassing the Jailbreak Check
- Successfully bypassing the restriction using Frida or other tools.
/**
* 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.
'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