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/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()")