Skip to content

Commit

Permalink
Adding angr plugin!
Browse files Browse the repository at this point in the history
  • Loading branch information
bannsec committed May 8, 2020
1 parent a92c8dc commit 320952d
Show file tree
Hide file tree
Showing 16 changed files with 610 additions and 8 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ RUN dpkg --add-architecture i386 && \
apt update && apt dist-upgrade -y && \
apt install -y python3 python3-pip python3-venv git libc6:i386 libncurses5:i386 libstdc++6:i386 multiarch-support adb bsdutils git wget curl bison flex pkg-config && \
mkdir -p /opt && cd /opt && mkdir cmake && cd cmake && wget -O cmake.sh https://github.com/`wget -q -O- https://github.com/Kitware/CMake/releases/latest | grep download | grep Linux | grep \.sh | cut -d '"' -f 2` && chmod +x cmake.sh && ./cmake.sh --skip-license && export PATH=$PWD/bin:$PATH && \
mkdir -p /opt && cd /opt && git clone https://github.com/radareorg/radare2.git && cd radare2 && ./sys/install.sh && r2pm init && r2pm install r2ghidra-dec
mkdir -p /opt && cd /opt && git clone https://github.com/radareorg/radare2.git && cd radare2 && ./sys/install.sh && r2pm init && r2pm install r2ghidra-dec && \
python3 -m pip install angr && python3 -m pip install --process-dependency-links https://github.com/angr/angr-targets/archive/master.zip

COPY . /opt/revenge/

Expand Down
9 changes: 9 additions & 0 deletions docs/api/plugins/angr/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
====
angr
====

.. autoclass:: revenge.plugins.angr.Angr
:members:
:undoc-members:
:show-inheritance:

1 change: 1 addition & 0 deletions docs/api/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Plugins
:caption: Plugins
:hidden:

angr/index.rst
decompiler/index.rst
dwarf/index.rst
java/index.rst
Expand Down
67 changes: 67 additions & 0 deletions docs/overview/plugins/angr/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
====
angr
====

The angr plugin is being written to help expose features of angr to dynamic
reversing.

Requirements
============

The current requirements to use the `angr` plugin are:

- Having the base angr installed
- Having `angr-targets` installed

Setup
=====

You can install `angr` with:

.. code-block:: bash
pip install angr
pip install --process-dependency-links https://github.com/angr/angr-targets/archive/master.zip
angr also has pre-built docker containers available which alleviate build
issues.

Usage
=====

Thread Plugin
-------------

As a thread plugin, `angr` gets exposed as a property of
:class:`~revenge.threads.Thread`. The primary use case of this is to allow
seamless `Symbion <http://angr.io/blog/angr_symbion/>`_ integration. When
requesting objects, the plugin will automatically configure those objects to
use `revenge` as a concrete backer as well as provide additional relocation
support that isn't available directly by `Symbion`.

In English, this means you can execute to interesting points in your code using
`revenge`, then easily get an `angr` state object that will pick up right at
that point.

Basic Example
~~~~~~~~~~~~~

.. code-block:: python3
# Set process breakpoint somewhere interesting
process.memoery[interesting].breakpoint = True
# Once you hit that interesting point, grab your thread
thread = list(process.threads)[0]
# Now easily grab an angr state as if angr was already at this point in
# execution
state = thread.angr.state
assert state.pc == thread.pc
# Other helpful things
thread.angr.project
thread.angr.simgr
For more info, see the :class:`~revenge.plugins.angr.Angr` API.
16 changes: 9 additions & 7 deletions revenge/engines/frida/memory/memory_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ def breakpoint(self, val):
assert type(val) is bool, "breakpoint set must be boolean."
resume_pointer = []

def breakpoint_on_message(msg, data=None):
def breakpoint_on_message(msg, raw_data=None):
payload = msg['payload']
ptype = payload["type"]
data = payload["data"]
Expand All @@ -666,6 +666,9 @@ def breakpoint_on_message(msg, data=None):
# Remove our state
self._process.threads._breakpoint_context.pop(data["tid"])

elif ptype == "before_replace":
self._process.threads._breakpoint_original_bytes[common.auto_int(data)] = raw_data

# Remove breakpoint
if val is False:
# We're already not a breakpoint
Expand All @@ -682,12 +685,11 @@ def breakpoint_on_message(msg, data=None):
if self.breakpoint:
return

unbreak = self._engine.run_script_generic(
'generic_suspend_until_true.js',
replace={"FUNCTION_HERE": hex(self.address)},
unload=False,
on_message=breakpoint_on_message,
)
self._engine.run_script_generic(
'generic_suspend_until_true.js',
replace={"FUNCTION_HERE": hex(self.address)},
unload=False,
on_message=breakpoint_on_message)

# Wait for notification of our unbreak address
while resume_pointer == []:
Expand Down
1 change: 1 addition & 0 deletions revenge/engines/frida/plugins/angr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .angr import Angr
6 changes: 6 additions & 0 deletions revenge/engines/frida/plugins/angr/angr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

from revenge.plugins.angr import Angr as AngrBase


class Angr(AngrBase):
pass
2 changes: 2 additions & 0 deletions revenge/js/generic_suspend_until_true.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var func_ptr = ptr("FUNCTION_HERE");
var shared_var = Memory.alloc(1);
shared_var.writeS8(0); // Init to false

send({"type": "before_replace", "data": func_ptr}, func_ptr.readByteArray(16))

Interceptor.attach(func_ptr, function (args) {

this.alloc = shared_var;
Expand Down
2 changes: 2 additions & 0 deletions revenge/plugins/angr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from .angr import Angr
196 changes: 196 additions & 0 deletions revenge/plugins/angr/angr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@

import logging

LOGGER = logging.getLogger(__name__)

try:
import angr
LOGGER.debug("angr found and successfully imported.")

from .revenge_target import RevengeConcreteTarget
LOGGER.debug("RevengeConcreteTarget found and successfully imported.")
ANGR_OK = True
except ModuleNotFoundError:
ANGR_OK = False
LOGGER.debug("angr not found or required lib not successfully imported.")

from revenge.exceptions import *
from revenge import common
from .. import Plugin


class Angr(Plugin):

def __init__(self, process, thread=None):
"""Use angr to enrich your reversing.
Examples:
.. code-block:: python3
# Grab current location
thread = list(process.threads)[0]
# Load options and state options can be configured
# They use the same name but are exposed as attributes here
# If you SET any of these, the project will be re-loaded next
# time you ask for an object. It will NOT affect the current
# object instance you have.
thread.angr.load_options
thread.angr.support_selfmodifying_code
thread.angr.use_sim_procedures
thread.angr.options
# Ask for a simgr for this location
simgr = thread.angr.simgr
# Whoops, we wanted self modifying code!
thread.angr.support_selfmodifying_code = True
simgr = thread.angr.simgr
# Use this as you normally would
simgr.explore(find=winner)
"""

self._process = process
self._thread = thread
self.__project = None
self.__sim_procedures_resolved = False

self.load_options = {'auto_load_libs': False}
self.support_selfmodifying_code = False
self.use_sim_procedures = True
self.exclude_sim_procedures_list = []
self.options = set([])

if ANGR_OK:
try:
self._process.threads._register_plugin(Angr._thread_plugin, "angr")
except RevengeModulePluginAlreadyRegistered:
# This will error out if we're already registered
pass

@property
def load_options(self):
"""angr load_options"""
return self.__load_options

@load_options.setter
def load_options(self, load_options):
self.__load_options = load_options
# Invalidate the project
self.__project = None

@property
def exclude_sim_procedures_list(self):
"""bool: Which procedures should angr not wrap?"""
return self.__exclude_sim_procedures_list

@exclude_sim_procedures_list.setter
def exclude_sim_procedures_list(self, exclude_sim_procedures_list):
self.__exclude_sim_procedures_list = exclude_sim_procedures_list
# Invalidate the project
self.__project = None

@property
def use_sim_procedures(self):
"""bool: Should angr use sim procedures?"""
return self.__use_sim_procedures

@use_sim_procedures.setter
def use_sim_procedures(self, use_sim_procedures):
self.__use_sim_procedures = use_sim_procedures
# Invalidate the project
self.__project = None

@property
def support_selfmodifying_code(self):
"""bool: Should angr support self modifying code?"""
return self.__support_selfmodifying_code

@support_selfmodifying_code.setter
def support_selfmodifying_code(self, support_selfmodifying_code):
self.__support_selfmodifying_code = support_selfmodifying_code
# Invalidate the project
self.__project = None

@property
def _is_valid(self):
# Not registering this as a process plugin for now.
return False

@classmethod
def _thread_plugin(klass, thread):
return klass(thread._process, thread=thread)

@property
def project(self):
"""Returns the angr project for this file."""
if self.__project is not None:
return self.__project

# Original path might not be our final path
# For example: loading angr on a remote android project
orig_path = self._process.modules[self._thread.pc].path
new_path = common.load_file(self._process, orig_path).name

self.__project = angr.Project(
new_path,
load_options=self.load_options,
concrete_target=self._concrete_target,
use_sim_procedures=self.use_sim_procedures,
exclude_sim_procedures_list=self.exclude_sim_procedures_list,
support_selfmodifying_code=self.support_selfmodifying_code)

return self.__project

@property
def state(self):
"""Returns a state object for the current thread state."""
if self._thread.breakpoint and self._thread.pc in self._process.threads._breakpoint_original_bytes:

LOGGER.warning("Overwriting current breakpoint in memory so it doesn't trip up angr.")
LOGGER.warning("This has the side-effect of disabling this breakpoint. You can re-enable manually.")

orig_bytes = self._process.threads._breakpoint_original_bytes[self._thread.pc]
self._process.memory[self._thread.pc:self._thread.pc + len(orig_bytes)].bytes = orig_bytes

state = self.project.factory.entry_state(
add_options=self.options)

if self._concrete_target is not None:
state.concrete.sync()

if not self.__sim_procedures_resolved:

# Fixup angr's Sim Procedures since it cannot handle PIC
me = self._process.modules[self._thread.pc]
imports = [rel.symbol.name for rel in self.project.loader.main_object.relocs if rel.symbol.is_import]

for imp in imports:
# TODO: This will only work for ELF...

try:
imp_addr = me.symbols["plt." + imp].address
except KeyError:
continue

self.project.rehook_symbol(imp_addr, imp, True)

self.__sim_procedures_resolved = True

return state

@property
def simgr(self):
"""Returns an angr simgr object for the current state."""
return self.project.factory.simgr(self.state)

@property
def _concrete_target(self):
"""Returns a concrete target for this context or None."""
if self._thread is not None:
return RevengeConcreteTarget(self._process, self._thread.context)


# doc fixup
Angr.__doc__ = Angr.__init__.__doc__
Loading

0 comments on commit 320952d

Please sign in to comment.