Package rekall :: Package plugins :: Package linux :: Module common
[frames] | no frames]

Source Code for Module rekall.plugins.linux.common

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2013 Google Inc. All Rights Reserved. 
  4  # 
  5  # This program is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or (at 
  8  # your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  # General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 18   
 19  """ 
 20  @author:       Andrew Case 
 21  @license:      GNU General Public License 2.0 or later 
 22  @contact:      atcuno@gmail.com 
 23  @organization: Digital Forensics Solutions 
 24  """ 
 25  import os 
 26  import re 
 27   
 28  from rekall import addrspace 
 29  from rekall import kb 
 30  from rekall import plugin 
 31  from rekall import obj 
 32  from rekall_lib import utils 
 33   
 34  from rekall.plugins import core 
35 36 37 -class KAllSyms(object):
38 """A parser for KAllSyms files.""" 39 # Location of the kallsyms file. We use it to do instant, accurate detection 40 # of the profile. 41 KALLSYMS_FILE = "/proc/kallsyms" 42 43 # The regular expression to parse the kallsyms file. 44 KALLSYMS_REGEXP = (r"(?P<offset>[0-9a-fA-F]+) " 45 r"(?P<type>[a-zA-Z]) " 46 r"(?P<symbol>[^ \t]+)" 47 r"(\t(?P<module>[^ ]+))?$") 48
49 - def __init__(self, session):
52
53 - def _ParseKallsym(self, line):
54 """Parses a single symbol line from a symbols file. 55 56 This is the result of obtaining symbols from nm: 57 58 0000000000 t linux_proc_banner 59 0000000010 s other_symbol [module] 60 61 Yields: 62 Tuple of offset, symbol_name, type, module 63 """ 64 65 matches = None 66 if line: 67 matches = re.match(self.KALLSYMS_REGEXP, line.rstrip("\n")) 68 69 if not matches: 70 raise ValueError("Invalid line: %s", line) 71 72 # Obtain all fields from the kallsyms entry 73 offset = matches.group("offset") 74 symbol_type = matches.group("type") 75 symbol = matches.group("symbol") 76 try: 77 module = matches.group("module") 78 except IndexError: 79 module = None 80 81 try: 82 offset = obj.Pointer.integer_to_address(int(offset, 16)) 83 except ValueError: 84 pass 85 86 return offset, symbol, symbol_type, module
87
88 - def ObtainSymbols(self):
89 """Obtain symbol names and values for a live machine. 90 91 Yields: 92 Tuples of offset, symbol_name, type, module 93 """ 94 kallsyms_as = self._OpenLiveSymbolsFile( 95 self.physical_address_space) 96 97 if not kallsyms_as: 98 return [] 99 100 # Proc files do not have a stat and the address space shows it as being 101 # of zero length. 102 if kallsyms_as.end() != 0: 103 data = kallsyms_as.read(0, kallsyms_as.end()) 104 else: 105 # Try to fully read the file 106 read_length = 1*1024*1024 107 data = kallsyms_as.read(0, read_length).strip("\x00") 108 while len(data) == read_length and read_length < 2**30: 109 read_length *= 2 110 data = kallsyms_as.read(0, read_length).strip("\x00") 111 112 return self.parse_data(data)
113
114 - def parse_data(self, data):
115 self.session.logging.debug( 116 "Found %s of size: %d", self.KALLSYMS_FILE, len(data)) 117 118 match_failures = 0 119 for line in data.split(os.linesep): 120 if match_failures >= 50: 121 break 122 123 try: 124 yield self._ParseKallsym(line) 125 except ValueError: 126 match_failures += 1 127 continue
128
129 - def _OpenLiveSymbolsFile(self, physical_address_space):
130 """Opens the live symbols file to parse.""" 131 132 return physical_address_space.get_file_address_space( 133 self.KALLSYMS_FILE)
134
135 136 -class AbstractLinuxCommandPlugin(plugin.PhysicalASMixin, 137 plugin.TypedProfileCommand, 138 plugin.ProfileCommand):
139 """A base class for all linux based plugins.""" 140 __abstract = True 141 mode = "mode_linux_memory"
142
143 144 -class AbstractLinuxParameterHook(kb.ParameterHook):
145 __abstract = True 146 mode = "mode_linux_memory"
147
148 149 -class LinuxFindDTB(AbstractLinuxCommandPlugin, core.FindDTB):
150 """A scanner for DTB values. Handles both 32 and 64 bits. 151 152 The plugin also autodetects when the guest is running as a XEN 153 ParaVirtualized guest and returns a compatible address space. 154 """ 155 156 __name = "find_dtb" 157
158 - def VerifyHit(self, dtb):
159 """Returns a valid address_space if the dtb is valid.""" 160 address_space = super(LinuxFindDTB, self).VerifyHit(dtb) 161 if address_space: 162 # Try to verify the profile by checking the linux_proc_banner. 163 # This is to discard kernel version strings found in memory we may 164 # know about but that don't really work with the current image. 165 linux_banner = address_space.session.profile.get_constant_object( 166 "linux_proc_banner", "String", vm=address_space) 167 if unicode(linux_banner).startswith(u"%s version %s"): 168 return address_space 169 170 self.session.logging.debug("Failed to verify dtb @ %#x" % dtb)
171
173 """Returns the correct address space class for this profile.""" 174 # The virtual address space implementation is chosen by the profile. 175 architecture = self.profile.metadata("arch") 176 if architecture == "AMD64": 177 178 # XEN PV guests have a mapping in p2m_top. We verify this symbol 179 # is not NULL. 180 pv_info_virt = self.profile.get_constant("pv_info") 181 182 if pv_info_virt: 183 pv_info_phys = self.profile.phys_addr(pv_info_virt) 184 pv_info = self.session.profile.pv_info( 185 offset=pv_info_phys, 186 vm=self.physical_address_space) 187 188 if pv_info.paravirt_enabled and pv_info.paravirt_enabled == 1: 189 self.session.logging.debug( 190 "Detected paravirtualized XEN guest") 191 impl = "XenParaVirtAMD64PagedMemory" 192 as_class = addrspace.BaseAddressSpace.classes[impl] 193 return as_class 194 195 elif self.profile.get_constant("arm_syscall"): 196 # An ARM address space. 197 self.session.logging.debug("Detected ARM Linux.") 198 impl = "ArmPagedMemory" 199 as_class = addrspace.BaseAddressSpace.classes[impl] 200 return as_class 201 202 return super(LinuxFindDTB, self).GetAddressSpaceImplementation()
203
204 - def dtb_hits(self):
205 """Tries to locate the DTB.""" 206 if self.profile.metadata("arch") in ("I386", "MIPS", "ARM"): 207 yield self.profile.phys_addr( 208 self.profile.get_constant("swapper_pg_dir", is_address=True)) 209 210 else: 211 yield self.profile.phys_addr( 212 self.profile.get_constant("init_level4_pgt", is_address=True))
213
214 - def render(self, renderer):
215 renderer.table_header([("DTB", "dtv", "[addrpad]"), 216 ("Valid", "valid", "")]) 217 218 for dtb in self.dtb_hits(): 219 renderer.table_row(dtb, self.VerifyHit(dtb) != None)
220
221 222 -class LinuxPlugin(plugin.KernelASMixin, AbstractLinuxCommandPlugin):
223 """Plugin which requires the kernel Address space to be loaded.""" 224 __abstract = True
225
226 227 -class LinProcessFilter(LinuxPlugin):
228 """A class for filtering processes.""" 229 230 __abstract = True 231 232 METHODS = [ 233 "InitTask" 234 ] 235 236 __args = [ 237 dict(name="pids", type="ArrayIntParser", positional=True, 238 help="One or more pids of processes to select."), 239 240 dict(name="proc_regex", type="RegEx", 241 help="A regex to select a process by name."), 242 243 dict(name="task", type="ArrayIntParser", 244 help="Kernel addresses of task structs."), 245 246 dict(name="method", choices=METHODS, type="ChoiceArray", 247 default=METHODS, 248 help="Method to list processes (Default uses all methods)."), 249 ] 250 251 @utils.safe_property
252 - def filtering_requested(self):
253 return (self.plugin_args.pids or self.plugin_args.proc_regex or 254 self.plugin_args.eprocess)
255
256 - def list_from_task_head(self):
257 for task_offset in self.plugin_args.task: 258 task = self.profile.task_struct( 259 offset=task_offset, vm=self.kernel_address_space) 260 261 yield task
262
263 - def list_tasks(self):
264 seen = set() 265 for proc in self.list_from_task_head(): 266 seen.add(proc.obj_offset) 267 268 for method in self.plugin_args.method: 269 for proc in self.session.GetParameter("pslist_%s" % method): 270 seen.add(proc) 271 272 result = [] 273 for x in seen: 274 result.append(self.profile.task_struct( 275 x, vm=self.session.kernel_address_space)) 276 277 return sorted(result, key=lambda x: x.pid)
278
279 - def filter_processes(self):
280 """Filters eprocess list using pids lists.""" 281 # If eprocess are given specifically only use those. 282 if self.plugin_args.task: 283 for task in self.list_from_task_head(): 284 yield task 285 286 else: 287 for proc in self.list_tasks(): 288 if not self.filtering_requested: 289 yield proc 290 291 else: 292 if int(proc.pid) in self.plugin_args.pids: 293 yield proc 294 295 elif (self.plugin_args.proc_regex and 296 self.plugin_args.proc_regex.match( 297 utils.SmartUnicode(proc.name))): 298 yield proc
299
300 - def virtual_process_from_physical_offset(self, physical_offset):
301 """Tries to return an task in virtual space from a physical offset. 302 303 We do this by reflecting off the list elements. 304 305 Args: 306 physical_offset: The physical offset of the process. 307 308 Returns: 309 an _TASK object or a NoneObject on failure. 310 """ 311 physical_task = self.profile.eprocess(offset=int(physical_offset), 312 vm=self.kernel_address_space.base) 313 314 # We cast our list entry in the kernel AS by following Flink into the 315 # kernel AS and then the Blink. Note the address space switch upon 316 # dereferencing the pointer. 317 our_list_entry = physical_task.tasks.next.dereference( 318 vm=self.kernel_address_space).prev.dereference() 319 320 # Now we get the task_struct object from the list entry. 321 return our_list_entry.dereference_as("task_struct", "tasks")
322
323 324 -class HeapScannerMixIn(object):
325 """A mixin for converting a scanner into a heap only scanner.""" 326
327 - def __init__(self, task=None, **kwargs):
328 super(HeapScannerMixIn, self).__init__(**kwargs) 329 self.task = task
330
331 - def scan(self, offset=0, maxlen=2**64): # pylint: disable=unused-argument
332 for vma in self.task.mm.mmap.walk_list("vm_next"): 333 start = max(vma.vm_start, self.task.mm.start_brk) 334 end = min(vma.vm_end, self.task.mm.brk) 335 336 # Only use the vmas inside the heap area. 337 for hit in super(HeapScannerMixIn, self).scan( 338 offset=start, maxlen=end-start): 339 yield hit
340
341 342 -class KernelAddressCheckerMixIn(object):
343 """A plugin mixin which does kernel address checks.""" 344
345 - def __init__(self, **kwargs):
346 super(KernelAddressCheckerMixIn, self).__init__(**kwargs) 347 348 # We use the module plugin to help us local addresses inside kernel 349 # modules. 350 self.module_plugin = self.session.plugins.lsmod(session=self.session)
351
352 353 -class Hostname(AbstractLinuxCommandPlugin):
354 __name = "hostname" 355
356 - def get_hostname(self):
357 hostname = "" 358 359 pslist_plugin = self.session.plugins.pslist(session=self.session) 360 for process in pslist_plugin.filter_processes(): 361 if not process.nsproxy or not process.nsproxy.uts_ns: 362 continue 363 task = process 364 break 365 366 profile = self.session.profile 367 default_hostname = (profile.get_kernel_config("CONFIG_DEFAULT_HOSTNAME") 368 or "(none)") 369 utsname = task.nsproxy.uts_ns.name 370 nodename = utsname.nodename.cast("String") 371 domainname = utsname.domainname.cast("String") 372 if nodename != None: 373 if domainname == default_hostname: 374 hostname = nodename 375 else: 376 hostname = "%s.%s" % (nodename, domainname) 377 return hostname
378
379 - def render(self, renderer):
380 renderer.table_header([("Hostname", "hostname", "80")]) 381 renderer.table_row(self.get_hostname())
382
383 384 385 -class LinuxPageOffset(AbstractLinuxParameterHook):
386 """The highest address for user mode/kernel mode division.""" 387 388 name = "linux_page_offset" 389
390 - def calculate(self):
391 """Returns PAGE_OFFSET.""" 392 return self.session.profile.GetPageOffset()
393
394 395 -class LinuxKASLR(AbstractLinuxParameterHook):
396 """The Kernel Address Space Randomization constant. 397 398 Note that this function assumes the profile is already correct. It is 399 not called during the profile guessing phase. So in reality this will 400 only come into play when the user provided the profile specifically. 401 """ 402 name = "kernel_slide" 403
404 - def calculate(self):
405 if self.session.GetCache("execution_phase") == "ProfileAutodetect": 406 raise RuntimeError( 407 "Attempting to get KASLR slide during profile " 408 "autodetection.") 409 410 # Try to get the kernel slide if kallsyms is available. 411 for offset, symbol, type, module in KAllSyms( 412 self.session).ObtainSymbols(): 413 if not module and type in ["t", "T"]: 414 offset_from_profile = self.session.profile.get_constant( 415 symbol) 416 417 # The KASLR is the difference between the profile and the 418 # kallsyms. 419 return offset - offset_from_profile 420 421 # We might want to scan for the kernel slide but the search space is 422 # really large, so right now we just do nothing. 423 return 0
424
425 426 -class LinuxInitTaskHook(AbstractLinuxParameterHook):
427 name = "pslist_InitTask" 428
429 - def calculate(self):
430 seen = set() 431 task_head = self.session.profile.get_constant_object( 432 "init_task", "task_struct") 433 434 for task in task_head.tasks: 435 if task.obj_offset not in seen: 436 seen.add(task.obj_offset) 437 438 return seen
439