When defenders made memory non-executable (DEP / W^X), injected shellcode stopped running — so attackers stopped injecting. Return- and jump-oriented programming build the payload out of code that's already there: tiny fragments ending in ret or jmp, chained into a program by a crafted stack. There's no sled (Lab 8), no new executable page (Lab 6), no foreign file (Lab 9) — nothing for a byte scanner to see. So the detection moves to the one thing the attack can't fake: control flow that doesn't obey the rules.
A normal program returns to the instruction right after a call — that's the contract of a function return. A ROP chain ignores that contract: it returns into the middle of existing functions, to short sequences that do one small thing and ret again, walking down a stack the attacker controls. The bytes are all legitimate, signed, on-disk code. What's illegitimate is the order they run in, and that disorder is measurable.
ret, compare. A ROP chain's returns were never pushed by a real call — so they diverge from the shadow copy. This is now hardware (Intel CET).A captured return trace from a crashing process. Verify it: each return is checked for call-preceded provenance and against the shadow stack, and the stack pointer is checked against the thread's real stack bounds. The genuine frames pass; the chain lights up.
Software ROP detection was noisy and slow, so control-flow integrity moved down into the hardware and the loader. You don't hand-roll this in a SOC — you verify it's on, and you alert when a process trips it:
# the enforcement layers, newest first Intel CET shadow stack (SS) + indirect-branch tracking (IBT) ; ret/jmp targets enforced in HW ARM PAC / BTI pointer authentication + branch-target identification ; Apple silicon MS CFG / XFG control-flow guard — valid indirect-call targets only OS ACG + stack-pivot checks ; arbitrary-code-guard, EMET/WDEG heritage # the SOC job: confirm it's enabled, and treat a CFI fault as an exploit signal a CET #CP (control-protection) fault -> ROP/JOP attempt, not a normal crash
jmp, driven by a dispatcher, so there's no ret to check against a return-address stack. That's why modern CFI needs indirect-branch tracking (IBT/BTI), not just a shadow stack — defeating one class doesn't defeat the family.longjmp, exception unwinding, and JIT engines all produce returns or jumps that look irregular. Naive provenance checks false-positive on them — which is exactly why the durable version is hardware-assisted and exception-aware.VirtualProtect chain) and then jump to a conventional payload — which lands you right back in Lab 6's territory. Catch it early (the pivot) or catch the aftermath (the RWX page); best, catch both.Next: Lab 11 — macOS persistence: Lab 1's story on your own platform, LaunchAgents to TCC, osquery-driven.