Skip to content

Commit bfb3a29

Browse files
committed
Improve DTB tables validation (check user space entries). Add index file support for a process dump.
1 parent fc38b69 commit bfb3a29

File tree

2 files changed

+101
-16
lines changed

2 files changed

+101
-16
lines changed

volatility/plugins/linux/auto_dump_map.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from struct import pack
23
from volatility import debug, utils
34
from volatility.plugins.linux.auto_dtblist import linux_auto_dtblist
45

@@ -14,10 +15,10 @@ def __init__(self, config, *args, **kwargs):
1415
help = "Starting virtual address to dump")
1516
self._config.add_option('VA-END', short_option='e', type='int', default = None,
1617
help = "Ending virtual address to dump")
17-
self._config.add_option('OUTPUTFILE', short_option='O', default=None,
18+
self._config.add_option('DUMP-FILE', short_option='O', default=None,
1819
help="Output file to write a dump of virtual address space to")
1920
self._config.add_option('DUMP-DIR', short_option='D', default = None,
20-
help = "Directory to write the output files to")
21+
help = "Directory to write the dump files to")
2122

2223
def _parse_address(self, addr):
2324
if addr[:2].lower() == '0x': # hexadecimal
@@ -28,6 +29,27 @@ def _parse_address(self, addr):
2829
radix = 10
2930
return int(addr, radix)
3031

32+
def _process_name(self, dtb):
33+
"""Returns the name of a process associated with the given dtb."""
34+
return "DTB_{0:#010x}".format(dtb)
35+
36+
def _process_dump_filepath(self, dtb):
37+
dump_dir = self._config.DUMP_DIR
38+
if not dump_dir:
39+
debug.error("Dump directory is not specified.")
40+
process_name = self._process_name(dtb)
41+
dump_filepath = os.path.join(dump_dir, '{0}.bin'.format(process_name))
42+
if not os.path.exists(dump_filepath):
43+
return dump_filepath
44+
# The dump file path is already exist. It means that there are more
45+
# than one process named 'process_name'.
46+
name_id = 0
47+
while True:
48+
dump_filepath = os.path.join(dump_dir, '{0}_{1}.bin'.format(process_name, name_id))
49+
if not os.path.exists(dump_filepath):
50+
return dump_filepath
51+
name_id += 1
52+
3153
def calculate(self):
3254
if self._config.PROC_DTB:
3355
process_dtblist = []
@@ -61,10 +83,10 @@ def calculate(self):
6183
yield process_dtb, vaddr, page
6284

6385
def render_text(self, outfd, data):
64-
output_file = self._config.OUTPUTFILE
86+
dump_file = self._config.DUMP_FILE
6587
dump_dir = self._config.DUMP_DIR
66-
if not output_file and not dump_dir:
67-
debug.error("Please specify an output file (use option --outputfile)"
88+
if not dump_file and not dump_dir:
89+
debug.error("Please specify an output file (use option --dump-file)"
6890
" or a dump directory (use option --dump-dir).")
6991
if dump_dir and not os.path.isdir(dump_dir):
7092
debug.error("'{0}' is not a directory.".format(self._config.DUMP_DIR))
@@ -74,13 +96,15 @@ def render_text(self, outfd, data):
7496
vaddr_start = None
7597
vaddr_end = None
7698
cur_process_dtb = None
77-
# Dump all processes to a single file (OUTPUTFILE) if DUMP_DIR is not specified.
78-
dump_fd = open(output_file, 'wb') if not dump_dir else None
99+
# Dump all processes to a single file (DUMP_FILE) if DUMP_DIR is not specified.
100+
dump_fd = open(dump_file, 'wb') if not dump_dir else None
101+
index_fd = IndexFile('{0}.index'.format(dump_file)) if not dump_dir else None
79102
for process_dtb, vaddr, page in data:
80103
if process_dtb != cur_process_dtb:
81-
# Print the last range of virtual addresses of the previous process
104+
# Write the last range of virtual addresses of the previous process
82105
if vaddr_start is not None:
83106
self.table_row(outfd, cur_process_dtb, vaddr_start, vaddr_end)
107+
index_fd.write_range(vaddr_start, vaddr_end)
84108
vaddr_start = None
85109
vaddr_end = None
86110
cur_process_dtb = process_dtb
@@ -89,18 +113,46 @@ def render_text(self, outfd, data):
89113
# next process.
90114
if dump_fd:
91115
dump_fd.close()
92-
output_file = os.path.join(dump_dir, "{0:#010x}".format(process_dtb))
93-
dump_fd = open(output_file, 'wb')
116+
index_fd.close()
117+
dump_file = os.path.join(self._process_dump_filepath(process_dtb))
118+
index_file = '{0}.index'.format(dump_file)
119+
dump_fd = open(dump_file, 'wb')
120+
index_fd = IndexFile(index_file)
94121
if vaddr == vaddr_end:
95122
vaddr_end += len(page)
96123
else:
97124
if vaddr_start is not None:
98125
self.table_row(outfd, process_dtb, vaddr_start, vaddr_end)
126+
index_fd.write_range(vaddr_start, vaddr_end)
99127
vaddr_start = vaddr
100128
vaddr_end = vaddr + len(page)
101129
dump_fd.write(page)
102-
# Print the last range of virtual addresses
130+
# Write the last range of virtual addresses
103131
if vaddr_start is not None:
104132
self.table_row(outfd, process_dtb, vaddr_start, vaddr_end)
133+
index_fd.write_range(vaddr_start, vaddr_end)
105134
if dump_fd:
106-
dump_fd.close()
135+
dump_fd.close()
136+
index_fd.close()
137+
138+
139+
class IndexFile(object):
140+
"""Used to describe virtual address ranges that are stored in a dump file."""
141+
def __init__(self, filepath):
142+
self.fd = open(filepath, 'wb')
143+
self.offset = 0
144+
145+
def write_range(self, vaddr_start, vaddr_end):
146+
"""Appends index record.
147+
148+
A record is a packed structure (12 bytes):
149+
0x0: virtual address start
150+
0x4: virtual address end
151+
0x8: offset of the range within a dump file
152+
153+
"""
154+
self.fd.write(pack('III', vaddr_start, vaddr_end, self.offset))
155+
self.offset += vaddr_end - vaddr_start
156+
157+
def close(self):
158+
self.fd.close()

volatility/plugins/overlays/linux/linux_auto.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
ARM_PGD_SIZE = 0x4000 # 16 KB
77
ARM_PGD_ENTRIES = 4096
88
ARM_PGD_ENTRY_SIZE = 4
9+
# Entry offsets
10+
ARM_PGD_DOMAIN_OFFSET = 5
911

1012
linux_auto_overlay = {
1113
'VOLATILITY_MAGIC': [None, {
@@ -47,8 +49,8 @@ def generate_suggestions(self):
4749
class VolatilityDTBARM(obj.VolatilityMagic):
4850
"""A scanner for DTB values in an ARM image."""
4951

50-
def _is_valid_pgd(self, pgd_addr):
51-
"""Checks if PGD table starting at pgd_addr is valid.
52+
def _is_valid_pgd_kernel_space(self, pgd_addr):
53+
"""Checks if kernel space entries in PGD table are valid.
5254
5355
In case of 3G/1G User Space/Kernel Space mapping of virtual memory,
5456
the last quarter (entries 3072-4095) of a PGD table maintains Kernel's
@@ -69,8 +71,12 @@ def _is_valid_pgd(self, pgd_addr):
6971
for entry_num in xrange(first_kernel_entry, ARM_PGD_ENTRIES):
7072
entry_offset = entry_num * ARM_PGD_ENTRY_SIZE
7173
(pgd_entry, ) = unpack('<I', pgd_page[entry_offset:entry_offset + ARM_PGD_ENTRY_SIZE])
72-
if (pgd_entry & 0x7ff) == 0x40e:
74+
#if (pgd_entry & 0xfff) == 0b010001001110: # TODO domain 0
75+
if (pgd_entry & 0b110000001111) == 0b010000001110:
76+
# bits[1:0] == 0b10: 'pgd_entry' is a section or a supersection descriptor
77+
# AP == b01, C == 1, B == 1
7378
valid_kernel_entries += 1
79+
#debug.debug("Valid kernel entrie: {0}".format(valid_kernel_entries))
7480
valid_kernel_entries_ratio = 1.0 * valid_kernel_entries / min(as_size, 896) # FIXME 896?
7581
if valid_kernel_entries_ratio > VALID_KERNEL_ENTRIES_THRESHOLD:
7682
debug.debug("Found {0} valid kernel entries in a PGD table at physical address {1:#x}. "
@@ -80,6 +86,33 @@ def _is_valid_pgd(self, pgd_addr):
8086
return True
8187
return False
8288

89+
def _is_valid_pgd_user_space(self, pgd_addr):
90+
"""Checks if user space entries in PGD table are valid.
91+
92+
Linux kernel doesn't use fine page tables to map memory, so there
93+
shouldn't be any fine page table descriptor in a PGD table.
94+
95+
"""
96+
pgd_page = self.obj_vm.read(pgd_addr, ARM_PGD_SIZE)
97+
#define TASK_SIZE (UL(CONFIG_PAGE_OFFSET) - UL(0x01000000))
98+
user_entries = ARM_PGD_ENTRIES / 4 * 3 - 16 # the last 16M is kernel module space
99+
for entry_num in xrange(user_entries):
100+
entry_offset = entry_num * ARM_PGD_ENTRY_SIZE
101+
(pgd_entry, ) = unpack('<I', pgd_page[entry_offset:entry_offset + ARM_PGD_ENTRY_SIZE])
102+
# TODO: domain user (1)
103+
if (pgd_entry & 0b11) == 0b11: # reserved
104+
debug.debug("Found incorrect page table descriptor (entry #{0}) in a PGD table "
105+
"at physical address {1:#x}.".format(entry_num, pgd_addr))
106+
return False
107+
return True
108+
109+
def _is_valid_pgd(self, pgd_addr):
110+
"""Checks if PGD table starting at pgd_addr is valid."""
111+
if self._is_valid_pgd_kernel_space(pgd_addr) and self._is_valid_pgd_user_space(pgd_addr):
112+
debug.debug("Found a valid PGD table at physical address {0:#x}.".format(pgd_addr))
113+
return True
114+
return False
115+
83116
def generate_suggestions(self):
84117
"""Tries to locate DTBs.
85118
@@ -115,4 +148,4 @@ def modification(self, profile):
115148
profile.object_classes.update({
116149
'VolatilityDTB': VolatilityDTB,
117150
'VolatilityLinuxAutoARMValidAS': VolatilityLinuxAutoARMValidAS,
118-
})
151+
})

0 commit comments

Comments
 (0)