Detección y Bypass de Anti-Tampering (Anti-Frida)
Este reto es del laboratorio FridaInTheMiddle, de 8kSec Academy curso iOS Application Exploitation Challenges. A diferencia de los retos de DVIA-v2, aquí la restricción del propio reto es: no se permite reversing estático, solo análisis dinámico con Frida.


la app implementa anti-tampering activo: detecta a Frida por puerto de socket y por librerías cargadas (dylibs), y si la detecta, cierra la app en 3 segundos.

Paso 1: Confirmar el dispositivo y la app
para esto ejecutamos el comando frida-ps -Uai
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida-ps -Uai
PID Name Identifier
---- ---------------- ---------------------------------
3708 App Store com.apple.AppStore
728 Calendario com.apple.mobilecal
6001 Configuración com.apple.Preferences
3828 Cámara com.apple.camera
2382 DVIA-v2 com.highaltitudehacks.DVIAswiftv2
6228 Dopamine com.opa334.Dopamine.8MP3V4USL6
6002 Filza com.tigisoftware.Filza
4518 FortiClientVPN com.fortinet.forticlient.vpn
6427 FridaInTheMiddle com.8ksec.FridaInTheMiddle
4517 Safari com.apple.mobilesafari
5752 Sileo org.coolstar.SileoStore
5841 TestFlight com.apple.TestFlight
6000 TrollStore Lite com.opa334.TrollStoreLite
4919 Vysor io.vysor.app
- Archivos com.apple.DocumentsApp
- Atajos com.apple.shortcuts
- Bolsa com.apple.stocks
- Brújula com.apple.compass
- Calculadora com.apple.calculator
- Casa com.apple.Home
- Clima com.apple.weather
- Consejos com.apple.tips
- Contactos com.apple.MobileAddressBook
- Drive com.google.Drive
- Encontrar com.apple.findmy
- FaceTime com.apple.facetime
- Fitness com.apple.Fitness
- Fotos com.apple.mobileslideshow
- Freeform com.apple.freeform
- Libros com.apple.iBooks
- Lulo STG co.com.lulobank.stg.8MP3V4USL6
- Lupa com.apple.Magnifier
- Mail com.apple.mobilemail
- Mapas com.apple.Maps
- Medición com.apple.measure
- Mensajes com.apple.MobileSMS
- Música com.apple.Music
- NewTerm ws.hbang.Terminal
- Notas com.apple.mobilenotes
- Notas de voz com.apple.VoiceMemos
- Podcasts com.apple.podcasts
- Recordatorios com.apple.reminders
- Reloj com.apple.mobiletimer
- Salud com.apple.Health
- TV com.apple.tv
- Teléfono com.apple.mobilephone
- Traducir com.apple.Translate
- Wallet com.apple.Passbook
- Watch com.apple.Bridge
- Zebra xyz.willy.Zebra
- iTunes Store com.apple.MobileStore
Paso 2: Reconocimiento dinámico de símbolos
como el reto no permite reversing estático, nada de strings/class-dump sobre el binario, hacemos el descubrimiento de símbolos en runtime con un script de Frida que enumera todo lo que carga el módulo del reto, guardamos el siguiente script como recon.js
Process.enumerateModules().forEach(mod => {
if (mod.name.includes("FridaInTheMiddle")) {
console.log("[+] Modulo encontrado: " + mod.name);
mod.enumerateSymbols().forEach(sym => {
if (sym.name.includes("Probe") || sym.name.includes("SanityCheck") || sym.name.includes("fridaDetected") || sym.name.includes("dummyFunction")) {
console.log(" -> " + sym.name + " @ " + sym.address);
}
});
}
});
y ejecutamos el comando frida -U -f com.8ksec.FridaInTheMiddle -l recon.js
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l recon.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawning `com.8ksec.FridaInTheMiddle`...
[+] Modulo encontrado: FridaInTheMiddle
[+] Modulo encontrado: FridaInTheMiddle.debug.dylib
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvg @ 0x1041dc364
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvs @ 0x1041dc3e8
-> $s16FridaInTheMiddle11ContentViewV14$fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI7BindingVySbGvg @ 0x1041dc480
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvg @ 0x1041dc51c
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvs @ 0x1041dc550
-> $s16FridaInTheMiddle17canarySocketProbeSbyF6$deferL_yyF @ 0x1041e11c0
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvpfP @ 0x1041dc314
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvg @ 0x1041dc364
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvs @ 0x1041dc3e8
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvpfi @ 0x1041dc474
-> $s16FridaInTheMiddle11ContentViewV14$fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI7BindingVySbGvg @ 0x1041dc480
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvg @ 0x1041dc51c
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvs @ 0x1041dc550
-> $s16FridaInTheMiddle11ContentViewV13dummyFunction4flagySS_tF @ 0x1041ddd24
-> $s16FridaInTheMiddle17systemSanityCheckSbyF @ 0x1041e0bfc
-> $s16FridaInTheMiddle17canarySocketProbeSbyF @ 0x1041e0c40
-> $s16FridaInTheMiddle17dyldSnapshotProbeSbyF @ 0x1041e0ff0
-> $s16FridaInTheMiddle17canarySocketProbeSbyF6$deferL_yyF @ 0x1041e11c0
-> $s16FridaInTheMiddle11ContentViewV13dummyFunction4flagySS_tF @ 0x1041ddd24
-> $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvpfP @ 0x1041dc314
-> $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvpfi @ 0x1041dc474
-> $s16FridaInTheMiddle17canarySocketProbeSbyF @ 0x1041e0c40
-> $s16FridaInTheMiddle17dyldSnapshotProbeSbyF @ 0x1041e0ff0
-> $s16FridaInTheMiddle17systemSanityCheckSbyF @ 0x1041e0bfc
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]-> Process terminated
[iOS Device::com.8ksec.FridaInTheMiddle ]->
Thank you for using Frida!
con esto identificamos, las funciones responsables de la detección (canarySocketProbe, dyldSnapshotProbe, systemSanityCheck) y la función objetivo (dummyFunction) que recibe la flag como argumento
Paso 3: Bypass del anti-tampering
forzamos que las funciones de detección siempre retornen false. la lógica es: cada vez que la función original termina de ejecutarse onLeave en vez de dejar que devuelva lo que calculó true si detectó Frida, sobreescribimos ese valor de retorno a 0x0 false en Swift/Bool a nivel de registro. Así la app nunca se entera de que algo la está detectando
guardamos el siguiente script como bypass.js
function patchDetection() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
try {
mod.enumerateSymbols().forEach(sym => {
const isCanary = sym.name.includes("canarySocketProbe") && sym.name.indexOf("$deferL_") === -1;
const isFridaDetectedGetter = sym.name.includes("fridaDetected") && sym.name.endsWith("vg");
if (isCanary || isFridaDetectedGetter) {
try {
Interceptor.attach(sym.address, {
onLeave: function (retval) {
this.context.x0 = ptr(0); // false
console.log("[+] Forzado a false: " + sym.name);
}
});
console.log("[+] Parcheado: " + sym.name);
} catch (e) {
console.log("[-] No se pudo parchear " + sym.name + ": " + e);
}
}
});
} catch (e) {}
});
}
patchDetection();
Y corremos el comando frida -U -f com.8ksec.FridaInTheMiddle -l bypass.js
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l bypass.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawning `com.8ksec.FridaInTheMiddle`...
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvg
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV14$fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI7BindingVySbGvg
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvg
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV13fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLLSbvg
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV14$fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI7BindingVySbGvg
[+] Parcheado: $s16FridaInTheMiddle11ContentViewV14_fridaDetected33_47C5B164625D4EB9AAF9AE914D67372FLL7SwiftUI5StateVySbGvg
[+] Parcheado: $s16FridaInTheMiddle17canarySocketProbeSbyF
[+] Parcheado: $s16FridaInTheMiddle17canarySocketProbeSbyF
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]-> [+] Forzado a false: $s16FridaInTheMiddle17canarySocketProbeSbyF
[+] Forzado a false: $s16FridaInTheMiddle17canarySocketProbeSbyF

Paso 4: Interceptar el argumento de dummyFunction
esta es la parte más interesante del reto: la flag no está en un string estático, se pasa como argumento a dummyFunction(flag: String) en tiempo de ejecución. como el reto prohíbe reversing estático, no podemos ver el código fuente para saber cómo viene ese argumento, hay que descubrirlo en runtime, observando la memoria real mientras la función se ejecuta
lo primero que probamos fue asumir que el argumento llega en el registro x0 como si fuera un puntero a texto, y convertirlo directo
guardamos el siguiente script como lectura.js
function patchDetection() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
try {
mod.enumerateSymbols().forEach(sym => {
const isCanary = sym.name.includes("canarySocketProbe") && sym.name.indexOf("$deferL_") === -1;
const isFridaDetectedGetter = sym.name.includes("fridaDetected") && sym.name.endsWith("vg");
if (isCanary || isFridaDetectedGetter) {
try {
Interceptor.attach(sym.address, {
onLeave: function (retval) {
this.context.x0 = ptr(0);
}
});
} catch (e) {}
}
});
} catch (e) {}
});
}
function hookDummyFunctionIntento1() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
mod.enumerateSymbols().forEach(sym => {
if (sym.name.indexOf("dummyFunction") !== -1) {
Interceptor.attach(sym.address, {
onEnter: function (args) {
console.log("[!] dummyFunction llamada - flag: " + this.context.x0.readUtf8String());
}
});
}
});
});
}
patchDetection();
hookDummyFunctionIntento1();
y ejecutamos el comando frida -U -f com.8ksec.FridaInTheMiddle -l lectura.js
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l lectura.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]-> Error: access violation accessing 0xd00000000000001f
at onEnter (/mnt/angussMoody/8ksec/1 Reto/lectura.js:29)
Error: access violation accessing 0xd00000000000001f
at onEnter (/mnt/angussMoody/8ksec/1 Reto/lectura.js:29)
el error access violation accessing 0xd00000000000001f confirma que: x0 no es una dirección de memoria válida donde haya texto, es un valor que trae otro tipo de información codificada, y por eso Frida ni siquiera logra leerlo como string. El argumento no llega como un puntero simple en x0, así que en vez de adivinar qué formato tiene, lo mejor es ver la memoria cruda con nuestros propios ojos
Volcamos los primeros 96 bytes de args[0] y args[1] tal como vienen, para verlos directamente
guardamos el siguiente script como diagnostico.js
function patchDetection() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
try {
mod.enumerateSymbols().forEach(sym => {
const isCanary = sym.name.includes("canarySocketProbe") && sym.name.indexOf("$deferL_") === -1;
const isFridaDetectedGetter = sym.name.includes("fridaDetected") && sym.name.endsWith("vg");
if (isCanary || isFridaDetectedGetter) {
try {
Interceptor.attach(sym.address, {
onLeave: function (retval) {
this.context.x0 = ptr(0);
console.log("[+] Forzado a false: " + sym.name);
}
});
} catch (e) {}
}
});
} catch (e) {}
});
}
function hookDummyFunctionDiagnostico() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
mod.enumerateSymbols().forEach(sym => {
if (sym.name.indexOf("dummyFunction") !== -1) {
Interceptor.attach(sym.address, {
onEnter: function (args) {
console.log('\n[!] dummyFunction llamada - volcando args[0] y args[1]:');
for (var i = 0; i < 2; i++) {
try {
console.log('--- args[' + i + '] ---');
console.log(hexdump(args[i], { length: 96, header: true }));
} catch (e) {
console.log('args[' + i + '] no es un puntero valido');
}
}
}
});
}
});
});
}
patchDetection();
hookDummyFunctionDiagnostico();
ejecutamos el comando frida -U -f com.8ksec.FridaInTheMiddle -l diagnostico.js y unas vez nos cargue la aplicación dar clic en Intercept First Argument Using Frida

┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l diagnostico.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]-> [+] Forzado a false: $s16FridaInTheMiddle17canarySocketProbeSbyF
[+] Forzado a false: $s16FridaInTheMiddle17canarySocketProbeSbyF
[!] dummyFunction llamada - volcando args[0] y args[1]:
--- args[0] ---
args[0] no es un puntero valido
--- args[1] ---
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
80000001041c16c0 65 63 74 65 64 2e 20 45 78 69 74 69 6e 67 20 69 ected. Exiting i
80000001041c16d0 6e 20 33 20 73 65 63 6f 6e 64 73 2e 2e 2e 00 00 n 3 seconds.....
80000001041c16e0 43 54 46 7b 79 6f 75 5f 65 76 61 64 65 64 5f 66 CTF{you_evaded_f
80000001041c16f0 72 69 64 61 5f 64 65 74 65 63 74 69 6f 6e 7d 00 rida_detection}.
80000001041c1700 31 32 37 2e 30 2e 30 2e 31 00 46 72 69 64 61 47 127.0.0.1.FridaG
80000001041c1710 61 64 67 65 74 00 00 00 00 00 00 00 00 00 00 00 adget...........
[!] dummyFunction llamada - volcando args[0] y args[1]:
--- args[0] ---
args[0] no es un puntero valido
--- args[1] ---
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
80000001041c16c0 65 63 74 65 64 2e 20 45 78 69 74 69 6e 67 20 69 ected. Exiting i
80000001041c16d0 6e 20 33 20 73 65 63 6f 6e 64 73 2e 2e 2e 00 00 n 3 seconds.....
80000001041c16e0 43 54 46 7b 79 6f 75 5f 65 76 61 64 65 64 5f 66 CTF{you_evaded_f
80000001041c16f0 72 69 64 61 5f 64 65 74 65 63 74 69 6f 6e 7d 00 rida_detection}.
80000001041c1700 31 32 37 2e 30 2e 30 2e 31 00 46 72 69 64 61 47 127.0.0.1.FridaG
80000001041c1710 61 64 67 65 74 00 00 00 00 00 00 00 00 00 00 00 adget...........
con este volcado podemos revisar los argumentos args[0] o args[1] y aparece el texto legible de la flag, esto nos dice dónde leer, en vez de asumirlo y con el offset confirmado en el paso anterior, armamos el hook definitivo que lee la flag directo
guardamos el siguiente script como flag.js
function patchDetection() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
try {
mod.enumerateSymbols().forEach(sym => {
const isCanary = sym.name.includes("canarySocketProbe") && sym.name.indexOf("$deferL_") === -1;
const isFridaDetectedGetter = sym.name.includes("fridaDetected") && sym.name.endsWith("vg");
if (isCanary || isFridaDetectedGetter) {
try {
Interceptor.attach(sym.address, {
onLeave: function (retval) {
this.context.x0 = ptr(0);
}
});
} catch (e) {}
}
});
} catch (e) {}
});
}
function hookDummyFunctionFlag() {
Process.enumerateModules().forEach(mod => {
if (mod.name !== "FridaInTheMiddle.debug.dylib") return;
mod.enumerateSymbols().forEach(sym => {
if (sym.name.indexOf("dummyFunction") !== -1) {
Interceptor.attach(sym.address, {
onEnter: function (args) {
try {
var flag = args[1].add(32).readUtf8String();
console.log("\n[FLAG " + flag);
} catch (e) {
console.log("[-] Error leyendo la flag: " + e);
}
}
});
}
});
});
}
patchDetection();
hookDummyFunctionFlag();
ejecutamos el comando frida -U -f com.8ksec.FridaInTheMiddle -l flag.js
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l flag.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]->
y unas vez nos cargue la aplicación le damos clic de nuevo en Intercept First Argument Using Frida

y ya vemos la flag CTF{you_evaded_frida_detection} de una forma más legible y ordenada
┌──(root㉿angussMoody)-[/mnt/angussMoody/8ksec/1 Reto]
└─# frida -U -f com.8ksec.FridaInTheMiddle -l flag.js
____
/ _ | Frida 17.15.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to iOS Device (id=137e70f9805dfaa6291989bfed24126fc4abdd5f)
Spawned `com.8ksec.FridaInTheMiddle`. Resuming main thread!
[iOS Device::com.8ksec.FridaInTheMiddle ]->
[FLAG] CTF{you_evaded_frida_detection}
Conclusión
este reto mostró cómo el anti-tampering de una app puede neutralizarse identificando en runtime las funciones responsables, sin tocar el binario y forzando su valor de retorno. pero la parte más valiosa fue la segunda mitad: cuando el dato que buscamos no está en un string plano sino que se pasa como argumento de una función en memoria, no hay que asumir cómo viene, hay que volcarla cruda hexdump y leer el patrón con los propios ojos antes de escribir el script de extracción. el primer intento, leer x0 directo falló y dio basura; fue el volcado el que reveló que el texto real empezaba 32 bytes después del puntero de args[1]