Skip to content

Commit

Permalink
Initial DWARF plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
bannsec committed Mar 9, 2020
1 parent 5955456 commit 9035a92
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 0 deletions.
11 changes: 11 additions & 0 deletions docs/api/plugins/dwarf/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
=====
Dwarf
=====

Dwarf
=====

.. autoclass:: revenge.plugins.dwarf.Dwarf
: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 @@ -8,6 +8,7 @@ Plugins
:hidden:

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

DWARF is a format for debugging info relating to ELF files. Standard
compilations of binaries do not contain DWARF info. However, when you compile
binaries with this info (generally with the `-g` flag), much more useful
inforamtion is available. This plugin attempts to expose that information.

General Interaction
===================

General interaction with the DWARF plugin is via the `modules`. For instance:

.. code-block:: python
bin = process.modules['bin']
dwarf = bin.dwarf
Functions
=========

Functions are enumerated and exposed via the
:attr:`~revenge.plugins.dwarf.Dwarf.functions` property.
1 change: 1 addition & 0 deletions docs/overview/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Plugins
:hidden:

decompiler/index.rst
dwarf/index.rst
radare2/index.rst

Plugins are a means for ``revenge`` to expose support in a general way. Plugins
Expand Down
5 changes: 5 additions & 0 deletions revenge/engines/frida/plugins/dwarf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

from revenge.plugins.dwarf import Dwarf as DwarfBase

class Dwarf(DwarfBase):
pass
2 changes: 2 additions & 0 deletions revenge/plugins/dwarf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

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

import logging

from prettytable import PrettyTable
from ... import common
from .. import Plugin

class Dwarf(Plugin):
_MODULE_PLUGIN_REGISTERED = False

def __init__(self, process, module=None):
"""Lookup Dwarf debugging information from the file.
Examples:
.. code-block:: python
dwarf = process.modules['*libc'].dwarf
# Show all known function names and their address and size
print(dwarf.functions)
# Print the first instruction block in main
print(dwarf.functions[b'main'].instruction_block)
"""
self._process = process
self._module = module

# Register this in modules
if not Dwarf._MODULE_PLUGIN_REGISTERED:
self._process.modules._register_plugin(Dwarf._modules_plugin, "dwarf")
Dwarf._MODULE_PLUGIN_REGISTERED = True

if self._dwarffile is not None:
self.__init_functions()

def __init_functions(self):

for CU in self._dwarffile.iter_CUs():
for DIE in CU.iter_DIEs():
try:
if DIE.tag == 'DW_TAG_subprogram':
lowpc = DIE.attributes['DW_AT_low_pc'].value

# DWARF v4 in section 2.17 describes how to interpret the
# DW_AT_high_pc attribute based on the class of its form.
# For class 'address' it's taken as an absolute address
# (similarly to DW_AT_low_pc); for class 'constant', it's
# an offset from DW_AT_low_pc.
highpc_attr = DIE.attributes['DW_AT_high_pc']
highpc_attr_class = describe_form_class(highpc_attr.form)
if highpc_attr_class == 'address':
highpc = highpc_attr.value
elif highpc_attr_class == 'constant':
highpc = lowpc + highpc_attr.value
else:
print('Error: invalid DW_AT_high_pc class:',
highpc_attr_class)
continue

self.functions[DIE.attributes['DW_AT_name'].value] = self._process.memory[self._module.base + lowpc - self.base_address : self._module.base + highpc - self.base_address]
except KeyError:
continue

@classmethod
def _modules_plugin(klass, module):
self = klass(module._process, module)

# ELF parsing error
if self._elffile is None:
return

# No point in having Dwarf object with no dwarf...
if not self._elffile.has_dwarf_info():
return

return self

@property
def _elffile(self):
try:
return self.__elffile
except AttributeError:
if self._module is None:
self.__elffile = None
else:
try:
self.__elffile = ELFFile(common.load_file(self._process, self._module.path))
except elftools.common.exceptions.ELFError:
self.__elffile = None

return self.__elffile

@property
def _dwarffile(self):
try:
return self.__dwarffile
except AttributeError:
if self._elffile is None:
self.__dwarffile = None
else:
if not self._elffile.has_dwarf_info():
self.__dwarffile = None
else:
self.__dwarffile = self._elffile.get_dwarf_info()

return self.__dwarffile

@property
def has_debug_info(self):
"""bool: Does this module actually have debugging info?"""
try:
return self.__has_debug_info
except AttributeError:
if self._dwarffile is not None:
self.__has_debug_info = self._dwarffile.has_debug_info
else:
self.__has_debug_info = False

return self.__has_debug_info

@property
def _is_valid(self):
# Not bothering to load this under process
return False

@property
def base_address(self):
"""int: What is the binary's defined base address."""
return next(x.header["p_vaddr"] for x in self._elffile.iter_segments() if x.header['p_type'] == "PT_LOAD" and x.header["p_offset"] == 0)

@property
def functions(self):
"""dict: Dictionary of function_name -> MemoryBytes."""
try:
return self.__functions
except AttributeError:
self.__functions = {}

return self.__functions

from elftools.elf.elffile import ELFFile
from elftools.common.py3compat import maxint, bytes2str
from elftools.dwarf.descriptions import describe_form_class
import elftools.common.exceptions

# Doc fixup
Dwarf.__doc__ = Dwarf.__init__.__doc__
#Dwarf._modules_plugin.__doc__ = Dwarf.__init__.__doc__
21 changes: 21 additions & 0 deletions tests/linux/bins/basic_dwarf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <string.h>

void func1() {
puts("func1");
}

int func2(char *thing) {
puts(thing);
}

int main(int argc, char **argv) {

if ( argc < 2 ) {
puts("Try more args.");
} else {
if ( ! strcmp( argv[1], "win" ) ) {
puts("Win.");
}
}
}
Binary file added tests/linux/bins/basic_dwarf_i686
Binary file not shown.
Binary file added tests/linux/bins/basic_dwarf_nopie_i686
Binary file not shown.
Binary file added tests/linux/bins/basic_dwarf_x64
Binary file not shown.
44 changes: 44 additions & 0 deletions tests/linux/plugins/dwarf/test_dwarf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

import logging

logger = logging.getLogger(__name__)

import os
import pytest
import revenge
types = revenge.types

here = os.path.dirname(os.path.abspath(__file__))
bin_location = os.path.join(here, "..", "..", "bins")

basic_dwarf_x64_path = os.path.join(bin_location, "basic_dwarf_x64")
basic_dwarf_i686_path = os.path.join(bin_location, "basic_dwarf_i686")
basic_dwarf_nopie_i686_path = os.path.join(bin_location, "basic_dwarf_nopie_i686")

def dwarf_basic(process):
"""Same tests for different archs."""
basic = process.modules['basic_dwarf*']
libc = process.modules['*libc*']

assert basic.dwarf.has_debug_info == True
assert libc.dwarf.has_debug_info == False

funcs = ["main", "func1", "func2"]
for func in funcs:
# Check that the dwarf info matches up with what we resolved for symbols
assert basic.dwarf.functions[func.encode()].address == basic.symbols[func].address

def test_dwarf_x64_basic():
process = revenge.Process(basic_dwarf_x64_path, resume=False, verbose=False)
dwarf_basic(process)
process.quit()

def test_dwarf_i686_basic():
process = revenge.Process(basic_dwarf_i686_path, resume=False, verbose=False)
dwarf_basic(process)
process.quit()

def test_dwarf_i686_nopie_basic():
process = revenge.Process(basic_dwarf_nopie_i686_path, resume=False, verbose=False)
dwarf_basic(process)
process.quit()

0 comments on commit 9035a92

Please sign in to comment.