From 05ed589fa0408529972bd4c3ba686576e9ac9009 Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:57:47 -0500 Subject: [PATCH] GP-4732: from the demo (lol) GP-4732: frome review GP-4732: working until we step GP-4732: manifestGP-4732: works, except for resumeGP-4732: successGP-4732: frome review --- .../certification.manifest | 1 + .../data/debugger-launchers/kernel-dbgeng.bat | 2 +- .../data/debugger-launchers/remote-dbgeng.bat | 11 +- .../data/debugger-launchers/svrcx-dbgeng.bat | 23 ++++ .../data/support/kernel-dbgeng.py | 2 +- .../data/support/remote-dbgeng.py | 43 +++---- .../data/support/svrcx-dbgeng.py | 73 +++++++++++ .../src/main/py/src/ghidradbg/hooks.py | 113 ++++++++++++++---- .../src/main/py/src/ghidradbg/util.py | 41 ++++++- .../TraceRmiLauncherServicePlugin.html | 30 ++++- 10 files changed, 272 insertions(+), 67 deletions(-) create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/svrcx-dbgeng.bat create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/svrcx-dbgeng.py diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest index 47aebb5c5b..f90bee9864 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest +++ b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest @@ -9,6 +9,7 @@ data/debugger-launchers/local-dbgeng-ext.bat||GHIDRA||||END| data/debugger-launchers/local-dbgeng.bat||GHIDRA||||END| data/debugger-launchers/local-ttd.bat||GHIDRA||||END| data/debugger-launchers/remote-dbgeng.bat||GHIDRA||||END| +data/debugger-launchers/svrcx-dbgeng.bat||GHIDRA||||END| src/main/py/LICENSE||GHIDRA||||END| src/main/py/MANIFEST.in||GHIDRA||||END| src/main/py/README.md||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 7c79d4fb62..13ae2099f0 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 @@ -12,7 +12,7 @@ ::@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_KCONNECT_STRING: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/debugger-launchers/remote-dbgeng.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/remote-dbgeng.bat index 14d4939504..e40f2453c1 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/remote-dbgeng.bat +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/remote-dbgeng.bat @@ -1,9 +1,8 @@ ::@title dbgeng-remote -::@image-opt env:OPT_TARGET_IMG ::@desc
-::@desc-::@desc This will launch the target on a remote machine using dbgeng.dll. +::@desc This will connect to a remote machine using the .server interface. ::@desc For setup instructions, press F1. ::@desc
::@desc @@ -11,11 +10,7 @@ ::@icon icon.debugger ::@help TraceRmiLauncherServicePlugin#dbgeng_remote ::@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_IMG:str!="" "Image" "The target binary executable image" -::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target" -::@env OPT_CONNECT_STRING:str="" "Connection" "Connection-string arguments (a la dbgsrv args)" -::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available." +::@env OPT_CONNECT_STRING:str="" "Connection" "Connection-string arguments (a la .server)" ::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)." @echo off diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/svrcx-dbgeng.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/svrcx-dbgeng.bat new file mode 100644 index 0000000000..3c6c5e17a6 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/svrcx-dbgeng.bat @@ -0,0 +1,23 @@ +::@title dbgeng-svrcx +::@image-opt env:OPT_TARGET_IMG +::@desc +::@desc+::@desc This will launch the target via a connection server on a remote machine. +::@desc For setup instructions, press F1. +::@desc
+::@desc +::@menu-group local +::@icon icon.debugger +::@help TraceRmiLauncherServicePlugin#dbgeng_svrcx +::@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_IMG:str!="" "Image" "The target binary executable image" +::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target" +::@env OPT_CONNECT_STRING:str="" "Connection" "Connection-string arguments (a la dbgsrv args)" +::@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)." + +@echo off + +"%OPT_PYTHON_EXE%" -i ..\support\svrcx-dbgeng.py 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 28c40cbe3b..514bbb752f 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py @@ -57,7 +57,7 @@ def main(): 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') + args = os.getenv('OPT_KCONNECT_STRING') cmd.ghidra_trace_attach_kernel(args, flags, start_trace=False) # TODO: HACK diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-dbgeng.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-dbgeng.py index f207173f66..6e6df60ffc 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-dbgeng.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-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 @@ -48,22 +48,11 @@ def main(): repl = cmd.repl cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) - args = os.getenv('OPT_TARGET_ARGS') - if args: - args = ' ' + args - cmd.ghidra_trace_connect_server(os.getenv('OPT_CONNECT_STRING')) - img = os.getenv('OPT_TARGET_IMG') - if img is not None and img != "": - cmd.ghidra_trace_create(img + args, start_trace=False) - - # TODO: HACK - try: - dbg.wait() - except KeyboardInterrupt as ki: - dbg.interrupt() - cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG')) + os.environ['OPT_USE_DBGMODEL'] = "false" + cmd.ghidra_trace_start("Remote") cmd.ghidra_trace_sync_enable() + dbg.interrupt() on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK) cmd.repl() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/support/svrcx-dbgeng.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/svrcx-dbgeng.py new file mode 100644 index 0000000000..f207173f66 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/svrcx-dbgeng.py @@ -0,0 +1,73 @@ +## ### +# 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 +import sys + + +home = os.getenv('GHIDRA_HOME') + +if os.path.isdir(f'{home}\\ghidra\\.git'): + sys.path.append( + f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src') + sys.path.append( + f'{home}\\ghidra\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src') +elif os.path.isdir(f'{home}\\.git'): + sys.path.append( + f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\build\\pypkg\\src') + sys.path.append( + f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\build\\pypkg\\src') +else: + sys.path.append( + f'{home}\\Ghidra\\Debug\\Debugger-agent-dbgeng\\pypkg\\src') + sys.path.append(f'{home}\\Ghidra\\Debug\\Debugger-rmi-trace\\pypkg\\src') + + +def main(): + # Delay these imports until sys.path is patched + from ghidradbg import commands as cmd + from pybag.dbgeng import core as DbgEng + from ghidradbg.hooks import on_state_changed + from ghidradbg.util import dbg + + # So that the user can re-enter by typing repl() + global repl + repl = cmd.repl + + cmd.ghidra_trace_connect(os.getenv('GHIDRA_TRACE_RMI_ADDR')) + args = os.getenv('OPT_TARGET_ARGS') + if args: + args = ' ' + args + cmd.ghidra_trace_connect_server(os.getenv('OPT_CONNECT_STRING')) + img = os.getenv('OPT_TARGET_IMG') + if img is not None and img != "": + cmd.ghidra_trace_create(img + args, start_trace=False) + + # TODO: HACK + try: + dbg.wait() + except KeyboardInterrupt as ki: + dbg.interrupt() + + cmd.ghidra_trace_start(os.getenv('OPT_TARGET_IMG')) + cmd.ghidra_trace_sync_enable() + + on_state_changed(DbgEng.DEBUG_CES_EXECUTION_STATUS, DbgEng.DEBUG_STATUS_BREAK) + cmd.repl() + + +if __name__ == '__main__': + main() 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 bf7b0516f3..dd01eab5a4 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 @@ -162,7 +162,7 @@ def log_errors(func): @log_errors def on_state_changed(*args): - # print("ON_STATE_CHANGED") + # print("ON_STATE_CHANGED") # print(args) if args[0] == DbgEng.DEBUG_CES_CURRENT_THREAD: return on_thread_selected(args) @@ -176,7 +176,7 @@ def on_state_changed(*args): proc = util.selected_process() if args[1] & DbgEng.DEBUG_STATUS_INSIDE_WAIT: if proc in PROC_STATE: - # Process may have exited (so deleted) first + # Process may have exited (so deleted) first PROC_STATE[proc].waiting = True return S_OK if proc in PROC_STATE: @@ -482,28 +482,45 @@ def on_exception(*args): @util.dbg.eng_thread def install_hooks(): - # print("Installing hooks") - if HOOK_STATE.installed: - return - HOOK_STATE.installed = True + # print("Installing hooks") + if HOOK_STATE.installed: + return + HOOK_STATE.installed = True - events = util.dbg._base.events + events = util.dbg._base.events + + if util.is_remote(): + events.engine_state(handler=on_state_changed_async) + events.debuggee_state(handler=on_debuggee_changed_async) + events.session_status(handler=on_session_status_changed_async) + events.symbol_state(handler=on_symbol_state_changed_async) + events.system_error(handler=on_system_error_async) - events.engine_state(handler=on_state_changed) - events.debuggee_state(handler=on_debuggee_changed) - events.session_status(handler=on_session_status_changed) - events.symbol_state(handler=on_symbol_state_changed) - events.system_error(handler=on_system_error) + events.create_process(handler=on_new_process_async) + events.exit_process(handler=on_process_deleted_async) + events.create_thread(handler=on_threads_changed_async) + events.exit_thread(handler=on_threads_changed_async) + events.module_load(handler=on_modules_changed_async) + events.unload_module(handler=on_modules_changed_async) - events.create_process(handler=on_new_process) - events.exit_process(handler=on_process_deleted) - events.create_thread(handler=on_threads_changed) - events.exit_thread(handler=on_threads_changed) - events.module_load(handler=on_modules_changed) - events.unload_module(handler=on_modules_changed) + events.breakpoint(handler=on_breakpoint_hit_async) + events.exception(handler=on_exception_async) + else: + events.engine_state(handler=on_state_changed) + events.debuggee_state(handler=on_debuggee_changed) + events.session_status(handler=on_session_status_changed) + events.symbol_state(handler=on_symbol_state_changed) + events.system_error(handler=on_system_error) - events.breakpoint(handler=on_breakpoint_hit) - events.exception(handler=on_exception) + events.create_process(handler=on_new_process) + events.exit_process(handler=on_process_deleted) + events.create_thread(handler=on_threads_changed) + events.exit_thread(handler=on_threads_changed) + events.module_load(handler=on_modules_changed) + events.unload_module(handler=on_modules_changed) + + events.breakpoint(handler=on_breakpoint_hit) + events.exception(handler=on_exception) @util.dbg.eng_thread @@ -527,3 +544,59 @@ def disable_current_process(): if proc in PROC_STATE: # Silently ignore already disabled del PROC_STATE[proc] + + +@log_errors +def on_state_changed_async(*args): + util.dbg.run_async(on_state_changed, *args) + + +@log_errors +def on_debuggee_changed_async(*args): + util.dbg.run_async(on_debuggee_changed, *args) + + +@log_errors +def on_session_status_changed_async(*args): + util.dbg.run_async(on_session_status_changed, *args) + + +@log_errors +def on_symbol_state_changed_async(*args): + util.dbg.run_async(on_symbol_state_changed, *args) + + +@log_errors +def on_system_error_async(*args): + util.dbg.run_async(on_system_error, *args) + + +@log_errors +def on_new_process_async(*args): + util.dbg.run_async(on_new_process, *args) + + +@log_errors +def on_process_deleted_async(*args): + util.dbg.run_async(on_process_deleted, *args) + + +@log_errors +def on_threads_changed_async(*args): + util.dbg.run_async(on_threads_changed, *args) + + +@log_errors +def on_modules_changed_async(*args): + util.dbg.run_async(on_modules_changed, *args) + + +@log_errors +def on_breakpoint_hit_async(*args): + util.dbg.run_async(on_breakpoint_hit, *args) + + +@log_errors +def on_exception_async(*args): + util.dbg.run_async(on_exception, *args) + 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 88a6084885..86d92bdb54 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 @@ -29,12 +29,13 @@ import traceback from comtypes import CoClass, GUID import comtypes from comtypes.gen import DbgMod -from comtypes.hresult import S_OK +from comtypes.hresult import S_OK, S_FALSE from pybag import pydbg, userdbg, kerneldbg, crashdbg from pybag.dbgeng import core as DbgEng from pybag.dbgeng import exception from pybag.dbgeng import util as DbgUtil from pybag.dbgeng.callbacks import DbgEngCallbacks +from pybag.dbgeng.idebugclient import DebugClient from ghidradbg.dbgmodel.ihostdatamodelaccess import HostDataModelAccess from _winapi import STILL_ACTIVE @@ -145,7 +146,7 @@ class DbgExecutor(object): def _submit_no_exit(self, fn, /, *args, **kwargs): f = Future() - if self._executing: + if self._executing and self._ghidra_dbg.IS_REMOTE == False: f.set_exception(DebuggeeRunningException("Debuggee is Running")) return f w = _WorkItem(f, fn, args, kwargs) @@ -163,7 +164,8 @@ class DbgExecutor(object): def _state_execute(self): self._executing = True - self._clear_queue() + if self._ghidra_dbg.IS_REMOTE == False: + self._clear_queue() def _state_break(self): self._executing = False @@ -239,9 +241,25 @@ class GhidraDbg(object): setattr(self, name, self.eng_thread(getattr(base, name))) self.IS_KERNEL = False self.IS_EXDI = False + self.IS_REMOTE = os.getenv('OPT_CONNECT_STRING') is not None def _new_base(self): - self._protected_base = AllDbg() + remote = os.getenv('OPT_CONNECT_STRING') + if remote is not None: + remote_client = DbgEng.DebugConnect(remote) + debug_client = self._generate_client(remote_client) + self._protected_base = AllDbg(client=debug_client) + else: + self._protected_base = AllDbg() + + + def _generate_client(self, original): + cli = POINTER(DbgEng.IDebugClient)() + cliptr = POINTER(POINTER(DbgEng.IDebugClient))(cli) + hr = original.CreateClient(cliptr) + exception.check_err(hr) + return DebugClient(client=cli) + @property def _base(self): @@ -655,6 +673,9 @@ def GetExitCode(): return STILL_ACTIVE exit_code = c_ulong() hr = dbg._base._client._cli.GetExitCode(byref(exit_code)) + # DebugConnect targets return E_UNEXPECTED but the target is STILL_ACTIVE + if hr != S_OK and hr != S_FALSE: + return STILL_ACTIVE return exit_code.value @@ -915,8 +936,20 @@ 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 + + +def set_remote(value): + dbg.IS_REMOTE = value + + +def is_remote(): + return dbg.IS_REMOTE + + 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 4cbe6a17ae..a40d2eae05 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 @@ -843,23 +843,41 @@ python3 -m pip install --no-index -f Debugger-rmi-trace\pypkg\dist -f Debugger-aThis launcher extends the base dbgeng launcher adding an option for connecting through a - remote process server.
+This launcher connects to a remote debugger that has opened a port for remote control. +
-dbgsrv -t tcp:port=12345 +.server tcp:port=12345
This launcher extends the base dbgeng launcher adding an option for connecting through a + remote process server.
+ ++ dbgsrv -t tcp:port=12345 ++
This version of the dbgeng should be used for kernel-debugging of a remote machine. Options