mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
1811 lines
56 KiB
Python
1811 lines
56 KiB
Python
# Volatility
|
|
# Copyright (C) 2010 Brendan Dolan-Gavitt
|
|
# Copyright (c) 2011 Michael Cohen <scudette@gmail.com>
|
|
#
|
|
# This file is part of Volatility.
|
|
#
|
|
# Volatility is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Volatility is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import re,copy
|
|
import sys, os
|
|
import zipfile
|
|
import struct
|
|
import time
|
|
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
|
|
|
|
x64_native_types = copy.deepcopy(native_types.x64_native_types)
|
|
|
|
x64_native_types['long'] = [8, '<q']
|
|
x64_native_types['unsigned long'] = [8, '<Q']
|
|
|
|
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 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')
|
|
|
|
while cur and cur.is_valid() and cur.offset < file_size:
|
|
buf = phys_as.zread(cur.phys_page * 4096, 4096)
|
|
|
|
yield (cur.offset.v(), buf)
|
|
|
|
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:
|
|
ret = self.fg_ops.fo_type
|
|
|
|
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
|
|
|
|
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 map.get_path() != "":
|
|
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 pdata.is_valid() and (0 <= pdata.flags <= 2):
|
|
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 bash_environment(self):
|
|
proc_as = self.get_process_address_space()
|
|
|
|
# In cases when mm is an invalid pointer
|
|
if not proc_as:
|
|
return
|
|
|
|
procvars = []
|
|
for mapping in self.get_proc_maps():
|
|
if not str(mapping.get_perms()) == "rw-" or mapping.get_path().find("bash") == -1:
|
|
continue
|
|
|
|
env_start = 0
|
|
for off in range(mapping.links.start, mapping.links.end):
|
|
# check the first index
|
|
addrstr = proc_as.read(off, self.pack_size)
|
|
if not addrstr or len(addrstr) != self.pack_size:
|
|
continue
|
|
addr = struct.unpack(self.pack_fmt, addrstr)[0]
|
|
# 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]
|
|
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 env_start == 0:
|
|
continue
|
|
|
|
envars = obj.Object(theType="Array", targetType=self.addr_type, 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 netstat(self):
|
|
for (filp, _, _) in self.lsof():
|
|
if 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
|
|
|
|
(lip, lport, rip, rport) = socket.get_connection_info()
|
|
|
|
yield (family, (socket, proto, lip, lport, rip, rport, state))
|
|
|
|
@property
|
|
def p_gid(self):
|
|
cred = self.p_ucred
|
|
|
|
if not cred.is_valid():
|
|
return "-"
|
|
|
|
if hasattr(cred, "cr_posix"):
|
|
ret = cred.cr_posix.cr_groups[0]
|
|
else:
|
|
ret = cred.cr_groups[0]
|
|
|
|
return ret
|
|
|
|
@property
|
|
def p_uid(self):
|
|
cred = self.p_ucred
|
|
|
|
if not cred.is_valid():
|
|
return "-"
|
|
|
|
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 not in seen_threads:
|
|
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 get_dyld_maps(self):
|
|
proc_as = self.get_process_address_space()
|
|
|
|
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)
|
|
|
|
# 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:
|
|
yield info_addr
|
|
#yield obj.Object("dyld_image_info", offset = info_addr, vm = proc_as)
|
|
|
|
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() != "":
|
|
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 psenv(self):
|
|
proc_as = self.get_process_address_space()
|
|
|
|
# We need a valid process AS to continue
|
|
if not proc_as:
|
|
return
|
|
|
|
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 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:
|
|
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 = ""
|
|
|
|
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):
|
|
return self.rt_nodes[0].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 ""
|
|
|
|
string_object = obj.Object("String", offset = self.string, vm = self.obj_vm, length = self.length)
|
|
return str(string_object or '')
|
|
|
|
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):
|
|
# check the heap
|
|
ret = ""
|
|
|
|
for i in [1, 2, 3, 4, 6, 7, 8, 9]:
|
|
if self.alias == i:
|
|
ret = "[heap]"
|
|
break
|
|
|
|
if ret != "":
|
|
return ret
|
|
|
|
if self.alias == 30:
|
|
ret = "[stack]"
|
|
|
|
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
|
|
|
|
def get_vnode(self):
|
|
map_obj = self
|
|
|
|
if self.is_sub_map == 1:
|
|
return "sub_map"
|
|
|
|
# find_vnode_object
|
|
vnode_object = map_obj.object.vm_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 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")
|
|
|
|
return tcp_states[tcpcb.t_state]
|
|
|
|
@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):
|
|
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.read(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.read(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))))
|
|
|
|
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,
|
|
'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)]]],
|
|
}],
|
|
'dyld32_image_info' : [ None, {
|
|
'imageFilePath' : [ None, ['pointer', ['String', dict(length = 256)]]],
|
|
}],
|
|
'dyld64_image_info' : [ None, {
|
|
'imageFilePath' : [ 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']],
|
|
}],
|
|
}
|
|
|
|
|