← Todas las entradas

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.

FaseQué enseñaError que representa
Identificar la APKElegir el enfoque según el objetivoTratar un binario cliente como caja negra
Descompilar con jadxVer el flujo aunque haya errores parcialesConfiar en que compilar u ofuscar oculta la lógica
Revisar MainActivitySeguir una validación localDecisiones sensibles en el cliente
Extraer el PINUn secreto embebido se recuperaHardcoding de credenciales
SHA256 con el formato exactoRespetar la transformación pedidaIgnorar 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.

ciberseguridadlaboratorioCTF