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.

image.png

image.png

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.

image.png

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

image.png

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

image.png

┌──(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

image.png

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]