Merge remote-tracking branch 'origin/GP-4732_d-millar_remote_dbgeng_RB250130--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-01-30 14:37:51 -05:00
commit c693e963eb
10 changed files with 272 additions and 67 deletions

View file

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

View file

@ -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)."

View file

@ -1,9 +1,8 @@
::@title dbgeng-remote
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Launch with <tt>dbgeng</tt> remotely (in a Python interpreter)</h3>
::@desc <h3>Connect to a remote debugger (via the .server interface)</h3>
::@desc <p>
::@desc This will launch the target on a remote machine using <tt>dbgeng.dll</tt>.
::@desc This will connect to a remote machine using the .server interface.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
@ -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

View file

@ -0,0 +1,23 @@
::@title dbgeng-svrcx
::@image-opt env:OPT_TARGET_IMG
::@desc <html><body width="300px">
::@desc <h3>Connect to a remote <tt>dbgeng</tt> connection server and launch the target (in a Python interpreter)</h3>
::@desc <p>
::@desc This will launch the target via a connection server on a remote machine.
::@desc For setup instructions, press <b>F1</b>.
::@desc </p>
::@desc </body></html>
::@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

View file

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

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

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

View file

@ -843,23 +843,41 @@ python3 -m pip install --no-index -f Debugger-rmi-trace\pypkg\dist -f Debugger-a
<H3><A name="dbgeng_remote"></A>dbgeng-remote</H3>
<P>This launcher extends the base dbgeng launcher adding an option for connecting through a
remote process server.</P>
<P>This launcher connects to a remote debugger that has opened a port for remote control.
</P>
<H4>Options</H4>
<UL>
<LI>
<B>Connection</B>: This is the connection string specifying the transport options for
communicating with the remote server. A typical example might be
'tcp:port=12345,server=192.168.0.2'' for a process server launched on the machine at
192.168.0.2 using:
communicating with the remote debugger. A typical example might be
'tcp:port=12345,server=192.168.0.2' for a debugger that has issued the command
<PRE>
dbgsrv -t tcp:port=12345
.server tcp:port=12345
</PRE>
</LI>
</UL>
<H3><A name="dbgeng_svrcx"></A>dbgeng-svrcx</H3>
<P>This launcher extends the base dbgeng launcher adding an option for connecting through a
remote process server.</P>
<H4>Options</H4>
<UL>
<LI>
<B>Connection</B>: This is the connection string specifying the transport options for
communicating with the remote server. A typical example might be
'tcp:port=12345,server=192.168.0.2' for a process server launched on the machine at
192.168.0.2 using:
<PRE>
dbgsrv -t tcp:port=12345
</PRE>
</LI>
</UL>
<H3><A name="dbgeng_kernel"></A>dbgeng-kernel</H3>
<P>This version of the dbgeng should be used for kernel-debugging of a remote machine. Options