← Back to blog

OIS-ROP: Executing Code That Doesn't Exist

No shellcode. No injected code. Just addresses pointing into Microsoft's own signed binaries.

Every byte of the "malware" is Microsoft's code. I just control which bytes execute and in what order.

What if the malicious code never existed?

Not hidden. Not obfuscated. Not encrypted. Simply never written.

The payload is a list of addresses. Every address points to instructions already present in legitimately signed Windows DLLs: kernel32.dll, ntdll.dll, user32.dll. The CPU executes Microsoft's code, in Microsoft's memory, with Microsoft's signatures. I'm just the orchestra conductor.


The Problem with Traditional Shellcode

EDR and security tools have gotten good at detecting malicious code. They look for:

The common thread: they're looking for your code. Code you wrote. Code you injected. Code that exists.

What if there was nothing to find?


The OIS Foundation

OIS (Offset Instruction Set) starts from a simple observation: every byte value (0x00 through 0xFF) exists somewhere in the Windows system DLLs. These files are massive. kernel32.dll alone contains hundreds of thousands of bytes.

Instead of storing a payload, you store coordinates:

Traditional:  [0x48, 0x31, 0xC0, 0xC3]  <-- actual shellcode bytes

OIS:          [k32:0x1055, k32:0x12D7, k32:0x8F2A, k32:0x3B91]
               ^-- offsets into kernel32.dll that contain those bytes


Finding a byte:
+------------------------------------------------------------------+
|  kernel32.dll (Microsoft-signed, 800KB+)                         |
+------------------------------------------------------------------+
|  ...                                                             |
|  0x1055: 0x48  <-- Need 0x48? It's at offset 0x1055              |
|  ...                                                             |
|  0x12D7: 0x31  <-- Need 0x31? It's at offset 0x12D7              |
|  ...                                                             |
|  0x8F2A: 0xC0  <-- Every byte exists somewhere                   |
|  ...                                                             |
+------------------------------------------------------------------+
               The DLL is the dictionary. Just cite page numbers.

To reconstruct, you read each offset from the signed DLL. The bytes were always there. You just indexed them.

This was the first stage: using signed binaries as a byte lookup table. The payload exists nowhere until reconstruction. But there's still a problem: eventually you have to put those bytes somewhere executable. And that's where EDR catches you. Private memory allocation with executable permissions.


OIS-ROP: Executing in Place

Return-Oriented Programming isn't new. It's been used in exploit development for years to bypass DEP. But OIS-ROP applies it differently: not to exploit a vulnerability, but as the primary execution model.

The key insight: I don't need to reconstruct shellcode at all. I can execute directly within the signed DLL's memory space.

A ROP chain is a sequence of addresses, each pointing to a "gadget," a small instruction sequence ending in RET. When RET executes, it pops the next address off the stack and jumps there. Chain enough gadgets together, you can perform arbitrary computation.

+-----------------------------------+     +-----------------------------------+
|  Image Memory (kernel32.dll)      |     |  Private Memory (Our Stack)       |
|  [Microsoft-signed, executable]   |     |  [Data only, not executable]      |
+-----------------------------------+     +-----------------------------------+
|                                   |     |                                   |
|  0x7FF81234: pop rcx              |<----+-- 0x7FF81234  <-- RSP starts here |
|              ret  ----------------+-----+-->                                |
|                                   |     |   0x41414141  (value for RCX)     |
|  0x7FF85678: pop rdx              |<----+-- 0x7FF85678                       |
|              ret  ----------------+-----+-->                                |
|                                   |     |   0x00000001  (value for RDX)     |
|  0x7FF8ABCD: jmp WinExec          |<----+-- 0x7FF8ABCD                       |
|                                   |     |                                   |
+-----------------------------------+     +-----------------------------------+

         CPU bounces here                        We control this
        (trusted code runs)                      (just addresses)

The "stack" I control contains only addresses and data. Never executable code. All execution happens within kernel32's image-backed memory. From the perspective of the EDR:

Attribute Traditional Shellcode OIS-ROP
Code location Private memory (suspicious) Image memory (trusted)
Memory protection RWX or RW→RX transition No changes. Image was always RX
Code origin Unknown/injected Microsoft-signed binary
What I control Code content Stack data only

Composing the Score

To execute WinExec("calc", 1) via OIS-ROP:

# Windows x64 calling convention:
# RCX = first argument (lpCmdLine)
# RDX = second argument (uCmdShow)

# Find gadgets in kernel32.dll:
pop_rcx = find_gadget(kernel32, [0x59, 0xC3])  # pop rcx; ret
pop_rdx = find_gadget(kernel32, [0x5A, 0xC3])  # pop rdx; ret
winexec = get_proc_address(kernel32, "WinExec")

# The chain (just addresses and values):
chain = [
    pop_rcx,           # Gadget: pop rcx; ret
    calc_string_addr,  # Value loaded into RCX
    pop_rdx,           # Gadget: pop rdx; ret
    0x1,               # Value loaded into RDX (SW_SHOWNORMAL)
    winexec,           # Address of WinExec
]

Every address in this chain points to code inside kernel32.dll. When I pivot RSP to the chain and trigger the first RET, the CPU starts bouncing through Microsoft's code, loading values into registers, until it hits WinExec with the arguments set up.

The "shellcode" never exists. Only addresses into signed code.

The Stack Pivot

The remaining challenge: how do you get RSP to point to the chain? Several options exist:

Once RSP points to the chain, you're not executing injected code. You're executing a very specific sequence of legitimate instructions that happen to do what you want.

This is also the moment of highest risk for the operator. The ROP chain itself is invisible. Just data. But the act of pivoting the stack pointer to an unexpected location is a detectable event. It's the conductor raising the baton.


The Cost of Precision

OIS-ROP is not a blunt instrument. It's a scalpel, and scalpels require precision.

Every gadget address is version-specific. If Microsoft updates ntdll.dll and a gadget moves by four bytes, the entire chain breaks. The orchestra plays the wrong notes. The process crashes.

This means:

Brittleness is the tradeoff for stealth. You're not writing portable shellcode. You're composing a symphony for a specific orchestra on a specific night.


Implications for Defense

What does this mean for detection?

Signature-based detection fails completely. There are no malicious bytes to signature. The payload is addresses into trusted code.

Behavioral detection gets harder. The execution happens within image-backed memory. Every instruction executed is part of a Microsoft-signed binary. The behavior (calling WinExec) is normal. It's the orchestration that's malicious.

Memory forensics finds nothing recognizable. No injected code. No shellcode patterns. Just a list of addresses that looks like stack data.

The defensive response has to shift from "look for malicious code" to "look for malicious orchestration." What does that look like in practice?

These are harder problems than pattern matching. They require understanding execution flow, not just scanning memory.


The Evolution of the Invisible

OIS-ROP sits at the end of a trajectory that's been developing for nearly two decades.

2007: Return-Oriented Programming. Hovav Shacham formalized ROP in his seminal paper "The Geometry of Innocent Flesh on the Bone." The insight: you don't need to inject code if you can chain together existing instruction sequences. But traditional ROP was a means to an end, a stage-one technique to disable DEP and execute conventional shellcode. The malicious payload still existed; ROP just opened the door.

2013: JIT-ROP. Snow et al. demonstrated that gadgets could be discovered dynamically at runtime, defeating fine-grained ASLR. The memory space became a dictionary to be harvested on-the-fly. But again, the goal was to enable execution of a separate payload.

2016: AtomBombing. Liberman showed that Windows atom tables could store data across process boundaries, with ROP triggering execution. The "where" of storage became creative. But the payload still had to be delivered somewhere.

2021: Rope. D'Elia et al. took ROP further, distributing entire payloads as ROP chains across multiple processes, coordinated via transacted NTFS files. Sophisticated orchestration. But they still compiled C code into ROP chains. The malware logic existed; it was just encoded differently.

OIS-ROP: The Endpoint. I ask a different question: what if there's no payload to encode? No shellcode to translate. No C code to compile. Just a list of coordinates into signed binaries that, when interpreted as a ROP chain, produces the desired behavior. The malicious semantics are never written down. They emerge from orchestration alone.

The shift: Previous techniques asked "how do I hide my payload?" OIS-ROP asks "what if there is no payload?"


The Continuum

OIS-ROP fits into a broader research program:

Each layer asks the same question: what assumptions does security tooling make, and what happens when you violate them?

Format destruction violates the assumption that files have identifiable structure. OIS violates the assumption that malicious bytes must be present somewhere. OIS-ROP violates the assumption that malicious execution requires malicious code.


The Interpreter Defines Meaning

At the heart of all this work is a single principle: meaning is not inherent in data. The interpreter creates it.

A byte sequence isn't "malicious" until something executes it that way. A file isn't "a PE" until a loader treats it as one. And code doesn't have to exist to be executed. You just need to convince the CPU to execute the right sequence of existing instructions.

Security tools look for malicious content. OIS-ROP demonstrates that content is irrelevant. What matters is orchestration, and orchestration leaves very different traces than injection.

The code I execute belongs to Microsoft. I just wrote the sheet music.

Chris Aziz, Bombadil Systems

OIS-ROP is part of the Beauty framework. For authorized security testing and research only.