ghidraxdbg 12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ghidraxdbg/__init__.py +17 -0
- ghidraxdbg/arch.py +236 -0
- ghidraxdbg/commands.py +1349 -0
- ghidraxdbg/hooks.py +419 -0
- ghidraxdbg/methods.py +680 -0
- ghidraxdbg/py.typed +0 -0
- ghidraxdbg/schema.xml +317 -0
- ghidraxdbg/util.py +297 -0
- ghidraxdbg-12.0.dist-info/METADATA +20 -0
- ghidraxdbg-12.0.dist-info/RECORD +13 -0
- ghidraxdbg-12.0.dist-info/WHEEL +5 -0
- ghidraxdbg-12.0.dist-info/licenses/LICENSE +11 -0
- ghidraxdbg-12.0.dist-info/top_level.txt +1 -0
ghidraxdbg/commands.py
ADDED
|
@@ -0,0 +1,1349 @@
|
|
|
1
|
+
## ###
|
|
2
|
+
# IP: GHIDRA
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
from concurrent.futures import Future
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
import inspect
|
|
20
|
+
import os.path
|
|
21
|
+
import re
|
|
22
|
+
import socket
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from typing import Any, Dict, Generator, Iterable, List, Optional, Sequence, Tuple, Union
|
|
26
|
+
|
|
27
|
+
from ghidratrace import sch
|
|
28
|
+
from ghidratrace.client import (Client, Address, AddressRange, Lifespan, RegVal,
|
|
29
|
+
Schedule, Trace, TraceObject, TraceObjectValue,
|
|
30
|
+
Transaction)
|
|
31
|
+
from ghidratrace.display import print_tabular_values, wait
|
|
32
|
+
|
|
33
|
+
from x64dbg_automate.models import BreakpointType, HardwareBreakpointType, MemoryBreakpointType
|
|
34
|
+
|
|
35
|
+
from . import util, arch, methods, hooks
|
|
36
|
+
|
|
37
|
+
STILL_ACTIVE = 259
|
|
38
|
+
PAGE_SIZE = 4096
|
|
39
|
+
|
|
40
|
+
SESSION_PATH = 'Sessions[0]' # Only ever one, it seems
|
|
41
|
+
AVAILABLES_PATH = SESSION_PATH + '.Available'
|
|
42
|
+
AVAILABLE_KEY_PATTERN = '[{pid}]'
|
|
43
|
+
AVAILABLE_PATTERN = AVAILABLES_PATH + AVAILABLE_KEY_PATTERN
|
|
44
|
+
PROCESSES_PATH = SESSION_PATH + '.Processes'
|
|
45
|
+
PROCESS_KEY_PATTERN = '[{procnum}]'
|
|
46
|
+
PROCESS_PATTERN = PROCESSES_PATH + PROCESS_KEY_PATTERN
|
|
47
|
+
PROC_DEBUG_PATTERN = PROCESS_PATTERN + '.Debug'
|
|
48
|
+
PROC_SBREAKS_PATTERN = PROC_DEBUG_PATTERN + '.Software Breakpoints'
|
|
49
|
+
PROC_HBREAKS_PATTERN = PROC_DEBUG_PATTERN + '.Hardware Breakpoints'
|
|
50
|
+
PROC_MBREAKS_PATTERN = PROC_DEBUG_PATTERN + '.Memory Breakpoints'
|
|
51
|
+
PROC_BREAK_KEY_PATTERN = '[{breaknum}]'
|
|
52
|
+
PROC_SBREAK_PATTERN = PROC_SBREAKS_PATTERN + PROC_BREAK_KEY_PATTERN
|
|
53
|
+
PROC_HBREAK_PATTERN = PROC_HBREAKS_PATTERN + PROC_BREAK_KEY_PATTERN
|
|
54
|
+
PROC_MBREAK_PATTERN = PROC_MBREAKS_PATTERN + PROC_BREAK_KEY_PATTERN
|
|
55
|
+
PROC_EVENTS_PATTERN = PROC_DEBUG_PATTERN + '.Events'
|
|
56
|
+
PROC_EVENT_KEY_PATTERN = '[{eventnum}]'
|
|
57
|
+
PROC_EVENT_PATTERN = PROC_EVENTS_PATTERN + PROC_EVENT_KEY_PATTERN
|
|
58
|
+
PROC_EXCS_PATTERN = PROC_DEBUG_PATTERN + '.Exceptions'
|
|
59
|
+
PROC_EXC_KEY_PATTERN = '[{eventnum}]'
|
|
60
|
+
PROC_EXC_PATTERN = PROC_EXCS_PATTERN + PROC_EXC_KEY_PATTERN
|
|
61
|
+
ENV_PATTERN = PROCESS_PATTERN + '.Environment'
|
|
62
|
+
THREADS_PATTERN = PROCESS_PATTERN + '.Threads'
|
|
63
|
+
THREAD_KEY_PATTERN = '[{tnum}]'
|
|
64
|
+
THREAD_PATTERN = THREADS_PATTERN + THREAD_KEY_PATTERN
|
|
65
|
+
STACK_PATTERN = THREAD_PATTERN + '.Stack.Frames'
|
|
66
|
+
FRAME_KEY_PATTERN = '[{level}]'
|
|
67
|
+
FRAME_PATTERN = STACK_PATTERN + FRAME_KEY_PATTERN
|
|
68
|
+
REGS_PATTERN = THREAD_PATTERN + '.Registers'
|
|
69
|
+
USER_REGS_PATTERN = THREAD_PATTERN + '.Registers.User'
|
|
70
|
+
MEMORY_PATTERN = PROCESS_PATTERN + '.Memory'
|
|
71
|
+
REGION_KEY_PATTERN = '[{start:08x}]'
|
|
72
|
+
REGION_PATTERN = MEMORY_PATTERN + REGION_KEY_PATTERN
|
|
73
|
+
MODULES_PATTERN = PROCESS_PATTERN + '.Modules'
|
|
74
|
+
MODULE_KEY_PATTERN = '[{modpath}]'
|
|
75
|
+
MODULE_PATTERN = MODULES_PATTERN + MODULE_KEY_PATTERN
|
|
76
|
+
SECTIONS_ADD_PATTERN = '.Sections'
|
|
77
|
+
SECTION_KEY_PATTERN = '[{secname}]'
|
|
78
|
+
SECTION_ADD_PATTERN = SECTIONS_ADD_PATTERN + SECTION_KEY_PATTERN
|
|
79
|
+
GENERIC_KEY_PATTERN = '[{key}]'
|
|
80
|
+
TTD_PATTERN = 'State.DebuggerVariables.{var}.TTD'
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# TODO: Symbols
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ErrorWithCode(Exception):
|
|
87
|
+
|
|
88
|
+
def __init__(self, code: int) -> None:
|
|
89
|
+
self.code = code
|
|
90
|
+
|
|
91
|
+
def __str__(self) -> str:
|
|
92
|
+
return repr(self.code)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Extra(object):
|
|
96
|
+
def __init__(self) -> None:
|
|
97
|
+
self.memory_mapper: Optional[arch.DefaultMemoryMapper] = None
|
|
98
|
+
self.register_mapper: Optional[arch.DefaultRegisterMapper] = None
|
|
99
|
+
|
|
100
|
+
def require_mm(self) -> arch.DefaultMemoryMapper:
|
|
101
|
+
if self.memory_mapper is None:
|
|
102
|
+
raise RuntimeError("No memory mapper")
|
|
103
|
+
return self.memory_mapper
|
|
104
|
+
|
|
105
|
+
def require_rm(self) -> arch.DefaultRegisterMapper:
|
|
106
|
+
if self.register_mapper is None:
|
|
107
|
+
raise RuntimeError("No register mapper")
|
|
108
|
+
return self.register_mapper
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class State(object):
|
|
112
|
+
|
|
113
|
+
def __init__(self) -> None:
|
|
114
|
+
self.reset_client()
|
|
115
|
+
|
|
116
|
+
def require_client(self) -> Client:
|
|
117
|
+
if self.client is None:
|
|
118
|
+
raise RuntimeError("Not connected")
|
|
119
|
+
return self.client
|
|
120
|
+
|
|
121
|
+
def require_no_client(self) -> None:
|
|
122
|
+
if self.client != None:
|
|
123
|
+
raise RuntimeError("Already connected")
|
|
124
|
+
|
|
125
|
+
def reset_client(self) -> None:
|
|
126
|
+
self.client: Optional[Client] = None
|
|
127
|
+
self.reset_trace()
|
|
128
|
+
|
|
129
|
+
def require_trace(self) -> Trace[Extra]:
|
|
130
|
+
if self.trace is None:
|
|
131
|
+
raise RuntimeError("No trace active")
|
|
132
|
+
return self.trace
|
|
133
|
+
|
|
134
|
+
def require_no_trace(self) -> None:
|
|
135
|
+
if self.trace != None:
|
|
136
|
+
raise RuntimeError("Trace already started")
|
|
137
|
+
|
|
138
|
+
def reset_trace(self) -> None:
|
|
139
|
+
self.trace: Optional[Trace[Extra]] = None
|
|
140
|
+
util.set_convenience_variable('_ghidra_tracing', "false")
|
|
141
|
+
self.reset_tx()
|
|
142
|
+
|
|
143
|
+
def require_tx(self) -> Tuple[Trace, Transaction]:
|
|
144
|
+
trace = self.require_trace()
|
|
145
|
+
if self.tx is None:
|
|
146
|
+
raise RuntimeError("No transaction")
|
|
147
|
+
return trace, self.tx
|
|
148
|
+
|
|
149
|
+
def require_no_tx(self) -> None:
|
|
150
|
+
if self.tx != None:
|
|
151
|
+
raise RuntimeError("Transaction already started")
|
|
152
|
+
|
|
153
|
+
def reset_tx(self) -> None:
|
|
154
|
+
self.tx: Optional[Transaction] = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
STATE = State()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def ghidra_trace_connect(address: Optional[str] = None) -> None:
|
|
161
|
+
"""Connect Python to Ghidra for tracing.
|
|
162
|
+
|
|
163
|
+
Address must be of the form 'host:port'
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
STATE.require_no_client()
|
|
167
|
+
if address is None:
|
|
168
|
+
raise RuntimeError(
|
|
169
|
+
"'ghidra_trace_connect': missing required argument 'address'")
|
|
170
|
+
|
|
171
|
+
parts = address.split(':')
|
|
172
|
+
if len(parts) != 2:
|
|
173
|
+
raise RuntimeError("address must be in the form 'host:port'")
|
|
174
|
+
host, port = parts
|
|
175
|
+
try:
|
|
176
|
+
c = socket.socket()
|
|
177
|
+
c.connect((host, int(port)))
|
|
178
|
+
# TODO: Can we get version info from the DLL?
|
|
179
|
+
STATE.client = Client(c, "x64dbg", methods.REGISTRY)
|
|
180
|
+
print(f"Connected to {STATE.client.description} at {address}")
|
|
181
|
+
except ValueError:
|
|
182
|
+
raise RuntimeError("port must be numeric")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def ghidra_trace_listen(address: str = '0.0.0.0:0') -> None:
|
|
186
|
+
"""Listen for Ghidra to connect for tracing.
|
|
187
|
+
|
|
188
|
+
Takes an optional address for the host and port on which to listen.
|
|
189
|
+
Either the form 'host:port' or just 'port'. If omitted, it will bind
|
|
190
|
+
to an ephemeral port on all interfaces. If only the port is given,
|
|
191
|
+
it will bind to that port on all interfaces. This command will block
|
|
192
|
+
until the connection is established.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
STATE.require_no_client()
|
|
196
|
+
parts = address.split(':')
|
|
197
|
+
if len(parts) == 1:
|
|
198
|
+
host, port = '0.0.0.0', parts[0]
|
|
199
|
+
elif len(parts) == 2:
|
|
200
|
+
host, port = parts
|
|
201
|
+
else:
|
|
202
|
+
raise RuntimeError("address must be 'port' or 'host:port'")
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
s = socket.socket()
|
|
206
|
+
s.bind((host, int(port)))
|
|
207
|
+
host, port = s.getsockname()
|
|
208
|
+
s.listen(1)
|
|
209
|
+
print("Listening at {}:{}...".format(host, port))
|
|
210
|
+
c, (chost, cport) = s.accept()
|
|
211
|
+
s.close()
|
|
212
|
+
print("Connection from {}:{}".format(chost, cport))
|
|
213
|
+
STATE.client = Client(c, "x64dbg", methods.REGISTRY)
|
|
214
|
+
except ValueError:
|
|
215
|
+
raise RuntimeError("port must be numeric")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def ghidra_trace_disconnect() -> None:
|
|
219
|
+
"""Disconnect Python from Ghidra for tracing."""
|
|
220
|
+
|
|
221
|
+
STATE.require_client().close()
|
|
222
|
+
STATE.reset_client()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def compute_name(progname: Optional[str] = None) -> str:
|
|
226
|
+
if progname is None:
|
|
227
|
+
return 'x64dbg/noname'
|
|
228
|
+
return 'x64dbg/' + re.split(r'/|\\', progname)[-1]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def start_trace(name: str) -> None:
|
|
232
|
+
language, compiler = arch.compute_ghidra_lcsp()
|
|
233
|
+
STATE.trace = STATE.require_client().create_trace(
|
|
234
|
+
name, language, compiler, extra=Extra())
|
|
235
|
+
STATE.trace.extra.memory_mapper = arch.compute_memory_mapper(language)
|
|
236
|
+
STATE.trace.extra.register_mapper = arch.compute_register_mapper(language)
|
|
237
|
+
|
|
238
|
+
frame = inspect.currentframe()
|
|
239
|
+
if frame is None:
|
|
240
|
+
raise AssertionError("cannot locate schema.xml")
|
|
241
|
+
parent = os.path.dirname(inspect.getfile(frame))
|
|
242
|
+
schema_fn = os.path.join(parent, 'schema.xml')
|
|
243
|
+
with open(schema_fn, 'r') as schema_file:
|
|
244
|
+
schema_xml = schema_file.read()
|
|
245
|
+
with STATE.trace.open_tx("Create Root Object"):
|
|
246
|
+
root = STATE.trace.create_root_object(schema_xml, 'X64DbgRoot')
|
|
247
|
+
root.set_value('_display', util.DBG_VERSION.full +
|
|
248
|
+
' via x64dbg_automate')
|
|
249
|
+
STATE.trace.create_object(SESSION_PATH).insert()
|
|
250
|
+
util.set_convenience_variable('_ghidra_tracing', "true")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def ghidra_trace_start(name: Optional[str] = None) -> None:
|
|
254
|
+
"""Start a Trace in Ghidra."""
|
|
255
|
+
|
|
256
|
+
STATE.require_client()
|
|
257
|
+
name = compute_name(name)
|
|
258
|
+
STATE.require_no_trace()
|
|
259
|
+
start_trace(name)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def ghidra_trace_stop() -> None:
|
|
263
|
+
"""Stop the Trace in Ghidra."""
|
|
264
|
+
|
|
265
|
+
STATE.require_trace().close()
|
|
266
|
+
STATE.reset_trace()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def ghidra_trace_restart(name: Optional[str] = None) -> None:
|
|
270
|
+
"""Restart or start the Trace in Ghidra."""
|
|
271
|
+
|
|
272
|
+
STATE.require_client()
|
|
273
|
+
if STATE.trace != None:
|
|
274
|
+
STATE.trace.close()
|
|
275
|
+
STATE.reset_trace()
|
|
276
|
+
name = compute_name(name)
|
|
277
|
+
start_trace(name)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def ghidra_trace_create(command: Optional[str] = None,
|
|
281
|
+
args: Optional[str] = '.',
|
|
282
|
+
initdir: Optional[str] = '.',
|
|
283
|
+
start_trace: bool = True,
|
|
284
|
+
wait: bool = False) -> None:
|
|
285
|
+
"""Create a session."""
|
|
286
|
+
|
|
287
|
+
dbg = util.dbg.client
|
|
288
|
+
if command != None:
|
|
289
|
+
dbg.load_executable(command, cmdline=args, current_dir=initdir)
|
|
290
|
+
if wait:
|
|
291
|
+
try:
|
|
292
|
+
dbg.wait_until_debugging()
|
|
293
|
+
except KeyboardInterrupt as ki:
|
|
294
|
+
dbg.interrupt()
|
|
295
|
+
if start_trace:
|
|
296
|
+
ghidra_trace_start(command)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def ghidra_trace_attach(pid: Optional[str] = None,
|
|
300
|
+
start_trace: bool = True) -> None:
|
|
301
|
+
"""Create a session by attaching."""
|
|
302
|
+
|
|
303
|
+
dbg = util.dbg.client
|
|
304
|
+
if pid != None:
|
|
305
|
+
dbg.attach(int(pid, 0))
|
|
306
|
+
try:
|
|
307
|
+
dbg.wait_until_debugging()
|
|
308
|
+
except KeyboardInterrupt as ki:
|
|
309
|
+
dbg.interrupt()
|
|
310
|
+
if start_trace:
|
|
311
|
+
ghidra_trace_start(f"pid_{pid}")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def ghidra_trace_connect_server(options: Union[str, bytes, None] = None) -> None:
|
|
315
|
+
"""Connect to a process server session."""
|
|
316
|
+
|
|
317
|
+
dbg = util.dbg.client
|
|
318
|
+
if options != None:
|
|
319
|
+
if isinstance(options, str):
|
|
320
|
+
enc_options = options.encode()
|
|
321
|
+
#dbg._client.ConnectProcessServer(enc_options)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def ghidra_trace_open(command: Optional[str] = None,
|
|
325
|
+
start_trace: bool = True) -> None:
|
|
326
|
+
"""Create a session."""
|
|
327
|
+
|
|
328
|
+
dbg = util.dbg.client
|
|
329
|
+
if start_trace:
|
|
330
|
+
ghidra_trace_start(command)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def ghidra_trace_kill() -> None:
|
|
334
|
+
"""Kill a session."""
|
|
335
|
+
|
|
336
|
+
dbg = util.dbg.client
|
|
337
|
+
dbg.unload_executable()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def ghidra_trace_info() -> None:
|
|
341
|
+
"""Get info about the Ghidra connection."""
|
|
342
|
+
|
|
343
|
+
if STATE.client is None:
|
|
344
|
+
print("Not connected to Ghidra")
|
|
345
|
+
return
|
|
346
|
+
host, port = STATE.client.s.getpeername()
|
|
347
|
+
print(f"Connected to {STATE.client.description} at {host}:{port}")
|
|
348
|
+
if STATE.trace is None:
|
|
349
|
+
print("No trace")
|
|
350
|
+
return
|
|
351
|
+
print("Trace active")
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def ghidra_trace_info_lcsp() -> None:
|
|
355
|
+
"""Get the selected Ghidra language-compiler-spec pair."""
|
|
356
|
+
|
|
357
|
+
language, compiler = arch.compute_ghidra_lcsp()
|
|
358
|
+
print("Selected Ghidra language: {}".format(language))
|
|
359
|
+
print("Selected Ghidra compiler: {}".format(compiler))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def ghidra_trace_txstart(description: str = "tx") -> None:
|
|
363
|
+
"""Start a transaction on the trace."""
|
|
364
|
+
|
|
365
|
+
STATE.require_no_tx()
|
|
366
|
+
STATE.tx = STATE.require_trace().start_tx(description, undoable=False)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def ghidra_trace_txcommit() -> None:
|
|
370
|
+
"""Commit the current transaction."""
|
|
371
|
+
|
|
372
|
+
STATE.require_tx()[1].commit()
|
|
373
|
+
STATE.reset_tx()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def ghidra_trace_txabort() -> None:
|
|
377
|
+
"""Abort the current transaction.
|
|
378
|
+
|
|
379
|
+
Use only in emergencies.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
trace, tx = STATE.require_tx()
|
|
383
|
+
print("Aborting trace transaction!")
|
|
384
|
+
tx.abort()
|
|
385
|
+
STATE.reset_tx()
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@contextmanager
|
|
389
|
+
def open_tracked_tx(description: str) -> Generator[Transaction, None, None]:
|
|
390
|
+
with STATE.require_trace().open_tx(description) as tx:
|
|
391
|
+
STATE.tx = tx
|
|
392
|
+
yield tx
|
|
393
|
+
STATE.reset_tx()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def ghidra_trace_save() -> None:
|
|
397
|
+
"""Save the current trace."""
|
|
398
|
+
|
|
399
|
+
STATE.require_trace().save()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def ghidra_trace_new_snap(description: Optional[str] = None,
|
|
403
|
+
time: Optional[Schedule] = None) -> Dict[str, int]:
|
|
404
|
+
"""Create a new snapshot.
|
|
405
|
+
|
|
406
|
+
Subsequent modifications to machine state will affect the new
|
|
407
|
+
snapshot.
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
description = str(description)
|
|
411
|
+
trace, tx = STATE.require_tx()
|
|
412
|
+
return {'snap': trace.snapshot(description, time=time)}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def quantize_pages(start: int, end: int) -> Tuple[int, int]:
|
|
416
|
+
return (start // PAGE_SIZE * PAGE_SIZE, (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def put_bytes(start: int, end: int, pages: bool,
|
|
420
|
+
display_result: bool = False) -> Dict[str, int]:
|
|
421
|
+
# print("PUT BYTES")
|
|
422
|
+
# COLOSSAL HACK, but x32dbg will die if you access a 64-bit value
|
|
423
|
+
bitness = util.dbg.client.debugee_bitness()
|
|
424
|
+
if start > 1<<bitness:
|
|
425
|
+
return {'count': 0}
|
|
426
|
+
|
|
427
|
+
trace = STATE.require_trace()
|
|
428
|
+
if pages:
|
|
429
|
+
start, end = quantize_pages(start, end)
|
|
430
|
+
if end - start <= 0:
|
|
431
|
+
return {'count': 0}
|
|
432
|
+
try:
|
|
433
|
+
buf = util.dbg.client.read_memory(start, end - start)
|
|
434
|
+
except Exception as e:
|
|
435
|
+
return {'count': -1}
|
|
436
|
+
|
|
437
|
+
count: Union[int, Future[int]] = 0
|
|
438
|
+
if buf != None:
|
|
439
|
+
nproc = util.selected_process()
|
|
440
|
+
base, addr = trace.extra.require_mm().map(nproc, start)
|
|
441
|
+
if base != addr.space:
|
|
442
|
+
trace.create_overlay_space(base, addr.space)
|
|
443
|
+
count = trace.put_bytes(addr, buf)
|
|
444
|
+
if display_result:
|
|
445
|
+
if isinstance(count, Future):
|
|
446
|
+
count.add_done_callback(lambda c: print(f"Wrote {c} bytes"))
|
|
447
|
+
else:
|
|
448
|
+
print(f"Wrote {count} bytes")
|
|
449
|
+
if isinstance(count, Future):
|
|
450
|
+
return {'count': -1}
|
|
451
|
+
else:
|
|
452
|
+
return {'count': count}
|
|
453
|
+
return {'count': 0}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def eval_address(address: Union[str, int]) -> int:
|
|
457
|
+
try:
|
|
458
|
+
result = util.parse_and_eval(address)
|
|
459
|
+
if isinstance(result, int):
|
|
460
|
+
return result
|
|
461
|
+
raise ValueError(f"Value '{address}' does not evaluate to an int")
|
|
462
|
+
except Exception:
|
|
463
|
+
raise RuntimeError(f"Cannot convert '{address}' to address")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def eval_range(address: Union[str, int],
|
|
467
|
+
length: Union[str, int]) -> Tuple[int, int]:
|
|
468
|
+
start = eval_address(address)
|
|
469
|
+
try:
|
|
470
|
+
l = util.parse_and_eval(length)
|
|
471
|
+
except Exception as e:
|
|
472
|
+
raise RuntimeError(f"Cannot convert '{length}' to length")
|
|
473
|
+
if not isinstance(l, int):
|
|
474
|
+
raise ValueError(f"Value '{address}' does not evaluate to an int")
|
|
475
|
+
end = start + l
|
|
476
|
+
return start, end
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def putmem(address: Union[str, int], length: Union[str, int],
|
|
480
|
+
pages: bool = True, display_result: bool = True) -> Dict[str, int]:
|
|
481
|
+
start, end = eval_range(address, length)
|
|
482
|
+
return put_bytes(start, end, pages, display_result)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def ghidra_trace_putmem(address: Union[str, int], length: Union[str, int],
|
|
486
|
+
pages: bool = True) -> Dict[str, int]:
|
|
487
|
+
"""Record the given block of memory into the Ghidra trace."""
|
|
488
|
+
|
|
489
|
+
STATE.require_tx()
|
|
490
|
+
return putmem(address, length, pages, True)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def putmem_state(address: Union[str, int], length: Union[str, int], state: str,
|
|
494
|
+
pages: bool = True) -> None:
|
|
495
|
+
trace = STATE.require_trace()
|
|
496
|
+
trace.validate_state(state)
|
|
497
|
+
start, end = eval_range(address, length)
|
|
498
|
+
if pages:
|
|
499
|
+
start, end = quantize_pages(start, end)
|
|
500
|
+
nproc = util.selected_process()
|
|
501
|
+
base, addr = trace.extra.require_mm().map(nproc, start)
|
|
502
|
+
if base != addr.space and state != 'unknown':
|
|
503
|
+
trace.create_overlay_space(base, addr.space)
|
|
504
|
+
trace.set_memory_state(addr.extend(end - start), state)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def ghidra_trace_putmem_state(address: Union[str, int], length: Union[str, int],
|
|
508
|
+
state: str, pages: bool = True) -> None:
|
|
509
|
+
"""Set the state of the given range of memory in the Ghidra trace."""
|
|
510
|
+
|
|
511
|
+
STATE.require_tx()
|
|
512
|
+
return putmem_state(address, length, state, pages)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def ghidra_trace_delmem(address: Union[str, int],
|
|
516
|
+
length: Union[str, int]) -> None:
|
|
517
|
+
"""Delete the given range of memory from the Ghidra trace.
|
|
518
|
+
|
|
519
|
+
Why would you do this? Keep in mind putmem quantizes to full pages
|
|
520
|
+
by default, usually to take advantage of spatial locality. This
|
|
521
|
+
command does not quantize. You must do that yourself, if necessary.
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
trace, tx = STATE.require_tx()
|
|
525
|
+
start, end = eval_range(address, length)
|
|
526
|
+
nproc = util.selected_process()
|
|
527
|
+
base, addr = trace.extra.require_mm().map(nproc, start)
|
|
528
|
+
# Do not create the space. We're deleting stuff.
|
|
529
|
+
trace.delete_bytes(addr.extend(end - start))
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def putreg() -> Dict[str, List[str]]:
|
|
533
|
+
trace = STATE.require_trace()
|
|
534
|
+
nproc = util.selected_process()
|
|
535
|
+
if nproc is None:
|
|
536
|
+
return {}
|
|
537
|
+
nthrd = util.selected_thread()
|
|
538
|
+
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
|
539
|
+
trace.create_overlay_space('register', space)
|
|
540
|
+
robj = trace.create_object(space)
|
|
541
|
+
robj.insert()
|
|
542
|
+
mapper = trace.extra.require_rm()
|
|
543
|
+
values = []
|
|
544
|
+
regs = util.dbg.client.get_regs()
|
|
545
|
+
ctxt = regs.context
|
|
546
|
+
for k in ctxt.model_fields.keys():
|
|
547
|
+
name = k
|
|
548
|
+
try:
|
|
549
|
+
value = getattr(ctxt, k)
|
|
550
|
+
except Exception:
|
|
551
|
+
value = 0
|
|
552
|
+
try:
|
|
553
|
+
if type(value) is int:
|
|
554
|
+
values.append(mapper.map_value(nproc, name, value))
|
|
555
|
+
robj.set_value(name, hex(value))
|
|
556
|
+
if type(value) is bytes:
|
|
557
|
+
value = int.from_bytes(value, "little")
|
|
558
|
+
values.append(mapper.map_value(nproc, name, value))
|
|
559
|
+
robj.set_value(name, hex(value))
|
|
560
|
+
except Exception:
|
|
561
|
+
pass
|
|
562
|
+
missing = trace.put_registers(space, values)
|
|
563
|
+
if isinstance(missing, Future):
|
|
564
|
+
return {'future': []}
|
|
565
|
+
return {'missing': missing}
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def ghidra_trace_putreg() -> None:
|
|
569
|
+
"""Record the given register group for the current frame into the Ghidra
|
|
570
|
+
trace.
|
|
571
|
+
|
|
572
|
+
If no group is specified, 'all' is assumed.
|
|
573
|
+
"""
|
|
574
|
+
|
|
575
|
+
STATE.require_tx()
|
|
576
|
+
putreg()
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def ghidra_trace_delreg(group='all') -> None:
|
|
580
|
+
"""Delete the given register group for the curent frame from the Ghidra
|
|
581
|
+
trace.
|
|
582
|
+
|
|
583
|
+
Why would you do this? If no group is specified, 'all' is assumed.
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
trace, tx = STATE.require_tx()
|
|
587
|
+
nproc = util.selected_process()
|
|
588
|
+
nthrd = util.selected_thread()
|
|
589
|
+
space = REGS_PATTERN.format(procnum=nproc, tnum=nthrd)
|
|
590
|
+
mapper = trace.extra.require_rm()
|
|
591
|
+
names = []
|
|
592
|
+
regs = util.dbg.client.get_regs()
|
|
593
|
+
ctxt = regs.context
|
|
594
|
+
for i in ctxt.model_fields.keys():
|
|
595
|
+
names.append(mapper.map_name(nproc, i))
|
|
596
|
+
trace.delete_registers(space, names)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def ghidra_trace_create_obj(path: str) -> None:
|
|
600
|
+
"""Create an object in the Ghidra trace.
|
|
601
|
+
|
|
602
|
+
The new object is in a detached state, so it may not be immediately
|
|
603
|
+
recognized by the Debugger GUI. Use 'ghidra_trace_insert-obj' to
|
|
604
|
+
finish the object, after all its required attributes are set.
|
|
605
|
+
"""
|
|
606
|
+
|
|
607
|
+
trace, tx = STATE.require_tx()
|
|
608
|
+
obj = trace.create_object(path)
|
|
609
|
+
obj.insert()
|
|
610
|
+
print(f"Created object: id={obj.id}, path='{obj.path}'")
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def ghidra_trace_insert_obj(path: str) -> None:
|
|
614
|
+
"""Insert an object into the Ghidra trace."""
|
|
615
|
+
|
|
616
|
+
# NOTE: id parameter is probably not necessary, since this command is for
|
|
617
|
+
# humans.
|
|
618
|
+
trace, tx = STATE.require_tx()
|
|
619
|
+
span = trace.proxy_object_path(path).insert()
|
|
620
|
+
print(f"Inserted object: lifespan={span}")
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def ghidra_trace_remove_obj(path: str) -> None:
|
|
624
|
+
"""Remove an object from the Ghidra trace.
|
|
625
|
+
|
|
626
|
+
This does not delete the object. It just removes it from the tree
|
|
627
|
+
for the current snap and onwards.
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
trace, tx = STATE.require_tx()
|
|
631
|
+
trace.proxy_object_path(path).remove()
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def to_bytes(value: Sequence) -> bytes:
|
|
635
|
+
return bytes(ord(value[i]) if type(value[i]) == str else int(value[i])
|
|
636
|
+
for i in range(0, len(value)))
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def to_string(value: Sequence, encoding: str) -> str:
|
|
640
|
+
b = to_bytes(value)
|
|
641
|
+
return str(b, encoding)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def to_bool_list(value: Sequence) -> List[bool]:
|
|
645
|
+
return [bool(value[i]) for i in range(0, len(value))]
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def to_int_list(value: Sequence) -> List[int]:
|
|
649
|
+
return [ord(value[i]) if type(value[i]) == str else int(value[i])
|
|
650
|
+
for i in range(0, len(value))]
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def to_short_list(value: Sequence) -> List[int]:
|
|
654
|
+
return [ord(value[i]) if type(value[i]) == str else int(value[i])
|
|
655
|
+
for i in range(0, len(value))]
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def to_string_list(value: Sequence, encoding: str) -> List[str]:
|
|
659
|
+
return [to_string(value[i], encoding) for i in range(0, len(value))]
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def eval_value(value: Any, schema: Optional[sch.Schema] = None) -> Tuple[Union[
|
|
663
|
+
bool, int, float, bytes, Tuple[str, Address], List[bool], List[int],
|
|
664
|
+
List[str], str], Optional[sch.Schema]]:
|
|
665
|
+
if (schema == sch.BYTE or schema == sch.SHORT or
|
|
666
|
+
schema == sch.INT or schema == sch.LONG or schema == None):
|
|
667
|
+
value = util.parse_and_eval(value)
|
|
668
|
+
return value, schema
|
|
669
|
+
if schema == sch.CHAR:
|
|
670
|
+
value = util.parse_and_eval(ord(value))
|
|
671
|
+
return value, schema
|
|
672
|
+
if schema == sch.BOOL:
|
|
673
|
+
value = util.parse_and_eval(value)
|
|
674
|
+
return bool(value), schema
|
|
675
|
+
if schema == sch.ADDRESS:
|
|
676
|
+
value = util.parse_and_eval(value)
|
|
677
|
+
nproc = util.selected_process()
|
|
678
|
+
base, addr = STATE.require_trace().extra.require_mm().map(nproc, value)
|
|
679
|
+
return (base, addr), sch.ADDRESS
|
|
680
|
+
if schema == sch.BOOL_ARR:
|
|
681
|
+
return to_bool_list(value), schema
|
|
682
|
+
if schema == sch.BYTE_ARR:
|
|
683
|
+
return to_bytes(value), schema
|
|
684
|
+
if schema == sch.SHORT_ARR:
|
|
685
|
+
return to_short_list(value), schema
|
|
686
|
+
if schema == sch.INT_ARR:
|
|
687
|
+
return to_int_list(value), schema
|
|
688
|
+
if schema == sch.LONG_ARR:
|
|
689
|
+
return to_int_list(value), schema
|
|
690
|
+
if schema == sch.STRING_ARR:
|
|
691
|
+
return to_string_list(value, 'utf-8'), schema
|
|
692
|
+
if schema == sch.CHAR_ARR:
|
|
693
|
+
return to_string(value, 'utf-8'), schema
|
|
694
|
+
if schema == sch.STRING:
|
|
695
|
+
return to_string(value, 'utf-8'), schema
|
|
696
|
+
|
|
697
|
+
return value, schema
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def ghidra_trace_set_value(path: str, key: str, value: Any,
|
|
701
|
+
schema: Optional[str] = None) -> None:
|
|
702
|
+
"""Set a value (attribute or element) in the Ghidra trace's object tree.
|
|
703
|
+
|
|
704
|
+
A void value implies removal.
|
|
705
|
+
NOTE: The type of an expression may be subject to the x64dbg's current
|
|
706
|
+
language, which current defaults to DEBUG_EXPR_CPLUSPLUS (vs DEBUG_EXPR_MASM).
|
|
707
|
+
For most non-primitive cases, we are punting to the Python API.
|
|
708
|
+
"""
|
|
709
|
+
real_schema = None if schema is None else sch.Schema(schema)
|
|
710
|
+
trace, tx = STATE.require_tx()
|
|
711
|
+
if real_schema == sch.OBJECT:
|
|
712
|
+
val: Union[bool, int, float, bytes, Tuple[str, Address], List[bool],
|
|
713
|
+
List[int], List[str], str, TraceObject,
|
|
714
|
+
Address] = trace.proxy_object_path(value)
|
|
715
|
+
else:
|
|
716
|
+
val, real_schema = eval_value(value, real_schema)
|
|
717
|
+
if real_schema == sch.ADDRESS and isinstance(val, tuple):
|
|
718
|
+
base, addr = val
|
|
719
|
+
val = addr
|
|
720
|
+
if base != addr.space:
|
|
721
|
+
trace.create_overlay_space(base, addr.space)
|
|
722
|
+
trace.proxy_object_path(path).set_value(key, val, real_schema)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def ghidra_trace_retain_values(path: str, keys: str) -> None:
|
|
726
|
+
"""Retain only those keys listed, settings all others to null.
|
|
727
|
+
|
|
728
|
+
Takes a list of keys to retain. The first argument may optionally be one of
|
|
729
|
+
the following:
|
|
730
|
+
|
|
731
|
+
--elements To set all other elements to null (default)
|
|
732
|
+
--attributes To set all other attributes to null
|
|
733
|
+
--both To set all other values (elements and attributes) to null
|
|
734
|
+
|
|
735
|
+
If, for some reason, one of the keys to retain would be mistaken for this
|
|
736
|
+
switch, then the switch is required. Only the first argument is taken as the
|
|
737
|
+
switch. All others are taken as keys.
|
|
738
|
+
"""
|
|
739
|
+
|
|
740
|
+
key_list = keys.split(" ")
|
|
741
|
+
|
|
742
|
+
trace, tx = STATE.require_tx()
|
|
743
|
+
kinds = 'elements'
|
|
744
|
+
if key_list[0] == '--elements':
|
|
745
|
+
kinds = 'elements'
|
|
746
|
+
key_list = key_list[1:]
|
|
747
|
+
elif key_list[0] == '--attributes':
|
|
748
|
+
kinds = 'attributes'
|
|
749
|
+
key_list = key_list[1:]
|
|
750
|
+
elif key_list[0] == '--both':
|
|
751
|
+
kinds = 'both'
|
|
752
|
+
key_list = key_list[1:]
|
|
753
|
+
elif key_list[0].startswith('--'):
|
|
754
|
+
raise RuntimeError("Invalid argument: " + key_list[0])
|
|
755
|
+
trace.proxy_object_path(path).retain_values(key_list, kinds=kinds)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def ghidra_trace_get_obj(path: str) -> None:
|
|
759
|
+
"""Get an object descriptor by its canonical path.
|
|
760
|
+
|
|
761
|
+
This isn't the most informative, but it will at least confirm
|
|
762
|
+
whether an object exists and provide its id.
|
|
763
|
+
"""
|
|
764
|
+
|
|
765
|
+
trace = STATE.require_trace()
|
|
766
|
+
object = trace.get_object(path)
|
|
767
|
+
print(f"{object.id}\t{object.path}")
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def ghidra_trace_get_values(pattern: str) -> None:
|
|
771
|
+
"""List all values matching a given path pattern."""
|
|
772
|
+
|
|
773
|
+
trace = STATE.require_trace()
|
|
774
|
+
values = wait(trace.get_values(pattern))
|
|
775
|
+
print_tabular_values(values, print)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def ghidra_trace_get_values_rng(address: Union[str, int],
|
|
779
|
+
length: Union[str, int]) -> None:
|
|
780
|
+
"""List all values intersecting a given address range."""
|
|
781
|
+
|
|
782
|
+
trace = STATE.require_trace()
|
|
783
|
+
start, end = eval_range(address, length)
|
|
784
|
+
nproc = util.selected_process()
|
|
785
|
+
base, addr = trace.extra.require_mm().map(nproc, start)
|
|
786
|
+
# Do not create the space. We're querying. No tx.
|
|
787
|
+
values = wait(trace.get_values_intersecting(addr.extend(end - start)))
|
|
788
|
+
print_tabular_values(values, print)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def activate(path: Optional[str] = None) -> None:
|
|
792
|
+
trace = STATE.require_trace()
|
|
793
|
+
if path is None:
|
|
794
|
+
nproc = util.selected_process()
|
|
795
|
+
if nproc is None:
|
|
796
|
+
path = PROCESSES_PATH
|
|
797
|
+
else:
|
|
798
|
+
nthrd = util.selected_thread()
|
|
799
|
+
if nthrd is None:
|
|
800
|
+
path = PROCESS_PATTERN.format(procnum=nproc)
|
|
801
|
+
else:
|
|
802
|
+
path = THREAD_PATTERN.format(procnum=nproc, tnum=nthrd)
|
|
803
|
+
#frame = util.selected_frame()
|
|
804
|
+
#if frame is None:
|
|
805
|
+
# path = THREAD_PATTERN.format(procnum=nproc, tnum=nthrd)
|
|
806
|
+
#else:
|
|
807
|
+
# path = FRAME_PATTERN.format(
|
|
808
|
+
# procnum=nproc, tnum=nthrd, level=frame)
|
|
809
|
+
trace.proxy_object_path(path).activate()
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def ghidra_trace_activate(path: Optional[str] = None) -> None:
|
|
813
|
+
"""Activate an object in Ghidra's GUI.
|
|
814
|
+
|
|
815
|
+
This has no effect if the current trace is not current in Ghidra. If
|
|
816
|
+
path is omitted, this will activate the current frame.
|
|
817
|
+
"""
|
|
818
|
+
|
|
819
|
+
activate(path)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def ghidra_trace_disassemble(address: Union[str, int]) -> None:
|
|
823
|
+
"""Disassemble starting at the given seed.
|
|
824
|
+
|
|
825
|
+
Disassembly proceeds linearly and terminates at the first branch or
|
|
826
|
+
unknown memory encountered.
|
|
827
|
+
"""
|
|
828
|
+
|
|
829
|
+
trace, tx = STATE.require_tx()
|
|
830
|
+
start = eval_address(address)
|
|
831
|
+
nproc = util.selected_process()
|
|
832
|
+
base, addr = trace.extra.require_mm().map(nproc, start)
|
|
833
|
+
if base != addr.space:
|
|
834
|
+
trace.create_overlay_space(base, addr.space)
|
|
835
|
+
|
|
836
|
+
length = trace.disassemble(addr)
|
|
837
|
+
print("Disassembled {} bytes".format(length))
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def compute_proc_state(nproc: Optional[int] = None) -> str:
|
|
841
|
+
if nproc is None:
|
|
842
|
+
return 'TERMINATED'
|
|
843
|
+
try:
|
|
844
|
+
if util.dbg.client.is_running():
|
|
845
|
+
return 'RUNNING'
|
|
846
|
+
return 'STOPPED'
|
|
847
|
+
except Exception:
|
|
848
|
+
return 'TERMINATED'
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def put_processes(running: bool = False) -> None:
|
|
852
|
+
# NB: This speeds things up, but desirable?
|
|
853
|
+
if running:
|
|
854
|
+
return
|
|
855
|
+
|
|
856
|
+
trace = STATE.require_trace()
|
|
857
|
+
|
|
858
|
+
keys = []
|
|
859
|
+
# Set running=True to avoid process changes, even while stopped
|
|
860
|
+
for i, p in enumerate(util.process_list0(running=True)):
|
|
861
|
+
pid = p[0]
|
|
862
|
+
ipath = PROCESS_PATTERN.format(procnum=pid)
|
|
863
|
+
keys.append(PROCESS_KEY_PATTERN.format(procnum=pid))
|
|
864
|
+
procobj = trace.create_object(ipath)
|
|
865
|
+
|
|
866
|
+
istate = compute_proc_state(i)
|
|
867
|
+
procobj.set_value('State', istate)
|
|
868
|
+
procobj.set_value('PID', pid)
|
|
869
|
+
procobj.set_value('_display', f'{i} {pid}')
|
|
870
|
+
if len(p) > 1:
|
|
871
|
+
procobj.set_value('Name', str(p[1]))
|
|
872
|
+
#procobj.set_value('PEB', hex(int(p[2])))
|
|
873
|
+
procobj.insert()
|
|
874
|
+
trace.proxy_object_path(PROCESSES_PATH).retain_values(keys)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
def put_state(event_process: int) -> None:
|
|
878
|
+
state = compute_proc_state(event_process)
|
|
879
|
+
if event_process is None:
|
|
880
|
+
event_process = util.last_process
|
|
881
|
+
ipath = PROCESS_PATTERN.format(procnum=event_process)
|
|
882
|
+
trace = STATE.require_trace()
|
|
883
|
+
procobj = trace.create_object(ipath)
|
|
884
|
+
procobj.set_value('State', state)
|
|
885
|
+
procobj.insert()
|
|
886
|
+
tnum = util.selected_thread()
|
|
887
|
+
if tnum is not None:
|
|
888
|
+
ipath = THREAD_PATTERN.format(procnum=event_process, tnum=tnum)
|
|
889
|
+
threadobj = trace.create_object(ipath)
|
|
890
|
+
threadobj.set_value('State', state)
|
|
891
|
+
threadobj.insert()
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def ghidra_trace_put_processes() -> None:
|
|
895
|
+
"""Put the list of processes into the trace's Processes list."""
|
|
896
|
+
|
|
897
|
+
trace, tx = STATE.require_tx()
|
|
898
|
+
with trace.client.batch() as b:
|
|
899
|
+
put_processes()
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def put_available() -> None:
|
|
903
|
+
trace = STATE.require_trace()
|
|
904
|
+
keys = []
|
|
905
|
+
for i, p in enumerate(util.process_list(running=True)):
|
|
906
|
+
pid = p[0]
|
|
907
|
+
ipath = AVAILABLE_PATTERN.format(pid=pid)
|
|
908
|
+
keys.append(AVAILABLE_KEY_PATTERN.format(pid=pid))
|
|
909
|
+
procobj = trace.create_object(ipath)
|
|
910
|
+
procobj.set_value('PID', pid)
|
|
911
|
+
procobj.set_value('_display', f'{i} {pid}')
|
|
912
|
+
if len(p) > 1:
|
|
913
|
+
name = str(p[1])
|
|
914
|
+
procobj.set_value('Name', name)
|
|
915
|
+
procobj.set_value('_display', f'{i} {pid} {name}')
|
|
916
|
+
procobj.insert()
|
|
917
|
+
trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def ghidra_trace_put_available() -> None:
|
|
921
|
+
"""Put the list of available processes into the trace's Available list."""
|
|
922
|
+
|
|
923
|
+
trace, tx = STATE.require_tx()
|
|
924
|
+
with trace.client.batch() as b:
|
|
925
|
+
put_available()
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def put_single_breakpoint(bp, bpath, nproc: int, ikeys: List[int]) -> None:
|
|
929
|
+
trace = STATE.require_trace()
|
|
930
|
+
mapper = trace.extra.require_mm()
|
|
931
|
+
|
|
932
|
+
address = bp.addr
|
|
933
|
+
brkobj = trace.create_object(bpath)
|
|
934
|
+
|
|
935
|
+
brkobj.set_value('_display', f'{hex(address)}')
|
|
936
|
+
if address is not None: # Implies execution break
|
|
937
|
+
base, addr = mapper.map(nproc, address)
|
|
938
|
+
if base != addr.space:
|
|
939
|
+
trace.create_overlay_space(base, addr.space)
|
|
940
|
+
brkobj.set_value('Range', addr.extend(1))
|
|
941
|
+
brkobj.set_value('Active', bp.active)
|
|
942
|
+
brkobj.set_value('Enabled', bp.enabled)
|
|
943
|
+
brkobj.set_value('Slot', str(bp.slot))
|
|
944
|
+
brkobj.set_value('Type', str(bp.type))
|
|
945
|
+
brkobj.set_value('TypeEx', str(bp.typeEx))
|
|
946
|
+
if bp.fastResume is True:
|
|
947
|
+
brkobj.set_value('FastResume', bp.fastResume)
|
|
948
|
+
if bp.silent is True:
|
|
949
|
+
brkobj.set_value('Silent', bp.silent)
|
|
950
|
+
if bp.singleshoot is True:
|
|
951
|
+
brkobj.set_value('SingleShot', bp.singleshoot)
|
|
952
|
+
if bp.mod is not None:
|
|
953
|
+
brkobj.set_value('Module', bp.mod)
|
|
954
|
+
if bp.name is not None and bp.name != "":
|
|
955
|
+
brkobj.set_value('Name', bp.name)
|
|
956
|
+
brkobj.set_value('_display', f'{hex(address)} {bp.name}')
|
|
957
|
+
if bp.commandText is not None and bp.commandText != "":
|
|
958
|
+
brkobj.set_value('Command', bp.commandText)
|
|
959
|
+
if bp.breakCondition is not None and bp.breakCondition != "":
|
|
960
|
+
brkobj.set_value('Condition', bp.breakCondition)
|
|
961
|
+
if bp.logText is not None and bp.logText != "":
|
|
962
|
+
brkobj.set_value('LogText', bp.logText)
|
|
963
|
+
if bp.logCondition is not None and bp.logCondition != "":
|
|
964
|
+
brkobj.set_value('LogCondition', bp.logCondition)
|
|
965
|
+
if bp.hwSize is not None and bp.hwSize != 0:
|
|
966
|
+
brkobj.set_value('HW Size', bp.hwSize)
|
|
967
|
+
brkobj.set_value('Range', addr.extend(bp.hwSize))
|
|
968
|
+
brkobj.set_value('HitCount', bp.hitCount)
|
|
969
|
+
if bp.type == BreakpointType.BpNormal:
|
|
970
|
+
brkobj.set_value('Kinds', 'SW_EXECUTE')
|
|
971
|
+
if bp.type == BreakpointType.BpHardware:
|
|
972
|
+
prot = {0: 'READ', 1: 'WRITE', 2: 'HW_EXECUTE'}[bp.typeEx]
|
|
973
|
+
brkobj.set_value('Kinds', prot)
|
|
974
|
+
if bp.type == BreakpointType.BpMemory:
|
|
975
|
+
prot = {0: 'READ', 1: 'WRITE', 2: 'HW_EXECUTE', 3: 'ACCESS'}[bp.typeEx]
|
|
976
|
+
brkobj.set_value('Kinds', prot)
|
|
977
|
+
brkobj.insert()
|
|
978
|
+
|
|
979
|
+
k = PROC_BREAK_KEY_PATTERN.format(breaknum=address)
|
|
980
|
+
ikeys.append(k)
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def put_breakpoints(type: BreakpointType) -> None:
|
|
984
|
+
nproc = util.selected_process()
|
|
985
|
+
|
|
986
|
+
trace = STATE.require_trace()
|
|
987
|
+
target = util.get_target()
|
|
988
|
+
pattern = ''
|
|
989
|
+
prot = ''
|
|
990
|
+
if type == BreakpointType.BpNormal:
|
|
991
|
+
pattern = PROC_SBREAKS_PATTERN
|
|
992
|
+
if type == BreakpointType.BpHardware:
|
|
993
|
+
pattern = PROC_HBREAKS_PATTERN
|
|
994
|
+
if type == BreakpointType.BpMemory:
|
|
995
|
+
pattern = PROC_MBREAKS_PATTERN
|
|
996
|
+
ibpath = pattern.format(procnum=nproc)
|
|
997
|
+
ibobj = trace.create_object(ibpath)
|
|
998
|
+
keys: List[str] = []
|
|
999
|
+
ikeys: List[int] = []
|
|
1000
|
+
for bp in util.dbg.client.get_breakpoints(type):
|
|
1001
|
+
bpath = ibpath + PROC_BREAK_KEY_PATTERN.format(breaknum=bp.addr)
|
|
1002
|
+
keys.append(bpath)
|
|
1003
|
+
put_single_breakpoint(bp, bpath, nproc, ikeys)
|
|
1004
|
+
ibobj.insert()
|
|
1005
|
+
trace.proxy_object_path(pattern).retain_values(keys)
|
|
1006
|
+
ibobj.retain_values(ikeys)
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def ghidra_trace_put_breakpoints() -> None:
|
|
1010
|
+
"""Put the current process's breakpoints into the trace."""
|
|
1011
|
+
|
|
1012
|
+
trace, tx = STATE.require_tx()
|
|
1013
|
+
with trace.client.batch() as b:
|
|
1014
|
+
put_breakpoints(BreakpointType.BpNormal)
|
|
1015
|
+
put_breakpoints(BreakpointType.BpHardware)
|
|
1016
|
+
put_breakpoints(BreakpointType.BpMemory)
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def put_environment() -> None:
|
|
1020
|
+
trace = STATE.require_trace()
|
|
1021
|
+
nproc = util.selected_process()
|
|
1022
|
+
epath = ENV_PATTERN.format(procnum=nproc)
|
|
1023
|
+
envobj = trace.create_object(epath)
|
|
1024
|
+
envobj.set_value('Debugger', 'x64dbg')
|
|
1025
|
+
envobj.set_value('Arch', arch.get_arch())
|
|
1026
|
+
envobj.set_value('OS', arch.get_osabi())
|
|
1027
|
+
envobj.set_value('Endian', arch.get_endian())
|
|
1028
|
+
envobj.insert()
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def ghidra_trace_put_environment() -> None:
|
|
1032
|
+
"""Put some environment indicators into the Ghidra trace."""
|
|
1033
|
+
|
|
1034
|
+
trace, tx = STATE.require_tx()
|
|
1035
|
+
with trace.client.batch() as b:
|
|
1036
|
+
put_environment()
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def put_regions() -> None:
|
|
1040
|
+
nproc = util.selected_process()
|
|
1041
|
+
try:
|
|
1042
|
+
regions = util.dbg.client.memmap()
|
|
1043
|
+
except Exception:
|
|
1044
|
+
regions = []
|
|
1045
|
+
#if len(regions) == 0:
|
|
1046
|
+
# regions = util.full_mem()
|
|
1047
|
+
|
|
1048
|
+
trace = STATE.require_trace()
|
|
1049
|
+
mapper = trace.extra.require_mm()
|
|
1050
|
+
keys = []
|
|
1051
|
+
mod_keys = []
|
|
1052
|
+
# r : MemMap
|
|
1053
|
+
for r in regions:
|
|
1054
|
+
rpath = REGION_PATTERN.format(procnum=nproc, start=r.base_address)
|
|
1055
|
+
keys.append(REGION_KEY_PATTERN.format(start=r.base_address))
|
|
1056
|
+
regobj = trace.create_object(rpath)
|
|
1057
|
+
(start_base, start_addr) = map_address(r.base_address)
|
|
1058
|
+
regobj.set_value('Range', start_addr.extend(r.region_size))
|
|
1059
|
+
regobj.set_value('_readable', r.protect ==
|
|
1060
|
+
None or r.protect & 0x66 != 0)
|
|
1061
|
+
regobj.set_value('_writable', r.protect ==
|
|
1062
|
+
None or r.protect & 0xCC != 0)
|
|
1063
|
+
regobj.set_value('_executable', r.protect ==
|
|
1064
|
+
None or r.protect & 0xF0 != 0)
|
|
1065
|
+
regobj.set_value('AllocationBase', hex(r.allocation_base))
|
|
1066
|
+
regobj.set_value('Protect', hex(r.protect))
|
|
1067
|
+
regobj.set_value('Type', hex(r.type))
|
|
1068
|
+
if hasattr(r, 'info') and r.info is not None:
|
|
1069
|
+
regobj.set_value('_display', r.info)
|
|
1070
|
+
base = util.dbg.eval('mod.base({})'.format(hex(r.base_address)))
|
|
1071
|
+
if base is not None and isinstance(base, int) is False:
|
|
1072
|
+
base = base[0]
|
|
1073
|
+
if base == r.base_address:
|
|
1074
|
+
name = r.info
|
|
1075
|
+
hbase = hex(base)
|
|
1076
|
+
mpath = MODULE_PATTERN.format(procnum=nproc, modpath=hbase)
|
|
1077
|
+
modobj = trace.create_object(mpath)
|
|
1078
|
+
mod_keys.append(MODULE_KEY_PATTERN.format(modpath=hbase))
|
|
1079
|
+
base_base, base_addr = mapper.map(nproc, base)
|
|
1080
|
+
if base_base != base_addr.space:
|
|
1081
|
+
trace.create_overlay_space(base_base, base_addr.space)
|
|
1082
|
+
modsize = util.dbg.eval('mod.size({})'.format(hbase))
|
|
1083
|
+
if modsize is None or len(modsize) < 2:
|
|
1084
|
+
size = 1
|
|
1085
|
+
else:
|
|
1086
|
+
size = modsize[0]
|
|
1087
|
+
modobj.set_value('Range', base_addr.extend(size))
|
|
1088
|
+
modobj.set_value('Name', name)
|
|
1089
|
+
modentry = util.dbg.eval('mod.entry({})'.format(hbase))
|
|
1090
|
+
if modentry is not None and isinstance(modentry, int):
|
|
1091
|
+
modobj.set_value('Entry', modentry)
|
|
1092
|
+
elif modentry is not None and len(modentry) > 0:
|
|
1093
|
+
modobj.set_value('Entry', modentry[0])
|
|
1094
|
+
modobj.insert()
|
|
1095
|
+
regobj.insert()
|
|
1096
|
+
if STATE.trace is None:
|
|
1097
|
+
return
|
|
1098
|
+
STATE.trace.proxy_object_path(
|
|
1099
|
+
MEMORY_PATTERN.format(procnum=nproc)).retain_values(keys)
|
|
1100
|
+
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
|
1101
|
+
procnum=nproc)).retain_values(mod_keys)
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def ghidra_trace_put_regions() -> None:
|
|
1105
|
+
"""Read the memory map, if applicable, and write to the trace's Regions."""
|
|
1106
|
+
|
|
1107
|
+
trace, tx = STATE.require_tx()
|
|
1108
|
+
with trace.client.batch() as b:
|
|
1109
|
+
put_regions()
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def put_modules() -> None:
|
|
1113
|
+
put_regions()
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def ghidra_trace_put_modules() -> None:
|
|
1117
|
+
"""Gather object files, if applicable, and write to the trace's Modules."""
|
|
1118
|
+
|
|
1119
|
+
trace, tx = STATE.require_tx()
|
|
1120
|
+
with trace.client.batch() as b:
|
|
1121
|
+
put_modules()
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
def compute_thread_display(i: int, pid: Optional[int], tid: int, t) -> str:
|
|
1125
|
+
return f'{i} {pid}:{tid}'
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
def put_threads(running: bool = False) -> None:
|
|
1129
|
+
# NB: This speeds things up, but desirable?
|
|
1130
|
+
if running:
|
|
1131
|
+
return
|
|
1132
|
+
|
|
1133
|
+
pid = util.selected_process()
|
|
1134
|
+
if pid is None:
|
|
1135
|
+
return
|
|
1136
|
+
trace = STATE.require_trace()
|
|
1137
|
+
|
|
1138
|
+
mapper = trace.extra.require_mm()
|
|
1139
|
+
keys = []
|
|
1140
|
+
|
|
1141
|
+
for i, t in enumerate(util.thread_list(running=False)):
|
|
1142
|
+
tid = int(t[0])
|
|
1143
|
+
tpath = THREAD_PATTERN.format(procnum=pid, tnum=tid)
|
|
1144
|
+
tobj = trace.create_object(tpath)
|
|
1145
|
+
keys.append(THREAD_KEY_PATTERN.format(tnum=tid))
|
|
1146
|
+
|
|
1147
|
+
tobj.set_value('_short_display', f'{i} {pid}:{tid}')
|
|
1148
|
+
tobj.set_value('_display', compute_thread_display(i, pid, tid, t))
|
|
1149
|
+
tobj.set_value('TID', tid)
|
|
1150
|
+
if tid in util.threads:
|
|
1151
|
+
thread_data = util.threads[tid]
|
|
1152
|
+
base, offset_start = mapper.map(pid, thread_data.lpStartAddress)
|
|
1153
|
+
tobj.set_value('Start', offset_start)
|
|
1154
|
+
base, offset_base = mapper.map(pid, thread_data.lpThreadLocalBase)
|
|
1155
|
+
tobj.set_value('TLB', offset_base)
|
|
1156
|
+
tobj.insert()
|
|
1157
|
+
stackobj = trace.create_object(tpath+".Stack")
|
|
1158
|
+
stackobj.insert()
|
|
1159
|
+
trace.proxy_object_path(THREADS_PATTERN.format(
|
|
1160
|
+
procnum=pid)).retain_values(keys)
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def put_event_thread(nthrd: Optional[int] = None) -> None:
|
|
1164
|
+
trace = STATE.require_trace()
|
|
1165
|
+
nproc = util.selected_process()
|
|
1166
|
+
# Assumption: Event thread is selected by x64dbg upon stopping
|
|
1167
|
+
if nthrd is None:
|
|
1168
|
+
nthrd = util.selected_thread()
|
|
1169
|
+
if nthrd != None:
|
|
1170
|
+
tpath = THREAD_PATTERN.format(procnum=nproc, tnum=nthrd)
|
|
1171
|
+
tobj = trace.proxy_object_path(tpath)
|
|
1172
|
+
else:
|
|
1173
|
+
tobj = None
|
|
1174
|
+
trace.proxy_object_path('').set_value('_event_thread', tobj)
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
def ghidra_trace_put_threads() -> None:
|
|
1178
|
+
"""Put the current process's threads into the Ghidra trace."""
|
|
1179
|
+
|
|
1180
|
+
trace, tx = STATE.require_tx()
|
|
1181
|
+
with trace.client.batch() as b:
|
|
1182
|
+
put_threads()
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
# TODO: if eventually exposed...
|
|
1186
|
+
# def put_frames() -> None:
|
|
1187
|
+
# nproc = util.selected_process()
|
|
1188
|
+
# if nproc < 0:
|
|
1189
|
+
# return
|
|
1190
|
+
# nthrd = util.selected_thread()
|
|
1191
|
+
# if nthrd is None:
|
|
1192
|
+
# return
|
|
1193
|
+
#
|
|
1194
|
+
# trace = STATE.require_trace()
|
|
1195
|
+
#
|
|
1196
|
+
# mapper = trace.extra.require_mm()
|
|
1197
|
+
# keys = []
|
|
1198
|
+
# # f : _DEBUG_STACK_FRAME
|
|
1199
|
+
# for f in util.dbg.client.backtrace_list():
|
|
1200
|
+
# fpath = FRAME_PATTERN.format(
|
|
1201
|
+
# procnum=nproc, tnum=nthrd, level=f.FrameNumber)
|
|
1202
|
+
# fobj = trace.create_object(fpath)
|
|
1203
|
+
# keys.append(FRAME_KEY_PATTERN.format(level=f.FrameNumber))
|
|
1204
|
+
# base, offset_inst = mapper.map(nproc, f.InstructionOffset)
|
|
1205
|
+
# if base != offset_inst.space:
|
|
1206
|
+
# trace.create_overlay_space(base, offset_inst.space)
|
|
1207
|
+
# fobj.set_value('Instruction Offset', offset_inst)
|
|
1208
|
+
# base, offset_stack = mapper.map(nproc, f.StackOffset)
|
|
1209
|
+
# if base != offset_stack.space:
|
|
1210
|
+
# trace.create_overlay_space(base, offset_stack.space)
|
|
1211
|
+
# base, offset_ret = mapper.map(nproc, f.ReturnOffset)
|
|
1212
|
+
# if base != offset_ret.space:
|
|
1213
|
+
# trace.create_overlay_space(base, offset_ret.space)
|
|
1214
|
+
# base, offset_frame = mapper.map(nproc, f.FrameOffset)
|
|
1215
|
+
# if base != offset_frame.space:
|
|
1216
|
+
# trace.create_overlay_space(base, offset_frame.space)
|
|
1217
|
+
# fobj.set_value('Stack Offset', offset_stack)
|
|
1218
|
+
# fobj.set_value('Return Offset', offset_ret)
|
|
1219
|
+
# fobj.set_value('Frame Offset', offset_frame)
|
|
1220
|
+
# fobj.set_value('_display', "#{} {}".format(
|
|
1221
|
+
# f.FrameNumber, offset_inst.offset))
|
|
1222
|
+
# fobj.insert()
|
|
1223
|
+
# trace.proxy_object_path(STACK_PATTERN.format(
|
|
1224
|
+
# procnum=nproc, tnum=nthrd)).retain_values(keys)
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
# def ghidra_trace_put_frames() -> None:
|
|
1228
|
+
# """Put the current thread's frames into the Ghidra trace."""
|
|
1229
|
+
#
|
|
1230
|
+
# trace, tx = STATE.require_tx()
|
|
1231
|
+
# with trace.client.batch() as b:
|
|
1232
|
+
# put_frames()
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
def map_address(address: int) -> Tuple[str, Address]:
|
|
1236
|
+
nproc = util.selected_process()
|
|
1237
|
+
trace = STATE.require_trace()
|
|
1238
|
+
mapper = trace.extra.require_mm()
|
|
1239
|
+
base, addr = mapper.map(nproc, address)
|
|
1240
|
+
if base != addr.space:
|
|
1241
|
+
trace.create_overlay_space(base, addr.space)
|
|
1242
|
+
return base, addr
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
def ghidra_trace_put_all() -> None:
|
|
1246
|
+
"""Put everything currently selected into the Ghidra trace."""
|
|
1247
|
+
|
|
1248
|
+
trace, tx = STATE.require_tx()
|
|
1249
|
+
with trace.client.batch() as b:
|
|
1250
|
+
#util.dbg.client.wait_cmd_ready()
|
|
1251
|
+
try:
|
|
1252
|
+
put_processes()
|
|
1253
|
+
put_environment()
|
|
1254
|
+
put_threads()
|
|
1255
|
+
putreg()
|
|
1256
|
+
put_regions()
|
|
1257
|
+
putmem(util.get_pc(), 1)
|
|
1258
|
+
putmem(util.get_sp(), 1)
|
|
1259
|
+
put_breakpoints(BreakpointType.BpNormal)
|
|
1260
|
+
put_breakpoints(BreakpointType.BpHardware)
|
|
1261
|
+
put_breakpoints(BreakpointType.BpMemory)
|
|
1262
|
+
put_available()
|
|
1263
|
+
activate()
|
|
1264
|
+
except Exception as e:
|
|
1265
|
+
print(e)
|
|
1266
|
+
pass
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def ghidra_trace_install_hooks() -> None:
|
|
1270
|
+
"""Install hooks to trace in Ghidra."""
|
|
1271
|
+
|
|
1272
|
+
hooks.install_hooks()
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
def ghidra_trace_remove_hooks() -> None:
|
|
1276
|
+
"""Remove hooks to trace in Ghidra.
|
|
1277
|
+
|
|
1278
|
+
Using this directly is not recommended, unless it seems the hooks
|
|
1279
|
+
are preventing x64dbg or other extensions from operating. Removing
|
|
1280
|
+
hooks will break trace synchronization until they are replaced.
|
|
1281
|
+
"""
|
|
1282
|
+
|
|
1283
|
+
hooks.remove_hooks()
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def ghidra_trace_sync_enable() -> None:
|
|
1287
|
+
"""Synchronize the current process with the Ghidra trace.
|
|
1288
|
+
|
|
1289
|
+
This will automatically install hooks if necessary. The goal is to
|
|
1290
|
+
record the current frame, thread, and process into the trace
|
|
1291
|
+
immediately, and then to append the trace upon stopping and/or
|
|
1292
|
+
selecting new frames. This action is effective only for the current
|
|
1293
|
+
process. This command must be executed for each individual process
|
|
1294
|
+
you'd like to synchronize. In older versions of x64dbg, certain
|
|
1295
|
+
events cannot be hooked. In that case, you may need to execute
|
|
1296
|
+
certain "trace put" commands manually, or go without.
|
|
1297
|
+
|
|
1298
|
+
This will have no effect unless or until you start a trace.
|
|
1299
|
+
"""
|
|
1300
|
+
|
|
1301
|
+
hooks.install_hooks()
|
|
1302
|
+
hooks.enable_current_process()
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
def ghidra_trace_sync_disable() -> None:
|
|
1306
|
+
"""Cease synchronizing the current process with the Ghidra trace.
|
|
1307
|
+
|
|
1308
|
+
This is the opposite of 'ghidra_trace_sync-disable', except it will
|
|
1309
|
+
not automatically remove hooks.
|
|
1310
|
+
"""
|
|
1311
|
+
|
|
1312
|
+
hooks.disable_current_process()
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def get_prompt_text() -> str:
|
|
1316
|
+
try:
|
|
1317
|
+
return "dbg>" #util.dbg.get_prompt_text()
|
|
1318
|
+
except util.DebuggeeRunningException:
|
|
1319
|
+
return 'Running>'
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
def exec_cmd(cmd: str) -> None:
|
|
1323
|
+
dbg = util.dbg
|
|
1324
|
+
dbg.cmd(cmd, quiet=False)
|
|
1325
|
+
stat = dbg.exec_status() # type:ignore
|
|
1326
|
+
if stat != 'BREAK':
|
|
1327
|
+
dbg.wait() # type:ignore
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def repl() -> None:
|
|
1331
|
+
print("")
|
|
1332
|
+
print("This is the Windows Debugger REPL. To drop to Python, type .exit")
|
|
1333
|
+
while True:
|
|
1334
|
+
print(get_prompt_text(), end=' ')
|
|
1335
|
+
try:
|
|
1336
|
+
cmd = input().strip()
|
|
1337
|
+
if cmd == '':
|
|
1338
|
+
continue
|
|
1339
|
+
elif cmd == '.exit':
|
|
1340
|
+
break
|
|
1341
|
+
exec_cmd(cmd)
|
|
1342
|
+
except KeyboardInterrupt as e:
|
|
1343
|
+
util.dbg.interrupt()
|
|
1344
|
+
except BaseException as e:
|
|
1345
|
+
pass # Error is printed by another mechanism
|
|
1346
|
+
print("")
|
|
1347
|
+
print("You have left the Windows Debugger REPL and are now at the Python "
|
|
1348
|
+
"interpreter.")
|
|
1349
|
+
print("To re-enter, type repl()")
|