Kuboid
Open Luck·Kuboid.in
Black Hat2024
Open in YouTube ↗

Bytecode Jiu-Jitsu: Choking Interpreters to Force Execution of Malicious Bytecode

Black Hat1,101 views38:36about 1 year ago

This talk introduces 'Bytecode Jiu-Jitsu,' a novel code injection technique that bypasses traditional security controls by injecting malicious bytecode directly into an interpreter process rather than using native code. The research demonstrates how to identify and manipulate internal interpreter data structures, such as the Virtual Program Counter (VPC) and symbol tables, to force the execution of arbitrary scripts. This approach effectively evades detection by EDR, antivirus, and memory forensics tools that typically monitor for native code execution or suspicious API calls. The presenters provide a proof-of-concept tool to demonstrate the technique's viability against interpreters like Python, Lua, and VBScript.

Bypassing EDR and Memory Forensics with Bytecode Jiu-Jitsu

TLDR: Researchers have developed a novel code injection technique called Bytecode Jiu-Jitsu that targets interpreter processes like Python, Lua, and VBScript instead of native code. By manipulating internal data structures like the Virtual Program Counter and symbol tables, attackers can force the execution of arbitrary scripts while remaining invisible to traditional EDR and memory forensics tools. This technique highlights a critical blind spot in current security monitoring, which often focuses heavily on native API calls and executable memory permissions.

Traditional code injection techniques like Process Hollowing or Process Herpaderping rely on manipulating native process memory and hijacking execution flow at the OS level. These methods are noisy. They trigger alerts in modern EDR solutions because they involve suspicious API calls like VirtualAllocEx or WriteProcessMemory followed by thread creation in a remote process. Security teams have spent years tuning their detection logic to catch these specific patterns. Bytecode Jiu-Jitsu changes the game by moving the attack surface from the OS layer to the interpreter layer.

The Mechanics of Bytecode Injection

Instead of injecting shellcode or a malicious DLL, this technique targets the interpreter itself. Every interpreter, whether it is Python, Lua, or the Windows Script Host, follows a predictable execution cycle. It fetches instructions, decodes them, and executes them using a Virtual Machine (VM) loop. This VM loop relies on internal data structures to track state: the Virtual Program Counter (VPC), the virtual stack, and the symbol table.

The research demonstrates that if an attacker can locate these structures in memory, they can manipulate them to redirect the interpreter's execution flow. The attack follows two distinct phases: a preparation phase and an attack phase. During preparation, the attacker crafts a malicious script and a target script. They then use dynamic analysis to observe how the interpreter accesses memory during the execution of these scripts. By tracking pointer dereferences and memory access patterns, the attacker can map out the interpreter's internal memory layout without needing the source code.

Once the memory map is established, the attack phase begins. The injector process infiltrates the victim's environment and scans the interpreter's memory to locate the VPC and the bytecode cache by signature. After identifying these, the injector overwrites the existing bytecode with the malicious payload and updates the VPC to point to the new entry point. Because the interpreter is already running and the memory is already marked as executable by the process itself, no new suspicious memory allocations are required. The interpreter simply continues its loop, unaware that its instruction stream has been subverted.

Technical Implementation and Detection Evasion

The power of this technique lies in its stealth. Most EDR and memory forensics tools, such as those using Volatility, are designed to look for anomalies in native process memory, such as RWX sections or hooked functions. Bytecode Jiu-Jitsu operates entirely within the memory space already allocated to the interpreter.

To implement this, the researchers used a custom injector that performs memory-only operations. The following logic outlines how an attacker might identify the necessary structures:

# Conceptual logic for identifying the VPC and symbol table
def find_interpreter_structures(process_handle):
    # Enumerate heap and stack memory
    memory_regions = get_all_memory_regions(process_handle)
    
    # Search for specific signatures or patterns 
    # associated with the interpreter's VM loop
    vpc_address = scan_for_vpc(memory_regions)
    symbol_table = scan_for_symbol_table(memory_regions)
    
    return vpc_address, symbol_table

By avoiding the standard Windows API calls that trigger EDR alerts, the attack remains invisible to 75 different antivirus products tested by the researchers. Even memory forensics tools failed to flag the injected bytecode because the modification occurred within the legitimate memory space of the interpreter, which the tools often treat as trusted.

Real-World Applicability for Pentesters

For a pentester or a red teamer, this technique is a powerful addition to your post-exploitation toolkit. If you have already gained a foothold on a system and need to execute code without triggering an EDR alert, targeting an active interpreter process is a viable path. Many enterprise environments rely heavily on automation scripts written in VBScript or Python. These processes are often long-running and have high privileges, making them ideal targets for injection.

During an engagement, you should look for running instances of cscript.exe, python.exe, or other scripting engines. Instead of trying to spawn a new process, which is easily logged, you can attach to an existing one and perform the memory manipulation described. This is particularly effective in environments where EDR is configured to monitor for process creation but is less aggressive about monitoring the internal state of scripting engines.

Defensive Considerations

Defending against this is difficult because it exploits the fundamental design of interpreters. However, blue teams can start by monitoring for unusual memory access patterns within interpreter processes. While the injection itself is stealthy, the process of scanning memory to find the VPC and symbol table can be detected if you have high-fidelity telemetry on memory read operations.

Furthermore, organizations should enforce strict execution policies for scripts. If a script does not need to run, it should be disabled. For those that must run, consider using hardened interpreter versions that implement Control Flow Guard or similar protections to prevent the redirection of execution flow.

Ultimately, this research serves as a reminder that as we harden the OS layer, attackers will inevitably move down the stack. The interpreter is now a primary target, and our detection strategies must evolve to include deep inspection of the processes we trust to run our code. If you are interested in testing this, the researchers have released their proof-of-concept tools for further investigation.

Talk Type
research presentation
Difficulty
expert
Has Demo Has Code Tool Released


Black Hat USA 2024

121 talks · 2024
Browse conference →
Premium Security Audit

We break your app before they do.

Professional penetration testing and vulnerability assessments by the Kuboid Secure Layer team. Securing your infrastructure at every layer.

Get in Touch
Official Security Partner
kuboid.in