How CAPEv2 Breakpoints (bp0-bp3) Enable Dynamic Unpacking: A Technical Guide

CAPEv2 breakpoints utilize hardware debug registers (DR0-DR3) to set up to four execution breakpoints that can bypass anti-debug tricks, capture instruction traces, and automatically dump unpacked payloads without manual modification of malware samples.

The kevoreilly/capev2 sandbox provides a powerful hardware-assisted debugging mechanism through its bp0 to bp3 options, allowing analysts to perform dynamic unpacking on protected binaries. By leveraging these CAPEv2 breakpoints in combination with actions like skip, goto, and step2oep, you can neutralize packer stubs and capture the original entry point execution in an automated pipeline.

Understanding CAPEv2 Breakpoint Options (bp0-bp3)

CAPEv2 exposes four hardware breakpoint slots (bp0, bp1, bp2, bp3) that map directly to the x86 debug registers DR0 through DR3. These breakpoints are resolved at runtime and can target various address formats.

Supported Address Formats for Hardware Breakpoints

Each CAPEv2 breakpoint accepts one of the following formats:

  • RVA (Relative Virtual Address): bp0=0x1234 – resolved against the image base
  • VA (Virtual Address): bp0=0x7ff60000 – used directly without translation
  • Module Export: bp0=kernel32.dll::CreateFileW – resolved to the function address
  • Special Token ep: bp0=ep – entry point of the main module or DLL entry point

Dynamic Base Selection Mechanisms

When using RVA format, CAPEv2 determines the image base through three mechanisms:

  1. base-on-api: Waits for a specific API call (e.g., base-on-api=NtReadFile), extracts the module base from the call stack, and applies subsequent RVA offsets to that base
  2. ep token: Watches module load events, calculates the entry point address from the image base plus entry point RVA
  3. Default fallback: Assumes the first loaded module (main.exe) if no base information is supplied

The base-on-api method is particularly effective for unpackers that relocate themselves or load additional modules after initial execution.

Trace Configuration with depth and count

After a CAPEv2 breakpoint triggers, you can control the instruction trace behavior:

  • depth: Determines call handling
    • depth=0 (default): Step over calls
    • depth>0: Step into functions to specified recursion depth
  • count: Limits the number of traced instructions (default: 128)

Example configuration for capturing 500 instructions at the unpacked entry point without entering subroutines:

bp3=ep,depth=0,count=500

Breakpoint Actions for Anti-Debug Bypass and Execution Control

CAPEv2 breakpoints support programmable actions that execute when the breakpoint hits. These actions, specified as action0 through action3, enable automated unpacking without manual intervention.

Action Syntax Effect
Skip action0=skip Skips the current instruction (registers unchanged, execution continues at next instruction)
Return action0=ret Forces return from current function by replacing EIP/RIP with the stack's return address
Goto API action0=goto:ntdll::NtAllocateVirtualMemory Patches execution to jump to specified API, bypassing anti-debug checks
Step to OEP action0=step2oep Traces execution until reaching the original entry point (used by UPX rule)
Set Zero Flag action0=SetZeroFlag Manipulates CPU flags to alter conditional jump outcomes

Action strings are parsed by the monitor component in lib/cuckoo/core/monitor.py, which coordinates with the debugger to apply the specified behavior.

Practical Workflow: Using CAPEv2 Breakpoints for Dynamic Unpacking

Implementing an effective unpacking strategy with CAPEv2 breakpoints follows a systematic workflow that combines breakpoint placement, anti-debug neutralization, and execution tracing.

  1. Locate the unpacker stub using existing YARA rules (e.g., Guloader.yar, UPX.yar) or manual analysis in a disassembler
  2. Create initial breakpoints on stub addresses using bp0 with RVA or $ macro references (e.g., bp0=$trap0)
  3. Attach neutralizing actions such as action0=skip or action0=goto:ntdll::NtAllocateVirtualMemory to bypass anti-debug checks
  4. Set entry point tracing with bp3=ep to capture execution at the unpacked payload's entry point
  5. Configure trace parameters using depth=0 (step-over) and count=500 (instruction limit) to capture sufficient unpacked behavior
  6. Submit the sample with the combined cape_options string via the Web UI or API
  7. Inspect results in the Debugger tab or debug.log to analyze the unpacked code execution

Real-World Example: Unpacking UPX Binaries

The built-in analyzer/windows/data/yara/UPX.yar rule demonstrates production-grade unpacking with CAPEv2 breakpoints:

cape_options = "bp0=$upx32*,bp0=$upx64*,hc0=1,action0=step2oep,imprec=1"

This configuration:

  • Sets bp0 on the UPX stub entry (wildcarded for 32/64-bit)
  • Uses action0=step2oep to automatically trace from the stub to the original entry point
  • Enables import reconstruction with imprec=1

To capture the unpacked execution trace, extend the options:

cape_options = "bp0=$upx32*,bp0=$upx64*,hc0=1,action0=step2oep,imprec=1,bp1=ep,depth=0,count=500"

Now the sandbox bypasses the UPX packer, reconstructs imports, and records 500 instructions of the unpacked payload's execution.

Key Source Files and Implementation Details

Understanding the implementation of CAPEv2 breakpoints requires familiarity with these core components:

File Purpose
docs/book/src/usage/monitor.rst Comprehensive documentation of bp0-bp3 syntax, ep token, base-on-api, and trace options
[lib/cuckoo/core/monitor.py](https://github.com/kevoreilly/capev2/blob/master/lib/cuckoo/core/monitor.py) Core implementation parsing cape_options, resolving breakpoints, setting hardware registers (DR0-DR3), and coordinating actions
[lib/cuckoo/core/trace.py](https://github.com/kevoreilly/capev2/blob/master/lib/cuckoo/core/trace.py) Handles single-step tracing after breakpoint hits, implementing depth (step-into vs step-over) and count logic
analyzer/windows/data/yara/Guloader.yar Production example of complex breakpoint chains with multiple bp0 assignments and action0=skip for anti-debug bypass
analyzer/windows/data/yara/UPX.yar Demonstrates action0=step2oep for automated original entry point detection in packed binaries

Summary

CAPEv2 breakpoints provide hardware-assisted debugging capabilities that enable automated dynamic unpacking of protected malware samples. Key takeaways include:

  • Four hardware slots (bp0-bp3) map to x86 debug registers DR0-DR3, supporting RVA, VA, module exports, and the special ep token
  • Dynamic base resolution via base-on-api ensures breakpoints hit correctly even when unpackers relocate code or load additional modules
  • Programmable actions (skip, ret, goto, step2oep) neutralize anti-debug checks and automate navigation to unpacked payloads
  • Configurable tracing with depth and count parameters captures precise instruction sequences at the original entry point
  • YARA integration allows persistent breakpoint configurations in rules like Guloader.yar and UPX.yar for repeatable analysis

Frequently Asked Questions

How do I set a CAPEv2 breakpoint on a specific API call rather than a raw address?

Use the module export syntax in your cape_options string. For example, bp0=kernel32.dll::CreateFileW tells the debugger to resolve and break on the address of CreateFileW at runtime. This is particularly useful when targeting unpacked code that dynamically imports APIs.

What is the difference between using bp0=ep and action0=step2oep for unpacking?

bp0=ep sets a hardware breakpoint on the entry point address of the main module, which works well if you know where the unpacked payload starts. action0=step2oep is an active tracing action used in rules like UPX.yar that single-steps through the unpacker stub until it detects the transition to the original entry point, automatically handling cases where the OEP is obfuscated or calculated at runtime.

Why does my breakpoint miss when analyzing a packed sample that relocates itself?

Packed samples often unpack to a different memory location than their initial load address. If you specify an RVA without a base address, the debugger defaults to the first loaded module (main.exe), which may be incorrect. Use base-on-api=ApiName to dynamically capture the correct image base from the call stack when a specific API is invoked, or use the ep token which resolves the entry point after the module loads.

Can I chain multiple actions to a single breakpoint?

Yes, CAPEv2 supports chaining multiple actions to a single breakpoint slot. The action string is parsed by lib/cuckoo/core/monitor.py, allowing combinations like action0=skip,count=0 or more complex sequences. However, remember that all four breakpoint slots share the same hardware registers (DR0-DR3), so if you need step-over tracing, one slot must remain free for the debugger to track return addresses.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →