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/hooks.py ADDED
@@ -0,0 +1,419 @@
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
+ from bisect import bisect_left, bisect_right
17
+ from dataclasses import dataclass, field
18
+ import functools
19
+ import sys
20
+ import threading
21
+ import time
22
+ import traceback
23
+ from typing import Any, Callable, Collection, Dict, Optional, TypeVar, cast
24
+
25
+ from ghidratrace.client import Schedule
26
+ from x64dbg_automate.events import EventType
27
+ from x64dbg_automate.models import BreakpointType
28
+
29
+ from . import commands, util
30
+
31
+
32
+ ALL_EVENTS = 0xFFFF
33
+
34
+
35
+ @dataclass(frozen=False)
36
+ class HookState:
37
+ installed = False
38
+ mem_catchpoint = None
39
+
40
+
41
+ @dataclass(frozen=False)
42
+ class ProcessState:
43
+ first = True
44
+ # For things we can detect changes to between stops
45
+ regions = False
46
+ modules = False
47
+ threads = False
48
+ breaks = True
49
+ watches = False
50
+ # For frames and threads that have already been synced since last stop
51
+ visited: set[Any] = field(default_factory=set)
52
+ waiting = False
53
+
54
+ def record(self, description: Optional[str] = None,
55
+ time: Optional[Schedule] = None) -> None:
56
+ first = self.first
57
+ self.first = False
58
+ trace = commands.STATE.require_trace()
59
+ if description is not None:
60
+ trace.snapshot(description, time=time)
61
+ if first:
62
+ commands.put_available()
63
+ commands.put_processes()
64
+ commands.put_environment()
65
+ commands.put_threads()
66
+ if self.threads:
67
+ commands.put_threads()
68
+ self.threads = False
69
+ thread = util.selected_thread()
70
+ if thread is not None:
71
+ if first or thread not in self.visited:
72
+ try:
73
+ commands.putreg()
74
+ commands.putmem('0x{:x}'.format(util.get_pc()),
75
+ "1", display_result=False)
76
+ commands.putmem('0x{:x}'.format(util.get_sp()-1),
77
+ "2", display_result=False)
78
+ commands.put_breakpoints(BreakpointType.BpNormal)
79
+ commands.put_breakpoints(BreakpointType.BpHardware)
80
+ commands.put_breakpoints(BreakpointType.BpMemory)
81
+ except Exception:
82
+ pass
83
+ #commands.put_frames()
84
+ self.visited.add(thread)
85
+ # TODO: hoping to support this at some point once the relevant APIs are exposed
86
+ # frame = util.selected_frame()
87
+ # hashable_frame = (thread, frame)
88
+ # if first or hashable_frame not in self.visited:
89
+ # self.visited.add(hashable_frame)
90
+ try:
91
+ if first or self.regions or self.modules:
92
+ commands.put_regions()
93
+ self.regions = False
94
+ self.modules = False
95
+ except:
96
+ pass
97
+
98
+ def record_continued(self) -> None:
99
+ try:
100
+ proc = util.selected_process()
101
+ commands.put_state(proc)
102
+ commands.put_breakpoints(BreakpointType.BpNormal)
103
+ commands.put_breakpoints(BreakpointType.BpHardware)
104
+ commands.put_breakpoints(BreakpointType.BpMemory)
105
+ except Exception:
106
+ pass
107
+
108
+ def record_exited(self, exit_code: Optional[str] = None,
109
+ time: Optional[Schedule] = None) -> None:
110
+ trace = commands.STATE.require_trace()
111
+ if exit_code is not None:
112
+ trace.snapshot(f"Exited {exit_code}", time=time)
113
+ ipath = commands.PROCESS_PATTERN.format(procnum=util.last_process)
114
+ procobj = trace.proxy_object_path(ipath)
115
+ procobj.set_value('Exit Code', exit_code)
116
+ procobj.set_value('State', 'TERMINATED')
117
+
118
+
119
+ @dataclass(frozen=False)
120
+ class BrkState:
121
+ break_loc_counts: Dict[int, int] = field(default_factory=dict)
122
+
123
+ def update_brkloc_count(self, b, count: int) -> None:
124
+ self.break_loc_counts[b.GetID()] = count
125
+
126
+ def get_brkloc_count(self, b) -> int:
127
+ return self.break_loc_counts.get(b.GetID(), 0)
128
+
129
+ def del_brkloc_count(self, b) -> int:
130
+ if b not in self.break_loc_counts:
131
+ return 0 # TODO: Print a warning?
132
+ count = self.break_loc_counts[b.GetID()]
133
+ del self.break_loc_counts[b.GetID()]
134
+ return count
135
+
136
+
137
+ HOOK_STATE = HookState()
138
+ BRK_STATE = BrkState()
139
+ PROC_STATE: Dict[int, ProcessState] = {}
140
+
141
+
142
+ C = TypeVar('C', bound=Callable)
143
+
144
+
145
+ def log_errors(func: C) -> C:
146
+ """Wrap a function in a try-except that prints and reraises the exception.
147
+
148
+ This is needed for exceptions that occur during event callbacks.
149
+ """
150
+ @functools.wraps(func)
151
+ def _func(*args, **kwargs) -> Any:
152
+ try:
153
+ return func(*args, **kwargs)
154
+ except:
155
+ traceback.print_exc()
156
+ raise
157
+ return cast(C, _func)
158
+
159
+
160
+ @log_errors
161
+ def on_state_changed(*args) -> None:
162
+ # print("ON_STATE_CHANGED")
163
+ ev_type = args[0].event_type
164
+ # print(ev_type)
165
+ proc = util.selected_process()
166
+ trace = commands.STATE.require_trace()
167
+ with trace.client.batch():
168
+ with trace.open_tx("State changed proc {}".format(proc)):
169
+ commands.put_state(proc)
170
+ if proc not in PROC_STATE:
171
+ if ev_type == EventType.EVENT_EXIT_PROCESS:
172
+ on_process_deleted(args)
173
+ return
174
+ PROC_STATE[proc].waiting = False
175
+ try:
176
+ if ev_type == EventType.EVENT_RESUME_DEBUG:
177
+ on_cont()
178
+ elif ev_type == EventType.EVENT_PAUSE_DEBUG:
179
+ on_stop()
180
+ except Exception:
181
+ pass
182
+
183
+
184
+ @log_errors
185
+ def on_breakpoint_hit(*args) -> None:
186
+ # print("ON_THREADS_CHANGED")
187
+ proc = util.selected_process()
188
+ if proc not in PROC_STATE:
189
+ return
190
+ data = args[0].event_data
191
+ PROC_STATE[proc].breaks = True
192
+
193
+
194
+
195
+ @log_errors
196
+ def on_new_process(*args) -> None:
197
+ # print("ON_NEW_PROCESS")
198
+ trace = commands.STATE.trace
199
+ if trace is None:
200
+ return
201
+ with trace.client.batch():
202
+ with trace.open_tx("New Process {}".format(util.selected_process())):
203
+ commands.put_processes()
204
+
205
+
206
+ def on_process_selected() -> None:
207
+ # print("PROCESS_SELECTED")
208
+ proc = util.selected_process()
209
+ if proc not in PROC_STATE:
210
+ return
211
+ trace = commands.STATE.trace
212
+ if trace is None:
213
+ return
214
+ with trace.client.batch():
215
+ with trace.open_tx("Process {} selected".format(proc)):
216
+ PROC_STATE[proc].record()
217
+ commands.activate()
218
+
219
+
220
+ @log_errors
221
+ def on_process_deleted(*args) -> None:
222
+ # print("PROCESS_DELETED: args={}".format(args))
223
+ proc = util.selected_process()
224
+ on_exited(args)
225
+ if proc in PROC_STATE:
226
+ del PROC_STATE[proc]
227
+ trace = commands.STATE.trace
228
+ if trace is None:
229
+ return
230
+ with trace.client.batch():
231
+ with trace.open_tx("Process {} deleted".format(proc)):
232
+ commands.put_processes() # TODO: Could just delete the one....
233
+
234
+
235
+ @log_errors
236
+ def on_threads_changed(*args) -> None:
237
+ # print("ON_THREADS_CHANGED")
238
+ data = args[0].event_data
239
+ proc = util.selected_process()
240
+ if proc not in PROC_STATE:
241
+ return
242
+ util.threads[data.dwThreadId] = data
243
+ state = PROC_STATE[proc]
244
+ state.threads = True
245
+ state.waiting = False
246
+ trace = commands.STATE.require_trace()
247
+ with trace.client.batch():
248
+ with trace.open_tx("Threads changed proc {}".format(proc)):
249
+ #commands.put_threads()
250
+ commands.put_state(proc)
251
+
252
+
253
+
254
+ def on_thread_selected(*args) -> None:
255
+ # print("THREAD_SELECTED: args={}".format(args))
256
+ # sys.stdout.flush()
257
+ nthrd = args[0][1]
258
+ nproc = util.selected_process()
259
+ if nproc not in PROC_STATE:
260
+ return
261
+ trace = commands.STATE.trace
262
+ if trace is None:
263
+ return
264
+ with trace.client.batch():
265
+ with trace.open_tx("Thread {}.{} selected".format(nproc, nthrd)):
266
+ commands.put_state(nproc)
267
+ state = PROC_STATE[nproc]
268
+ if state.waiting:
269
+ state.record_continued()
270
+ else:
271
+ state.record()
272
+ commands.activate()
273
+
274
+
275
+ def on_register_changed(regnum) -> None:
276
+ # print("REGISTER_CHANGED")
277
+ proc = util.selected_process()
278
+ if proc not in PROC_STATE:
279
+ return
280
+ trace = commands.STATE.trace
281
+ if trace is None:
282
+ return
283
+ with trace.client.batch():
284
+ with trace.open_tx("Register {} changed".format(regnum)):
285
+ commands.putreg()
286
+ commands.activate()
287
+
288
+
289
+ def on_memory_changed(space) -> None:
290
+ proc = util.selected_process()
291
+ if proc not in PROC_STATE:
292
+ return
293
+ trace = commands.STATE.trace
294
+ if trace is None:
295
+ return
296
+ # Not great, but invalidate the whole space
297
+ # UI will only re-fetch what it needs
298
+ # But, some observations will not be recovered
299
+ try:
300
+ with trace.client.batch():
301
+ with trace.open_tx("Memory changed"):
302
+ commands.putmem_state(0, 2**64, 'unknown')
303
+ except Exception:
304
+ pass
305
+
306
+
307
+ def on_cont(*args) -> None:
308
+ # print("ON CONT")
309
+ proc = util.selected_process()
310
+ if proc not in PROC_STATE:
311
+ return
312
+ trace = commands.STATE.trace
313
+ if trace is None:
314
+ return
315
+ state = PROC_STATE[proc]
316
+ with trace.client.batch():
317
+ with trace.open_tx("Continued"):
318
+ state.record_continued()
319
+ return
320
+
321
+
322
+ def on_stop(*args) -> None:
323
+ # print("ON STOP")
324
+ proc = util.selected_process()
325
+ if proc not in PROC_STATE:
326
+ return
327
+ trace = commands.STATE.trace
328
+ if trace is None:
329
+ return
330
+ state = PROC_STATE[proc]
331
+ state.visited.clear()
332
+ time = None
333
+ with trace.client.batch():
334
+ with trace.open_tx("Stopped"):
335
+ description = "Stopped"
336
+ state.record(description, time)
337
+ try:
338
+ commands.put_event_thread()
339
+ except:
340
+ pass
341
+ commands.activate()
342
+
343
+
344
+ def on_exited(*args) -> None:
345
+ # print("ON EXITED")
346
+ trace = commands.STATE.trace
347
+ if trace is None:
348
+ return
349
+ state = PROC_STATE[util.last_process]
350
+ state.visited.clear()
351
+ with trace.client.batch():
352
+ with trace.open_tx("Exited"):
353
+ exit_code = args[0][0].event_data.dwExitCode
354
+ state.record_exited(exit_code)
355
+ commands.activate()
356
+
357
+
358
+ @log_errors
359
+ def on_modules_changed(*args) -> None:
360
+ # print("ON_MODULES_CHANGED")
361
+ #data = args[0].event_data
362
+ proc = util.selected_process()
363
+ if proc not in PROC_STATE:
364
+ return
365
+ state = PROC_STATE[proc]
366
+ state.modules = True
367
+ state.waiting = False
368
+ trace = commands.STATE.require_trace()
369
+ with trace.client.batch():
370
+ with trace.open_tx("Modules changed proc {}".format(proc)):
371
+ #commands.put_modules()
372
+ commands.put_state(proc)
373
+
374
+
375
+ def install_hooks() -> None:
376
+ # print("Installing hooks")
377
+ if HOOK_STATE.installed:
378
+ return
379
+ HOOK_STATE.installed = True
380
+
381
+ dbg = util.dbg.client
382
+ dbg.watch_debug_event(EventType.EVENT_OUTPUT_DEBUG_STRING, lambda x: on_breakpoint_hit(x))
383
+ dbg.watch_debug_event(EventType.EVENT_BREAKPOINT, lambda x: on_state_changed(x))
384
+ dbg.watch_debug_event(EventType.EVENT_SYSTEMBREAKPOINT, lambda x: on_state_changed(x))
385
+ dbg.watch_debug_event(EventType.EVENT_EXCEPTION, lambda x: on_state_changed(x))
386
+ dbg.watch_debug_event(EventType.EVENT_CREATE_THREAD, lambda x: on_threads_changed(x))
387
+ dbg.watch_debug_event(EventType.EVENT_EXIT_THREAD, lambda x: on_threads_changed(x))
388
+ dbg.watch_debug_event(EventType.EVENT_LOAD_DLL, lambda x: on_modules_changed(x))
389
+ dbg.watch_debug_event(EventType.EVENT_UNLOAD_DLL, lambda x: on_modules_changed(x))
390
+ dbg.watch_debug_event(EventType.EVENT_STEPPED, lambda x: on_state_changed(x))
391
+ dbg.watch_debug_event(EventType.EVENT_PAUSE_DEBUG, lambda x: on_state_changed(x))
392
+ dbg.watch_debug_event(EventType.EVENT_RESUME_DEBUG, lambda x: on_state_changed(x))
393
+ dbg.watch_debug_event(EventType.EVENT_ATTACH, lambda x: on_state_changed(x))
394
+ dbg.watch_debug_event(EventType.EVENT_DETACH, lambda x: on_state_changed(x))
395
+ dbg.watch_debug_event(EventType.EVENT_INIT_DEBUG, lambda x: on_state_changed(x))
396
+ dbg.watch_debug_event(EventType.EVENT_STOP_DEBUG, lambda x: on_state_changed(x))
397
+ dbg.watch_debug_event(EventType.EVENT_CREATE_PROCESS, lambda x: on_state_changed(x))
398
+ dbg.watch_debug_event(EventType.EVENT_EXIT_PROCESS, lambda x: on_state_changed(x))
399
+
400
+
401
+ def remove_hooks() -> None:
402
+ # print("Removing hooks")
403
+ if HOOK_STATE.installed:
404
+ HOOK_STATE.installed = False
405
+
406
+
407
+ def enable_current_process() -> None:
408
+ # print("Enable current process")
409
+ proc = util.selected_process()
410
+ PROC_STATE[proc] = ProcessState()
411
+
412
+
413
+ def disable_current_process() -> None:
414
+ # print("Disable current process")
415
+ proc = util.selected_process()
416
+ if proc in PROC_STATE:
417
+ # Silently ignore already disabled
418
+ del PROC_STATE[proc]
419
+