Skip to main content
Angrop includes a sophisticated optimization system that discovers advanced gadget combinations and enables complex ROP chain generation. This guide explains how optimization works and when to use it.

What is Optimization?

After finding gadgets, angrop can run an optimization phase that:
  1. Discovers multi-gadget capabilities - Combines simple gadgets to create complex operations
  2. Normalizes non-self-contained gadgets - Makes advanced gadgets usable
  3. Builds register move graphs - Finds efficient paths to move data between registers
  4. Enables harder chains - Makes previously impossible chains possible
Optimization is enabled by default when calling find_gadgets(optimize=True). For large binaries, optimization can take significant time but dramatically improves chain generation success rates.

The optimize Parameter

The optimize parameter controls whether optimization runs after gadget finding.

In find_gadgets()

In find_gadgets_single_threaded()

In load_gadgets()

Manual Optimization

You can run optimization separately for better control:

Method Signature

Parameters:
  • processes: Number of processes to use for parallel optimization (default: 4)

Real-World Example: Linux Kernel

From angrop’s kernel test suite (examples/linux_escape_chain/solve.py):
For large binaries (especially kernels), separating optimization from gadget finding provides better progress visibility and allows you to cache gadgets without optimization, then optimize as needed.

What Happens During Optimization

Angrop runs several optimization passes:

1. Register Mover Optimization

From the source code (reg_mover.py:302-343), angrop:
1

Build register move graph

Creates a directed graph where:
  • Nodes are registers
  • Edges are possible register moves
  • Edge weights are gadget efficiency (stack change, etc.)
2

Normalize complex gadgets

Converts non-self-contained gadgets into usable register moves:
3

Find push/pop move chains

Discovers register moves via push/pop sequences:

2. Register Setter Optimization

From the source code (reg_setter.py:432-448), angrop:
1

Optimize with register moves

Uses the register move graph to discover new ways to set registers:
2

Optimize with complex gadgets

Normalizes gadgets that:
  • Require setup (non-self-contained)
  • Have symbolic memory accesses
  • Use conditional branches
Makes them usable by building setup chains automatically.

3. Graph Reduction

From the source code (reg_setter.py:451-473), angrop:
  • Limits gadgets per edge to top 5 (by efficiency)
  • Builds a “giga graph” for constraint solving
  • Optimizes gadget selection for minimal overhead

Benefits of Optimization

Before Optimization

After Optimization

Performance Characteristics

Small Binaries (< 1MB)

Medium Binaries (1-10MB)

Large Binaries (> 10MB)

Optimization time is proportional to:
  • Number of gadgets found
  • Number of registers in architecture
  • Complexity of gadget relationships
  • Number of CPU cores available

Optimization Strategies

Strategy 1: Always Optimize for Production

Why: Maximizes chain generation success rate

Strategy 2: Skip for Quick Testing

Why: Faster iteration during development

Strategy 3: Separate Phases for Large Binaries

Why: Better control and caching for slow gadget finding

Strategy 4: Incremental Optimization

Why: Only pay optimization cost when necessary

Advanced: Understanding Optimization Internals

Normalization Process

From the source code (builder.py), “normalization” means:
  1. Identify dependencies - What registers/memory does gadget need?
  2. Build setup chain - Generate chain to satisfy dependencies
  3. Combine gadget with setup - Create self-contained “RopBlock”
  4. Cache result - Reuse normalized gadget in future chains
Example:

Register Move Graph

From the source code (reg_mover.py:345-361):

Constraint-Based Gadget Selection

Angrop uses constraint solving to find optimal gadget combinations:

Optimization and Badbytes

Optimization respects badbyte restrictions:

Disabling Optimization for Speed

When you don’t need complex chains:

Troubleshooting Optimization

Long Optimization Times

To speed up optimization:
  • Use more CPU cores (processes=cpu_count())
  • Enable fast_mode=True to find fewer gadgets
  • Set stricter badbytes to eliminate gadgets early
  • Cache optimized gadgets for reuse

Optimization Failures

Optimization can fail to run in some cases:

Best Practices

  1. Always optimize for production exploits - Maximizes success rate
  2. Cache gadgets with optimization - Save time on repeated use
  3. Use parallel optimization - Set processes=cpu_count()
  4. Separate phases for large binaries - Better progress tracking
  5. Profile your workflow - Measure time spent in each phase
  6. Skip optimization during development - Faster iteration

Measuring Optimization Impact

Next Steps