示例#1
0
    def read(self, addr, length):
        first_block = 0x1000 - addr % 0x1000
        full_blocks = ((length + (addr % 0x1000)) / 0x1000) - 1
        left_over = (length + addr) % 0x1000

        baddr = self.get_addr(addr)
        if baddr == None:
            return obj.NoneObject("Could not get base address at " + str(addr))

        if length < first_block:
            stuff_read = self.base.read(baddr, length)
            return stuff_read

        stuff_read = self.base.read(baddr, first_block)
        new_addr = addr + first_block
        for _i in range(0, full_blocks):
            baddr = self.get_addr(new_addr)
            if baddr == None:
                return obj.NoneObject("Could not get base address at " +
                                      str(new_addr))
            stuff_read = stuff_read + self.base.read(baddr, 0x1000)
            new_addr = new_addr + 0x1000

        if left_over > 0:
            baddr = self.get_addr(new_addr)
            if baddr == None:
                return obj.NoneObject("Could not get base address at " +
                                      str(new_addr))
            stuff_read = stuff_read + self.base.read(baddr, left_over)

        return stuff_read
示例#2
0
def dump_hashes(sysaddr, samaddr):
    if sysaddr == None:
        yield obj.NoneObject("SYSTEM address is None: Did you use the correct profile?")
    if samaddr == None:
        yield obj.NoneObject("SAM address is None: Did you use the correct profile?")
    bootkey = get_bootkey(sysaddr)
    hbootkey = get_hbootkey(samaddr, bootkey)

    if hbootkey:
        for user in get_user_keys(samaddr):
            ret = get_user_hashes(user, hbootkey)
            if not ret:
                yield obj.NoneObject("Cannot get user hashes for {0}".format(user))
            else:
                lmhash, nthash = ret
                if not lmhash:
                    lmhash = empty_lm
                if not nthash:
                    nthash = empty_nt
                ## temporary fix to prevent UnicodeDecodeError backtraces 
                ## however this can cause truncated user names as a result
                name = get_user_name(user).encode('ascii', 'ignore')
                yield "{0}:{1}:{2}:{3}:::".format(name, int(str(user.Name), 16),
                                                  lmhash.encode('hex'), nthash.encode('hex'))
    else:
        yield obj.NoneObject("Hbootkey is not valid")
示例#3
0
def dump_hashes(sysaddr, samaddr):
    if sysaddr == None:
        yield obj.NoneObject(
            "SYSTEM address is None: Did you use the correct profile?")
    if samaddr == None:
        yield obj.NoneObject(
            "SAM address is None: Did you use the correct profile?")
    bootkey = get_bootkey(sysaddr)
    hbootkey = get_hbootkey(samaddr, bootkey)

    if hbootkey:
        for user in get_user_keys(samaddr):
            ret = get_user_hashes(user, hbootkey)
            if not ret:
                yield obj.NoneObject(f"Cannot get user hashes for {user}")
            else:
                lmhash, nthash = ret
                if not lmhash:
                    lmhash = empty_lm
                if not nthash:
                    nthash = empty_nt
                ## temporary fix to prevent UnicodeDecodeError backtraces
                ## however this can cause truncated user names as a result
                name = get_user_name(user)
                if name is not None:
                    name = name.encode('ascii', 'ignore')
                else:
                    name = "(unavailable)"
                yield f"{name}:{int(str(user.Name), 16)}:{lmhash.hex()}:{nthash.hex()}:::"
    else:
        yield obj.NoneObject("Hbootkey is not valid")
示例#4
0
    def __new__(cls, theType, offset, vm, parent = None, **args):
        # Don't waste time if we're based on a NULL pointer
        # I can't think of a better check than this...
        if offset < 4:
            return obj.NoneObject("MMVAD probably instantiated from a NULL pointer, there is no tag to read")

        if not vm:
            return obj.NoneObject("Could not find address space for _MMVAD object")

        ## Note that since we were called from __new__ we can return a
        ## completely different object here (including
        ## NoneObject). This also means that we can not add any
        ## specialist methods to the _MMVAD class.

        ## We must not polute Object's constructor by providing the
        ## members or struct_size we were instantiated with
        args.pop('struct_size', None)
        args.pop('members', None)

        # Start off with an _MMVAD_LONG
        result = obj.Object('_MMVAD_LONG', offset = offset, vm = vm, parent = parent, **args)

        # Get the tag and change the vad type if necessary
        real_type = cls.tag_map.get(str(result.Tag), None)
        if not real_type:
            return obj.NoneObject("Tag {0} not known".format(str(result.Tag)))

        if result.__class__.__name__ != real_type:
            result = obj.Object(real_type, offset = offset, vm = vm, parent = parent, **args)

        return result
示例#5
0
    def _imported_functions(self):
        """
        Generator for imported functions. 

        @return: tuple (Ordinal, FunctionVA, Name)

        If the function is imported by ordinal, then Ordinal is the 
        ordinal value and Name is None. 

        If the function is imported by name, then Ordinal is the
        hint and Name is the imported function name (or None if its
        paged). 

        FunctionVA is the virtual address of the imported function,
        as applied to the IAT by the Windows loader. If the FirstThunk
        is paged, then FunctionVA will be None. 
        """

        i = 0
        while 1:
            thunk = obj.Object(
                '_IMAGE_THUNK_DATA',
                offset=self.obj_parent.DllBase + self.OriginalFirstThunk +
                i * self.obj_vm.profile.get_obj_size('_IMAGE_THUNK_DATA'),
                vm=self.obj_native_vm)

            # We've reached the end when the element is zero
            if thunk == None or thunk.AddressOfData == 0:
                break

            o = obj.NoneObject("Ordinal not accessible?")
            n = obj.NoneObject("Imported by ordinal?")
            f = obj.NoneObject("FirstThunk not accessible")

            # If the highest bit (32 for x86 and 64 for x64) is set, the function is
            # imported by ordinal and the lowest 16-bits contain the ordinal value.
            # Otherwise, the lowest bits (0-31 for x86 and 0-63 for x64) contain an
            # RVA to an _IMAGE_IMPORT_BY_NAME struct.
            if thunk.OrdinalBit == 1:
                o = thunk.Ordinal & 0xFFFF
            else:
                iibn = obj.Object("_IMAGE_IMPORT_BY_NAME",
                                  offset=self.obj_parent.DllBase +
                                  thunk.AddressOfData,
                                  vm=self.obj_native_vm)
                o = iibn.Hint
                n = iibn.Name

            # See if the import is bound (i.e. resolved)
            first_thunk = obj.Object(
                '_IMAGE_THUNK_DATA',
                offset=self.obj_parent.DllBase + self.FirstThunk +
                i * self.obj_vm.profile.get_obj_size('_IMAGE_THUNK_DATA'),
                vm=self.obj_native_vm)
            if first_thunk:
                f = first_thunk.Function.v()

            yield o, f, n
            i += 1
示例#6
0
def value_data(val):
    inline = val.DataLength & 0x80000000

    if inline:
        inline_len = val.DataLength & 0x7FFFFFFF
        if inline_len == 0 or inline_len > 4:
            valdata = None
        else:
            valdata = val.obj_vm.read(val.Data.obj_offset, inline_len)

    elif val.obj_vm.hive.Version == 5 and val.DataLength > 0x4000:
        # Value is a BIG_DATA block, stored in chunked format
        datalen = val.DataLength
        big_data = obj.Object("_CM_BIG_DATA", val.Data, val.obj_vm)
        valdata = ""
        thelist = []
        if not big_data.Count or big_data.Count > 0x80000000:
            thelist = []
        else:
            for i in range(big_data.Count):
                ptr_off = big_data.List + (i * 4)
                chunk_addr = obj.Object("unsigned int", ptr_off, val.obj_vm)
                if not val.obj_vm.is_valid_address(chunk_addr):
                    continue
                thelist.append(chunk_addr)

        for chunk in thelist:
            amount_to_read = min(BIG_DATA_MAGIC, datalen)
            chunk_data = val.obj_vm.read(chunk, amount_to_read)
            if not chunk_data:
                valdata = None
                break
            valdata += chunk_data
            datalen -= amount_to_read
    else:
        valdata = val.obj_vm.read(val.Data, val.DataLength)

    valtype = VALUE_TYPES.get(val.Type.v(), "REG_UNKNOWN")
    if valdata == None:
        return (valtype, obj.NoneObject("Value data is unreadable"))
    if valtype in ["REG_DWORD", "REG_DWORD_BIG_ENDIAN", "REG_QWORD"]:
        if len(valdata) != struct.calcsize(value_formats[valtype]):
            return (
                valtype,
                obj.NoneObject(
                    f"Value data did not match the expected data size for a {valtype}"
                ),
            )

    if valtype in ["REG_SZ", "REG_EXPAND_SZ", "REG_LINK"]:
        valdata = valdata.decode('utf-16-le', "ignore")
    elif valtype == "REG_MULTI_SZ":
        valdata = valdata.decode('utf-16-le', "ignore").split(b'\x00')
    elif valtype in ["REG_DWORD", "REG_DWORD_BIG_ENDIAN", "REG_QWORD"]:
        valdata = struct.unpack(value_formats[valtype], valdata)[0]
    return (valtype, valdata)
示例#7
0
 def render_text(self, outfd, data):
     self.table_header(outfd, [("Offset", "[addrpad]"), ("Name", "20"),
                               ("DTB", "[addrpad]")])
     for task in data:
         if isinstance(task, task_struct):
             dtb = obj.NoneObject()
             if mm_struct.is_offset_defined('pgd'):
                 pgd = task.mm.pgd
                 dtb = self.addr_space.vtop(pgd) if pgd else pgd
             self.table_row(outfd, task.obj_offset, task.comm, dtb)
         else:  # dtblist
             self.table_row(outfd, obj.NoneObject(), obj.NoneObject(), task)
示例#8
0
    def _read_long_long_phys(self, addr):
        if not addr:
            return obj.NoneObject("Unable to read None")

        try:
            string = self.base.read(addr, 8)
        except IOError:
            string = None
        if not string:
            return obj.NoneObject("Unable to read base AS at " + hex(addr))
        longlongval, = self._longlong_struct.unpack(string)
        return longlongval
示例#9
0
    def _nt_header(self):
        """Return the _IMAGE_NT_HEADERS object"""

        try:
            dos_header = obj.Object("_IMAGE_DOS_HEADER", offset = self.DllBase,
                                    vm = self.obj_native_vm)

            return dos_header.get_nt_header()
        except ValueError:
            return obj.NoneObject("Failed initial sanity checks")
        except exceptions.SanityCheckException:
            return obj.NoneObject("Failed initial sanity checks. Try -u or --unsafe")
示例#10
0
    def get_version_info(self, addr_space, offset):
        """Accepts an address space and an executable image offset
        
           Returns a VS_VERSION_INFO object of NoneObject
        """
        if not addr_space.is_valid_address(offset):
            return obj.NoneObject("Disk image not resident in memory")

        try:
            nt_header = self.get_nt_header(addr_space=addr_space,
                                           base_addr=offset)
        except ValueError, ve:
            return obj.NoneObject(
                "PE file failed initial sanity checks: {0}".format(ve))
示例#11
0
    def __init__(self, base, config, **kwargs):
        self.as_assert(base, "No base Address Space")
        standard.FileAddressSpace.__init__(self,
                                           base,
                                           config,
                                           layered=True,
                                           **kwargs)
        self.runs = []
        self.PageDict = {}
        self.HighestPage = 0
        self.PageIndex = 0
        self.AddressList = []
        self.LookupCache = {}
        self.PageCache = Store(50)
        self.MemRangeCnt = 0
        self.offset = 0
        self.entry_count = 0xFF

        # Extract header information
        self.as_assert(self.profile.has_type("PO_MEMORY_IMAGE"),
                       "PO_MEMORY_IMAGE is not available in profile")
        self.header = obj.Object('PO_MEMORY_IMAGE', 0, base)

        ## Is the signature right?
        if self.header.Signature.lower() not in ['hibr', 'wake']:
            self.header = obj.NoneObject("Invalid hibernation header")
            volmag = obj.NoneObject("Invalid hibernation header")
        else:
            volmag = obj.VolMagic(base)
            self.entry_count = volmag.HibrEntryCount.v()

        PROC_PAGE = volmag.HibrProcPage.v()

        # Check it's definitely a hibernation file
        self.as_assert(self._get_first_table_page() is not None,
                       "No xpress signature found")

        # Extract processor state
        self.ProcState = obj.Object("_KPROCESSOR_STATE", PROC_PAGE * 4096,
                                    base)

        ## This is a pointer to the page table - any ASs above us dont
        ## need to search for it.
        self.dtb = self.ProcState.SpecialRegisters.Cr3.v()

        # This is a lengthy process, it was cached, but it may be best to delay this
        # until it's absolutely necessary and/or convert it into a generator...
        self.build_page_cache()
示例#12
0
    def _read_long_long_phys_cached(self, addr, cache={}):
        """
        This is an optimized version of read_long_long_phys that memoizes the
        return values in the cache dictionary.

        When working on memory dumps on disk, this helps speed things up for a
        few read-intensive plugins (mac_compressed_swap for example).

        cache IS NOT a parameter. It's a local object that persists across calls
        and the appropriate return values are stored there for fast retrieval.
        """
        try:
            return cache[addr]
        except KeyError:
            try:
                string = self.base.read(addr, 8)
            except IOError:
                string = None
            if not string:
                val = obj.NoneObject("Unable to read_long_long_phys at " + hex(addr))
                cache[addr] = val
                return val
            longlongval, = self._longlong_struct.unpack(string)
            cache[addr] = longlongval
            return longlongval
示例#13
0
def dump_memory_hashes(addr_space, config, syshive, samhive):
    if syshive != None and samhive != None:
        sysaddr = hive.HiveAddressSpace(addr_space, config, syshive)
        samaddr = hive.HiveAddressSpace(addr_space, config, samhive)
        return dump_hashes(sysaddr, samaddr)
    return obj.NoneObject(
        "SYSTEM or SAM address is None: Did you use the correct profile?")
示例#14
0
    def dbgkd_version64(self):
        """Scan backwards from the base of KDBG to find the 
        _DBGKD_GET_VERSION64. We have a winner when kernel 
        base addresses and process list head match."""

        # Account for address masking differences in x86 and x64
        memory_model = self.obj_vm.profile.metadata.get('memory_model', '32bit')

        dbgkd_off = self.obj_offset & 0xFFFFFFFFFFFFF000
        dbgkd_end = dbgkd_off + 0x1000
        # The _DBGKD_GET_VERSION64 structure is autogenerated, so
        # this value should be correct for each profile
        dbgkd_size = self.obj_vm.profile.get_obj_size("_DBGKD_GET_VERSION64")

        while dbgkd_off <= (dbgkd_end - dbgkd_size):

            dbgkd = obj.Object("_DBGKD_GET_VERSION64",
                        offset = dbgkd_off,
                        vm = self.obj_vm)

            if memory_model == "32bit":
                KernBase = dbgkd.KernBase & 0xFFFFFFFF
                PsLoadedModuleList = dbgkd.PsLoadedModuleList & 0xFFFFFFFF
            else:
                KernBase = dbgkd.KernBase
                PsLoadedModuleList = dbgkd.PsLoadedModuleList

            if ((KernBase == self.KernBase) and (PsLoadedModuleList == self.PsLoadedModuleList)):
                return dbgkd

            dbgkd_off += 1

        return obj.NoneObject("Cannot find _DBGKD_GET_VERSION64")
示例#15
0
    def find_shared_info(self):
        """The way we find win32k!gSharedInfo on Windows 7
        is different than before. For each DWORD in the 
        win32k.sys module's .data section (DWORD-aligned)
        we check if its the HeEntrySize member of a possible
        tagSHAREDINFO structure. This should equal the size 
        of a _HANDLEENTRY.

        The HeEntrySize member didn't exist before Windows 7
        thus the need for separate methods."""

        handle_table_size = self.obj_vm.profile.\
                            get_obj_size("_HANDLEENTRY")

        handle_entry_offset = self.obj_vm.profile.\
                            get_obj_offset("tagSHAREDINFO", "HeEntrySize")

        for chunk in self._section_chunks(".data"):

            if chunk != handle_table_size:
                continue

            shared_info = obj.Object("tagSHAREDINFO",
                                     offset=chunk.obj_offset -
                                     handle_entry_offset,
                                     vm=self.obj_vm)

            if shared_info.is_valid():
                return shared_info

        return obj.NoneObject("Cannot find win32k!gSharedInfo")
示例#16
0
 def read_long(self, addr):
     _baseaddr = self.get_addr(addr)
     string = self.read(addr, 4)
     if not string:
         return obj.NoneObject("Could not read long at " + str(addr))
     longval, = self._long_struct.unpack(string)
     return longval
示例#17
0
    def __read_bytes(self, vaddr, length, pad):
        """
        Read 'length' bytes from the virtual address 'vaddr'.
        The 'pad' parameter controls whether unavailable bytes 
        are padded with zeros.
        """
        vaddr, length = int(vaddr), int(length)

        ret = ''

        while length > 0:
            chunk_len = min(length, 0x1000 - (vaddr % 0x1000))

            buf = self.__read_chunk(vaddr, chunk_len)
            if not buf:
                if pad:
                    buf = '\x00' * chunk_len
                else:
                    return obj.NoneObject("Could not read_chunks from addr " + hex(vaddr) + " of size " + hex(chunk_len))

            ret += buf
            vaddr += chunk_len
            length -= chunk_len

        return ret
示例#18
0
    def calculate(self):
        """Determines the address space"""
        addr_space = utils.load_as(self._config)

        result = None
        adrs = addr_space
        while adrs:
            if adrs.__class__.__name__ == 'WindowsHiberFileSpace32':
                sr = adrs.ProcState.SpecialRegisters

                peb = obj.NoneObject("Cannot locate a valid PEB")

                # Find the PEB by cycling through processes. This method works 
                # on all versions of Windows x86 and x64. 
                for task in tasks.pslist(addr_space):
                    if task.Peb:
                        peb = task.Peb
                        break

                result = {'header': adrs.get_header(),
                          'sr': sr,
                          'peb': peb,
                          'adrs': adrs }
            adrs = adrs.base

        if result == None:
            debug.error("Memory Image could not be identified or did not contain hiberation information")

        return result
示例#19
0
    def get_version_info(self):
        """Get the _VS_VERSION_INFO structure"""

        try:
            nt_header = self.get_nt_header()
        except ValueError, ve:
            return obj.NoneObject("PE file failed initial sanity checks: {0}".format(ve))
示例#20
0
def get_kdbg(addr_space):
    """A function designed to return the KDBG structure from 
    an address space. First we try scanning for KDBG and if 
    that fails, we try scanning for KPCR and bouncing back to
    KDBG from there. 

    Also note, both the primary and backup methods rely on the 
    4-byte KDBG.Header.OwnerTag. If someone overwrites this 
    value, then neither method will succeed. The same is true 
    even if a user specifies --kdbg, because we check for the 
    OwnerTag even in that case. 
    """

    kdbgo = obj.VolMagic(addr_space).KDBG.v()

    kdbg = obj.Object("_KDDEBUGGER_DATA64", offset=kdbgo, vm=addr_space)

    if kdbg.is_valid():
        return kdbg

    # Fall back to finding it via the KPCR. We cannot
    # accept the first/best suggestion, because only
    # the KPCR for the first CPU allows us to find KDBG.
    for kpcr_off in obj.VolMagic(addr_space).KPCR.generate_suggestions():

        kpcr = obj.Object("_KPCR", offset=kpcr_off, vm=addr_space)

        kdbg = kpcr.get_kdbg()

        if kdbg.is_valid():
            return kdbg

    return obj.NoneObject(
        "KDDEBUGGER structure not found using either KDBG signature or KPCR pointer"
    )
示例#21
0
 def Thread(self):
     """Return the ETHREAD if its thread owned"""
     if self.ThreadOwned:
         return self.pOwner.\
                     dereference_as("tagTHREADINFO").\
                     pEThread.dereference()
     return obj.NoneObject("Cannot find thread")
示例#22
0
 def read_long(self, addr: int) -> int:
     _baseaddr = self.get_addr(addr)
     string = self.read(addr, 4)
     if not string:
         return obj.NoneObject(f"Could not read long at {addr}")
     (longval, ) = self._long_struct.unpack(string)
     return longval
示例#23
0
    def get_debug_directory(self):
        """Return the debug directory object for this PE"""

        try:
            data_dir = self.debug_dir()
        except ValueError, why:
            return obj.NoneObject(str(why))
    def Peb32(self):
        """ Returns a _PEB object which is using the process address space.

        The PEB structure is referencing back into the process address
        space so we need to switch address spaces when we look at
        it. This method ensures this happens automatically.
        """
        wow64process = self.Wow64Process

        if wow64process.is_valid():
            process_ad = self.get_process_address_space()
            if process_ad:

                # starting with windows 10 the Wow64Process member
                # points to an _EWOW64PROCESS with a Peb
                try:
                    offset = wow64process.Peb
                except AttributeError:
                    offset = wow64process

                peb32 = obj.Object("_PEB32", offset = offset, vm = process_ad, name = "Peb32", parent = self)

                if peb32.is_valid():
                    return peb32
        
        return obj.NoneObject("Peb32 not found")
 def dereference(self):
     length = self.Length.v()
     if length > 0 and length <= 1024:
         data = self.Buffer.dereference_as('String', encoding = 'utf16', length = length)
         return data
     else:
         return obj.NoneObject("Buffer length {0} for _UNICODE_STRING not within bounds".format(length))
    def get_object_bottom_up(self, struct_name, object_type, skip_type_check):
        """Get the windows object contained within this pool
        by using the bottom-up approach to finding the object
        """

        if not object_type:
            return obj.Object(struct_name, vm = self.obj_vm, 
                        offset = self.obj_offset +
                        self.obj_vm.profile.get_obj_size("_POOL_HEADER"), 
                        native_vm = self.obj_native_vm)

        pool_alignment = obj.VolMagic(self.obj_vm).PoolAlignment.v()

        the_object = obj.Object(struct_name, vm = self.obj_vm, 
                        offset = (self.obj_offset + self.BlockSize * pool_alignment - 
                        common.pool_align(self.obj_vm, struct_name, pool_alignment)),
                        native_vm = self.obj_native_vm)

        header = the_object.get_object_header()

        if (skip_type_check or 
                    header.get_object_type() == object_type):
            return the_object
        else:
            return obj.NoneObject("Cannot find the object")
示例#27
0
    def find_gahti(self):
        """Find this session's gahti. 

        This can potentially be much faster by searching for 
        '\0' * sizeof(tagHANDLETYPEINFO) instead 
        of moving on a dword aligned boundary through
        the section. 
        """

        for chunk in self._section_chunks(".rdata"):
            if not chunk.is_valid():
                continue

            gahti = obj.Object("gahti",
                               offset=chunk.obj_offset,
                               vm=self.obj_vm)

            ## The sanity check here is based on the fact that the first entry
            ## in the gahti is always for TYPE_FREE. The fnDestroy pointer will
            ## be NULL, the alloc tag will be an empty string, and the creation
            ## flags will be zero. We also then check the alloc tag of the first
            ## USER handle type which should be Uswd (TYPE_WINDOW).
            ## Update: fnDestroy is no longer NULL for TYPE_FREE on Win8/2012.
            if (str(gahti.types[0].dwAllocTag) == ''
                    and gahti.types[0].bObjectCreateFlags == 0
                    and str(gahti.types[1].dwAllocTag) == "Uswd"):
                return gahti

        return obj.NoneObject("Cannot find win32k!_gahti")
示例#28
0
    def find_shared_info(self):
        """Find this session's tagSHAREDINFO structure. 

        This structure is embedded in win32k's .data section, 
        (i.e. not in dynamically allocated memory). Thus we 
        iterate over each DWORD-aligned possibility and treat 
        it as a tagSHAREDINFO until the sanity checks are met. 
        """

        for chunk in self._section_chunks(".data"):
            # If the base of the value is paged
            if not chunk.is_valid():
                continue
            # Treat it as a shared info struct
            shared_info = obj.Object("tagSHAREDINFO",
                                     offset=chunk.obj_offset,
                                     vm=self.obj_vm)
            # Sanity check it
            try:
                if shared_info.is_valid():
                    return shared_info
            except obj.InvalidOffsetError:
                pass

        return obj.NoneObject("Cannot find win32k!gSharedInfo")
示例#29
0
 def read_long(self, addr):
     _baseaddr = self.translate(addr)
     string = self.read(addr, 4)
     if not string:
         return obj.NoneObject("Could not read data at " + str(addr))
     (longval, ) = struct.unpack('=I', string)
     return longval
示例#30
0
    def virtual_process_from_physical_offset(addr_space, offset):
        """ Returns a virtual process from a physical offset in memory """
        # Since this is a physical offset, we find the process
        flat_addr_space = utils.load_as(addr_space.get_config(), astype = 'physical')
        flateproc = obj.Object("_EPROCESS", offset, flat_addr_space)
        # then use the virtual address of its first thread to get into virtual land
        # (Note: the addr_space and flat_addr_space use the same config, so should have the same profile)
        tleoffset = addr_space.profile.get_obj_offset("_ETHREAD", "ThreadListEntry")

        # start out with the member offset given to us from the profile 
        offsets = [tleoffset]

        # if (and only if) we're dealing with 64-bit Windows 7 SP1 
        # then add the other commonly seen member offset to the list 
        meta = addr_space.profile.metadata
        major = meta.get("major", 0)
        minor = meta.get("minor", 0)
        build = meta.get("build", 0)
        version = (major, minor, build)

        if meta.get("memory_model") == "64bit" and version == (6, 1, 7601):
            offsets.append(tleoffset + 8)

        ## use the member offset from the profile 
        for ofs in offsets:
            ethread = obj.Object("_ETHREAD", offset = flateproc.ThreadListHead.Flink.v() - ofs, vm = addr_space)
            # and ask for the thread's process to get an _EPROCESS with a virtual address space
            virtual_process = ethread.owning_process()
            # Sanity check the bounce. See Issue 154.
            if virtual_process and offset == addr_space.vtop(virtual_process.obj_offset):
                return virtual_process

        return obj.NoneObject("Unable to bounce back from virtual _ETHREAD to virtual _EPROCESS")