When I was a student, I took a class on buffer overflow. At the time, it was relatively easy, you just had to follow step by step the reference article, ie “Smashing the stack for fun and profit”, to understand what it was and achieve it easily.
Since then, water has flowed under the bridges, and OSes have developed various mechanisms that make it much complicated, if not even impossible, to operate a buffer overflow.
I remain convinced that Smashing the stack is a reference in the matter, and that it is necessary to have understood it before being able to go further in software exploitation.
So, one of the possible solutions is to install an OS “like back then”. But it can be a bit of a pain.
Another solution is to disable what changed between 1996 and 2020, and other protections, put in place by OSes and compilers since then that would be a nuisance to follow the article again.
In this article, we will take an Ubuntu 18.04 as an example, but it is likely that all modern OSs and compilers implement the different mechanisms that we will mention here.
We will start with the protections designed to prohibit overflow exploitation. Whether by overflow detection (SSP), address randomization (ASLR) or inability to execute in the stack (NX).
Stack - Smashing - Protector, initially named ProPolice, is a protection system developed by IBM from 2001, completely redeveloped by Red Hat in 2005. On Ubuntu, this protection has been added by default to the compilation since version 6.10, i.e. since 2006.
This is a patch applied to
gcc, so that it adds
a canary to the compilation, which takes the general idea of Stackguard. The idea of
canary is to add data next to the local variables of each
function, just after saving the return address. If this data is
overwritten, then an overflow is detected and the program stops, with a
nice error message:
*** stack smashing detected ***: <unknown> terminated
Abandon (core dumped)
This protection will therefore detect when you perform a buffer overflow, and will block the rest of the execution.
To disable it: use the
-fno-stack-protector option at compile time.
Address Space Layout Randomization is a protection mechanism available in the Linux kernel by default since June 2005. On Ubuntu, this kernel has been used since October 2005.
ASLR allows you to randomly place data areas in virtual memory (heap, stack, libraries, etc.). By placing these different elements randomly, you will no longer be able to predict the address of your shellcode. This therefore makes operating a buffer overflow more complicated.
To deactivate it: Put
0 in the
/proc/sys/kernel/randomize_va_space or use the
following command line:
sysctl -w kernel.randomize_va_space=0
This protection has been introduced in processors since 2003 for AMD and 2004 for Intel. This has been taken into account by the Linux kernel since version 2.6.8 (August 2004). However, it was not enabled by default on most Linux systems prior to 2009, due to error issues caused on some processors. Ubuntu has managed this protection fully and on all processors since 2011.
This is a protection that prevents code from being executed in areas of memory that should not be used for this, such as the stack. This is implemented directly in the processor. This is a bit, NX (for No eXecute), used to define whether a memory page is executable (0 for no, 1 for yes). This bit is interpreted by the MMU when accessing memory pages.
To disable it: it is necessary to mark a program as
requiring an executable stack, using
execstack. Install the
package like any other package:
apt-get install execstack
You also need to compile the executable with the
-z execstack option.
Cleanup of assembly code
At this point, you could implement your overflows as at the time but to stay as close as possible to the examples in the article, there are still three little things to disable to simplify the compiled and assembler codes … Independent execution the position in memory for the code (PIC), for the executables (PIE) and the exceptions frames.
The PIC mechanism (for Position Independent Code) has been enabled by default at compile time in Ubuntu since April 2017.
Its purpose is to allow libraries to be loaded at different memory addresses from time to time. For this, all calls to variables will be replaced by relative and not absolute addresses.
To disable it: use the
As for PIC, the PIE (for Position Independent Executable) has been enabled by default at compile time in Ubuntu since April 2017.
This mechanism is similar to PIC in principle, as it allows executables to be loaded to different memory addresses each time they are executed. This time, it’s done in link editing, for, among other things, the addresses in the GOT table.
To disable it: use the
gcc to disable protection for executables.
This system has been standardized in the document Linux Standard Base Core Specification 4.0, dating from 2008.
This is data added to facilitate debugging and handle exceptions. The compiler will add sections (.eh_frame and .eh_framehdr) in which will be stored data which will allow the function call stack to be moved up when an exception occurs.
To disable it: remove these sections and the
corresponding instructions via the
Last little things
Now that the covers are removed and the corations removed, there are still two final details to be faithful to the original item. Pass the code to 32bits and add some includes to compile.
From the 1996 article, the processor used by Aleph One was a 32-bit processor, it is even conceivable that it was a Pentium Pro (1995-1998). This can be seen from the assembly dump instruction set, and especially since the first specifications for 64-bit processors will not be released until August 2000.
If you want to get as close as possible to his machine, and have an almost similar set of instructions:
- install the
- compile with option
If we write the programs as is, the compiler will grumble … It actually lacks the inclusion of the headers of the libraries used. However, they are defined in the C90 standard and therefore already existed in 1996.
<string.h>for using string processing functions, such as
<stdio.h>for I/O management functions, such as
<unistd.h>to get the definition of the
For convenience, you can add these three lines at the top of all codes: