# Volatility # Copyright (C) 2010 Brendan Dolan-Gavitt # Copyright (c) 2011 Michael Cohen # # 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 Version 2 as # published by the Free Software Foundation. You may not use, modify or # distribute this program under any other version of the GNU General # Public License. # # 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 . # """ @author: Brendan Dolan-Gavitt @license: GNU General Public License 2.0 @contact: brendandg@gatech.edu @organization: Georgia Institute of Technology """ import os, struct, socket import copy import zipfile import volatility.plugins import volatility.plugins.overlays.basic as basic import volatility.plugins.overlays.native_types as native_types import volatility.exceptions as exceptions import volatility.obj as obj import volatility.debug as debug import volatility.dwarf as dwarf import volatility.plugins.linux.common as linux_common import volatility.plugins.linux.flags as linux_flags import volatility.addrspace as addrspace import volatility.utils as utils import volatility.protos as protos x64_native_types = copy.deepcopy(native_types.x64_native_types) x64_native_types['long'] = [8, ' output.dwarf """ dwarfdata = None sysmapdata = None # XXX Do we want to initialize this memmodel, arch = "32bit", "x86" profilename = os.path.splitext(os.path.basename(profpkg.filename))[0] for f in profpkg.filelist: if f.filename.lower().endswith('.dwarf'): dwarfdata = profpkg.read(f.filename) elif 'system.map' in f.filename.lower(): sysmapdata = profpkg.read(f.filename) arch, memmodel, sysmap = parse_system_map(profpkg.read(f.filename), "kernel") if memmodel == "64bit": arch = "x64" if not sysmapdata or not dwarfdata: # Might be worth throwing an exception here? return None class AbstractLinuxProfile(obj.Profile): __doc__ = "A Profile for Linux " + profilename + " " + arch _md_os = "linux" _md_memory_model = memmodel _md_arch = arch # Override 64-bit native_types native_mapping = {'32bit': native_types.x86_native_types, '64bit': x64_native_types} def __init__(self, *args, **kwargs): # change the name to catch any code referencing the old hash table self.sys_map = {} self.sym_addr_cache = {} obj.Profile.__init__(self, *args, **kwargs) def clear(self): """Clear out the system map, and everything else""" self.sys_map = {} obj.Profile.clear(self) def reset(self): """Reset the vtypes, sysmap and apply modifications, then compile""" self.clear() self.load_vtypes() self.load_sysmap() self.load_modifications() self.compile() def _merge_anonymous_members(self, vtypesvar): members_index = 1 types_index = 1 offset_index = 0 try: for candidate in vtypesvar: done = False while not done: if any(member.startswith('__unnamed_') for member in vtypesvar[candidate][members_index]): for member in vtypesvar[candidate][members_index].keys(): if member.startswith('__unnamed_'): member_type = vtypesvar[candidate][members_index][member][types_index][0] location = vtypesvar[candidate][members_index][member][offset_index] vtypesvar[candidate][members_index].update(vtypesvar[member_type][members_index]) for name in vtypesvar[member_type][members_index].keys(): vtypesvar[candidate][members_index][name][offset_index] += location del vtypesvar[candidate][members_index][member] # Don't update done because we'll need to check if any # of the newly imported types need merging else: done = True except KeyError, e: import pdb pdb.set_trace() raise exceptions.VolatilityException("Inconsistent linux profile - unable to look up " + str(e)) def load_vtypes(self): """Loads up the vtypes data""" ntvar = self.metadata.get('memory_model', '32bit') self.native_types = copy.deepcopy(self.native_mapping.get(ntvar)) vtypesvar = dwarf.DWARFParser(dwarfdata).finalize() self._merge_anonymous_members(vtypesvar) self.vtypes.update(vtypesvar) debug.debug("{2}: Found dwarf file {0} with {1} symbols".format(f.filename, len(vtypesvar.keys()), profilename)) def load_sysmap(self): """Loads up the system map data""" arch, _memmodel, sysmapvar = parse_system_map(sysmapdata, "kernel") debug.debug("{2}: Found system file {0} with {1} symbols".format(f.filename, len(sysmapvar.keys()), profilename)) self.sys_map.update(sysmapvar) def get_all_symbols(self, module = "kernel"): """ Gets all the symbol tuples for the given module """ ret = [] symtable = self.sys_map if module in symtable: mod = symtable[module] for (name, addrs) in mod.items(): ret.append(addrs) else: debug.info("All symbols requested for non-existent module %s" % module) return ret def get_all_addresses(self, module = "kernel"): """ Gets all the symbol addresses for the given module """ # returns a hash table for quick looks # the main use of this function is to see if an address is known ret = {} symbols = self.get_all_symbols(module) for sym in symbols: for (addr, addrtype) in sym: ret[addr] = 1 return ret def _get_symbol_by_address(self, module, sym_address): ret = "" symtable = self.sys_map mod = symtable[module] for (name, addrs) in mod.items(): for (addr, addr_type) in addrs: if sym_address == addr: ret = name break return ret def get_symbol_by_address(self, module, sym_address): key = "%s|%d" % (module, sym_address) if key in self.sym_addr_cache: ret = self.sym_addr_cache[key] else: ret = self._get_symbol_by_address(module, sym_address) self.sym_addr_cache[key] = ret return ret def get_all_symbol_names(self, module = "kernel"): symtable = self.sys_map if module in symtable: ret = symtable[module].keys() else: debug.error("get_all_symbol_names called on non-existent module") return ret def get_next_symbol_address(self, sym_name, module = "kernel"): """ This is used to find the address of the next symbol in the profile For some data structures, we cannot determine their size automaticlaly so this can be used to figure it out on the fly """ high_addr = 0xffffffffffffffff table_addr = self.get_symbol(sym_name, module = module) addrs = self.get_all_addresses(module = module) for addr in addrs.keys(): if table_addr < addr < high_addr: high_addr = addr return high_addr def get_symbol(self, sym_name, nm_type = "", module = "kernel"): """Gets a symbol out of the profile sym_name -> name of the symbol nm_tyes -> types as defined by 'nm' (man nm for examples) module -> which module to get the symbol from, default is kernel, otherwise can be any name seen in 'lsmod' This fixes a few issues from the old static hash table method: 1) Conflicting symbols can be handled, if a symbol is found to conflict on any profile, then the plugin will need to provide the nm_type to differentiate, otherwise the plugin will be errored out 2) Can handle symbols gathered from modules on disk as well from the static kernel symtable is stored as a hash table of: symtable[module][sym_name] = [(symbol address, symbol type), (symbol addres, symbol type), ...] The function has overly verbose error checking on purpose... """ symtable = self.sys_map ret = None # check if the module is there... if module in symtable: mod = symtable[module] # check if the requested symbol is in the module if sym_name in mod: sym_list = mod[sym_name] # if a symbol has multiple definitions, then the plugin needs to specify the type if len(sym_list) > 1: if nm_type == "": debug.debug("Requested symbol {0:s} in module {1:s} has multiple definitions and no type given\n".format(sym_name, module)) return None else: for (addr, stype) in sym_list: if stype == nm_type: ret = addr break if ret == None: debug.error("Requested symbol {0:s} in module {1:s} could not be found\n".format(sym_name, module)) else: # get the address of the symbol ret = sym_list[0][0] else: debug.debug("Requested symbol {0:s} not found in module {1:s}\n".format(sym_name, module)) else: debug.info("Requested module {0:s} not found in symbol table\n".format(module)) return ret def get_symbol_type(self, sym_name, nm_type = "", module = "kernel"): symtable = self.sys_map ret = None # check if the module is there... if module in symtable: mod = symtable[module] # check if the requested symbol is in the module if sym_name in mod: sym_list = mod[sym_name] # if a symbol has multiple definitions, then the plugin needs to specify the type if len(sym_list) > 1: if nm_type == "": debug.debug("Requested symbol {0:s} in module {1:s} has multiple definitions and no type given\n".format(sym_name, module)) return None else: for (addr, stype) in sym_list: if stype == nm_type: ret = addr break if ret == None: debug.error("Requested symbol {0:s} in module {1:s} could not be found\n".format(sym_name, module)) else: # get the type of the symbol ret = sym_list[0][1] else: debug.debug("Requested symbol {0:s} not found in module {1:s}\n".format(sym_name, module)) else: debug.info("Requested module {0:s} not found in symbol table\n".format(module)) return ret cls = AbstractLinuxProfile cls.__name__ = 'Linux' + profilename.replace('.', '_') + arch return cls ################################ # Track down the zip files # Push them through the factory # Check whether ProfileModifications will work new_classes = [] for path in set(volatility.plugins.__path__): for path, _, files in os.walk(path): for fn in files: if zipfile.is_zipfile(os.path.join(path, fn)): new_classes.append(LinuxProfileFactory(zipfile.ZipFile(os.path.join(path, fn)))) ################################ # really 'file' but don't want to mess with python's version class linux_file(obj.CType): @property def dentry(self): if hasattr(self, "f_dentry"): ret = self.f_dentry else: ret = self.f_path.dentry return ret @property def vfsmnt(self): if hasattr(self, "f_vfsmnt"): ret = self.f_vfsmnt else: ret = self.f_path.mnt return ret # FIXME - walking backwards has not been thorougly tested class hlist_node(obj.CType): """A hlist_node makes a doubly linked list.""" def list_of_type(self, obj_type, member, offset = -1, forward = True, head_sentinel = True): if not self.is_valid(): return ## Get the first element if forward: nxt = self.m("next").dereference() else: nxt = self.pprev.dereference().dereference() offset = self.obj_vm.profile.get_obj_offset(obj_type, member) seen = set() if head_sentinel: # We're a header element and not to be included in the list seen.add(self.obj_offset) while nxt.is_valid() and nxt.obj_offset not in seen: ## Instantiate the object item = obj.Object(obj_type, offset = nxt.obj_offset - offset, vm = self.obj_vm, parent = self.obj_parent, name = obj_type) seen.add(nxt.obj_offset) yield item if forward: nxt = item.m(member).m("next").dereference() else: nxt = item.m(member).pprev.dereference().dereference() def __nonzero__(self): ## List entries are valid when both Flinks and Blink are valid return bool(self.next) or bool(self.pprev) def __iter__(self): return self.list_of_type(self.obj_parent.obj_name, self.obj_name) class list_head(obj.CType): """A list_head makes a doubly linked list.""" def list_of_type(self, obj_type, member, offset = -1, forward = True, head_sentinel = True): if not self.is_valid(): return ## Get the first element if forward: nxt = self.next.dereference() else: nxt = self.prev.dereference() offset = self.obj_vm.profile.get_obj_offset(obj_type, member) seen = set() if head_sentinel: # We're a header element and not to be included in the list seen.add(self.obj_offset) while nxt.is_valid() and nxt.obj_offset not in seen: ## Instantiate the object item = obj.Object(obj_type, offset = nxt.obj_offset - offset, vm = self.obj_vm, parent = self.obj_parent, name = obj_type) seen.add(nxt.obj_offset) yield item if forward: nxt = item.m(member).m("next").dereference() else: nxt = item.m(member).prev.dereference() def __nonzero__(self): ## List entries are valid when both Flinks and Blink are valid return bool(self.next) or bool(self.prev) def __iter__(self): return self.list_of_type(self.obj_parent.obj_name, self.obj_name) class hlist_bl_node(obj.CType): """A list_head makes a doubly linked list.""" def list_of_type(self, obj_type, member, offset = -1, forward = True, head_sentinel = True): if not self.is_valid(): return ## Get the first element if forward: nxt = self.next.dereference() else: nxt = self.prev.dereference() offset = self.obj_vm.profile.get_obj_offset(obj_type, member) seen = set() if head_sentinel: # We're a header element and not to be included in the list seen.add(self.obj_offset) while nxt.is_valid() and nxt.obj_offset not in seen: ## Instantiate the object item = obj.Object(obj_type, offset = nxt.obj_offset - offset, vm = self.obj_vm, parent = self.obj_parent, name = obj_type) seen.add(nxt.obj_offset) yield item if forward: nxt = item.m(member).next.dereference() else: nxt = item.m(member).prev.dereference() def __nonzero__(self): ## List entries are valid when both Flinks and Blink are valid return bool(self.next) or bool(self.prev) def __iter__(self): return self.list_of_type(self.obj_parent.obj_name, self.obj_name) class files_struct(obj.CType): def get_fds(self): if hasattr(self, "fdt"): fdt = self.fdt ret = fdt.fd.dereference() else: ret = self.fd.dereference() return ret def get_max_fds(self): if hasattr(self, "fdt"): ret = self.fdt.max_fds else: ret = self.max_fds return ret class kernel_param(obj.CType): @property def get(self): if self.members.get("get"): ret = self.m("get") else: ret = self.ops.get return ret class kparam_array(obj.CType): @property def get(self): if self.members.get("get"): ret = self.m("get") else: ret = self.ops.get return ret class gate_struct64(obj.CType): @property def Address(self): low = self.offset_low middle = self.offset_middle high = self.offset_high ret = (high << 32) | (middle << 16) | low return ret class desc_struct(obj.CType): @property def Address(self): return (self.b & 0xffff0000) | (self.a & 0x0000ffff) class module_sect_attr(obj.CType): @property def sect_name(self): if type(self.m("name")) == obj.Array: name = obj.Object("String", offset = self.m("name").obj_offset, vm = self.obj_vm, length = 32) else: name = self.name.dereference_as("String", length = 255) return str(name) class sock(obj.CType): @property def sk_node(self): return self.__sk_common.skc_node #pylint: disable-msg=W0212 class inet_sock(obj.CType): """Class for an internet socket object""" @property def protocol(self): """Return the protocol string (i.e. IPv4, IPv6)""" return protos.protos.get(self.sk.sk_protocol.v(), "UNKNOWN") @property def state(self): state = self.sk.__sk_common.skc_state #pylint: disable-msg=W0212 return linux_flags.tcp_states[state] @property def src_port(self): if hasattr(self, "sport"): return socket.htons(self.sport) elif hasattr(self, "inet_sport"): return socket.htons(self.inet_sport) else: return None @property def dst_port(self): if hasattr(self, "dport"): return socket.htons(self.dport) elif hasattr(self, "inet_dport"): return socket.htons(self.inet_dport) elif hasattr(self, "sk") and hasattr(self.sk, "__sk_common") and hasattr(self.sk.__sk_common, "skc_dport"): return self.sk.__sk_common.skc_dport else: return None @property def src_addr(self): if self.sk.__sk_common.skc_family == socket.AF_INET: # FIXME: Consider using kernel version metadata rather than checking hasattr if hasattr(self, "rcv_saddr"): saddr = self.rcv_saddr elif hasattr(self, "inet_rcv_saddr"): saddr = self.inet_rcv_saddr else: saddr = self.sk.__sk_common.skc_rcv_saddr return saddr.cast("IpAddress") else: return self.pinet6.saddr.cast("Ipv6Address") @property def dst_addr(self): if self.sk.__sk_common.skc_family == socket.AF_INET: # FIXME: Consider using kernel version metadata rather than checking hasattr if hasattr(self, "daddr") and self.daddr: daddr = self.daddr elif hasattr(self, "inet_daddr") and self.inet_daddr: daddr = self.inet_daddr else: daddr = self.sk.__sk_common.skc_daddr return daddr.cast("IpAddress") else: if hasattr(self.pinet6, "daddr"): return self.pinet6.daddr.cast("Ipv6Address") else: return self.sk.__sk_common.skc_v6_daddr.cast("Ipv6Address") #pylint: disable-msg=W0212 class tty_ldisc(obj.CType): @property def ops(self): check = self.members.get("ops") if check: ret = self.m('ops') else: ret = self return ret class in_device(obj.CType): def devices(self): cur = self.ifa_list while cur != None and cur.is_valid(): yield cur cur = cur.ifa_next class net_device(obj.CType): @property def mac_addr(self): macaddr = "00:00:00:00:00:00" if self.members.has_key("perm_addr"): hwaddr = self.perm_addr macaddr = ":".join(["{0:02x}".format(x) for x in hwaddr][:6]) if macaddr == "00:00:00:00:00:00": hwaddr = self.obj_vm.zread(self.dev_addr, 6) macaddr = ":".join(["{0:02x}".format(ord(x)) for x in hwaddr][:6]) return macaddr @property def promisc(self): return self.flags & 0x100 == 0x100 # IFF_PROMISC class module_struct(obj.CType): def _get_sect_count(self, grp): arr = obj.Object(theType = 'Array', offset = grp.attrs, vm = self.obj_vm, targetType = 'Pointer', count = 25) idx = 0 while arr[idx]: idx = idx + 1 return idx def get_sections(self): if hasattr(self.sect_attrs, "nsections"): num_sects = self.sect_attrs.nsections else: num_sects = self._get_sect_count(self.sect_attrs.grp) attrs = obj.Object(theType = 'Array', offset = self.sect_attrs.attrs.obj_offset, vm = self.obj_vm, targetType = 'module_sect_attr', count = num_sects) for attr in attrs: yield attr def get_param_val(self, param, _over = 0): ints = { self.obj_vm.profile.get_symbol("param_get_invbool") : "int", self.obj_vm.profile.get_symbol("param_get_bool") : "int", self.obj_vm.profile.get_symbol("param_get_int") : "int", self.obj_vm.profile.get_symbol("param_get_ulong") : "unsigned long", self.obj_vm.profile.get_symbol("param_get_long") : "long", self.obj_vm.profile.get_symbol("param_get_uint") : "unsigned int", self.obj_vm.profile.get_symbol("param_get_ushort") : "unsigned short", self.obj_vm.profile.get_symbol("param_get_short") : "short", self.obj_vm.profile.get_symbol("param_get_byte") : "char", } getfn = param.get if getfn == 0: val = "" elif getfn == self.obj_vm.profile.get_symbol("param_array_get"): val = "" arr = param.arr overwrite = param.arr if arr.num: maxi = arr.num.dereference() else: maxi = arr.max for i in range(maxi): if i > 0: val = val + "," arg = arr.elem + arr.elemsize * i overwrite.arg = arg mret = self.get_param_val(overwrite) val = val + str(mret or '') elif getfn == self.obj_vm.profile.get_symbol("param_get_string"): val = param.str.dereference_as("String", length = param.str.maxlen) elif getfn == self.obj_vm.profile.get_symbol("param_get_charp"): addr = obj.Object("Pointer", offset = param.arg, vm = self.obj_vm) if addr == 0: val = "(null)" else: val = addr.dereference_as("String", length = 256) elif getfn.v() in ints: val = obj.Object(ints[getfn.v()], offset = param.arg, vm = self.obj_vm) if getfn == self.obj_vm.profile.get_symbol("param_get_bool"): if val: val = 'Y' else: val = 'N' if getfn == self.obj_vm.profile.get_symbol("param_get_invbool"): if val: val = 'N' else: val = 'Y' else: print "Unknown get_fn: {0:#x}".format(getfn) return None return val def get_params(self): params = "" param_array = obj.Object(theType = 'Array', offset = self.kp, vm = self.obj_vm, targetType = 'kernel_param', count = self.num_kp) for param in param_array: val = self.get_param_val(param) params = params + "{0}={1} ".format(param.name.dereference_as("String", length = 255), val) return params def get_symbols(self): ret_syms = [] if self.obj_vm.profile.metadata.get('arch').lower() == 'x64': struct_name = "elf64_sym" else: struct_name = "elf32_sym" syms = obj.Object(theType = "Array", targetType = struct_name, offset = self.symtab, count = self.num_symtab + 1, vm = self.obj_vm) for sym_struct in syms: sym_name_addr = self.strtab + sym_struct.st_name sym_name = self.obj_vm.read(sym_name_addr, 64) if not sym_name: continue idx = sym_name.index("\x00") if idx != -1: sym_name = sym_name[:idx] if sym_name != "": ret_syms.append((str(sym_name), sym_struct.st_value.v())) return ret_syms def get_symbol_for_address(self, wanted_address): ret = None for (sym_name, sym_addr) in self.get_symbols(): if sym_addr == wanted_address: ret = sym_name break return ret def get_symbol(self, wanted_sym_name): ret = None for (sym_name, sym_addr) in self.get_symbols(): if wanted_sym_name == sym_name: ret = sym_addr break return ret def is_valid(self): valid = False if self.state.v() in [0, 1, 2] and \ self.core_size >= 1 and self.core_size <= 1000000 and \ self.core_text_size >= 1 and self.core_text_size <= 1000000: s = self.obj_vm.read(self.name.obj_offset, 64) if s: idx = s.find("\x00") if idx > 1: good = True name = s[:idx] for n in name: if not (32 < ord(n) < 127): good = False break if good and self.module_core.is_valid(): valid = True return valid class vm_area_struct(obj.CType): def vm_name(self, task): if self.vm_file: fname = linux_common.get_path(task, self.vm_file) elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: fname = "[heap]" elif self.vm_start <= task.mm.start_stack and self.vm_end >= task.mm.start_stack: fname = "[stack]" elif hasattr(self.vm_mm.context, "vdso") and self.vm_start == self.vm_mm.context.vdso: fname = "[vdso]" else: fname = "Anonymous Mapping" return fname extended_flags = { 0x00000001 : "VM_READ", 0x00000002 : "VM_WRITE", 0x00000004 : "VM_EXEC", 0x00000008 : "VM_SHARED", 0x00000010 : "VM_MAYREAD", 0x00000020 : "VM_MAYWRITE", 0x00000040 : "VM_MAYEXEC", 0x00000080 : "VM_MAYSHARE", 0x00000100 : "VM_GROWSDOWN", 0x00000200 : "VM_NOHUGEPAGE", 0x00000400 : "VM_PFNMAP", 0x00000800 : "VM_DENYWRITE", 0x00001000 : "VM_EXECUTABLE", 0x00002000 : "VM_LOCKED", 0x00004000 : "VM_IO", 0x00008000 : "VM_SEQ_READ", 0x00010000 : "VM_RAND_READ", 0x00020000 : "VM_DONTCOPY", 0x00040000 : "VM_DONTEXPAND", 0x00080000 : "VM_RESERVED", 0x00100000 : "VM_ACCOUNT", 0x00200000 : "VM_NORESERVE", 0x00400000 : "VM_HUGETLB", 0x00800000 : "VM_NONLINEAR", 0x01000000 : "VM_MAPPED_COP__VM_HUGEPAGE", 0x02000000 : "VM_INSERTPAGE", 0x04000000 : "VM_ALWAYSDUMP", 0x08000000 : "VM_CAN_NONLINEAR", 0x10000000 : "VM_MIXEDMAP", 0x20000000 : "VM_SAO", 0x40000000 : "VM_PFN_AT_MMAP", 0x80000000 : "VM_MERGEABLE", } def _parse_perms(self, flags): fstr = "" for mask in sorted(self.extended_flags.keys()): if flags & mask == mask: fstr = fstr + self.extended_flags[mask] + "|" if len(fstr) != 0: fstr = fstr[:-1] return fstr def protection(self): return self._parse_perms(self.vm_flags.v() & 0b1111) def flags(self): return self._parse_perms(self.vm_flags.v()) # used by malfind def is_suspicious(self): ret = False flags_str = self.flags() if flags_str == "VM_READ|VM_WRITE|VM_EXEC": ret = True elif flags_str == "VM_READ|VM_EXEC" and not self.vm_file: ret = True return ret def info(self, task): if self.vm_file: inode = self.vm_file.dentry.d_inode major, minor = inode.i_sb.major, inode.i_sb.minor ino = inode.i_ino pgoff = self.vm_pgoff << 12 else: (major, minor, ino, pgoff) = [0] * 4 fname = self.vm_name(task) if fname == "Anonymous Mapping": fname = "" return fname, major, minor, ino, pgoff class task_struct(obj.CType): def is_valid_task(self): ret = self.fs.v() != 0 and self.files.v() != 0 if ret and self.members.get("cred"): ret = self.cred.is_valid() return ret def getcwd(self): rdentry = self.fs.get_root_dentry() rmnt = self.fs.get_root_mnt() pdentry = self.fs.get_pwd_dentry() pmnt = self.fs.get_pwd_mnt() path = linux_common.do_get_path(rdentry, rmnt, pdentry, pmnt) if path == []: path = "" return path @property def uid(self): ret = self.members.get("uid") if ret is None: if hasattr(self.cred.uid, "val"): ret = self.cred.uid.val else: ret = self.cred.uid else: ret = self.m("uid") return ret @property def gid(self): ret = self.members.get("gid") if ret is None: gid = self.cred.gid if hasattr(gid, 'counter'): ret = obj.Object("int", offset = gid.v(), vm = self.obj_vm) elif hasattr(gid, "val"): ret = gid.val else: ret = gid else: ret = self.m("gid") return ret @property def euid(self): ret = self.members.get("euid") if ret is None: ret = self.cred.euid else: ret = self.m("euid") return ret def find_heap_vma(self): ret = None for vma in self.get_proc_maps(): # find the data section of bash if vma.vm_start <= self.mm.start_brk and vma.vm_end >= self.mm.brk: ret = vma break return ret def bash_hash_entries(self): nbuckets_offset = self.obj_vm.profile.get_obj_offset("_bash_hash_table", "nbuckets") heap_vma = self.find_heap_vma() if heap_vma == None: debug.debug("Unable to find heap for pid %d" % self.pid) return proc_as = self.get_process_address_space() for off in self.search_process_memory(["\x40\x00\x00\x00"], heap_only=True): # test the number of buckets htable = obj.Object("_bash_hash_table", offset = off - nbuckets_offset, vm = proc_as) if htable.is_valid(): bucket_array = obj.Object(theType="Array", targetType="Pointer", offset = htable.bucket_array, vm = htable.nbuckets.obj_vm, count = 64) for bucket_ptr in bucket_array: bucket = bucket_ptr.dereference_as("bucket_contents") while bucket.times_found > 0 and bucket.data.is_valid() and bucket.key.is_valid(): pdata = bucket.data if pdata.path.is_valid() and (0 <= pdata.flags <= 2): yield bucket bucket = bucket.next off = off + 1 def ldrmodules(self): proc_maps = {} dl_maps = {} seen_starts = {} proc_as = self.get_process_address_space() if not proc_as: return # get libraries from proc_maps for vma in self.get_proc_maps(): sig = proc_as.read(vma.vm_start, 4) if sig == "\x7fELF": flags = str(vma.vm_flags) if flags in ["rw-", "r--"]: continue fname = vma.vm_name(self) if fname == "[vdso]": continue start = vma.vm_start.v() proc_maps[start] = fname seen_starts[start] = 1 # get libraries from userland for so in self.get_libdl_maps(): if so.l_addr == 0x0 or len(str(so.l_name)) == 0: continue start = so.l_addr.v() dl_maps[start] = str(so.l_name) seen_starts[start] = 1 for start in seen_starts: vm_name = "" if start in proc_maps: pmaps = "True" vm_name = proc_maps[start] else: pmaps = "False" if start in dl_maps: dmaps = "True" # we prefer the name from proc_maps as it is within kernel memory if vm_name == "": vm_name = dl_maps[start] else: dmaps = "False" yield (start, vm_name, pmaps, dmaps) def plt_hook_info(self): elfs = dict() for elf, elf_start, elf_end, soname, needed in self.elfs(): elfs[(self, soname)] = (elf, elf_start, elf_end, needed) for k, v in elfs.iteritems(): task, soname = k elf, elf_start, elf_end, needed = v if elf._get_typename("hdr") == "elf32_hdr": elf_arch = 32 else: elf_arch = 64 needed_expanded = set([soname]) if (task, None) in elfs: needed_expanded.add(None) # jmp slot can point to ELF itself if the fn hasn't been called yet (RTLD_LAZY) # can point to main binary (None above) if this is a plugin-style symbol while len(needed) > 0: dep = needed.pop(0) needed_expanded.add(dep) try: needed += set(elfs[(task, dep)][3]) - needed_expanded except KeyError: needed_expanded.remove(dep) for reloc in elf.relocations(): rsym = elf.relocation_symbol(reloc) if rsym == None: continue symbol_name = elf.symbol_name(rsym) if symbol_name == None: symbol_name = "" offset = reloc.r_offset if offset < elf_start: offset = elf_start + offset if elf_arch == 32: addr = obj.Object("unsigned int", offset = offset, vm = elf.obj_vm) else: addr = obj.Object("unsigned long long", offset = offset, vm = elf.obj_vm) match = False for dep in needed_expanded: _, dep_start, dep_end, _ = elfs[(task, dep)] if addr >= dep_start and addr < dep_end: match = dep hookdesc = '' vma = None for i in task.get_proc_maps(): if addr >= i.vm_start and addr < i.vm_end: vma = i break if vma: if vma.vm_file: hookdesc = linux_common.get_path(task, vma.vm_file) else: hookdesc = '[{0:x}:{1:x},{2}]'.format(vma.vm_start, vma.vm_end, vma.vm_flags) if hookdesc == "": hookdesc = 'invalid memory' if match != False: if match == soname: hookdesc = '[RTLD_LAZY]' hooked = False else: hooked = True yield soname, elf, elf_start, elf_end, addr, symbol_name, hookdesc, hooked def _is_api_hooked(self, sym_addr, proc_as): hook_type = None addr = None counter = 1 prev_op = None if self.obj_vm.profile.metadata.get('memory_model', '32bit') == '32bit': mode = distorm3.Decode32Bits else: mode = distorm3.Decode64Bits data = proc_as.read(sym_addr, 24) for op in distorm3.Decompose(sym_addr, data, mode): if not op or not op.valid: continue if op.mnemonic == "JMP": hook_type = "JMP" addr = 0 # default in case we cannot extract # check for a mov reg, addr; jmp reg; if prev_op and prev_op.mnemonic == "MOV" and prev_op.operands[0].type == 'Register' and op.operands[0].type == 'Register': prev_name = prev_op.operands[0].name # same register if prev_name == op.operands[0].name: addr = prev_op.operands[1].value else: addr = op.operands[0].value elif op.mnemonic == "CALL": hook_type = "CALL" addr = op.operands[0].value # push xxxx; ret; elif counter == 2 and op.mnemonic == "RET": if prev_op.mnemonic == "MOV" and prev_op.operands[0].type == 'Register' and prev_op.operands[0].name in ["RAX", "EAX"]: break elif prev_op.mnemonic == "XOR" and prev_op.operands[0].type == 'Register' and prev_op.operands[1].type == 'Register': break elif prev_op.mnemonic == "MOV" and prev_op.operands[0].type == 'Register' and prev_op.operands[1].type == 'Register': break hook_type = "RET" addr = sym_addr if hook_type: break counter = counter + 1 if counter == 4: break prev_op = op if hook_type and addr: ret = hook_type, addr else: ret = None return ret def _get_hooked_name(self, addr): hook_vma = None hookdesc = "" for i in self.get_proc_maps(): if addr >= i.vm_start and addr < i.vm_end: hook_vma = i break if hook_vma: if hook_vma.vm_file: hookdesc = linux_common.get_path(self, hook_vma.vm_file) else: hookdesc = '[{0:x}:{1:x},{2}]'.format(hook_vma.vm_start, hook_vma.vm_end, hook_vma.vm_flags) return (hook_vma, hookdesc) def apihook_info(self): for soname, elf, elf_start, elf_end, addr, symbol_name, _, plt_hooked in self.plt_hook_info(): is_hooked = self._is_api_hooked(addr, elf.obj_vm) if is_hooked: hook_type, hook_addr = is_hooked else: continue (hook_vma, hookdesc) = self._get_hooked_name(addr) (hook_func_vma, hookfuncdesc) = self._get_hooked_name(hook_addr) if not hook_vma or not hook_func_vma or hook_vma.vm_start != hook_func_vma.vm_start: yield hookdesc, symbol_name, addr, hook_type, hook_addr, hookfuncdesc def bash_history_entries(self): proc_as = self.get_process_address_space() if not proc_as: return # Keep a bucket of history objects so we can order them history_entries = [] # Brute force the history list of an address isn't provided ts_offset = proc_as.profile.get_obj_offset("_hist_entry", "timestamp") # Are we dealing with 32 or 64-bit pointers if proc_as.profile.metadata.get('memory_model', '32bit') == '32bit': pack_format = "I" else: pack_format = "Q" bang_addrs = [] # Look for strings that begin with pound/hash on the process heap for ptr_hash in self.search_process_memory(["#"], heap_only = True): # Find pointers to this strings address, also on the heap bang_addrs.append(struct.pack(pack_format, ptr_hash)) for (idx, ptr_string) in enumerate(self.search_process_memory(bang_addrs, heap_only = True)): # Check if we found a valid history entry object hist = obj.Object("_hist_entry", offset = ptr_string - ts_offset, vm = proc_as) if hist.is_valid(): history_entries.append(hist) # Report everything we found in order for hist in sorted(history_entries, key = attrgetter('time_as_integer')): yield hist def psenv(self): env = "" if self.mm: # set the as with our new dtb so we can read from userland proc_as = self.get_process_address_space() # read argv from userland start = self.mm.env_start.v() env = proc_as.read(start, self.mm.env_end - self.mm.env_start + 10) if env: ents = env.split("\x00") for varstr in ents: eqidx = varstr.find("=") if eqidx == -1: continue key = varstr[:eqidx] val = varstr[eqidx+1:] yield (key, val) def bash_environment(self): # Are we dealing with 32 or 64-bit pointers if self.obj_vm.profile.metadata.get('memory_model', '32bit') == '32bit': pack_format = " 0: nullidx = buf.find("\x00") # single char name, = if nullidx >= eqidx: env_start = addr if env_start == 0: continue envars = obj.Object(theType="Array", targetType="Pointer", vm=proc_as, offset=env_start, count=256) for var in envars: if var: sizes = [8, 16, 32, 64, 128, 256, 384, 512, 1024, 2048, 4096] good_varstr = None for size in sizes: varstr = proc_as.read(var, size) if not varstr: continue eqidx = varstr.find("=") idx = varstr.find("\x00") if idx == -1 or eqidx == -1 or idx < eqidx: continue good_varstr = varstr break if good_varstr: good_varstr = good_varstr[:idx] key = good_varstr[:eqidx] val = good_varstr[eqidx+1:] yield (key, val) else: break def lsof(self): fds = self.files.get_fds() max_fds = self.files.get_max_fds() fds = obj.Object(theType = 'Array', offset = fds.obj_offset, vm = self.obj_vm, targetType = 'Pointer', count = max_fds) # mem corruption check if max_fds > 500000: return for i in range(max_fds): if fds[i]: filp = obj.Object('file', offset = fds[i], vm = self.obj_vm) yield filp, i # has to get the struct socket given an inode (see SOCKET_I in sock.h) def SOCKET_I(self, inode): # if too many of these, write a container_of backsize = self.obj_vm.profile.get_obj_size("socket") addr = inode - backsize return obj.Object('socket', offset = addr, vm = self.obj_vm) def netstat(self): sfop = self.obj_vm.profile.get_symbol("socket_file_ops") dfop = self.obj_vm.profile.get_symbol("sockfs_dentry_operations") for (filp, fdnum) in self.lsof(): if filp.f_op == sfop or filp.dentry.d_op == dfop: iaddr = filp.dentry.d_inode skt = self.SOCKET_I(iaddr) inet_sock = obj.Object("inet_sock", offset = skt.sk, vm = self.obj_vm) if inet_sock.protocol in ("TCP", "UDP", "IP", "HOPOPT"): #hopopt is where unix sockets end up on linux state = inet_sock.state if inet_sock.protocol == "TCP" else "" family = inet_sock.sk.__sk_common.skc_family #pylint: disable-msg=W0212 if family == socket.AF_UNIX: unix_sock = obj.Object("unix_sock", offset = inet_sock.sk.v(), vm = self.obj_vm) if unix_sock.addr: name_obj = obj.Object("sockaddr_un", offset = unix_sock.addr.name.obj_offset, vm = self.obj_vm) name = str(name_obj.sun_path) else: name = "" yield (socket.AF_UNIX, (name, iaddr.i_ino)) elif family in (socket.AF_INET, socket.AF_INET6): sport = inet_sock.src_port dport = inet_sock.dst_port saddr = inet_sock.src_addr daddr = inet_sock.dst_addr yield (socket.AF_INET, (inet_sock, inet_sock.protocol, saddr, sport, daddr, dport, state)) def get_process_address_space(self): ## If we've got a NoneObject, return it maintain the reason if not self.mm: return self.mm if self.mm.pgd.v() == None: return self.mm.pgd.v() directory_table_base = self.obj_vm.vtop(self.mm.pgd.v()) try: process_as = self.obj_vm.__class__( self.obj_vm.base, self.obj_vm.get_config(), dtb = directory_table_base) except AssertionError, _e: return obj.NoneObject("Unable to get process AS") process_as.name = "Process {0}".format(self.pid) return process_as def get_libdl_maps(self): proc_as = self.get_process_address_space() found_list = False for vma in self.get_proc_maps(): # find the executable part of libdl ehdr = obj.Object("elf_hdr", offset = vma.vm_start, vm = proc_as) if not ehdr.is_valid(): #print "could not get header for %d | %s" % (self.pid, self.comm) continue for phdr in ehdr.program_headers(): if str(phdr.p_type) != 'PT_DYNAMIC': continue for dsec in phdr.dynamic_sections(): # link_map is stored at the second GOT entry if dsec.d_tag == 3: # DT_PLTGOT seen_ents = {} got_start = dsec.d_ptr # size_cache tells us if we are a 32 or 64 bit ELF file link_map_addr = obj.Object("Pointer", offset = got_start + (dsec.size_cache / 8), vm = proc_as) link_map = obj.Object("elf_link_map", offset = link_map_addr, vm = proc_as, parent = dsec) for ent in link_map: if ent.obj_offset in seen_ents: continue found_list = True yield ent seen_ents[ent.obj_offset] = 1 if found_list: break def threads(self): thread_offset = self.obj_vm.profile.get_obj_offset("task_struct", "thread_group") threads = [self] x = obj.Object('task_struct', self.thread_group.next.v() - thread_offset, self.obj_vm) while x not in threads: threads.append(x) x = obj.Object('task_struct', x.thread_group.next.v() - thread_offset, self.obj_vm) return threads def get_proc_maps(self): if not self.mm: return for vma in linux_common.walk_internal_list("vm_area_struct", "vm_next", self.mm.mmap): yield vma def _walk_rb(self, rb): if not rb.is_valid(): return # container_of rboff = self.obj_vm.profile.get_obj_offset("vm_area_struct", "vm_rb") vma = obj.Object("vm_area_struct", offset = rb - rboff, vm = self.obj_vm) yield vma for vma in self._walk_rb(rb.rb_left): yield vma for vma in self._walk_rb(rb.rb_right): yield vma # based on find_vma in mm/mmap.c def get_proc_maps_rb(self): vmas = {} rb = self.mm.mm_rb.rb_node for vma in self._walk_rb(rb): vmas[vma.vm_start] = vma for key in sorted(vmas.iterkeys()): yield vmas[key] def search_process_memory(self, s, heap_only = False): # Allow for some overlap in case objects are # right on page boundaries overlap = 1024 # Make sure s in a list. This allows you to search for # multiple strings at once, without changing the API. if type(s) != list: debug.warning("Single strings to search_process_memory is deprecated, use a list instead") s = [s] scan_blk_sz = 1024 * 1024 * 10 addr_space = self.get_process_address_space() for vma in self.get_proc_maps(): if heap_only: if not (vma.vm_start <= self.mm.start_brk and vma.vm_end >= self.mm.brk): continue offset = vma.vm_start out_of_range = vma.vm_start + (vma.vm_end - vma.vm_start) while offset < out_of_range: # Read some data and match it. to_read = min(scan_blk_sz + overlap, out_of_range - offset) data = addr_space.zread(offset, to_read) if not data: break for x in s: for hit in utils.iterfind(data, x): yield offset + hit offset += min(to_read, scan_blk_sz) def elfs(self): proc_as = self.get_process_address_space() for vma in self.get_proc_maps(): elf = obj.Object("elf_hdr", offset = vma.vm_start, vm = proc_as) if not elf.is_valid(): continue pt_loads = [] dt_soname = None dt_strtab = None dt_needed = [] #### Walk pt_load and gather ranges for phdr in elf.program_headers(): if not phdr.is_valid(): continue if str(phdr.p_type) == 'PT_LOAD': pt_loads.append((phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz)) if str(phdr.p_type) != 'PT_DYNAMIC': continue for dsec in phdr.dynamic_sections(): if dsec.d_tag == 5: dt_strtab = dsec.d_ptr elif dsec.d_tag == 14: dt_soname = dsec.d_ptr elif dsec.d_tag == 1: dt_needed.append(dsec.d_ptr) break if dt_strtab == None or dt_needed == []: continue needed = [] for n_idx in dt_needed: buf = proc_as.read(dt_strtab + n_idx, 256) if buf: idx = buf.find("\x00") if idx != -1: buf = buf[:idx] if len(buf) > 0: needed.append(buf) soname = "" if dt_soname: soname = proc_as.read(dt_strtab + dt_soname, 256) if soname: idx = soname.find("\x00") if idx != -1: soname = soname[:idx] if not soname or len(soname) == 0: soname = linux_common.get_path(self, vma.vm_file) if pt_loads: (elf_start, elf_end) = (min(s[0] for s in pt_loads), max(s[1] for s in pt_loads)) else: continue # TODO - test diff without setting soname of vma if soname or needed: yield elf, elf_start, elf_end, soname, needed def ACTHZ(self, CLOCK_TICK_RATE, HZ): LATCH = ((CLOCK_TICK_RATE + HZ/2) / HZ) return self.SH_DIV(CLOCK_TICK_RATE, LATCH, 8) def SH_DIV(self, NOM, DEN, LSH): return ((NOM / DEN) << LSH) + (((NOM % DEN) << LSH) + DEN / 2) / DEN def TICK_NSEC(self): HZ = 1000 CLOCK_TICK_RATE = 1193182 return self.SH_DIV(1000000 * 1000, self.ACTHZ(CLOCK_TICK_RATE, HZ), 8) def get_time_vars(self): ''' Sometime in 3.[3-5], Linux switched to a global timekeeper structure This just figures out which is in use and returns the correct variables ''' wall_addr = self.obj_vm.profile.get_symbol("wall_to_monotonic") sleep_addr = self.obj_vm.profile.get_symbol("total_sleep_time") # old way if wall_addr and sleep_addr: wall = obj.Object("timespec", offset = wall_addr, vm = self.obj_vm) timeo = obj.Object("timespec", offset = sleep_addr, vm = self.obj_vm) elif wall_addr: wall = obj.Object("timespec", offset = wall_addr, vm = self.obj_vm) timeo = linux_common.vol_timespec(0, 0) # timekeeper way else: timekeeper_addr = self.obj_vm.profile.get_symbol("timekeeper") timekeeper = obj.Object("timekeeper", offset = timekeeper_addr, vm = self.obj_vm) wall = timekeeper.wall_to_monotonic timeo = timekeeper.total_sleep_time return (wall, timeo) # based on 2.6.35 getboottime def get_boot_time(self): (wall, timeo) = self.get_time_vars() secs = wall.tv_sec + timeo.tv_sec nsecs = wall.tv_nsec + timeo.tv_nsec secs = secs * -1 nsecs = nsecs * -1 while nsecs >= linux_common.nsecs_per: nsecs = nsecs - linux_common.nsecs_per secs = secs + 1 while nsecs < 0: nsecs = nsecs + linux_common.nsecs_per secs = secs - 1 boot_time = secs + (nsecs / linux_common.nsecs_per / 100) return boot_time def get_task_start_time(self): start_time = self.start_time start_secs = start_time.tv_sec + (start_time.tv_nsec / linux_common.nsecs_per / 100) sec = self.get_boot_time() + start_secs # convert the integer as little endian try: data = struct.pack("> 20 @property def minor(self): return self.s_dev & ((1 << 20) - 1) class inode(obj.CType): def is_dir(self): """Mimic the S_ISDIR macro""" return self.i_mode & linux_flags.S_IFMT == linux_flags.S_IFDIR def is_reg(self): """Mimic the S_ISREG macro""" return self.i_mode & linux_flags.S_IFMT == linux_flags.S_IFREG class timespec(obj.CType): def as_timestamp(self): time_val = struct.pack("