Exploit Development vs Malware Engineering: A Boundary Analysis
Where does exploit development end and malware engineering begin?
Abstract
WannaCry’s file enumeration loop: strcmp filters already-encrypted .wnry files, GetFileAttributesA validates the target, then wannacry_encrypt_file is called. Annotated in Ghidra. This is pure Mal Dev territory, the exploit primitive has already handed off control by this point.
If you’ve spent any real time in offensive security, you’ve probably run into this confusion yourself, exploit development and malware engineering get used interchangeably, even by people who should know better. They’re not the same thing. They’re related, deeply so, but they’re not the same.
Exploit developers focus on turning a bug into reliable code execution. Malware developers focus on what happens after that maintaining access, hiding from defenders, and making the whole thing work at scale. One is the breach, the other is the occupation.
In this post I want to dig into where exactly that line falls technically, architecturally, using WannaCry and NotPetya as the case studies, since both ran on the same underlying exploit primitive but ended up looking completely different as operations. That gap is interesting and worth understanding properly.
Why Is This Even Confusing?
Because, both fields works almost at the same layer i.e, low-level, close to metal, deep in OS internals. Both discipline write code designed to bypass security controls. Both show up in the same attack chains. When you’re looking at an incident report and see EternalBlue feeding into DoublePulsar feeding into a ransomware payload, it’s not obvious where one discipline ends and the other picks up.
That’s the way I want you to be confused! Okay, jokes apart. Going forward I’ll use eXp dev for exploit developer and Mal Dev for malware developer just to keep things easy for me.
A Bit of History (The Condensed Version)
The story goes back further than most people think. It didn’t start with Stuxnet.
In the 70s and 80s, the early stuff was genuinely just curiosity programs like Creeper were more experiment than attack. The Brain virus in 1986 was arguably the first intentional one, created to track pirated software. Primitive, but purposeful.
Then came the Morris Worm in 1988 the first piece of code to actually exploit systemic weaknesses in internet infrastructure at scale, and the event that produced the first felony conviction under the CFAA. That same year, the AIDS Trojan (1989) gave the world its first real look at what ransom-based extortion through malware could look like, decades before anyone coined the term ransomware.
The 2000s shifted things toward speed and financial motive.
SQL Slammer in 2003 376 bytes took down chunks of the global internet in under ten minutes. Botnets became the dominant model.
Stuxnet (2010) changed the frame entirely. A cyberweapon engineered for physical destruction of Iranian centrifuges, the first time anyone publicly observed code causing real-world industrial damage. Then EternalBlue leaked from the NSA’s toolkit in 2017 and powered two of the most destructive attacks ever recorded. We’ll get into both of those in detail below.
Today the ecosystem is fully professionalized. Zero-day brokers pay millions per vulnerability. Ransomware-as-a-Service platforms let low-skill adversaries rent infrastructure from more capable developers. The tooling gap between nation-states and criminal groups has narrowed considerably.
The Technical Overlap (And Why It Matters)
Why most of the people even including in my starting phase of my career use to confused between this two fields, but now I know that at the foundational level, they run on identical mechanics. The same core knowledge base underpins both.
Memory manipulation is the obvious one. Both fields require a deep working understanding of stack and heap layouts. eXp dev uses memory corruption to pivot control flow, that’s the whole game. Mal Dev uses those same primitives to allocate memory, inject loaders into target processes (process hollowing, reflective DLL injection), and execute payloads cleanly without leaving obvious forensic traces.
Constrained code design is another shared domain. Neither field gets to write standard bloated software. Shellcode has to be position-independent, avoid bad bytes, fit in tight spaces. ROP chains are a requirement in both not just for bypassing DEP/NX/ASLR during initial exploitation, but for advanced malware surviving inside hardened processes without triggering behavioral signatures.
API resolution is where this gets really interesting from a detection perspective. Static imports are trivially flagged. Both exploit shellcode and modern malware resolve APIs dynamically at runtime walking the PEB to find kernel32.dll, resolving VirtualAlloc and CreateThread without any hardcoded addresses. Both also use direct or indirect syscalls to step around the user-mode hooks that EDR solutions place on NTDLL. The technique is identical; the purpose is the same.
Staged architecture shows up in both as well. Real offensive tools can’t be monolithic. Stagers exist to survive tight delivery constraints, establish a channel, and pull down a larger second stage. Metasploit’s TCP stager + Meterpreter is the public textbook example. Malware families do the exact same thing for different operational reasons.
Don’t you think now that the technical Overlap is real? Ofcourse you do, but shared mechanics don’t mean shared objectives, and that’s where the actual boundary lives.
Where They Actually Diverge
Architecturally, eXp dev is a pipeline problem. How? well, it take one bug, produce one reliable primitive. Mal Dev is a systems problem. How? Well again, just like this, it build modular components that run, communicate, persist, and evolve over months.
Now after getting into this field or better let’s think from this way.
Forward: if I had to wrap this exploit into a platform component, what would the interface look like?
Not pop calc.exe that proves nothing operationally. A real exploit module needs to accept a payload stub, handle thread execution, catch exceptions without crashing the host process, and hand control cleanly to the next stage. The exploit becomes a delivery adapter. One interchangeable module. The malware architecture above it shouldn’t care how it works internally.
Backward: if my initial access vector changes, does my core architecture break?
If adversaries swap from phishing macros to an RCE bug to a local priv-esc chain, the post-exploitation logic i.e., credential dumping, lateral movement, persistence should be completely unaffected. Only the delivery adapter changes. If your malware breaks every time the access method changes, you’ve built a script, not an architecture.
Here’s a structured way to look at how these differ in practice:
| Dimension | Exploit Development | Malware Engineering |
|---|---|---|
| Core Objective | Produce a reliable primitive from a vulnerable system state arbitrary read/write, PC control. | Consume that primitive to execute, persist, conceal, and scale. |
| Primary Output | A delivery mechanism and a constrained, ephemeral payload, shellcode, stager. | A modular, evolving software system, loaders, implants, C2, rootkits. |
| Design Focus | Hyper-specific to the target’s memory layout, environment, and mitigation state. | Abstracted and portable the implant doesn’t care how it got on the machine. |
| Execution Lifespan | Fleeting. Works once, hands off, done. | Persistent. Must survive reboots, EDR scans, memory forensics, and network interruptions over months. |
| Constraint Type | Spatial: bad characters, buffer sizes, ROP chain length. | Behavioral: API hooking, ETW telemetry, anomalous network patterns. |
Case Study: EternalBlue Powering Two Very Different Operations
EternalBlue (MS17-010) is a pool buffer overflow in the SMBv1 Trans2 request handler inside srv.sys. The root cause is improper bounds checking during SetupAndX negotiation, leading to non-paged pool corruption which enables an arbitrary write primitive and ultimately System-level code execution with no credentials over port 445.
That single primitive powered two structurally very different attacks in 2017. The difference between them is exactly the eXp dev/Mal Dev boundary in practice.
WannaCry
WannaCry didn’t need phishing or user interaction for lateral movement. Once it had any foothold in a network, it used EternalBlue to spread to every unpatched Windows machine it could reach. EternalBlue is pure exploit code crafted SMB packets triggering the srv.sys overflow, with DoublePulsar installed as a kernel-mode loader to deliver subsequent shellcode cleanly.
The malware itself was two separate logical pieces: a propagation engine doing IP scanning, vulnerability checking, and exploit delivery, and a ransomware payload doing file enumeration, encryption, and ransom UI embedded as a separate executable resource inside the main binary.
The role split: eXp dev turned MS17-010 into a callable primitive. Mal Dev built the worm logic, the kill-switch domain check, the service behavior, the encryption workflow everything that made it an operation rather than just code execution.
# Illustrative only not functional, internals omitted
def propagate_network(local_ip_ranges):
for ip in enumerate_candidate_ips(local_ip_ranges):
if not is_port_open(ip, 445):
continue
if not is_host_vulnerable_smb(ip):
continue
# This is the boundary. EternalBlue is called as a black-box primitive.
# SMB packet crafting, Trans2 overflow, heap manipulation, DoublePulsar
# loader, shellcode handoff all of that lives inside here.
# The propagation logic above never touches any of it.
success = exploit_ms17_010(ip, payload=stager_shellcode)
if success:
log_event(f"Exploited {ip} via EternalBlue/DoublePulsar")
else:
try_credential_based_spread(ip)
def exploit_ms17_010(target_ip, payload):
"""
Exploit module interface:
Input: target IP, position-independent shellcode stub
Output: True (execution achieved) / False (failed)
Everything inside pool corruption, ROP chain, kernel shellcode,
payload injection is eXp dev territory. Omitted intentionally.
"""
pass
NotPetya
NotPetya came in differently. The initial vector was a compromised software update for M.E.Doc, Ukrainian accounting software a supply-chain attack that dropped the malware with local admin rights across thousands of organizations at once. From there it chained EternalBlue, EternalRomance, a modified Mimikatz build for credential theft, and PsExec/WMI for remote execution using those stolen credentials.
Structurally it had a propagation engine (host enumeration, credential harvesting, multi-method spread) and a destructive payload that overwrote the MBR and corrupted the MFT. Systems became unbootable. The encryption was deliberately non-recoverable this was a wiper disguised as ransomware, not actual ransomware. There was no real payment path. The objective was destruction.
The role split looks different here: eXp dev delivered EternalBlue and EternalRomance as interchangeable modules in the propagation engine. Mal Dev built the credential theft pipeline, the multi-method lateral movement, the boot sector manipulation, and the anti-forensics a coherent coordinated system with multiple redundant spread mechanisms.
# Illustrative only destructive operations omitted
def payload_entry():
"""
Post-exploitation entry point called after EternalBlue,
after PsExec/WMI, or after the malicious M.E.Doc update.
Delivery method is irrelevant here. This is pure Mal Dev territory.
"""
disable_recovery_and_snapshots()
escalate_privileges_if_needed()
if CONFIG.mode == "ransomware":
run_ransomware()
elif CONFIG.mode == "wiper":
run_wiper()
def run_ransomware():
for path in enumerate_target_files():
try:
data = read_file(path)
encrypted = encrypt_data_with_session_key(data)
write_file(path, encrypted)
mark_file_as_encrypted(path)
except IOError:
continue
show_ransom_note_ui()
def run_wiper():
# NotPetya's actual behavior: MBR overwrite + MFT corruption.
# No valid decryption path. Reboot = permanent brick.
overwrite_boot_record()
corrupt_file_system_metadata()
force_reboot()
payload_entry() is the handoff point where exploit success transfers to malware logic, regardless of how delivery happened. That moment is the boundary.
So Where Does the Line Actually Fall?
The boundary is the execution primitive handoff. The moment constrained, target-specific shellcode transfers control to an architecture-agnostic loader that’s the line. Everything before it is exploit engineering. Everything after is malware engineering.
The two 2017 cases make this concrete. EternalBlue was identical in both WannaCry and NotPetya. The same exploit, the same primitive, the same port 445 vector. The completely different operational outcomes a ransomware worm vs a disguised nation-state wiper came entirely from the malware architecture built around that primitive. Same breach, radically different occupation.
Well, Well, now you have the Answer to our very first Question that is Where does exploit development end and malware engineering begin?
What’s Next
There’s a lot left to dig into, and I’ll cover these in future tracks:
- Do modern C2 frameworks dissolve this boundary? Tools like Cobalt Strike blur the line between delivery and post-exploitation. Is the eXp dev/Mal Dev boundary still meaningful when one framework does both?
- How does EDR change exploit design constraints? As behavioral detection matures, does shellcode need to mimic “legitimate” traffic patterns just to survive the handoff cleanly?
- Is exploit development becoming obsolete with Living-off-the-Land? If adversaries get initial access via phishing + LOLBins, does the exploit engineering discipline move entirely up-market toward zero-days and nation-state tooling?
SHLORBT