This blog post will discuss the basics of binary exploit development on modern x64 Linux systems, including the following topics:

☐ Stack Canary Bypass
☐ Address Space Layout Randomization Bypass
☐ Data Execution Prevention Bypass
☐ Buffer Overflows
☐ Format Strings
☐ Return-Oriented Programming

A custom binary will be used to demonstrate a simple case incorporating all mentioned topics. Ideally, the reader should be familiar with basic buffer overflows, x64 architecture basics, and using a debugger.

Exploit Protection Mechanisms and Bypasses

NX — No Execute — DEP — Data Execution prevention is an exploit mechanism that does not allow the attacker to execute a custom shellcode from the stack. It dates back to 2004 and uses the NX — No Execute bit in AMD processors and XD — Execute Disable bit in Intel processors. Bypassing DEP mostly includes using existing binary functions in a creative way or using functions accessible to the binary, such as libc functions (ret2libc attacks), but without executing anything from the stack. Having function arguments on the stack is completely valid.

Address Space Layout Randomization — ASLR is an exploit countermeasure that randomizes the binary’s core memory areas during every execution. Bypassing ASLR can include brute forcing, leaking important memory addresses, and using non-randomized space.

Stack Canary — Stack Cookie — Stack Protector — Stack Guard is a simple, yet effective protection mechanism. A random value (or a hardcoded value in some custom implementations) is pushed on the stack and added to the binary during the compilation process. After the program execution, the value is checked again — if it changed, the program errors out with a message such as “stack smashing detected”. To bypass stack cookies — the value should be leaked, or brute-forced, and the exact offset should be known before the actual exploitation.

Format string vulnerabilities occur when an unsanitized user input is being passed to a format function — a printf-like function that allows the format string parameter to specify the type of conversion of the format function. The potential attacker could read and write in the memory areas, or even execute code.

Custom Binary Inspection

The protection mechanisms are checked with checksec — our binary has NX, and stack cookies enabled. Since ASLR is an OS level protection — it is checked from the randomize_va_space file — the number 2 indicates that it is enabled and that also data segments are randomized. The binary invokes ping and prints the first parameter with the binary invocation (-h), and another parameter entered when the binary runs (TEST).

Simple Fuzzing

The next step is to see how the binary handles command injection vulnerabilities — it turned out that it does not append user input to any system command. After this simple check, a longer string was sent containing %x (reading from the stack in string formatting) — which should detect both potential buffer overflows and format string vulnerabilities — the first input is vulnerable to format string vulnerability — as the output contains some memory output, and the second input results in the message “stack smashing detected” — which indicated that the binary might just have been overflowed.

Detailed Binary Inspection (Searching for the Stack Cookie Offset)

Let us see the binary functionality and check which functions are used with gdb. We will search for the __stack_chk_fail function to determine the offset of the stack cookie. This function is added when compiling with the stack cookie protection option. When disassembling the main function, we see a check_internet function that contains the stack cookie check.

The rax register has been set up before the call and compared to the stack cookie value. If the condition is met, the program continues its execution, else the call to 0x401040 will error out.

The breakpoint is set at the nop instruction, and the program is run with a custom string with length 100 (pattern_create) which will help us to determine the exact offsets — pattern_create from gdb-peda addon — the same as the metasploit pattern create. We will stepi — step one instruction twice to reach 0x4011e8 memory address — just before the stack cookie register comparison with the current stack canary value. Of course, if we overwrite this address, the program errors out if we continue the execution, but now we know that the stack canary will be at position 88 in our exploit buffer!

Getting the $RIP Offset (Leaking the Stack Cookie Value)

We can also note from the previous screenshot that the stack cookie resides at the address $rbp-0x8. The register $rip which will continue our execution after the buffer overflow is located usually at $rbp+0x8, thus the offset of $rip is 88+16.

By fuzzing the format string vulnerability with %llx (unsigned long lower-case hexadecimal) and searching for values that end with 00 and change just the first 7 bytes each time the program starts — we might conclude that this is a stack cookie (there are other cookie types, but this is the default type added by gcc on my x64 Kali Linux).

We can easily confirm this by setting a breakpoint just before the stack cookie check call and compare the eax value to position number 19 from the memory leak (%19$llx — 19th position of %llx). In the following screenshot we confirm that the leaked address is exactly the $rax address before the __stack_chk_fail function call.

Bypassing ASLR

By disassembling the function check_internet_connection, we see that a system function is used to invoke the ping function. If we could find the [email protected] address and provide it with the right argument such as “sh”, we could easily get a shell, without trying to use the format string for further memory leaks (for example to leak the libc base and find the constant offset to system functions).

This turned out to be quite easy — we will use the gdb-peda find functionality to get the string sh which resides at a non-randomized memory address. It might be funny, but it seems that the sentence “your internet connection should be checked now through bash”, hardcoded in the binary functionality, puts the “sh” string to the memory from the last word.

This would still be exploitable, but a longer ROP chain would be required to set up the letters ‘s’ and ‘h’ to the right place as an argument to the system command. We see that the non-randomized system function is at: 0x0000000000401050, and that the string sh can be found in the binary (reliable and aslr bypass) at: 0x402063:

Bypassing ASLR and DEP — Gaining a Shell

ROP — Returned-oriented programming is a practical technique in bypassing memory protections. Attackers mostly use memory gadgets from executable and non-randomized or known areas to set up the stack and the registers properly with desired values, to execute custom code.

In this case, we already know how to bypass stack cookie: we have a system function at a non-randomized address, and a pointer to the argument — “sh” for shell. The only thing left is to overwrite the $rip register with “pop rdi; ret” instruction which will place the “sh” argument to the $rdi register and return the execution to the system function.

RopGadget is a useful tool for finding the desired instruction chain in the binary. A small python script is used to spawn a shell combining all mentioned techniques:

Exploitation with Python

Pwntools is a useful Python library that ensures interacting with binaries during exploitation, although this is just a simple case. The script is explained in detail in corresponding sections, but it also has comments on each line as a reminder:

Conclusion

Exploit development is a true hacking playground, where creativity is used extensively to bypass modern protection mechanisms. Penetration Testing Teams, Red Teaming Teams, Bug Bounty, Government Agencies and Research Teams alike highly value skillful exploit developers. So, this article is an introduction to more advanced topics and demonstrates a simple case of using common exploitation techniques (buffer overflow, format strings, ROP), and bypassing some of the most common binary protection mechanisms (DEP, ASLR, Stack Canary).