HackTricks
Search…
Pentesting
Powered By GitBook
Angr

Installation

1
sudo apt-get install python3-dev libffi-dev build-essential
2
python3 -m pip install --user virtualenv
3
python3 -m venv ang
4
source ang/bin/activate
5
pip install angr
Copied!

Basic Actions

1
import angr
2
import monkeyhex # this will format numerical results in hexadecimal
3
#Load binary
4
proj = angr.Project('/bin/true')
5
6
#BASIC BINARY DATA
7
proj.arch #Get arch "<Arch AMD64 (LE)>"
8
proj.arch.name #'AMD64'
9
proj.arch.memory_endness #'Iend_LE'
10
proj.entry #Get entrypoint "0x4023c0"
11
proj.filename #Get filename "/bin/true"
12
13
#There are specific options to load binaries
14
#Usually you won't need to use them but you could
15
angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
Copied!

Loaded and Main object information

Loaded Data

1
#LOADED DATA
2
proj.loader #<Loaded true, maps [0x400000:0x5004000]>
3
proj.loader.min_addr #0x400000
4
proj.loader.max_addr #0x5004000
5
proj.loader.all_objects #All loaded
6
proj.loader.shared_objects #Loaded binaries
7
"""
8
OrderedDict([('true', <ELF Object true, maps [0x400000:0x40a377]>),
9
('libc.so.6',
10
<ELF Object libc-2.31.so, maps [0x500000:0x6c4507]>),
11
('ld-linux-x86-64.so.2',
12
<ELF Object ld-2.31.so, maps [0x700000:0x72c177]>),
13
('extern-address space',
14
<ExternObject Object cle##externs, maps [0x800000:0x87ffff]>),
15
('cle##tls',
16
<ELFTLSObjectV2 Object cle##tls, maps [0x900000:0x91500f]>)])
17
"""
18
proj.loader.all_elf_objects #Get all ELF objects loaded (Linux)
19
proj.loader.all_pe_objects #Get all binaries loaded (Windows)
20
proj.loader.find_object_containing(0x400000)#Get object loaded in an address "<ELF Object fauxware, maps [0x400000:0x60105f]>"
Copied!

Main Object

1
#Main Object (main binary loaded)
2
obj = proj.loader.main_object #<ELF Object true, maps [0x400000:0x60721f]>
3
obj.execstack #"False" Check for executable stack
4
obj.pic #"True" Check PIC
5
obj.imports #Get imports
6
obj.segments #<Regions: [<ELFSegment flags=0x5, relro=0x0, vaddr=0x400000, memsize=0xa74, filesize=0xa74, offset=0x0>, <ELFSegment flags=0x4, relro=0x1, vaddr=0x600e28, memsize=0x1d8, filesize=0x1d8, offset=0xe28>, <ELFSegment flags=0x6, relro=0x0, vaddr=0x601000, memsize=0x60, filesize=0x50, offset=0x1000>]>
7
obj.find_segment_containing(obj.entry) #Get segment by address
8
obj.sections #<Regions: [<Unnamed | offset 0x0, vaddr 0x0, size 0x0>, <.interp | offset 0x238, vaddr 0x400238, size 0x1c>, <.note.ABI-tag | offset 0x254, vaddr 0x400254, size 0x20>, <.note.gnu.build-id ...
9
obj.find_section_containing(obj.entry) #Get section by address
10
obj.plt['strcmp'] #Get plt address of a funcion (0x400550)
11
obj.reverse_plt[0x400550] #Get function from plt address ('strcmp')
Copied!

Symbols and Relocations

1
strcmp = proj.loader.find_symbol('strcmp') #<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
2
3
strcmp.name #'strcmp'
4
strcmp.owne #<ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>
5
strcmp.rebased_addr #0x1089cd0
6
strcmp.linked_addr #0x89cd0
7
strcmp.relative_addr #0x89cd0
8
strcmp.is_export #True, as 'strcmp' is a function exported by libc
9
10
#Get strcmp from the main object
11
main_strcmp = proj.loader.main_object.get_symbol('strcmp')
12
main_strcmp.is_export #False
13
main_strcmp.is_import #True
14
main_strcmp.resolvedby #<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
Copied!

Blocks

1
#Blocks
2
block = proj.factory.block(proj.entry) #Get the block of the entrypoint fo the binary
3
block.pp() #Print disassembly of the block
4
block.instructions #"0xb" Get number of instructions
5
block.instruction_addrs #Get instructions addresses "[0x401670, 0x401672, 0x401675, 0x401676, 0x401679, 0x40167d, 0x40167e, 0x40167f, 0x401686, 0x40168d, 0x401694]"
Copied!

Dynamic Analysis

Simulation Manager, States

1
#Live States
2
#This is useful to modify content in a live analysis
3
state = proj.factory.entry_state()
4
state.regs.rip #Get the RIP
5
state.mem[proj.entry].int.resolved #Resolve as a C int (BV)
6
state.mem[proj.entry].int.concreteved #Resolve as python int
7
state.regs.rsi = state.solver.BVV(3, 64) #Modify RIP
8
state.mem[0x1000].long = 4 #Modify mem
9
10
#Other States
11
project.factory.entry_state()
12
project.factory.blank_state() #Most of its data left uninitialized
13
project.factory.full_init_statetate() #Execute through any initializers that need to be run before the main binary's entry point
14
project.factory.call_state() #Ready to execute a given function.
15
16
#Simulation manager
17
#The simulation manager stores all the states across the execution of the binary
18
simgr = proj.factory.simulation_manager(state) #Start
19
simgr.step() #Execute one step
20
simgr.active[0].regs.rip #Get RIP from the last state
Copied!

Calling functions

    You can pass a list of arguments through args and a dictionary of environment variables through env into entry_state and full_init_state. The values in these structures can be strings or bitvectors, and will be serialized into the state as the arguments and environment to the simulated execution. The default args is an empty list, so if the program you're analyzing expects to find at least an argv[0], you should always provide that!
    If you'd like to have argc be symbolic, you can pass a symbolic bitvector as argc to the entry_state and full_init_state constructors. Be careful, though: if you do this, you should also add a constraint to the resulting state that your value for argc cannot be larger than the number of args you passed into args.
    To use the call state, you should call it with .call_state(addr, arg1, arg2, ...), where addr is the address of the function you want to call and argN is the Nth argument to that function, either as a python integer, string, or array, or a bitvector. If you want to have memory allocated and actually pass in a pointer to an object, you should wrap it in an PointerWrapper, i.e. angr.PointerWrapper("point to me!"). The results of this API can be a little unpredictable, but we're working on it.

BitVectors

1
#BitVectors
2
state = proj.factory.entry_state()
3
bv = state.solver.BVV(0x1234, 32) #Create BV of 32bits with the value "0x1234"
4
state.solver.eval(bv) #Convert BV to python int
5
bv.zero_extend(30) #Will add 30 zeros on the left of the bitvector
6
bv.sign_extend(30) #Will add 30 zeros or ones on the left of the BV extending the sign
Copied!

Symbolic BitVectors & Constraints

1
x = state.solver.BVS("x", 64) #Symbolic variable BV of length 64
2
y = state.solver.BVS("y", 64)
3
4
#Symbolic oprations
5
tree = (x + 1) / (y + 2)
6
tree #<BV64 (x_9_64 + 0x1) / (y_10_64 + 0x2)>
7
tree.op #'__floordiv__' Access last operation
8
tree.args #(<BV64 x_9_64 + 0x1>, <BV64 y_10_64 + 0x2>)
9
tree.args[0].op #'__add__' Access of dirst arg
10
tree.args[0].args #(<BV64 x_9_64>, <BV64 0x1>)
11
tree.args[0].args[1].op #'BVV'
12
tree.args[0].args[1].args #(1, 64)
13
14
#Symbolic constraints solver
15
state = proj.factory.entry_state() #Get a fresh state without constraints
16
input = state.solver.BVS('input', 64)
17
operation = (((input + 4) * 3) >> 1) + input
18
output = 200
19
state.solver.add(operation == output)
20
state.solver.eval(input) #0x3333333333333381
21
state.solver.add(input < 2**32)
22
state.satisfiable() #False
23
24
#Solver solutions
25
solver.eval(expression) #one possible solution
26
solver.eval_one(expression) #solution to the given expression, or throw an error if more than one solution is possible.
27
solver.eval_upto(expression, n) #n solutions to the given expression, returning fewer than n if fewer than n are possible.
28
solver.eval_atleast(expression, n) #n solutions to the given expression, throwing an error if fewer than n are possible.
29
solver.eval_exact(expression, n) #n solutions to the given expression, throwing an error if fewer or more than are possible.
30
solver.min(expression) #minimum possible solution to the given expression.
31
solver.max(expression) #maximum possible solution to the given expression.
Copied!

Hooking

1
>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] # this is a CLASS
2
>>> proj.hook(0x10000, stub_func()) # hook with an instance of the class
3
4
>>> proj.is_hooked(0x10000) # these functions should be pretty self-explanitory
5
True
6
>>> proj.hooked_by(0x10000)
7
<ReturnUnconstrained>
8
>>> proj.unhook(0x10000)
9
10
>>> @proj.hook(0x20000, length=5)
11
... def my_hook(state):
12
... state.regs.rax = 1
13
14
>>> proj.is_hooked(0x20000)
15
True
Copied!
Furthermore, you can use proj.hook_symbol(name, hook), providing the name of a symbol as the first argument, to hook the address where the symbol lives

Examples

Last modified 27d ago