| name | angr-binary-analysis |
| description | Use angr for binary analysis, reverse engineering, and symbolic execution. Use this skill whenever the user needs to analyze binaries, extract binary information (architecture, entry points, symbols, sections), perform dynamic analysis with simulation managers, solve CTF challenges with symbolic execution, hook functions, or work with bitvectors and constraints. Trigger this skill for any binary analysis task, reverse engineering work, CTF binary challenges, or when examining ELF/PE files programmatically. |
Angr Binary Analysis Skill
A comprehensive skill for using angr to analyze binaries, perform symbolic execution, and solve reverse engineering challenges.
Quick Start
import angr
import monkeyhex
proj = angr.Project('/path/to/binary')
Binary Information Extraction
Basic Binary Data
Get fundamental information about the loaded binary:
proj.arch
proj.arch.name
proj.arch.memory_endness
proj.entry
proj.filename
Loader Information
Access loaded objects and memory mappings:
proj.loader.min_addr
proj.loader.max_addr
proj.loader.all_objects
proj.loader.shared_objects
proj.loader.all_elf_objects
proj.loader.all_pe_objects
proj.loader.find_object_containing(0x400000)
Main Object Analysis
Analyze the main binary's structure:
obj = proj.loader.main_object
obj.execstack
obj.pic
obj.imports
obj.segments
obj.sections
obj.find_segment_containing(addr)
obj.find_section_containing(addr)
obj.plt['function_name']
obj.reverse_plt[0x400550]
Symbol Analysis
Find and analyze symbols:
symbol = proj.loader.find_symbol('strcmp')
symbol.name
symbol.owner
symbol.rebased_addr
symbol.linked_addr
symbol.is_export
main_symbol = proj.loader.main_object.get_symbol('strcmp')
main_symbol.is_import
main_symbol.resolvedby
Code Blocks
Disassemble and analyze basic blocks:
block = proj.factory.block(proj.entry)
block.pp()
block.instructions
block.instruction_addrs
Dynamic Analysis
Creating States
Different state types for different analysis needs:
state = proj.factory.entry_state()
state = proj.factory.blank_state()
state = proj.factory.full_init_state()
state = proj.factory.call_state(func_addr, arg1, arg2)
State Manipulation
Read and modify state during analysis:
state.regs.rip
state.regs.rax
state.mem[addr].int.resolved
state.mem[addr].int.concrete
state.mem[addr].long
state.regs.rsi = state.solver.BVV(3, 64)
state.mem[0x1000].long = 4
Simulation Manager
Execute and track binary execution:
simgr = proj.factory.simulation_manager(state)
simgr.step()
simgr.active[0].regs.rip
simgr.explore(find=0x400500, avoid=0x400600)
Passing Arguments
Provide command-line arguments and environment variables:
state = proj.factory.entry_state(args=['./binary', 'arg1', 'arg2'])
state = proj.factory.entry_state(env={'VAR': 'value'})
argc = state.solver.BVS('argc', 64)
state = proj.factory.entry_state(argc=argc, args=['./binary'])
state.solver.add(argc <= 1)
state = proj.factory.call_state(func_addr, arg1, arg2)
state = proj.factory.call_state(func_addr, angr.PointerWrapper("string"))
Symbolic Execution
BitVectors
Create and manipulate bitvectors:
bv = state.solver.BVV(0x1234, 32)
state.solver.eval(bv)
bv.zero_extend(30)
bv.sign_extend(30)
Symbolic Variables
Create symbolic variables for analysis:
x = state.solver.BVS("x", 64)
y = state.solver.BVS("y", 64)
tree = (x + 1) / (y + 2)
tree.op
tree.args
Constraints and Solving
Add constraints and find solutions:
state = proj.factory.entry_state()
input = state.solver.BVS('input', 64)
operation = (((input + 4) * 3) >> 1) + input
state.solver.add(operation == 200)
solution = state.solver.eval(input)
state.solver.add(input < 2**32)
state.solver.satisfiable()
Solver Methods
Different ways to extract solutions:
solver.eval(expression)
solver.eval_one(expression)
solver.eval_upto(expression, n)
solver.eval_atleast(expression, n)
solver.eval_exact(expression, n)
solver.min(expression)
solver.max(expression)
Hooking
Hooking Addresses
Replace code at specific addresses:
stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained']
proj.hook(0x10000, stub_func())
proj.is_hooked(0x10000)
proj.hooked_by(0x10000)
proj.unhook(0x10000)
@proj.hook(0x20000, length=5)
def my_hook(state):
state.regs.rax = 1
Hooking Symbols
Hook by symbol name instead of address:
proj.hook_symbol('function_name', hook_instance)
Common Patterns
Find Password/Flag
proj = angr.Project('binary')
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
simgr.explore(find=0x400500, avoid=0x400600)
if simgr.found:
solution = simgr.found[0].posix.dumps(0)
print(solution)
Symbolic Input Analysis
proj = angr.Project('binary')
state = proj.factory.entry_state()
state = proj.hook_symbol('__isoc99_scanf', angr.SIM_PROCEDURES['libc']['scanf']())
state = proj.factory.full_init_state(args=['./binary', 'input.txt'])
Function Analysis
func_addr = proj.loader.main_object.plt['target_function']
state = proj.factory.call_state(func_addr, arg1, arg2)
simgr = proj.factory.simulation_manager(state)
simgr.step()
Tips
- Use
monkeyhex to format addresses in hexadecimal for readability
- Always provide
argv[0] if the binary expects command-line arguments
- Use
full_init_state() when the binary has complex initialization
- Constrain symbolic variables to reasonable ranges to speed up solving
- Use
simgr.explore() with find and avoid to guide exploration
- Hook functions that are slow or non-deterministic to speed up analysis
- Check
simgr.deadended for states that crashed or terminated
- Use
simgr.unsat to find states where constraints couldn't be satisfied