Find The Easy Pass
A Windows crackme binary that gates a flag behind a hardcoded password check. Static reversing in Ghidra buries the validation logic in decompiler noise, so the fix is to pivot to dynamic analysis: Immunity Debugger's string search points at the check routine, and a single breakpoint right before the comparison exposes the expected password directly in memory.
Challenge overview
| Field | Value |
|---|---|
| Platform | Hack The Box · Challenge |
| Category | Reversing |
| Difficulty | Easy |
| Status | Retired |
| Files delivered | challenge.zip → a single Windows PE executable |
Attack map
[challenge.zip]
│
▼
[.exe] Windows PE binary, password-gated
│
▼
[Ghidra] static decompilation
│ too many auto-generated functions/refs
▼ to pinpoint the check quickly
[Immunity Debugger] search referenced strings
│ → locate the validation routine
▼
[Breakpoint F2] halt right before the comparison
│ → expected password sits in memory
▼
["fortran!"] password recovered, re-run & confirm
│
▼
[FLAG] HTB{fortran!}Un binario Windows tipo crackme que oculta una flag tras una contraseña fija comprobada en el código. El reversing estático en Ghidra entierra la lógica de validación en ruido de descompilación, así que la solución es cambiar a análisis dinámico: la búsqueda de strings de Immunity Debugger señala la rutina de comprobación, y un único breakpoint justo antes de la comparación deja la contraseña esperada visible directamente en memoria.
Ficha del challenge
| Campo | Valor |
|---|---|
| Plataforma | Hack The Box · Challenge |
| Categoría | Reversing |
| Dificultad | Easy |
| Estado | Retirado |
| Archivos entregados | challenge.zip → un único ejecutable Windows PE |
Mapa del ataque
[challenge.zip]
│
▼
[.exe] binario PE Windows, protegido por contraseña
│
▼
[Ghidra] descompilación estática
│ demasiadas funciones/referencias autogeneradas
▼ para localizar rápido el check
[Immunity Debugger] búsqueda de strings referenciadas
│ → localiza la rutina de validación
▼
[Breakpoint F2] detiene justo antes de la comparación
│ → la contraseña esperada queda en memoria
▼
["fortran!"] contraseña recuperada, se reejecuta y confirma
│
▼
[FLAG] HTB{fortran!}
1. Objective and delivered files
Before touching a debugger it's worth being precise about what "done" looks like: find the plaintext password the binary expects, then wrap it in the flag format HTB expects for this category.
The challenge ships a single challenge.zip. No source, no hints beyond the category (Reversing) and the goal: recover the password used by the program and submit it as:
HTB{PASS}2. Extraction and initial triage
Unzip first, then look at what you actually got before picking a tool. The archive contains one file: a Windows executable, which immediately narrows the toolset to Windows-native or Windows-aware reversing tools (Ghidra, IDA, x32dbg, Immunity Debugger, etc.).
unzip challenge.zipThe result is a plain Windows binary with no obvious packing or protection mentioned — a good candidate to start with static analysis, since reading the logic before ever executing it avoids triggering anything unexpected and gives full visibility over the program's control flow up front.
3. Static reversing with Ghidra
Static analysis is the safer first move: load the binary, let the decompiler build a picture of the functions and their relationships, and look for the one that reads user input and compares it against something.
Loading the executable into Ghidra and running auto-analysis produces a full function list and decompiled pseudo-C for each of them. In practice, though, this binary surfaces a wall of functions and cross-references: a good chunk of what a decompiler lists for an unstripped Windows PE is compiler and runtime scaffolding (CRT startup, initialization thunks, imported-function wrappers) rather than the actual program logic, and picking the real password check out of that list by eye is slow going. Static reversing here is technically possible, just uncomfortable — there's no fast path to the comparison without either recognizing the check's signature at a glance or reading through most of the noise first.
4. Switching to dynamic analysis with Immunity Debugger
Instead of reconstructing the check by reading code, let the running program show it: attach a debugger, and instead of tracing execution instruction by instruction, ask it directly for the strings the binary references. Whatever text the program shows the user (prompts, success/failure messages) is a strong signal for where the validation logic lives.
With the binary loaded in Immunity Debugger, a string search over all referenced strings surfaces the literal text the program uses, which leads to the routine tied to password validation:
Immunity Debugger ▸ Search for ▸ All referenced strings5. Breakpoint and password recovery
This is the core trick of the challenge: a breakpoint set right on the validation routine, before the comparison actually runs, catches the program with the expected password already sitting in memory — no need to defeat or even understand any encoding, since at the moment of comparison the value is already plaintext.
A breakpoint is placed on the relevant instruction with F2, and the program is then run and fed any input to reach it:
F2 → breakpoint on the password-check routine
F9 → runWhen execution halts at the breakpoint, the password the program expects is directly visible in memory, in plaintext, right before it gets compared against whatever the user typed:
fortran!6. Validation
Recovering a value in a debugger is not the same as confirming it's correct — the binary still has to accept it once the breakpoint is out of the way.
The program is restarted, this time without the breakpoint, and the recovered string is entered as the password. It is accepted.
7. Flag
HTB{fortran!}Technique summary
| Phase | Component | Technique / Weakness | Impact |
|---|---|---|---|
| Static | Windows PE (Ghidra decompilation) | Unstripped binary buries the check under compiler-generated noise | Slows the static path down but doesn't stop it |
| Dynamic | Password-check routine (Immunity Debugger) | String search pinpoints the validation code path | Turns a full-binary review into a single targeted breakpoint |
| Dynamic | Runtime memory | Plaintext password held in memory at comparison time, no anti-debug | Full credential disclosure from a single breakpoint before the check executes |
Lessons learned
- When a decompiler buries the logic you need under auto-generated noise, don't force the static path — pivot to a debugger and let the running program hand you the value instead of reconstructing every code path by hand.
- Searching referenced strings is usually the fastest way into a binary's relevant code path: whatever it shows the user tends to sit right next to the logic that decides what gets shown.
- A breakpoint placed immediately before a comparison exposes the exact value being checked — this only works because the target does no anti-debugging and applies no obfuscation that survives to the comparison instant.
- From a build/defense perspective, this is the textbook anti-pattern: never hold a secret in plaintext in memory for a straight comparison against user input. Any debugger attach defeats it trivially — the same weakness shows up in real-world hardcoded credentials and unprotected license checks.
- Static and dynamic reversing are complements, not alternatives: use whichever gets you to the answer with the least friction for the binary in front of you.
1. Objetivo y archivos entregados
Antes de tocar un debugger conviene tener claro qué significa "terminado": encontrar la contraseña en texto plano que espera el binario y envolverla en el formato de flag que pide HTB para esta categoría.
El challenge entrega un único challenge.zip. Sin código fuente ni más pistas que la categoría (Reversing) y el objetivo: recuperar la contraseña que usa el programa y enviarla como:
HTB{PASS}2. Extracción y triage inicial
Primero descomprimir, luego mirar qué se ha obtenido de verdad antes de elegir herramienta. El archivo contiene un solo fichero: un ejecutable Windows, lo que acota de inmediato el conjunto de herramientas a las nativas o compatibles con Windows (Ghidra, IDA, x32dbg, Immunity Debugger, etc.).
unzip challenge.zipEl resultado es un binario Windows sencillo, sin empaquetado ni protección evidente mencionados — un buen candidato para empezar por análisis estático, ya que leer la lógica antes de ejecutarla evita disparar nada inesperado y da visibilidad completa del flujo del programa desde el principio.
3. Reversing estático con Ghidra
El análisis estático es el movimiento inicial más seguro: cargar el binario, dejar que el descompilador construya un mapa de las funciones y sus relaciones, y buscar la que lee la entrada del usuario y la compara con algo.
Cargar el ejecutable en Ghidra y lanzar el auto-análisis produce un listado completo de funciones y su pseudo-C descompilado. En la práctica, este binario expone una pared de funciones y referencias cruzadas: buena parte de lo que un descompilador lista para un PE Windows sin stripping es andamiaje del compilador y del runtime (arranque del CRT, thunks de inicialización, wrappers de funciones importadas) y no lógica real del programa, y separar a ojo el check de contraseña real de ese listado es lento. El reversing estático aquí es técnicamente posible, solo que incómodo — no hay atajo hasta la comparación sin reconocer la firma del check de un vistazo o sin leer antes buena parte del ruido.
4. Cambio a análisis dinámico con Immunity Debugger
En vez de reconstruir el check leyendo código, dejar que el programa en ejecución lo enseñe: adjuntar un debugger y, en lugar de trazar la ejecución instrucción a instrucción, pedirle directamente las strings que referencia el binario. Cualquier texto que el programa le muestre al usuario (prompts, mensajes de éxito/fallo) es una señal fuerte de dónde vive la lógica de validación.
Con el binario cargado en Immunity Debugger, una búsqueda sobre todas las strings referenciadas saca a la luz el texto literal que usa el programa, lo que lleva hasta la rutina ligada a la validación de la contraseña:
Immunity Debugger ▸ Search for ▸ All referenced strings5. Breakpoint y recuperación de la contraseña
Este es el truco central del challenge: un breakpoint colocado justo en la rutina de validación, antes de que la comparación se ejecute realmente, atrapa al programa con la contraseña esperada ya en memoria — sin necesidad de romper ni siquiera entender ninguna codificación, porque en el momento de la comparación el valor ya está en texto plano.
Se coloca un breakpoint en la instrucción relevante con F2, y luego se ejecuta el programa introduciendo cualquier entrada para llegar hasta él:
F2 → breakpoint en la rutina de comprobación de la contraseña
F9 → ejecutarCuando la ejecución se detiene en el breakpoint, la contraseña que espera el programa queda visible directamente en memoria, en texto plano, justo antes de compararse con lo que haya escrito el usuario:
fortran!6. Validación
Recuperar un valor en un debugger no es lo mismo que confirmar que es correcto — el binario todavía tiene que aceptarlo una vez retirado el breakpoint.
Se reinicia el programa, esta vez sin el breakpoint, y se introduce la cadena recuperada como contraseña. Es aceptada.
7. Flag
HTB{fortran!}Resumen de técnicas
| Fase | Componente | Técnica / Debilidad | Impacto |
|---|---|---|---|
| Estático | PE Windows (descompilación Ghidra) | Binario sin stripping entierra el check bajo ruido generado por el compilador | Ralentiza la vía estática pero no la bloquea |
| Dinámico | Rutina de comprobación de contraseña (Immunity Debugger) | La búsqueda de strings localiza el camino de código de la validación | Convierte una revisión completa del binario en un único breakpoint dirigido |
| Dinámico | Memoria en tiempo de ejecución | Contraseña en texto plano en memoria en el momento de comparar, sin anti-debug | Divulgación completa de la credencial con un solo breakpoint antes de que se ejecute el check |
Lecciones aprendidas
- Cuando un descompilador entierra la lógica que necesitas bajo ruido autogenerado, no fuerces la vía estática — cambia a un debugger y deja que el programa en ejecución te entregue el valor en vez de reconstruir a mano cada camino de código.
- Buscar las strings referenciadas suele ser la vía más rápida hasta el camino de código relevante de un binario: lo que le muestra al usuario tiende a estar justo al lado de la lógica que decide qué se muestra.
- Un breakpoint colocado justo antes de una comparación expone el valor exacto que se está comprobando — esto solo funciona porque el objetivo no aplica anti-debugging ni ninguna ofuscación que sobreviva hasta el instante de la comparación.
- Desde la perspectiva de desarrollo/defensa, este es el antipatrón de libro: nunca guardar un secreto en texto plano en memoria para una comparación directa contra la entrada del usuario. Cualquier adjunción de debugger lo derrota trivialmente — la misma debilidad aparece en credenciales hardcodeadas reales y en checks de licencia sin proteger.
- Reversing estático y dinámico son complementarios, no alternativas: usa el que te lleve a la respuesta con menos fricción para el binario que tienes delante.