GP-326: never say die

GP-326: recompiling to htmnl
GP-326: recompiling to htmnl
GP-326: last?
GP-326: getting there
GP-326: roll along
GP-326: rolling along
GP-326: test fix
GP-326: miscellaneous post-review fixes
GP-326: complicated stuff
GP-326: more simple stuff
GP-326: navhead fix
GP-326: better docs
GP-326: html for md
GP-326: html for md
GP-326: tutorial edits
GP-326: tutorial edits
GP-326: re-arranging docs
GP-326: from review
GP-326: adding a debugger
GP-326: docs
GP-326: using TestResources - tests pass
GP-326: working tests
GP-326: most cmd/meth tests working
GP-326: cmd tests pass
GP-326: passes thru putmem
GP-326: one test running
GP-326: better startup logic
GP-326: first pass tests
GP-326: misc cleanup
GP-326: cleaner startup
GP-326: cleanup
GP-326: fixes for crash dump
GP-326: util cleanup
GP-326: objects cont.
GP-326: first pass at objects
GP-326: some cleanup
GP-326: regions
GP-326: sections
GP-326: modules
GP-326: alt launchers
GP-326: symbols
GP-326: memory
GP-326: stack frame - regs + locals
GP-326: frames
GP-326: threads
GP-326: better start sequence
GP-326: working launcher
GP-326: util.version
GP-326: arch
This commit is contained in:
d-millar
2025-01-08 13:16:34 -05:00
parent 860c754484
commit d5df1c16bb
28 changed files with 4816 additions and 1 deletions

View File

@@ -0,0 +1 @@
# Debugger-agent-drgn

View File

@@ -0,0 +1,20 @@
/* ###
* 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.
*/
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/hasPythonPackage.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-agent-drgn'

View File

@@ -0,0 +1,11 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
##MODULE IP: Apache License 2.0 with LLVM Exceptions
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
build.gradle||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END|
src/main/py/MANIFEST.in||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END|
src/main/py/src/ghidradrgn/schema.xml||GHIDRA||||END|

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
## ###
# 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.
##
#@title drgn-core
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>drgn-core</tt></h3>
#@desc <p>
#@desc This will attach to an existing core dump using <tt>drgn</tt>.
#@desc For setup instructions, press <b>F1</b>.
#@desc </p>
#@desc </body></html>
#@menu-group drgn
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#drgn-core
#@env OPT_TARGET_IMG:file!="" "Core dump" "The target core dump"
export OPT_TARGET_KIND="coredump"
drgn -c "$OPT_TARGET_IMG" ../support/local-drgn.py

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
## ###
# 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.
##
#@title drgn-kernel
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>drgn-kernel</tt></h3>
#@desc <p>
#@desc This will attach to the local machine's kernel using <tt>drgn</tt>.
#@desc For setup instructions, press <b>F1</b>.
#@desc </p>
#@desc </body></html>
#@menu-group drgn
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#drgn-kernel
export OPT_TARGET_KIND="kernel"
sudo -E drgn ../support/local-drgn.py

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
## ###
# 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.
##
#@title drgn
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>drgn</tt></h3>
#@desc <p>
#@desc This will attach to a target running on the local machine using <tt>drgn</tt>.
#@desc For setup instructions, press <b>F1</b>.
#@desc </p>
#@desc </body></html>
#@menu-group drgn
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#drgn
#@env OPT_TARGET_PID:int=44068 "PID" "The target's process id"
export OPT_TARGET_KIND="user"
# sudo -E drgn -p "$OPT_TARGET_PID" ../support/local-drgn.py
# or 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
drgn -p "$OPT_TARGET_PID" ../support/local-drgn.py

View File

@@ -0,0 +1,57 @@
## ###
# 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 drgn:
# EASY-INSTALL-ENTRY-SCRIPT: 'drgn==0.0.24','console_scripts','drgn'
import os
import re
import sys
import drgn.cli
home = os.getenv('GHIDRA_HOME')
if os.path.isdir(f'{home}/ghidra/.git'):
sys.path.append(
f'{home}/ghidra/Ghidra/Debug/Debugger-agent-drgn/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-drgn/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-drgn/pypkg/src')
sys.path.append(f'{home}/Ghidra/Debug/Debugger-rmi-trace/pypkg/src')
def main():
from ghidradrgn import commands as cmd
cmd.ghidra_trace_connect(address=os.getenv('GHIDRA_TRACE_RMI_ADDR'))
cmd.ghidra_trace_create(start_trace=True)
cmd.ghidra_trace_txstart()
cmd.ghidra_trace_put_all()
cmd.ghidra_trace_txcommit()
cmd.ghidra_trace_activate()
drgn.cli.run_interactive(cmd.prog)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,11 @@
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.

View File

@@ -0,0 +1 @@
include src/ghidradrgn/schema.xml

View File

@@ -0,0 +1,3 @@
# Ghidra Trace RMI for drgn
Package for connecting drgn to Ghidra via Trace RMI.

View File

@@ -0,0 +1,25 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "ghidradrgn"
version = "11.3"
authors = [
{ name="Ghidra Development Team" },
]
description = "Ghidra's Plugin for drgn"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
]
dependencies = [
"ghidratrace==11.3",
]
[project.urls]
"Homepage" = "https://github.com/NationalSecurityAgency/ghidra"
"Bug Tracker" = "https://github.com/NationalSecurityAgency/ghidra/issues"

View File

@@ -0,0 +1,16 @@
## ###
# 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 . import util, commands

View File

@@ -0,0 +1,209 @@
## ###
# 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.client import Address, RegVal
import drgn
from . import util
# NOTE: This map is derived from the ldefs using a script
language_map = {
'AARCH64': ['AARCH64:BE:64:v8A', 'AARCH64:LE:64:AppleSilicon', 'AARCH64:LE:64:v8A'],
'ARM': ['ARM:BE:32:v8', 'ARM:BE:32:v8T', 'ARM:LE:32:v8', 'ARM:LE:32:v8T'],
'PPC64': ['PowerPC:BE:64:4xx', 'PowerPC:LE:64:4xx'],
'S390': [],
'S390X': [],
'I386': ['x86:LE:32:default'],
'X86_64': ['x86:LE:64:default'],
'UNKNOWN': ['DATA:LE:64:default', 'DATA:LE:64:default'],
}
data64_compiler_map = {
None: 'pointer64',
}
default_compiler_map = {
'Language.C': 'default',
}
x86_compiler_map = {
'Language.C': 'gcc',
}
compiler_map = {
'DATA:BE:64:': data64_compiler_map,
'DATA:LE:64:': data64_compiler_map,
'x86:LE:32:': x86_compiler_map,
'x86:LE:64:': x86_compiler_map,
'AARCH64:LE:64:': default_compiler_map,
'ARM:BE:32:': default_compiler_map,
'ARM:LE:32:': default_compiler_map,
'PowerPC:BE:64:': default_compiler_map,
'PowerPC:LE:64:': default_compiler_map,
}
def get_arch():
platform = drgn.host_platform
return platform.arch.name
def get_endian():
parm = util.get_convenience_variable('endian')
if parm != 'auto':
return parm
platform = drgn.host_platform
order = platform.flags.IS_LITTLE_ENDIAN
if order.value > 0:
return 'little'
else:
return 'big'
def get_size():
parm = util.get_convenience_variable('size')
if parm != 'auto':
return parm
platform = drgn.host_platform
order = platform.flags.IS_64_BIT
if order.value > 0:
return '64'
else:
return '32'
def get_osabi():
return "Language.C"
def compute_ghidra_language():
# First, check if the parameter is set
lang = util.get_convenience_variable('ghidra-language')
if lang != 'auto':
return lang
# Get the list of possible languages for the arch. We'll need to sift
# through them by endian and probably prefer default/simpler variants. The
# heuristic for "simpler" will be 'default' then shortest variant id.
arch = get_arch()
endian = get_endian()
sz = get_size()
lebe = ':BE:' if endian == 'big' else ':LE:'
if not arch in language_map:
return 'DATA' + lebe + sz +':default'
langs = language_map[arch]
matched_endian = sorted(
(l for l in langs if lebe in l),
key=lambda l: 0 if l.endswith(':default') else len(l)
)
if len(matched_endian) > 0:
return matched_endian[0]
# NOTE: I'm disinclined to fall back to a language match with wrong endian.
return 'DATA' + lebe + sz + ':default'
def compute_ghidra_compiler(lang):
# First, check if the parameter is set
comp = util.get_convenience_variable('ghidra-compiler')
if comp != 'auto':
return comp
# Check if the selected lang has specific compiler recommendations
matched_lang = sorted(
(l for l in compiler_map if l in lang),
# key=lambda l: compiler_map[l]
)
if len(matched_lang) == 0:
print(f"{lang} not found in compiler map - using default compiler")
return 'default'
comp_map = compiler_map[matched_lang[0]]
if comp_map == data64_compiler_map:
print(f"Using the DATA64 compiler map")
osabi = get_osabi()
if osabi in comp_map:
return comp_map[osabi]
if lang.startswith("X86:"):
print(f"{osabi} not found in compiler map - using gcc")
return 'gcc'
if None in comp_map:
return comp_map[None]
print(f"{osabi} not found in compiler map - using default compiler")
return 'default'
def compute_ghidra_lcsp():
lang = compute_ghidra_language()
comp = compute_ghidra_compiler(lang)
return lang, comp
class DefaultMemoryMapper(object):
def __init__(self, defaultSpace):
self.defaultSpace = defaultSpace
def map(self, proc: drgn.Program, offset: int):
space = self.defaultSpace
return self.defaultSpace, Address(space, offset)
def map_back(self, proc: drgn.Program, address: Address) -> int:
if address.space == self.defaultSpace:
return address.offset
raise ValueError(
f"Address {address} is not in process {proc}")
DEFAULT_MEMORY_MAPPER = DefaultMemoryMapper('ram')
memory_mappers = {}
def compute_memory_mapper(lang):
if not lang in memory_mappers:
return DEFAULT_MEMORY_MAPPER
return memory_mappers[lang]
class DefaultRegisterMapper(object):
def __init__(self, byte_order):
if not byte_order in ['big', 'little']:
raise ValueError("Invalid byte_order: {}".format(byte_order))
self.byte_order = byte_order
self.union_winners = {}
def map_name(self, proc, name):
return name
def map_value(self, proc, name, value):
return RegVal(self.map_name(proc, name), value)
def map_name_back(self, proc, name):
return name
def map_value_back(self, proc, name, value):
return RegVal(self.map_name_back(proc, name), value)
DEFAULT_BE_REGISTER_MAPPER = DefaultRegisterMapper('big')
DEFAULT_LE_REGISTER_MAPPER = DefaultRegisterMapper('little')
def compute_register_mapper(lang):
if ':BE:' in lang:
return DEFAULT_BE_REGISTER_MAPPER
else:
return DEFAULT_LE_REGISTER_MAPPER

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,249 @@
## ###
# 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 threading
import time
import drgn
from . import commands, util
ALL_EVENTS = 0xFFFF
class HookState(object):
__slots__ = ('installed', 'mem_catchpoint')
def __init__(self):
self.installed = False
self.mem_catchpoint = None
class ProcessState(object):
__slots__ = ('first', 'regions', 'modules', 'threads',
'breaks', 'watches', 'visited')
def __init__(self):
self.first = True
# For things we can detect changes to between stops
self.regions = False
self.modules = False
self.threads = False
self.breaks = False
self.watches = False
# For frames and threads that have already been synced since last stop
self.visited = set()
def record(self, description=None):
first = self.first
self.first = False
if description is not None:
commands.STATE.trace.snapshot(description)
if first:
commands.put_processes()
commands.put_environment()
if self.threads:
commands.put_threads()
self.threads = False
nthrd = util.selected_thread()
if nthrd is not None:
if first or nthrd not in self.visited:
commands.put_frames()
self.visited.add(nthrd)
level = util.selected_frame()
hashable_frame = (nthrd, level)
if first or hashable_frame not in self.visited:
commands.putreg()
try:
commands.putmem(commands.get_pc(), 1, True, True)
except BaseException as e:
print(f"Couldn't record page with PC: {e}")
try:
commands.putmem(commands.get_sp(), 1, True, True)
except BaseException as e:
print(f"Couldn't record page with SP: {e}")
self.visited.add(hashable_frame)
if first or self.regions or self.modules:
# Sections, memory syscalls, or stack allocations
commands.put_regions()
self.regions = False
if first or self.modules:
commands.put_modules()
self.modules = False
def record_continued(self):
commands.put_processes()
commands.put_threads()
def record_exited(self, exit_code):
nproc = util.selected_process()
ipath = commands.PROCESS_PATTERN.format(procnum=nproc)
procobj = commands.STATE.trace.proxy_object_path(ipath)
procobj.set_value('Exit Code', exit_code)
procobj.set_value('State', 'TERMINATED')
HOOK_STATE = HookState()
PROC_STATE = {}
def on_new_process(event):
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("New Process {}".format(event.process.num)):
commands.put_processes() # TODO: Could put just the one....
def on_process_selected():
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Process {} selected".format(nproc)):
PROC_STATE[nproc].record()
commands.activate()
def on_new_thread(event):
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
PROC_STATE[nproc].threads = True
def on_thread_selected():
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
nthrd = util.selected_thread()
with commands.STATE.client.batch():
with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
PROC_STATE[nproc].record()
commands.put_threads()
commands.activate()
def on_frame_selected():
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
nthrd = util.selected_thread()
level = util.selected_frame()
with commands.STATE.client.batch():
with trace.open_tx("Frame {}.{}.{} selected".format(nproc, nthrd, level)):
PROC_STATE[nproc].record()
commands.put_threads()
commands.put_frames()
commands.activate()
def on_memory_changed(event):
nproc = util.get_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Memory *0x{:08x} changed".format(event.address)):
commands.put_bytes(event.address, event.address + event.length,
pages=False, is_mi=False, result=None)
def on_register_changed(event):
nproc = util.get_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
with commands.STATE.client.batch():
with trace.open_tx("Register {} changed".format(event.regnum)):
commands.putreg()
def on_cont(event):
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
trace = commands.STATE.trace
if trace is None:
return
state = PROC_STATE[nproc]
with commands.STATE.client.batch():
with trace.open_tx("Continued"):
state.record_continued()
def on_stop(event):
nproc = util.selected_process()
if nproc not in PROC_STATE:
PROC_STATE[nproc] = ProcessState()
trace = commands.STATE.trace
if trace is None:
print("no trace")
return
state = PROC_STATE[nproc]
state.visited.clear()
with commands.STATE.client.batch():
with trace.open_tx("Stopped"):
state.record("Stopped")
commands.put_threads()
commands.put_frames()
commands.activate()
def modules_changed():
nproc = util.selected_process()
if nproc not in PROC_STATE:
return
PROC_STATE[nproc].modules = True
def install_hooks():
if HOOK_STATE.installed:
return
HOOK_STATE.installed = True
event_thread = EventThread()
event_thread.start()
def remove_hooks():
if not HOOK_STATE.installed:
return
HOOK_STATE.installed = False
def enable_current_process():
nproc = util.selected_process()
PROC_STATE[nproc] = ProcessState()
def disable_current_process():
nproc = util.selected_process()
if nproc in PROC_STATE:
del PROC_STATE[nproc]

View File

@@ -0,0 +1,388 @@
## ###
# 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 concurrent.futures import Future, ThreadPoolExecutor
from contextlib import redirect_stdout
from io import StringIO
import re
import sys
import time
import drgn
import drgn.cli
from ghidratrace import sch
from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange
from . import util, commands, hooks
REGISTRY = MethodRegistry(ThreadPoolExecutor(
max_workers=1, thread_name_prefix='MethodRegistry'))
def extre(base, ext):
return re.compile(base.pattern + ext)
PROCESSES_PATTERN = re.compile('Processes')
PROCESS_PATTERN = extre(PROCESSES_PATTERN, '\[(?P<procnum>\\d*)\]')
ENV_PATTERN = extre(PROCESS_PATTERN, '\.Environment')
THREADS_PATTERN = extre(PROCESS_PATTERN, '\.Threads')
THREAD_PATTERN = extre(THREADS_PATTERN, '\[(?P<tnum>\\d*)\]')
STACK_PATTERN = extre(THREAD_PATTERN, '\.Stack')
FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
LOCALS_PATTERN = extre(FRAME_PATTERN, '.Locals')
MEMORY_PATTERN = extre(PROCESS_PATTERN, '\.Memory')
MODULES_PATTERN = extre(PROCESS_PATTERN, '\.Modules')
MODULE_PATTERN = extre(MODULES_PATTERN, '\[(?P<modbase>.*)\]')
def find_availpid_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['pid'])
return pid
def find_availpid_by_obj(object):
return find_availpid_by_pattern(AVAILABLE_PATTERN, object, "an Available")
def find_proc_by_num(id):
if id != util.selected_process():
util.select_process(id)
return util.selected_process()
def find_proc_by_pattern(object, pattern, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
procnum = int(mat['procnum'])
return find_proc_by_num(procnum)
def find_proc_by_obj(object):
return find_proc_by_pattern(object, PROCESS_PATTERN, "an Process")
def find_proc_by_env_obj(object):
return find_proc_by_pattern(object, ENV_PATTERN, "an Environment")
def find_proc_by_threads_obj(object):
return find_proc_by_pattern(object, THREADS_PATTERN, "a ThreadContainer")
def find_proc_by_mem_obj(object):
return find_proc_by_pattern(object, MEMORY_PATTERN, "a Memory")
def find_proc_by_modules_obj(object):
return find_proc_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
def find_thread_by_num(id):
if id != util.selected_thread():
util.select_thread(id)
return util.selected_thread()
def find_thread_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
find_proc_by_num(pnum)
return find_thread_by_num(tnum)
def find_thread_by_obj(object):
return find_thread_by_pattern(THREAD_PATTERN, object, "a Thread")
def find_thread_by_stack_obj(object):
return find_thread_by_pattern(STACK_PATTERN, object, "a Stack")
def find_thread_by_regs_obj(object):
return find_thread_by_pattern(REGS_PATTERN, object, "a RegisterValueContainer")
def find_frame_by_level(level):
tnum = util.selected_thread()
thread = commands.prog.thread(tnum)
try:
frames = thread.stack_trace()
except Exception as e:
print(e)
return
for i,f in enumerate(frames):
if i == level:
if i != util.selected_frame():
util.select_frame(i)
return i,f
def find_frame_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
tnum = int(mat['tnum'])
level = int(mat['level'])
find_proc_by_num(pnum)
find_thread_by_num(tnum)
return find_frame_by_level(level)
def find_frame_by_obj(object):
return find_frame_by_pattern(FRAME_PATTERN, object, "a StackFrame")
def find_frame_by_regs_obj(object):
return find_frame_by_pattern(REGS_PATTERN, object, "a RegisterValueContainer")
def find_frame_by_locals_obj(object):
return find_frame_by_pattern(LOCALS_PATTERN, object, "a LocalsContainer")
def find_module_by_base(modbase):
for m in commands.prog.modules():
if modbase == str(hex(m.address_range[0])):
return m
def find_module_by_pattern(pattern, object, err_msg):
mat = pattern.fullmatch(object.path)
if mat is None:
raise TypeError(f"{object} is not {err_msg}")
pnum = int(mat['procnum'])
modbase = mat['modbase']
find_proc_by_num(pnum)
return find_module_by_base(modbase)
def find_module_by_obj(object):
return find_module_by_pattern(MODULE_PATTERN, object, "a Module")
shared_globals = dict()
@REGISTRY.method
def execute(cmd: str, to_string: bool=False):
"""Execute a Python3 command or script."""
if to_string:
data = StringIO()
with redirect_stdout(data):
exec(cmd, shared_globals)
return data.getvalue()
else:
exec(cmd, shared_globals)
@REGISTRY.method(action='refresh', display='Refresh Processes')
def refresh_processes(node: sch.Schema('ProcessContainer')):
"""Refresh the list of processes."""
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_processes()
@REGISTRY.method(action='refresh', display='Refresh Environment')
def refresh_environment(node: sch.Schema('Environment')):
"""Refresh the environment descriptors (arch, os, endian)."""
with commands.open_tracked_tx('Refresh Environment'):
commands.ghidra_trace_put_environment()
@REGISTRY.method(action='refresh', display='Refresh Threads')
def refresh_threads(node: sch.Schema('ThreadContainer')):
"""Refresh the list of threads in the process."""
with commands.open_tracked_tx('Refresh Threads'):
commands.ghidra_trace_put_threads()
# @REGISTRY.method(action='refresh', display='Refresh Symbols')
# def refresh_symbols(node: sch.Schema('SymbolContainer')):
# """Refresh the list of symbols in the process."""
# with commands.open_tracked_tx('Refresh Symbols'):
# commands.ghidra_trace_put_symbols()
@REGISTRY.method(action='show_symbol', display='Retrieve Symbols')
def retrieve_symbols(
session: sch.Schema('SymbolContainer'),
pattern: ParamDesc(str, display='Pattern')):
"""
Load the symbol set matching the pattern.
"""
with commands.open_tracked_tx('Retrieve Symbols'):
commands.put_symbols(pattern)
@REGISTRY.method(action='refresh', display='Refresh Stack')
def refresh_stack(node: sch.Schema('Stack')):
"""Refresh the backtrace for the thread."""
tnum = find_thread_by_stack_obj(node)
with commands.open_tracked_tx('Refresh Stack'):
commands.ghidra_trace_put_frames()
@REGISTRY.method(action='refresh', display='Refresh Registers')
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
"""Refresh the register values for the selected frame"""
level = find_frame_by_regs_obj(node)
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_putreg()
@REGISTRY.method(action='refresh', display='Refresh Locals')
def refresh_locals(node: sch.Schema('LocalsContainer')):
"""Refresh the local values for the selected frame"""
level = find_frame_by_locals_obj(node)
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_put_locals()
@REGISTRY.method(action='refresh', display='Refresh Memory')
def refresh_mappings(node: sch.Schema('Memory')):
"""Refresh the list of memory regions for the process."""
with commands.open_tracked_tx('Refresh Memory Regions'):
commands.ghidra_trace_put_regions()
@REGISTRY.method(action='refresh', display='Refresh Modules')
def refresh_modules(node: sch.Schema('ModuleContainer')):
"""
Refresh the modules list for the process.
"""
with commands.open_tracked_tx('Refresh Modules'):
commands.ghidra_trace_put_modules()
@REGISTRY.method(action='activate')
def activate_process(process: sch.Schema('Process')):
"""Switch to the process."""
find_proc_by_obj(process)
@REGISTRY.method(action='activate')
def activate_thread(thread: sch.Schema('Thread')):
"""Switch to the thread."""
find_thread_by_obj(thread)
@REGISTRY.method(action='activate')
def activate_frame(frame: sch.Schema('StackFrame')):
"""Select the frame."""
i,f = find_frame_by_obj(frame)
util.select_frame(i)
with commands.open_tracked_tx('Refresh Stack'):
commands.ghidra_trace_put_frames()
with commands.open_tracked_tx('Refresh Registers'):
commands.ghidra_trace_putreg()
@REGISTRY.method
def read_mem(process: sch.Schema('Process'), range: AddressRange):
"""Read memory."""
# print("READ_MEM: process={}, range={}".format(process, range))
nproc = find_proc_by_obj(process)
offset_start = process.trace.memory_mapper.map_back(
nproc, Address(range.space, range.min))
with commands.open_tracked_tx('Read Memory'):
result = commands.put_bytes(
offset_start, offset_start + range.length() - 1, pages=True, display_result=False)
if result['count'] == 0:
commands.putmem_state(
offset_start, offset_start+range.length() - 1, 'error')
@REGISTRY.method(action='attach', display='Attach by pid')
def attach_pid(
processes: sch.Schema('ProcessContainer'),
pid: ParamDesc(str, display='PID')):
"""Attach the process to the given target."""
prog = drgn.Program()
prog.set_pid(int(pid))
util.selected_pid = int(pid)
util.selected_tid = prog.main_thread().tid
default_symbols = {"default": True, "main": True}
try:
prog.load_debug_info(None, **default_symbols)
except drgn.MissingDebugInfoError as e:
print(e)
#commands.ghidra_trace_start(pid)
commands.PROGRAMS[pid] = prog
commands.prog = prog
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_processes()
@REGISTRY.method(action='attach', display='Attach core dump')
def attach_core(
processes: sch.Schema('ProcessContainer'),
core: ParamDesc(str, display='Core dump')):
"""Attach the process to the given target."""
prog = drgn.Program()
prog.set_core_dump(core)
default_symbols = {"default": True, "main": True}
try:
prog.load_debug_info(None, **default_symbols)
except drgn.MissingDebugInfoError as e:
print(e)
util.selected_pid += 1
commands.PROGRAMS[util.selected_pid] = prog
commands.prog = prog
with commands.open_tracked_tx('Refresh Processes'):
commands.ghidra_trace_put_processes()
@REGISTRY.method(action='step_into')
def step_into(thread: sch.Schema('Thread'), n: ParamDesc(int, display='N')=1):
"""Step one instruction exactly."""
find_thread_by_obj(thread)
time.sleep(1)
hooks.on_stop(None)
# @REGISTRY.method
# def kill(process: sch.Schema('Process')):
# """Kill execution of the process."""
# commands.ghidra_trace_kill()
# @REGISTRY.method(action='resume')
# def go(process: sch.Schema('Process')):
# """Continue execution of the process."""
# util.dbg.run_async(lambda: dbg().go())
# @REGISTRY.method
# def interrupt(process: sch.Schema('Process')):
# """Interrupt the execution of the debugged program."""
# # SetInterrupt is reentrant, so bypass the thread checks
# util.dbg._protected_base._control.SetInterrupt(
# DbgEng.DEBUG_INTERRUPT_ACTIVE)

View File

@@ -0,0 +1,183 @@
<context>
<schema name="DrgnRoot" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<attribute name="Processes" schema="ProcessContainer" required="yes" fixed="yes" />
<attribute name="State" schema="ANY" />
<attribute-alias from="_state" to="State" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY"/>
</schema>
<schema name="Selectable" elementResync="NEVER" attributeResync="NEVER">
<element schema="OBJECT" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ProcessContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Process" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<element schema="VOID" />
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
<attribute name="Exit Code" schema="LONG" />
<attribute-alias from="_exit_code" to="Exit Code" />
<attribute name="Environment" schema="Environment" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="Handle" schema="STRING" fixed="yes" />
<attribute name="Id" schema="STRING" fixed="yes" />
<attribute name="PID" schema="LONG" hidden="yes" />
<attribute-alias from="_pid" to="PID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Environment" elementResync="NEVER" attributeResync="NEVER">
<interface name="Environment" />
<element schema="VOID" />
<attribute name="OS" schema="STRING" />
<attribute name="Arch" schema="STRING" />
<attribute name="Endian" schema="STRING" />
<attribute name="Debugger" schema="STRING" />
<attribute-alias from="_os" to="OS" />
<attribute-alias from="_arch" to="Arch" />
<attribute-alias from="_endian" to="Endian" />
<attribute-alias from="_debugger" to="Debugger" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ModuleContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="Module" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Memory" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Memory" />
<element schema="MemoryRegion" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="ThreadContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Thread" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Stack" schema="Stack" required="yes" fixed="yes" />
<attribute name="Environment" schema="ANY" fixed="yes" />
<attribute name="Id" schema="STRING" fixed="yes" />
<attribute name="TID" schema="LONG" />
<attribute-alias from="_tid" to="TID" />
<attribute name="State" schema="EXECUTION_STATE" required="yes" hidden="yes" />
<attribute-alias from="_state" to="State" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Module" elementResync="NEVER" attributeResync="NEVER">
<interface name="Module" />
<element schema="VOID" />
<attribute name="Sections" schema="SectionContainer" required="yes" fixed="yes" />
<attribute name="Symbols" schema="SymbolContainer" required="yes" fixed="yes" />
<attribute name="Range" schema="RANGE" />
<attribute name="Name" schema="STRING" />
<attribute-alias from="_module_name" to="Name" />
<attribute-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute name="ToDisplayString" schema="BOOL" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="MemoryRegion" elementResync="NEVER" attributeResync="NEVER">
<interface name="MemoryRegion" />
<element schema="VOID" />
<attribute name="Object File" schema="STRING" fixed="yes" />
<attribute name="_readable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_writable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="_executable" schema="BOOL" required="yes" hidden="yes" />
<attribute name="Range" schema="RANGE" required="yes" />
<attribute-alias from="_range" to="Range" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="SectionContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<element schema="Section" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="SymbolContainer" canonical="yes" elementResync="ONCE" attributeResync="NEVER">
<element schema="Symbol" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="Symbol" elementResync="NEVER" attributeResync="NEVER">
<element schema="VOID" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />
<attribute name="Function" schema="STRING" hidden="yes" />
<attribute-alias from="_function" to="Function" />
<attribute name="PC" schema="ADDRESS" required="yes" />
<attribute-alias from="_pc" to="PC" />
<attribute name="SP" schema="ADDRESS" />
<attribute name="Registers" schema="RegisterValueContainer" required="yes" fixed="yes" />
<attribute name="Locals" schema="LocalsContainer" required="yes" fixed="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="Section" elementResync="NEVER" attributeResync="NEVER">
<interface name="Section" />
<element schema="VOID" />
<attribute name="Range" schema="RANGE" />
<attribute-alias from="_range" to="Range" />
<attribute name="Offset" schema="STRING" fixed="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="RegisterValueContainer" attributeResync="ONCE">
<interface name="RegisterContainer" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
<schema name="LocalsContainer" attributeResync="ONCE">
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="Local" />
</schema>
<schema name="Local" attributeResync="ONCE">
<attribute name="Address" schema="ADDRESS" />
<attribute name="Kind" schema="ANY" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
</context>

View File

@@ -0,0 +1,115 @@
## ###
# 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
import os
import re
import sys
import drgn
import drgn.cli
DrgnVersion = namedtuple('DrgnVersion', ['display', 'full'])
selected_pid = 0
selected_tid = 0
selected_level = 0
def _compute_drgn_ver():
blurb = drgn.cli.version_header()
top = blurb.split('\n')[0]
full = top.split()[1] # "drgn x.y.z"
return DrgnVersion(top, full)
DRGN_VERSION = _compute_drgn_ver()
def full_mem(self):
return Region(0, 1 << 64, 0, None, 'full memory')
def get_debugger():
return drgn
def get_target():
return commands.prog
def get_process(name):
return get_target()[name]
def selected_process():
return selected_pid
def selected_thread():
return selected_tid
def selected_frame():
return selected_level
def select_process(id: int):
global selected_pid
selected_pid = id
return selected_pid
def select_thread(id: int):
global selected_tid
selected_tid = id
return selected_tid
def select_frame(id: int):
global selected_level
selected_level = id
return selected_level
conv_map = {}
def get_convenience_variable(id):
#val = get_target().GetEnvironment().Get(id)
if id not in conv_map:
return "auto"
val = conv_map[id]
if val is None:
return "auto"
return val
def set_convenience_variable(id, value):
#env = get_target().GetEnvironment()
# return env.Set(id, value, True)
conv_map[id] = value
def escape_ansi(line):
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
return ansi_escape.sub('', line)
def debracket(init):
val = init
val = val.replace("[", "(")
val = val.replace("]", ")")
return val