Skip to main content

Overview

angrop’s chain builder automatically assembles ROP gadgets into functional exploit chains. Rather than manually selecting and linking gadgets, you specify high-level goals (“set rax to 0x1234”, “write data to memory”, “call execve”) and angrop finds the optimal gadget sequence.

Architecture

The chain builder consists of specialized modules, each responsible for different types of operations:

Core Modules

From chain_builder/__init__.py:42-53:
Each module:
  1. Bootstraps by analyzing available gadgets and building internal data structures
  2. Optimizes by finding better gadget combinations
  3. Generates chains on demand using graph search and constraint solving

The RegSetter Module

The most fundamental module. Sets registers to arbitrary values using graph search.

Graph Search Algorithm

From reg_setter.py:474-612, RegSetter builds a directed graph where:
  • Nodes: States representing which registers have been set to target values
  • Edges: Gadgets that transition between states
Example: To set rax=0x1234 and rbx=0x5678:
Edges are gadgets like:
  • pop rax; ret - Sets rax
  • pop rbx; pop r12; ret - Sets rbx (and r12)
  • pop rax; pop rbx; ret - Sets both at once

Register Setting Strategy

From reg_setter.py:749-777:
Hard registers are handled specially:
  • Registers containing badbytes: Use arithmetic gadgets to construct the value
  • Unpoppable registers: Use register moves or memory reads

Concrete Value Crafting

When a register can’t be popped directly, angrop crafts the value using arithmetic: From reg_setter.py:699-725:
Example: To set rax = 0x41424344 (contains null bytes):
  1. pop rax; ret with value 0x41424300
  2. add rax, 0x44; ret
  3. Result: rax = 0x41424344 (no null bytes in payload)

The MemWriter Module

Writes arbitrary data to memory addresses.

Basic Memory Writing

From mem_writer.py:464-492:
Gadget selection criteria (from mem_writer.py:358-375):
  1. Self-contained
  2. Exactly one memory write
  3. Address and data independently controllable
  4. No symbolic memory reads

Handling Badbytes

When data contains badbytes, angrop uses memory modification gadgets: From mem_writer.py:631-661:
Example: Writing \x00\x41\x42\x43 with \x00 as badbyte:
  1. Write \x01\x41\x42\x43 (no badbytes)
  2. Execute xor dword [rax], 0x00000001
  3. Result: \x00\x41\x42\x43

The MemChanger Module

Modifies memory in-place using arithmetic/logical operations. Supported operations:
  • mem_add(addr, value) - [addr] += value
  • mem_xor(addr, value) - [addr] ^= value
  • mem_or(addr, value) - [addr] |= value
  • mem_and(addr, value) - [addr] &= value
These are used both for crafting values and for direct exploitation.

The FuncCaller Module

Invokes functions with specified arguments, handling calling conventions. From the ChainBuilder API:

The SysCaller Module

Builds syscall invocation chains. From chain_builder/__init__.py:138-152:
Special case: execve chains

Chain Composition

Chains are built incrementally and can be combined:

RopChain Objects

From usage:

RopBlock Normalization

Complex gadgets are wrapped in RopBlocks that encapsulate:
  • The gadget sequence
  • Required stack values
  • Symbolic constraints
  • Register preservation requirements
From builder.py:879-956:

Optimization

The chain builder optimizes iteratively: From chain_builder/__init__.py:232-244:
Optimization strategies:
  1. Register moves: Find ways to set hard registers by moving from easy ones
  2. Gadget normalization: Make non-self-contained gadgets usable
  3. Chain shortening: Replace long chains with shorter equivalent ones

Constraint Solving

The chain builder uses symbolic execution and constraint solving throughout:

Building Reg Setting Chains

From builder.py:362-556:

Rebalancing Constraints

When gadgets transform values, angrop “rebalances” constraints: From builder.py:245-359:
This allows using gadgets like pop rax; add rax, 0x10; ret transparently.

Example: Building a Complete Chain

The chain builder handles all the complexity:
  • Gadget selection and ordering
  • Register allocation and preservation
  • Constraint solving for stack values
  • Calling convention adherence
  • Badbyte avoidance