From 1fab470a5b21c192e40bcf6df97323a509719d95 Mon Sep 17 00:00:00 2001 From: d-millar <33498836+d-millar@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:24:35 -0400 Subject: [PATCH] GP-4686: more edits GP-4686: formatting GP-4686: post-review fixes GP-4686: post-review fixes GP-4686: remote options GP-4686: remote options GP-4686: remote options GP-4686: post-review / readmem logic GP-4686: extended launch GP-4686: better desc for kernel GP-4686: aisle 9 GP-4686: basically working GP-4686: better display GP-4686: unnecessary? GP-4686: better attr display logic GP-4686: temp GP-4686: addresses for synthetics GP-4686: cleanup, minor errors, start on CreateProcess2 GP-4686: adding names & addresses GP-4686: print hell, but fixes TARGET_OBJECT GP-4686: first pass kernel stuff --- .../certification.manifest | 3 + .../data/debugger-launchers/kernel-dbgeng.bat | 20 ++ .../debugger-launchers/local-dbgeng-ext.bat | 26 +++ .../data/debugger-launchers/remote-dbgeng.bat | 22 ++ .../data/support/kernel-dbgeng.py | 67 ++++++ .../data/support/local-dbgeng-ext.py | 77 ++++++ .../data/support/remote-dbgeng.py | 73 ++++++ .../src/main/py/src/ghidradbg/arch.py | 12 +- .../src/main/py/src/ghidradbg/commands.py | 220 +++++++++++++----- .../py/src/ghidradbg/dbgmodel/DbgModel.idl | 7 + .../src/ghidradbg/dbgmodel/imodeliterator.py | 6 +- .../py/src/ghidradbg/dbgmodel/imodelobject.py | 137 ++++++++--- .../dbgmodel/istringdisplayableconcept.py | 37 +++ .../src/main/py/src/ghidradbg/hooks.py | 4 + .../src/main/py/src/ghidradbg/methods.py | 1 + .../src/main/py/src/ghidradbg/schema.xml | 22 +- .../src/main/py/src/ghidradbg/util.py | 146 +++++++++++- .../TraceRmiLauncherServicePlugin.html | 71 ++++++ 18 files changed, 845 insertions(+), 106 deletions(-) create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-ext.bat create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/remote-dbgeng.bat create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-ext.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-dbgeng.py create mode 100644 Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/istringdisplayableconcept.py diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest index f9799eecd2b..cbd16ecd1d8 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest +++ b/Ghidra/Debug/Debugger-agent-dbgeng/certification.manifest @@ -2,8 +2,11 @@ ##MODULE IP: Apache License 2.0 ##MODULE IP: MIT Module.manifest||GHIDRA||||END| +data/debugger-launchers/kernel-dbgeng.bat||GHIDRA||||END| +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| 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 new file mode 100644 index 00000000000..f34c4d4ba78 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/kernel-dbgeng.bat @@ -0,0 +1,20 @@ +::@title dbgeng-kernel +::@desc
+::@desc+::@desc This will connect the kernel debugger to a remote machine using dbgeng.dll. +::@desc For setup instructions, press F1. +::@desc
+::@desc +::@menu-group local +::@icon icon.debugger +::@help TraceRmiLauncherServicePlugin#dbgeng_kernel +::@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_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\kernel-dbgeng.py diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-ext.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-ext.bat new file mode 100644 index 00000000000..4bd01784e33 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng-ext.bat @@ -0,0 +1,26 @@ +::@title dbgeng-ext +::@desc +::@desc+::@desc This will launch the target on the local machine using dbgeng.dll. +::@desc For setup instructions, press F1. +::@desc
+::@desc +::@menu-group local +::@icon icon.debugger +::@help TraceRmiLauncherServicePlugin#dbgeng_ext +::@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:file="" "Image" "The target binary executable image" +::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target" +::@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)." +::@env OPT_TARGET_DIR:str="" "Dir" "Initial directory" +::@env OPT_TARGET_ENV:str="" "Env" "Environment variables (sep=/0)" +::@env OPT_CREATE_FLAGS:str="1" "Create flags" "Creation flags" +::@env OPT_CREATE_ENGFLAGS:str="0" "Create flags (Engine)" "Engine-specific creation flags" +::@env OPT_VERIFIER_FLAGS:str="0" "Verifier flags" "Verifier flags" + +@echo off + +"%OPT_PYTHON_EXE%" -i ..\support\local-dbgeng-ext.py 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 new file mode 100644 index 00000000000..ac6588971db --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/remote-dbgeng.bat @@ -0,0 +1,22 @@ +::@title dbgeng-remote +::@desc +::@desc+::@desc This will launch the target on a remote machine using dbgeng.dll. +::@desc For setup instructions, press F1. +::@desc
+::@desc +::@menu-group local +::@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:file="" "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\remote-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 new file mode 100644 index 00000000000..9a578564ee0 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/kernel-dbgeng.py @@ -0,0 +1,67 @@ +## ### +# 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') + cmd.ghidra_trace_attach_kernel(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/data/support/local-dbgeng-ext.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-ext.py new file mode 100644 index 00000000000..819902b2033 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/local-dbgeng-ext.py @@ -0,0 +1,77 @@ +## ### +# 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_create_ext( + os.getenv('OPT_TARGET_IMG') + args, + os.getenv('OPT_TARGET_DIR'), + os.getenv('OPT_TARGET_ENV'), + os.getenv('OPT_CREATE_FLAGS'), + os.getenv('OPT_CREATE_ENGFLAGS'), + os.getenv('OPT_VERIFIER_FLAGS'), + 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/data/support/remote-dbgeng.py b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-dbgeng.py new file mode 100644 index 00000000000..f207173f665 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/support/remote-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/arch.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/arch.py index aa2b15be90e..94c76ecaf52 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 @@ -204,14 +204,18 @@ def __init__(self, defaultSpace): self.defaultSpace = defaultSpace def map(self, proc: int, offset: int): - space = self.defaultSpace + if proc == 0: + space = self.defaultSpace + else: + space = f'{self.defaultSpace}{proc}' return self.defaultSpace, Address(space, offset) def map_back(self, proc: int, address: Address) -> int: - if address.space == self.defaultSpace: + if address.space == self.defaultSpace and proc == 0: return address.offset - raise ValueError( - f"Address {address} is not in process {proc.GetProcessID()}") + if address.space == f'{self.defaultSpace}{proc}': + return address.offset + raise ValueError(f"Address {address} is not in process {proc}") DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram') 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 4c369f34cab..171cfbf78b3 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 @@ -22,6 +22,7 @@ import sys import time +from comtypes import c_ulong from ghidratrace import sch from ghidratrace.client import Client, Address, AddressRange, TraceObject from pybag import pydbg, userdbg, kerneldbg @@ -31,7 +32,7 @@ from . import util, arch, methods, hooks from .dbgmodel.imodelobject import ModelObjectKind - +from .dbgeng.idebugclient5 import * PAGE_SIZE = 4096 @@ -68,10 +69,11 @@ class ErrorWithCode(Exception): + def __init__(self, code): self.code = code - def __str__(self)->str: + def __str__(self) -> str: return repr(self.code) @@ -216,6 +218,8 @@ def start_trace(name): with STATE.trace.open_tx("Create Root Object"): root = STATE.trace.create_root_object(schema_xml, 'DbgRoot') root.set_value('_display', util.DBG_VERSION.full + ' via pybag' + variant) + if util.dbg.use_generics: + put_generic(root) util.set_convenience_variable('_ghidra_tracing', "true") @@ -261,6 +265,67 @@ def ghidra_trace_create(command=None, initial_break=True, timeout=DbgEng.WAIT_IN ghidra_trace_start(command) +@util.dbg.eng_thread +def ghidra_trace_create_ext(command=None, initialDirectory='.', envVariables="\0\0", create_flags=1, create_flags_eng=0, verifier_flags=0, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True): + """ + Create a session. + """ + + dbg = util.dbg._base + if command != None: + if create_flags == "": + create_flags = 1 + if create_flags_eng == "": + create_flags_eng = 0 + if verifier_flags == "": + verifier_flags = 0 + options = DbgEng._DEBUG_CREATE_PROCESS_OPTIONS() + options.CreateFlags = c_ulong(int(create_flags)) + options.EngCreateFlags = c_ulong(int(create_flags_eng)) + options.VerifierFlags = c_ulong(int(verifier_flags)) + options.Reserved = c_ulong(int(0)) + if initialDirectory == "": + initialDirectory = None + if envVariables == "": + envVariables = None + if envVariables is not None and envVariables.endswith("/0/0") is False: + envVariables += "/0/0" + dbg._client.CreateProcess2(command, options, initialDirectory, envVariables) + if initial_break: + dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK) + if start_trace: + ghidra_trace_start(command) + + +@util.dbg.eng_thread +def ghidra_trace_attach_kernel(command=None, initial_break=True, timeout=DbgEng.WAIT_INFINITE, start_trace=True): + """ + Create a session. + """ + + dbg = util.dbg._base + util.set_kernel(True) + if initial_break: + dbg._control.AddEngineOptions(DbgEng.DEBUG_ENGINITIAL_BREAK) + if command != None: + dbg._client.AttachKernel(command) + if start_trace: + ghidra_trace_start(command) + + +@util.dbg.eng_thread +def ghidra_trace_connect_server(options=None): + """ + Connect to a process server session. + """ + + dbg = util.dbg._base + if options != None: + if isinstance(options, str): + enc_options = options.encode() + dbg._client.ConnectProcessServer(enc_options) + + @util.dbg.eng_thread def ghidra_trace_kill(): """ @@ -370,7 +435,7 @@ def ghidra_trace_set_snap(snap=None): def quantize_pages(start, end): - return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE) + return (start // PAGE_SIZE * PAGE_SIZE, (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE) @util.dbg.eng_thread @@ -453,7 +518,7 @@ def putmem_state(address, length, state, pages=True): nproc = util.selected_process() base, addr = STATE.trace.memory_mapper.map(nproc, start) if base != addr.space and state != 'unknown': - trace.create_overlay_space(base, addr.space) + STATE.trace.create_overlay_space(base, addr.space) STATE.trace.set_memory_state(addr.extend(end - start), state) @@ -718,6 +783,7 @@ def ghidra_trace_get_obj(path): class TableColumn(object): + def __init__(self, head): self.head = head self.contents = [head] @@ -735,6 +801,7 @@ def print_cell(self, i): class Tabular(object): + def __init__(self, heads): self.columns = [TableColumn(h) for h in heads] self.columns[-1].is_last = True @@ -925,7 +992,7 @@ def put_available(): ppath = AVAILABLE_PATTERN.format(pid=id) procobj = STATE.trace.create_object(ppath) keys.append(AVAILABLE_KEY_PATTERN.format(pid=id)) - pidstr = ('0x{:x}' if radix == + 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) @@ -1077,6 +1144,8 @@ def put_regions(): regions = util.dbg._base.memory_list() except Exception: regions = [] + if len(regions) == 0: + regions = util.full_mem() mapper = STATE.trace.memory_mapper keys = [] @@ -1087,15 +1156,17 @@ def put_regions(): regobj = STATE.trace.create_object(rpath) (start_base, start_addr) = map_address(r.BaseAddress) regobj.set_value('Range', start_addr.extend(r.RegionSize)) - regobj.set_value('_readable', r.Protect == + regobj.set_value('_readable', r.Protect == None or r.Protect & 0x66 != 0) - regobj.set_value('_writable', r.Protect == + regobj.set_value('_writable', r.Protect == None or r.Protect & 0xCC != 0) - regobj.set_value('_executable', r.Protect == + regobj.set_value('_executable', r.Protect == None or r.Protect & 0xF0 != 0) regobj.set_value('AllocationBase', hex(r.AllocationBase)) regobj.set_value('Protect', hex(r.Protect)) regobj.set_value('Type', hex(r.Type)) + if hasattr(r, 'Name') and r.Name is not None: + regobj.set_value('_display', r.Name) regobj.insert() STATE.trace.proxy_object_path( MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys) @@ -1296,37 +1367,44 @@ def ghidra_trace_put_frames(): put_frames() -def update_by_container(np, index, obj): +def update_by_container(np, keyval, obj): + index = keyval[0] + key = '' if np.endswith("Processes") or np.endswith("Threads"): istate = compute_proc_state(index) obj.set_value('State', istate) + if np.endswith("Sessions"): + key = '[{:x}]'.format(index) if np.endswith("Processes"): create_generic(obj.path) - id = util.get_proc_id(index) obj.set_value('PID', index) - obj.set_value('_display', '{:x} {:x}'.format(id, index)) + create_generic(obj.path + ".Memory") + if util.is_kernel(): + key = '[{:x}]'.format(index) + else: + id = util.get_proc_id(index) + key = '{:x} [{:x}]'.format(id, index) if np.endswith("Breakpoints"): create_generic(obj.path) - #id = util.get_thread_id(index) - #obj.set_value('TID', index) - #obj.set_value('_display','{:x} {:x}'.format(id, index)) if np.endswith("Threads"): create_generic(obj.path) - id = util.get_thread_id(index) obj.set_value('TID', index) - obj.set_value('_display', '{:x} {:x}'.format(id, index)) + if util.is_kernel(): + key = '[{:x}]'.format(index) + else: + id = util.get_thread_id(index) + key = '{:x} [{:x}]'.format(id, index) if np.endswith("Frames"): mo = util.get_object(obj.path) map = util.get_attributes(mo) attr = map["Attributes"] if attr is None: return - create_generic(obj.path+".Attributes") - map = util.get_attributes(attr) + map = util.get_attributes(attr) pc = util.get_value(map["InstructionOffset"]) (pc_base, pc_addr) = map_address(pc) obj.set_value('Instruction Offset', pc_addr) - obj.set_value('_display', '#{:x} 0x{:x}'.format(index, pc)) + key = '#{:x} 0x{:x}'.format(index, pc) if np.endswith("Modules"): create_generic(obj.path) mo = util.get_object(obj.path) @@ -1337,7 +1415,12 @@ def update_by_container(np, index, obj): obj.set_value('Name', '{}'.format(name)) (base_base, base_addr) = map_address(base) obj.set_value('Range', base_addr.extend(size)) - obj.set_value('_display', '{:x} {:x} {}'.format(index, base, name)) + key = '{:x} {:x} {}'.format(index, base, name) + disp = util.to_display_string(keyval[1]) + if disp is not None: + key += " " + disp + if key is not None and key != "": + obj.set_value('_display', key) def create_generic(path): @@ -1348,58 +1431,81 @@ def create_generic(path): def put_generic(node): - #print(f"put_generic: {node}") + # print(f"put_generic: {node}") nproc = util.selected_process() if nproc is None: return nthrd = util.selected_thread() - mapper = STATE.trace.memory_mapper mo = util.get_object(node.path) - kind = util.get_kind(mo) - type = util.get_type(mo) - vstr = util.get_value(mo) - # print(f"MO={mo}") + mapper = STATE.trace.register_mapper + attributes = util.get_attributes(mo) # print(f"ATTR={attributes}") - mapper = STATE.trace.register_mapper values = [] - for key, value in attributes.items(): - if value is None: - continue - kind = util.get_kind(value) - vstr = util.get_value(value) - #print(f"key={key} kind={kind} value={vstr} type={type}") - if kind == ModelObjectKind.PROPERTY_ACCESSOR.value or \ - kind == ModelObjectKind.SYNTHETIC.value or \ - kind == ModelObjectKind.METHOD.value: - if vstr is not None: - key += " : " + vstr - apath = node.path+'.'+key - aobj = STATE.trace.create_object(apath) - aobj.insert() - else: - try: - if node.path.endswith('.User'): - values.append(mapper.map_value(nproc, key, vstr)) - node.set_value(key, hex(vstr)) - except Exception as e: - pass # Error is printed by another mechanism + if attributes is not None: + for key, value in attributes.items(): + kind = util.get_kind(value) + if kind == ModelObjectKind.METHOD.value: + continue + # print(f"key={key} kind={kind}") + if kind != ModelObjectKind.INTRINSIC.value: + apath = node.path + '.' + key + aobj = STATE.trace.create_object(apath) + set_display(key, value, aobj) + aobj.insert() + else: + val = util.get_value(value) + try: + if node.path.endswith('.User'): + # print(f"PUT_REG: {key} {val}") + values.append(mapper.map_value(nproc, key, val)) + node.set_value(key, hex(val)) + elif isinstance(val, int): + (v_base, v_addr) = map_address(val) + node.set_value(key, v_addr, schema="ADDRESS") + else: + node.set_value(key, val) + except Exception as e: + print(f"Attribute exception for {key} {type(val)}: {e}") elements = util.get_elements(mo) # print(f"ELEM={elements}") keys = [] - for el in elements: - index = el[0] - key = GENERIC_KEY_PATTERN.format(key=index) - lpath = node.path+key - lobj = STATE.trace.create_object(lpath) - update_by_container(node.path, index, lobj) - lobj.insert() - keys.append(key) - node.retain_values(keys) + if elements is not None: + for el in elements: + index = el[0] + key = GENERIC_KEY_PATTERN.format(key=index) + lpath = node.path + key + lobj = STATE.trace.create_object(lpath) + update_by_container(node.path, el, lobj) + lobj.insert() + keys.append(key) + node.retain_values(keys) return (values, keys) +def set_display(key, value, obj): + kind = util.get_kind(value) + vstr = util.get_value(value) + # istr = util.get_intrinsic_value(value) + if kind == ModelObjectKind.TARGET_OBJECT.value: + hloc = util.get_location(value) + ti = util.get_type_info(value) + if ti is not None: + name = util.get_name(ti) + if name is not None: + key += " : " + name + obj.set_value('_display', key) + if hloc is not None: + key += " @ " + str(hloc) + obj.set_value('_display', key) + (hloc_base, hloc_addr) = map_address(int(hloc,0)) + obj.set_value('_address', hloc_addr, schema=Address) + if vstr is not None: + key += " : " + str(vstr) + obj.set_value('_display', key) + + def map_address(address): nproc = util.selected_process() mapper = STATE.trace.memory_mapper diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl index eea67cdce2b..50f0a9fdc59 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/DbgModel.idl @@ -155,6 +155,13 @@ library DbgMod DWORD NotSupported; }; + typedef struct _DEBUG_CREATE_PROCESS_OPTIONS { + ULONG CreateFlags; + ULONG EngCreateFlags; + ULONG VerfierFlags; + ULONG Reserved; + } DEBUG_CREATE_PROCESS_OPTIONS, *PDEBUG_CREATE_PROCESS_OPTIONS; + /* struct _MEMORY_BASIC_INFORMATION64 { ULONGLONG BaseAddress; diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py index 4d2c8376b55..c5bf8451e8e 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodeliterator.py @@ -40,8 +40,10 @@ def GetNext(self, dimensions): except COMError as ce: return None index = mo.ModelObject(indexer) - id = index.GetIntrinsicValue().value - return (id, mo.ModelObject(object)) + ival = index.GetIntrinsicValue() + if ival is None: + return (0, mo.ModelObject(object)) + return (ival.value, mo.ModelObject(object)) def Reset(self): hr = self._keys.Reset() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelobject.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelobject.py index f0abda7ca1a..9758306eb56 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelobject.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/imodelobject.py @@ -22,10 +22,13 @@ from comtypes.hresult import S_OK, S_FALSE from pybag.dbgeng import exception +from comtypes import BSTR from comtypes.gen.DbgMod import * from .iiterableconcept import IterableConcept +from .istringdisplayableconcept import StringDisplayableConcept from .ikeyenumerator import KeyEnumerator +from .irawenumerator import RawEnumerator class ModelObjectKind(Enum): @@ -45,6 +48,7 @@ class ModelObject(object): def __init__(self, obj): self._obj = obj self.concept = None + self.dconcept = None exception.wrap_comclass(self._obj) def Release(self): @@ -96,12 +100,16 @@ def EnumerateRawValues(self, kind, searchFlag): return RawEnumerator(keys, kind) def GetConcept(self, ref): - ifc = POINTER(IUnknown)() - metadata = POINTER(DbgMod.IKeyStore)() - hr = self._obj.GetConcept(ref._iid_, byref(ifc), byref(metadata)) - if hr != S_OK: + try: + ifc = POINTER(IUnknown)() + metadata = POINTER(DbgMod.IKeyStore)() + hr = self._obj.GetConcept(ref._iid_, byref(ifc), byref(metadata)) + if hr != S_OK: + return None + return cast(ifc, POINTER(ref)) + except Exception as e: + print(f"GetConcept exception: {e}") return None - return cast(ifc, POINTER(ref)) def GetContext(self, context): raise exception.E_NOTIMPL_Error @@ -111,10 +119,14 @@ def GetContextForDataModel(self, dataModelObject, context): def GetIntrinsicValue(self): var = VARIANT() - hr = self._obj.GetIntrinsicValue(var) - if hr != S_OK: + try: + hr = self._obj.GetIntrinsicValue(var) + if hr != S_OK: + return None + return var + except Exception as e: + print(f"GetIntrinsicValue exception: {e}") return None - return var def GetIntrinsicValueAs(self, vt): raise exception.E_NOTIMPL_Error @@ -140,8 +152,31 @@ def GetKind(self): exception.check_err(hr) return kind - def GetLocation(self, location): - raise exception.E_NOTIMPL_Error + # DOESN"T WORK YET + # def GetTypeKind(self): + # typeKind = None + # modelKind = self.GetKind() + # if modelKind.value == ModelObjectKind.TARGET_OBJECT.value: + # targetInfo = self.GetTargetInfo() + # if targetInfo is not None: + # typeKind = DbgMod.tagTYPEKIND() + # hr = targetInfo._obj.GetTypeKind(byref(typeKind)) + # if hr != S_OK: + # return None + # if modelKind.value == ModelObjectKind.INTRINSIC.value: + # typeInfo = self.GetTypeInfo() + # if typeInfo is not None: + # typeKind = DbgMod.tagTYPEKIND() + # hr = typeInfo._obj.GetTypeKind(byref(typeKind)) + # if hr != S_OK: + # return None + # return typeKind + + def GetLocation(self): + loc = DbgMod._Location() + hr = self._obj.GetLocation(loc) + exception.check_err(hr) + return loc def GetNumberOfParentModels(self, numModels): raise exception.E_NOTIMPL_Error @@ -152,18 +187,55 @@ def GetParentModel(self, i, model, context): def GetRawReference(self, kind, name, searchFlags, object): raise exception.E_NOTIMPL_Error - def GetRawValue(self, kind, name, searchFlags, object): - raise exception.E_NOTIMPL_Error + def GetRawValue(self, kind, name, searchFlags): + kbuf = cast(c_wchar_p(name), POINTER(c_ushort)) + value = POINTER(DbgMod.IModelObject)() + hr = self._obj.GetRawValue(kind, kbuf, searchFlags, byref(value)) + if hr != S_OK: + return None + return ModelObject(value) def GetTargetInfo(self): - location = POINTER(DbgMod._Location)() + location = DbgMod._Location() type = POINTER(DbgMod.IDebugHostType)() hr = self._obj.GetTargetInfo(location, byref(type)) exception.check_err(hr) - return type + return ModelObject(type) - def GetTypeInfo(self, type): - raise exception.E_NOTIMPL_Error + def GetTypeInfo(self): + type = POINTER(DbgMod.IDebugHostType)() + hr = self._obj.GetTypeInfo(byref(type)) + exception.check_err(hr) + return ModelObject(type) + + def GetName(self): + name = BSTR() + hr = self._obj.GetName(name) + exception.check_err(hr) + return name + + def ToDisplayString(self): + if self.dconcept is None: + dconcept = self.GetConcept(DbgMod.IStringDisplayableConcept) + if dconcept is None: + return None + self.dconcept = StringDisplayableConcept(dconcept) + return self.dconcept.ToDisplayString(self) + + # This does NOT work - returns a null pointer for value. Why? + # One possibility: casting is not a valid way to obtain an IModelMethod + # + # def ToDisplayString0(self): + # map = self.GetAttributes() + # method = map["ToDisplayString"] + # mm = cast(method._obj, POINTER(DbgMod.IModelMethod)) + # context = self._obj + # args = POINTER(DbgMod.IModelObject)() + # value = POINTER(DbgMod.IModelObject)() + # meta = POINTER(DbgMod.IKeyStore)() + # hr = mm.Call(context, c_ulonglong(0), args, byref(value), byref(meta)) + # exception.check_err(hr) + # return ModelObject(value) def IsEqualTo(self, other, equal): raise exception.E_NOTIMPL_Error @@ -198,23 +270,28 @@ def GetKeyValueMap(self): return map def GetRawValueMap(self): + # print(f"GetRawValueMap: {self}") map = {} kind = self.GetKind() - keys = self.EnumerateRawValues(kind, c_long(0)) + # TODO: forcing kind to 0 because we can't GetTypeKind + keys = self.EnumerateRawValues(c_long(0), 0) (k, v) = keys.GetNext() while k is not None: map[k.value] = v (k, v) = keys.GetNext() + # print(f"{k}:{v}") return map def GetAttributes(self): map = {} kind = self.GetKind() - if kind == ModelObjectKind.ERROR: + # print(f"GetAttributes: {kind}") + if kind is not None and kind.value == ModelObjectKind.ERROR.value: + print(f"ERROR from GetAttributes") return map - if kind == ModelObjectKind.INTRINSIC or \ - kind == ModelObjectKind.TARGET_OBJECT or \ - kind == ModelObjectKind.TARGET_OBJECT_REFERENCE: + if kind.value == ModelObjectKind.INTRINSIC.value or \ + kind.value == ModelObjectKind.TARGET_OBJECT.value or \ + kind.value == ModelObjectKind.TARGET_OBJECT_REFERENCE.value: return self.GetRawValueMap() return self.GetKeyValueMap() @@ -245,6 +322,9 @@ def GetElement(self, key): def GetOffspring(self, path): next = self for element in path: + if next is None: + return None + kind = next.GetKind() if element.startswith("["): idx = element[1:len(element)-1] if "x" not in idx: @@ -252,12 +332,17 @@ def GetOffspring(self, path): else: idx = int(idx, 16) next = next.GetElement(idx) + # THIS IS RELATIVELY HORRIBLE - replace with GetRawValue? + elif kind is not None and kind.value == ModelObjectKind.TARGET_OBJECT.value: + map = next.GetAttributes() + next = map[element] else: next = next.GetKeyValue(element) - if next is None: - print(f"{element} not found") + #if next is None: + # print(f"{element} not found") return next + def GetValue(self): value = self.GetIntrinsicValue() if value is None: @@ -266,9 +351,3 @@ def GetValue(self): return None return value.value - def GetTypeKind(self): - kind = self.GetKind() - if kind == ModelObjectKind.TARGET_OBJECT or \ - kind == ModelObjectKind.INTRINSIC: - return self.GetTargetInfo() - return None diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/istringdisplayableconcept.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/istringdisplayableconcept.py new file mode 100644 index 00000000000..1dd8abed499 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/dbgmodel/istringdisplayableconcept.py @@ -0,0 +1,37 @@ +## ### +# 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 ctypes import * + +from comtypes import BSTR, COMError +from comtypes.gen import DbgMod +from comtypes.hresult import S_OK, S_FALSE +from pybag.dbgeng import exception + + +class StringDisplayableConcept(object): + def __init__(self, concept): + self._concept = concept + concept.AddRef() + + # StringDisplayableConcept + + def ToDisplayString(self, context): + try: + val = BSTR() + self._concept.ToDisplayString(context._obj, None, byref(val)) + except COMError as ce: + return None + return val.value 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 5f23c243c09..15ad6995fe8 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 @@ -64,8 +64,11 @@ def record(self, description=None): if description is not None: commands.STATE.trace.snapshot(description) if first: + if util.is_kernel(): + commands.create_generic("Sessions") commands.put_processes() commands.put_environment() + commands.put_threads() if self.threads: commands.put_threads() self.threads = False @@ -98,6 +101,7 @@ def record_continued(self): commands.put_threads(running=True) def record_exited(self, exit_code, description=None): + # print("RECORD_EXITED") if description is not None: commands.STATE.trace.snapshot(description) proc = util.selected_process() diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py index af20e448114..1661e5220e0 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py @@ -536,6 +536,7 @@ def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')): @REGISTRY.method +@util.dbg.eng_thread def read_mem(process: sch.Schema('Process'), range: AddressRange): """Read memory.""" # print("READ_MEM: process={}, range={}".format(process, range)) diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml index 1d40aeb01f4..be20b61eddb 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/schema.xml @@ -8,13 +8,13 @@This launcher extends the base dbgeng launcher adding extra options (a la IDebugClient's CreateProcess2).
+ + +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 are the + same as the base dbgeng, except for the connection-string arguments. For remote + debugging, the target machine should be booted with the appropriate options, set using BCDEDIT or the + equivalent, such as: +
++bcdedit /debug ON +bdcedit /dbgsettings NET HOSTIP:IP PORT:54321 KEY:1.1.1.1 ++
+ where IP= the address of the machine runing Ghidra. +
+ +This is a nascent extension to our launcher for the Windows Debugger. The launcher itself