From 84a0108256038ce4f870b458790910e0dc945dae Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:35:34 -0500 Subject: [PATCH] GP-5185: from the review GP-5185: don't revert GP-5185: ModelQuery fixes GP-5185: fix for ModelQuery GP-5185: threads GP-5185: attempt at threads GP-5815: main containers working GP-5185 certified GP-5815: new logic ala Volatility GP-5185: better print GP-5185: first pass --- .../certification.manifest | 1 + .../data/debugger-launchers/kernel-dbgeng.bat | 2 + .../data/support/kernel-dbgeng.py | 41 +- .../src/main/py/src/ghidradbg/arch.py | 1 + .../src/main/py/src/ghidradbg/commands.py | 17 +- .../py/src/ghidradbg/exdi/exdi_commands.py | 244 +++++++++++ .../py/src/ghidradbg/exdi/exdi_methods.py | 54 +++ .../src/main/py/src/ghidradbg/hooks.py | 7 + .../src/main/py/src/ghidradbg/schema_exdi.xml | 410 ++++++++++++++++++ .../src/main/py/src/ghidradbg/util.py | 39 +- .../TraceRmiLauncherServicePlugin.html | 38 ++ .../gui/memory/DebuggerRegionsPanel.java | 14 +- .../core/debug/gui/model/ModelQuery.java | 8 +- .../gui/modules/DebuggerModulesPanel.java | 14 +- .../gui/modules/DebuggerSectionsPanel.java | 14 +- .../gui/thread/DebuggerThreadsPanel.java | 14 +- 16 files changed, 868 insertions(+), 50 deletions(-) create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_commands.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_methods.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest index e01aa867bf..47aebb5c5b 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest +++ b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest @@ -15,4 +15,5 @@ src/main/py/README.md||GHIDRA||||END| src/main/py/pyproject.toml||GHIDRA||||END| src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl||GHIDRA||||END| src/main/py/src/ghidradbg/schema.xml||GHIDRA||||END| +src/main/py/src/ghidradbg/schema_exdi.xml||GHIDRA||||END| src/main/py/src/ghidrattd/schema.xml||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat index 3b8fea1998..7c79d4fb62 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat @@ -9,9 +9,11 @@ ::@menu-group local ::@icon icon.debugger ::@help TraceRmiLauncherServicePlugin#dbgeng_kernel +::@enum Connection:str Remote Local EXDI ::@env OPT_PYTHON_EXE:file!="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH." :: Use env instead of args, because "all args except first" is terrible to implement in batch ::@env OPT_TARGET_ARGS:str="" "Arguments" "Connection-string arguments (a la .server)" +::@env OPT_TARGET_FLAGS:Connection="Remote" "Type" "Type/flags for connection (Remote/Local/EXDI)." ::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available." ::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)." diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py index 9a578564ee..28c40cbe3b 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py @@ -1,17 +1,17 @@ ## ### -# IP: GHIDRA -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. ## import os @@ -38,6 +38,7 @@ else: def main(): # Delay these imports until sys.path is patched from ghidradbg import commands as cmd + from ghidradbg import util from pybag.dbgeng import core as DbgEng from ghidradbg.hooks import on_state_changed from ghidradbg.util import dbg @@ -47,8 +48,17 @@ def main(): repl = cmd.repl cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) + type = os.getenv('OPT_TARGET_FLAGS') + flags = 0 + if type == "Local": + flags = 1 + if type == "EXDI": + print(f"ConfigFile: {os.getenv('EXDI_GDBSRV_XML_CONFIG_FILE')}") + print(f"RegMapFile: {os.getenv('EXDI_SYSTEM_REGISTERS_MAP_XML_FILE')}") + util.set_convenience_variable('output-radix', 16) + flags = 2 args = os.getenv('OPT_TARGET_ARGS') - cmd.ghidra_trace_attach_kernel(args, start_trace=False) + cmd.ghidra_trace_attach_kernel(args, flags, start_trace=False) # TODO: HACK try: @@ -56,7 +66,8 @@ def main(): except KeyboardInterrupt as ki: dbg.interrupt() - cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG')) + #cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG')) + cmd.ghidra_trace_start("System") cmd.ghidra_trace_sync_enable() on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/arch.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/arch.py index 7a0ab053a7..e3e9b96999 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/arch.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/arch.py @@ -38,6 +38,7 @@ data64_compiler_map = { x86_compiler_map = { 'windows': 'windows', 'Cygwin': 'windows', + 'default': 'windows', } default_compiler_map = { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py index b1e52de754..6594bc6b5f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py @@ -32,6 +32,7 @@ from pybag.dbgeng.win32.kernel32 import STILL_ACTIVE from . import util, arch, methods, hooks from .dbgmodel.imodelobject import ModelObjectKind +from .exdi import exdi_commands, exdi_methods PAGE_SIZE = 4096 @@ -209,7 +210,10 @@ def start_trace(name): STATE.trace.register_mapper = arch.compute_register_mapper(language) parent = os.path.dirname(inspect.getfile(inspect.currentframe())) - schema_fn = os.path.join(parent, 'schema.xml') + if util.is_exdi(): + schema_fn = os.path.join(parent, 'schema_exdi.xml') + else: + schema_fn = os.path.join(parent, 'schema.xml') with open(schema_fn, 'r') as schema_file: schema_xml = schema_file.read() using_dbgmodel = os.getenv('OPT_USE_DBGMODEL') == "true" @@ -314,17 +318,19 @@ def ghidra_trace_attach(pid=None, attach_flags='0', initial_break=True, timeout= @util.dbg.eng_thread -def ghidra_trace_attach_kernel(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True): +def ghidra_trace_attach_kernel(command=None, flags=DbgEng.DEBUG_ATTACH_KERNEL_CONNECTION, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True): """ Create a session. """ dbg = util.dbg._base util.set_kernel(True) + if flags == 2: + util.set_exdi(True) if initial_break: dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK) if command != None: - dbg._client.AttachKernel(command) + dbg._client.AttachKernel(command, flags=int(flags)) if start_trace: ghidra_trace_start(command) @@ -592,7 +598,10 @@ def putreg(): for i in range(0, len(regs)): name = regs._reg.GetDescription(i)[0] try: - value = regs._get_register_by_index(i) + value = regs._get_register_by_index(i) + except Exception: + value = 0 + try: values.append(mapper.map_value(nproc, name, value)) robj.set_value(name, hex(value)) except Exception: diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_commands.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_commands.py new file mode 100644 index 0000000000..9c8f037500 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_commands.py @@ -0,0 +1,244 @@ +## ### +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## +from ghidratrace import sch +from ghidratrace.client import Client, Address, AddressRange, TraceObject + +PAGE_SIZE = 4096 + +from ghidradbg import arch, commands, util + +SESSION_PATH = 'Sessions[0]' +PROCESSES_PATH = SESSION_PATH + '.ExdiProcesses' +PROCESS_KEY_PATTERN = '[{pid}]' +PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN +PROC_BREAKS_PATTERN = PROCESS_PATTERN + '.Breakpoints' +PROC_BREAK_KEY_PATTERN = '[{breaknum}.{locnum}]' +THREADS_PATTERN = PROCESS_PATTERN + '.Threads' +THREAD_KEY_PATTERN = '[{tnum}]' +THREAD_PATTERN = THREADS_PATTERN + THREAD_KEY_PATTERN +MEMORY_PATH = SESSION_PATH + '.Memory' +REGION_KEY_PATTERN = '[{start}]' +REGION_PATTERN = MEMORY_PATH + REGION_KEY_PATTERN +KMODULES_PATH = SESSION_PATH + '.Modules' +KMODULE_KEY_PATTERN = '[{modpath}]' +KMODULE_PATTERN = KMODULES_PATH + KMODULE_KEY_PATTERN +MODULES_PATTERN = PROCESS_PATTERN + '.Modules' +MODULE_KEY_PATTERN = '[{modpath}]' +MODULE_PATTERN = MODULES_PATTERN + MODULE_KEY_PATTERN +SECTIONS_ADD_PATTERN = '.Sections' +SECTION_KEY_PATTERN = '[{secname}]' +SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN + +@util.dbg.eng_thread +def ghidra_trace_put_processes_exdi(): + """ + Put the list of processes into the trace's processes list. + """ + + radix = util.get_convenience_variable('output-radix') + commands.STATE.require_tx() + with commands.STATE.client.batch() as b: + put_processes_exdi(commands.STATE, radix) + + +@util.dbg.eng_thread +def ghidra_trace_put_regions_exdi(): + """ + Read the memory map, if applicable, and write to the trace's Regions + """ + + commands.STATE.require_tx() + with commands.STATE.client.batch() as b: + put_regions_exdi(commands.STATE) + + +@util.dbg.eng_thread +def ghidra_trace_put_kmodules_exdi(): + """ + Gather object files, if applicable, and write to the trace's Modules + """ + + commands.STATE.require_tx() + with commands.STATE.client.batch() as b: + put_kmodules_exdi(commands.STATE) + + +@util.dbg.eng_thread +def ghidra_trace_put_threads_exdi(pid): + """ + Put the current process's threads into the Ghidra trace + """ + + radix = util.get_convenience_variable('output-radix') + commands.STATE.require_tx() + with commands.STATE.client.batch() as b: + put_threads_exdi(commands.STATE, pid, radix) + + +@util.dbg.eng_thread +def ghidra_trace_put_all_exdi(): + """ + Put everything currently selected into the Ghidra trace + """ + + radix = util.get_convenience_variable('output-radix') + commands.STATE.require_tx() + with commands.STATE.client.batch() as b: + if util.dbg.use_generics == False: + put_processes_exdi(commands.STATE, radix) + put_regions_exdi(commands.STATE) + put_kmodules_exdi(commands.STATE) + + +@util.dbg.eng_thread +def put_processes_exdi(state, radix): + radix = util.get_convenience_variable('output-radix') + keys = [] + result = util.dbg._base.cmd("!process 0 0") + lines = result.split("\n") + count = int((len(lines)-2)/5) + for i in range(0,count): + l1 = lines[i*5+1].strip().split() # PROCESS + l2 = lines[i*5+2].strip().split() # SessionId, Cid, Peb: ParentId + l3 = lines[i*5+3].strip().split() # DirBase, ObjectTable, HandleCount + l4 = lines[i*5+4].strip().split() # Image + id = int(l2[3], 16) + name = l4[1] + ppath = PROCESS_PATTERN.format(pid=id) + procobj = state.trace.create_object(ppath) + keys.append(PROCESS_KEY_PATTERN.format(pid=id)) + pidstr = ('0x{:x}' if radix == + 16 else '0{:o}' if radix == 8 else '{}').format(id) + procobj.set_value('PID', id) + procobj.set_value('Name', name) + procobj.set_value('_display', '[{}] {}'.format(pidstr, name)) + (base, addr) = commands.map_address(int(l1[1],16)) + procobj.set_value('EPROCESS', addr, schema="ADDRESS") + (base, addr) = commands.map_address(int(l2[5],16)) + procobj.set_value('PEB', addr, schema="ADDRESS") + (base, addr) = commands.map_address(int(l3[1],16)) + procobj.set_value('DirBase', addr, schema="ADDRESS") + (base, addr) = commands.map_address(int(l3[3],16)) + procobj.set_value('ObjectTable', addr, schema="ADDRESS") + #procobj.set_value('ObjectTable', l3[3]) + tcobj = state.trace.create_object(ppath+".Threads") + procobj.insert() + tcobj.insert() + state.trace.proxy_object_path(PROCESSES_PATH).retain_values(keys) + + +@util.dbg.eng_thread +def put_regions_exdi(state): + radix = util.get_convenience_variable('output-radix') + keys = [] + result = util.dbg._base.cmd("!address") + lines = result.split("\n") + init = False + for l in lines: + if "-------" in l: + init = True + continue + if init == False: + continue + fields = l.strip().replace('`','').split() # PROCESS + if len(fields) < 4: + continue + start = fields[0] + #finish = fields[1] + length = fields[2] + type = fields[3] + (sbase, saddr) = commands.map_address(int(start,16)) + #(fbase, faddr) = commands.map_address(int(finish,16)) + rng = saddr.extend(int(length,16)) + rpath = REGION_PATTERN.format(start=start) + keys.append(REGION_KEY_PATTERN.format(start=start)) + regobj = state.trace.create_object(rpath) + regobj.set_value('Range', rng, schema="RANGE") + regobj.set_value('Size', length) + regobj.set_value('Type', type) + regobj.set_value('_readable', True) + regobj.set_value('_writable', True) + regobj.set_value('_executable', True) + regobj.set_value('_display', '[{}] {}'.format( + start, type)) + regobj.insert() + state.trace.proxy_object_path(MEMORY_PATH).retain_values(keys) + + +@util.dbg.eng_thread +def put_kmodules_exdi(state): + radix = util.get_convenience_variable('output-radix') + keys = [] + result = util.dbg._base.cmd("lm") + lines = result.split("\n") + init = False + for l in lines: + if "start" in l: + continue + if "Unloaded" in l: + continue + fields = l.strip().replace('`','').split() + if len(fields) < 3: + continue + start = fields[0] + finish = fields[1] + name = fields[2] + sname = name.replace('.sys','').replace('.dll','') + (sbase, saddr) = commands.map_address(int(start,16)) + (fbase, faddr) = commands.map_address(int(finish,16)) + sz = faddr.offset - saddr.offset + rng = saddr.extend(sz) + mpath = KMODULE_PATTERN.format(modpath=sname) + keys.append(KMODULE_KEY_PATTERN.format(modpath=sname)) + modobj = commands.STATE.trace.create_object(mpath) + modobj.set_value('Name', name) + modobj.set_value('Base', saddr, schema="ADDRESS") + modobj.set_value('Range', rng, schema="RANGE") + modobj.set_value('Size', hex(sz)) + modobj.insert() + state.trace.proxy_object_path(KMODULES_PATH).retain_values(keys) + + +@util.dbg.eng_thread +def put_threads_exdi(state, pid, radix): + radix = util.get_convenience_variable('output-radix') + pidstr = ('0x{:x}' if radix == 16 else '0{:o}' if radix == 8 else '{}').format(pid) + keys = [] + result = util.dbg._base.cmd("!process "+hex(pid)+" 4") + lines = result.split("\n") + for l in lines: + l = l.strip() + if "THREAD" not in l: + continue + fields = l.split() + cid = fields[3] # pid.tid (decimal) + tid = int(cid.split('.')[1],16) + tidstr = ('0x{:x}' if radix == + 16 else '0{:o}' if radix == 8 else '{}').format(tid) + tpath = THREAD_PATTERN.format(pid=pid, tnum=tid) + tobj = commands.STATE.trace.create_object(tpath) + keys.append(THREAD_KEY_PATTERN.format(tnum=tidstr)) + tobj = state.trace.create_object(tpath) + tobj.set_value('PID', pidstr) + tobj.set_value('TID', tidstr) + tobj.set_value('_display', '[{}]'.format(tidstr)) + tobj.set_value('ETHREAD', fields[1]) + tobj.set_value('TEB', fields[5]) + tobj.set_value('Win32Thread', fields[7]) + tobj.set_value('State', fields[8]) + tobj.insert() + commands.STATE.trace.proxy_object_path( + THREADS_PATTERN.format(pid=pidstr)).retain_values(keys) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_methods.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_methods.py new file mode 100644 index 0000000000..1becb0920a --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/exdi/exdi_methods.py @@ -0,0 +1,54 @@ +## ### +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## +import re + +from ghidratrace import sch +from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange +from ghidradbg import util, commands, methods +from ghidradbg.methods import REGISTRY, SESSIONS_PATTERN, SESSION_PATTERN, extre + +from . import exdi_commands + +XPROCESSES_PATTERN = extre(SESSION_PATTERN, '\.ExdiProcesses') +XPROCESS_PATTERN = extre(XPROCESSES_PATTERN, '\[(?P\\d*)\]') +XTHREADS_PATTERN = extre(XPROCESS_PATTERN, '\.Threads') + +def find_pid_by_pattern(pattern, object, err_msg): + mat = pattern.fullmatch(object.path) + if mat is None: + raise TypeError(f"{object} is not {err_msg}") + pid = int(mat['procnum']) + return pid + + +def find_pid_by_obj(object): + return find_pid_by_pattern(XTHREADS_PATTERN, object, "an ExdiThreadsContainer") + + + +@REGISTRY.method(action='refresh', display="Refresh Target Processes") +def refresh_exdi_processes(node: sch.Schema('ExdiProcessContainer')): + """Refresh the list of processes in the target kernel.""" + with commands.open_tracked_tx('Refresh Processes'): + exdi_commands.ghidra_trace_put_processes_exdi() + + +@REGISTRY.method(action='refresh', display="Refresh Process Threads") +def refresh_exdi_threads(node: sch.Schema('ExdiThreadContainer')): + """Refresh the list of threads in the process.""" + pid = find_pid_by_obj(node) + with commands.open_tracked_tx('Refresh Threads'): + exdi_commands.ghidra_trace_put_threads_exdi(pid) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py index f0103ea692..bf7b0516f3 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/hooks.py @@ -27,6 +27,7 @@ from pybag.dbgeng.callbacks import EventHandler from pybag.dbgeng.idebugbreakpoint import DebugBreakpoint from . import commands, util +from .exdi import exdi_commands ALL_EVENTS = 0xFFFF @@ -65,6 +66,8 @@ class ProcessState(object): if first: if util.is_kernel(): commands.create_generic("Sessions") + if util.is_exdi() and util.dbg.use_generics is False: + commands.create_generic("Sessions[0].ExdiProcesses") commands.put_processes() commands.put_environment() commands.put_threads() @@ -86,9 +89,13 @@ class ProcessState(object): if first or hashable_frame not in self.visited: self.visited.add(hashable_frame) if first or self.regions: + if util.is_exdi(): + exdi_commands.put_regions_exdi(commands.STATE) commands.put_regions() self.regions = False if first or self.modules: + if util.is_exdi(): + exdi_commands.put_kmodules_exdi(commands.STATE) commands.put_modules() self.modules = False if first or self.breaks: diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml new file mode 100644 index 0000000000..66c1b27d30 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema_exdi.xml @@ -0,0 +1,410 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py index 9c5f0f593d..a370cfc720 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/util.py @@ -1,17 +1,17 @@ ## ### -# IP: GHIDRA -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# IP: GHIDRA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. ## from collections import namedtuple from concurrent.futures import Future @@ -238,6 +238,7 @@ class GhidraDbg(object): ]: setattr(self, name, self.eng_thread(getattr(base, name))) self.IS_KERNEL = False + self.IS_EXDI = False def _new_base(self): self._protected_base = AllDbg() @@ -455,6 +456,8 @@ def get_breakpoints(): @dbg.eng_thread def selected_process(): try: + if is_exdi(): + return 0 if is_kernel(): do = dbg._base._systems.GetCurrentProcessDataOffset() id = c_ulong() @@ -472,6 +475,8 @@ def selected_process(): @dbg.eng_thread def selected_process_space(): try: + if is_exdi(): + return 0 if is_kernel(): return dbg._base._systems.GetCurrentProcessDataOffset() return selected_process() @@ -754,6 +759,8 @@ def split_path(pathString): segs = pathString.split(".") for s in segs: if s.endswith("]"): + if "[" not in s: + print(f"Missing terminator: {s}") index = s.index("[") list.append(s[:index]) list.append(s[index:]) @@ -902,3 +909,9 @@ def set_kernel(value): def is_kernel(): return dbg.IS_KERNEL + +def set_exdi(value): + dbg.IS_EXDI = value + +def is_exdi(): + return dbg.IS_EXDI diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html index 2917e2b759..2fd845fddc 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html @@ -825,6 +825,44 @@ bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1 'net:port=54321,key=1.1.1.1'.' + + +

EXDI

+ +

Setup for EXDI connections is fairly complicated and difficult to get correct. + The argument string typically should be something like:

+ +

The CLSID here should match the CLSID in the exdiConfigData.xml file in the debugger architectural directory. If windbg has been + run using EXDI at some point, there will also be an entry in the System Registry for this CLSID. The InprocServer32 subentry + for this CLSID in the Registry should point to a copy of ExdiGdbSrv.dll, typically the one in the same directory. This DLL + must reside somewhere that the debugger has permission to load from, i.e. not in the WindowsApps directory tree. + The exdiConfigData file should be configured for the target you're using. We heavily recommend using displayCommPackets==yes, + as many of the tasks take considerable time, and this is the only indicator of progress. +

+ +

The Kd=Guess parameter causes the underlying engine to scan memory for the kernel's base address, which will probably not + be provided by the gdbstub. (Kd=NtBaseAddr is also a valid option, as is eliminating the parameter, but, currently, we have no + idea how to point the configuration at a correct value. Using this option will cause the load to spin pointlessly.) If you can, + we highly recommend breaking the target near the base address, as the search proceeds down through memory starting at the current + program counter. If the difference between the PC and the base address is large, the loading process will punt before useful + values are detected. If anyone understand how to extend this search (or knows how to set the base address to sidestep the scan), + we would really love some guidance. +

+

TTD (Time-Travel Debugging)

This is a nascent extension to our launcher for the Windows Debugger. The launcher itself diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java index 3399ccde50..bcb7db67b0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -197,11 +197,17 @@ public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel seedPath = object.getCanonicalPath().getKeyList(); List processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); if (processPath != null) { - return successorRegions(rootSchema, processPath); + ModelQuery result = successorRegions(rootSchema, processPath); + if (!result.isEmpty()) { + return result; + } } List memoryPath = rootSchema.searchForSuitable(TargetMemory.class, seedPath); if (memoryPath != null) { - return successorRegions(rootSchema, memoryPath); + ModelQuery result = successorRegions(rootSchema, memoryPath); + if (!result.isEmpty()) { + return result; + } } return successorRegions(rootSchema, List.of()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ModelQuery.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ModelQuery.java index 1dfc017764..3379626584 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ModelQuery.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ModelQuery.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -240,4 +240,8 @@ public class ModelQuery { } return false; } + + public boolean isEmpty() { + return predicates.isEmpty(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java index 2b447e3fea..33302ada21 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPanel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -242,13 +242,19 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel seedPath = object.getCanonicalPath().getKeyList(); List processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); if (processPath != null) { - return successorModules(rootSchema, processPath); + ModelQuery result = successorModules(rootSchema, processPath); + if (!result.isEmpty()) { + return result; + } } List containerPath = rootSchema.searchForSuitableContainer(TargetModule.class, seedPath); if (containerPath != null) { - return successorModules(rootSchema, containerPath); + ModelQuery result = successorModules(rootSchema, containerPath); + if (!result.isEmpty()) { + return result; + } } return successorModules(rootSchema, List.of()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionsPanel.java index 016049218b..b303088409 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionsPanel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -221,14 +221,20 @@ public class DebuggerSectionsPanel extends AbstractObjectsTableBasedPanel seedPath = object.getCanonicalPath().getKeyList(); List processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); if (processPath != null) { - return successorSections(rootSchema, processPath); + ModelQuery result = successorSections(rootSchema, processPath); + if (!result.isEmpty()) { + return result; + } } // Yes, anchor on the *module* container when searching for sections List containerPath = rootSchema.searchForSuitableContainer(TargetModule.class, seedPath); if (containerPath != null) { - return successorSections(rootSchema, containerPath); + ModelQuery result = successorSections(rootSchema, containerPath); + if (!result.isEmpty()) { + return result; + } } return successorSections(rootSchema, List.of()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java index 55ca501f66..25a182d578 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPanel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -347,13 +347,19 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel seedPath = object.getCanonicalPath().getKeyList(); List processPath = rootSchema.searchForAncestor(TargetProcess.class, seedPath); if (processPath != null) { - return successorThreads(rootSchema, processPath); + ModelQuery result = successorThreads(rootSchema, processPath); + if (!result.isEmpty()) { + return result; + } } List containerPath = rootSchema.searchForSuitableContainer(TargetThread.class, seedPath); if (containerPath != null) { - return successorThreads(rootSchema, containerPath); + ModelQuery result = successorThreads(rootSchema, containerPath); + if (!result.isEmpty()) { + return result; + } } return successorThreads(rootSchema, List.of()); }