Skip to main content
The solver plugin (SimSolver) is the primary interface for creating and manipulating symbolic variables and solving constraints in angr. It’s accessible via state.solver.

Overview

The solver plugin wraps the Claripy constraint solver and provides methods for:
  • Creating symbolic variables
  • Adding constraints
  • Evaluating expressions
  • Checking satisfiability
  • Finding min/max values

Creating Symbolic Variables

BVS

BVS(name, size, min=None, max=None, stride=None, uninitialized=False, 
    explicit_name=False, key=None, eternal=False, inspect=True, events=True, **kwargs)
Create a bit-vector symbol (symbolic variable).
name
str
The name of the symbolic variable.
size
int
The size in bits of the bitvector.
min
int
Minimum value (VSA only).
max
int
Maximum value (VSA only).
stride
int
Stride/step value (VSA only).
uninitialized
bool
default:"false"
Whether to mark this as an uninitialized value for analysis.
explicit_name
bool
default:"false"
If True, don’t append a unique identifier to the name.
key
tuple
Tracking key for variable registration (e.g., ('mem', 0x1000)).
eternal
bool
default:"false"
If True with a key, all states with the same ancestry retrieve the same symbol.
return
BV
A symbolic bitvector.
# Create 32-bit symbolic variable
x = state.solver.BVS('x', 32)

# Create with explicit name (no unique suffix)
y = state.solver.BVS('input', 64, explicit_name=True)

# Create tracked variable
buf = state.solver.BVS('buf', 256, key=('mem', 0x1000), eternal=True)

# VSA: Create with value constraints
port = state.solver.BVS('port', 16, min=1024, max=65535)

Unconstrained

Unconstrained(name, bits, uninitialized=True, inspect=True, events=True, 
              key=None, eternal=False, **kwargs)
Create an unconstrained symbol or concrete zero based on state options.
name
str
The name of the symbol.
bits
int
The size in bits.
return
BV
A symbolic bitvector if SYMBOLIC_INITIAL_VALUES is set, otherwise concrete 0.
# Creates symbolic if SYMBOLIC_INITIAL_VALUES is in state.options
val = state.solver.Unconstrained('uninit_var', 32)

Adding Constraints

add

add(*constraints)
Add constraints to the solver state.
constraints
BV
Boolean constraint expressions to add. Pass multiple constraints as separate arguments.
# Add single constraint
state.solver.add(x > 10)

# Add multiple constraints
state.solver.add(x > 10, x < 100, y == 42)

# Add complex constraint
state.solver.add(x + y == z * 2)
  • Constraints must be boolean expressions (comparisons, logical operations)
  • Adding conflicting constraints makes the state unsatisfiable
  • Constraints are permanent and affect all future solver operations
  • Use state.solver.satisfiable() to check if constraints are consistent

Evaluating Expressions

eval

eval(e, cast_to=None, **kwargs)
Get one possible solution for an expression.
e
BV
The expression to evaluate.
cast_to
type
Type to cast result to (int, bytes, etc.).
extra_constraints
tuple
Additional temporary constraints for this evaluation only.
return
int | bytes
One possible value satisfying the constraints.
# Evaluate to integer
value = state.solver.eval(x)
print(value)  # e.g., 42

# Evaluate to bytes
string = state.solver.eval(buf, cast_to=bytes)
print(string)  # e.g., b'hello'

# Evaluate with extra constraints
value = state.solver.eval(x, extra_constraints=(x > 100,))

eval_one

eval_one(e, cast_to=None, **kwargs)
Get the only possible solution. Raises an error if there are zero or multiple solutions.
return
int | bytes
The unique solution.
state.solver.add(x == 42)
value = state.solver.eval_one(x)  # Returns 42

# With default on failure
value = state.solver.eval_one(x, default=0)  # Returns 0 if multiple solutions

eval_upto

eval_upto(e, n, cast_to=None, **kwargs)
Get up to n possible solutions.
n
int
Maximum number of solutions to return.
return
list
List of solutions (length 1 to n).
# Get up to 10 solutions
values = state.solver.eval_upto(x, 10)
print(values)  # e.g., [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Check if variable has limited solutions
if len(state.solver.eval_upto(x, 2)) == 1:
    print("x is concrete")

eval_exact

eval_exact(e, n, cast_to=None, **kwargs)
Get exactly n solutions. Raises error if there are more or fewer.
state.solver.add(x < 5, x >= 0)
values = state.solver.eval_exact(x, 5)  # [0, 1, 2, 3, 4]

eval_atleast / eval_atmost

eval_atleast(e, n, cast_to=None, **kwargs)
eval_atmost(e, n, cast_to=None, **kwargs)
Get at least/at most n solutions. Raises error if requirement not met.
# Ensure at least 5 solutions exist
values = state.solver.eval_atleast(x, 5)

# Ensure at most 3 solutions exist
values = state.solver.eval_atmost(x, 3)

Min/Max Values

min / max

min(e, extra_constraints=(), exact=None, signed=False)
max(e, extra_constraints=(), exact=None, signed=False)
Find the minimum or maximum possible value of an expression.
e
BV
The expression to evaluate.
signed
bool
default:"false"
Whether to treat the expression as signed.
return
int
The minimum or maximum value.
state.solver.add(x > 10, x < 100)
min_val = state.solver.min(x)  # 11
max_val = state.solver.max(x)  # 99

# Signed min/max
state.solver.add(y < 0)
min_val = state.solver.min(y, signed=True)  # Negative value

Satisfiability Checking

satisfiable

satisfiable(extra_constraints=(), exact=None)
Check if the current constraints are satisfiable.
extra_constraints
tuple
Additional temporary constraints to check.
return
bool
True if satisfiable, False if unsatisfiable.
# Check if constraints are consistent
if state.solver.satisfiable():
    print("State is feasible")

# Check with extra constraints
if state.solver.satisfiable(extra_constraints=(x == 42,)):
    print("x can be 42")

is_true / is_false

is_true(e, extra_constraints=(), exact=None)
is_false(e, extra_constraints=(), exact=None)
Check if an expression is definitely true or false.
return
bool
True if the expression is definitely true/false under current constraints.
state.solver.add(x > 10)

if state.solver.is_true(x > 5):
    print("x is definitely > 5")

if state.solver.is_false(x < 5):
    print("x is definitely >= 5")

solution

solution(e, v, extra_constraints=(), exact=None)
Check if v is a valid solution for expression e.
e
BV
The expression.
v
BV | int
The proposed solution.
return
bool
True if v is a valid solution.
state.solver.add(x > 10, x < 20)
print(state.solver.solution(x, 15))  # True
print(state.solver.solution(x, 5))   # False

Constraint Management

constraints

Property that returns the current constraint list.
all_constraints = state.solver.constraints
print(f"State has {len(all_constraints)} constraints")

for constraint in all_constraints:
    print(constraint)

unsat_core

unsat_core(extra_constraints=())
Get the unsatisfiable core (minimal set of conflicting constraints).
return
tuple
Tuple of constraints that form an unsatisfiable core.
# Requires CONSTRAINT_TRACKING_IN_SOLVER option
state.options.add('CONSTRAINT_TRACKING_IN_SOLVER')

state.solver.add(x > 10)
state.solver.add(x < 5)  # Conflict!

try:
    state.solver.eval(x)
except:
    core = state.solver.unsat_core()
    print(f"Conflicting constraints: {core}")

reload_solver

reload_solver(constraints=None)
Reload the solver, useful when changing solver options.
# Change solver options
state.options.add('REPLACEMENT_SOLVER')
state.solver.reload_solver()

Variable Management

register_variable

register_variable(v, key, eternal=True)
Register a variable with the tracking system.
v
BV
The variable to register.
key
tuple
Tracking key tuple (e.g., ('mem', 0x1000)).
eternal
bool
default:"true"
If True, variable persists across state copies.
buf = state.solver.BVS('buffer', 256)
state.solver.register_variable(buf, ('mem', 0x1000), eternal=True)

get_variables

get_variables(*keys)
Iterate over tracked variables matching the given key prefix.
keys
any
Variable-length key prefix to filter by.
return
iterator
Iterator of (key, variable) tuples.
# Get all memory variables
for key, var in state.solver.get_variables('mem'):
    print(f"Memory at {key}: {var}")

# Get specific file variables  
for key, var in state.solver.get_variables('file', 2):
    print(f"File 2 variable: {key} = {var}")

# Get all tracked variables
for key, var in state.solver.get_variables():
    print(f"{key}: {var}")

describe_variables

describe_variables(v)
Get the tracking keys for all registered variables in an AST.
v
BV
An expression containing variables.
return
iterator
Iterator of tracking keys.
expr = x + y + z
for key in state.solver.describe_variables(expr):
    print(f"Expression contains variable: {key}")

Utility Methods

symbolic

symbolic(e)
Check if an expression is symbolic (contains variables).
return
bool
True if symbolic, False if concrete.
if state.solver.symbolic(x):
    print("x is symbolic")
else:
    print("x is concrete")

unique

unique(e, **kwargs)
Check if expression has exactly one solution, and add that constraint.
return
bool
True if unique, False if multiple solutions exist.
if state.solver.unique(x):
    print(f"x must be {state.solver.eval(x)}")

single_valued

single_valued(e)
Check if expression is concrete or has only one possible value (without querying solver).
return
bool
True if single-valued.
if state.solver.single_valued(x):
    print("x has a single value")

simplify

simplify(e=None)
Simplify an expression or the current constraint set.
e
BV
Expression to simplify. If None, simplifies all constraints.
return
BV
Simplified expression.
# Simplify expression
simple = state.solver.simplify(x + x + 0)
# simple might be 2*x

# Simplify all constraints
state.solver.simplify()

variables

variables(e)
Get the set of variable names in an expression.
return
set
Set of variable name strings.
vars = state.solver.variables(x + y * 2)
print(vars)  # {'x', 'y'}

Claripy Interoperability

The solver provides access to all Claripy module functions as properties:
# These are equivalent:
state.solver.BVV(0x1234, 32)
claripy.BVV(0x1234, 32)

# Boolean operations
state.solver.And(cond1, cond2)
state.solver.Or(cond1, cond2)
state.solver.Not(cond)

# Arithmetic
state.solver.ULT(x, y)  # Unsigned less than
state.solver.SLT(x, y)  # Signed less than

# Bit operations
state.solver.Extract(63, 32, x)  # Get bits 63-32
state.solver.Concat(x, y)  # Concatenate

Examples

Symbolic Input Processing

import angr

project = angr.Project('/bin/example')
state = project.factory.entry_state()

# Create symbolic input
input_size = 64
sym_input = state.solver.BVS('input', input_size * 8)
state.memory.store(0x1000, sym_input)

# Run and add constraints
simgr = project.factory.simulation_manager(state)
simgr.explore(find=0x401234)

if simgr.found:
    found = simgr.found[0]
    # Get valid input
    solution = found.solver.eval(sym_input, cast_to=bytes)
    print(f"Valid input: {solution}")

Password Cracking

# Symbolic password
password = state.solver.BVS('password', 32 * 8)  # 32 bytes
state.memory.store(password_addr, password)

# Run to password check
simgr.explore(find=success_addr, avoid=failure_addr)

if simgr.found:
    found = simgr.found[0]
    pwd = found.solver.eval(password, cast_to=bytes)
    print(f"Password: {pwd.decode()}")

Constraint-Based Analysis

# Add application constraints
state.solver.add(x >= 0)
state.solver.add(x <= 1000)
state.solver.add(y == x * 2 + 5)

# Find values
if state.solver.satisfiable():
    x_val = state.solver.eval(x)
    y_val = state.solver.eval(y)
    print(f"x={x_val}, y={y_val}")
    
# Check properties
if state.solver.is_true(y > x):
    print("y is always greater than x")

See Also

Build docs developers (and LLMs) love