Recommended resources
The freely available resources this lesson will direct you to.
- Modern Bytecode Instrumentation with ByteBuddy – Rafael Winterhalter | The Marco Showwww.youtube.com
- The definitive guide to Java agents by Rafael Winterhalterwww.youtube.com
- Smashing Runtime Application Self-Protection (RASP) - Deep Kondahwww.deep-kondah.com
- [PDF] Low-level RASP: Protecting Applications Implemented in ... - Black Hati.blackhat.com
Hello! Welcome to the next phase of our journey into architecting security products.
In the previous module, we designed a complete Cloud Security Posture Management (CSPM) system. We focused on securing the cloud control plane—the static configuration of your infrastructure—culminating in a graph-based data store to uncover complex attack paths. That was about finding vulnerabilities in the blueprints of your house.
Now, we shift our focus from the blueprints to what happens inside the house while it's occupied. What if an attacker bypasses all your locks and alarms and is already interacting with your application? This brings us to the domain of runtime security.
This lesson is the first in our module on Architecting Runtime Application Self-Protection (RASP). Our learning outcome is to: Architect RASP instrumentation approaches, comparing bytecode modification, language-level hooks, and library interposition.
We will explore the fundamental techniques RASP products use to embed themselves into a running application. This is the core "how" that enables a RASP to see and stop attacks in real time, from the inside. Your background in software engineering will be valuable here, as we'll be discussing concepts close to the metal of language runtimes and operating systems.
1. What is RASP Instrumentation?
A RASP tool's power comes from its intimate, in-process view of the application. Unlike a WAF, which sees HTTP traffic from the outside, a RASP can see what the application does with that traffic. Does it pass a user-supplied string to a SQL query? Does it try to execute a system command?
To achieve this visibility, RASP must perform instrumentation: the process of modifying an application's executable code, either before or during runtime, to insert security sensors and controls.
Smashing Runtime Application Self-Protection (RASP)
The article 'Smashing Runtime Application Self-Protection (RASP)' provides a good introduction to the concept. It explains how RASP integrates with an application to gain context that external tools lack.
Please read the introduction and the sections 'Runtime Application Self-Protection (RASP)' and 'Instrumented-based RASP'. Focus on how RASP uses instrumentation to place 'sensors' or 'hooks' into the application's data flow.
As the article describes, these "hooks" allow the RASP agent to inspect function calls, parameters, and data flow at critical points—for example, right before the application executes a database query or starts a new process.
The architectural challenge lies in how to place these hooks. There are three primary approaches, each with significant design trade-offs.
2. Approach 1: Bytecode Modification
For applications running on managed runtimes like the Java Virtual Machine (JVM) or .NET's Common Language Runtime (CLR), the most common approach is modifying the intermediate language (IL) or bytecode. Let's focus on the JVM, as it provides a powerful and well-defined mechanism for this: Java Agents.
A Java Agent is a special JAR file that the JVM can load to instrument a running application. It uses the Instrumentation API to intercept classes as they are loaded and rewrite their bytecode on the fly.
This is the core of how many Application Performance Monitoring (APM) and RASP products for Java work. To understand this from first principles, there's no better source than the creator of ByteBuddy, a popular bytecode manipulation library.
The definitive guide to Java agents by Rafael Winterhalter
In this talk, 'The definitive guide to Java agents,' Rafael Winterhalter explains the fundamentals of Java agents and the Instrumentation API.
Please watch the following segments: What are Java Agents? (00:06 - 01:04) Agent Entry Points (premain and agentmain) (01:43 - 06:14) The Instrumentation API (07:26 - 10:19) Modifying Bytecode (10:19 - 12:45) \nFocus on understanding how an agent registers a ClassFileTransformer to get the raw byte array of a class before it's used by the JVM, and how this enables security tools to inject logic.
As the video explains, the process looks like this:
- Agent Loading: The RASP agent is loaded into the JVM using the
-javaagentcommand-line flag (premain) or attached to an already running process (agentmain). - Transformer Registration: The agent gets an
Instrumentationobject and registers a customClassFileTransformer. - Class Interception: Whenever the JVM's class loader loads a class (e.g.,
java.sql.PreparedStatement), it passes the class's bytecode (as abyte[]) to our transformer. - Bytecode Rewriting: Our transformer checks if this is a class we want to instrument. If so, it uses a library like ASM (low-level) or ByteBuddy (high-level) to parse the bytecode, insert new instructions (our "sensors"), and generate a new byte array.
- Return Modified Code: The transformer returns the modified bytecode to the JVM, which then uses it to define the class.
The application code is unchanged on disk, but its in-memory representation now contains our security hooks.
To make the process of writing these agents easier, libraries like ByteBuddy provide a high-level, Java-like API, abstracting away the complexity of direct bytecode manipulation.
Modern Bytecode Instrumentation with ByteBuddy – Rafael Winterhalter | The Marco Show
In this follow-up interview, Rafael Winterhalter further clarifies the role of ByteBuddy as an abstraction layer over low-level bytecode tools like ASM.
Please watch these two clips: What is Bytecode Instrumentation? (01:44 - 03:39) ByteBuddy vs. ASM (07:27 - 08:48) \nThis will help you understand the tooling landscape and why you wouldn't typically manipulate raw bytecode by hand when building a RASP.
Architectural Trade-offs of Bytecode Modification
- Strengths:
- High-fidelity context: The hooks operate within the application's context, with full access to typed objects, parameters, and local variables. You can distinguish between a string from an HTTP parameter and one from a configuration file.
- Granular control: You can instrument individual methods or even specific lines of code.
- Weaknesses:
- Language-specific: An agent for the JVM won't work for .NET, Python, or Node.js.
- Complex and fragile: The JVM is a complex machine. Incorrect bytecode can crash the application. Instrumentation can also interfere with JVM optimizations like JIT compilation, as mentioned in the first video (28:32 - 29:38).
- Bypassable: A sophisticated attacker with the ability to load their own Java Agent could potentially re-transform the RASP's instrumented classes to remove the security hooks.
3. Approach 2: Language-Level Hooks
For interpreted languages like Python, Ruby, and Node.js (JavaScript), direct bytecode manipulation is possible but often less common or standardized than in the JVM world. Instead, RASP tools often leverage hooking mechanisms provided by the language runtime itself.
This approach involves using features of the language to redefine or wrap existing functions and classes.
- Python: One could use
sys.settraceto hook into function calls (used by debuggers), or more commonly, engage in monkey-patching. For example, a RASP agent could, at startup, replace the standardos.systemfunction with its own wrapper:import os # Store the original function original_system = os.system def secure_system_wrapper(command): print(f"RASP: Intercepted call to os.system with command: {command}") if is_malicious(command): raise SecurityException("RASP: Blocked malicious command execution.") else: # Call the original function return original_system(command) # Overwrite the function in the os module os.system = secure_system_wrapper - Ruby: Ruby's open classes and
TracePointAPI provide powerful ways to intercept method calls and other events across the application. - Node.js: The dynamic nature of JavaScript allows for similar prototype chain manipulation and function wrapping to intercept calls to modules like
child_processorfs.
Architectural Trade-offs of Language-Level Hooks
- Strengths:
- Simpler implementation: Often easier and more idiomatic to the language than manipulating bytecode.
- Good context: Still operates within the application process with access to high-level language objects.
- Weaknesses:
- Inconsistent APIs: The hooking capabilities vary wildly between languages, making it difficult to build a unified RASP product.
- Potentially bypassable: An attacker might be able to call the original function if they can get a reference to it before the RASP agent patches it.
- Performance: Tracing APIs can introduce significant performance overhead if used indiscriminately.
4. Approach 3: Library Interposition (Native Hooking)
What if we moved the hook from the application layer down into the native code that the language runtime itself depends on? This is the idea behind library interposition.
Regardless of whether it's Java, Python, or Node.js, when an application wants to perform an OS-level action like opening a file or starting a process, it ultimately calls a function in a native shared library (e.g., libc.so on Linux, ntdll.dll on Windows). The language runtime is the bridge. For example, Java's ProcessBuilder.start() eventually calls a native function inside libjvm.so named Java_java_lang_UNIXProcess_forkAndExec.
This approach instruments the application at a much lower level.
[PDF] Low-level RASP: Protecting Applications Implemented in ...
The Black Hat presentation 'Low-level RASP' is an excellent resource that champions this very architecture. It argues for moving hooks from the high-level programming language (HPL) layer to the native C/C++ layer.
This is a slide deck, so it's a quick read. Please review the following concepts: Bypass Methodologies (Slide 5): Note how calling native methods via FFI can bypass traditional RASP. Multiple HPL Environments (Slide 7): Understand the challenge of diverse hook technologies. LL-RASP Design Goal (Slide 9): See the core idea: "Set hook points as lower as possible." Architectural Split (Slides 10-11): Grasp the design of a language-independent engine (librs_engine.so) and language-dependent extensions (librs_jdk.so, librs_py.so). Implementation Examples (Slides 13-17): Quickly scan the code snippets. You don't need to understand every line, but observe how they hook Java_xx_forkAndExec for Java, uv_spawn for Node.js, and system for Python. This shows the pattern in action.
The key architectural pattern proposed is a unified native engine. A RASP product can be built with a core, language-agnostic hooking engine (librs_engine.so in their example) that knows how to intercept function calls in any shared library. Then, you provide small, language-specific extensions that tell the engine which native functions to hook for that particular runtime (e.g., librs_jdk.so knows about Java_..._forkAndExec, while librs_py.so knows about PyEval_EvalFrameEx).
Architectural Trade-offs of Library Interposition
- Strengths:
- More robust to in-app bypass: An attacker writing Java or Python code cannot easily undo a hook placed in a native C library.
- Language-agnostic core: The main hooking logic can be reused across different languages, reducing development costs.
- Potential performance benefits: As the slides argue, some operations like capturing a stack trace can be faster when done from the native layer.
- Weaknesses:
- Loss of application context: When you intercept a call to
fork()inlibc, you only see low-level arguments like the executable path and environment variables. You've lost the rich context of which Java object or which Python function initiated the call, unless you do significant engineering work to reconstruct it (as the LL-RASP paper does). - High complexity and non-portability: This is deep systems programming. It's highly dependent on the operating system, CPU architecture, and the internal implementation of the language runtime, which can change between versions.
- Loss of application context: When you intercept a call to
5. Comparison and Architectural Summary
Choosing an instrumentation strategy is a fundamental design decision with no single "best" answer. The choice depends on what you are trying to protect and the trade-offs you are willing to make.
The image below provides a good summary of various implementation methods and their characteristics. Our three discussed approaches map roughly to:
- Bytecode Modification: A primary example of "Agent-Based" or "Bytecode Manipulation."
- Language-Level Hooks: Fits under "Code-Level Integration" or "API Wrapping."
- Library Interposition: A form of "Binary Instrumentation."

Here's a simplified comparison of the three approaches we've architected:
| Approach | Bytecode Modification | Language-Level Hooks | Library Interposition |
|---|---|---|---|
| Layer | Application IL/Bytecode | High-Level Language API | Native Shared Libraries (OS/Runtime) |
| Context | High. Full access to application objects and types. | High. Access to language objects. | Low. Sees OS-level primitives (file handles, process args). |
| Complexity | High. Requires deep knowledge of bytecode and runtime. | Medium. Often idiomatic to the language but can be tricky. | Very High. Deep systems programming, OS- and architecture-specific. |
| Portability | Language-specific (e.g., JVM, CLR). | Language-specific (e.g., Python, Ruby). | OS-specific (e.g., Linux, Windows) but core engine is reusable. |
| Robustness | Moderate. Can be bypassed by other agents. | Low to Moderate. Can be bypassed within the language. | High. Very difficult to bypass from application code. |
Conclusion
In this lesson, we have architected the three primary approaches to RASP instrumentation. We've seen how a RASP product can gain its all-important internal visibility by modifying application code at different layers of the stack.
Key Takeaways:
- Instrumentation is Key: RASP's effectiveness hinges on its ability to insert sensors and controls into a running application.
- Bytecode Modification is a powerful, high-context approach common in managed runtimes like the JVM, but it is complex and language-specific. It's the classic, deep-integration method.
- Language-Level Hooks offer a simpler, more idiomatic way to instrument dynamic languages like Python and Ruby, but the mechanisms vary and may be less robust.
- Library Interposition is a low-level, highly robust approach that hooks the native libraries an application depends on. It offers a path to a unified engine for multiple languages but trades high-level application context for resilience.
- It's All About Trade-offs: The choice of instrumentation strategy is a core architectural decision for any RASP product, balancing context, robustness, complexity, and portability.
Preview of the Next Lesson
We now understand the different ways we can architect the placement of our security hooks. But once a hook is in place, what should it do? In our next lesson, we will answer that question by designing the next component in our RASP architecture. The learning outcome will be to: Design a runtime context capture system to collect data like HTTP parameters and SQL queries via instrumented hooks.