Buffer Overflow Prep - Beginner friendly note

Buffer Overflow Prep (Beginner friendly - I hope)

I know this topic is extremely important for people who is into OSCP preparation like I am. I take this seriously. Although I have zero programming language knowledge, I still manage to put together Buffer Overflow (BOF) to some certain extend.

Tryhackme ALSO SAID:

PLEASE NOTE THAT THIS ROOM DOES NOT TEACH BUFFER OVERFLOWS FROM SCRATCH. IT IS INTENDED TO HELP OSCP STUDENTS AND ALSO BRING TO THEIR ATTENTION SOME FEATURES OF MONA WHICH WILL SAVE TIME IN THE OSCP EXAM.

For context: (To be honest, I struggle tremendously on what Tryhackme is trying to do in these task below, it take a good chunk of my time to realized that they are mimics vulnerable applications on the victims machine. These vulnerable applications then listen for output from the network.) We will take advantage of the fact that they are vulnerable to BOF and exploit them from there.

Note: I will try to keep this note as newbie friendly as possible. Because I'm a newbie myself at the time of writing this note, I will try to also explain to myself what is going on.


To begin with, Tryhackme command - ==xfreerdp /u:admin /p:password /cert:ignore /v:10.10.15.213 /workarea /tls-seclevel:0== - is no longer works for xfreerdp3.

Use this:

xfreerdp3 /u:admin /p:password /v:10.10.15.213 /cert:ignore /workarea /sec:rdp

Task 1: oscp.exe - OVERFLOW1

Step 1: Run Immunity Debugger as Admin

Step 2: Open oscp.exe with Immunity Debugger

Step 3: The binary will open in a "paused" state, so click the red play icon or choose Debug -> Run. In a terminal window, the oscp.exe binary should be running, and tells us that it is listening on port 1337.

Step 4: On your Kali box, connect to port 1337 on 10.10.15.213 using netcat:

nc 10.10.15.213 1337

Type "HELP" and press Enter. Note that there are 10 different OVERFLOW commands numbered 1 - 10. Type "OVERFLOW1 test" and press enter. The response should be "OVERFLOW1 COMPLETE". Terminate the connection.

Step 5: Mona Configuration

The mona script has been preinstalled, however to make it easier to work with, you should configure a working folder using the following command, which you can run in the command input box at the bottom of the Immunity Debugger window:

Why are we doing this? In a hypothesis environment, all that will be done by the victim, not the hacker right? You asked well, see the answer at the end of this write up! [1] [2] [3]


IN THE NEXT FEW STEPS OUR STEPS CAN BE SUM UP AS THESE:

  • FIND THE EXACT AMOUNT OF BYTES** which crashes the server using fuzzer.py

  • GENERATE A UNIQUE PATTERN using pattern_create.rb to help identify the EIP overwrite location

  • USE THAT PATTERN AS PAYLOAD in exploit.py and trigger the crash again

  • USE MONA TO FIND THE OFFSET where EIP is overwritten with part of the pattern

  • RUN exploit.py AGAIN with 'A' * offset + 'B' * 4 to confirm EIP is now 42424242 (i.e., we control it)

  • FIND BAD CHARACTERS — bytes that get corrupted when sent to the app, like \x00, \x0a, etc.

  • FIND A SAFE jmp esp ADDRESS that doesn't contain any badchars

  • GENERATE THE FINAL PAYLOAD with:

    • NOP sled

    • Clean shellcode (no badchars)

    • Padding (optional)

  • SEND IT and enjoy your reverse shell

Sigh

That was exhausting.

Anyway, confusingly enough, let's continue with the steps.


Step 6: Fuzzing

Create a file on your Kali box called fuzzer.py with the following contents:

Run the fuzzer.py script using python: python3 fuzzer.py

The fuzzer will send increasingly long strings comprised of As. If the fuzzer crashes the server with one of the strings, the fuzzer should exit with an error message. Make a note of the largest number of bytes that were sent (mine was 2000).

Step 7: Crash Replication & Controlling EIP

Create another file on your Kali box called exploit.py with the following contents:

Run the following command to generate a cyclic pattern of a length 400 (Which means if mine were 2000 now I have to fill in 2400 = 2000 + 400) bytes longer that the string that crashed the server (change the -l value to this):

`/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2400

I prefer:

If you are using the AttackBox, use the following path to pattern_create.rb instead (also ensure to change the -l value):

/opt/metasploit-framework/embedded/framework/tools/exploit/pattern_create.rb -l 2400

Copy the output and place it into the payload variable of the exploit.py script.

On Windows, in Immunity Debugger, re-open the oscp.exe again using the same method as before, and click the red play icon to get it running. You will have to do this prior to each time we run the exploit.py (which we will run multiple times with incremental modifications).

On Kali, run the modified exploit.py script: python3 exploit.py

The script should crash the oscp.exe server again. This time, in Immunity Debugger, in the command input box at the bottom of the screen, run the following mona command, changing the distance to the same length as the pattern you created:

!mona findmsp -distance 2YXX

Mona should display a log window with the output of the command. If not, click the "Window" menu and then "Log data" to view it (choose "CPU" to switch back to the standard view).

In this output you should see a line which states:

EIP contains normal pattern : ... (offset XXXX) (Mine is 1978)

What is EIP? See the Q&A section[4]

Update your exploit.py script and set the offset variable to this value (was previously set to 0). Set the payload variable to an empty string again. Set the retn variable to "BBBB".

Restart oscp.exe in Immunity and run the modified exploit.py script again. The EIP register should now be overwritten with the 4 B's (e.g. 42424242).

Cool

Step 8: Finding Bad Characters

Generate a bytearray using mona, and exclude the null byte (\x00) by default. Note the location of the bytearray.bin file that is generated (if the working folder was set per the Mona Configuration section of this guide, then the location should be C:\mona\oscp\bytearray.bin).

What is this for? - You asked?

Answer: The purpose of this step is to identify bad characters — bytes that break our payload — by comparing what we sent versus what actually landed in memory. We keep trimming both our payload and the Mona-generated bytearray until we know exactly which characters are safe.

Why are we doing this so many times ? - You asked?

Answer: Every time a bad character is hit, Mona stops analyzing the rest of the payload — because it thinks the string is over. That’s why we often have to do this step multiple times, trimming as we go.

Now generate a string of bad chars that is identical to the bytearray. The following python script can be used to generate a string of bad chars from \x01 to \xff:

Update your exploit.py script and set the payload variable to the string of bad chars the script generates.

Remember to keep the BBBB.

Why keep BBBB in badchar tests?

It ensures EIP gets overwritten in a controlled, stable way

ESP lands exactly where you want it — pointing to your bytearray

You get accurate comparison results in !mona compare

Prevents weird crashes or stack shifts from corrupting the test

Restart oscp.exe in Immunity and run the modified exploit.py script again. Make a note of the address to which the ESP register points and use it in the following mona command:

!mona compare -f C:\mona\oscp\bytearray.bin -a <address>

A popup window should appear labelled "mona Memory comparison results". If not, use the Window menu to switch to it. The window shows the results of the comparison, indicating any characters that are different in memory to what they are in the generated bytearray.bin file.

Not all of these might be badchars! Sometimes badchars cause the next byte to get corrupted as well, or even effect the rest of the string.

PAY REALLY CLOSE ATTENTION FOR WHAT TRYHACKME SAID ABOVE!

Because our line of bytes payload may not all corrupted, we might as well slowly remove each bytes, not call them all corrupted, that's wrong! So if you found consecutive byte like 01 02 03 a1 a2 a3, may be start removing 01, a1 first.

We have 00 07 08 2e 2f a0 a1 so we start with:

Notice the changes of the ESP Address!

The first badchar in the list should be the null byte (\x00) since we already removed it from the file. Make a note of any others. Generate a new bytearray in mona, specifying these new badchars along with \x00. Then update the payload variable in your exploit.py script and remove the new badchars as well.

Restart oscp.exe in Immunity and run the modified exploit.py script again. Repeat the badchar comparison until the results status returns "Unmodified". This indicates that no more badchars exist.

Step 9: Finding a Jump Point

With the oscp.exe either running or in a crashed state, run the following mona command, making sure to update the -cpb option with all the badchars you identified (including \x00):

!mona jmp -r esp -cpb "\x00"

Our command should be:

This command finds all "jmp esp" (or equivalent) instructions with addresses that don't contain any of the badchars specified. The results should display in the "Log data" window (use the Window menu to switch to it if needed).

Choose an address and update your exploit.py script, setting the "retn" variable to the address, written backwards (since the system is little endian). For example if the address is \x01\x02\x03\x04 in Immunity, write it as \x04\x03\x02\x01 in your exploit.

✅ What We’re Looking For:

We want a JMP ESP address that:

  1. Comes from a module with ASLR, Rebase, and SafeSEH all set to False

  2. Doesn’t contain any bad characters

  3. Is located in a module that’s loaded at runtime (like essfunc.dll) ✅

Why ESP? [5] What is a "jump point"? [6] Why not jump straight to the shellcode address? [7] See the Q&A section below.

Based on this?

We should pick the first one.

Go ahead and use this 625011AF and put it in Cyberchef:

And put it in rent with:

What is rent? [8] See the Q&A section below.

Step 10: Generate Payload

Run the following msfvenom command on Kali, using your Kali VPN IP as the LHOST and updating the -b option with all the badchars you identified (including \x00):

Copy the generated C code strings and integrate them into your exploit.py script payload variable using the following notation:

Step 11: Prepend NOPs

Since an encoder was likely used to generate the payload, you will need some space in memory for the payload to unpack itself. You can do this by setting the padding variable to a string of 16 or more "No Operation" (\x90) bytes:

Step 12: Exploit!

With the correct prefix, offset, return address, padding, and payload set, you can now exploit the buffer overflow to get a reverse shell.

Start a netcat listener on your Kali box using the LPORT you specified in the msfvenom command (4444 if you didn't change it).

Restart oscp.exe in Immunity and run the modified exploit.py script again. Your netcat listener should catch a reverse shell!

(To be continue if I found some thing cool from Task 2 -> Task 10...)

Task 9: oscp.exe - OVERFLOW9

In this task something interesting happens

At if you will see the bad char was 00 04 05 3e 3f e1 e2, normally you would be good to go when you remove 04 3e e1 as bad char the next line of bad char was shown is 00 04 3e 3f 40 e1.

As we have removed 04 3e e1 we will need to look into 3f 40.

Personally, it takes me 15 minutest to realized that 3e corrupted 3f earlier so 3f couldn't corrupt 40. So now we see 3f 40 in bad char, the real bad char will be 3f.

The answer will be \x00\x04\x3e\x3f\xe1 for the question of which are the bad chars of Task 9.


Q&A with a friend

[1] In Task 1, what does: !mona config -set workingfolder c:\mona\%p do?

Answer:

🔧 !mona config -set workingfolder c:\mona\%p — What does it do?

This tells Mona, the exploit dev helper plugin inside Immunity Debugger, to:

Create a working folder on your Windows machine at C:\mona\PROCESS_NAME, and store all its output files there (patterns, logs, bytearrays, badchar comparisons, etc.)


🧠 What is %p?

%p is a Mona variable that represents the name of the current process you're debugging.

So, if you’re debugging:

Then Mona will automatically save all files into:


💾 Why is this important?

Because later you’ll run commands like:

And Mona will generate:

  • pattern.txt

  • offset.txt

  • bytearray.bin

  • logs.txt

If you don’t set a working folder? 💥 These files go nowhere or get dumped in temp folders. Total mess.


✅ Summary:

Command
Meaning

!mona

Run Mona script inside Immunity Debugger

config

Configure Mona’s settings

-set workingfolder

Define where output files go

c:\mona\%p

Use folder c:\mona\process_name automatically

[2] In a hypothesis environment, all that will be done by the victim, not the hacker right?

🧠 In Real Life (Not the TryHackMe Lab), Who Does What?

💻 In TryHackMe / OSCP / CTF:

  • You’re both the attacker and the debugger.

  • You run Immunity Debugger + oscp.exe on your own Windows VM, so you can:

    • See crashes

    • Analyze EIP

    • Use mona for offsets and badchar comparisons

    • Build a working exploit

👉 This is your exploit development lab.


🌐 In Real World / Hypothetical Attack:

  • The victim is running the vulnerable binary (e.g., a server or client app).

  • You, the attacker, don’t have access to their debugger.

So:

  • You don’t run mona on the target.

  • You don’t know the crash output (unless you can monitor it indirectly).

  • You develop the exploit in your own lab, using a copy of the vulnerable binary.


🔬 This Is Called "Developing an Exploit Locally"

You:

  1. Find the same vulnerable version of the software.

  2. Run it in your Windows VM.

  3. Use:

    • Immunity Debugger

    • mona

    • Fuzzers

    • Payload testing

  4. Once exploit is stable → you craft:

    • Payload + EIP overwrite

    • Remote exploit script

Then finally: 5. Send it to the real target.


🧨 Think Like This:

  • You build and test the weapon in your lab.

  • You fire the weapon at the victim, who just happens to be running a system with the same vulnerability.

  • The victim never sees mona or a debugger — they just get pwned.


✅ TL;DR:

Action
Who Does It
Where

Run mona, !findmsp, etc.

Attacker

In local lab (Immunity Debugger)

Run vulnerable service (e.g. oscp.exe)

Victim

In real target environment

Analyze crash, offset, badchars

Attacker

In your own VM

Launch final exploit

Attacker

Against victim over the network

[3]: So the attacker have to understand things thoroughly to be able to spot the similarity between a vuln app in the lab and vuln app in the wild right?

Absolutely right.


🔎 Why Deep Understanding Matters:

Imagine this:

  • In your lab, you exploit a vuln in oscp.exe → BOF via OVERFLOW1.

  • In the wild, you scan a box and find:

    • A custom service on port 1337.

    • The banner looks weird.

    • It crashes when you send large input.

    • You reverse it and realize it parses data the same way as oscp.exe.

Boom — you realize:

"This is functionally the same vuln. It just has a different name."

Because you've seen the pattern before, you can adapt your BOF exploit to this new app.


🧠 It's Like Street Fighting vs. Dojo Sparring

  • In the lab (dojo), everything’s padded and slow.

  • In the wild (street), it’s fast and messy.

  • But if you trained hard, you know the moves, and when the moment comes — you recognize it instantly.

That’s why people like you grind:

  • TryHackMe BOF

  • OSCP-style labs

  • Debugging

  • Understanding memory

You're training to see the pattern under the chaos.


🛠️ What You’re Actually Learning When You Do BOF Labs:

Lab Skill
Real World Benefit

Spotting a crash

Recognize exploitable parsing logic

Calculating offset

Know where EIP control happens

Identifying badchars

Know what encoding/sanitization is in place

Locating JMP ESP

Know how to land your payload in hostile memory

Rebuilding shellcode

Know how to customize and weaponize it


🧬 Final Truth

Yes — the attacker must understand:

  • CPU architecture

  • Stack behavior

  • Assembly basics

  • Windows memory protection (DEP/NX, ASLR, etc.)

  • Payload crafting

  • Binary formats (PE, ELF)

Because only then can you see the same vulnerability wearing different clothes.

[4] What is EIP?

🔹 EIP = Extended Instruction Pointer

It’s a special CPU register on 32-bit systems that tells the CPU:

“This is the memory address of the next instruction to execute.”

So if EIP is 0x625011AF, the CPU will:

  1. Go to that memory location,

  2. Read the instructions stored there,

  3. Execute them.

Because if you can overwrite EIP, you can tell the CPU:

“Hey, don’t go where the programmer wanted you to go — go HERE, where I’ve planted my shellcode instead.”

🔥 That’s how you hijack a program.

[5] Why ESP?

Because in your buffer:

  • overflow hits EIP

  • retn becomes EIP

  • Then ESP points to padding + payload

So if you make EIP = a jmp esp address…

You land directly on your shellcode.

[6] What is a "jump point"?

A “jump point” is:

  • An address in memory (like 0x625011af)

  • Inside a loaded module (like essfunc.dll)

  • That contains the instruction:

So when the CPU sees:

It jumps to wherever ESP is pointing — your shellcode.

[7] Why not jump straight to the shellcode address?

Because:

  • You usually don’t know the exact memory address of your shellcode.

  • Memory layout can vary between runs (especially with ASLR)

  • ESP is predictable and reliable

  • So JMP ESP is a universal bridge between EIP and your payload

[8] 🔧 In exploit.py, what is retn?

retn is short for “return address”, and in the context of buffer overflows, it’s the 4-byte value that will overwrite the EIP (instruction pointer) — which tells the CPU where to go next.

[BONUS] How can multiple JMP ESP addresses work if there’s only one ESP (top of the stack)?

Each JMP ESP instruction lives at a different memory address in different loaded modules, but all do the same thing: jump to wherever ESP points at runtime. You pick ONE good address (no badchars, stable, from a non-ASLR module), and place it in EIP. When executed, it redirects execution to the current top of the stack — where you’ve carefully placed your shellcode.

ESP is only one value at crash time. All JMP ESPs just redirect to it. You’re not jumping “to the JMP ESP,” you’re using the JMP ESP as a tool to jump to ESP.

[BONUS 2]: How can JMP ESP addresses work across different machines running the same vulnerable app?

🧠 Answer:

In most OSCP boxes and real-world exploits, the key is that many DLLs or executables are compiled without ASLR (Address Space Layout Randomization) and without rebasing.

When ASLR is disabled, the module always loads at the same memory address, meaning:

A JMP ESP found at 0x625011AF on your local machine will also exist at the same address on the remote victim.


I hope you enjoy this guide as much as I enjoy writing it. Have a good day!

Last updated