Testing Shellcode

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.

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.

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$

Tip

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.