text: # Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
  
  import re
  import json
  import fnmatch
  import traceback
  from typing import List, Tuple, Dict
  
  import speakeasy.common as common
  import speakeasy.winenv.arch as e_arch
  from speakeasy.engines import unicorn_eng
  from speakeasy.profiler import Profiler
  import speakeasy.version as version
  from speakeasy.memmgr import MemoryManager
  
  from speakeasy.errors import EmuException
  
  EMU_ENGINES = (
                ('unicorn', unicorn_eng.EmuEngine),
                )
  
  WILDCARD_FLAG = bool
  API_LEVEL = Tuple[Dict[str, List[common.ApiHook]], WILDCARD_FLAG]
  MODULE_LEVEL = Tuple[Dict[str, API_LEVEL], WILDCARD_FLAG]
  
  
  # Generic emulator class for binary code
  class BinaryEmulator(MemoryManager):
      """
      Base class for emulating binaries
      """
      def __init__(self, config, logger=None):
  
          super(BinaryEmulator, self).__init__()
  
          self.stack_base = 0
          self.page_size = None
          self.inst_count = 0
          self.curr_instr_size = 0
          self.disasm_eng = None
          self.builtin_hooks_set = False
          self.emu_eng = None
          self.maps = []
          self.config = config
          self.hooks = {}
  
          self.profiler = Profiler()
  
          self.runtime = 0
  
          self.emu_version = self.get_emu_version()
          self.logger = logger
  
      def log_info(self, msg):
          if self.logger:
              self.logger.info(msg)
  
      def log_error(self, msg):
          if self.logger:
              self.logger.error(msg)
  
      def log_exception(self, msg):
          if self.logger:
              self.logger.exception(msg)
  
      def get_profiler(self):
          """
          Get the current event profiler object (if any)
          """
          return self.profiler
  
      def get_report(self):
          """
          Get the emulation report for all runs that were executed
          """
          if self.profiler:
              return self.profiler.get_report()
  
      def get_json_report(self):
          """
          Get the emulation report for all runs that were executed formatted as a JSON string
          """
          if self.profiler:
              return self.profiler.get_json_report()
  
      def _parse_config(self, config):
          """
          Parse the config to be used for emulation
          """
          if isinstance(config, str):
              config = json.loads(config)
          self.config = config
  
          _eng = config.get('emu_engine', '')
          for name, eng in EMU_ENGINES:
              if name.lower() == _eng.lower():
                  self.emu_eng = eng()
          if not self.emu_eng:
              raise EmuException('Unsupported emulation engine: %s' % (_eng))
  
          self.osversion = config.get('os_ver', {})
          self.env = config.get('env', {})
          self.user_config = config.get('user', {})
          self.domain = config.get('domain')
          self.hostname = config.get('hostname')
          self.symlinks = config.get('symlinks', [])
          self.config_modules = config.get('modules', {})
          self.config_system_modules = self.config_modules.get('system_modules', [])
          self.config_processes = config.get('processes', [])
          self.config_user_modules = self.config_modules.get('user_modules', [])
  
          self.config_analysis = config.get('analysis', {})
          self.max_instructions = config.get('max_instructions', -1)
          self.timeout = config.get('timeout', 0)
          self.max_api_count = config.get('max_api_count', 5000)
          self.exceptions = config.get('exceptions', {})
          self.drive_config = config.get('drives', [])
          self.filesystem_config = config.get('filesystem', {})
          self.keep_memory_on_free = config.get('keep_memory_on_free', False)
  
          self.network_config = config.get('network', {})
          self.command_line = config.get('command_line', '')
  
      def get_emu_version(self):
          """
          Get the version of the emulator
          """
          return version.__version__
  
      def get_os_version(self):
          """
          Get version of the OS being emulated
          """
          return self.osversion
  
      def get_osver_string(self):
          """
          Get the human readable OS version string
          """
          osver = self.get_os_version()
          if osver:
              os_name = osver.get('name', '')
              major = osver.get('major')
              minor = osver.get('minor')
              if major is not None and minor is not None:
                  verstr = '%s.%d_%d' % (os_name, major, minor)
                  return verstr
  
      def get_domain(self):
          """
          Get domain of the machine being emulated
          """
          return self.domain
  
      def get_hostname(self):
          """
          Get hostname of the machine being emulated
          """
          return self.hostname
  
      def get_user(self):
          """
          Get the current emulated user's name
          """
          return self.user_config
  
      def sizeof(self, obj):
          """
          Get the size (in the emulation space) of the supplied object
          """
          return obj.sizeof()
  
      def get_bytes(self, obj):
          """
          Get the bytes represented in the emulation space of the supplied object
          """
          return obj.get_bytes()
  
      def stop(self):
          """
          Stop emulation completely
          """
          self.emu_eng.stop()
          if self.profiler:
              self.profiler.stop_run_clock()
  
      def start(self, addr, size):
          """
          Begin emulation
          """
          self.set_hooks()
          self._set_emu_hooks()
          if self.profiler:
              self.profiler.set_start_time()
          try:
              self.emu_eng.start(addr, timeout=self.timeout, count=self.max_instructions)
          except Exception:
              if self.profiler:
                  self.profiler.log_error(traceback.format_exc())
              self.on_emu_complete()
  
      def get_network_config(self):
          """
          Get the network settings specified in the network section of the config file
          """
          return self.network_config
  
      def get_filesystem_config(self):
          """
          Get the filesystem settings specified in the filesystem section of the config file
          """
          return self.filesystem_config
  
      def get_drive_config(self):
          """
          Get the drive settings specified in the drives section of the config file
          """
          return self.drive_config
  
      def reg_write(self, reg, val):
          """
          Write a value to an emulated cpu register
          """
          if isinstance(reg, str):
              _reg = e_arch.REG_LOOKUP.get(reg.lower())
              if not _reg:
                  raise EmuException('Invalid register access %s' % (reg))
              reg = _reg
  
          self.emu_eng.reg_write(reg, val)
  
      def reg_read(self, reg):
          """
          Read a value from an emulated cpu register
          """
          if isinstance(reg, str):
              _reg = e_arch.REG_LOOKUP.get(reg.lower())
              if not _reg:
                  raise EmuException('Invalid register access %s' % (reg))
              reg = _reg
  
          return self.emu_eng.reg_read(reg)
  
      def set_hooks(self):
          """
          Set instruction level hooks
          """
          for ht in (common.HOOK_CODE, common.HOOK_MEM_READ, common.HOOK_MEM_WRITE,
                     common.HOOK_MEM_INVALID, common.HOOK_INTERRUPT):
              for hook in self.hooks.get(ht, []):
                  if not hook.added:
                      hook.add()
  
      def _cs_disasm(self, mem, addr, fast=True):
          """
          Disassemble bytes using capstone
          """
          try:
              if fast:
                  tu = [i for i in self.disasm_eng.disasm_lite(bytes(mem), addr)]
                  address, size, mnem, oper = tu[0]
              else:
                  return [i for i in self.disasm_eng.disasm(bytes(mem), addr)]
          except IndexError:
              raise EmuException("Failed to disasm at address: 0x%x" % (addr))
  
          op = '%s %s' % (mnem, oper)
          return ((mnem, oper, op))
  
      def disasm(self, mem, addr, fast=True):
          """
          Disassemble bytes at a specified address
          """
          return self._cs_disasm(mem, addr, fast=fast)
  
      def get_register_state(self):
          """
          Get the current state of registers from the emulator
          """
          regs = {}
          if e_arch.ARCH_X86 == self.get_arch():
              for name, reg in (('esp', e_arch.X86_REG_ESP),
                                ('ebp', e_arch.X86_REG_EBP),
                                ('eip', e_arch.X86_REG_EIP),
                                ('esi', e_arch.X86_REG_ESI),
                                ('edi', e_arch.X86_REG_EDI),
                                ('eax', e_arch.X86_REG_EAX),
                                ('ebx', e_arch.X86_REG_EBX),
                                ('ecx', e_arch.X86_REG_ECX),
                                ('edx', e_arch.X86_REG_EDX)):
                  val = self.reg_read(reg)
                  regs[name] = "{0:#0{1}x}".format(val, 2 + (self.get_ptr_size() * 2))
          elif e_arch.ARCH_AMD64 == self.get_arch():
              for name, reg in (('rsp', e_arch.AMD64_REG_RSP),
                                ('rbp', e_arch.AMD64_REG_RBP),
                                ('rip', e_arch.AMD64_REG_RIP),
                                ('rsi', e_arch.AMD64_REG_RSI),
                                ('rdi', e_arch.AMD64_REG_RDI),
                                ('rax', e_arch.AMD64_REG_RAX),
                                ('rbx', e_arch.AMD64_REG_RBX),
                                ('rcx', e_arch.AMD64_REG_RCX),
                                ('rdx', e_arch.AMD64_REG_RDX),
                                ('r8',  e_arch.AMD64_REG_R8),
                                ('r9',  e_arch.AMD64_REG_R9),
                                ('r10', e_arch.AMD64_REG_R10),
                                ('r11', e_arch.AMD64_REG_R11),
                                ('r12', e_arch.AMD64_REG_R12),
                                ('r13', e_arch.AMD64_REG_R13),
                                ('r14', e_arch.AMD64_REG_R14),
                                ('r15', e_arch.AMD64_REG_R15)):
                  val = self.reg_read(reg)
                  regs[name] = "{0:#0{1}x}".format(val, 2 + (self.get_ptr_size() * 2))
          return regs
  
      def get_disasm(self, addr, size, fast=True):
          """
          Get the disassembly from an address
          """
          return self.disasm(self.mem_read(addr, size), addr, fast)
  
      def set_func_args(self, stack_addr, ret_addr, *args, home_space=True):
          """
          Set the arguments before an emulated function call. This is how we pass
          arguments to a function when calling it through the emulator.
          """
          curr_sp = stack_addr - self.ptr_size
          nargs = len(args)
  
          if self.get_arch() == e_arch.ARCH_X86:
              sp = e_arch.X86_REG_ESP
          elif self.get_arch() == e_arch.ARCH_AMD64:
              sp = e_arch.AMD64_REG_RSP
              i = 0
              for i, r in enumerate((e_arch.AMD64_REG_RCX, e_arch.AMD64_REG_RDX,
                                     e_arch.AMD64_REG_R8, e_arch.AMD64_REG_R9)):
                  if nargs == 0:
                      break
                  self.reg_write(r, args[i])
                  nargs -= 1
              # Set the stack home space
              if home_space:
                  curr_sp -= 0x20
              self.reg_write(sp, curr_sp)
          else:
              raise EmuException('Unsupported architecture')
  
          if nargs > 0:
              for arg in args[-nargs:][::-1]:
                  a = arg.to_bytes(self.ptr_size, byteorder='little')
  
                  self.mem_write(curr_sp, a)
                  self.reg_write(sp, curr_sp)
                  curr_sp -= self.ptr_size
  
          # Set the return address
          r = ret_addr.to_bytes(self.ptr_size, byteorder='little')
          self.mem_write(curr_sp, r)
          self.reg_write(sp, curr_sp)
  
      def get_func_argv(self, callconv, argc):
          """
          Get the arguments for a function given the supplied calling convention
          """
          argv = []
          ptr_size = self.get_ptr_size()
          arch = self.get_arch()
          nargs = argc
          endian = 'little'
  
          # Handle calling conventions using floats
          if arch in (e_arch.ARCH_X86, e_arch.ARCH_AMD64):
              if callconv == e_arch.CALL_CONV_FLOAT:
  
                  for i, r in enumerate((e_arch.X86_REG_XMM0,
                                         e_arch.X86_REG_XMM1,
                                         e_arch.X86_REG_XMM2,
                                         e_arch.X86_REG_XMM3)):
                      if nargs == 0:
                          break
                      val = self.reg_read(r)
                      argv.append(val)
                      nargs -= 1
  
          if arch == e_arch.ARCH_X86:
              sp = self.reg_read(e_arch.X86_REG_ESP)
              if callconv == e_arch.CALL_CONV_FASTCALL:
                  if nargs >= 2:
                      argv.append(self.reg_read(e_arch.X86_REG_ECX))
                      argv.append(self.reg_read(e_arch.X86_REG_EDX))
                      nargs -= 2
                  elif nargs == 1:
                      argv.append(self.reg_read(e_arch.X86_REG_ECX))
                      nargs -= 1
          elif arch == e_arch.ARCH_AMD64:
              sp = self.reg_read(e_arch.AMD64_REG_RSP)
              sp += 0x20
  
              for i, r in enumerate((e_arch.AMD64_REG_RCX, e_arch.AMD64_REG_RDX,
                                     e_arch.AMD64_REG_R8, e_arch.AMD64_REG_R9)):
                  if nargs == 0:
                      break
                  val = self.reg_read(r)
                  argv.append(val)
                  nargs -= 1
          else:
              raise EmuException('Unsupported architecture')
  
          # Skip past the saved ret addr
          sp += ptr_size
          for i in range(nargs):
              ptr = self.mem_read(sp, ptr_size)
              argv.append(int.from_bytes(ptr, endian))
              sp += ptr_size
  
          return argv
  
      def do_call_return(self, argc, ret_addr=None, ret_value=None, conv=None):
          """
          Set the emulation state after a call has completed
          """
          if self.get_arch() == e_arch.ARCH_X86:
              sp = e_arch.X86_REG_ESP
              pc = e_arch.X86_REG_EIP
              rr = e_arch.X86_REG_EAX
          elif self.get_arch() == e_arch.ARCH_AMD64:
              sp = e_arch.AMD64_REG_RSP
              pc = e_arch.AMD64_REG_RIP
              rr = e_arch.AMD64_REG_RAX
          else:
              raise EmuException('Unsupported architecture')
  
          if conv == e_arch.CALL_CONV_FLOAT:
              rr = e_arch.X86_REG_XMM0
  
          stk_ptr = self.reg_read(sp)
  
          if ret_addr:
              self.reg_write(sp, stk_ptr + self.ptr_size)
              self.reg_write(pc, ret_addr)
          if ret_value is not None:
              self.reg_write(rr, ret_value)
  
          # Cleanup the stack
          if conv == e_arch.CALL_CONV_CDECL:
              # If cdecl, the emu engine will clean the stack
              pass
          elif conv == e_arch.CALL_CONV_FASTCALL:
              if self.get_arch() == e_arch.ARCH_X86:
                  if argc > 2:
                      self.clean_stack_args(argc - 2)
          else:
              self.clean_stack_args(argc)
  
      def get_ret_address(self):
          """
          Get the return address from the stack
          """
  
          endian = 'little'
  
          sp = self.get_stack_ptr()
          ret = self.mem_read(sp, self.ptr_size)
          ret = int.from_bytes(ret, endian)
          return ret
  
      def push_stack(self, val):
          """
          Put a value on the stack and adjust the stack pointer
          """
          endian = 'little'
          sp = self.get_stack_ptr()
          bval = val.to_bytes(self.ptr_size, endian)
          sp -= self.ptr_size
          self.mem_write(sp, bval)
          self.set_stack_ptr(sp)
          return val
  
      def pop_stack(self):
          """
          Get value from the stack and adjust the stack pointer
          """
          endian = 'little'
          sp = self.get_stack_ptr()
          val = self.mem_read(sp, self.ptr_size)
          val = int.from_bytes(val, endian)
          sp += self.ptr_size
          self.set_stack_ptr(sp)
          return val
  
      def get_stack_ptr(self):
          """
          Get the current address of the stack pointer
          """
          if self.get_arch() == e_arch.ARCH_X86:
              sp = self.reg_read(e_arch.X86_REG_ESP)
          elif self.get_arch() == e_arch.ARCH_AMD64:
              sp = self.reg_read(e_arch.AMD64_REG_RSP)
          return sp
  
      def set_stack_ptr(self, addr):
          """
          Set the current address of the stack pointer
          """
          if self.get_arch() == e_arch.ARCH_X86:
              self.reg_write(e_arch.X86_REG_ESP, addr)
          elif self.get_arch() == e_arch.ARCH_AMD64:
              self.reg_write(e_arch.AMD64_REG_RSP, addr)
  
      def format_stack(self, num_ptrs):
          """
          Get the stack and format it for display
          """
          out = []
          sp = self.get_stack_ptr()
          for i in range(num_ptrs):
              try:
                  ptr = self.mem_read(sp, self.get_ptr_size())
              except Exception:
                  return out
              ptr = int.from_bytes(ptr, 'little')
              tag = self.get_address_tag(ptr)
              out.append((sp, ptr, tag))
              sp += self.get_ptr_size()
          return out
  
      def print_stack(self, num_ptrs):
          """
          This a debug function used to print the current stack state
          """
          ptrs = self.format_stack(num_ptrs)
          print('Stack:')
          print('***********************')
          for p in ptrs:
              sp, ptr, tag = p
              if tag:
                  fmt = 'sp=0x%x:\t0x%x\t->\t%s' % (sp, ptr, tag)
              else:
                  fmt = 'sp=0x%x:\t0x%x\t' % (sp, ptr)
  
              print(fmt.expandtabs(5))
              sp += self.get_ptr_size()
  
      def get_stack_trace(self, num_ptrs=16):
          """
          Get the current stack state
          """
          trace = []
          sp = self.get_stack_ptr()
          try:
              for i in range(num_ptrs):
                  ptr = self.mem_read(sp, self.get_ptr_size())
                  ptr = int.from_bytes(ptr, 'little')
                  tag = self.get_address_tag(ptr)
                  fmt = "{0:#0{1}x}".format(ptr, 2 + (self.get_ptr_size() * 2))
                  sp_off = "{0:#0{1}x}".format(i * self.get_ptr_size(), 2 * 2)
                  if not tag:
                      entry = 'sp+%s: %s' % (sp_off, fmt)
                  else:
                      entry = 'sp+%s: %s -> %s' % (sp_off, fmt, tag)
                  trace.append(entry)
                  sp += self.get_ptr_size()
          finally:
              return trace
  
      def get_pc(self):
          """
          Get the value of the current program counter
          """
          if self.get_arch() == e_arch.ARCH_X86:
              pc = self.reg_read(e_arch.X86_REG_EIP)
          elif self.get_arch() == e_arch.ARCH_AMD64:
              pc = self.reg_read(e_arch.AMD64_REG_RIP)
          else:
              raise EmuException('Unsupported architecture')
          return pc
  
      def set_pc(self, addr):
          """
          Set the value of the current program counter
          """
          if self.get_arch() == e_arch.ARCH_X86:
              self.reg_write(e_arch.X86_REG_EIP, addr)
          elif self.get_arch() == e_arch.ARCH_AMD64:
              self.reg_write(e_arch.AMD64_REG_RIP, addr)
          else:
              raise EmuException('Unsupported architecture')
  
      def get_return_val(self):
          """
          Get the current value in the return register
          """
          if self.get_arch() == e_arch.ARCH_X86:
              val = self.reg_read(e_arch.X86_REG_EAX)
          elif self.get_arch() == e_arch.ARCH_AMD64:
              val = self.reg_read(e_arch.AMD64_REG_RAX)
          else:
              raise EmuException('Unsupported architecture')
          return val
  
      def reset_stack(self, base):
          """
          Reset stack to the supplied base address
          """
          arch = self.get_arch()
          ptr = base
  
          if arch == e_arch.ARCH_X86:
              self.reg_write(e_arch.X86_REG_ESP, base)
              self.reg_write(e_arch.X86_REG_EBP, base)
          elif arch == e_arch.ARCH_AMD64:
              # Save room for the "home space"
              ptr -= self.ptr_size * 5
              self.reg_write(e_arch.AMD64_REG_RSP, ptr)
              self.reg_write(e_arch.AMD64_REG_RBP, ptr)
  
          return base, ptr
  
      def alloc_stack(self, size):
          """
          Allocate memory to use for the program stack
          """
          # Allocate memory for our stack
          # Stack grows down
          chunk = self.get_valid_ranges(size, addr=0x1200000)
          addr, block_size = chunk
          self.mem_map(block_size, base=addr, tag='emu.stack')
  
          base = addr + block_size
          self.mem_reserve(size, base=base)
  
          base, ptr = self.reset_stack(base)
  
          return base, ptr
  
      def clean_stack_args(self, argc):
          """
          Adjust the stack for arguments that were supplied
          """
          ptr_size = self.get_ptr_size()
          arch = self.get_arch()
  
          if argc == 0:
              return
  
          if arch == e_arch.ARCH_X86:
              sp = self.reg_read(e_arch.X86_REG_ESP)
              sp += (ptr_size * argc)
              self.reg_write(e_arch.X86_REG_ESP, sp)
  
          elif arch == e_arch.ARCH_AMD64:
              return
          else:
              raise EmuException('Unsupported architecture')
  
      def get_arch(self):
          """
          Get the current emulated architecture
          """
          return self.arch
  
      def get_arch_name(self):
          """
          Get the name of current emulated architecture
          """
          if self.arch == e_arch.ARCH_AMD64:
              return 'amd64'
          elif self.arch == e_arch.ARCH_X86:
              return 'x86'
          return ''
  
      def eval_emu_var(self):
          """
          Used to expand variables supplied in the emulator config file. This
          might be useful for accessing files that are a relative path of the
          speakeasy package.
          For example:
              $ROOT$: This variable corresponds to the package root for speakeasy
          """
  
      def read_mem_string(self, address, width=1, max_chars=0):
          """
          Read a string from emulated memory
          """
          char = b'\xFF'
          string = b''
          i = 0
  
          if width == 1:
              decode = 'latin1'
          elif width == 2:
              decode = 'utf-16le'
          else:
              raise ValueError('Invalid string encoding')
  
          while int.from_bytes(char, 'little') != 0:
              if max_chars and i >= max_chars:
                  break
              char = self.mem_read(address, width)
  
              string += char
              address += width
              i += 1
  
          try:
              dec = string.decode(decode, 'ignore').replace('\x00', '')
          except Exception:
              dec = string.replace(b'\x00', b'')
          return dec
  
      def mem_string_len(self, address, width=1):
          """
          Get the length of a string from emulated memory
          """
          slen = -1
          char = b'\xFF'
  
          while int.from_bytes(char, 'little') != 0:
              char = self.mem_read(address, width)
              address += width
              slen += 1
          return slen
  
      def get_ansi_strings(self, data, min_len=4):
          """
          Get all ansi strings from a supplied memory blob
          """
          astrs = []
          pat = b'[\x20-\x7f]{%d,}' % (min_len)
          res = re.compile(pat)
          hits = res.findall(data)
          offset = 0
          for s in hits:
              try:
                  offset = data.find(s, offset)
                  s = s.decode('utf-8')
                  astrs.append((offset, s))
                  offset += 1
              except UnicodeDecodeError:
                  continue
          return astrs
  
      def get_unicode_strings(self, data, min_len=4):
          """
          Get all unicode strings from a supplied memory blob
          """
          wstrs = []
          pat = b'(?:[\x20-\x7f]\x00){%d,}' % (min_len)
          res = re.compile(pat)
          hits = res.findall(data)
          offset = 0
          for ws in hits:
              try:
                  offset = data.find(ws, offset)
                  ws = ws.decode('utf-16le')
                  wstrs.append((offset, ws))
                  offset += 1
              except UnicodeDecodeError:
                  continue
          return wstrs
  
      def mem_copy(self, dst, src, n):
          """
          Copy bytes from one emulated address to another
          """
          sbytes = self.mem_read(src, n)
          self.mem_write(dst, sbytes)
          return n
  
      def write_mem_string(self, string, address, width=1):
          """
          Write string data to an emulated memory address
          """
  
          if width == 1:
              encode = 'utf-8'
          elif width == 2:
              encode = 'utf-16le'
          else:
              raise ValueError('Invalid string encoding')
  
          enc_str = string.encode(encode)
          self.mem_write(address, enc_str)
  
      def get_ptr_size(self):
          """
          Get the pointer size of the current emulation state
          """
          return self.ptr_size
  
      def get_mem_strings(self):
          """
          Get ansi and unicode strings from emulated memory
          """
          tgt_tag_prefixes = ('emu.stack', 'api')
          ansi_strings = []
          unicode_strings = []
          ret_ansi = []
          ret_unicode = []
  
          for mmap in self.get_mem_maps():
              tag = mmap.get_tag()
              if tag and tag.startswith(tgt_tag_prefixes) and tag != self.input.get('mem_tag'):
                  data = self.mem_read(mmap.get_base(), mmap.get_size()-1)
                  ansi_strings += self.get_ansi_strings(data)
                  unicode_strings += self.get_unicode_strings(data)
  
          [ret_ansi.append(a) for a in ansi_strings if a not in ret_ansi]
          [ret_unicode.append(a) for a in unicode_strings if a not in ret_unicode]
  
          return (ret_ansi, ret_unicode)
  
      def set_ptr_size(self, arch):
          """
          Set the current pointer size used in the emulator
          """
          if arch == e_arch.ARCH_AMD64:
              self.ptr_size = 8
          elif arch == e_arch.ARCH_X86:
              self.ptr_size = 4
          else:
              raise EmuException('Unsupported architecture')
  
      def get_module_from_addr(self, addr):
          """
          If the supplied address belongs to a module, return it
          """
          for mod in self.modules:
              base, size = mod[1]
              if addr >= base and addr <= base + size:
                  return mod[0]
          return None
  
      def get_api_hooks(self, mod_name, func_name) -> List[common.ApiHook]:
          """
          If an API hook has been set, return it here
          """
  
          mod_name = mod_name.lower()
          func_name = func_name.lower()
          try:
              hook_struct, wildcard_module = self.hooks[common.HOOK_API]
          except KeyError:
              return []
          try:
              modules = [hook_struct[mod_name]]
          except KeyError:
              modules = []
          if wildcard_module:
              for module_name_saved, value in hook_struct.items():
                  if fnmatch.fnmatch(mod_name, module_name_saved) and mod_name != module_name_saved:
                      modules.append(value)
          user_hooks = []
          for module in modules:
              hooks, wildcard_api = module
              try:
                  user_hooks.extend(hooks[func_name])
              except KeyError:
                  pass
              if wildcard_api:
                  for func_name_saved, list_of_hooks in hooks.items():
                      if (fnmatch.fnmatch(func_name, func_name_saved) and
                         func_name != func_name_saved):
                          user_hooks.extend(list_of_hooks)
          return user_hooks
  
      def add_api_hook(self, cb, module='', api_name='', argc=0, call_conv=None,
                       emu=None) -> common.ApiHook:
          """
          Add an API level hook (e.g. kernel32.CreateFile) here
          """
          module = module.lower()
          api_name = api_name.lower()
  
          wildcard_module, wildcard_api = False, False
          for wc in ['?', '*', '[', ']']:
              if wc in module:
                  wildcard_module = True
              if wc in api_name:
                  wildcard_api = True
  
          if not emu:
              emu = self
          hook = common.ApiHook(emu, self.emu_eng, cb, module, api_name, argc, call_conv)
          _hooks: MODULE_LEVEL = self.hooks.get(common.HOOK_API)
  
          api_dictionary = (
              {
                api_name: [hook]
              },
              wildcard_api)
          if not _hooks:
              # First addition
              obj = ({module: api_dictionary}, wildcard_module)
          else:
              module_dict, previous_wildcard_module = _hooks
              try:
                  api_dict, previous_wildcard_api = module_dict[module]
              except KeyError:
                  # The module asked is not present, so we just add the api dictionary
                  module_dict[module] = api_dictionary
              else:
                  # The module asked is present, so we can just add the hook
                  api_dict.setdefault(api_name, []).append(hook)
                  module_dict[module] = (api_dict, previous_wildcard_api | wildcard_api)
              obj = (module_dict, previous_wildcard_module | wildcard_module)
          self.hooks.update({common.HOOK_API: obj})
          return hook
  
      def add_code_hook(self, cb, begin=1, end=0, ctx={}, emu=None):
          """
          Add a hook that will fire for every CPU instruction
          """
          hl = self.hooks.get(common.HOOK_CODE, [])
          if not emu:
              emu = self
          hook = common.CodeHook(self, self.emu_eng, cb, begin, end, ctx)
          if not hl:
              self.hooks.update({common.HOOK_CODE: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def _dynamic_code_cb(self, emu, addr, size, ctx={}):
          """
          Call all subscribers that want callbacks dynamic code callbacks
          """
  
          profiler = self.get_profiler()
          mm = self.get_address_map(addr)
          if profiler:
              run = self.get_current_run()
              profiler.log_dyn_code(run, mm.get_tag(), mm.get_base(), mm.get_size())
  
          for h in self.hooks.get(common.HOOK_DYN_CODE, []):
              h.cb(mm)
  
          # Delete the code hook that got us here
          if ctx and isinstance(ctx, dict):
              h = ctx.get('_delete_hook')
              if h:
                  h.disable()
  
      def _set_dyn_code_hook(self, addr, size, ctx={}):
          """
          Set the top level dispatch hook for dynamic code execution
          """
          max_hook_size = 0x10
          if size > max_hook_size:
              size = max_hook_size
  
          ch = self.add_code_hook(cb=self._dynamic_code_cb, begin=addr, end=addr + size, ctx=ctx)
          ctx.update({'_delete_hook': ch})
  
      def add_dyn_code_hook(self, cb, ctx=[], emu=None):
          """
          Add a hook that will fire when dynamically generated/copied code is executed
          """
          if not emu:
              emu = self
          hl = self.hooks.get(common.HOOK_DYN_CODE, [])
  
          hook = common.DynCodeHook(emu, self.emu_eng, cb, ctx)
          if not hl:
              self.hooks.update({common.HOOK_DYN_CODE: [hook, ]})
          else:
              hl.insert(0, hook)
  
          return hook
  
      def add_mem_read_hook(self, cb, begin=1, end=0, emu=None):
          """
          Add a hook that will fire for memory reads
          """
          if not emu:
              emu = self
          hook = common.ReadMemHook(emu, self.emu_eng, cb, begin, end)
          hl = self.hooks.get(common.HOOK_MEM_READ)
          if not hl:
              self.hooks.update({common.HOOK_MEM_READ: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def add_mem_write_hook(self, cb, begin=1, end=0, emu=None):
          """
          Add a hook that will fire for memory writes
          """
          if not emu:
              emu = self
          hook = common.WriteMemHook(emu, self.emu_eng, cb, begin, end)
          hl = self.hooks.get(common.HOOK_MEM_WRITE)
          if not hl:
              self.hooks.update({common.HOOK_MEM_WRITE: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def add_mem_map_hook(self, cb, begin=1, end=0, emu=None):
          """
          Add a hook that will fire for memory maps
          """
          if not emu:
              emu = self
          hook = common.MapMemHook(emu, self.emu_eng, cb, begin, end)
          hl = self.hooks.get(common.HOOK_MEM_MAP)
          if not hl:
              self.hooks.update({common.HOOK_MEM_MAP: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def _hook_mem_invalid_dispatch(self, emu, access, address, size, value, ctx):
          """
          This handler will dispatch other invalid memory hooks
          """
          hl = self.hooks.get(common.HOOK_MEM_INVALID, [])
  
          rv = True
          for mem_access_hook in hl[:-1]:
              if mem_access_hook.enabled:
                  rv = mem_access_hook.cb(emu, access, address, size, value, ctx)
                  if rv is False:
                      break
          return rv
  
      def add_mem_invalid_hook(self, cb, emu=None):
          """
          Add a hook that will fire for invalid memory access
          """
          hook = common.InvalidMemHook(self, self.emu_eng, cb, native_hook=False)
          hl = self.hooks.get(common.HOOK_MEM_INVALID)
          if not emu:
              emu = self
          if not hl:
              dispatch_hook = common.InvalidMemHook(emu, self.emu_eng,
                                                    self._hook_mem_invalid_dispatch,
                                                    native_hook=True)
              if self.emu_eng:
                  dispatch_hook.add()
  
              self.hooks.update({common.HOOK_MEM_INVALID: [hook, dispatch_hook]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def add_interrupt_hook(self, cb, ctx=[], emu=None):
          """
          Add a hook that will fire for software interrupts
          """
          if not emu:
              emu = self
          hook = common.InterruptHook(emu, self.emu_eng, cb, ctx=[])
          hl = self.hooks.get(common.HOOK_INTERRUPT)
          if not hl:
              self.hooks.update({common.HOOK_INTERRUPT: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  
      def add_instruction_hook(self, cb, begin=1, end=0, ctx=[], emu=None, insn=None):
          """
          Add a hook that will fire for IN, SYSCALL, or SYSENTER instructions
          """
          if not emu:
              emu = self
          hook = common.InstructionHook(emu, self.emu_eng, cb, ctx=[], insn=insn)
          hl = self.hooks.get(common.HOOK_INSN)
          if not hl:
              self.hooks.update({common.HOOK_INSN: [hook, ]})
          else:
              hl.insert(0, hook)
  
          if self.emu_eng:
              hook.add()
  
          return hook
  