AVILX
REVERSING WRITEUP · CRACKMES.ONE · 2026
var
Binario estático y stripped — password fragmentado en tres movabs
| Autor reto | unchapelierfou |
| Plataforma | crackmes.one |
| Dificultad | 1.0 / 5.0 |
| Arquitectura | x86-64 — Unix/Linux |
| Modo | Normal Mode — Análisis estático |
| Analista | Jesús Ávila (Avilx) |
| Fecha | 09 de Septiembre 2026 |
| Portafolio | avilx.dev |
Documentación de aprendizaje en reversing | Entorno controlado
Información del Binario
| Campo | Valor |
|---|---|
| Nombre | var |
| Tipo | ELF 64-bit LSB executable |
| Arquitectura | x86-64 |
| Enlazado | statically linked |
| Stripped | sí |
| BuildID | 8115a71bd29763978c08d5194969c05d46c6e2b8 |
| Stack Canary | No Canary Found |
| NX | Enabled |
| PIE | PIE Disabled |
| RELRO | Partial RELRO |
Este binario presenta tres características inusuales respecto a los anteriores. Primero, está statically linked: toda la libc está copiada dentro del ejecutable, lo que genera un output masivo en
strings y hace ineficaz a ltrace — no hay PLT para interceptar. Segundo, está stripped: no hay símbolos de funciones, lo que impide usar grep sobre nombres de funciones en el disassembly. Tercero, PIE está deshabilitado: las direcciones son fijas en tiempo de ejecución, lo que simplifica el análisis del disassembly porque los offsets del objdump coinciden con las direcciones reales.Reconocimiento
File y checksec
$ file ./var
var: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux),
statically linked, for GNU/Linux 2.6.24,
BuildID[sha1]=8115a71bd29763978c08d5194969c05d46c6e2b8, stripped
$ checksec --file=./var
Partial RELRO | No Canary Found | NX enabled | PIE Disabled | No Symbols
El binario es estático y stripped.
ltrace no funciona en binarios estáticos porque no existe la PLT — la tabla que usa el dynamic linker para resolver funciones externas. Sin PLT no hay nada que interceptar. La ausencia de canary y PIE sugiere que el stack es vulnerable a overflow, aunque el nombre del crackme resultó ser un troll.Strings con offsets
El output de strings fue masivo por el linkado estático. El filtro estándar no fue suficiente, así que se usó strings -t x para obtener offsets y buscar strings relevantes:
$ strings -a -t x ./var | grep "Flag"
108e Flag_OveH
93984 Flag :
Se identificaron dos strings relevantes.
0x93984 es el prompt “Flag : “ que imprime el programa. 0x108e muestra Flag_OveH — un fragmento que sugiere Flag_Overflow, nombre de una variable interna. La H al final es un artefacto de alineación en x86-64, no parte del string.El nombre
Flag_Overflow y la ausencia de canary sugieren un buffer overflow como mecanismo. Se intentará desbordar el buffer de input para sobreescribir la variable de comparación.Ejecución inicial
$ ./var
Flag : test
No dude =/
Análisis del Disassembly
Localización de main
Al estar stripped, no es posible hacer grep <main>. Se usó el offset del prompt para localizar la función principal:
$ objdump -d -M intel ./var | grep -B 20 -A 50 "93984"
Layout del stack
El disassembly reveló el siguiente layout:
401072: sub rsp,0x50 ; reserva 80 bytes
401086: mov [rbp-0x8],rax ; canary (fs:0x28)
40108c: movabs rax,0x65764f5f67616c46
401096: mov [rbp-0x40],rax ; fragmento 1 del password
40109a: movabs rax,0x445f657265485f72
4010a4: mov [rbp-0x38],rax ; fragmento 2 del password
4010a8: mov [rbp-0x30],0x656475 ; fragmento 3 del password
4010af: mov edi,0x493984 ; "Flag : "
4010b9: call printf
4010be: lea rax,[rbp-0x20] ; buffer de input
4010cf: call scanf
4010d4: lea rdx,[rbp-0x20] ; input -> rdx
4010d8: lea rax,[rbp-0x40] ; password -> rax
4010e2: call strcmp
4010e7: test eax,eax
4010e9: jne 0x4010f7 ; si distintos -> "No dude =/"
4010eb: mov edi,0x49398f ; "Congratz..."
El password se construye en el stack a partir de tres valores inmediatos cargados con
movabs. El input del usuario se lee en [rbp-0x20] y se compara con el password en [rbp-0x40] mediante strcmp. La diferencia de offsets es 0x40 - 0x20 = 32 bytes.Intento de buffer overflow
Al ver Flag_Overflow, sin canary y con el buffer a 32 bytes del password, se intentó un overflow para sobreescribir [rbp-0x40]:
python3 -c "import sys; sys.stdout.write('A'*32 +
'Flag_Over_Here_D')" | ./var
*** stack smashing detected ***: ./var terminated
El overflow activó protección de stack. El buffer crece hacia direcciones altas (
rbp-0x00), mientras el password está en dirección baja (rbp-0x40) — un overflow normal no llega ahí. El nombre Flag_Overflow era un troll del autor.Decodificación little-endian
El password real está en los tres movabs. Se decodificó cada uno en little-endian:
# Fragmento 1
>>> import struct
>>> struct.pack('Q', 0x65764f5f67616c46)[::-1].decode()
'Flag_Ove'
# Fragmento 2
>>> struct.pack('Q', 0x445f657265485f72)[::-1].decode()
'r_Here_D'
# Fragmento 3
>>> struct.pack('I', 0x656475)[::-1].decode()
'ude'
Los tres fragmentos forman el password completo:
Flag_Ove + r_Here_D + ude = Flag_Over_Here_Dude. Las H y D que aparecían como artefactos en strings son en realidad bytes del string completo que quedaban cortados por la alineación de 8 bytes de cada QWORD.Solución
Password encontrado
PASSWORD ENCONTRADO
$ ./var
Flag : Flag_Over_Here_Dude
Congratz ... easy wasn't it ?
El password
Flag_Over_Here_Dude fue reconstruido decodificando tres valores movabs en little-endian y concatenando los fragmentos en orden de offset. El binario respondió: “Congratz …easy wasn’t it ?”Reflexión Final
Qué salió bien
Identificar rápidamente que ltrace no funciona en binarios estáticos fue clave para no perder tiempo. La técnica de strings -t x con offsets permitió localizar strings relevantes en un output de miles de líneas.
La decodificación little-endian de movabs ya estaba dominada de challenges anteriores — aplicarla a tres fragmentos consecutivos fue directo una vez localizado el disassembly correcto.
Qué salió mal
El nombre Flag_Overflow confundió el análisis inicial. Se dedicó tiempo a intentar un buffer overflow que no era el vector — el overflow falla porque el buffer crece hacia direcciones altas y el password está en una dirección más baja. Además, hay protección de stack activa a pesar de que checksec reportó No Canary Found.
También se intentó el password incompleto (Flag_Over_Here_D) antes de decodificar el tercer movabs. El string en memoria no termina en D — esa letra es el primer byte del fragmento siguiente (Dude).
Lecciones aprendidas
En un binario statically linked y stripped, ltrace y el grep por nombre de función no funcionan. La estrategia correcta es localizar el disassembly por referencia a strings conocidos usando sus offsets.
Los movabs con valores de 8 bytes en little-endian son un patrón común para inicializar variables en el stack. Cada QWORD carga exactamente 8 caracteres. Si el string tiene más de 8 bytes se parte en múltiples movabs consecutivos en offsets contiguos — hay que decodificarlos todos y concatenarlos en orden.
El nombre de una variable interna visible en strings no siempre describe el mecanismo real — puede ser un nombre de variable semánticamente relacionado con el contenido, no con la técnica de solución.
Tiempo total
| Fase | Tiempo |
|---|---|
| Reconocimiento (file, checksec, strings) | 5 minutos |
| Análisis del disassembly | 15 minutos |
| Intento de buffer overflow | 10 minutos |
| Decodificación movabs | 10 minutos |
| Verificación | 5 minutos |
| Total | 45 minutos |