# PinBreaker: una app Android pensada para enseñar por qué ocultar secretos en cliente no funciona

7 min read
Table of Contents

Máquina creada por: Oscar
Plataforma: The Hackers Labs
Sistema operativo: Android
Dificultad: principiante


Sobre este CTF

Este laboratorio lo diseñé para enseñar una idea muy concreta: cuando un secreto crítico queda embebido en el cliente, la validación deja de ser un control real y pasa a ser simplemente una barrera superficial. En este caso, la aplicación pide un PIN y sugiere que, una vez encontrado, se calcule su SHA256 para obtener la flag. La parte importante no es “romper” la app, sino entender qué decisión de diseño la hace débil desde el principio.

La cadena aquí es deliberadamente corta y directa. No buscaba construir una APK llena de trucos, ofuscación agresiva o rutas artificiales, sino obligar a mirar el binario con criterio: descompilar, localizar el punto de entrada lógico y revisar cómo se valida la entrada del usuario. El aprendizaje que quería forzar es muy reutilizable fuera de un CTF: si una aplicación cliente contiene el valor esperado o la lógica completa de validación, cualquiera con acceso al paquete puede reconstruirla.

También quise que el laboratorio sirviera como recordatorio de algo básico pero todavía frecuente en entornos reales: no se deben confiar secretos ni decisiones de autorización al lado cliente. En un reto pequeño esto se ve en forma de PIN hardcodeado; en escenarios reales aparece como claves embebidas, validaciones offline mal planteadas, feature flags sensibles o lógica de negocio que debería residir en servidor.


Información técnica

CampoValor
NombrePinBreaker
IP objetivoNo aplica
ServiciosAplicación Android (.apk)
Cadena principalDescompilación de la APK → revisión de MainActivity → extracción del PIN hardcodeado → cálculo de SHA256
DificultadPrincipiante

Descubrimiento del artefacto

El punto de partida es simplemente la APK proporcionada en el laboratorio:

Terminal window
oscar@oscar-System-Product-Name:~/CTF$ ls -la
total 4915
drwxrwxr-x 2 oscar oscar 3 mar 1 16:42 .
drwxr-x--- 37 oscar oscar 57 mar 1 16:41 ..
-rw-rw-r-- 1 oscar oscar 5777184 mar 1 16:41 PinBreaker.apk

Aquí no hay superficie de red ni un servicio que enumerar. La fase de reconocimiento consiste en identificar correctamente el tipo de objetivo y elegir el enfoque adecuado. Ese detalle forma parte del diseño del reto: quería que el análisis arrancase desde ingeniería inversa básica, no desde automatismos de enumeración típicos de máquinas Linux o Windows.

Lo que representa esta fase en términos de aprendizaje es sencillo: antes de atacar nada, hay que entender qué se tiene delante. En este caso, el valor pedagógico está en reconocer que la fuente de verdad probablemente esté dentro del paquete y que, por tanto, revisar la APK es el camino natural.


Descompilación y revisión inicial

El siguiente paso fue descompilar la aplicación con jadx:

Terminal window
oscar@oscar-System-Product-Name:~/CTF$ jadx -d PinBreaker_jadx PinBreaker.apk
INFO - loading ...
INFO - processing ...
ERROR - finished with errors, count: 32

Aunque la salida muestra errores, en este caso no impiden el análisis útil del binario. jadx genera igualmente la estructura de trabajo:

Terminal window
oscar@oscar-System-Product-Name:~/CTF/PinBreaker_jadx$ ls
resources sources

Este detalle también me interesaba como parte del laboratorio. En escenarios reales, las herramientas de reversing no siempre producen una reconstrucción perfecta. Aun así, eso no significa que el análisis haya fracasado. Muchas veces basta con recuperar las clases relevantes y seguir el flujo principal. El reto está construido para que el alumno no se bloquee por mensajes ruidosos de herramienta y se centre en lo importante: si el código sensible se ha recuperado, el problema ya es visible.


Localización de la lógica principal

Una vez generada la estructura, el siguiente movimiento fue localizar la actividad principal:

Terminal window
oscar@oscar-System-Product-Name:~/CTF/PinBreaker_jadx$ find -name "MainActivity*"
./sources/com/pinbreaker/ctf/MainActivity.java

La ruta encontrada fue:

./sources/com/pinbreaker/ctf/MainActivity.java

Diseñé esta fase para que el recorrido fuese legible y útil para quien está empezando a analizar aplicaciones móviles. No hacía falta perseguir múltiples clases, servicios secundarios ni mecanismos de carga dinámica. La intención era que el alumno asociase rápidamente la interfaz con la lógica que procesa el PIN y entendiese cómo una comprobación aparentemente inocente puede revelar el secreto completo.

Este tipo de revisión es relevante fuera del CTF porque muchas implementaciones móviles siguen concentrando decisiones sensibles en clases muy predecibles: actividades principales, controladores de login, validadores locales o utilidades de configuración. La simplicidad de acceso a esa lógica es, precisamente, el problema.


Extracción del PIN embebido

Al revisar MainActivity.java, aparece la validación del PIN:

package com.pinbreaker.ctf;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
public final class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button unlockButton = (Button) findViewById(R.id.unlockButton);
final EditText pinInput = (EditText) findViewById(R.id.pinInput);
final TextView result = (TextView) findViewById(R.id.result);
unlockButton.setOnClickListener(new View.OnClickListener() {
@Override
public final void onClick(View view) {
MainActivity.onCreate$lambda$0(pinInput, this, result, view);
}
});
}
private static final void onCreate$lambda$0(EditText $pinInput, MainActivity this$0, TextView $result, View it) {
String pin = $pinInput.getText().toString();
if (this$0.checkPin(pin)) {
$result.setText("✅ PIN correcto. Calcula su hash SHA256 para obtener la flag.");
} else {
$result.setText("❌ PIN incorrecto");
}
}
private final boolean checkPin(String pin) {
return Intrinsics.areEqual(pin, "8524947156");
}
}

La clave está en esta línea:

return Intrinsics.areEqual(pin, "8524947156");

El PIN correcto es, por tanto:

8524947156

Aquí está el núcleo del laboratorio. Quería mostrar de la forma más limpia posible el error de diseño: la aplicación contiene el valor esperado y realiza la comparación localmente. No hay una comprobación remota, no hay separación entre interfaz y decisión de seguridad, y no hay nada que realmente proteja el secreto una vez el atacante dispone del APK.

Ese patrón es pequeño, pero representa un fallo muy real. En producción no siempre adopta la forma de un PIN. A veces es una API key incrustada, una contraseña de servicio, un token de prueba olvidado, una comprobación de licencia local o una validación de privilegios que debería vivir en backend. El problema de fondo es el mismo: confiar en que el cliente va a ocultar algo que, por definición, se entrega al usuario.


Obtención de la flag

Una vez recuperado el PIN, la propia aplicación y el enunciado indican que la flag se obtiene calculando su hash SHA256. El cálculo se hizo así:

Terminal window
oscar@oscar-System-Product-Name:~/CTF/PinBreaker_jadx$ echo "8524947156" | sha256sum
2a4be6606b9490b9955c7aac8e856c8e3098f9b15e98a8985ce5c192049c96ef

Con las notas disponibles, la flag resultante es:

2a4be6606b9490b9955c7aac8e856c8e3098f9b15e98a8985ce5c192049c96ef

Hay, no obstante, un matiz técnico importante. El comando mostrado usa echo sin -n, así que introduce un salto de línea al final antes de pasar la cadena a sha256sum. Eso significa que el hash calculado corresponde a 8524947156\n, no necesariamente al texto desnudo 8524947156. No lo corrijo porque no hay que inventar ni alterar el comportamiento reflejado en las notas, pero sí conviene dejar constancia de ello: en un laboratorio así, ese tipo de detalle puede ser intencionado o simplemente una consecuencia operativa del comando usado.

También hay aprendizaje aquí. Incluso cuando el secreto ya se ha recuperado, sigue siendo necesario respetar exactamente la transformación pedida. En seguridad ofensiva y en análisis de aplicaciones, los detalles de formato importan.


Qué quería enseñar al diseñar PinBreaker

La idea de fondo en este CTF era enseñar una cadena mínima pero muy clara: entregar la lógica de validación y el secreto al cliente equivale a renunciar a protegerlos. No hace falta explotar memoria, interceptar tráfico ni abusar de componentes complejos. Basta con inspeccionar el paquete y leer con atención.

Más que un walkthrough, este laboratorio está pensado como una demostración de criterio: revisar dónde vive la validación, preguntarse quién controla realmente el secreto y entender que la ofuscación ligera o la simple compilación no son medidas de protección.

FaseQué enseñaError representado
Identificación de la APKElegir el enfoque según el tipo de objetivoTratar un binario cliente como si fuera una caja negra
Descompilación con jadxObtener visibilidad suficiente aunque haya errores parcialesConfiar en que compilar u ofuscar mínimamente oculta la lógica
Revisión de MainActivitySeguir el flujo principal de una validación localColocar decisiones sensibles en el lado cliente
Extracción del PINRecuperar secretos embebidos desde código descompiladoHardcoding de credenciales o valores de acceso
Cálculo del SHA256Respetar exactamente la transformación pedidaIgnorar detalles de formato al derivar artefactos de seguridad

La cadena tiene sentido fuera del entorno CTF porque el patrón sigue apareciendo en software real. Cada vez que una app móvil, de escritorio o incluso una SPA contiene secretos o validaciones críticas que deberían residir en servidor, el problema es esencialmente el mismo que aquí, solo con más capas alrededor.


Recursos y referencias

  • jadx, para descompilación y revisión estática de aplicaciones Android.
  • sha256sum, para derivar la flag a partir del PIN recuperado.
  • Buenas prácticas de seguridad en aplicaciones móviles: no almacenar secretos sensibles ni lógica de autorización en cliente.
My avatar

¿Te ha resultado útil o interesante este post? Soy Oscar, Senior Platform Engineer y SRE, y en este blog comparto mis reflexiones, experimentos y retos técnicos sobre automatización, seguridad (especialmente el diseño de CTFs), optimización de rendimiento y el impacto de la tecnología en el desarrollo profesional.

Conecta y conversemos: LinkedIn Mi código y proyectos en: GitHub


More Posts