Exemplo n.º 1
0
    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)
Exemplo n.º 2
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 get_com_interface(self, name):
        """
        Retreive a COM interface by name
        """
        ci = self.com.get_interface(name, self.get_ptr_size())
        if not ci:
            raise Win32EmuError('Invalid COM interface: %s' % (name))

        com_ptr = self.mem_map(self.sizeof(ci.iface),
                               tag='emu.COM.%s' % (name))
        ci.address = com_ptr
        return ci

    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 is 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
        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