20 September, 2021

Linux X86 Assembly - How To Test Custom Shellcode Using a C Payload Tester

Linux X86 Assembly - How To Test Custom Shellcode Using a C Payload Tester
Travis Phillips
Author: Travis Phillips
Share:
Overview


In the last blog post in this series, we created a tool to make it easy to build our custom payloads and extract them.  However, what if we want to test them before trying to use them?  It seems like a good idea to make sure it works before you include it in an exploit.  Testing it first would at least let you know that it works and reduce troubleshooting surface if the exploit fails.  Today we are going to focus on building a payload tester stub in the C programming language.  This will make it easy for us to copy and paste our C-style formatted payload from our build-and-extract tool.  Once it’s pasted in the tester stub, just compile and run it and you will be able to see your payload in action.  The code for payload tester stub and Makefile can be found in the /utils/ folder of the Secure Ideas Professionally Evil x86_asm GitHub repository.

A Quick Primer on C: Char Arrays

Our last blog demonstrated that the build-and-extract payload script could output our hello world payload as a C-code format by using the –style=c switch.  The resulting output would look like the following:

The build-and-extract payload python script's C-Style output format.

Let’s break down the output a bit.  If you are familiar with the C programming language, you can probably skip ahead.  The first 4 lines are simply a few comments that show the payload’s source file and size as shown below.

C-Styled comment header stating the size and source file of the payload.

The rest of the output is actually a single line of code, formatted across a few lines to just look cleaner.  This line declares and initializes a char array called payload.  A char (short for character) is a data type for a single byte.  A char array is an array of bytes.  The output from our build-and-extract script also initializes it using a string with “\x##” hex notation of our payload.  This line can be copied into a C program and it would effectively be a buffer with our payload data.

a C-styled char array buffer containing the payload code.

A Quick Primer on C: Pointers and Typing as a Function

Another topic that we will want to touch upon before heading into C code here is pointers and more over, function pointers.  In C functions are normally invoked by adding () or (parameter1=val, etc) after the functions name.  For example int res = strlen(someString); would be a valid line.  This is important when we get to function pointers.

A pointer is simply an address to a memory address.  This can be a pointer to any sort of data type.  A pointer is declared with a data type and an asterisk followed by the name.  For example, a pointer to a char buffer could be declared as char *myBuffer.  You could also have it point to an integer or a data struct or anything else.

Pointers are easy to pass around from function to function in C and allow your application direct memory read and write to the same area of memory across different functions.  However, where pointers get even more interesting, is they can be nearly anything, including a pointer to a function!  By setting the pointers declaration as a function pointer, you can use the pointer like a function.  This can allow you to have dynamic functions at runtime, which can be useful for plugins or applications that allow complex configurations, and even LD_PRELOAD hijacking.

In this example, we will want to use a function pointer to point to our payload buffer, so we can use the function pointer to invoke our payload.  Here are some examples of how to declare various function pointers with different definitions:

Function Declaration Pointer Declaration
size_t strlen(const char *s); size_t (*ptr)(const char *);
int my_func(int argc, char **argv) int (*ptr)(int, char **);
void my_func() void (*ptr)();

In our case, we just want to run our payload and are not expecting a return value, nor are we passing parameters to it.  So that last entry in the table above is a prime example of what we want.  Below shows how we would point this to our payload:

// Create a function pointer to the shellcode and
// display it to the user.
void (*payload_ptr)() =  (void(*)())&payload;

Linux Memory Mapping: Memory Page Permissions in the Age of DEP/NX

Memory is usually mapped in what are known as pages.  A page is just a chunk of memory that should be a fixed size, which is decided by the system.  On a Linux system, it is possible to determine what the size page is using getpagesize().  Pages also allow a region of memory to have read, write, and execute permissions set on it.  Permissions matter as they determine if code can be executed or not.  If the EIP register tries to point somewhere in a memory region that is NOT marked as executable, the program should segfault.  This is designed so if the EIP ended up in a section containing data rather than code it should just stop executing.

In the day of DEP/NX in Linux, at launch time a section can either be executable or writable, but not both.  In general, code is generally static and doesn’t get changed at runtime so you shouldn’t need to write to regions containing code.  However, there are times where code would need to modify itself at runtime, such as compressed or self-modifying code.  Since there is a need, there is a way!  You can modify the permissions by calling mprotect().  The mprotect() call allows you to set the RWX permissions on a memory page.  It is important to note that the pointer for this call must point to the start of a memory page, and the size should be the size of a memory page.

If you ever need to see the memory pages of a process, you can check under /proc/<PID>/maps to see the maps and their permissions.

A view of memory mapping permission of the cat program on a Linux system.

Overview of Our Stub

First let’s review our code, in its complete form, and then we shall break it down to discuss the various parts.  The code for payload tester stub and Makefile can be found in the /utils/ folder of the Secure Ideas Professionally Evil x86_asm GitHub repository.  Here is the complete stub code:

/**********************************************************************
*
* Program: x86_shellcode_tester.c
*
* Date: 08/06/2021
*
* Author: Travis Phillips
*
* Purpose: This code is used to provide a C template to paste shellcode
*          into and be able to run it live from within an ELF binary's
*          char buffer. This allows you to create a buffer with the
*          shellcode globally and this program will mark it as RWX using
*          mprotect() and then finally jump into.
*
* Compile: gcc -m32 x86_shellcode_tester.c -o x86_shellcode_tester
*
***********************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

/////////////////////////////////////////////////////
//  source file: hello_world_gas_solution_3.s
// payload size: 36
/////////////////////////////////////////////////////
char payload[] = "\xeb\x0f\x6a\x04\x58\x6a\x01\x5b\x59\x6a\x0e\x5a\xcd\x80\x93\xcd"
                 "\x80\xe8\xec\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72"
                 "\x6c\x64\x21\x0a";

int main() {
    // Print the banner.
    puts("\n\t\033[33;1m---===[ Shellcode Tester Stub v1.0 ]===---\033[0m\n");
    
    // Print the size of the shellcode.
    printf(" [\033[34;1m*\033[0m] Shellcode Size:  %d\n", sizeof(payload)-1);

    // Create a function pointer to the shellcode and
    // display it to the user.
    void (*payload_ptr)() =  (void(*)())&payload;
    printf(" [\033[34;1m*\033[0m] Shellcode Address: 0x%08x\n", payload_ptr);

    // Calculate the address to the start of the page for the
    // the shellcode.
    void *page_offset = (void *)((int)payload_ptr & ~(getpagesize()-1));
    printf(" [\033[34;1m*\033[0m] Shellcode page: 0x%08x\n", page_offset);

    // Use mprotect to mark that page as RWX.
    mprotect(page_offset, 4096, PROT_READ|PROT_WRITE|PROT_EXEC);

    // Finally, use our function pointer to jump into our payload.
    puts("\n\033[33;1m---------[ Begin Shellcode Execution ]---------\033[0m");
    payload_ptr();

    // We likely won't get here, but might as well include it just in case.
    puts("\033[33;1m---------[  End Shellcode Execution  ]---------\033[0m");
    return 0;
}

The first part of our code is a comments section to just provide some information about the file to humans followed by some standard header includes we need to use the functions we are using in C.  Next up in the code is the following snippet:

/////////////////////////////////////////////////////
//  source file: hello_world_gas_solution_3.s
// paylaod size: 36
/////////////////////////////////////////////////////
char payload[] = "\xeb\x0f\x6a\x04\x58\x6a\x01\x5b\x59\x6a\x0e\x5a\xcd\x80\x93\xcd"
                 "\x80\xe8\xec\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72"
                 "\x6c\x64\x21\x0a";

This block of code contains our payload.  We are doing this outside of main() so it is a global variable.  This code was copied and pasted exactly as the build and extract payload script output.  This payload stub tester code can be re-used for most payload tests by simply replacing this section with another payload.  Next we start the main() function:

int main() {
    // Print the banner.
    puts("\n\t\033[33;1m---===[ Shellcode Tester Stub v1.0 ]===---\033[0m\n");
    
    // Print the size of the shellcode.
    printf(" [\033[34;1m*\033[0m] Shellcode Size:  %d\n", sizeof(payload)-1);

    // Create a function pointer to the shellcode and
    // display it to the user.
    void (*payload_ptr)() =  (void(*)())&payload;
    printf(" [\033[34;1m*\033[0m] Shellcode Address: 0x%08x\n", payload_ptr);

    // Calculate the address to the start of the page for the
    // the shellcode.
    void *page_offset = (void *)((int)payload_ptr & ~(getpagesize()-1));
    printf(" [\033[34;1m*\033[0m] Shellcode page: 0x%08x\n", page_offset);

    // Use mprotect to mark that page as RWX.
    mprotect(page_offset, getpagesize(), PROT_READ|PROT_WRITE|PROT_EXEC);

    // Finally, use our function pointer to jump into our payload.
    puts("\n\033[33;1m---------[ Begin Shellcode Execution ]---------\033[0m");
    payload_ptr();

    // We likely won't get here, but might as well include it just in case.
    puts("\033[33;1m---------[  End Shellcode Execution  ]---------\033[0m");
    return 0;
}

This code is pretty well commented, and the first few blocks are pretty obvious on printing the banner and payload size.  However, the next block is where we create a function pointer that points to our global variable that contains the payload.

    // Create a function pointer to the shellcode and
    // display it to the user.
    void (*payload_ptr)() =  (void(*)())&payload;
    printf(" [\033[34;1m*\033[0m] Shellcode Address: 0x%08x\n", payload_ptr);

This creates the function pointer called payload_ptr.  This pointer can be invoked as a function, which means we can call our payload like a function now, but this will fail as this memory region is likely set without execution permissions.  We will also output the address where the payload is stored in memory to the user since this might help with debugging.  This address should remain fairly constant between runs unless your compiler built this ELF using PIE.

The next block of code is about finding the start of the memory page that contains our payload.

    // Calculate the address to the start of the page for the
    // the shellcode.
    void *page_offset = (void *)((int)payload_ptr & ~(getpagesize()-1));
    printf(" [\033[34;1m*\033[0m] Shellcode page: 0x%08x\n", page_offset);

Here we create a void pointer called page_offset which is set to the start of the memory page that contains the payload.  The way to calculate this is to:

  1. Get the address to our payload
  2. Get the page size and use bitwise NOT to invert it.
  3. Finally, use a bitwise AND operation on the payload address and inverted page size.

Finally, we will output the payload’s page start to the user, just to make it easier to debug should anything go wrong.  Finally, we reach the code block that addresses the payload’s memory page permissions:

    // Use mprotect to mark that page as RWX.
    mprotect(page_offset, 4096, PROT_READ|PROT_WRITE|PROT_EXEC);

This line uses mprotect() and will set the memory page’s permissions to be read, write, and executable (RWX).  The last three blocks simply prints that it’s starting the payload, uses our payload function pointer to invoke it, then prints when it returns from that payload.

    // Use mprotect to mark that page as RWX.
    mprotect(page_offset, getpagesize(), PROT_READ|PROT_WRITE|PROT_EXEC);

    // Finally, use our function pointer to jump into our payload.
    puts("\n\033[33;1m---------[ Begin Shellcode Execution ]---------\033[0m");
    payload_ptr();

    // We likely won't get here, but might as well include it just in case.
    puts("\033[33;1m---------[  End Shellcode Execution  ]---------\033[0m");
    return 0;

Compiling the Stub and Testing Our Payload 

Building the payload tester stub is simple.  There are two ways this can be done; by compiling by hand using the gcc command from the source files comment block, or using the Makefile that is provided in the Github repository.  The Makefile is also shown below:

all: x86_shellcode_tester


x86_shellcode_tester:
	@echo "\n\033[33;1mBuilding X86 Shellcode Tester\033[0m"
	gcc -m32 x86_shellcode_tester.c -o x86_shellcode_tester

clean:
	@echo "\n\033[33;1mRemoving executables and object files\033[0m"
	rm -f x86_shellcode_tester

For this example, I’m going to use the Makefile to build the tester, which allows us to simply run the make command.  After it is built, we should be able to run our tester and watch our payload execute as shown below.

Building the x86 shellcode tester program and running it to execute and test our hello world payload.

Conclusion

I hope you’ve enjoyed this blog post and learned something new today about testing your shellcode and Using C function pointers.  The code for this post will be added to the Secure Ideas Professionally Evil x86_asm GitHub repository.  In future posts, we will:

  • Other Syscalls and Uses.

Ready for a challenge?  We post Mystery Challenges on Facebook, Linkedin, and Twitter.  If you’re interested in security fundamentals, we have a Professionally Evil Fundamentals (PEF) channel that covers a variety of technology topics.  We also answer general basic questions in our Knowledge Center.  Finally, if you’re looking for a penetration test, professional training for your organization, or just have general security questions please Contact Us.

Linux X86 Assembly Series Blog Post

Interested in more information about the X86 architecture and Linux shellcode/assembly? This blog is a part of a series and the full list of blogs in this series can be found below:

    1. A Hacker's Tour of the X86 CPU Architecture
    2. Linux X86 Assembly – How to Build a Hello World Program in NASM
    3. Linux X86 Assembly – How to Build a Hello World Program in GAS
    4. Linux X86 Assembly – How to Make Our Hello World Usable as an Exploit Payload
    5. Linux X86 Assembly – How To Make Payload Extraction Easier
    6. Linux X86 Assembly – How To Test Custom Shellcode Using a C Payload Tester

Join the professionally evil newsletter

Related Resources