In my previous article, I showed you how easy it is to hijack a human brain. This time, I'll show you that your computers are just as gullible.

People think they are safe because their programs "don't do much," like a simple loop that waits for input.

The target was stubborn. It was a simple C program running in an infinite loop, designed to do nothing but run forever.

None

But it had a fatal flaw — a classic, textbook vulnerability. It used an unbounded gets() function to write user input into a tiny, 8-byte buffer.

In the hands of someone who knows how to look under the hood, a tiny, 8-byte oversight is not just a bug, it's an invitation. To a junior developer, it's a convenient way to read text. To me, it's a door with no lock.

Because gets() never checks if the input is too long, I can shove as much data as I want into that tiny space until it spills over and starts drowning the program's logic.

*warning: the 'gets' function is dangerous and should not be used.

My objective was clear: shatter that infinite loop and force the program to terminate cleanly, on my terms, with an exit code of 1.

Here is how I broke it.

Clearing the Armor

Before launching the attack, I needed to ensure the environment wouldn't get in the way.

I set up my staging ground in an Ubuntu WSL 2 environment. Modern compilers are smart; they wrap programs in invisible armor like stack canaries and randomized memory addresses. I compiled the target program specifically to strip those defenses away, forcing it into a vulnerable 32-bit architecture where memory is predictable.

With the target defenseless, I attached a debugger (GDB) to peer into its mind: the memory stack. I needed precise coordinates. When the program asked for input, where exactly was that data going?

None

By tracing the execution, the map revealed itself.

The Custom 8-byte Weapon

Before I could smash the stack, I needed to craft the "orders" I wanted the CPU to follow. I didn't want to just crash the program. I wanted a clean, professional exit with a status code of 1.

I drafted a simple set of assembly instructions to hijack the system.

Writing shellcode is an exercise in constraint. The gets() function reads input as a string, meaning the moment it sees a "null byte" (a 0x00), it stops reading. My code had to be entirely null-free.

None
gcc -m32 -fno-stack-protector -fno-pie -std=c99 -masm=intel asm.c -o asm

Instead of directly assigning a 1 to the exit code register (which would generate forbidden null bytes), I used a sleight of hand.

I zeroed out the register completely using an xor command, and then simply incremented it by one (inc). I pushed the system call number onto the stack and triggered the kernel interrupt.

Extracted into raw hex, my custom, 8-byte weapon looked like this: \x31\xdb\x43\x6a\x01\x58\xcd\x80

None
objdump -d asm > asmdump

Casing the Joint

Before I strike, I needed to recall the layout. Using GDB, I peeked at the memory addresses once more.

None
  • The Vault (Buffer): Located at 0xffffcad8. This is where my shellcode starts its journey.
  • The Guard (Saved EBP): Located at 0xffffcae0. I have to walk past this (4 bytes of junk) to get to the real prize.
  • The Keys to the Kingdom (Return Address): Located at 0xffffcae4. By the time the program reaches this address, my payload has already replaced the legitimate exit route with a one-way trip to the shellcode.

The math is elementary.

Between my buffer and that precious Return Address were exactly 12 bytes. All I had to do was fill those 12 bytes with "junk" and then replace the Return Address with a pointer to my own malicious code.

The "Egg": My Digital Signature

To make the program exit with a code of 1, I crafted a 16-byte masterpiece called the "egg":

None
  1. The Silent Slide: Four "NOP" instructions to fill the gap.
  2. The Custom 8-byte Weapon: Eight bytes of machine code that tell the CPU to trigger a "sys_exit" with a status of 1.
  3. The Redirect: The final four bytes were the address of my buffer written in Little Endian format to trick the CPU into jumping right back into my trap.

When I fed this "egg" to the program, it didn't stand a chance.

The Exit

It didn't loop forever like the developer intended. It blindly followed my instructions, executed my shellcode, and died exactly how I wanted it to.

None
The "Kill Shot": Verification that the program terminated with the desired exit code.

You can write all the loops you want, but as long as you leave an 8-byte buffer unguarded ….. I'll probably be seeing you again.

None