Cybersecurity blog header

OWASP FSTM, Stage 9: Exploitation of executables

The double free executable exploitation technique focuses on the implementation of the malloc and free functions

Exploitation of executables is the final step in a firmware analysis, in which an exploit is developed for a vulnerability discovered in earlier phases

Vulnerability exploitation of executables techniques vary greatly depending on the type of vulnerability and the component they affect, although the most serious and damaging ones usually come from executables without the relevant security measures or that make use of vulnerable functions.

If the previous analysis phases have been successful, at this point a vulnerability or hints of a vulnerability will have been discovered in one of the firmware components of an OT or IoT device. Sometimes that vulnerability is found in an executable, for which a proof-of-concept (PoC) or exploit can be written.

PoCs and exploits can vary depending on the type of vulnerability. However, if the vulnerability is in the programming of an executable, a buffer overflow vulnerability can lead to remote code execution, for which there are several known tools and techniques.

This article presents techniques and tools for exploiting these executables as part of a comprehensive IoT security testing, following OWASP FSTM methodology.

1 Techniques for exploitation of executables

Usually, the most serious and difficult to detect vulnerabilities in a firmware come from bugs in the executables it contains. They are difficult to detect, since they usually require a detailed inspection of the source code or the compiled executable and, a priori, given no indication of their existence. However, some can expose serious vulnerabilities and their successful exploitation is definitive in a firmware analysis.

1.1 Buffer overflow

The most common forms of vulnerabilities in executables are due to memory corruption. This category includes buffer overflows, which can lead to a remote code execution vulnerability.

A buffer is a region of memory in which a value is temporarily stored. In C, these buffers are implemented as arrays with a fixed capacity. If the code does not check the length of a value in the input, data is written beyond the buffer limits: the buffer overflows.

Buffer overflow is one of the most common executable exploitation techniquesDue to the organization of the automatic variables in memory, which are created in the stack, with respect to the return address of the function containing it, overflowing data can reach the return address and overwrite it.

With this buffer overflow technique you can write an arbitrary return address and control the execution flow

With this exploitation of executables technique, it is possible to write an arbitrary return address and control the execution flow.

In addition, this same bug allows the attacker to inject a shellcode into memory and write the address of this shellcode to the return address, thus executing arbitrary code. The use of NX (no execute) prevents the execution of shellcodes on the stack.

1.2 Format string attack

This type of bug is produced by an incorrect use of the printf function in C. When the format string of this function, which is the first argument, indicates more values than the number of following arguments, it can start reading values from the stack. A priori, this problem does not seem exploitable, but when the user has control over the format string as in the following code it allows operations not contemplated by the programmer:

#include <stdio.h>

int main(void) {
char buffer[30];

gets(buffer);

printf(buffer);
return 0;
}

In this case, an attacker can enter a format string in the input and printf interprets it as such, so that the contents of the stack can be read:

$ ./test

%x %x %x %x %x %x
f7f74080 0 5657b1c0 782573fc 20782520 25207825

In fact, you can continue reading until you get the data contained in the buffer, which the attacker himself has entered. This opens the possibility of interpreting part of the same input as a memory address with the format modifier %s, allowing to read a random memory location.

In addition, the format modifier %n writes to the provided memory address the number of characters written to stdout, so that, combined with the previous technique, integer values can be written to arbitrary memory locations.

1.3 Heap overflow

The heap overflow technique is very similar to the buffer overflow or stack overflow technique but in the dynamic memory space instead of in the stack.

When a programmer allocates dynamic memory, it is allocated linearly. If, for reasons of program construction, we manage to exceed the limits of this buffered memory, we could overwrite values in neighboring memory.

In this case the attacker must make a more detailed analysis of what can be found in the adjacent memory because the dynamic memory is not as structured as the stack. If in adjacent memory we find a pointer in which the programmer trusts, we could overwrite its value. This can have very different examples, from making the program copy data in unwanted sections to even taking control of the execution.

1.4 Use After Free

This vulnerability is due to the use of dynamic memory pointers after having freed their contents. Due to the way glibc works when allocating dynamic memory, with the right conditions, a newly freed memory space can be reused and allocated in the next call to malloc.

This generates situations where two apparently distinct pointers have access to the same memory address and, if the attacker can reach one of them and modify its contents, it can unexpectedly affect the other.

For example, considering the following code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
char* buffer0;
char* buffer1;

buffer0 = (char *) malloc(11);
/* tareas */
free(buffer0);

buffer1 = (char *) malloc(11);
fgets(buffer1, 11, stdin);

if (strncmp(buffer0, “go forward”, 10) == 0) {
printf(“flag\n”);
}
free(buffer1);
return 0;
}

Apparently, buffer0 and buffer1 point to different memory regions and should not interfere with each other. However, the attacker can write to the address of buffer1, which, because malloc allocates the newly freed memory space, points to the same memory address as buffer0, so he can assign value to it.

1.5 Double Free

This exploitation of executables technique focuses on the implementation of the malloc and free functions. If you somehow manage to force the same block of memory to be freed twice, this will cause malloc to return the same block of memory twice for two different allocations.

To exemplify this behavior, the following piece of pseudo-code is attached:

char * a = malloc(10); // 0x55971353c210
char *b = malloc(10); // 0x55971353c230
char *c = malloc(10); // 0x55971353c250

free(a); // Free a
free(b); // Free b to avoid double free or corruption (fasttop)
free(a); // Free a again

char * d = malloc(10); // 0x55971353c210
char * e = malloc(10); // 0x55971353c230
char * f = malloc(10); // 0x55971353c210 – Same address as d!

Beyond the ability to write to f and d simultaneously there is an important and exploitable effect of this implementation. In the above example, the same section of memory exists in a hosted and free manner at the same time for a period. If in this period it is possible to write to the hosted memory structure, control over the memory address where the next malloc will be made has been obtained.

char * a = malloc(0x20); // 0x55971353c210
free(a);
free(a); // Double free!
char * b = malloc(0x20); // 0x55971353c210 – Same address as a!
// 0x55971353c210 exists as occupied in b
// but as free in a
strcpy(b, “\x78\x56\x34\x12”); // Write in b an arbitrary address
malloc(0x20); // Ignore this memory allocation
char * c = malloc(0x20); // 0x12345678 – Points to arbitrary address!

1.6. Other exploitation techniques

In addition to those mentioned above, there are many other advanced exploitation of executables techniques that can be useful. This article is not intended to be an exhaustive guide to each of them, but some of the best known are listed below:

  • Null Byte Poisoning.
  • Forging Chunks.
  • Shrinking Free Chunks.
  • House of Spirit.
  • House of Lore.
  • House of Force.
  • House of Einherjar.
  • Unlink exploit.

2 Binary protection and hardening

When an indication of vulnerability has been found in an executable, before proceeding to try to exploit this vulnerability, a study must be made of what prevention measures are deployed in the environment in which it is executed.

There are protections against various exploitation of executables techniques found in the executable itself or in the way it has been compiled. On the other hand, the kernel can also enable some anti-exploit measures. Some of the most important techniques and protection measures are explained below.

2.1 PIE (Position Independent Executables)

This compilation option allows the generation of code that can be executed independently of the absolute memory address where it has been loaded. This is achieved by relative memory addressing and avoiding the use of absolute references.

This technique allows the binary to be loaded into different memory addresses each time it is executed, making it difficult to use ROP (Return Oriented Programming) techniques which will be explained below.

Since the code must have the ability to jump between addresses, when PIE is used, it is mandatory to use relative addresses. This same system can be used to develop exploits, bypassing protection.

2.2 RELRO (Relocation Read-Only)

Although it exists in two forms (partial and total), it boils down to an increase in the protection of the GOT section of an executable. This section of the executable is responsible for storing the addresses of functions external to the program and present in libraries.

The purpose of this protection is to prevent overwriting of the addresses where the libc functions are located.

Partial protection can be bypassed when in an exploit we can write to arbitrary memory locations (as in the case of a format string attack).

2.3 NX (NoeXecute)

This technique ensures that the stack is an area that does not allow code execution and is dedicated only to its original function of storing data.

Both the binary must enable it and the processor must support it for this protection to be activated. It prevents an attacker from inserting his own code into the stack zone for later execution.

To bypass this protection, the ROP (Return Oriented Programming) technique has been developed where pieces of existing code in the program are used to achieve the desired goal instead of writing traditional shellcodes.

2.4 Stack Canaries

These are values that the compiler inserts in function calls to ensure that a stack overflow has not overwritten the return value of a function. These are values that serve as a check before returning to the execution point that called a function.

There are techniques that allow us to bypass these protections. An example would be to discover the value of the “canary” before performing the exploit and put the value in place during the exploit. This can be done in string format attacks since they allow reading arbitrary memory locations.

In the case of 32-bit machines or less a brute force attack can be made on the canary. Sometimes it is the only technique that can get result to bypass this protection.

2.5 ASLR (Address Space Layout Randomization)

In this case, the operating system, or the kernel oversees enabling the randomization of the addresses of different memory spaces. When ASLR is enabled, each time a process is executed, the addresses of the heap, stack, mmap base and even the base of the main executable will be different. This reduces the likelihood that an attacker can reliably reproduce a memory corruption attack.

It also means that the addresses of dependencies such as libc are at different addresses each time the program is executed as with PIE.

2.6 Automating testing / checking

Given the number of protections and techniques to prevent binary exploitation, it is impractical to write an exploit only to find out later that it will not work because the stack does not allow execution or because some of the references to which the exploit calls change address with each execution…

For this reason, there are many tools that inform us of the protections enabled for a particular binary and the environment in which it is executed.

Some of the alternatives are Hardening check or checksec.sh. Their use is simple and is exemplified below:

$ checksec –file=/bin/ls
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 6 18 /bin/ls

3 Evasion techniques

There are multiple techniques and tools to avoid the executable protections that have been presented. The following is a description of the most relevant ones.

3.1 Return-Oriented Programming

As already mentioned, the execution of shellcodes from the stack is not directly possible if the NX mechanism is enabled, which prevents the execution of code in a data zone. However, there are techniques to circumvent this protection.

Return-oriented ret programming or ROP consists of finding code fragments ending in a (return) instruction, called gadgets. Once these fragments are found, a buffer overflow vulnerability is used to overwrite the return address of the original function with the address of the first gadget, and the following positions with the addresses of an ordered chain of gadgets.

The result is that, on return from the original function, the first gadget is executed, which, on completion and without having modified the values in the stack, executes a return that jumps to the second gadget. Thus, the whole chain of gadgets is executed, which constitute the shellcode.

Writing a shellcode with ROP must consider those instructions that modify the stack, since they would overwrite the chain. This can be avoided by interleaving the necessary values for the execution of each gadget. Function calls can also be executed by placing the function address, followed by the return address corresponding to the next gadget. For passing parameters, depending on the function calling convention, they can be passed on the stack, after the return address, or use, prior to the call, gadgets that write the appropriate registers.

The limitation of ROP is the search for gadgets to compose the shellcode. This technique usually focuses on searching for libc functions, such as system, which allows a system file to be executed, although the use of ASLR can complicate the task. This technique is known as ret2libc, and there are other similar techniques that use ROP for different purposes, such as ret2csu or ret2dlresolve.

Even in cases where there is not enough space in the stack to complete a gadget chain, there are different techniques to take control of the stack pointer to fake its address and obtain more space. These techniques are known as stack pivoting.

There are also tools such as ROPgadget that make it easy to find gadgets in an executable.

The heap overflow technique is another process for executable exploitation

3.2 GOT Overwrite

The GOT section of an executable contains a table with references to functions external to the executable itself already loaded in memory.

When NX protection is enabled in an executable, one of the options to bypass this protection is to overwrite the GOT table to call a function of our choice when we encounter any other external call.

To illustrate the concept, a vulnerable program is attached below:

char buffer[300];

while(1) {
fgets(buffer, sizeof(buffer), stdin);
printf(buffer);
}

In this case, using a “string format” vulnerability, we could overwrite the “GOT” entry of the printf function to point to “system” so that on the second iteration of the loop the equivalent code would be as follows:

char buffer[300];

while(1) {
fgets(buffer, sizeof(buffer), stdin);
system(buffer);
}

3.3 Canary leaking y brute forcing

If we find a string format vulnerability in an executable, it is possible to dump the stack until we find the canary value so that when we perform a buffer overflow we can include the canary in the exploit, evading this protection technique.

It is also theoretically possible to perform a brute force attack on the protection value of the stack by relaunching the process until a collision is achieved. This is a slow and tedious process that may not always be possible. It is especially implausible on 64-bit platforms where the attack is impractical in almost any situation.

The brute force attack could be especially practical in the scenario where we can interact with a fork of the original process. Forks of a process inherit a memory map equal to that of the parent, so they retain their canary. This ensures that we can attempt values while maintaining a constant canary and limiting the execution time of the attack.

3.4 Syscalls

System calls have the acronym syscall. This is the mechanism by which a program calls the kernel to perform specific tasks such as creating processes, peripheral I/O and others that require the elevation of permissions. When browsing the list of syscalls the reader will notice that there are some similar to libc functions such as open(), fork() or read(). This is because these functions are wrappers of the system calls to make implementation easier for developers.

The system call in Linux is triggered by the int80 instruction. When the kernel checks the value stored in RAX (the value of the syscall number) it identifies which syscall should be executed. The rest of the parameters can be stored in RDI, RSI, RDX, etc. and each parameter has a different meaning depending on each call. One notable case is execve.

The syscall execve executes the schedule supplied in the RDI parameter, the arvp parameter in RSI and the envp parameter in RDX. It is interesting because if the system function does not exist it can be used to call /bin/sh instead. The execution would be with the parameter in a pointer to /bin/sh to RDI, entering value 0 in RSI and TDX (to be able to generate a shell it is necessary that the parameters arv and envp are NULL). Numerous proofs of concept can be found on the net.

Gadgets can be obtained for running system and obtaining a terminal using ROP.

3.5 Sigreturn-Oriented Programming

The SROP technique consists of executing a buffer overflow to overwrite the stack of a signal handler call. The signal handler, when executed, saves the state of the registers, as well as the return address, in the stack, and returns with the sigreturn system calls, which retrieve the state of the registers and return to the return address.

Overwriting these values allows control over the value of each register, which gives full control over the execution flow, although it can be complex to execute without producing errors.

3.6 Malloc hook overwrite

The C language has a feature functionality called __malloc_hook, according to the GNU page it is defined as «the variable to the pointer that executes malloc each time it is called».

On the other hand, a one_gadget is simply an execve(“/bin/sh”) command that is present in the glibc library. If you manage to overwrite the value of __malloc_hook with the address of one_gadget, calling malloc will immediately result in a terminal. Although, in many cases, this can generate a segmentation fault.

3.7 NOP sleds

A NOP slide, sled or ramp is a sequence of NOP (no operation) instructions intended to bring the CPU to the end of the instruction execution flow. It is generally used to take the execution along the desired path. It is a technique widely used in exploit development.

This technique is the oldest and used to exploit stack buffer overflows. It is a brute force method to find the memory region where the instructions to be exploited are located at the cost of increasing the size of the attacked memory area. The format of this would be like the example below:

from pwn import *

context.binary = ELF(‘./vuln’)

p = process()

payload = b’\x90′ * 240 # The NOPs
payload += asm(shellcraft.sh()) # The shellcode
payload = payload.ljust(312, b’A’) # Padding
payload += p32(0xffffcfb4 + 120) # Address of the buffer + half nop length

log.info(p.clean())

p.sendline(payload)

p.interactive()

Obviously, this is not always the best way to perform an exploitation of executables operation. Sometimes the available buffer sizes will not allow the inclusion of NOP sleds of the desired size. However, in some systems it will be the only way to evade application hardening measures to achieve a successful attack.

Exploitation of executables is a complex and error-prone task that requires experience, time, and effort. However, successfully exploiting an executable can breach the security of the entire system, so it is very useful to know the different existing techniques and practice them to apply them in firmware analysis.

Moreover, in the case of executables used in firmware, it is surprisingly common to find that they lack basic protections against exploitation, so they can be especially effective.

References

  • https://github.com/ProhtMeyhet/hardening-check
  • https://github.com/slimm609/checksec.sh
  • https://ir0nstone.gitbook.io/notes/
  • https://github.com/carlospolop/hacktricks
  • https://github.com/JonathanSalwan/ROPgadget
  • https://heap-exploitation.dhavalkapil.com/
More articles in this series about OWASP

This article is part of a series of articles about OWASP

  1. OWASP methodology, the beacon illuminating cyber risks
  2. OWASP: Top 10 Web Application Vulnerabilities
  3. IoT and embedded devices security analysis following OWASP
  4. OWASP FSTM, stage 1: Information gathering and reconnaissance
  5. OWASP FSTM, stage 2: Obtaining IOT device firmware
  6. OWASP FSTM, stage 3: Analyzing firmware
  7. OWASP FSTM, stage 4: Extracting the filesystem
  8. OWASP FSTM, stage 5: Analyzing filesystem contents
  9. OWASP FSTM step 6: firmware emulation
  10. OWASP FSTM, step 7: Dynamic analysis
  11. OWASP FSTM, step 8: Runtime analysis
  12. OWASP FSTM, Stage 9: Exploitation of executables
  13. IoT Security assessment
  14. OWASP API Security Top 10
  15. OWASP SAMM: Assessing and Improving Enterprise Software Security
  16. OWASP: Top 10 Mobile Application Risks