コード例 #1
0
ファイル: win32.py プロジェクト: yashomer1994/speakeasy
    def setup(self, stack_commit=0):

        # Set the emulator to run in protected mode
        self.om = objman.ObjectManager(emu=self)

        arch = self.get_arch()
        self._setup_gdt(arch)
        self.setup_user_shared_data()
        self.set_ptr_size(self.arch)

        if arch == _arch.ARCH_X86:
            self.peb_addr = self.fs_addr + 0x30
        elif arch == _arch.ARCH_AMD64:
            self.peb_addr = self.gs_addr + 0x60

        self.api = WindowsApi(self)

        # Init symlinks
        for sl in self.symlinks:
            self.om.add_symlink(sl['name'], sl['target'])

        self.init_sys_modules(self.config_system_modules)
コード例 #2
0
ファイル: win32.py プロジェクト: yashomer1994/speakeasy
class Win32Emulator(WindowsEmulator):
    """
    User Mode Windows Emulator Class
    """
    def __init__(self,
                 config,
                 argv=[],
                 debug=False,
                 logger=None,
                 exit_event=None):
        super(Win32Emulator, self).__init__(config,
                                            debug=debug,
                                            logger=logger,
                                            exit_event=exit_event)

        self.last_error = 0
        self.peb_addr = 0
        self.heap_allocs = []
        self.argv = argv
        self.sessman = SessionManager(config)
        self.com = COM(config)

    def get_argv(self):
        """
        Get command line arguments (if any) that are being passed
        to the emulated process. (e.g. main(argv))
        """
        argv0 = ''
        out = []

        if len(self.argv):
            for m in self.modules:
                pe = m[0]
                emu_path = m[2]
                if pe.is_exe():
                    argv0 = emu_path
            out = [argv0] + self.argv
        elif self.command_line:
            out = shlex.split(self.command_line, posix=False)
        return out

    def set_last_error(self, code):
        """
        Set the last error code for the current thread
        """
        if self.curr_thread:
            self.curr_thread.set_last_error(code)

    def get_last_error(self):
        """
        Get the last error code for the current thread
        """
        if self.curr_thread:
            return self.curr_thread.get_last_error()

    def get_session_manager(self):
        """
        Get the session manager for the emulator. This will manage things like desktops,
        windows, and session isolation
        """
        return self.sessman

    def add_vectored_exception_handler(self, first, handler):
        """
        Add a vectored exception handler that will be executed on an exception
        """
        self.veh_handlers.append(handler)

    def remove_vectored_exception_handler(self, handler):
        """
        Remove a vectored exception handler
        """
        self.veh_handlers.remove(handler)

    def get_processes(self):
        if len(self.processes) <= 1:
            self.init_processes(self.config_processes)
        return self.processes

    def init_processes(self, processes):
        """
        Initialize configured processes set in the emulator config
        """
        for proc in processes:
            p = objman.Process(self)
            self.add_object(p)

            p.name = proc.get('name', '')
            new_pid = proc.get('pid')
            if new_pid:
                p.pid = new_pid

            base = proc.get('base_addr')

            if isinstance(base, str):
                base = int(base, 16)
            p.base = base
            p.path = proc.get('path')
            p.session = proc.get('session', 0)
            p.image = ntpath.basename(p.path)

            self.processes.append(p)

    def load_module(self, path=None, data=None):
        """
        Load a module into the emulator space from the specified path
        """
        pe = self.load_pe(path=path,
                          data=data,
                          imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError('Unsupported architecture: %s', pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        if not data:
            file_name = os.path.basename(path) + '.exe'
            mod_name = os.path.splitext(file_name)[0]

        else:
            mod_hash = hashlib.sha256()
            mod_hash.update(data)
            mod_hash = mod_hash.hexdigest()
            mod_name = mod_hash
            file_name = '%s.exe' % (mod_name)

        self.api = WindowsApi(self)

        cd = self.get_cd()
        if not cd.endswith('\\'):
            cd += '\\'
        emu_path = cd + file_name

        if not data:
            with open(path, 'rb') as f:
                data = f.read()
        self.fileman.add_existing_file(emu_path, data)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            self.profiler.strings['ansi'] = self.get_ansi_strings(data)
            self.profiler.strings['unicode'] = self.get_unicode_strings(data)

        # Set the emulated path
        emu_path = ''
        self.cd = self.get_cd()
        if self.cd:
            if not self.cd.endswith('\\'):
                self.cd += '\\'
            emu_path = self.cd + os.path.basename(file_name)

        pe.set_emu_path(emu_path)

        self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
        self.mem_write(pe.base, pe.mapped_image)

        self.setup()

        if not self.stack_base:
            self.stack_base, stack_addr = self.alloc_stack(0x12000)
        self.set_func_args(self.stack_base, self.return_hook)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(
                    addr, data_ptr.to_bytes(self.get_ptr_size(), 'little'))
        return pe

    def run_module(self, module, all_entrypoints=False):
        """
        Begin emulating a previously loaded module

        Arguments:
            module: Module to emulate
        """

        if not module:
            self.stop()
            raise Win32EmuError('Module not found')

        # Check if any TLS callbacks exist, these run before the module's entry point
        tls = module.get_tls_callbacks()
        for i, cb_addr in enumerate(tls):
            base = module.get_base()
            if base < cb_addr < base + module.get_image_size():
                run = Run()
                run.start_addr = cb_addr
                run.type = 'tls_callback_%d' % (i)
                run.args = [base, DLL_PROCESS_ATTACH, 0]
                self.add_run(run)

        # Queue up the module's main entry point
        ep = module.base + module.ep

        run = Run()
        run.start_addr = ep

        main_exe = None
        if not module.is_exe():
            run.args = [module.base, DLL_PROCESS_ATTACH, 0]
            run.type = 'dll_entry.DLL_PROCESS_ATTACH'
            container = self.init_container_process()
            if container:
                self.processes.append(container)
                self.curr_process = container
        else:
            run.type = 'module_entry'
            main_exe = module
            run.args = [
                self.mem_map(8, tag='emu.module_arg_%d' % (i))
                for i in range(4)
            ]

        if main_exe:
            self.user_modules = [main_exe] + self.user_modules

        self.add_run(run)

        if all_entrypoints:
            # Only emulate a subset of all the exported functions
            # There are some modules (such as the windows kernel) with
            # thousands of exports
            exports = [
                k for k in module.get_exports()[:MAX_EXPORTS_TO_EMULATE]
            ]

            if exports:
                args = [
                    self.mem_map(8,
                                 tag='emu.export_arg_%d' % (i),
                                 base=0x41420000) for i in range(4)
                ]  # noqa
                for exp in exports:
                    if exp.name in ('DllMain', 'ServiceMain'):
                        continue
                    run = Run()
                    if exp.name:
                        fn = exp.name
                    else:
                        fn = 'no_name'

                    run.type = 'export.%s' % (fn)
                    run.start_addr = exp.address
                    # Here we set dummy args to pass into the export function
                    run.args = args
                    # Store these runs and only queue them before the unload
                    # routine this is because some exports may not be ready to
                    # be called yet
                    self.add_run(run)

        # Create an empty process object for the module if none is
        # supplied
        if len(self.processes) == 0:
            p = objman.Process(self,
                               path=module.get_emu_path(),
                               base=module.base,
                               pe=module,
                               cmdline=self.command_line)
            self.curr_process = p
            self.om.objects.update({p.address: p})
            mm = self.get_address_map(module.base)
            if mm:
                mm.process = self.curr_process

        t = objman.Thread(self,
                          stack_base=self.stack_base,
                          stack_commit=module.stack_commit)

        self.om.objects.update({t.address: t})
        self.curr_process.threads.append(t)
        self.curr_thread = t

        peb = self.alloc_peb(self.curr_process)

        # Set the TEB
        self.init_teb(t, peb)

        # Begin emulation
        self.start()

    def emulate_module(self, path):
        """
        Load and emulate binary from the given path
        """
        mod = self.load_module(path)
        self.run_module(mod)

    def load_shellcode(self, path, arch, data=None):
        """
        Load position independent code (i.e. shellcode) to prepare for emulation
        """
        sc_hash = None

        if arch == 'x86':
            arch = _arch.ARCH_X86
        elif arch in ('x64', 'amd64'):
            arch = _arch.ARCH_AMD64

        self.arch = arch

        if data:
            sc_hash = hashlib.sha256()
            sc_hash.update(data)
            sc_hash = sc_hash.hexdigest()
            sc = data
        else:
            with open(path, 'rb') as scpath:
                sc = scpath.read()

            sc_hash = hashlib.sha256()
            sc_hash.update(sc)
            sc_hash = sc_hash.hexdigest()

        if self.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif self.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError('Unsupported architecture: %s' % self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, self.arch)

        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        sc_tag = 'emu.shellcode.%s' % (sc_hash)

        # Map the shellcode into memory
        sc_addr = self.mem_map(len(sc), tag=sc_tag)
        self.mem_write(sc_addr, sc)

        self.pic_buffers.append((path, sc_addr, len(sc)))

        sc_arch = 'unknown'
        if arch == _arch.ARCH_AMD64:
            sc_arch = 'x64'
        elif arch == _arch.ARCH_X86:
            sc_arch = 'x86'

        if self.profiler:
            self.input = {
                'path': path,
                'sha256': sc_hash,
                'size': len(sc),
                'arch': sc_arch,
                'mem_tag': sc_tag,
                'emu_version': self.get_emu_version(),
                'os_run': self.get_osver_string()
            }
            self.profiler.add_input_metadata(self.input)
            # Strings the initial buffer so that we can detect decoded strings later on
            if self.do_strings:
                self.profiler.strings['ansi'] = self.get_ansi_strings(sc)
                self.profiler.strings['unicode'] = self.get_unicode_strings(sc)

        self.setup()

        return sc_addr

    def run_shellcode(self, sc_addr, offset=0):
        """
        Begin emulating position independent code (i.e. shellcode) to prepare for emulation
        """

        target = None
        for sc_path, _sc_addr, size in self.pic_buffers:
            if _sc_addr == sc_addr:
                target = _sc_addr
                break

        if not target:
            raise Win32EmuError('Invalid shellcode address')

        stack_commit = 0x4000

        self.stack_base, stack_addr = self.alloc_stack(stack_commit)
        self.set_func_args(self.stack_base, self.return_hook, 0x7000)

        run = Run()
        run.type = 'shellcode'
        run.start_addr = sc_addr + offset
        run.instr_cnt = 0
        args = [
            self.mem_map(1024,
                         tag='emu.shellcode_arg_%d' % (i),
                         base=0x41420000 + i) for i in range(4)
        ]
        run.args = (args)

        self.reg_write(_arch.X86_REG_ECX, 1024)

        self.add_run(run)

        # Create an empty process object for the shellcode if none is
        # supplied
        container = self.init_container_process()
        if container:
            self.processes.append(container)
            self.curr_process = container
        else:
            p = objman.Process(self)
            self.processes.append(p)
            self.curr_process = p

        mm = self.get_address_map(sc_addr)
        if mm:
            mm.set_process(self.curr_process)

        t = objman.Thread(self,
                          stack_base=self.stack_base,
                          stack_commit=stack_commit)
        self.om.objects.update({t.address: t})
        self.curr_process.threads.append(t)

        self.curr_thread = t

        peb = self.alloc_peb(self.curr_process)

        # Set the TEB
        self.init_teb(t, peb)

        self.start()

    def alloc_peb(self, proc):
        """
        Allocate memory for the Process Environment Block (PEB)
        """

        if proc.is_peb_active:
            return
        size = proc.get_peb_ldr().sizeof()
        res, size = self.get_valid_ranges(size)
        self.mem_reserve(size, base=res, tag='emu.struct.PEB_LDR_DATA')
        proc.set_peb_ldr_address(res)

        peb = proc.get_peb()
        proc.is_peb_active = True
        peb.object.ImageBaseAddress = proc.base
        peb.write_back()
        return peb

    def set_unhandled_exception_handler(self, handler_addr):
        """
        Establish a handler for unhandled exceptions that occur during emulation
        """
        self.unhandled_exception_filter = handler_addr

    def setup(self, stack_commit=0):

        # Set the emulator to run in protected mode
        self.om = objman.ObjectManager(emu=self)

        arch = self.get_arch()
        self._setup_gdt(arch)
        self.setup_user_shared_data()
        self.set_ptr_size(self.arch)

        if arch == _arch.ARCH_X86:
            self.peb_addr = self.fs_addr + 0x30
        elif arch == _arch.ARCH_AMD64:
            self.peb_addr = self.gs_addr + 0x60

        self.api = WindowsApi(self)

        # Init symlinks
        for sl in self.symlinks:
            self.om.add_symlink(sl['name'], sl['target'])

        self.init_sys_modules(self.config_system_modules)

    def init_sys_modules(self, modules_config):
        """
        Get the system modules (e.g. drivers) that are loaded in the emulator
        """
        sys_mods = []

        for modconf in modules_config:

            mod = w32common.DecoyModule()
            mod.name = modconf['name']
            base = modconf.get('base_addr')
            if isinstance(base, str):
                base = int(base, 16)

            mod.decoy_base = base
            mod.decoy_path = modconf['path']

            drv = modconf.get('driver')
            if drv:
                devs = drv.get('devices')
                for dev in devs:
                    name = dev.get('name', '')
                    do = self.new_object(objman.Device)
                    do.name = name

            sys_mods.append(mod)
        return sys_mods

    def init_container_process(self):
        """
        Create a process to be used to host shellcode or DLLs
        """
        for p in self.config_processes:
            if p.get('is_main_exe'):
                name = p.get('name', '')
                emu_path = p.get('path', '')
                base = p.get('base_addr', 0)
                if isinstance(base, str):
                    base = int(base, 0)
                cmd_line = p.get('command_line', '')

                proc = objman.Process(self,
                                      name=name,
                                      path=emu_path,
                                      base=base,
                                      cmdline=cmd_line)
                return proc
        return None

    def get_user_modules(self):
        """
        Get the user modules (e.g. dlls) that are loaded in the emulator
        """
        # Generate the decoy user module list
        if len(self.user_modules) < 2:
            # Check if we have a host process configured
            proc_mod = None
            for p in self.config_processes:

                if not self.user_modules and p.get('is_main_exe'):
                    proc_mod = p
                    break

            if proc_mod:
                all_user_mods = [proc_mod] + self.config_user_modules
                user_modules = self.init_user_modules(all_user_mods)
            else:
                user_modules = self.init_user_modules(self.config_user_modules)

            self.user_modules += user_modules
            # add sample to user modules list if it is a dll
            if self.modules and not self.modules[0][0].is_exe():
                self.user_modules.append(self.modules[0][0])

        return self.user_modules

    def exit_process(self):
        """
        An emulated binary is attempted to terminate its current process.
        Signal that the run has finished.
        """
        self.enable_code_hook()
        self.run_complete = True

    def _hook_mem_unmapped(self, emu, access, address, size, value, user_data):

        _access = self.emu_eng.mem_access.get(access)

        if _access == common.INVALID_MEM_READ:
            p = self.get_current_process()
            pld = p.get_peb_ldr()
            if address > pld.address and address < (pld.address +
                                                    pld.sizeof()):
                self.mem_map_reserve(pld.address)
                user_mods = self.get_user_modules()
                self.init_peb(user_mods)
                return True
        return super(Win32Emulator,
                     self)._hook_mem_unmapped(emu, access, address, size,
                                              value, user_data)

    def set_hooks(self):
        """Set the emulator callbacks"""

        super(Win32Emulator, self).set_hooks()

        if not self.builtin_hooks_set:
            self.add_mem_invalid_hook(cb=self._hook_mem_unmapped)
            self.add_interrupt_hook(cb=self._hook_interrupt)
            self.builtin_hooks_set = True

        self.set_mem_tracing_hooks()

    def stop(self):
        self.run_complete = True
        super(Win32Emulator, self).stop()

    def on_emu_complete(self):
        """
        Called when all runs have completed emulation
        """
        if not self.emu_complete:
            self.emu_complete = True
            if self.do_strings and self.profiler:
                dec_ansi, dec_unicode = self.get_mem_strings()
                dec_ansi = [
                    a for a in dec_ansi
                    if a not in self.profiler.strings['ansi']
                ]
                dec_unicode = [
                    u for u in dec_unicode
                    if u not in self.profiler.strings['unicode']
                ]
                self.profiler.decoded_strings['ansi'] = dec_ansi
                self.profiler.decoded_strings['unicode'] = dec_unicode
        self.stop()

    def on_run_complete(self):
        """
        Clean up after a run completes. This function will pop the
        next run from the run queue and emulate it.
        """
        self.run_complete = True
        self.curr_run.ret_val = self.get_return_val()
        if self.profiler:
            self.profiler.log_dropped_files(self.curr_run,
                                            self.get_dropped_files())

        return self._exec_next_run()

    def heap_alloc(self, size, heap='None'):
        """
        Allocate a memory chunk and add it to the "heap"
        """
        addr = self.mem_map(size, base=None, tag='api.heap.%s' % (heap))
        self.heap_allocs.append((addr, size, heap))
        return addr
コード例 #3
0
ファイル: win32.py プロジェクト: yashomer1994/speakeasy
    def load_module(self, path=None, data=None):
        """
        Load a module into the emulator space from the specified path
        """
        pe = self.load_pe(path=path,
                          data=data,
                          imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError('Unsupported architecture: %s', pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        if not data:
            file_name = os.path.basename(path) + '.exe'
            mod_name = os.path.splitext(file_name)[0]

        else:
            mod_hash = hashlib.sha256()
            mod_hash.update(data)
            mod_hash = mod_hash.hexdigest()
            mod_name = mod_hash
            file_name = '%s.exe' % (mod_name)

        self.api = WindowsApi(self)

        cd = self.get_cd()
        if not cd.endswith('\\'):
            cd += '\\'
        emu_path = cd + file_name

        if not data:
            with open(path, 'rb') as f:
                data = f.read()
        self.fileman.add_existing_file(emu_path, data)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            self.profiler.strings['ansi'] = self.get_ansi_strings(data)
            self.profiler.strings['unicode'] = self.get_unicode_strings(data)

        # Set the emulated path
        emu_path = ''
        self.cd = self.get_cd()
        if self.cd:
            if not self.cd.endswith('\\'):
                self.cd += '\\'
            emu_path = self.cd + os.path.basename(file_name)

        pe.set_emu_path(emu_path)

        self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
        self.mem_write(pe.base, pe.mapped_image)

        self.setup()

        if not self.stack_base:
            self.stack_base, stack_addr = self.alloc_stack(0x12000)
        self.set_func_args(self.stack_base, self.return_hook)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(
                    addr, data_ptr.to_bytes(self.get_ptr_size(), 'little'))
        return pe
コード例 #4
0
class Win32Emulator(WindowsEmulator):
    """
    User Mode Windows Emulator Class
    """

    def __init__(self, config, argv=[], debug=False, logger=None, exit_event=None):
        super(Win32Emulator, self).__init__(
            config, debug=debug, logger=logger, exit_event=exit_event
        )

        self.last_error = 0
        self.peb_addr = 0
        self.heap_allocs = []
        self.argv = argv
        self.sessman = SessionManager(config)
        self.com = COM(config)

    def get_argv(self):
        """
        Get command line arguments (if any) that are being passed
        to the emulated process. (e.g. main(argv))
        """
        argv0 = ""
        out = []

        if len(self.argv):
            for m in self.modules:
                pe = m[0]
                emu_path = m[2]
                if pe.is_exe():
                    argv0 = emu_path
            out = [argv0] + self.argv
        elif self.command_line:
            out = shlex.split(self.command_line, posix=False)
        return out

    def set_last_error(self, code):
        """
        Set the last error code for the current thread
        """
        if self.curr_thread:
            self.curr_thread.set_last_error(code)

    def get_last_error(self):
        """
        Get the last error code for the current thread
        """
        if self.curr_thread:
            return self.curr_thread.get_last_error()

    def get_session_manager(self):
        """
        Get the session manager for the emulator. This will manage things like desktops,
        windows, and session isolation
        """
        return self.sessman

    def add_vectored_exception_handler(self, first, handler):
        """
        Add a vectored exception handler that will be executed on an exception
        """
        self.veh_handlers.append(handler)

    def remove_vectored_exception_handler(self, handler):
        """
        Remove a vectored exception handler
        """
        self.veh_handlers.remove(handler)

    def get_processes(self):
        if len(self.processes) <= 1:
            self.init_processes(self.config_processes)
        return self.processes

    def init_processes(self, processes):
        """
        Initialize configured processes set in the emulator config
        """
        for proc in processes:
            p = objman.Process(self)
            self.add_object(p)

            p.name = proc.get("name", "")
            new_pid = proc.get("pid")
            if new_pid:
                p.pid = new_pid

            base = proc.get("base_addr")

            if isinstance(base, str):
                base = int(base, 16)
            p.base = base
            p.path = proc.get("path")
            p.session = proc.get("session", 0)
            p.image = ntpath.basename(p.path)

            self.processes.append(p)

    def load_module(self, path=None, data=None, first_time_setup=True):
        """
        Load a module into the emulator space from the specified path
        """
        self._init_name(path, data)
        pe = self.load_pe(path=path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError("Unsupported architecture: %s", pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        # No need to initialize the engine and Capstone again
        if first_time_setup:
            self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

            if not self.disasm_eng:
                self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        self.api = WindowsApi(self)

        cd = self.get_cd()
        if not cd.endswith("\\"):
            cd += "\\"
        emu_path = cd + self.file_name

        if not data:
            with open(path, "rb") as f:
                data = f.read()
        self.fileman.add_existing_file(emu_path, data)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            self.profiler.strings["ansi"] = [a[1] for a in self.get_ansi_strings(data)]
            self.profiler.strings["unicode"] = [
                u[1] for u in self.get_unicode_strings(data)
            ]
        # Set the emulated path
        emu_path = ""
        self.cd = self.get_cd()
        if self.cd:
            if not self.cd.endswith('\\'):
                self.cd += '\\'
            emu_path = self.cd + os.path.basename(self.file_name)

        pe.set_emu_path(emu_path)

        # There's a bit of a problem here, if we cannot reserve memory
        # at the PE's desired base address, and the relocation table
        # is not present, we can't rebase it. So this is gonna have to
        # be a bit of a hack for binaries without a relocation table.
        # This logic is really only for child processes, since we're pretty
        # much guarenteed memory at the base address of the main module.
        #   1. If the memory at the child's desired load address is already
        #      being used, remap it somewhere else. I'm pretty sure that
        #      the already-used memory will always be for a module,
        #      since desired load addresses don't really vary across PEs
        #   2. Fix up any modules that speakeasy has open for the parent
        #      to reflect where it was remapped
        #   3. Try and grab memory at the child's desired base address,
        #      if that isn't still isn't possible, we're out of luck
        #
        # But if the relocation table is present, we can rebase it,
        # so we do that instead of the above hack.
        imgbase = pe.OPTIONAL_HEADER.ImageBase
        ranges = self.get_valid_ranges(pe.image_size, addr=imgbase)
        base, size = ranges

        if base != imgbase:
            if pe.has_reloc_table():
                pe.rebase(base)
            else:
                parent_map = self.get_address_map(imgbase)

                # Already being used by the parent, so let's remap the parent
                # Do get_valid_ranges on the parent map size so we get a
                # suitable region for it
                new_parent_mem, unused = self.get_valid_ranges(parent_map.size)
                new_parent_mem = self.mem_remap(imgbase, new_parent_mem)

                # Failed
                if new_parent_mem == -1:
                    # XXX what to do here
                    pass

                # Update parent module pointer
                for pe_, ranges_, emu_path_ in self.modules:
                    base_, size_ = ranges_

                    if base_ == imgbase:
                        self.modules.remove((pe_, ranges_, emu_path_))
                        self.modules.append((pe_, (new_parent_mem, size_), emu_path_))
                        break

                # Alright, let's try to grab that memory for the child again
                ranges = self.get_valid_ranges(pe.image_size, addr=imgbase)
                base, size = ranges

                if base != imgbase:
                    # Out of luck
                    # XXX what to do here
                    pass

        self.mem_map(pe.image_size, base=base,
                tag='emu.module.%s' % (self.mod_name))

        self.modules.append((pe, ranges, emu_path))
        self.mem_write(pe.base, pe.mapped_image)

        self.setup(first_time_setup=first_time_setup)

        if not self.stack_base:
            self.stack_base, stack_addr = self.alloc_stack(0x12000)
        self.set_func_args(self.stack_base, self.return_hook)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(addr, data_ptr.to_bytes(self.get_ptr_size(), "little"))
        return pe

    def prepare_module_for_emulation(self, module, all_entrypoints, entrypoints):
        if not module:
            self.stop()
            raise Win32EmuError("Module not found")

        # Check if any TLS callbacks exist, these run before the module's entry point
        tls = module.get_tls_callbacks()
        for i, cb_addr in enumerate(tls):
            base = module.get_base()
            if base < cb_addr < base + module.get_image_size():
                run = Run()
                run.start_addr = cb_addr
                run.type = "tls_callback_%d" % (i)
                run.args = [base, DLL_PROCESS_ATTACH, 0]
                self.add_run(run)

        ep = module.base + module.ep

        run = Run()
        run.start_addr = ep

        main_exe = None
        if not module.is_exe():
            run.args = [module.base, DLL_PROCESS_ATTACH, 0]
            run.type = "dll_entry.DLL_PROCESS_ATTACH"
            container = self.init_container_process()
            if container:
                self.processes.append(container)
                self.curr_process = container
        else:
            run.type = "module_entry"
            main_exe = module
            run.args = [
                self.mem_map(8, tag="emu.module_arg_%d" % (i)) for i in range(4)
            ]

        if main_exe:
            self.user_modules = [main_exe] + self.user_modules
        # we consider this run only if all entry_points is selected or DLL_PROCESS_ATTACH is in the entrypoints
        if all_entrypoints or "DLL_PROCESS_ATTACH" in entrypoints:
            self.add_run(run)

        if all_entrypoints or entrypoints:
            # Only emulate a subset of all the exported functions
            # There are some modules (such as the windows kernel) with
            # thousands of exports
            exports = [k for k in module.get_exports()[:MAX_EXPORTS_TO_EMULATE]]

            if exports:
                args = [
                    self.mem_map(8, tag="emu.export_arg_%d" % (i), base=0x41420000)
                    for i in range(4)
                ]  # noqa
                for exp in exports:
                    if exp.name in ("DllMain",):
                        continue
                    if all_entrypoints or exp.name in entrypoints:
                        run = Run()
                        if exp.name:
                            fn = exp.name
                        else:
                            fn = "no_name"

                        run.type = "export.%s" % (fn)
                        run.start_addr = exp.address
                        if exp.name == "ServiceMain":
                            # ServiceMain accepts a (argc, argv) pair like main().
                            #
                            # now, we're not exactly sure if we're in A or W mode.
                            # maybe there are some hints we could take to guess this.
                            # instead, we'll assume W mode and use default service name "IPRIP".
                            #
                            # hack: if we're actually in A mode, then string routines
                            # will think the service name is "I" which isn't perfect,
                            # but might still be good enough.
                            #
                            # layout:
                            #   argc: 1
                            #   argv:
                            #     0x00:    (argv[0]) pointer to +0x10 -+
                            #     0x04/08: (argv[1]) 0x0               |
                            #     0x10:    "IPRIP"  <------------------+
                            svc_name = "IPRIP\x00".encode("utf-16le")
                            argc = 1
                            argv = self.mem_map(
                                len(svc_name) + 0x10,
                                tag="emu.export_ServiceMain_argv",
                                base=0x41420000,
                            )

                            self.write_ptr(argv, argv + 0x10)
                            self.mem_write(argv + 0x10, svc_name)

                            run.args = [argc, argv]
                        else:
                            # Here we set dummy args to pass into the export function
                            run.args = args
                        # Store these runs and only queue them before the unload
                        # routine this is because some exports may not be ready to
                        # be called yet
                        self.add_run(run)

        return

    def run_module(self, module, all_entrypoints=False, emulate_children=False, entrypoints=None):
        """
        Begin emulating a previously loaded module

        Arguments:
            module: Module to emulate
        """
        if entrypoints is None:
            entrypoints = []

        self.prepare_module_for_emulation(module, all_entrypoints, entrypoints)

        # Create an empty process object for the module if none is
        # supplied, only do this for the main module
        if len(self.processes) == 0:
            p = objman.Process(
                self,
                path=module.get_emu_path(),
                base=module.base,
                pe=module,
                cmdline=self.command_line,
            )
            self.curr_process = p
            self.om.objects.update({p.address: p})
            mm = self.get_address_map(module.base)
            if mm:
                mm.process = self.curr_process

        t = objman.Thread(
            self, stack_base=self.stack_base, stack_commit=module.stack_commit
        )

        self.om.objects.update({t.address: t})
        self.curr_process.threads.append(t)
        self.curr_thread = t

        peb = self.alloc_peb(self.curr_process)

        # Set the TEB
        self.init_teb(t, peb)

        # Begin emulation of main module
        self.start()

        if not emulate_children or len(self.child_processes) == 0:
            return

        # Emulate any child processes
        while len(self.child_processes) > 0:
            child = self.child_processes.pop(0)

            child.pe = self.load_module(data=child.pe_data, first_time_setup=False)
            self.prepare_module_for_emulation(child.pe, all_entrypoints, entrypoints)

            self.command_line = child.cmdline

            self.curr_process = child
            self.curr_process.base = child.pe.base
            self.curr_thread = child.threads[0]

            self.om.objects.update({self.curr_thread.address: self.curr_thread})

            # PEB and TEB will be initialized when the next run happens

            self.start()

        return

    def _init_name(self, path, data=None):
        if not data:
            self.file_name = os.path.basename(path)
            self.mod_name = os.path.splitext(self.file_name)[0]
        else:
            mod_hash = hashlib.sha256()
            mod_hash.update(data)
            mod_hash = mod_hash.hexdigest()
            self.mod_name = mod_hash
            self.file_name = f"{self.mod_name}.exe"
        self.bin_base_name = os.path.basename(self.file_name)

    def emulate_module(self, path):
        """
        Load and emulate binary from the given path
        """
        mod = self.load_module(path)
        self.run_module(mod)

    def load_shellcode(self, path, arch, data=None):
        """
        Load position independent code (i.e. shellcode) to prepare for emulation
        """
        sc_hash = None
        self._init_name(path, data)
        if arch == "x86":
            arch = _arch.ARCH_X86
        elif arch in ("x64", "amd64"):
            arch = _arch.ARCH_AMD64

        self.arch = arch

        if data:
            sc_hash = hashlib.sha256()
            sc_hash.update(data)
            sc_hash = sc_hash.hexdigest()
            sc = data
        else:
            with open(path, "rb") as scpath:
                sc = scpath.read()

            sc_hash = hashlib.sha256()
            sc_hash.update(sc)
            sc_hash = sc_hash.hexdigest()

        if self.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif self.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError("Unsupported architecture: %s" % self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, self.arch)


        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        sc_tag = "emu.shellcode.%s" % (sc_hash)

        # Map the shellcode into memory
        sc_addr = self.mem_map(len(sc), tag=sc_tag)
        self.mem_write(sc_addr, sc)

        self.pic_buffers.append((path, sc_addr, len(sc)))

        sc_arch = "unknown"
        if arch == _arch.ARCH_AMD64:
            sc_arch = "x64"
        elif arch == _arch.ARCH_X86:
            sc_arch = "x86"

        if self.profiler:
            self.input = {
                "path": path,
                "sha256": sc_hash,
                "size": len(sc),
                "arch": sc_arch,
                "mem_tag": sc_tag,
                "emu_version": self.get_emu_version(),
                "os_run": self.get_osver_string(),
            }
            self.profiler.add_input_metadata(self.input)
            # Strings the initial buffer so that we can detect decoded strings later on
            if self.do_strings:
                self.profiler.strings["ansi"] = [
                    a[1] for a in self.get_ansi_strings(sc)
                ]
                self.profiler.strings["unicode"] = [
                    u[1] for u in self.get_unicode_strings(sc)
                ]
        self.setup()

        return sc_addr

    def run_shellcode(self, sc_addr, offset=0):
        """
        Begin emulating position independent code (i.e. shellcode) to prepare for emulation
        """

        target = None
        for sc_path, _sc_addr, size in self.pic_buffers:
            if _sc_addr == sc_addr:
                target = _sc_addr
                break

        if not target:
            raise Win32EmuError("Invalid shellcode address")

        stack_commit = 0x4000

        self.stack_base, stack_addr = self.alloc_stack(stack_commit)
        self.set_func_args(self.stack_base, self.return_hook, 0x7000)

        run = Run()
        run.type = "shellcode"
        run.start_addr = sc_addr + offset
        run.instr_cnt = 0
        args = [
            self.mem_map(1024, tag="emu.shellcode_arg_%d" % (i), base=0x41420000 + i)
            for i in range(4)
        ]
        run.args = args

        self.reg_write(_arch.X86_REG_ECX, 1024)

        self.add_run(run)

        # Create an empty process object for the shellcode if none is
        # supplied
        container = self.init_container_process()
        if container:
            self.processes.append(container)
            self.curr_process = container
        else:
            p = objman.Process(self)
            self.processes.append(p)
            self.curr_process = p

        mm = self.get_address_map(sc_addr)
        if mm:
            mm.set_process(self.curr_process)

        t = objman.Thread(self, stack_base=self.stack_base, stack_commit=stack_commit)
        self.om.objects.update({t.address: t})
        self.curr_process.threads.append(t)

        self.curr_thread = t

        peb = self.alloc_peb(self.curr_process)

        # Set the TEB
        self.init_teb(t, peb)

        self.start()

    def alloc_peb(self, proc):
        """
        Allocate memory for the Process Environment Block (PEB)
        """
        if proc.is_peb_active:
            return
        size = proc.get_peb_ldr().sizeof()
        res, size = self.get_valid_ranges(size)
        self.mem_reserve(size, base=res, tag="emu.struct.PEB_LDR_DATA")
        proc.set_peb_ldr_address(res)

        peb = proc.get_peb()
        proc.is_peb_active = True
        peb.object.ImageBaseAddress = proc.base
        peb.object.OSMajorVersion = self.osversion["major"]
        peb.object.OSMinorVersion = self.osversion["minor"]
        peb.object.OSBuildNumber = self.osversion["build"]
        peb.write_back()
        return peb

    def set_unhandled_exception_handler(self, handler_addr):
        """
        Establish a handler for unhandled exceptions that occur during emulation
        """
        self.unhandled_exception_filter = handler_addr

    def setup(self, stack_commit=0, first_time_setup=True):
        if first_time_setup:
            # Set the emulator to run in protected mode
            self.om = objman.ObjectManager(emu=self)

        arch = self.get_arch()
        self._setup_gdt(arch)
        self.setup_user_shared_data()
        self.set_ptr_size(self.arch)

        if arch == _arch.ARCH_X86:
            self.peb_addr = self.fs_addr + 0x30
        elif arch == _arch.ARCH_AMD64:
            self.peb_addr = self.gs_addr + 0x60

        self.api = WindowsApi(self)

        # Init symlinks
        for sl in self.symlinks:
            self.om.add_symlink(sl["name"], sl["target"])

        self.init_sys_modules(self.config_system_modules)

    def init_sys_modules(self, modules_config):
        """
        Get the system modules (e.g. drivers) that are loaded in the emulator
        """
        sys_mods = []

        for modconf in modules_config:

            mod = w32common.DecoyModule()
            mod.name = modconf["name"]
            base = modconf.get("base_addr")
            if isinstance(base, str):
                base = int(base, 16)

            mod.decoy_base = base
            mod.decoy_path = modconf["path"]

            drv = modconf.get("driver")
            if drv:
                devs = drv.get("devices")
                for dev in devs:
                    name = dev.get("name", "")
                    do = self.new_object(objman.Device)
                    do.name = name

            sys_mods.append(mod)
        return sys_mods

    def init_container_process(self):
        """
        Create a process to be used to host shellcode or DLLs
        """
        for p in self.config_processes:
            if p.get("is_main_exe"):
                name = p.get("name", "")
                emu_path = p.get("path", "")
                base = p.get("base_addr", 0)
                if isinstance(base, str):
                    base = int(base, 0)
                cmd_line = p.get("command_line", "")

                proc = objman.Process(
                    self, name=name, path=emu_path, base=base, cmdline=cmd_line
                )
                return proc
        return None

    def get_user_modules(self):
        """
        Get the user modules (e.g. dlls) that are loaded in the emulator
        """
        # Generate the decoy user module list
        if len(self.user_modules) < 2:
            # Check if we have a host process configured
            proc_mod = None
            for p in self.config_processes:

                if not self.user_modules and p.get("is_main_exe"):
                    proc_mod = p
                    break

            if proc_mod:
                all_user_mods = [proc_mod] + self.config_user_modules
                user_modules = self.init_user_modules(all_user_mods)
            else:
                user_modules = self.init_user_modules(self.config_user_modules)

            self.user_modules += user_modules
            # add sample to user modules list if it is a dll
            if self.modules and not self.modules[0][0].is_exe():
                self.user_modules.append(self.modules[0][0])

        return self.user_modules

    def exit_process(self):
        """
        An emulated binary is attempted to terminate its current process.
        Signal that the run has finished.
        """
        self.enable_code_hook()
        self.run_complete = True

    def _hook_mem_unmapped(self, emu, access, address, size, value, user_data):

        _access = self.emu_eng.mem_access.get(access)

        if _access == common.INVALID_MEM_READ:
            p = self.get_current_process()
            pld = p.get_peb_ldr()
            if address > pld.address and address < (pld.address + pld.sizeof()):
                self.mem_map_reserve(pld.address)
                user_mods = self.get_user_modules()
                self.init_peb(user_mods)
                return True
        return super(Win32Emulator, self)._hook_mem_unmapped(
            emu, access, address, size, value, user_data
        )

    def set_hooks(self):
        """Set the emulator callbacks"""

        super(Win32Emulator, self).set_hooks()

        if not self.builtin_hooks_set:
            self.add_mem_invalid_hook(cb=self._hook_mem_unmapped)
            self.add_interrupt_hook(cb=self._hook_interrupt)
            self.builtin_hooks_set = True

        self.set_mem_tracing_hooks()

    def stop(self):
        self.run_complete = True
        # self._unset_emu_hooks()
        # self.unset_hooks()
        super(Win32Emulator, self).stop()

    def on_emu_complete(self):
        """
        Called when all runs have completed emulation
        """
        if not self.emu_complete:
            self.emu_complete = True
            if self.do_strings and self.profiler:
                dec_ansi, dec_unicode = self.get_mem_strings()
                dec_ansi = [
                    a[1] for a in dec_ansi if a not in self.profiler.strings["ansi"]
                ]
                dec_unicode = [
                    u[1]
                    for u in dec_unicode
                    if u not in self.profiler.strings["unicode"]
                ]
                self.profiler.decoded_strings["ansi"] = dec_ansi
                self.profiler.decoded_strings["unicode"] = dec_unicode
        self.stop()

    def on_run_complete(self):
        """
        Clean up after a run completes. This function will pop the
        next run from the run queue and emulate it.
        """
        self.run_complete = True
        self.curr_run.ret_val = self.get_return_val()
        if self.profiler:
            self.profiler.log_dropped_files(self.curr_run, self.get_dropped_files())

        return self._exec_next_run()

    def heap_alloc(self, size, heap="None"):
        """
        Allocate a memory chunk and add it to the "heap"
        """
        addr = self.mem_map(size, base=None, tag="api.heap.%s" % (heap))
        self.heap_allocs.append((addr, size, heap))
        return addr
コード例 #5
0
    def load_module(self, path=None, data=None, first_time_setup=True):
        """
        Load a module into the emulator space from the specified path
        """
        self._init_name(path, data)
        pe = self.load_pe(path=path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise Win32EmuError("Unsupported architecture: %s", pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        # No need to initialize the engine and Capstone again
        if first_time_setup:
            self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

            if not self.disasm_eng:
                self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        self.api = WindowsApi(self)

        cd = self.get_cd()
        if not cd.endswith("\\"):
            cd += "\\"
        emu_path = cd + self.file_name

        if not data:
            with open(path, "rb") as f:
                data = f.read()
        self.fileman.add_existing_file(emu_path, data)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            self.profiler.strings["ansi"] = [a[1] for a in self.get_ansi_strings(data)]
            self.profiler.strings["unicode"] = [
                u[1] for u in self.get_unicode_strings(data)
            ]
        # Set the emulated path
        emu_path = ""
        self.cd = self.get_cd()
        if self.cd:
            if not self.cd.endswith('\\'):
                self.cd += '\\'
            emu_path = self.cd + os.path.basename(self.file_name)

        pe.set_emu_path(emu_path)

        # There's a bit of a problem here, if we cannot reserve memory
        # at the PE's desired base address, and the relocation table
        # is not present, we can't rebase it. So this is gonna have to
        # be a bit of a hack for binaries without a relocation table.
        # This logic is really only for child processes, since we're pretty
        # much guarenteed memory at the base address of the main module.
        #   1. If the memory at the child's desired load address is already
        #      being used, remap it somewhere else. I'm pretty sure that
        #      the already-used memory will always be for a module,
        #      since desired load addresses don't really vary across PEs
        #   2. Fix up any modules that speakeasy has open for the parent
        #      to reflect where it was remapped
        #   3. Try and grab memory at the child's desired base address,
        #      if that isn't still isn't possible, we're out of luck
        #
        # But if the relocation table is present, we can rebase it,
        # so we do that instead of the above hack.
        imgbase = pe.OPTIONAL_HEADER.ImageBase
        ranges = self.get_valid_ranges(pe.image_size, addr=imgbase)
        base, size = ranges

        if base != imgbase:
            if pe.has_reloc_table():
                pe.rebase(base)
            else:
                parent_map = self.get_address_map(imgbase)

                # Already being used by the parent, so let's remap the parent
                # Do get_valid_ranges on the parent map size so we get a
                # suitable region for it
                new_parent_mem, unused = self.get_valid_ranges(parent_map.size)
                new_parent_mem = self.mem_remap(imgbase, new_parent_mem)

                # Failed
                if new_parent_mem == -1:
                    # XXX what to do here
                    pass

                # Update parent module pointer
                for pe_, ranges_, emu_path_ in self.modules:
                    base_, size_ = ranges_

                    if base_ == imgbase:
                        self.modules.remove((pe_, ranges_, emu_path_))
                        self.modules.append((pe_, (new_parent_mem, size_), emu_path_))
                        break

                # Alright, let's try to grab that memory for the child again
                ranges = self.get_valid_ranges(pe.image_size, addr=imgbase)
                base, size = ranges

                if base != imgbase:
                    # Out of luck
                    # XXX what to do here
                    pass

        self.mem_map(pe.image_size, base=base,
                tag='emu.module.%s' % (self.mod_name))

        self.modules.append((pe, ranges, emu_path))
        self.mem_write(pe.base, pe.mapped_image)

        self.setup(first_time_setup=first_time_setup)

        if not self.stack_base:
            self.stack_base, stack_addr = self.alloc_stack(0x12000)
        self.set_func_args(self.stack_base, self.return_hook)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(addr, data_ptr.to_bytes(self.get_ptr_size(), "little"))
        return pe
コード例 #6
0
class WinKernelEmulator(WindowsEmulator, IoManager):
    """
    Class used to emulate Windows drivers
    """
    def __init__(self, config, debug=False, logger=None, exit_event=None):
        super(WinKernelEmulator, self).__init__(config,
                                                debug=debug,
                                                logger=logger,
                                                exit_event=exit_event)

        self.disasm_eng = None
        self.curr_mod = None
        self.debug = debug
        self.drivers = []
        self.pool_allocs = []
        self.all_entrypoints = False
        self.kernel_mode = True
        self.irql = ddk.PASSIVE_LEVEL
        self.delayed_runs = []
        self.system_time = SYSTEM_TIME_START
        self.ktypes = ntos

    def get_system_time(self):
        return self.system_time

    def get_system_process(self):
        """
        Get the process object for the system process (PID 4)
        """
        for proc in self.processes:
            if proc.get_pid() == 4:
                return proc

    def get_current_irql(self):
        """
        Get the current interrupt request level
        """
        return self.irql

    def set_current_irql(self, irql):
        """
        Set the current interrupt request level
        """
        self.irql = irql

    def create_driver_object(self, name=None, pe=None):
        """
        Create a driver object for the driver that is going to be emulated
        """

        drv = objman.Driver(emu=self)

        # If no PE was supplied, assign a dummy driver
        if not pe:
            # Get the path for the dummy driver
            default_path = self.get_native_module_path('default_sys')

            pe = w32common.DecoyModule(path=default_path)
            if name:
                bn = ntpath.basename(name)
            else:
                bn = 'none'
            pe.decoy_path = ('%sdrivers\\%s.sys' %
                             (self.get_system_root(), os.path.basename(bn)))
            pe.decoy_base = pe.get_base()

        else:
            if not name:
                bn = pe.path
                path = '%sdrivers\\%s' % (self.get_system_root(),
                                          os.path.basename(bn))
                pe.decoy_path = path
                pe.decoy_base = pe.base

        drv.init_driver_object(name, pe, is_decoy=False)

        self.add_object(drv)

        self.drivers.append(drv)
        return drv

    def load_module(self, path=None, data=None):
        """
        Load the kernel module to be emulated
        """
        pe = self.load_pe(path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise KernelEmuError('Unsupported architecture: %s', pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        self.api = WindowsApi(self)

        self.om = objman.ObjectManager(emu=self)

        if not data:
            file_name = os.path.basename(path)
            mod_name = os.path.splitext(file_name)[0]
        else:
            drv_hash = hashlib.sha256()
            drv_hash.update(data)
            drv_hash = drv_hash.hexdigest()
            mod_name = drv_hash
            file_name = '%s.sys' % (mod_name)
        emu_path = '%sdrivers\\%s' % (self.get_system_root(), file_name)
        pe.emu_path = emu_path
        self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
        self.mem_write(pe.base, pe.mapped_image)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            astrs = self.get_ansi_strings(pe.mapped_image)
            wstrs = self.get_unicode_strings(pe.mapped_image)

            for s in astrs:
                if s not in self.profiler.strings['ansi']:
                    self.profiler.strings['ansi'].append(s)

            for s in wstrs:
                if s not in self.profiler.strings['unicode']:
                    self.profiler.strings['unicode'].append(s)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(
                    addr, data_ptr.to_bytes(self.get_ptr_size(), 'little'))

        # Set the emulator to run in protected mode
        self._setup_gdt(self.get_arch())

        self.setup_kernel_mode()

        self.setup_user_shared_data()

        if not self.stack_base:
            self.stack_base, stack_ptr = self.alloc_stack(pe.stack_commit)

        return pe

    def pool_alloc(self, pooltype, size, tag='None'):
        """
        Allocate memory in the emulated "pool"
        """

        if pooltype == ddk.POOL_TYPE.NonPagedPool:
            pt = 'NonPagedPool'
        elif pooltype == ddk.POOL_TYPE.PagedPool:
            pt = 'PagedPool'
        elif pooltype == ddk.POOL_TYPE.NonPagedPoolNx:
            pt = 'NonPagedPoolNx'
        else:
            pt = 'unk'

        system_proc = self.get_system_process()

        addr = self.mem_map(size,
                            base=None,
                            tag='api.pool.%s.%s' % (pt, tag),
                            process=system_proc)
        self.pool_allocs.append((addr, pooltype, size, tag))
        return addr

    def init_sys_modules(self, modules_config):
        """
        Initialize kernel mode modules that may be accessed by emulated modules
        """
        sysmods = super(WinKernelEmulator,
                        self).init_sys_modules(modules_config)

        # Initalize any DRIVER_OBJECTs needed by the module
        for mc in modules_config:
            drv = mc.get('driver')
            if drv:
                mod = [m for m in sysmods if m.name == mc.get('name')]
                if not mod:
                    continue

                mod = mod[0]

                driver = self.create_driver_object(name=drv.get('name'),
                                                   pe=mod)
                devs = drv.get('devices')
                for dev in devs:
                    name = dev.get('name', '')
                    ext_size = dev.get('ext_size', 0)
                    devtype = dev.get('devtype', 0)
                    chars = dev.get('chars', 0)
                    self.create_device_object(name, driver, ext_size, devtype,
                                              chars)

        for m in self.modules:
            mod = m[0]
            sysmods.append(mod)

        return sysmods

    def get_processes(self):
        """
        Get processes that exist in the emulation space
        """
        if not self.processes:
            self.init_processes(self.config_processes)
        return self.processes

    def init_processes(self, processes):
        for proc in processes:
            p = objman.Process(self)
            self.add_object(p)
            p.name = proc.get('name', '')
            p.pid = proc.get('pid')

            if p.name.lower() == 'system':
                p.pid = 4
                p.path = 'System'

            if not p.pid:
                p.pid = self.om.new_id()
            base = proc.get('base_addr')

            if isinstance(base, str):
                base = int(base, 16)
            p.base = base
            if not p.path:
                p.path = proc.get('path')
            p.image = ntpath.basename(p.path)

            # Create an initial thread for each process
            t = objman.Thread(self)
            self.add_object(t)
            t.process = p
            p.threads.append(t)
            self.processes.append(p)

        # The SYSTEM process should be the starting context
        sp = [p for p in self.processes if p.name.lower() == 'system']
        if sp:
            sp = sp[0]
            self.set_current_process(sp)

    def alloc_peb(self, proc):
        """
        Allocate PEB and related substructures for a given process
        """
        ldr = proc.get_peb_ldr()
        if not ldr.address:
            size = ldr.sizeof()
            res, size = self.get_valid_ranges(size)
            base = self.mem_map(size, base=res, tag='emu.struct.PEB_LDR_DATA')
            proc.set_peb_ldr_address(base)
        return proc.get_peb()

    def get_process_peb(self, process):
        """
        Retrieve the PEB for the supplied process
        """
        self.alloc_peb(process)
        user_mods = self.get_user_modules()
        process.init_peb(user_mods)
        return process.peb

    def set_current_process(self, process):
        """
        Set the current process context
        """
        self.curr_process = process

    def get_current_process(self):
        """
        Get the current process context
        """
        if not self.processes:
            self.processes = self.get_processes()
        return self.curr_process

    def run_module(self, module, all_entrypoints=False):
        """
        Begin emulation fo a previously loaded kernel module
        """

        self.all_entrypoints = all_entrypoints

        # Create the service key for the driver
        drv = self.create_driver_object(pe=module)
        svc_key = self.regman.create_key(drv.get_reg_path())
        # Create the values for the service key
        svc_key.create_value('ImagePath', regdefs.REG_EXPAND_SZ,
                             module.get_emu_path())
        svc_key.create_value('Type', regdefs.REG_DWORD,
                             0x1)  # SERVICE_KERNEL_DRIVER
        svc_key.create_value('Start', regdefs.REG_DWORD,
                             0x3)  # SERVICE_DEMAND_START
        svc_key.create_value('ErrorControl', regdefs.REG_DWORD,
                             0x1)  # SERVICE_ERROR_NORMAL

        # Create the parameters subkey
        self.regman.create_key(drv.get_reg_path() + '\\Parameters')

        if module.ep > 0:

            ep = module.base + module.ep

            run = Run()
            run.type = EP_DRIVER_ENTRY
            run.start_addr = ep
            run.instr_cnt = 0
            run.args = [drv.address, drv.reg_path_ptr]
            self.add_run(run)

        if self.all_entrypoints:
            # Only emulate a subset of all the exported functions
            # There are some modules (such as the windows kernel) with thousands of exports
            exports = [
                k for k in module.get_exports()[:MAX_EXPORTS_TO_EMULATE]
            ]

            if exports:
                args = [
                    self.mem_map(8, tag='emu.export_arg_%d' % (i))
                    for i in range(4)
                ]
                for exp in exports:
                    run = Run()
                    if exp.name:
                        fn = exp.name
                    else:
                        fn = 'no_name'
                    run.type = 'export.%s' % (fn)
                    run.start_addr = exp.address
                    # Here we set dummy args to pass into the export function
                    run.args = args
                    # Store these runs and only queue them before the unload routine
                    # this is because some exports may not be ready to be called yet
                    self.delayed_runs.append(run)

        self.start()

    def create_device_object(self,
                             name='',
                             drv=0,
                             ext_size=0,
                             devtype=0,
                             chars=0,
                             tag=''):
        """
        Create a device object to use for kernel emulation
        """
        dev = objman.Device(self)

        alloc_size = ext_size + dev.sizeof()

        if not name:
            devname = r'\Device\%x' % (dev.get_id())
            if not tag:
                tag = 'emu.device.autogen'
            name = '%s.%s' % (tag, devname)
        else:
            devname = name
            if not tag:
                tag = 'emu.object'
            name = '%s.%s' % (tag, devname)

        dev.address = self.mem_map(alloc_size, tag=name)
        dev.name = devname

        # Create a FILE_OBJECT for the device
        fobj = objman.FileObject(self)
        dev.object.DeviceObject = dev.address
        dev.file_object = fobj

        self.add_object(dev)

        if drv:
            drv.read_back()
            dev.object.DriverObject = drv.address
            dev.driver = drv

            if not drv.object.DeviceObject:
                drv.object.DeviceObject = dev.address
                drv.write_back()
            else:
                next_dev = self.get_object_from_addr(drv.object.DeviceObject)
                while next_dev:
                    if next_dev.object.NextDevice:
                        next_dev = \
                            self.get_object_from_addr(drv.object.NextDevice)
                    else:
                        # This is the last in the list, add our new device
                        next_dev.object.NextDevice = dev.address
                        next_dev.write_back()
                        break

        drv.devices.append(dev)

        dev.object.Characteristics = chars
        dev.object.DeviceType = devtype
        if ext_size > 0:
            dev.object.DeviceExtension = dev.address + dev.sizeof()

        dev.write_back()

        return dev

    def add_symlink(self, symlink, devname):
        """
        Add a symlink for a device
        """
        self.om.add_symlink(symlink, devname)

    def _call_driver_dispatch(self, func, dev_addr, irp_addr):
        """
        Call a WDM driver dispatch function with a supplied IRP
        """
        stk_ptr = self.get_stack_ptr()
        self.set_func_args(stk_ptr, self.return_hook, dev_addr, irp_addr)
        self.set_pc(func)

    def new_irp(self):
        """
        Create a new IRP
        """
        return objman.Irp(emu=self)

    def irp_mj_create(self, func, dev):

        # Generate an IRP for the create request
        irp = self.new_irp()

        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def irp_mj_close(self, func, dev):
        irp = self.new_irp()
        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def irp_mj_dev_io(self, func, dev):
        irp = self.new_irp()

        ios = irp.get_curr_stack_loc()
        ios.object.MajorFunction = ddk.IRP_MJ_DEVICE_CONTROL
        ios.object.Parameters.DeviceIoControl.IoControlCode = 0x5d5d5d5d

        ios.write_back()
        irp.write_back()

        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def irp_mj_read(self, func, dev):
        irp = self.new_irp()

        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def irp_mj_write(self, func, dev):
        irp = self.new_irp()

        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def irp_mj_cleanup(self, func, dev):
        irp = self.new_irp()

        self._call_driver_dispatch(func, dev.address, irp.address)
        return irp

    def driver_unload(self, drv):
        """
        Call the unload routine for a driver
        """

        if not drv.on_unload or drv.unload_called:
            self.on_emu_complete()
            return

        stk_ptr = self.get_stack_ptr()
        self.set_func_args(stk_ptr, self.exit_hook, drv.address)
        self.set_pc(drv.on_unload)

        run = Run()
        run.type = EP_DRIVER_UNLOAD
        run.start_addr = drv.on_unload
        run.instr_cnt = 0
        run.args = (drv.address, )
        run.ret_val = None
        self.add_run(run)
        drv.unload_called = True

    def next_driver_func(self, drv):

        func_addr = None
        func_handler = None

        if self.curr_run.type is not None:
            self.curr_run.ret_val = self.get_return_val()

        # Check if theres anything in the run queue
        if len(self.run_queue):
            return

        if not self.all_entrypoints:
            return

        # TODO right now just use the first device object that was created
        dev = None
        if len(drv.devices):
            dev = drv.devices[0]

        # Run any remaining IRP handlers
        for hdlr, i in ((self.irp_mj_create, ddk.IRP_MJ_CREATE),
                        (self.irp_mj_dev_io, ddk.IRP_MJ_DEVICE_CONTROL),
                        (self.irp_mj_read, ddk.IRP_MJ_READ),
                        (self.irp_mj_write, ddk.IRP_MJ_WRITE),
                        (self.irp_mj_close, ddk.IRP_MJ_CLOSE),
                        (self.irp_mj_cleanup, ddk.IRP_MJ_CLEANUP)):

            # Did we run this mj func yet?
            if i not in [r.type for r in self.runs]:
                func_handler = hdlr
                func_addr = int(drv.mj_funcs[i])

                if not func_addr:
                    continue
                break

        if len(self.delayed_runs):
            [self.add_run(r) for r in self.delayed_runs]
            self.delayed_runs = []

        if not func_addr or not dev:
            # We are done here, call the unload routine
            self.driver_unload(drv)
            return

        irp = func_handler(func_addr, dev)

        run = Run()
        run.type = i
        run.start_addr = func_addr
        run.instr_cnt = 0
        run.args = (dev.address, irp.address)
        self.add_run(run)

    def on_run_complete(self):

        self.curr_run.ret_val = self.get_return_val()

        for drv in self.drivers:
            drv.read_back()
            if drv.pe == self.curr_mod:
                self.next_driver_func(drv)

        # Dispatch the next run
        return self._exec_next_run()

    def on_emu_complete(self):
        if not self.emu_complete:
            self.emu_complete = True

            if self.do_strings and self.profiler:
                dec_ansi, dec_unicode = self.get_mem_strings()
                dec_ansi = [
                    a for a in dec_ansi
                    if a not in self.profiler.strings['ansi']
                ]
                dec_unicode = [
                    u for u in dec_unicode
                    if u not in self.profiler.strings['unicode']
                ]
                self.profiler.decoded_strings['ansi'] = dec_ansi
                self.profiler.decoded_strings['unicode'] = dec_unicode
        self.stop()

    def set_hooks(self):
        """Set the emulator callbacks"""

        super(WinKernelEmulator, self).set_hooks()

        if not self.builtin_hooks_set:
            self.add_mem_invalid_hook(cb=self._hook_mem_unmapped)
            self.add_interrupt_hook(cb=self._hook_interrupt)
            self.builtin_hooks_set = True

        self.set_mem_tracing_hooks()

    def get_kernel_base(self):
        """
        Get the base address of the kernel image (ntoskrnl.exe)
        """
        # Get kernel base address
        kern = self.get_kernel_mod()
        return kern.get_base()

    def get_kernel_mod(self):
        """
        Get the kernel image module
        """
        sys_mods = self.get_sys_modules()
        for mod in sys_mods:
            if mod.name.lower() == 'ntoskrnl':
                return mod
        raise KernelEmuError('Failed to get kernel base')

    def _set_entry_point_names(self):
        run_types = {
            ddk.IRP_MJ_CREATE: 'irp_mj_create',
            ddk.IRP_MJ_DEVICE_CONTROL: 'irp_mj_device_control',
            ddk.IRP_MJ_READ: 'irp_mj_read',
            ddk.IRP_MJ_WRITE: 'irp_mj_write',
            ddk.IRP_MJ_CLOSE: 'irp_mj_close',
            ddk.IRP_MJ_CLEANUP: 'irp_mj_cleanup',
            EP_DRIVER_ENTRY: 'entry_point',
            EP_DRIVER_UNLOAD: 'driver_unload'
        }
        for r in self.runs:
            if not r.type or run_types.get(r.type):
                r.type = run_types.get(r.type, 'unk')

    def get_report(self):
        """ Retrieve the execution profile for the emulator """
        self._set_entry_point_names()
        return super(WinKernelEmulator, self).get_report()

    def get_json_report(self):
        self._set_entry_point_names()
        return super(WinKernelEmulator, self).get_json_report()

    def get_ssdt_ptr(self):
        return self.ssdt_ptr

    def setup_kernel_mode(self):

        idt = objman.IDT(self)
        idt.init_descriptors()

        # selector, base, limit, flags
        self.reg_write(_arch.X86_REG_IDTR,
                       (0, idt.object.Descriptors, idt.object.Limit, 0))

        # Setup the SSDT
        ssdt = self.ktypes.SSDT(self.get_ptr_size())
        size = self.get_ptr_size() * 256

        self.ssdt_ptr = self.mem_map(size, base=None, tag='api.struct.SSDT')
        ssdt.NumberOfServices = 256
        ssdt.pServiceTable = self.ssdt_ptr + self.sizeof(ssdt)
        self.mem_write(self.ssdt_ptr, self.get_bytes(ssdt))

        self.get_sys_modules()

        self.setup_msrs()

        for sl in self.symlinks:
            self.om.add_symlink(sl['name'], sl['target'])

    def setup_msrs(self):
        """
        Setup machine specific registers for kernel emulation
        """
        # Initalize the LSTAR on amd64 with the address of KiSystemCall64
        km = self.get_kernel_mod()

        if self.get_arch() == _arch.ARCH_AMD64:
            ksc64_off = km.find_bytes(b'\x00' * 100, 0)
            if ksc64_off != -1:
                self.map_decoy(km)
                sdt = km.get_export_by_name('KeServiceDescriptorTable')
                if sdt:
                    kbase = km.get_base()
                    sdt_addr = kbase + sdt
                    # Set the symbols up
                    for i in range(0x20):
                        self.symbols.update({
                            sdt_addr + i:
                            (km.get_base_name(), 'KeServiceDescriptorTable')
                        })
                    self.symbols.update({
                        sdt_addr: (km.get_base_name(),
                                   'KeServiceDescriptorTable.pServiceTable')
                    })
                    self.symbols.update({
                        sdt_addr + 0x10:
                        (km.get_base_name(),
                         'KeServiceDescriptorTable.NumberOfServices')
                    })
                    ksc64_off += 5

                    ksc64_addr = kbase + ksc64_off
                    self.symbols.update(
                        {ksc64_addr: (km.get_base_name(), 'KiSystemCall64')})

                    # Write the address of our fake KiSystemCall64 to the LSTAR register
                    self.reg_write(_arch.X86_REG_MSR,
                                   (_arch.LSTAR, ksc64_addr))
                    # ssdt load:
                    # KeServiceDescriptorTable
                    sdt_offset = (sdt_addr - ksc64_addr) - 7
                    data = b'\x90\x90\xc3' + sdt_offset.to_bytes(4, 'little')
                    self.mem_write(kbase + ksc64_off, data)
                    ksc64_off += 7
                    # shadow_ssdt_load:
                    # KeServiceDescriptorTableShadow
                    sdt_offset = sdt_addr - (kbase + ksc64_off)
                    data = b'\x90\x90\xc3' + sdt_offset.to_bytes(4, 'little')
                    km.set_bytes(ksc64_off, data)
                    ksc64_off += 7
                    data = b'\x90\x90\x90\x90\x90\x90\xc3'
                    km.set_bytes(ksc64_off, data)
コード例 #7
0
    def load_module(self, path=None, data=None):
        """
        Load the kernel module to be emulated
        """
        pe = self.load_pe(path, data=data, imp_id=w32common.IMPORT_HOOK_ADDR)

        if pe.arch == _arch.ARCH_X86:
            disasm_mode = cs.CS_MODE_32
        elif pe.arch == _arch.ARCH_AMD64:
            disasm_mode = cs.CS_MODE_64
        else:
            raise KernelEmuError('Unsupported architecture: %s', pe.arch)

        if not self.arch:
            self.arch = pe.arch
            self.set_ptr_size(self.arch)

        self.emu_eng.init_engine(_arch.ARCH_X86, pe.arch)

        if not self.disasm_eng:
            self.disasm_eng = cs.Cs(cs.CS_ARCH_X86, disasm_mode)

        self.api = WindowsApi(self)

        self.om = objman.ObjectManager(emu=self)

        if not data:
            file_name = os.path.basename(path)
            mod_name = os.path.splitext(file_name)[0]
        else:
            drv_hash = hashlib.sha256()
            drv_hash.update(data)
            drv_hash = drv_hash.hexdigest()
            mod_name = drv_hash
            file_name = '%s.sys' % (mod_name)
        emu_path = '%sdrivers\\%s' % (self.get_system_root(), file_name)
        pe.emu_path = emu_path
        self.map_pe(pe, mod_name=mod_name, emu_path=emu_path)
        self.mem_write(pe.base, pe.mapped_image)

        # Strings the initial buffer so that we can detect decoded strings later on
        if self.profiler and self.do_strings:
            astrs = self.get_ansi_strings(pe.mapped_image)
            wstrs = self.get_unicode_strings(pe.mapped_image)

            for s in astrs:
                if s not in self.profiler.strings['ansi']:
                    self.profiler.strings['ansi'].append(s)

            for s in wstrs:
                if s not in self.profiler.strings['unicode']:
                    self.profiler.strings['unicode'].append(s)

        # Init imported data
        for addr, imp in pe.imports.items():
            mn, fn = imp
            mod, eh = self.api.get_data_export_handler(mn, fn)
            if eh:
                data_ptr = self.handle_import_data(mn, fn)
                sym = "%s.%s" % (mn, fn)
                self.global_data.update({addr: [sym, data_ptr]})
                self.mem_write(
                    addr, data_ptr.to_bytes(self.get_ptr_size(), 'little'))

        # Set the emulator to run in protected mode
        self._setup_gdt(self.get_arch())

        self.setup_kernel_mode()

        self.setup_user_shared_data()

        if not self.stack_base:
            self.stack_base, stack_ptr = self.alloc_stack(pe.stack_commit)

        return pe