# 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 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 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 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"
        
    @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 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 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 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])

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"

    @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

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")

        return tcp_states[tcpcb.t_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 _get_symbol_by_address(self, module, sym_address):
            ret = ""
            
            symtable = self.sys_map

            mod = symtable[module]

            for (name, addrs) in mod.items():
                for (addr, _) in addrs:
                    key = "%s|%x" % (module, addr)
                    self.sba_cache[key] = name
         
                    if sym_address == addr or sym_address == self.shift_address + addr:
                        ret = name
                        break
                    
            return ret
        
        def get_symbol_by_address(self, module, sym_address):
            key = "%s|%x" % (module, sym_address)
            if key in self.sba_cache:
                ret = self.sba_cache[key]
            else:
                ret = self._get_symbol_by_address(module, sym_address)
            
            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,
            'dyld32_image_info' : dyld32_image_info,
            'dyld64_image_info' : dyld64_image_info,
            'fileglob' : fileglob,
            'vnode' : vnode,
            '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']], 
        }], 
}


