# 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,copy import sys, os import zipfile import struct import time import string from operator import attrgetter import volatility.plugins as plugins import volatility.debug as debug import volatility.obj as obj import volatility.plugins.overlays.basic as basic import volatility.addrspace as addrspace import volatility.scan as scan import volatility.plugins.addrspaces.amd64 as amd64 import volatility.plugins.addrspaces.intel as intel import volatility.plugins.overlays.native_types as native_types import volatility.utils as utils import volatility.plugins.mac.common as common import volatility.plugins.malware.malfind as malfind try: import yara has_yara = True except ImportError: has_yara = False x64_native_types = copy.deepcopy(native_types.x64_native_types) x64_native_types['long'] = [8, '<q'] x64_native_types['unsigned long'] = [8, '<Q'] dynamic_env_hint = None dyld_vtypes = { 'dyld32_image_info' : [12, { 'imageLoadAddress' : [0, ['pointer', ['unsigned int']]], 'imageFilePath' : [4, ['pointer', ['char']]], 'imageFileModDate' : [8, ['pointer', ['unsigned int']]], }], 'dyld32_all_image_infos' : [20 , { 'version' : [0, ['unsigned int']], 'infoArrayCount' : [4, ['unsigned int']], 'infoArray' : [8, ['pointer', ['dyld32_image_info']]], 'notification' : [12, ['pointer', ['void']]], 'processDetachedFromSharedRegion': [16, ['unsigned int']], }], 'dyld64_image_info' : [24, { 'imageLoadAddress' : [0, ['pointer', ['unsigned int']]], 'imageFilePath' : [8, ['pointer', ['char']]], 'imageFileModDate' : [16, ['pointer', ['unsigned int']]], }], 'dyld64_all_image_infos' : [28 , { 'version' : [0, ['unsigned int']], 'infoArrayCount' : [4, ['unsigned int']], 'infoArray' : [8, ['pointer', ['dyld64_image_info']]], 'notification' : [16, ['pointer', ['void']]], 'processDetachedFromSharedRegion': [24, ['unsigned int']], }], } class BashEnvYaraScanner(malfind.BaseYaraScanner): """A scanner over all memory regions of a process.""" def __init__(self, task = None, **kwargs): """Scan the process address space through the VMAs. Args: task: The task_struct object for this task. """ self.task = task malfind.BaseYaraScanner.__init__(self, address_space = task.get_process_address_space(), **kwargs) def scan(self, offset = 0, maxlen = None, max_size = None): shared_start = self.task.task.shared_region.sr_base_address shared_end = shared_start + self.task.task.shared_region.sr_size for map in self.task.get_proc_maps(): start = map.links.start.v() end = map.links.end.v() length = end - start if length >= 0x1000000: continue if shared_start <= start <= shared_end: continue if map.get_perms() != "rw-" or map.get_path() != "": continue for match in malfind.BaseYaraScanner.scan(self, start, length): yield match class DyldTypes(obj.ProfileModification): conditions = {"os" : lambda x : x in ["mac"]} def modification(self, profile): profile.vtypes.update(dyld_vtypes) mig_vtypes_32 = { 'mig_hash_entry' : [16, { 'num' : [0, ['int']], 'routine' : [4, ['pointer', ['void']]], 'size' : [8, ['int']], 'callcount' : [12, ['unsigned int']], }], } mig_vtypes_64 = { 'mig_hash_entry' : [24, { 'num' : [0, ['long long']], 'routine' : [8, ['pointer', ['void']]], 'size' : [16, ['int']], 'callcount' : [20, ['unsigned int']], }], } class MigTypes(obj.ProfileModification): conditions = {"os" : lambda x : x in ["mac"]} def modification(self, profile): if profile.metadata.get('memory_model', '32bit') == "32bit": profile.vtypes.update(mig_vtypes_32) else: profile.vtypes.update(mig_vtypes_64) class catfishScan(scan.BaseScanner): """ Scanner for Catfish string for Mountain Lion """ checks = [] def __init__(self, needles = None): self.needles = needles self.checks = [ ("MultiStringFinderCheck", {'needles':needles}) ] scan.BaseScanner.__init__(self) def scan(self, address_space, offset = 0, maxlen = None): for offset in scan.BaseScanner.scan(self, address_space, offset, maxlen): yield offset class VolatilityDTB(obj.VolatilityMagic): """A scanner for DTB values.""" def _get_dtb_pre_m_lion(self): profile = self.obj_vm.profile if self.obj_vm.profile.metadata.get('memory_model', '32bit') == "32bit": ret = profile.get_symbol("_IdlePDPT") # on 10.5.x the PDTD symbol is a pointer instead of an array like 10.6 and 10.7 if ret % 0x1000: ret = self.obj_vm.read(ret, 4) ret = struct.unpack("<I", ret)[0] else: ret = profile.get_symbol("_IdlePML4") # so it seems some kernels don't define this as the physical address, but actually the virtual # while others define it as the physical, easy enough to figure out on the fly if ret > 0xffffff8000000000: ret = ret - 0xffffff8000000000 return ret ## Based off volafox's method for finding vm_kernel_shift through loGlo & hardcoded Catfish def _get_dtb_m_lion(self): tbl = self.obj_vm.profile.sys_map["kernel"] config = self.obj_vm.get_config() if config.SHIFT: shift_address = config.SHIFT else: scanner = catfishScan(needles = ["Catfish \x00\x00"]) for catfish_offset in scanner.scan(self.obj_vm): shift_address = catfish_offset - (tbl["_lowGlo"][0][0] % 0xFFFFFF80) break self.obj_vm.profile.shift_address = shift_address bootpml4 = (tbl["_BootPML4"][0][0] % 0xFFFFFF80) + shift_address boot_pml4_dtb = amd64.AMD64PagedMemory(self.obj_vm, config, dtb = bootpml4) idlepml4_addr = (tbl['_IdlePML4'][0][0]) + shift_address idlepml4_ptr = obj.Object("unsigned int", offset = idlepml4_addr, vm = boot_pml4_dtb) return idlepml4_ptr.v() def generate_suggestions(self): profile = self.obj_vm.profile bootpml = profile.get_symbol("_BootPML4") if bootpml: ret = self._get_dtb_m_lion() else: ret = self._get_dtb_pre_m_lion() yield ret class VolatilityMacIntelValidAS(obj.VolatilityMagic): """An object to check that an address space is a valid Mac Intel Paged space""" def _set_profile_metadata(self, version): start = version[len("Darwin Kernel Version "):] idx = start.find(":") (major, minor, _) = [int(x) for x in start[:idx].split(".")] setattr(self.obj_vm.profile, '_md_major', major) setattr(self.obj_vm.profile, '_md_minor', minor) def generate_suggestions(self): version_addr = self.obj_vm.profile.get_symbol("_version") string = self.obj_vm.read(version_addr, 60) if string and string.startswith("Darwin"): self._set_profile_metadata(string) yield True else: yield False class ifnet(obj.CType): def sockaddr_dl(self): if hasattr(self, "if_lladdr"): ret = obj.Object("sockaddr_dl", offset = self.if_lladdr.ifa_addr.v(), vm = self.obj_vm) else: ret = obj.Object("sockaddr_dl", offset = self.if_addrhead.tqh_first.ifa_addr.v(), vm = self.obj_vm) return ret class vnode(obj.CType): def is_dir(self): return self.v_type == 2 def is_reg(self): return self.v_type == 1 def _do_calc_path(self, ret, vnodeobj, vname): if vnodeobj == None: return if vname: ret.append(vname) if vnodeobj.v_flag.v() & 0x000001 != 0 and vnodeobj.v_mount.v() != 0: if vnodeobj.v_mount.mnt_vnodecovered.v() != 0: self._do_calc_path(ret, vnodeobj.v_mount.mnt_vnodecovered, vnodeobj.v_mount.mnt_vnodecovered.v_name) else: self._do_calc_path(ret, vnodeobj.v_parent, vnodeobj.v_parent.v_name) def full_path(self): if self.v_flag.v() & 0x000001 != 0 and self.v_mount.v() != 0 and self.v_mount.mnt_flag.v() & 0x00004000 != 0: ret = "/" else: elements = [] files = [] self._do_calc_path(elements, self, self.v_name) elements.reverse() for e in elements: files.append(str(e.dereference())) ret = "/".join(files) if ret: ret = "/" + ret return ret def get_contents(self): moc = self.v_un.vu_ubcinfo.ui_control.moc_object memq = moc.memq cur = memq.m("next").dereference_as("vm_page") file_size = self.v_un.vu_ubcinfo.ui_size phys_as = utils.load_as(self.obj_vm.get_config(), astype = 'physical') idx = 0 written = 0 while cur and cur.is_valid() and cur.offset < file_size: # the last element of the queue seems to track the size of the queue if cur.offset != 0 and cur.offset == idx: break if cur.phys_page != 0 and cur.offset >= 0: sz = 4096 if file_size - written < 4096: sz = file_size - written buf = phys_as.zread(cur.phys_page * 4096, sz) yield (cur.offset.v(), buf) idx = idx + 1 written = written + 4096 cur = cur.listq.next.dereference_as("vm_page") class fileglob(obj.CType): @property def fg_type(self): ret = self.members.get("fg_type") if ret: ret = self.m("fg_type") else: if self.fg_ops.is_valid(): ret = self.fg_ops.fo_type else: ret = 'INVALID' ret = str(ret) return ret class kauth_scope(obj.CType): @property def ks_identifier(self): ident_ptr = self.m("ks_identifier") ident = self.obj_vm.read(ident_ptr, 256) if ident: idx = ident.find("\x00") if idx != -1: ident = ident[:idx] return ident def listeners(self): ls_array = obj.Object(theType="Array", targetType="kauth_local_listener", offset = self.m("ks_listeners").obj_offset, vm = self.obj_vm, count = 16) for ls in ls_array: if ls.is_valid() and ls.kll_callback != 0: yield ls class thread(obj.CType): def start_time(self): baddr = self.obj_vm.profile.get_symbol("_clock_boottime") boot_time = obj.Object("unsigned long long", offset = baddr, vm = self.obj_vm) abs_time = boot_time + self.sched_stamp try: data = struct.pack("<I", abs_time) except struct.error: return "" bufferas = addrspace.BufferAddressSpace(self.obj_vm.get_config(), data = data) dt = obj.Object("UnixTimeStamp", offset = 0, vm = bufferas, is_utc = True) return dt class proc(obj.CType): def __init__(self, theType, offset, vm, name = None, **kwargs): self.pack_fmt = "" self.pack_size = 0 self.addr_type = "" obj.CType.__init__(self, theType, offset, vm, name, **kwargs) bit_string = str(self.task.map.pmap.pm_task_map or '')[9:] if bit_string.find("64BIT") == -1: self.pack_fmt = "<I" self.pack_size = 4 self.addr_type = "unsigned int" else: self.pack_fmt = "<Q" self.pack_size = 8 self.addr_type = "unsigned long long" def bash_hash_entries(self): proc_as = self.get_process_address_space() # In cases when mm is an invalid pointer if not proc_as: return shared_start = self.task.shared_region.sr_base_address shared_end = shared_start + self.task.shared_region.sr_size bit_string = str(self.task.map.pmap.pm_task_map or '')[9:] if bit_string.find("64BIT") == -1: addr_type = "unsigned int" bucket_contents_type = "mac32_bucket_contents" htable_type = "mac32_bash_hash_table" nbuckets_offset = self.obj_vm.profile.get_obj_offset(htable_type, "nbuckets") else: addr_type = "unsigned long long" bucket_contents_type = "mac64_bucket_contents" htable_type = "mac64_bash_hash_table" nbuckets_offset = self.obj_vm.profile.get_obj_offset(htable_type, "nbuckets") for map in self.get_proc_maps(): if shared_start <= map.start <= shared_end: continue if map.get_perms() != "rw-": continue if map.get_path() != "": continue ## 1 GB limit to prevent major delays...the bash hash data ## should not be found in a region larger than this. if map.end - map.start > 0x40000000: continue off = map.start while off < map.end: # test the number of buckets dr = proc_as.read(off + nbuckets_offset, 4) if dr == None: new_off = (off & ~0xfff) + 0xfff + 1 off = new_off continue test = struct.unpack("<I", dr)[0] if test != 64: off = off + 1 continue htable = obj.Object(htable_type, offset = off, vm = proc_as) if htable.is_valid(): bucket_array = obj.Object(theType="Array", targetType=addr_type, offset = htable.bucket_array, vm = htable.nbuckets.obj_vm, count = 64) for bucket_ptr in bucket_array: bucket = obj.Object(bucket_contents_type, offset = bucket_ptr, vm = htable.nbuckets.obj_vm) while bucket != None and bucket.times_found > 0: pdata = bucket.data if pdata == None: bucket = bucket.next_bucket() continue if bucket.key != None and bucket.data != None and pdata.is_valid() and (0 <= pdata.flags <= 2): if len(str(bucket.key)) > 0 or len(str(bucket.data.path)) > 0: yield bucket bucket = bucket.next_bucket() off = off + 1 def bash_history_entries(self): proc_as = self.get_process_address_space() bit_string = str(self.task.map.pmap.pm_task_map or '')[9:] if bit_string.find("64BIT") == -1: pack_format = "<I" hist_struct = "bash32_hist_entry" else: pack_format = "<Q" hist_struct = "bash64_hist_entry" # Brute force the history list of an address isn't provided ts_offset = proc_as.profile.get_obj_offset(hist_struct, "timestamp") history_entries = [] bang_addrs = [] # Look for strings that begin with pound/hash on the process heap for ptr_hash in self.search_process_memory_rw_nofile(["#"]): # Find pointers to this strings address, also on the heap addr = struct.pack(pack_format, ptr_hash) bang_addrs.append(addr) for (idx, ptr_string) in enumerate(self.search_process_memory_rw_nofile(bang_addrs)): # Check if we found a valid history entry object hist = obj.Object(hist_struct, 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 _get_libc_range(self, proc_as): libc_map = None mapping = None for dmap in self.get_dyld_maps(): if dmap.imageFilePath.endswith("libsystem_c.dylib"): libc_map = dmap break if libc_map: mh = obj.Object("macho_header", offset = libc_map.imageLoadAddress, vm = proc_as) for seg in mh.segments(): if str(seg.segname) == "__DATA": mapping = [[seg.vmaddr, seg.vmaddr + seg.vmsize, seg.vmsize]] return mapping # this tries to find libc in memory, which holds the pointer to the dynamic env # if we can't get the address of libc (due to needed data being paged out) then we have ot scan all ranges def _get_env_mappings(self, proc_as): mappings = self._get_libc_range(proc_as) if not mappings: mappings = [] for mapping in self.get_proc_maps(): if str(mapping.get_perms()) != "rw-" or mapping.get_path() == "": continue mappings.append([mapping.start, mapping.end, mapping.end - mapping.start]) # the mapping holding the environment seems to be high in memory... mappings.reverse() return mappings def _carve_mappings_for_env(self, proc_as, mappings): global dynamic_env_hint seen_ptrs = {} seen_firsts = {} env_start = 0 for (start, end, length) in mappings: if env_start: break off = start if length >= 0x1000000: continue while off < end: if env_start: break # check the first index addrstr = proc_as.read(off, self.pack_size) if not addrstr: off = (off & ~0xfff) + 0xfff + 1 continue off = off + 4 addr = struct.unpack(self.pack_fmt, addrstr)[0] if addr in seen_ptrs: continue seen_ptrs[addr] = 1 # check first idx... if addr: firstaddrstr = proc_as.read(addr, self.pack_size) if not firstaddrstr or len(firstaddrstr) != self.pack_size: continue firstaddr = struct.unpack(self.pack_fmt, firstaddrstr)[0] if firstaddr in seen_firsts: continue seen_firsts[firstaddr] = 1 buf = proc_as.read(firstaddr, 64) if not buf: continue eqidx = buf.find("=") if eqidx > 0: nullidx = buf.find("\x00") # single char name, = if nullidx >= eqidx: env_start = addr if not dynamic_env_hint: dynamic_env_hint = [start, end, length] break return env_start def _get_env_vars(self, proc_as, env_start): good_vars = [] envars = obj.Object(theType="Array", targetType=self.addr_type, vm=proc_as, offset=env_start, count=256) for var in envars: if not var or not var.is_valid(): break sizes = [32, 64, 128, 256, 8, 16, 384, 512, 1024, 2048, 4096] good_varstr = None for size in sizes: varstr = proc_as.read(var, size) if not varstr: break 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:] if len(key) > 0 and len(val) > 0 and self._valid_string(key) and self._valid_string(val): good_vars.append((key, val)) else: break return good_vars def _dynamic_env(self, proc_as, pack_format, addr_sz): env_start = 0 if dynamic_env_hint: mappings = [dynamic_env_hint] env_start = self._carve_mappings_for_env(proc_as, mappings) good_vars = self._get_env_vars(proc_as, env_start) if len(good_vars) < 2: env_start = 0 # find either libc itself or all mappings if env_start == 0: mappings = self._get_env_mappings(proc_as) env_start = self._carve_mappings_for_env(proc_as, mappings) if env_start != 0: good_vars = self._get_env_vars(proc_as, env_start) else: good_vars = [] return good_vars def _valid_string(self, test_string): valid = True test_string = str(test_string) for s in test_string: if not s in string.printable: valid = False break return valid def _shell_variables(self, proc_as, pack_format, addr_sz, htable_type): if has_yara == False: return nbuckets_offset = self.obj_vm.profile.get_obj_offset(htable_type, "nbuckets") if addr_sz == 4: edata_type = "mac32_envdata" else: edata_type = "mac64_envdata" seen_ptr = {} s = "{ 40 00 00 00 }" rules = yara.compile(sources = { 'n' : 'rule r1 {strings: $a = ' + s + ' condition: $a}' }) scanner = BashEnvYaraScanner(task = self, rules = rules) for hit, off in scanner.scan(): htable = obj.Object(htable_type, offset = off - addr_sz, vm = proc_as) if not htable.is_valid(): continue for ent in htable: if not ent.m("key").is_valid(): continue if self._valid_string(ent.key): key = str(ent.key) else: key = "" val_addr = ent.data.dereference_as(edata_type).value if val_addr.is_valid() and self._valid_string(val_addr.dereference()): val = str(val_addr.dereference()) else: val = "" if len(key) > 0 and len(val) > 0: yield key, val def _load_time_env(self, proc_as): start = self.user_stack - self.p_argslen skip = len(self.get_arguments()) end = self.p_argslen to_read = end - skip vars_buf = proc_as.read(start + skip, to_read) if vars_buf: ents = vars_buf.split("\x00") for varstr in ents: eqidx = varstr.find("=") if eqidx == -1: continue key = varstr[:eqidx] val = varstr[eqidx+1:] yield (key, val) def psenv(self): proc_as = self.get_process_address_space() # In cases when mm is an invalid pointer if not proc_as: return # don't scan the kernel if self.p_pid == 0: return # Are we dealing with 32 or 64-bit pointers if self.obj_vm.profile.metadata.get('memory_model', '32bit') == '32bit': pack_format = "<I" addr_sz = 4 htable_type = "mac32_bash_hash_table" else: pack_format = "<Q" addr_sz = 8 htable_type = "mac64_bash_hash_table" env_count = 0 for key, val in self._dynamic_env(proc_as, pack_format, addr_sz): yield key, val env_count = env_count + 1 # if the dynamic env isn't in memory (or is corrupt) # then we find the inital program load env # this has the disadvantage of not finding variables added since runtime # and won't catch changes to existing variables if env_count < 3: for key, val in self._load_time_env(proc_as): yield key, val # shell variables only live inside bash (e.g., HISTFILE) if str(self.p_comm) == "bash": for key, val in self._shell_variables(proc_as, pack_format, addr_sz, htable_type): yield key, val def netstat(self): for (filp, _, _) in self.lsof(): if filp.f_fglob.is_valid() and filp.f_fglob.fg_type == 'DTYPE_SOCKET': socket = filp.f_fglob.fg_data.dereference_as("socket") family = socket.family if family == 1: upcb = socket.so_pcb.dereference_as("unpcb") path = upcb.unp_addr.sun_path yield (family, (socket.v(), path)) elif family in [2, 30]: proto = socket.protocol state = socket.state vals = socket.get_connection_info() if vals: (lip, lport, rip, rport) = vals yield (family, (socket, proto, lip, lport, rip, rport, state)) @property def p_gid(self): cred = self.p_ucred if not cred.is_valid(): return -1 if hasattr(cred, "cr_posix"): try: ret = cred.cr_posix.cr_groups[0] except IndexError: ret = obj.Object("unsigned int", offset = cred.cr_posix.cr_groups.obj_offset, vm = self.obj_vm) else: ret = cred.cr_groups[0] return ret @property def p_uid(self): cred = self.p_ucred if not cred.is_valid(): return -1 if hasattr(cred, "cr_posix"): ret = cred.cr_posix.cr_uid else: ret = cred.cr_uid return ret def threads(self): threads = [] seen_threads = [] qentry = self.task.threads for thread in qentry.thread_walk_list(qentry.obj_offset): if thread.obj_offset in seen_threads: break seen_threads.append(thread.obj_offset) threads.append(thread) return threads def get_process_address_space(self): cr3 = self.task.map.pmap.pm_cr3 map_val = str(self.task.map.pmap.pm_task_map or '') # if the machine is 64 bit capable is_64bit_cap = common.is_64bit_capable(self.obj_vm) if map_val == "TASK_MAP_32BIT" and is_64bit_cap: # A 32 bit process on a 64 bit system, requires 64 bit paging # Catch exceptions when trying to get a process AS for kernel_task # which isn't really even a process. It needs to use the default cr3 try: proc_as = amd64.AMD64PagedMemory(self.obj_vm.base, self.obj_vm.get_config(), dtb = cr3, skip_as_check = True) except IOError: proc_as = self.obj_vm elif map_val == "TASK_MAP_32BIT": # A 32 bit process on a 32 bit system need # bypass b/c no sharing of address space proc_as = intel.IA32PagedMemoryPae(self.obj_vm.base, self.obj_vm.get_config(), dtb = cr3, skip_as_check = True) elif (map_val == "TASK_MAP_64BIT_SHARED" and self.obj_vm.profile.metadata.get('memory_model', '32bit') == "32bit"): # A 64 bit process running on a 32 bit system proc_as = amd64.AMD64PagedMemory(self.obj_vm.base, self.obj_vm.get_config(), dtb = cr3, skip_as_check = True) elif map_val in ["TASK_MAP_64BIT", "TASK_MAP_64BIT_SHARED"]: # A 64 bit process on a 64 bit system cr3 &= 0xFFFFFFE0 proc_as = amd64.AMD64PagedMemory(self.obj_vm.base, self.obj_vm.get_config(), dtb = cr3, skip_as_check = True) else: proc_as = obj.NoneObject("Cannot get process AS for pm_task_map: {0}".format(map_val)) return proc_as def start_time(self): nsecs_per = 1000000 start_time = self.p_start start_secs = start_time.tv_sec + (start_time.tv_usec / nsecs_per) # convert the integer as little endian. we catch struct.error # here because if the process has exited (i.e. detected with mac_dead_procs) # then the timestamp may not be valid. start_secs could be negative # or higher than can fit in a 32-bit "I" integer field. try: data = struct.pack("<I", start_secs) except struct.error: return "" bufferas = addrspace.BufferAddressSpace(self.obj_vm.get_config(), data = data) dt = obj.Object("UnixTimeStamp", offset = 0, vm = bufferas, is_utc = True) return dt def text_start(self): text_start = 0 wanted_vnode = self.p_textvp.v() if wanted_vnode: for map in self.get_proc_maps(): vnode = map.get_vnode() if vnode and vnode != "sub_map" and vnode.v() == wanted_vnode: text_start = map.start.v() break # both offset and vp were bogus if text_start == 0: found_map = None for map in self.get_dyld_maps(): found_map = map break if found_map: text_start = found_map.imageLoadAddress return text_start def get_macho(self, exe_address): proc_as = self.get_process_address_space() m = obj.Object("macho_header", offset = exe_address, vm = proc_as) buffer = "" for seg in m.segments(): if str(seg.segname) == "__PAGEZERO": continue # this is related to the shared cache map # contact Andrew for full details if str(seg.segname) == "__LINKEDIT" and seg.vmsize > 20000000: continue cur = seg.vmaddr end = seg.vmaddr + seg.vmsize while cur < end: buffer = buffer + proc_as.zread(cur, 4096) cur = cur + 4096 return buffer def procdump(self): start = self.text_start() if start: ret = self.get_macho(start) else: ret = "" return ret def get_dyld_maps(self): proc_as = self.get_process_address_space() if proc_as == None: return if self.pack_size == 4: dtype = "dyld32_all_image_infos" itype = "dyld32_image_info" else: dtype = "dyld64_all_image_infos" itype = "dyld64_image_info" infos = obj.Object(dtype, offset=self.task.all_image_info_addr, vm=proc_as) if not infos: return # the pointer address info_buf = proc_as.read(infos.infoArray.obj_offset, self.pack_size) if not info_buf: return info_addr = struct.unpack(self.pack_fmt, info_buf)[0] img_infos = obj.Object(theType = "Array", targetType = itype, offset = info_addr, count = infos.infoArrayCount, vm = proc_as) for info_addr in img_infos: if info_addr: yield info_addr def get_proc_maps(self): map = self.task.map.hdr.links.next for i in xrange(self.task.map.hdr.nentries): if not map: break yield map map = map.links.next def find_heap_map(self): ret = None for pmap in self.get_proc_maps(): if pmap.get_special_path() == "[heap]": ret = pmap break return None def find_map(self, addr): ret = None for vma in self.get_proc_maps(): if int(vma.links.start) <= int(addr) <= int(vma.links.end): ret = vma break return ret def find_map_path(self, addr): path = "" m = self.find_map(addr) if m: path = m.get_path() if path == "": path = m.get_special_path() return path def search_process_memory(self, s): """Search process memory. @param s: a list of strings like ["one", "two"] """ # Allow for some overlap in case objects are # right on page boundaries overlap = 1024 scan_blk_sz = 1024 * 1024 * 10 addr_space = self.get_process_address_space() for vma in self.get_proc_maps(): offset = vma.links.start out_of_range = vma.links.start + (vma.links.end - vma.links.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 search_process_memory_rw_nofile(self, s): """Search process memory. @param s: a list of strings like ["one", "two"] """ # Allow for some overlap in case objects are # right on page boundaries overlap = 1024 scan_blk_sz = 1024 * 1024 * 10 addr_space = self.get_process_address_space() for vma in self.get_proc_maps(): if vma.get_perms() != "rw-" or vma.get_path() != "": if vma.get_special_path() != "[heap]": continue offset = vma.links.start out_of_range = vma.links.start + (vma.links.end - vma.links.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 get_environment(self): env = "" for (k, v) in self.psenv(): env = env + "{0}={1} ".format(k, v) return env def get_arguments(self): proc_as = self.get_process_address_space() # We need a valid process AS to continue if not proc_as: return "" argsstart = self.user_stack - self.p_argslen # Stack location may be paged out or not contain any args if (not proc_as.is_valid_address(argsstart) or self.p_argslen == 0 or self.p_argc == 0): return "" # Add one because the first two are usually duplicates argc = self.p_argc + 1 args = [] while argc > 0: arg = obj.Object("String", offset = argsstart, vm = proc_as, length = 256) if not arg: break # Initial address of the next string argsstart += len(str(arg)) + 1 # Very first one is aligned in some crack ass way if len(args) == 0: while (proc_as.read(argsstart, 1) == "\x00" and argsstart < self.user_stack): argsstart += 1 args.append(arg) else: # Only add this string if its not a duplicate of the first if str(arg) != str(args[0]): args.append(arg) argc -= 1 return " ".join([str(s) for s in args]) def lsof(self): num_fds = self.p_fd.fd_lastfile nfiles = self.p_fd.fd_nfiles if nfiles > num_fds: num_fds = nfiles fds = obj.Object('Array', offset = self.p_fd.fd_ofiles, vm = self.obj_vm, targetType = 'Pointer', count = num_fds) for i, fd in enumerate(fds): f = fd.dereference_as("fileproc") if f and f.f_fglob.is_valid(): ftype = f.f_fglob.fg_type if ftype == 'DTYPE_VNODE': vnode = f.f_fglob.fg_data.dereference_as("vnode") path = vnode.full_path() else: path = "<%s>" % ftype.replace("DTYPE_", "").lower() yield f, path, i class rtentry(obj.CType): def get_time(self): if not hasattr(self, "base_calendartime"): return "N/A" data = struct.pack("<I", self.base_calendartime) bufferas = addrspace.BufferAddressSpace(self.obj_vm.get_config(), data = data) dt = obj.Object("UnixTimeStamp", offset = 0, vm = bufferas, is_utc = True) return dt @property def sent(self): if hasattr(self, "rt_stats"): ret = self.rt_stats.nstat_txpackets else: ret = "N/A" return ret @property def rx(self): if hasattr(self, "rt_stats"): ret = self.rt_stats.nstat_rxpackets else: ret = "N/A" return ret @property def delta(self): if self.expire() == 0: ret = 0 else: ret = self.expire() - self.base_uptime return ret def expire(self): if hasattr(self, "rt_expire"): ret = self.rt_expire else: ret = 0 return ret @property def name(self): return "{}{}".format(self.rt_ifp.if_name.dereference(), self.rt_ifp.if_unit) @property def source_ip(self): try: node = self.rt_nodes[0] except IndexError: node = obj.Object("radix_node", offset = self.rt_nodes.obj_offset, vm = self.obj_vm) return node.rn_u.rn_leaf.rn_Key.dereference_as("sockaddr").get_address() @property def dest_ip(self): return self.rt_gateway.get_address() class queue_entry(obj.CType): def walk_list(self, list_head): n = self.next.dereference_as("task") while n and n.obj_offset != list_head: yield n n = n.tasks.next.dereference_as("task") p = self.prev.dereference_as("task") while p and p.obj_offset != list_head: yield p p = p.tasks.prev.dereference_as("task") class zone(obj.CType): def _get_from_active_zones(self): ret = [] first_elem = self.active_zones elem = first_elem # TODO sz = 16 i = 0 while elem != first_elem.v() or i == 0: a = elem.v() b = sz off = a + b ret.append(off) i = i + 1 if i == 4: break elem = elem.m("next") return ret def get_active_elements(self, elem_type, zone_idx=-1): ret = [] if hasattr(self, "active_zones"): objs = self._get_from_active_zones() else: debug.error("zone does not have active zones.") for o in objs: val = obj.Object(elem_type, offset = o, vm = self.obj_vm) ret.append(val) return ret def get_free_elements(self, elem_type): ret = [] nxt = obj.Object("zone_free_element", offset = self.free_elements, vm = self.obj_vm) while nxt: o = nxt.obj_offset val = obj.Object(elem_type, offset = o, vm = self.obj_vm) ret.append(val) nxt = nxt.m("next") return ret class sysctl_oid(obj.CType): def get_perms(self): """ # define CTLFLAG_RD 0x80000000 /* Allow reads of variable */ # define CTLFLAG_WR 0x40000000 /* Allow writes to the variable */ # define CTLFLAG_LOCKED 0x00800000 /* node will handle locking itself */ """ ret = "" checks = [0x80000000, 0x40000000, 0x00800000] perms = ["R", "W", "L"] for (i, c) in enumerate(checks): if c & self.oid_kind: ret = ret + perms[i] else: ret = ret + "-" return ret def get_ctltype(self): """ #define CTLTYPE_NODE 1 #define CTLTYPE_INT 2 /* name describes an integer */ #define CTLTYPE_STRING 3 /* name describes a string */ #define CTLTYPE_QUAD 4 /* name describes a 64-bit number */ #define CTLTYPE_OPAQUE 5 /* name describes a structure */ #define CTLTYPE_STRUCT CTLTYPE_OPAQUE /* name describes a structure */ """ types = {1: 'CTLTYPE_NODE', 2: 'CTLTYPE_INT', 3: 'CTLTYPE_STRING', 4: 'CTLTYPE_QUAD', 5: 'CTLTYPE_OPAQUE'} ctltype = self.oid_kind & 0xf try: return types[ctltype] except KeyError: return "INVALID -1" class OSString(obj.CType): def __str__(self): if self.string == 0: return "" if self.length > 4096: return "" string_object = obj.Object("String", offset = self.string, vm = self.obj_vm, length = self.length) return str(string_object or '') class vm_map_object(obj.CType): def object(self): if hasattr(self, "vm_object"): ret = self.m("vm_object") else: ret = self.vmo_object return ret class vm_map_entry(obj.CType): @property def start(self): return self.links.start @property def end(self): return self.links.end def get_perms(self): permask = "rwx" perms = "" for (ctr, i) in enumerate([1, 3, 5]): if (self.protection & i) == i: perms = perms + permask[ctr] else: perms = perms + "-" return perms # used to find heap, stack, etc. def get_special_path(self): if hasattr(self, "alias"): check = self.alias.v() else: check = self.vme_offset.v() & 0xfff if 0 < check < 10: ret = "[heap]" elif check == 30: ret = "[stack]" else: ret = "" return ret def get_path(self): vnode = self.get_vnode() if type(vnode) == str and vnode == "sub_map": ret = vnode elif vnode: path = [] while vnode: path.append(str(vnode.v_name.dereference() or '')) vnode = vnode.v_parent path.reverse() ret = "/".join(path) else: ret = "" return ret @property def object(self): if hasattr(self, "vme_object"): ret = self.vme_object else: ret = self.m("object") return ret def get_vnode(self): map_obj = self if self.is_sub_map == 1: return "sub_map" # find_vnode_object vnode_object = map_obj.object.object() while vnode_object.shadow.dereference() != None: vnode_object = vnode_object.shadow.dereference() ops = vnode_object.pager.mo_pager_ops.v() if ops == self.obj_vm.profile.get_symbol("_vnode_pager_ops"): vpager = obj.Object("vnode_pager", offset = vnode_object.pager, vm = self.obj_vm) ret = vpager.vnode_handle else: ret = None return ret def resident_count(self): vmobj = self.object.object() if not vmobj: return 0 # based on OBJ_RESIDENT_COUNT # all versions since OS X 10.6 if hasattr(vmobj, "all_reusable"): if vmobj.all_reusable == 1: count = vmobj.wired_page_count.v() else: count = vmobj.resident_page_count.v() - vmobj.reusable_page_count.v() # really old systems - OS X 10.5 else: count = vmobj.resident_page_count.v() return count def is_suspicious(self): ret = False perms = self.get_perms() if perms == "rwx": ret = True elif perms == "r-x" and self.get_path() == "": ret = True return ret class inpcb(obj.CType): def get_tcp_state(self): tcp_states = ( "CLOSED", "LISTEN", "SYN_SENT", "SYN_RECV", "ESTABLISHED", "CLOSE_WAIT", "FIN_WAIT1", "CLOSING", "LAST_ACK", "FIN_WAIT2", "TIME_WAIT") tcpcb = self.inp_ppcb.dereference_as("tcpcb") state_type = tcpcb.t_state if state_type: state = tcp_states[state_type] else: state = "" return state def ipv4_info(self): lip = self.inp_dependladdr.inp46_local.ia46_addr4.s_addr.v() lport = self.inp_lport rip = self.inp_dependfaddr.inp46_foreign.ia46_addr4.s_addr.v() rport = self.inp_fport return [lip, lport, rip, rport] def ipv6_info(self): lip = self.inp_dependladdr.inp6_local.__u6_addr.v() lport = self.inp_lport rip = self.inp_dependfaddr.inp6_foreign.__u6_addr.v() rport = self.inp_fport return [lip, lport, rip, rport] class inpcbinfo(obj.CType): @property def hashbase(self): ret = self.members.get("hashbase") if ret is None: ret = self.ipi_hashbase else: ret = self.m("hashbase") return ret @property def hashmask(self): ret = self.members.get("hashmask") if ret is None: ret = self.ipi_hashmask else: ret = self.m("hashmask") return ret @property def listhead(self): ret = self.members.get("listhead") if ret is None: ret = self.ipi_listhead else: ret = self.m("listhead") return ret class socket(obj.CType): @property def family(self): return self.so_proto.pr_domain.dom_family @property def protocol(self): proto = self.so_proto.pr_protocol if proto == 6: ret = "TCP" elif proto == 17: ret = "UDP" else: ret = "" return ret def _get_tcp_state(self): tcp_states = ( "CLOSED", "LISTEN", "SYN_SENT", "SYN_RECV", "ESTABLISHED", "CLOSE_WAIT", "FIN_WAIT1", "CLOSING", "LAST_ACK", "FIN_WAIT2", "TIME_WAIT") inpcb = self.so_pcb.dereference_as("inpcb") tcpcb = inpcb.inp_ppcb.dereference_as("tcpcb") state = tcpcb.t_state if state: ret = tcp_states[tcpcb.t_state] else: ret = "<INVALID>" return ret @property def state(self): if self.so_proto.pr_protocol == 6: ret = self._get_tcp_state() else: ret = "" return ret def get_connection_info(self): if not self.so_pcb.is_valid(): return None ipcb = self.so_pcb.dereference_as("inpcb") if self.family == 2: ret = ipcb.ipv4_info() else: ret = ipcb.ipv6_info() return ret class sockaddr_dl(obj.CType): def v(self): """Get the value of the sockaddr_dl object.""" ret = "" for i in xrange(self.sdl_alen): try: e = self.sdl_data[self.sdl_nlen + i] e = ord(e.v()) except IndexError: e = 0 ret = ret + "%.02x:" % e if ret and ret[-1] == ":": ret = ret[:-1] return ret class sockaddr(obj.CType): def get_address(self): family = self.sa_family ip = "" if family == 2: # AF_INET addr_in = obj.Object("sockaddr_in", offset = self.obj_offset, vm = self.obj_vm) ip = addr_in.sin_addr.s_addr.v() elif family == 30: # AF_INET6 addr_in6 = obj.Object("sockaddr_in6", offset = self.obj_offset, vm = self.obj_vm) ip = addr_in6.sin6_addr.__u6_addr.v() elif family == 18: # AF_LINK addr_dl = obj.Object("sockaddr_dl", offset = self.obj_offset, vm = self.obj_vm) ip = addr_dl.v() return ip class dyld32_image_info(obj.CType): def _read_ptr(self, addr): addr = self.obj_vm.read(addr, 4) if not addr: ret = None else: ret = struct.unpack("<I", addr)[0] return ret @property def imageFilePath(self): addr = self.m("imageFilePath").obj_offset addr = self._read_ptr(addr) if addr == None: return "" buf = self.obj_vm.zread(addr, 256) if buf: idx = buf.find("\x00") if idx != -1: buf = buf[:idx] return buf @property def imageLoadAddress(self): addr = self.m("imageLoadAddress").obj_offset addr = self._read_ptr(addr) return addr class dyld64_image_info(obj.CType): def _read_ptr(self, addr): addr = self.obj_vm.read(addr, 8) if addr == None: ret = None else: ret = struct.unpack("<Q", addr)[0] return ret @property def imageFilePath(self): addr = self.m("imageFilePath").obj_offset addr = self._read_ptr(addr) if addr == None: return "" buf = self.obj_vm.zread(addr, 256) if buf: idx = buf.find("\x00") if idx != -1: buf = buf[:idx] return buf @property def imageLoadAddress(self): addr = self.m("imageLoadAddress").obj_offset addr = self._read_ptr(addr) return addr def exec_vtypes(filename): env = {} exec(filename, dict(__builtins__ = None), env) return env["mac_types"] def parse_dsymutil(data, module): """Parse the symbol file.""" sys_map = {} sys_map[module] = {} want_lower = ["_IdlePML4"] type_map = {} type_map[module] = {} arch = "" # get the system map for line in data.splitlines(): ents = line.split() match = re.search("\[.*?\(([^\)]+)\)\s+[0-9A-Fa-z]+\s+\d+\s+([0-9A-Fa-f]+)\s'(\w+)'", line) if match: (sym_type, addr, name) = match.groups() sym_type = sym_type.strip() addr = int(addr, 16) if addr == 0 or name == "": continue if not name in sys_map[module]: sys_map[module][name] = [(addr, sym_type)] # every symbol is in the symbol table twice # except for the entries in 'want_lower', we need the higher address for all oldaddr = sys_map[module][name][0][0] if addr < oldaddr and name in want_lower: sys_map[module][name] = [(addr, sym_type)] if not addr in type_map[module]: type_map[module][addr] = (name, [sym_type]) type_map[module][addr][1].append(sym_type) elif line.find("Symbol table for") != -1: if line.find("i386") != -1: arch = "32bit" else: arch = "64bit" if arch == "": return None return arch, sys_map, type_map def MacProfileFactory(profpkg): vtypesvar = {} sysmapvar = {} typesmapvar = {} memmodel, arch = "32bit", "x86" profilename = os.path.splitext(os.path.basename(profpkg.filename))[0] for f in profpkg.filelist: if 'symbol.dsymutil' in f.filename.lower(): memmodel, sysmap, typemap = parse_dsymutil(profpkg.read(f.filename), "kernel") if memmodel == "64bit": arch = "x64" sysmapvar.update(sysmap) typesmapvar.update(typemap) debug.debug("{2}: Found system file {0} with {1} symbols".format(f.filename, len(sysmapvar.keys()), profilename)) elif f.filename.endswith(".vtypes"): v = exec_vtypes(profpkg.read(f.filename)) vtypesvar.update(v) if not sysmapvar or not vtypesvar: # Might be worth throwing an exception here? return None class AbstractMacProfile(obj.Profile): __doc__ = "A Profile for Mac " + profilename + " " + arch _md_os = "mac" _md_memory_model = memmodel native_mapping = {'32bit': native_types.x86_native_types, '64bit': x64_native_types} def __init__(self, *args, **kwargs): self._init_vars() obj.Profile.__init__(self, *args, **kwargs) def _init_vars(self): self.sys_map = {} self.type_map = {} self.shift_address = 0 self.sba_cache = {} self.sbat_cache = {} def clear(self): """Clear out the system map, and everything else""" self._init_vars() 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 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)) self.vtypes.update(vtypesvar) def load_sysmap(self): """Loads up the system map data""" self.sys_map.update(sysmapvar) self.type_map.update(typesmapvar) # Returns a list of (name, addr) 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(): addr = addrs[0][0] if self.shift_address and addr: addr = addr + self.shift_address ret.append([name, addr]) 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 (_name, addr) in symbols: ret[addr] = 1 return ret ############################################ # Returns a list of (name, addr) def get_all_function_symbols(self, module = "kernel"): """ Gets all the function tuples for the given module """ ret = [] symtable = self.type_map if module in symtable: mod = symtable[module] for (addr, (name, _sym_types)) in mod.items(): if self.shift_address and addr: addr = addr + self.shift_address ret.append([name, addr]) else: debug.info("All symbols requested for non-existent module %s" % module) return ret def get_all_function_addresses(self, module = "kernel"): """ Gets all the function 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_function_symbols(module) for (_name, addr) in symbols: ret[addr] = 1 return ret def _get_symbol_by_address_type(self, module, wanted_sym_address, wanted_sym_type): ret = "" symtable = self.type_map mod = symtable[module] for (addr, (name, sym_types)) in mod.items(): for sym_type in sym_types: key = "%s|%x|%s" % (module, addr, sym_type) self.sbat_cache[key] = name if (wanted_sym_address == addr or wanted_sym_address == self.shift_address + addr) and wanted_sym_type == sym_type: ret = name break return ret def get_symbol_by_address_type(self, module, sym_address, sym_type): key = "%s|%x|%s" % (module, sym_address, sym_type) if key in self.sbat_cache: ret = self.sbat_cache[key] else: ret = self._get_symbol_by_address_type(module, sym_address, sym_type) return ret def _fill_sba_cache(self): ret = "" symtable = self.sys_map mod = symtable["kernel"] for (name, addrs) in mod.items(): for (addr, _) in addrs: key = "%s|%x" % ("kernel", addr) self.sba_cache[key] = name key = "%s|%x" % ("kernel", addr + self.shift_address) self.sba_cache[key] = name def get_symbol_by_address(self, module, sym_address): if self.sba_cache == {}: self._fill_sba_cache() key = "%s|%x" % (module, sym_address) if key in self.sba_cache: ret = self.sba_cache[key] else: 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.error("Requested symbol {0:s} in module {1:s} has multiple definitions and no type given\n".format(sym_name, module)) 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)) if self.shift_address and ret: ret = ret + self.shift_address return ret cls = AbstractMacProfile cls.__name__ = 'Mac' + 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(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(MacProfileFactory(zipfile.ZipFile(os.path.join(path, fn)))) kext_overlay = { 'kmod_info_class': [None, { 'name' : [ None , ['String', dict(length = 64)]], }], } class KextOverlay(obj.ProfileModification): conditions = {'os': lambda x: x == 'mac'} before = ['BasicObjectClasses'] def modification(self, profile): if 'kmod_info_class' in profile.vtypes: profile.merge_overlay(kext_overlay) class MacOverlay(obj.ProfileModification): conditions = {'os': lambda x: x == 'mac'} before = ['BasicObjectClasses'] def modification(self, profile): profile.merge_overlay(mac_overlay) class MacObjectClasses(obj.ProfileModification): conditions = {'os': lambda x: x == 'mac'} before = ['BasicObjectClasses'] def modification(self, profile): profile.object_classes.update({ 'VolatilityDTB': VolatilityDTB, 'VolatilityMacIntelValidAS' : VolatilityMacIntelValidAS, 'proc' : proc, 'thread' : thread, 'kauth_scope' : kauth_scope, 'dyld32_image_info' : dyld32_image_info, 'dyld64_image_info' : dyld64_image_info, 'fileglob' : fileglob, 'vnode' : vnode, 'ifnet' : ifnet, 'socket' : socket, 'inpcbinfo' : inpcbinfo, 'inpcb' : inpcb, 'zone' : zone, 'OSString' : OSString, 'OSString_class' : OSString, 'sysctl_oid' : sysctl_oid, 'IpAddress': basic.IpAddress, 'Ipv6Address': basic.Ipv6Address, 'sockaddr' : sockaddr, 'sockaddr_dl' : sockaddr_dl, 'vm_map_entry' : vm_map_entry, 'vm_map_object' : vm_map_object, 'rtentry' : rtentry, 'queue_entry' : queue_entry, }) mac_overlay = { 'VOLATILITY_MAGIC': [None, { 'DTB' : [ 0x0, ['VolatilityDTB', dict(configname = "DTB")]], 'IA32ValidAS' : [ 0x0, ['VolatilityMacIntelValidAS']], 'AMD64ValidAS' : [ 0x0, ['VolatilityMacIntelValidAS']], }], 'session' : [ None, { 's_login' : [ None , ['String', dict(length = 256)]], }], 'kfs_event' : [ None, { 'str' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'zone' : [ None, { 'zone_name': [ None, ['pointer', ['String', dict(length = 256)]]], }], 'mac_policy_conf' : [ None, { 'mpc_name' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'proc' : [ None, { 'p_comm' : [ None, ['String', dict(length = 17)]], 'task' : [ None, ['pointer', ['task']]], }], 'ifnet' : [ None, { 'if_name' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'vnode' : [ None, { 'v_name' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'boot_args' : [ None, { 'CommandLine' : [ None, ['String', dict(length = 1024)]], }], 'vfsstatfs' : [ None, { 'f_fstypename' : [ None, ['String', dict(length = 16)]], 'f_mntonname' : [ None, ['String', dict(length = 1024)]], 'f_mntfromname' : [ None, ['String', dict(length = 1024)]], }], 'kmod_info' : [ None, { 'name' : [ None, ['String', dict(length = 64)]], 'version' : [ None, ['String', dict(length = 64)]], }], 'ipf_filter' : [ None, { 'name' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'sysctl_oid' : [ None, { 'oid_name' : [ None, ['pointer', ['String', dict(length = 256)]]], }], 'sockaddr_un': [ None, { 'sun_path' : [ None, ['String', dict(length = 104)]], }], 'in_addr' : [ None, { 's_addr' : [ None, ['IpAddress']], }], 'in6_addr' : [ None, { '__u6_addr' : [ None, ['Ipv6Address']], }], 'inpcb' : [ None, { 'inp_lport' : [ None, ['unsigned be short']], 'inp_fport' : [ None, ['unsigned be short']], }], }