PinBreaker: una app Android pensada para enseñar por qué ocultar secretos en cliente no funciona
Por qué ocultar un secreto en el cliente no protege nada, contado sobre una app Android que guarda su PIN en el propio código.
Principiante · Android
URL del reto: labs.thehackerslabs.com/machines/68
Por qué la diseñé
PinBreaker enseña una idea muy concreta, cuando un secreto crítico se mete en el cliente, la validación deja de ser un control y pasa a ser una barrera de mentira. La app pide un PIN y, con él, se calcula un SHA256 para sacar la flag. Lo importante es entender qué decisión de diseño la hace débil desde el principio, más que romper la app.
La cadena es corta a propósito, sin una APK llena de trucos ni ofuscación agresiva. Quería obligar a mirar el binario con criterio, descompilar, localizar el punto lógico y ver cómo se valida la entrada. Si una app cliente lleva dentro el valor esperado o la lógica completa de validación, cualquiera con el paquete la reconstruye. No se deben confiar secretos ni decisiones de autorización al lado cliente.
La máquina de un vistazo
El objetivo es una sola app Android, sin red que enumerar. Todo el fallo vive dentro del paquete. Se descompila el APK, se localiza la actividad principal, se lee la comparación del PIN contra un valor fijo y con ese PIN se deriva la flag.
Cómo la monté, paso a paso
Un secreto metido en el código
El fallo entero cabe en una línea. En la actividad principal de la app, la comprobación del PIN es una comparación directa contra una constante escrita en el propio código, del tipo areEqual(pin, "…"). No hay comprobación remota, no hay separación entre la interfaz y la decisión de seguridad, y no hay nada que proteja ese valor una vez alguien tiene el APK. Quería mostrar el error de la forma más limpia posible.
El reversing que no necesita ser sofisticado
Con jadx se descompila la app y se llega a esa clase. Aunque la herramienta suelte errores parciales, el análisis sigue siendo útil, basta con recuperar las clases relevantes y seguir el flujo principal. Lo puse así a propósito, para que quien empieza no se bloquee por mensajes ruidosos y vea que, si el código sensible se ha recuperado, el problema ya está a la vista. Las decisiones sensibles suelen concentrarse en sitios muy predecibles, la actividad principal, un controlador de login o un validador local.
Del PIN a la flag, cuidando el formato
Recuperado el PIN, la flag sale de su SHA256. Y aquí dejé un matiz que enseña por sí solo, según cómo se calcule el hash, con o sin el salto de línea final, el resultado cambia. Incluso con el secreto ya en la mano, hay que respetar exactamente la transformación pedida. En análisis de aplicaciones los detalles de formato importan.
Qué quería enseñar
Entregar al cliente la lógica de validación y el secreto equivale a renunciar a protegerlos. No hace falta explotar memoria, interceptar tráfico ni abusar de componentes complejos, basta con abrir el paquete y leer con calma.
| Fase | Qué enseña | Error que representa |
|---|---|---|
| Identificar la APK | Elegir el enfoque según el objetivo | Tratar un binario cliente como caja negra |
| Descompilar con jadx | Ver el flujo aunque haya errores parciales | Confiar en que compilar u ofuscar oculta la lógica |
| Revisar MainActivity | Seguir una validación local | Decisiones sensibles en el cliente |
| Extraer el PIN | Un secreto embebido se recupera | Hardcoding de credenciales |
| SHA256 con el formato exacto | Respetar la transformación pedida | Ignorar los detalles de formato |
El patrón sigue apareciendo en software que se usa a diario. Cada vez que una app móvil, de escritorio o una SPA guarda secretos o validaciones que deberían vivir en el servidor, el problema es el mismo que aquí, solo con más capas alrededor.