from abc import ABCMeta, abstractmethod import lldb import json, struct, signal class ScriptedProcess(metaclass=ABCMeta): """ The base class for a scripted process. Most of the base class methods are `@abstractmethod` that need to be overwritten by the inheriting class. DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE. THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE. """ capabilities = None memory_regions = None loaded_images = None threads = None metadata = None @abstractmethod def __init__(self, exe_ctx, args): """ Construct a scripted process. Args: exe_ctx (lldb.SBExecutionContext): The execution context for the scripted process. args (lldb.SBStructuredData): A Dictionary holding arbitrary key/value pairs used by the scripted process. """ target = None self.target = None self.args = None self.arch = None if isinstance(exe_ctx, lldb.SBExecutionContext): target = exe_ctx.target if isinstance(target, lldb.SBTarget) and target.IsValid(): self.target = target triple = self.target.triple if triple: self.arch = triple.split('-')[0] self.dbg = target.GetDebugger() if isinstance(args, lldb.SBStructuredData) and args.IsValid(): self.args = args self.threads = {} self.loaded_images = [] self.metadata = {} self.capabilities = {} self.pid = 42 def get_capabilities(self): """ Get a dictionary containing the process capabilities. Returns: Dict[str:bool]: The dictionary of capability, with the capability name as the key and a boolean flag as the value. The dictionary can be empty. """ return self.capabilities def get_memory_region_containing_address(self, addr): """ Get the memory region for the scripted process, containing a specific address. Args: addr (int): Address to look for in the scripted process memory regions. Returns: lldb.SBMemoryRegionInfo: The memory region containing the address. None if out of bounds. """ return None def get_threads_info(self): """ Get the dictionary describing the process' Scripted Threads. Returns: Dict: The dictionary of threads, with the thread ID as the key and a Scripted Thread instance as the value. The dictionary can be empty. """ return self.threads @abstractmethod def read_memory_at_address(self, addr, size, error): """ Get a memory buffer from the scripted process at a certain address, of a certain size. Args: addr (int): Address from which we should start reading. size (int): Size of the memory to read. error (lldb.SBError): Error object. Returns: lldb.SBData: An `lldb.SBData` buffer with the target byte size and byte order storing the memory read. """ pass def write_memory_at_address(self, addr, data, error): """ Write a buffer to the scripted process memory. Args: addr (int): Address from which we should start reading. data (lldb.SBData): An `lldb.SBData` buffer to write to the process memory. error (lldb.SBError): Error object. Returns: size (int): Size of the memory to read. """ error.SetErrorString("%s doesn't support memory writes." % self.__class__.__name__) return 0 def get_loaded_images(self): """ Get the list of loaded images for the scripted process. ``` scripted_image = { uuid = "c6ea2b64-f77c-3d27-9528-74f507b9078b", path = "/usr/lib/dyld" load_addr = 0xbadc0ffee } ``` Returns: List[scripted_image]: A list of `scripted_image` dictionaries containing for each entry the library UUID or its file path and its load address. None if the list is empty. """ return self.loaded_images def get_process_id(self): """ Get the scripted process identifier. Returns: int: The scripted process identifier. """ return self.pid def launch(self): """ Simulate the scripted process launch. Returns: lldb.SBError: An `lldb.SBError` with error code 0. """ return lldb.SBError() def attach(self, attach_info): """ Simulate the scripted process attach. Args: attach_info (lldb.SBAttachInfo): The information related to the process we're attaching to. Returns: lldb.SBError: An `lldb.SBError` with error code 0. """ return lldb.SBError() def resume(self, should_stop=True): """ Simulate the scripted process resume. Args: should_stop (bool): If True, resume will also force the process state to stopped after running it. Returns: lldb.SBError: An `lldb.SBError` with error code 0. """ process = self.target.GetProcess() if not process: error = lldb.SBError() error.SetErrorString("Invalid process.") return error process.ForceScriptedState(lldb.eStateRunning); if (should_stop): process.ForceScriptedState(lldb.eStateStopped); return lldb.SBError() @abstractmethod def is_alive(self): """ Check if the scripted process is alive. Returns: bool: True if scripted process is alive. False otherwise. """ pass @abstractmethod def get_scripted_thread_plugin(self): """ Get scripted thread plugin name. Returns: str: Name of the scripted thread plugin. """ return None def get_process_metadata(self): """ Get some metadata for the scripted process. Returns: Dict: A dictionary containing metadata for the scripted process. None if the process as no metadata. """ return self.metadata def create_breakpoint(self, addr, error): """ Create a breakpoint in the scripted process from an address. This is mainly used with interactive scripted process debugging. Args: addr (int): Address at which the breakpoint should be set. error (lldb.SBError): Error object. Returns: SBBreakpoint: A valid breakpoint object that was created a the specified address. None if the breakpoint creation failed. """ error.SetErrorString("%s doesn't support creating breakpoints." % self.__class__.__name__) return False class ScriptedThread(metaclass=ABCMeta): """ The base class for a scripted thread. Most of the base class methods are `@abstractmethod` that need to be overwritten by the inheriting class. DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE. THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE. """ @abstractmethod def __init__(self, scripted_process, args): """ Construct a scripted thread. Args: process (ScriptedProcess): The scripted process owning this thread. args (lldb.SBStructuredData): A Dictionary holding arbitrary key/value pairs used by the scripted thread. """ self.target = None self.scripted_process = None self.process = None self.args = None self.idx = 0 self.tid = 0 self.idx = None self.name = None self.queue = None self.state = None self.stop_reason = None self.register_info = None self.register_ctx = {} self.frames = [] self.extended_info = [] if isinstance(scripted_process, ScriptedProcess): self.target = scripted_process.target self.scripted_process = scripted_process self.process = self.target.GetProcess() self.get_register_info() def get_thread_idx(self): """ Get the scripted thread index. Returns: int: The index of the scripted thread in the scripted process. """ return self.idx def get_thread_id(self): """ Get the scripted thread identifier. Returns: int: The identifier of the scripted thread. """ return self.tid def get_name(self): """ Get the scripted thread name. Returns: str: The name of the scripted thread. """ return self.name def get_state(self): """ Get the scripted thread state type. eStateStopped, ///< Process or thread is stopped and can be examined. eStateRunning, ///< Process or thread is running and can't be examined. eStateStepping, ///< Process or thread is in the process of stepping and can /// not be examined. eStateCrashed, ///< Process or thread has crashed and can be examined. Returns: int: The state type of the scripted thread. Returns lldb.eStateStopped by default. """ return lldb.eStateStopped def get_queue(self): """ Get the scripted thread associated queue name. This method is optional. Returns: str: The queue name associated with the scripted thread. """ return self.queue @abstractmethod def get_stop_reason(self): """ Get the dictionary describing the stop reason type with some data. This method is optional. Returns: Dict: The dictionary holding the stop reason type and the possibly the stop reason data. """ pass def get_stackframes(self): """ Get the list of stack frames for the scripted thread. ``` scripted_frame = { idx = 0, pc = 0xbadc0ffee } ``` Returns: List[scripted_frame]: A list of `scripted_frame` dictionaries containing at least for each entry, the frame index and the program counter value for that frame. The list can be empty. """ return self.frames def get_register_info(self): if self.register_info is None: self.register_info = dict() if self.scripted_process.arch == 'x86_64': self.register_info['sets'] = ['General Purpose Registers'] self.register_info['registers'] = INTEL64_GPR elif 'arm64' in self.scripted_process.arch: self.register_info['sets'] = ['General Purpose Registers'] self.register_info['registers'] = ARM64_GPR else: raise ValueError('Unknown architecture', self.scripted_process.arch) return self.register_info @abstractmethod def get_register_context(self): """ Get the scripted thread register context Returns: str: A byte representing all register's value. """ pass def get_extended_info(self): """ Get scripted thread extended information. Returns: List: A list containing the extended information for the scripted process. None if the thread as no extended information. """ return self.extended_info class PassthroughScriptedProcess(ScriptedProcess): driving_target = None driving_process = None def __init__(self, exe_ctx, args, launched_driving_process=True): super().__init__(exe_ctx, args) self.driving_target = None self.driving_process = None self.driving_target_idx = args.GetValueForKey("driving_target_idx") if self.driving_target_idx and self.driving_target_idx.IsValid(): idx = self.driving_target_idx.GetUnsignedIntegerValue(42) self.driving_target = self.target.GetDebugger().GetTargetAtIndex(idx) if launched_driving_process: self.driving_process = self.driving_target.GetProcess() for driving_thread in self.driving_process: structured_data = lldb.SBStructuredData() structured_data.SetFromJSON( json.dumps( { "driving_target_idx": idx, "thread_idx": driving_thread.GetIndexID(), } ) ) self.threads[ driving_thread.GetThreadID() ] = PassthroughScriptedThread(self, structured_data) for module in self.driving_target.modules: path = module.file.fullpath load_addr = module.GetObjectFileHeaderAddress().GetLoadAddress( self.driving_target ) self.loaded_images.append({"path": path, "load_addr": load_addr}) def get_memory_region_containing_address(self, addr): mem_region = lldb.SBMemoryRegionInfo() error = self.driving_process.GetMemoryRegionInfo(addr, mem_region) if error.Fail(): return None return mem_region def read_memory_at_address(self, addr, size, error): data = lldb.SBData() bytes_read = self.driving_process.ReadMemory(addr, size, error) if error.Fail(): return data data.SetDataWithOwnership( error, bytes_read, self.driving_target.GetByteOrder(), self.driving_target.GetAddressByteSize(), ) return data def write_memory_at_address(self, addr, data, error): return self.driving_process.WriteMemory( addr, bytearray(data.uint8.all()), error ) def get_process_id(self): return self.driving_process.GetProcessID() def is_alive(self): return True def get_scripted_thread_plugin(self): return f"{PassthroughScriptedThread.__module__}.{PassthroughScriptedThread.__name__}" class PassthroughScriptedThread(ScriptedThread): def __init__(self, process, args): super().__init__(process, args) driving_target_idx = args.GetValueForKey("driving_target_idx") thread_idx = args.GetValueForKey("thread_idx") # TODO: Change to Walrus operator (:=) with oneline if assignment # Requires python 3.8 val = thread_idx.GetUnsignedIntegerValue() if val is not None: self.idx = val self.driving_target = None self.driving_process = None self.driving_thread = None # TODO: Change to Walrus operator (:=) with oneline if assignment # Requires python 3.8 val = driving_target_idx.GetUnsignedIntegerValue() if val is not None: self.driving_target = self.target.GetDebugger().GetTargetAtIndex(val) self.driving_process = self.driving_target.GetProcess() self.driving_thread = self.driving_process.GetThreadByIndexID(self.idx) if self.driving_thread: self.id = self.driving_thread.GetThreadID() def get_thread_id(self): return self.id def get_name(self): return f"{PassthroughScriptedThread.__name__}.thread-{self.idx}" def get_stop_reason(self): stop_reason = {"type": lldb.eStopReasonInvalid, "data": {}} if ( self.driving_thread and self.driving_thread.IsValid() and self.get_thread_id() == self.driving_thread.GetThreadID() ): stop_reason["type"] = lldb.eStopReasonNone # TODO: Passthrough stop reason from driving process if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: if "arm64" in self.scripted_process.arch: stop_reason["type"] = lldb.eStopReasonException stop_reason["data"][ "desc" ] = self.driving_thread.GetStopDescription(100) elif self.scripted_process.arch == "x86_64": stop_reason["type"] = lldb.eStopReasonSignal stop_reason["data"]["signal"] = signal.SIGTRAP else: stop_reason["type"] = self.driving_thread.GetStopReason() return stop_reason def get_register_context(self): if not self.driving_thread or self.driving_thread.GetNumFrames() == 0: return None frame = self.driving_thread.GetFrameAtIndex(0) GPRs = None registerSet = frame.registers # Returns an SBValueList. for regs in registerSet: if "general purpose" in regs.name.lower(): GPRs = regs break if not GPRs: return None for reg in GPRs: self.register_ctx[reg.name] = int(reg.value, base=16) return struct.pack(f"{len(self.register_ctx)}Q", *self.register_ctx.values()) ARM64_GPR = [ {'name': 'x0', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0, 'generic': 'arg0', 'alt-name': 'arg0'}, {'name': 'x1', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg1', 'alt-name': 'arg1'}, {'name': 'x2', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg2', 'alt-name': 'arg2'}, {'name': 'x3', 'bitsize': 64, 'offset': 24, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 3, 'dwarf': 3, 'generic': 'arg3', 'alt-name': 'arg3'}, {'name': 'x4', 'bitsize': 64, 'offset': 32, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 4, 'dwarf': 4, 'generic': 'arg4', 'alt-name': 'arg4'}, {'name': 'x5', 'bitsize': 64, 'offset': 40, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 5, 'dwarf': 5, 'generic': 'arg5', 'alt-name': 'arg5'}, {'name': 'x6', 'bitsize': 64, 'offset': 48, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 6, 'dwarf': 6, 'generic': 'arg6', 'alt-name': 'arg6'}, {'name': 'x7', 'bitsize': 64, 'offset': 56, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 7, 'dwarf': 7, 'generic': 'arg7', 'alt-name': 'arg7'}, {'name': 'x8', 'bitsize': 64, 'offset': 64, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 8, 'dwarf': 8 }, {'name': 'x9', 'bitsize': 64, 'offset': 72, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 9, 'dwarf': 9 }, {'name': 'x10', 'bitsize': 64, 'offset': 80, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 10, 'dwarf': 10}, {'name': 'x11', 'bitsize': 64, 'offset': 88, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 11, 'dwarf': 11}, {'name': 'x12', 'bitsize': 64, 'offset': 96, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 12, 'dwarf': 12}, {'name': 'x13', 'bitsize': 64, 'offset': 104, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 13, 'dwarf': 13}, {'name': 'x14', 'bitsize': 64, 'offset': 112, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 14, 'dwarf': 14}, {'name': 'x15', 'bitsize': 64, 'offset': 120, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 15, 'dwarf': 15}, {'name': 'x16', 'bitsize': 64, 'offset': 128, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 16, 'dwarf': 16}, {'name': 'x17', 'bitsize': 64, 'offset': 136, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 17, 'dwarf': 17}, {'name': 'x18', 'bitsize': 64, 'offset': 144, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 18, 'dwarf': 18}, {'name': 'x19', 'bitsize': 64, 'offset': 152, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 19, 'dwarf': 19}, {'name': 'x20', 'bitsize': 64, 'offset': 160, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 20, 'dwarf': 20}, {'name': 'x21', 'bitsize': 64, 'offset': 168, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 21, 'dwarf': 21}, {'name': 'x22', 'bitsize': 64, 'offset': 176, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 22, 'dwarf': 22}, {'name': 'x23', 'bitsize': 64, 'offset': 184, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 23, 'dwarf': 23}, {'name': 'x24', 'bitsize': 64, 'offset': 192, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 24, 'dwarf': 24}, {'name': 'x25', 'bitsize': 64, 'offset': 200, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 25, 'dwarf': 25}, {'name': 'x26', 'bitsize': 64, 'offset': 208, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 26, 'dwarf': 26}, {'name': 'x27', 'bitsize': 64, 'offset': 216, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 27, 'dwarf': 27}, {'name': 'x28', 'bitsize': 64, 'offset': 224, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 28, 'dwarf': 28}, {'name': 'x29', 'bitsize': 64, 'offset': 232, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 29, 'dwarf': 29, 'generic': 'fp', 'alt-name': 'fp'}, {'name': 'x30', 'bitsize': 64, 'offset': 240, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 30, 'dwarf': 30, 'generic': 'lr', 'alt-name': 'lr'}, {'name': 'sp', 'bitsize': 64, 'offset': 248, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 31, 'dwarf': 31, 'generic': 'sp', 'alt-name': 'sp'}, {'name': 'pc', 'bitsize': 64, 'offset': 256, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 32, 'dwarf': 32, 'generic': 'pc', 'alt-name': 'pc'}, {'name': 'cpsr', 'bitsize': 32, 'offset': 264, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 33, 'dwarf': 33} ] INTEL64_GPR = [ {'name': 'rax', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0}, {'name': 'rbx', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 3, 'dwarf': 3}, {'name': 'rcx', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg4', 'alt-name': 'arg4'}, {'name': 'rdx', 'bitsize': 64, 'offset': 24, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg3', 'alt-name': 'arg3'}, {'name': 'rdi', 'bitsize': 64, 'offset': 32, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 5, 'dwarf': 5, 'generic': 'arg1', 'alt-name': 'arg1'}, {'name': 'rsi', 'bitsize': 64, 'offset': 40, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 4, 'dwarf': 4, 'generic': 'arg2', 'alt-name': 'arg2'}, {'name': 'rbp', 'bitsize': 64, 'offset': 48, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 6, 'dwarf': 6, 'generic': 'fp', 'alt-name': 'fp'}, {'name': 'rsp', 'bitsize': 64, 'offset': 56, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 7, 'dwarf': 7, 'generic': 'sp', 'alt-name': 'sp'}, {'name': 'r8', 'bitsize': 64, 'offset': 64, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 8, 'dwarf': 8, 'generic': 'arg5', 'alt-name': 'arg5'}, {'name': 'r9', 'bitsize': 64, 'offset': 72, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 9, 'dwarf': 9, 'generic': 'arg6', 'alt-name': 'arg6'}, {'name': 'r10', 'bitsize': 64, 'offset': 80, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 10, 'dwarf': 10}, {'name': 'r11', 'bitsize': 64, 'offset': 88, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 11, 'dwarf': 11}, {'name': 'r12', 'bitsize': 64, 'offset': 96, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 12, 'dwarf': 12}, {'name': 'r13', 'bitsize': 64, 'offset': 104, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 13, 'dwarf': 13}, {'name': 'r14', 'bitsize': 64, 'offset': 112, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 14, 'dwarf': 14}, {'name': 'r15', 'bitsize': 64, 'offset': 120, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 15, 'dwarf': 15}, {'name': 'rip', 'bitsize': 64, 'offset': 128, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 16, 'dwarf': 16, 'generic': 'pc', 'alt-name': 'pc'}, {'name': 'rflags', 'bitsize': 64, 'offset': 136, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'generic': 'flags', 'alt-name': 'flags'}, {'name': 'cs', 'bitsize': 64, 'offset': 144, 'encoding': 'uint', 'format': 'hex', 'set': 0}, {'name': 'fs', 'bitsize': 64, 'offset': 152, 'encoding': 'uint', 'format': 'hex', 'set': 0}, {'name': 'gs', 'bitsize': 64, 'offset': 160, 'encoding': 'uint', 'format': 'hex', 'set': 0} ]