AVILX

REVERSING WRITEUP · CRACKMES.ONE · 2026

var

Binario estático y stripped — password fragmentado en tres movabs

Autor retounchapelierfou
Plataformacrackmes.one
Dificultad1.0 / 5.0
Arquitecturax86-64 — Unix/Linux
ModoNormal Mode — Análisis estático
AnalistaJesús Ávila (Avilx)
Fecha09 de Septiembre 2026
Portafolioavilx.dev
PASSWORD ENCONTRADO REVERSING · ASSEMBLY · CRACKME · x86-64

Documentación de aprendizaje en reversing | Entorno controlado

Información del Binario

Campo Valor
Nombrevar
TipoELF 64-bit LSB executable
Arquitecturax86-64
Enlazadostatically linked
Stripped
BuildID8115a71bd29763978c08d5194969c05d46c6e2b8
Stack CanaryNo Canary Found
NXEnabled
PIEPIE Disabled
RELROPartial RELRO
Nota
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
Hallazgo
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 :
Hallazgo
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.
Hipótesis
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..."
Hallazgo
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
Hipótesis
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'
Hallazgo
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

Flag_Over_Here_Dude
PASSWORD ENCONTRADO
$ ./var
Flag : Flag_Over_Here_Dude
Congratz ... easy wasn't it ?
Hallazgo
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 disassembly15 minutos
Intento de buffer overflow10 minutos
Decodificación movabs10 minutos
Verificación5 minutos
Total45 minutos