Linux X86 Assembly – How to test a custom shellcode using a C payload tester




In the last blog post in this series, we created a tool to make it easier to create our custom payloads and extract them. However, what if we want to test them out before trying to use them? It seems like a good idea to make sure it works before including it in an exploit. Testing it first would at least let you know it’s working and reduce the troubleshooting area if the exploit fails. Today we are going to focus on creating a payload tester stub in the C programming language. This will allow us to easily copy and paste our C style formatted payload from our authoring tool and extraction. Once it’s pasted into the tester stub, just compile and run it and you’ll be able to see your payload in action. The payload tester and Makefile stub code is located in the / utils / folder of the Secure Ideas Professionally Evil x86_asm GitHub repository.

A quick introduction to C: Char Arrays

Our last blog demonstrated that the build-and-extract payload script can generate our hello world payload in C-code format using the –Style = c switch. The resulting output would look like the following:

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

C-style comment header indicating the size and source file of the payload.

The rest of the output is actually a single line of code, formatted as a few lines to look cleaner. This line declares and initializes an array of characters called payload. A char (short for character) is a data type for a single byte. A character array is an array of bytes. The output of our build-and-extract script also initializes it using a string with the hexadecimal ” x ##” 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-style character array buffer containing the payload code.

A quick introduction to C: pointers and typing as a function

Another topic we’ll want to cover before we get to C code here is pointers and more, function pointers. In C, functions are normally called by appending () or (parameter1 = val, etc.) after the function name. For example int res = strlen (someString); would be a valid line. This is important when we come to function pointers.

A pointer is simply an address to a memory address. It can be a pointer to any type of data. A pointer is declared with a data type and an asterisk followed by the name. For example, a pointer to a character buffer could be declared as char * myBuffer. You can also point it to an integer or a data structure or whatever.

Pointers are easy to toggle between functions in C and allow your application to read and write directly to memory in the same memory area through different functions. However, where pointers get even more interesting is that they can be just about anything, including a pointer to a function! By defining the declaration of pointers as a function pointer, you can use the pointer as 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’ll want to use a function pointer to point to our payload buffer, so that we can use the function pointer to call our payload. Here are some examples of declaring different 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 **);
cancel my_func () empty (* ptr) ();

In our case, we just want to run our payload and don’t expect a return value or pass parameters to it. So this last entry in the table above is a great 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 DEP / NX era

Memory is usually mapped into something called pages. A page is just a piece of memory that must have a fixed size, which is decided by the system. On a Linux system, it is possible to determine the page size using getpagesize (). Pages also allow a region of memory to have read, write, and execute permissions set on them. Permissions are important because they determine whether code can be executed or not. If the EIP register tries to point somewhere in a region of memory that is NOT marked as executable, the program should make a segmentation error. This is designed so that if the EIP ends up in a section containing data rather than code, it should simply stop executing.

In the days of DEP / NX on Linux, at launch time, a section could be either executable or writable, but not both. In general, code is usually static and is not changed at runtime, so you shouldn’t need to write to regions that contain code. However, there are times when code needs to modify itself at run time, such as compressed or self-modifying code. Since there is a need, there is a way! You can change permissions by calling mprotect (). The mprotect () call allows you to set RWX permissions on a memory page. It is important to note that the pointer of this call must point to the beginning of a memory page and that the size must be the size of a memory page.

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

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

Overview of our heel

Let’s go through our code first, in its full form, and then we’ll break it down to discuss the different parts. The payload tester and Makefile stub code is located in the / utils / folder of the Secure Ideas Professionally Evil x86_asm GitHub repository. Here is the full 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

//  source file: hello_world_gas_solution_3.s
// payload size: 36
char payload[] = "xebx0fx6ax04x58x6ax01x5bx59x6ax0ex5axcdx80x93xcd"

int main() {
    // Print the banner.