mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
384 lines
14 KiB
Python
384 lines
14 KiB
Python
# Volatility
|
|
# Copyright (C) 2010 Brendan Dolan-Gavitt
|
|
# Copyright (c) 2011 Michael Cohen <scudette@gmail.com>
|
|
#
|
|
# This file is part of Volatility.
|
|
#
|
|
# Volatility is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Volatility is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import re
|
|
|
|
class DWARFParser(object):
|
|
"""A parser for DWARF files."""
|
|
|
|
# Nasty, but appears to parse the lines we need
|
|
dwarf_header_regex = re.compile(
|
|
r'<(?P<level>\d+)><(?P<statement_id>[0-9+]+)><(?P<kind>\w+)>')
|
|
dwarf_key_val_regex = re.compile(
|
|
'\s*(?P<keyname>\w+)<(?P<val>[^>]*)>')
|
|
|
|
dwarf_header_regex2 = re.compile(r'<(?P<level>\d+)><(?P<statement_id>0x[0-9a-fA-F]+([+]0x[0-9a-fA-F]+)?)><(?P<kind>\w+)>')
|
|
|
|
sz2tp = {8: 'long long', 4: 'int', 2: 'short', 1: 'char'}
|
|
tp2vol = {
|
|
'_Bool': 'unsigned char',
|
|
'char': 'char',
|
|
'float': 'float',
|
|
'double': 'double',
|
|
'long double': 'double',
|
|
'int': 'int',
|
|
'long int': 'long',
|
|
'long long int': 'long long',
|
|
'long long unsigned int': 'unsigned long long',
|
|
'long unsigned int': 'unsigned long',
|
|
'short int': 'short',
|
|
'short unsigned int': 'unsigned short',
|
|
'signed char': 'signed char',
|
|
'unsigned char': 'unsigned char',
|
|
'unsigned int': 'unsigned int',
|
|
'sizetype' : 'unsigned long',
|
|
}
|
|
|
|
|
|
def __init__(self, data = None):
|
|
self.current_level = -1
|
|
self.name_stack = []
|
|
self.id_to_name = {}
|
|
self.all_vtypes = {}
|
|
self.vtypes = {}
|
|
self.enums = {}
|
|
self.all_vars = {}
|
|
self.vars = {}
|
|
self.all_local_vars = []
|
|
self.local_vars = []
|
|
self.anons = 0
|
|
self.base = 10
|
|
|
|
if data:
|
|
for line in data.splitlines():
|
|
self.feed_line(line)
|
|
|
|
def resolve(self, memb):
|
|
"""Lookup anonymous member and replace it with a well known one."""
|
|
# Reference to another type
|
|
if isinstance(memb, str) and memb.startswith('<'):
|
|
if memb[1:3] == "0x":
|
|
memb = "<0x" + memb[3:].lstrip('0')
|
|
|
|
resolved = self.id_to_name[memb[1:]]
|
|
|
|
return self.resolve(resolved)
|
|
|
|
elif isinstance(memb, list):
|
|
return [self.resolve(r) for r in memb]
|
|
else:
|
|
# Literal
|
|
return memb
|
|
|
|
def resolve_refs(self):
|
|
"""Replace references with types."""
|
|
for v in self.vtypes:
|
|
for m in self.vtypes[v][1]:
|
|
self.vtypes[v][1][m] = self.resolve(self.vtypes[v][1][m])
|
|
|
|
return self.vtypes
|
|
|
|
def deep_replace(self, t, search, repl):
|
|
"""Recursively replace anonymous references."""
|
|
if t == search:
|
|
return repl
|
|
|
|
elif isinstance(t, list):
|
|
return [self.deep_replace(x, search, repl) for x in t]
|
|
else:
|
|
return t
|
|
|
|
def get_deepest(self, t):
|
|
if isinstance(t, list):
|
|
if len(t) == 1:
|
|
return t[0]
|
|
else:
|
|
for part in t:
|
|
res = self.get_deepest(part)
|
|
if res:
|
|
return res
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
def base_type_name(self, data):
|
|
"""Replace references to base types."""
|
|
if 'DW_AT_name' in data:
|
|
return self.tp2vol[data['DW_AT_name'].strip('"')]
|
|
else:
|
|
sz = int(data['DW_AT_byte_size'], self.base)
|
|
if data['DW_AT_encoding'] == 'DW_ATE_unsigned':
|
|
return 'unsigned ' + self.sz2tp[sz]
|
|
else:
|
|
return self.sz2tp[sz]
|
|
|
|
def feed_line(self, line):
|
|
"""Accepts another line from the input.
|
|
|
|
A DWARF line looks like:
|
|
<2><1442><DW_TAG_member> DW_AT_name<fs> ...
|
|
|
|
The header is level, statement_id, and kind followed by key value pairs.
|
|
"""
|
|
# Does the header match?
|
|
m = self.dwarf_header_regex.match(line)
|
|
|
|
if self.dwarf_header_regex2.match(line):
|
|
m = self.dwarf_header_regex2.match(line)
|
|
self.base = 16
|
|
|
|
if m:
|
|
parsed = m.groupdict()
|
|
parsed['data'] = {}
|
|
# Now parse the key value pairs
|
|
while m:
|
|
i = m.end()
|
|
m = self.dwarf_key_val_regex.search(line, i)
|
|
if m:
|
|
d = m.groupdict()
|
|
parsed['data'][d['keyname']] = d['val']
|
|
|
|
if parsed['kind'] in ('DW_TAG_formal_parameter', 'DW_TAG_variable'):
|
|
self.process_variable(parsed['data'])
|
|
else:
|
|
self.process_statement(**parsed) #pylint: disable-msg=W0142
|
|
|
|
def process_statement(self, kind, level, data, statement_id):
|
|
"""Process a single parsed statement."""
|
|
new_level = int(level)
|
|
if new_level > self.current_level:
|
|
self.current_level = new_level
|
|
self.name_stack.append([])
|
|
elif new_level < self.current_level:
|
|
self.name_stack = self.name_stack[:new_level + 1]
|
|
self.current_level = new_level
|
|
|
|
self.name_stack[-1] = [kind, statement_id]
|
|
|
|
try:
|
|
parent_kind, parent_name = self.name_stack[-2]
|
|
except IndexError:
|
|
parent_kind, parent_name = (None, None)
|
|
|
|
if kind == 'DW_TAG_compile_unit':
|
|
self.finalize()
|
|
self.vtypes = {}
|
|
self.vars = {}
|
|
self.all_local_vars += self.local_vars
|
|
self.local_vars = []
|
|
self.id_to_name = {}
|
|
|
|
elif kind == 'DW_TAG_structure_type':
|
|
name = data.get('DW_AT_name', "__unnamed_%s" % statement_id).strip('"')
|
|
|
|
self.name_stack[-1][1] = name
|
|
self.id_to_name[statement_id] = [name]
|
|
|
|
# If it's just a forward declaration, we want the name around,
|
|
# but there won't be a size
|
|
if 'DW_AT_declaration' not in data:
|
|
self.vtypes[name] = [ int(data['DW_AT_byte_size'], self.base), {} ]
|
|
|
|
elif kind == 'DW_TAG_union_type':
|
|
name = data.get('DW_AT_name', "__unnamed_%s" % statement_id).strip('"')
|
|
self.name_stack[-1][1] = name
|
|
self.id_to_name[statement_id] = [name]
|
|
self.vtypes[name] = [ int(data['DW_AT_byte_size'], self.base), {} ]
|
|
|
|
elif kind == 'DW_TAG_array_type':
|
|
self.name_stack[-1][1] = statement_id
|
|
self.id_to_name[statement_id] = data['DW_AT_type']
|
|
|
|
elif kind == 'DW_TAG_enumeration_type':
|
|
name = data.get('DW_AT_name', "__unnamed_%s" % statement_id).strip('"')
|
|
self.name_stack[-1][1] = name
|
|
self.id_to_name[statement_id] = [name]
|
|
|
|
# If it's just a forward declaration, we want the name around,
|
|
# but there won't be a size
|
|
if 'DW_AT_declaration' not in data:
|
|
sz = int(data['DW_AT_byte_size'], self.base)
|
|
self.enums[name] = [sz, {}]
|
|
|
|
elif kind == 'DW_TAG_pointer_type':
|
|
self.id_to_name[statement_id] = ['pointer', data.get('DW_AT_type', ['void'])]
|
|
|
|
elif kind == 'DW_TAG_base_type':
|
|
self.id_to_name[statement_id] = [self.base_type_name(data)]
|
|
|
|
elif kind == 'DW_TAG_volatile_type':
|
|
self.id_to_name[statement_id] = data.get('DW_AT_type', ['void'])
|
|
|
|
elif kind == 'DW_TAG_const_type':
|
|
self.id_to_name[statement_id] = data.get('DW_AT_type', ['void'])
|
|
|
|
elif kind == 'DW_TAG_typedef':
|
|
self.id_to_name[statement_id] = data['DW_AT_type']
|
|
|
|
elif kind == 'DW_TAG_subroutine_type':
|
|
self.id_to_name[statement_id] = ['void'] # Don't need these
|
|
|
|
elif kind == 'DW_TAG_variable' and level == '1':
|
|
if 'DW_AT_location' in data:
|
|
split = data['DW_AT_location'].split()
|
|
if len(split) > 1:
|
|
loc = int(split[1], 0)
|
|
self.vars[data['DW_AT_name']] = [loc, data['DW_AT_type']]
|
|
|
|
elif kind == 'DW_TAG_subprogram':
|
|
# IDEK
|
|
pass
|
|
|
|
elif kind == 'DW_TAG_member' and parent_kind == 'DW_TAG_structure_type':
|
|
name = data.get('DW_AT_name', "__unnamed_%s" % statement_id).strip('"')
|
|
try:
|
|
off = int(data['DW_AT_data_member_location'].split()[1])
|
|
except:
|
|
d = data['DW_AT_data_member_location']
|
|
idx = d.find("(")
|
|
|
|
if idx != -1:
|
|
d = d[:idx]
|
|
|
|
off = int(d)
|
|
|
|
if 'DW_AT_bit_size' in data and 'DW_AT_bit_offset' in data:
|
|
full_size = int(data['DW_AT_byte_size'], self.base) * 8
|
|
stbit = int(data['DW_AT_bit_offset'], self.base)
|
|
edbit = stbit + int(data['DW_AT_bit_size'], self.base)
|
|
stbit = full_size - stbit
|
|
edbit = full_size - edbit
|
|
stbit, edbit = edbit, stbit
|
|
assert stbit < edbit
|
|
memb_tp = ['BitField', dict(start_bit = stbit, end_bit = edbit)]
|
|
else:
|
|
memb_tp = data['DW_AT_type']
|
|
|
|
self.vtypes[parent_name][1][name] = [off, memb_tp]
|
|
|
|
elif kind == 'DW_TAG_member' and parent_kind == 'DW_TAG_union_type':
|
|
name = data.get('DW_AT_name', "__unnamed_%s" % statement_id).strip('"')
|
|
self.vtypes[parent_name][1][name] = [0, data['DW_AT_type']]
|
|
|
|
elif kind == 'DW_TAG_enumerator' and parent_kind == 'DW_TAG_enumeration_type':
|
|
name = data['DW_AT_name'].strip('"')
|
|
|
|
try:
|
|
val = int(data['DW_AT_const_value'])
|
|
except ValueError:
|
|
val = int(data['DW_AT_const_value'].split('(')[0], self.base)
|
|
|
|
self.enums[parent_name][1][name] = val
|
|
|
|
elif kind == 'DW_TAG_subrange_type' and parent_kind == 'DW_TAG_array_type':
|
|
if 'DW_AT_upper_bound' in data:
|
|
try:
|
|
sz = int(data['DW_AT_upper_bound'])
|
|
except ValueError:
|
|
try:
|
|
sz = int(data['DW_AT_upper_bound'].split('(')[0])
|
|
except ValueError:
|
|
# Give up
|
|
sz = 0
|
|
sz += 1
|
|
else:
|
|
sz = 0
|
|
|
|
tp = self.id_to_name[parent_name]
|
|
self.id_to_name[parent_name] = ['array', sz, tp]
|
|
else:
|
|
pass
|
|
#print "Skipping unsupported tag %s" % parsed['kind']
|
|
|
|
|
|
def process_variable(self, data):
|
|
"""Process a local variable."""
|
|
if ('DW_AT_name' in data and 'DW_AT_decl_line' in data and
|
|
'DW_AT_type' in data):
|
|
self.local_vars.append(
|
|
(data['DW_AT_name'], int(data['DW_AT_decl_line'], self.base),
|
|
data['DW_AT_decl_file'].split()[1], data['DW_AT_type']))
|
|
|
|
def finalize(self):
|
|
"""Finalize the output."""
|
|
if self.vtypes:
|
|
self.vtypes = self.resolve_refs()
|
|
self.all_vtypes.update(self.vtypes)
|
|
if self.vars:
|
|
self.vars = dict(((k, self.resolve(v)) for k, v in self.vars.items()))
|
|
self.all_vars.update(self.vars)
|
|
if self.local_vars:
|
|
self.local_vars = [ (name, lineno, decl_file, self.resolve(tp)) for
|
|
(name, lineno, decl_file, tp) in self.local_vars ]
|
|
self.all_local_vars += self.local_vars
|
|
|
|
# Get rid of unneeded unknowns (shades of Rumsfeld here)
|
|
# Needs to be done in fixed point fashion
|
|
changed = True
|
|
while changed:
|
|
changed = False
|
|
s = set()
|
|
for m in self.all_vtypes:
|
|
for t in self.all_vtypes[m][1].values():
|
|
s.add(self.get_deepest(t))
|
|
for m in self.all_vars:
|
|
s.add(self.get_deepest(self.all_vars[m][1]))
|
|
for v in list(self.all_vtypes):
|
|
if v.startswith('__unnamed_') and v not in s:
|
|
del self.all_vtypes[v]
|
|
changed = True
|
|
|
|
# Merge the enums into the types directly:
|
|
for t in self.all_vtypes:
|
|
for m in list(self.all_vtypes[t][1]):
|
|
memb = self.all_vtypes[t][1][m]
|
|
d = self.get_deepest(memb)
|
|
if d in self.enums:
|
|
sz = self.enums[d][0]
|
|
vals = dict((v, k) for k, v in self.enums[d][1].items())
|
|
self.all_vtypes[t][1][m] = self.deep_replace(
|
|
memb, [d],
|
|
['Enumeration', dict(target = self.sz2tp[sz], choices = vals)]
|
|
)
|
|
|
|
return self.all_vtypes
|
|
|
|
def print_output(self):
|
|
self.finalize()
|
|
print "linux_types = {"
|
|
|
|
for t in self.all_vtypes:
|
|
print " '%s': [ %#x, {" % (t, self.all_vtypes[t][0])
|
|
for m in sorted(self.all_vtypes[t][1], key = lambda m: self.all_vtypes[t][1][m][0]):
|
|
print " '%s': [%#x, %s]," % (m, self.all_vtypes[t][1][m][0], self.all_vtypes[t][1][m][1])
|
|
print "}],"
|
|
print "}"
|
|
print
|
|
print "linux_gvars = {"
|
|
for v in sorted(self.all_vars, key = lambda v: self.all_vars[v][0]):
|
|
print " '%s': [%#010x, %s]," % (v, self.all_vars[v][0], self.all_vars[v][1])
|
|
print "}"
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
dp = DWARFParser(open(sys.argv[1], "rb").read())
|
|
dp.print_output()
|