Il codice che ci permette di eseguire una Shell in C è questo:
1 #include <stdio.h>
2
3 void main()
4 {
5 char *array[2];
6 array[0] = "/bin/sh";
7 array[1] = NULL;
8 execve(array[0], array, NULL);
9 }
Tale codice dovrebbe essere compilato in questo modo:
gcc -o shellcode -ggdb -static shellcode.c
in modo da poter utilizzare nella maniera migliore il GDB. L'opzione static è utilizzata per includere anche il codice dalla funzione execve che altrimenti sarebbe linkata dinamicamente. Ed ora diamoci sotto col GDB:
$ gdb -quiet shellcode 1 (gdb) disassemble main 2 Dump of assembler code for function main: 3 0x80481e0 <main>: push %ebp 4 0x80481e1 <main+1>: mov %esp,%ebp 5 0x80481e3 <main+3>: sub $0x8,%esp 6 0x80481e6 <main+6>: movl $0x808cec8,0xfffffff8(%ebp) 7 0x80481ed <main+13>: movl $0x0,0xfffffffc(%ebp) 8 0x80481f4 <main+20>: sub $0x4,%esp 9 0x80481f7 <main+23>: push $0x0 10 0x80481f9 <main+25>: lea 0xfffffff8(%ebp),%eax 11 0x80481fc <main+28>: push %eax 12 0x80481fd <main+29>: pushl 0xfffffff8(%ebp) 13 0x8048200 <main+32>: call 0x804cb40 <execve> 14 0x8048205 <main+37>: add $0x10,%esp 15 0x8048208 <main+40>: leave 16 0x8048209 <main+41>: ret 17 End of assembler dump.
Alla chiamata della funzione execve ed al salvataggio del nuovo Frame Pointer
la situazione dello stack sarà dunque quella rappresentata in Fig.
Disassembliamo dunque excve:
(gdb) disassemble execve Dump of assembler code for function execve: 1 0x804cb40 <execve>: push %ebp 2 0x804cb41 <execve+1>: mov $0x0,%eax 3 0x804cb46 <execve+6>: mov %esp,%ebp 4 0x804cb48 <execve+8>: test %eax,%eax 5 0x804cb4a <execve+10>: push %edi 6 0x804cb4b <execve+11>: push %ebx 7 0x804cb4c <execve+12>: mov 0x8(%ebp),%edi 8 0x804cb4f <execve+15>: je 0x804cb56 <execve+22> 9 0x804cb51 <execve+17>: call 0x0 10 0x804cb56 <execve+22>: mov 0xc(%ebp),%ecx 11 0x804cb59 <execve+25>: mov 0x10(%ebp),%edx 12 0x804cb5c <execve+28>: push %ebx 13 0x804cb5d <execve+29>: mov %edi,%ebx 14 0x804cb5f <execve+31>: mov $0xb,%eax 15 0x804cb64 <execve+36>: int $0x80 16 0x804cb66 <execve+38>: pop %ebx 17 0x804cb67 <execve+39>: mov %eax,%ebx 18 0x804cb69 <execve+41>: cmp $0xfffff000,%ebx 19 0x804cb6f <execve+47>: jbe 0x804cb7f <execve+63> 20 0x804cb71 <execve+49>: neg %ebx 21 0x804cb73 <execve+51>: call 0x8048498 <__errno_location> 22 0x804cb78 <execve+56>: mov %ebx,(%eax) 23 0x804cb7a <execve+58>: mov $0xffffffff,%ebx 24 0x804cb7f <execve+63>: mov %ebx,%eax 25 0x804cb81 <execve+65>: pop %ebx 26 0x804cb82 <execve+66>: pop %edi 27 0x804cb83 <execve+67>: pop %ebp 28 0x804cb84 <execve+68>: ret 29 0x804cb85 <execve+69>: lea 0x0(%esi),%esi End of assembler dump.
Del seguente codice ci interessa praticamente solo quello che avviene fino al passaggio al kernel mode tramite l'istruzione presente alla riga 15. Fondamentalmente viene solo effettuata la copia di alcuni valori e parametri in alcuni registri:
Poiché a noi interessano solo le istruzioni di copiatura non importa in quale ordine esse vengano effettuate31.2 possiamo elencare ora le istruzioni fondamentali per la chiamata excve