hilda 2.0.16__py3-none-any.whl → 3.0.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.
- hilda/_version.py +9 -4
- hilda/breakpoints.py +480 -0
- hilda/hilda_client.py +78 -305
- hilda/ipython_extensions/keybindings.py +34 -11
- hilda/objective_c_class.py +2 -6
- hilda/snippets/mach/CFRunLoopServiceMachPort_hooks.py +2 -5
- hilda/snippets/macho/all_image_infos.py +7 -4
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/METADATA +15 -15
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/RECORD +13 -12
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/WHEEL +1 -1
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/LICENSE +0 -0
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/entry_points.txt +0 -0
- {hilda-2.0.16.dist-info → hilda-3.0.0.dist-info}/top_level.txt +0 -0
hilda/hilda_client.py
CHANGED
|
@@ -5,7 +5,6 @@ import importlib.util
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
-
import pickle
|
|
9
8
|
import struct
|
|
10
9
|
import sys
|
|
11
10
|
import time
|
|
@@ -18,10 +17,9 @@ from functools import cached_property, wraps
|
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any, Callable, Optional, Union
|
|
20
19
|
|
|
20
|
+
import click
|
|
21
21
|
import hexdump
|
|
22
22
|
import IPython
|
|
23
|
-
from humanfriendly import prompts
|
|
24
|
-
from humanfriendly.terminal.html import html_to_ansi
|
|
25
23
|
from IPython.core.magic import register_line_magic # noqa: F401
|
|
26
24
|
from pygments import highlight
|
|
27
25
|
from pygments.formatters import TerminalTrueColorFormatter
|
|
@@ -30,11 +28,13 @@ from tqdm import tqdm
|
|
|
30
28
|
from traitlets.config import Config
|
|
31
29
|
|
|
32
30
|
from hilda import objective_c_class
|
|
31
|
+
from hilda.breakpoints import BreakpointList, HildaBreakpoint, WhereType
|
|
33
32
|
from hilda.common import CfSerializable, selection_prompt
|
|
34
33
|
from hilda.exceptions import AccessingMemoryError, AccessingRegisterError, AddingLldbSymbolError, \
|
|
35
|
-
|
|
34
|
+
ConvertingFromNSObjectError, ConvertingToNsObjectError, CreatingObjectiveCSymbolError, \
|
|
36
35
|
DisableJetsamMemoryChecksError, EvaluatingExpressionError, HildaException, InvalidThreadIndexError, \
|
|
37
36
|
SymbolAbsentError
|
|
37
|
+
from hilda.ipython_extensions.keybindings import get_keybindings
|
|
38
38
|
from hilda.lldb_importer import lldb
|
|
39
39
|
from hilda.objective_c_symbol import ObjectiveCSymbol
|
|
40
40
|
from hilda.registers import Registers
|
|
@@ -50,31 +50,12 @@ except ImportError:
|
|
|
50
50
|
lldb.KEYSTONE_SUPPORT = False
|
|
51
51
|
print('failed to import keystone. disabling some features')
|
|
52
52
|
|
|
53
|
-
hilda_art = Path(__file__).resolve().parent.joinpath('hilda_ascii_art.html').read_text()
|
|
54
|
-
|
|
55
|
-
GREETING = f"""
|
|
56
|
-
{hilda_art}
|
|
57
|
-
|
|
58
|
-
<b>Hilda has been successfully loaded! 😎
|
|
59
|
-
Usage:
|
|
60
|
-
<span style="color: magenta">p</span> Global to access all features.
|
|
61
|
-
<span style="color: magenta">F1</span> Show UI.
|
|
62
|
-
<span style="color: magenta">F2</span> Toggle enabling of stdout & stderr.
|
|
63
|
-
<span style="color: magenta">F7</span> Step Into.
|
|
64
|
-
<span style="color: magenta">F8</span> Step Over.
|
|
65
|
-
<span style="color: magenta">F9</span> Continue.
|
|
66
|
-
<span style="color: magenta">F10</span> Stop.
|
|
67
|
-
|
|
68
|
-
Have a nice flight ✈️! Starting an IPython shell...
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
53
|
|
|
72
54
|
def disable_logs() -> None:
|
|
73
55
|
logging.getLogger('asyncio').disabled = True
|
|
74
56
|
logging.getLogger('parso.cache').disabled = True
|
|
75
57
|
logging.getLogger('parso.cache.pickle').disabled = True
|
|
76
58
|
logging.getLogger('parso.python.diff').disabled = True
|
|
77
|
-
logging.getLogger('humanfriendly.prompts').disabled = True
|
|
78
59
|
logging.getLogger('blib2to3.pgen2.driver').disabled = True
|
|
79
60
|
logging.getLogger('hilda.launch_lldb').setLevel(logging.INFO)
|
|
80
61
|
|
|
@@ -82,6 +63,17 @@ def disable_logs() -> None:
|
|
|
82
63
|
SerializableSymbol = namedtuple('SerializableSymbol', 'address type_ filename')
|
|
83
64
|
|
|
84
65
|
|
|
66
|
+
@dataclass
|
|
67
|
+
class HelpSnippet:
|
|
68
|
+
""" Small snippet line to occur from `HildaClient.show_help()` """
|
|
69
|
+
|
|
70
|
+
key: str
|
|
71
|
+
description: str
|
|
72
|
+
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
return click.style(self.key.ljust(8), bold=True, fg='magenta') + click.style(self.description, bold=True)
|
|
75
|
+
|
|
76
|
+
|
|
85
77
|
@dataclass
|
|
86
78
|
class Configs:
|
|
87
79
|
""" Configuration settings for evaluation and monitoring. """
|
|
@@ -125,39 +117,17 @@ def stop_is_needed(func: Callable):
|
|
|
125
117
|
return wrapper
|
|
126
118
|
|
|
127
119
|
|
|
128
|
-
class HildaBreakpoint:
|
|
129
|
-
def __init__(self, hilda_client: 'HildaClient', lldb_breakpoint: lldb.SBBreakpoint,
|
|
130
|
-
address: Union[str, int], forced: bool = False, options: Optional[typing.Mapping] = None,
|
|
131
|
-
callback: Optional[Callable] = None) -> None:
|
|
132
|
-
self._hilda_client = hilda_client
|
|
133
|
-
self.address = address
|
|
134
|
-
self.forced = forced
|
|
135
|
-
self.options = options
|
|
136
|
-
self.callback = callback
|
|
137
|
-
self.lldb_breakpoint = lldb_breakpoint
|
|
138
|
-
|
|
139
|
-
def __repr__(self) -> str:
|
|
140
|
-
return (f'<{self.__class__.__name__} LLDB:{self.lldb_breakpoint} FORCED:{self.forced} OPTIONS:{self.options} '
|
|
141
|
-
f'CALLBACK:{self.callback}>')
|
|
142
|
-
|
|
143
|
-
def __str__(self) -> str:
|
|
144
|
-
return repr(self)
|
|
145
|
-
|
|
146
|
-
def remove(self) -> None:
|
|
147
|
-
self._hilda_client.remove_hilda_breakpoint(self.lldb_breakpoint.id)
|
|
148
|
-
|
|
149
|
-
|
|
150
120
|
class HildaClient:
|
|
151
121
|
RETVAL_BIT_COUNT = 64
|
|
152
122
|
|
|
153
|
-
def __init__(self, debugger: lldb.SBDebugger):
|
|
123
|
+
def __init__(self, debugger: lldb.SBDebugger) -> None:
|
|
154
124
|
self.logger = logging.getLogger(__name__)
|
|
155
125
|
self.endianness = '<'
|
|
156
126
|
self.debugger = debugger
|
|
157
127
|
self.target = debugger.GetSelectedTarget()
|
|
158
128
|
self.process = self.target.GetProcess()
|
|
159
129
|
self.symbols = SymbolsJar.create(self)
|
|
160
|
-
self.breakpoints =
|
|
130
|
+
self.breakpoints = BreakpointList(self)
|
|
161
131
|
self.captured_objects = {}
|
|
162
132
|
self.registers = Registers(self)
|
|
163
133
|
self.arch = self.target.GetTriple().split('-')[0]
|
|
@@ -177,14 +147,15 @@ class HildaClient:
|
|
|
177
147
|
self.log_info(f'Target: {self.target}')
|
|
178
148
|
self.log_info(f'Process: {self.process}')
|
|
179
149
|
|
|
180
|
-
def hd(self, buf):
|
|
150
|
+
def hd(self, buf: bytes) -> None:
|
|
181
151
|
"""
|
|
182
|
-
Print
|
|
152
|
+
Print hexdump representation for given buffer.
|
|
153
|
+
|
|
183
154
|
:param buf: buffer to print in hexdump form
|
|
184
155
|
"""
|
|
185
|
-
|
|
156
|
+
hexdump.hexdump(buf)
|
|
186
157
|
|
|
187
|
-
def lsof(self) -> dict:
|
|
158
|
+
def lsof(self) -> dict[int, Any]:
|
|
188
159
|
"""
|
|
189
160
|
Get dictionary of all open FDs
|
|
190
161
|
:return: Mapping between open FDs and their paths
|
|
@@ -201,7 +172,7 @@ class HildaClient:
|
|
|
201
172
|
if i == depth:
|
|
202
173
|
break
|
|
203
174
|
row = ''
|
|
204
|
-
row +=
|
|
175
|
+
row += click.style(f'0x{frame.addr.GetFileAddress():x} ', fg='cyan')
|
|
205
176
|
row += str(frame)
|
|
206
177
|
if i == 0:
|
|
207
178
|
# first line
|
|
@@ -222,7 +193,7 @@ class HildaClient:
|
|
|
222
193
|
if result:
|
|
223
194
|
raise DisableJetsamMemoryChecksError()
|
|
224
195
|
|
|
225
|
-
def symbol(self, address):
|
|
196
|
+
def symbol(self, address: int) -> Symbol:
|
|
226
197
|
"""
|
|
227
198
|
Get symbol object for a given address
|
|
228
199
|
:param address:
|
|
@@ -230,7 +201,7 @@ class HildaClient:
|
|
|
230
201
|
"""
|
|
231
202
|
return Symbol.create(address, self)
|
|
232
203
|
|
|
233
|
-
def objc_symbol(self, address) -> ObjectiveCSymbol:
|
|
204
|
+
def objc_symbol(self, address: int) -> ObjectiveCSymbol:
|
|
234
205
|
"""
|
|
235
206
|
Get objc symbol wrapper for given address
|
|
236
207
|
:param address:
|
|
@@ -241,11 +212,12 @@ class HildaClient:
|
|
|
241
212
|
except HildaException as e:
|
|
242
213
|
raise CreatingObjectiveCSymbolError from e
|
|
243
214
|
|
|
244
|
-
def inject(self, filename):
|
|
215
|
+
def inject(self, filename: str) -> SymbolsJar:
|
|
245
216
|
"""
|
|
246
|
-
Inject a single library into currently running process
|
|
247
|
-
|
|
248
|
-
:
|
|
217
|
+
Inject a single library into currently running process.
|
|
218
|
+
|
|
219
|
+
:param filename: library to inject (dylib)
|
|
220
|
+
:return: SymbolsJar
|
|
249
221
|
"""
|
|
250
222
|
module = self.target.FindModule(lldb.SBFileSpec(os.path.basename(filename), False))
|
|
251
223
|
if module.file.basename is not None:
|
|
@@ -459,7 +431,8 @@ class HildaClient:
|
|
|
459
431
|
|
|
460
432
|
def set_register(self, name: str, value: Union[float, int]) -> None:
|
|
461
433
|
"""
|
|
462
|
-
Set value for register by its name
|
|
434
|
+
Set value for register by its name.
|
|
435
|
+
|
|
463
436
|
:param name: Register name
|
|
464
437
|
:param value: Register value
|
|
465
438
|
"""
|
|
@@ -473,7 +446,8 @@ class HildaClient:
|
|
|
473
446
|
|
|
474
447
|
def objc_call(self, obj: int, selector: str, *params):
|
|
475
448
|
"""
|
|
476
|
-
Simulate a call to an objc selector
|
|
449
|
+
Simulate a call to an objc selector.
|
|
450
|
+
|
|
477
451
|
:param obj: obj to pass into `objc_msgSend`
|
|
478
452
|
:param selector: selector to execute
|
|
479
453
|
:param params: any other additional parameters the selector requires
|
|
@@ -489,7 +463,7 @@ class HildaClient:
|
|
|
489
463
|
with self.stopped():
|
|
490
464
|
return self.evaluate_expression(call_expression)
|
|
491
465
|
|
|
492
|
-
def call(self, address, argv: list = None):
|
|
466
|
+
def call(self, address, argv: Optional[list] = None):
|
|
493
467
|
"""
|
|
494
468
|
Call function at given address with given parameters
|
|
495
469
|
:param address:
|
|
@@ -506,107 +480,11 @@ class HildaClient:
|
|
|
506
480
|
"""
|
|
507
481
|
Monitor every time a given address is called
|
|
508
482
|
|
|
509
|
-
|
|
510
|
-
regs={reg1: format}
|
|
511
|
-
will print register values
|
|
512
|
-
|
|
513
|
-
Available formats:
|
|
514
|
-
x: hex
|
|
515
|
-
s: string
|
|
516
|
-
cf: use CFCopyDescription() to get more informative description of the object
|
|
517
|
-
po: use LLDB po command
|
|
518
|
-
std::string: for std::string
|
|
519
|
-
|
|
520
|
-
User defined function, will be called like `format_function(hilda_client, value)`.
|
|
521
|
-
|
|
522
|
-
For example:
|
|
523
|
-
regs={'x0': 'x'} -> x0 will be printed in HEX format
|
|
524
|
-
expr={lldb_expression: format}
|
|
525
|
-
lldb_expression can be for example '$x0' or '$arg1'
|
|
526
|
-
format behaves just like 'regs' option
|
|
527
|
-
retval=format
|
|
528
|
-
Print function's return value. The format is the same as regs format.
|
|
529
|
-
stop=True
|
|
530
|
-
force a stop at every hit
|
|
531
|
-
bt=True
|
|
532
|
-
print backtrace
|
|
533
|
-
cmd=[cmd1, cmd2]
|
|
534
|
-
run several LLDB commands, one by another
|
|
535
|
-
force_return=value
|
|
536
|
-
force a return from function with the specified value
|
|
537
|
-
name=some_value
|
|
538
|
-
use `some_name` instead of the symbol name automatically extracted from the calling frame
|
|
539
|
-
override=True
|
|
540
|
-
override previous break point at same location
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
:param address:
|
|
544
|
-
:param condition: set as a conditional breakpoint using an lldb expression
|
|
545
|
-
:param options:
|
|
546
|
-
:return:
|
|
483
|
+
Alias of self.breakpoints.add_monitor()
|
|
547
484
|
"""
|
|
485
|
+
return self.breakpoints.add_monitor(address, condition, **options)
|
|
548
486
|
|
|
549
|
-
|
|
550
|
-
"""
|
|
551
|
-
:param HildaClient hilda: Hilda client.
|
|
552
|
-
:param lldb.SBFrame frame: LLDB Frame object.
|
|
553
|
-
:param lldb.SBBreakpointLocation bp_loc: LLDB Breakpoint location object.
|
|
554
|
-
:param dict options: User defined options.
|
|
555
|
-
"""
|
|
556
|
-
bp = bp_loc.GetBreakpoint()
|
|
557
|
-
|
|
558
|
-
symbol = hilda.symbol(hilda.frame.addr.GetLoadAddress(hilda.target)) # type: Symbol
|
|
559
|
-
|
|
560
|
-
# by default, attempt to resolve the symbol name through lldb
|
|
561
|
-
name = str(symbol.lldb_symbol)
|
|
562
|
-
if options.get('name', False):
|
|
563
|
-
name = options['name']
|
|
564
|
-
|
|
565
|
-
log_message = f'🚨 #{bp.id} 0x{symbol:x} {name} - Thread #{self.thread.idx}:{hex(self.thread.id)}'
|
|
566
|
-
|
|
567
|
-
if 'regs' in options:
|
|
568
|
-
log_message += '\nregs:'
|
|
569
|
-
for name, fmt in options['regs'].items():
|
|
570
|
-
value = hilda.symbol(frame.FindRegister(name).unsigned)
|
|
571
|
-
log_message += f'\n\t{name} = {hilda._monitor_format_value(fmt, value)}'
|
|
572
|
-
|
|
573
|
-
if 'expr' in options:
|
|
574
|
-
log_message += '\nexpr:'
|
|
575
|
-
for name, fmt in options['expr'].items():
|
|
576
|
-
value = hilda.symbol(hilda.evaluate_expression(name))
|
|
577
|
-
log_message += f'\n\t{name} = {hilda._monitor_format_value(fmt, value)}'
|
|
578
|
-
|
|
579
|
-
force_return = options.get('force_return')
|
|
580
|
-
if force_return is not None:
|
|
581
|
-
hilda.force_return(force_return)
|
|
582
|
-
log_message += f'\nforced return: {force_return}'
|
|
583
|
-
|
|
584
|
-
if options.get('bt'):
|
|
585
|
-
# bugfix: for callstacks from xpc events
|
|
586
|
-
hilda.finish()
|
|
587
|
-
for frame in hilda.bt():
|
|
588
|
-
log_message += f'\n\t{frame[0]} {frame[1]}'
|
|
589
|
-
|
|
590
|
-
retval = options.get('retval')
|
|
591
|
-
if retval is not None:
|
|
592
|
-
# return from function
|
|
593
|
-
hilda.finish()
|
|
594
|
-
value = hilda.evaluate_expression('$arg1')
|
|
595
|
-
log_message += f'\nreturned: {hilda._monitor_format_value(retval, value)}'
|
|
596
|
-
|
|
597
|
-
hilda.log_info(log_message)
|
|
598
|
-
|
|
599
|
-
for cmd in options.get('cmd', []):
|
|
600
|
-
hilda.lldb_handle_command(cmd)
|
|
601
|
-
|
|
602
|
-
if options.get('stop', False):
|
|
603
|
-
hilda.log_info('Process remains stopped and focused on current thread')
|
|
604
|
-
else:
|
|
605
|
-
hilda.cont()
|
|
606
|
-
|
|
607
|
-
return self.bp(address, callback, condition=condition, **options)
|
|
608
|
-
|
|
609
|
-
def show_current_source(self):
|
|
487
|
+
def show_current_source(self) -> None:
|
|
610
488
|
""" print current source code if possible """
|
|
611
489
|
self.lldb_handle_command('f')
|
|
612
490
|
|
|
@@ -632,26 +510,7 @@ class HildaClient:
|
|
|
632
510
|
if self.ui_manager.active:
|
|
633
511
|
self.ui_manager.show()
|
|
634
512
|
|
|
635
|
-
def
|
|
636
|
-
"""
|
|
637
|
-
Remove all breakpoints created by Hilda
|
|
638
|
-
:param remove_forced: include removed of "forced" breakpoints
|
|
639
|
-
"""
|
|
640
|
-
breakpoints = list(self.breakpoints.items())
|
|
641
|
-
for bp_id, bp in breakpoints:
|
|
642
|
-
if remove_forced or not bp.forced:
|
|
643
|
-
self.remove_hilda_breakpoint(bp_id)
|
|
644
|
-
|
|
645
|
-
def remove_hilda_breakpoint(self, bp_id: int) -> None:
|
|
646
|
-
"""
|
|
647
|
-
Remove a single breakpoint placed by Hilda
|
|
648
|
-
:param bp_id: Breakpoint's ID
|
|
649
|
-
"""
|
|
650
|
-
self.target.BreakpointDelete(bp_id)
|
|
651
|
-
del self.breakpoints[bp_id]
|
|
652
|
-
self.log_info(f'BP #{bp_id} has been removed')
|
|
653
|
-
|
|
654
|
-
def force_return(self, value=0):
|
|
513
|
+
def force_return(self, value: int = 0) -> None:
|
|
655
514
|
"""
|
|
656
515
|
Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
|
|
657
516
|
yielding a specified value.
|
|
@@ -661,11 +520,11 @@ class HildaClient:
|
|
|
661
520
|
self.finish()
|
|
662
521
|
self.set_register('x0', value)
|
|
663
522
|
|
|
664
|
-
def proc_info(self):
|
|
523
|
+
def proc_info(self) -> None:
|
|
665
524
|
""" Print information about currently running mapped process. """
|
|
666
525
|
print(self.process)
|
|
667
526
|
|
|
668
|
-
def print_proc_entitlements(self):
|
|
527
|
+
def print_proc_entitlements(self) -> None:
|
|
669
528
|
""" Get the plist embedded inside the process' __LINKEDIT section. """
|
|
670
529
|
linkedit_section = self.target.modules[0].FindSection('__LINKEDIT')
|
|
671
530
|
linkedit_data = self.symbol(linkedit_section.GetLoadAddress(self.target)).peek(linkedit_section.size)
|
|
@@ -675,112 +534,24 @@ class HildaClient:
|
|
|
675
534
|
entitlements = str(linkedit_data[linkedit_data.find(b'<?xml'):].split(b'\xfa', 1)[0], 'utf8')
|
|
676
535
|
print(highlight(entitlements, XmlLexer(), TerminalTrueColorFormatter()))
|
|
677
536
|
|
|
678
|
-
def bp(self, address_or_name:
|
|
679
|
-
|
|
537
|
+
def bp(self, address_or_name: WhereType, callback: Optional[Callable] = None, condition: Optional[str] = None,
|
|
538
|
+
guarded: bool = False, description: Optional[str] = None, **options) -> HildaBreakpoint:
|
|
680
539
|
"""
|
|
681
540
|
Add a breakpoint
|
|
682
|
-
|
|
541
|
+
|
|
542
|
+
Alias of self.breakpoints.add()
|
|
543
|
+
|
|
544
|
+
:param address_or_name: Where to place the breakpoint
|
|
683
545
|
:param condition: set as a conditional breakpoint using lldb expression
|
|
684
546
|
:param callback: callback(hilda, *args) to be called
|
|
685
|
-
:param
|
|
686
|
-
:param
|
|
547
|
+
:param guarded: whether the breakpoint should be protected frm usual removal.
|
|
548
|
+
:param description: Attach a breakpoint description
|
|
687
549
|
:param options: can contain an `override` keyword to specify if to override an existing BP
|
|
688
550
|
:return: native LLDB breakpoint
|
|
689
551
|
"""
|
|
690
|
-
|
|
691
|
-
override = True if options.get('override', True) else False
|
|
692
|
-
if override or prompts.prompt_for_confirmation('A breakpoint already exist in given location. '
|
|
693
|
-
'Would you like to delete the previous one?', True):
|
|
694
|
-
breakpoints = list(self.breakpoints.items())
|
|
695
|
-
for bp_id, bp in breakpoints:
|
|
696
|
-
if address_or_name == bp.address:
|
|
697
|
-
self.remove_hilda_breakpoint(bp_id)
|
|
698
|
-
|
|
699
|
-
if isinstance(address_or_name, int):
|
|
700
|
-
bp = self.target.BreakpointCreateByAddress(address_or_name)
|
|
701
|
-
elif isinstance(address_or_name, str):
|
|
702
|
-
bp = self.target.BreakpointCreateByName(address_or_name)
|
|
703
|
-
|
|
704
|
-
if condition is not None:
|
|
705
|
-
bp.SetCondition(condition)
|
|
706
|
-
|
|
707
|
-
# add into Hilda's internal list of breakpoints
|
|
708
|
-
self.breakpoints[bp.id] = HildaBreakpoint(self, bp, address=address_or_name, forced=forced, options=options,
|
|
709
|
-
callback=callback)
|
|
710
|
-
|
|
711
|
-
if callback is not None:
|
|
712
|
-
bp.SetScriptCallbackFunction('lldb.hilda_client.bp_callback_router')
|
|
713
|
-
|
|
714
|
-
self.log_info(f'Breakpoint #{bp.id} has been set')
|
|
715
|
-
return self.breakpoints[bp.id]
|
|
716
|
-
|
|
717
|
-
def bp_callback_router(self, frame, bp_loc, *_):
|
|
718
|
-
"""
|
|
719
|
-
Route the breakpoint callback the specific breakpoint callback.
|
|
720
|
-
:param lldb.SBFrame frame: LLDB Frame object.
|
|
721
|
-
:param lldb.SBBreakpointLocation bp_loc: LLDB Breakpoint location object.
|
|
722
|
-
"""
|
|
723
|
-
bp_id = bp_loc.GetBreakpoint().GetID()
|
|
724
|
-
self._bp_frame = frame
|
|
725
|
-
try:
|
|
726
|
-
self.breakpoints[bp_id].callback(self, frame, bp_loc, self.breakpoints[bp_id].options)
|
|
727
|
-
finally:
|
|
728
|
-
self._bp_frame = None
|
|
729
|
-
|
|
730
|
-
def show_hilda_breakpoints(self):
|
|
731
|
-
""" Show existing breakpoints created by Hilda. """
|
|
732
|
-
for bp_id, bp in self.breakpoints.items():
|
|
733
|
-
print(f'🚨 Breakpoint #{bp_id}: Forced: {bp.forced}')
|
|
734
|
-
if isinstance(bp.address, int):
|
|
735
|
-
print(f'\tAddress: 0x{bp.address:x}')
|
|
736
|
-
elif isinstance(bp.address, str):
|
|
737
|
-
print(f'\tName: {bp.address}')
|
|
738
|
-
print(f'\tOptions: {bp.options}')
|
|
739
|
-
|
|
740
|
-
def save(self, filename=None):
|
|
741
|
-
"""
|
|
742
|
-
Save loaded symbols map (for loading later using the load() command)
|
|
743
|
-
:param filename: optional filename for where to store
|
|
744
|
-
"""
|
|
745
|
-
if filename is None:
|
|
746
|
-
filename = self._get_saved_state_filename()
|
|
747
|
-
|
|
748
|
-
self.log_info(f'saving current state info: {filename}')
|
|
749
|
-
with open(filename, 'wb') as f:
|
|
750
|
-
symbols_copy = {}
|
|
751
|
-
for k, v in self.symbols.items():
|
|
752
|
-
# converting the symbols into serializable objects
|
|
753
|
-
symbols_copy[k] = SerializableSymbol(address=int(v),
|
|
754
|
-
type_=v.type_,
|
|
755
|
-
filename=v.filename)
|
|
756
|
-
pickle.dump(symbols_copy, f)
|
|
552
|
+
return self.breakpoints.add(address_or_name, callback, condition, guarded, description=description, **options)
|
|
757
553
|
|
|
758
|
-
def
|
|
759
|
-
"""
|
|
760
|
-
Load an existing symbols map (previously saved by the save() command)
|
|
761
|
-
:param filename: filename to load from
|
|
762
|
-
"""
|
|
763
|
-
if filename is None:
|
|
764
|
-
filename = self._get_saved_state_filename()
|
|
765
|
-
|
|
766
|
-
self.log_info(f'loading current state from: {filename}')
|
|
767
|
-
with open(filename, 'rb') as f:
|
|
768
|
-
symbols_copy = pickle.load(f)
|
|
769
|
-
|
|
770
|
-
for k, v in tqdm(symbols_copy.items()):
|
|
771
|
-
self.symbols[k] = self.symbol(v.address)
|
|
772
|
-
|
|
773
|
-
# perform sanity test for symbol rand
|
|
774
|
-
if self.symbols.rand() == 0 and self.symbols.rand() == 0:
|
|
775
|
-
# rand returning 0 twice means the loaded file is probably outdated
|
|
776
|
-
raise BrokenLocalSymbolsJarError()
|
|
777
|
-
|
|
778
|
-
# assuming the first main image will always change
|
|
779
|
-
self.rebind_symbols(image_range=[0, 0])
|
|
780
|
-
self.init_dynamic_environment()
|
|
781
|
-
self._symbols_loaded = True
|
|
782
|
-
|
|
783
|
-
def po(self, expression, cast=None):
|
|
554
|
+
def po(self, expression: str, cast: Optional[str] = None) -> str:
|
|
784
555
|
"""
|
|
785
556
|
Print given object using LLDB's po command
|
|
786
557
|
|
|
@@ -804,7 +575,7 @@ class HildaClient:
|
|
|
804
575
|
raise EvaluatingExpressionError(res.GetError())
|
|
805
576
|
return res.GetOutput().strip()
|
|
806
577
|
|
|
807
|
-
def globalize_symbols(self):
|
|
578
|
+
def globalize_symbols(self) -> None:
|
|
808
579
|
"""
|
|
809
580
|
Make all symbols in python's global scope
|
|
810
581
|
"""
|
|
@@ -817,18 +588,18 @@ class HildaClient:
|
|
|
817
588
|
and '.' not in name:
|
|
818
589
|
self._add_global(name, value, reserved_names)
|
|
819
590
|
|
|
820
|
-
def jump(self, symbol: int):
|
|
591
|
+
def jump(self, symbol: int) -> None:
|
|
821
592
|
""" jump to given symbol """
|
|
822
593
|
self.lldb_handle_command(f'j *{symbol}')
|
|
823
594
|
|
|
824
|
-
def lldb_handle_command(self, cmd):
|
|
595
|
+
def lldb_handle_command(self, cmd: str) -> None:
|
|
825
596
|
"""
|
|
826
597
|
Execute an LLDB command
|
|
827
598
|
|
|
828
599
|
For example:
|
|
829
600
|
lldb_handle_command('register read')
|
|
830
601
|
|
|
831
|
-
:param cmd:
|
|
602
|
+
:param cmd: LLDB command
|
|
832
603
|
"""
|
|
833
604
|
self.debugger.HandleCommand(cmd)
|
|
834
605
|
|
|
@@ -968,7 +739,7 @@ class HildaClient:
|
|
|
968
739
|
return self.thread.GetSelectedFrame()
|
|
969
740
|
|
|
970
741
|
@contextmanager
|
|
971
|
-
def stopped(self, interval=0):
|
|
742
|
+
def stopped(self, interval: int = 0):
|
|
972
743
|
"""
|
|
973
744
|
Context-Manager for execution while process is stopped.
|
|
974
745
|
If interval is supplied, then if the device is in running state, it will sleep for the interval
|
|
@@ -988,7 +759,7 @@ class HildaClient:
|
|
|
988
759
|
self.cont()
|
|
989
760
|
|
|
990
761
|
@contextmanager
|
|
991
|
-
def safe_malloc(self, size):
|
|
762
|
+
def safe_malloc(self, size: int):
|
|
992
763
|
"""
|
|
993
764
|
Context-Manager for allocating a block of memory which is freed afterwards
|
|
994
765
|
:param size:
|
|
@@ -1097,19 +868,36 @@ class HildaClient:
|
|
|
1097
868
|
return
|
|
1098
869
|
client.finish()
|
|
1099
870
|
client.log_info(f'Desired module has been loaded: {expression}. Process remains stopped')
|
|
1100
|
-
|
|
1101
|
-
client.
|
|
871
|
+
bp_id = bp_loc.GetBreakpoint().GetID()
|
|
872
|
+
client.breakpoints.remove(bp_id)
|
|
1102
873
|
|
|
1103
874
|
self.bp('dlopen', bp)
|
|
1104
875
|
self.cont()
|
|
1105
876
|
|
|
877
|
+
def show_help(self, *_) -> None:
|
|
878
|
+
"""
|
|
879
|
+
Show banner help message
|
|
880
|
+
"""
|
|
881
|
+
help_snippets = [HelpSnippet(key='p', description='Global to access all features')]
|
|
882
|
+
for keybinding in get_keybindings(self):
|
|
883
|
+
help_snippets.append(HelpSnippet(key=keybinding.key.upper(), description=keybinding.description))
|
|
884
|
+
|
|
885
|
+
for help_snippet in help_snippets:
|
|
886
|
+
click.echo(help_snippet)
|
|
887
|
+
|
|
1106
888
|
def interact(self, additional_namespace: Optional[typing.Mapping] = None,
|
|
1107
889
|
startup_files: Optional[list[str]] = None) -> None:
|
|
1108
890
|
""" Start an interactive Hilda shell """
|
|
1109
891
|
if not self._dynamic_env_loaded:
|
|
1110
892
|
self.init_dynamic_environment()
|
|
1111
|
-
|
|
1112
|
-
|
|
893
|
+
|
|
894
|
+
# Show greeting
|
|
895
|
+
click.secho('Hilda has been successfully loaded! 😎', bold=True)
|
|
896
|
+
click.secho('Usage:', bold=True)
|
|
897
|
+
self.show_help()
|
|
898
|
+
click.echo(click.style('Have a nice flight ✈️! Starting an IPython shell...', bold=True))
|
|
899
|
+
|
|
900
|
+
# Configure and start IPython shell
|
|
1113
901
|
ipython_config = Config()
|
|
1114
902
|
ipython_config.IPCompleter.use_jedi = True
|
|
1115
903
|
ipython_config.BaseIPythonApplication.profile = 'hilda'
|
|
@@ -1215,21 +1003,6 @@ class HildaClient:
|
|
|
1215
1003
|
else:
|
|
1216
1004
|
return value[0].peek_str()
|
|
1217
1005
|
|
|
1218
|
-
def _monitor_format_value(self, fmt, value):
|
|
1219
|
-
if callable(fmt):
|
|
1220
|
-
return fmt(self, value)
|
|
1221
|
-
formatters = {
|
|
1222
|
-
'x': lambda val: f'0x{int(val):x}',
|
|
1223
|
-
's': lambda val: val.peek_str() if val else None,
|
|
1224
|
-
'cf': lambda val: val.cf_description,
|
|
1225
|
-
'po': lambda val: val.po(),
|
|
1226
|
-
'std::string': self._std_string
|
|
1227
|
-
}
|
|
1228
|
-
if fmt in formatters:
|
|
1229
|
-
return formatters[fmt](value)
|
|
1230
|
-
else:
|
|
1231
|
-
return f'{value:x} (unsupported format)'
|
|
1232
|
-
|
|
1233
1006
|
@cached_property
|
|
1234
1007
|
def _object_identifier(self) -> Symbol:
|
|
1235
1008
|
return self.symbols.objc_getClass('VMUObjectIdentifier').objc_call('alloc').objc_call(
|
|
@@ -1,24 +1,47 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
1
4
|
from prompt_toolkit.enums import DEFAULT_BUFFER
|
|
2
5
|
from prompt_toolkit.filters import EmacsInsertMode, HasFocus, HasSelection, ViInsertMode
|
|
3
6
|
from prompt_toolkit.keys import Keys
|
|
4
7
|
|
|
5
8
|
|
|
9
|
+
@dataclass
|
|
10
|
+
class Keybinding:
|
|
11
|
+
key: str
|
|
12
|
+
description: str
|
|
13
|
+
callback: Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_keybindings(hilda_client) -> list[Keybinding]:
|
|
17
|
+
"""
|
|
18
|
+
Get list of keybindings
|
|
19
|
+
|
|
20
|
+
:param hilda.hilda_client.HildaClient hilda_client: Hilda client to bind the keys to operations
|
|
21
|
+
"""
|
|
22
|
+
return [
|
|
23
|
+
Keybinding(key=Keys.F1, description='Show this help', callback=hilda_client.show_help),
|
|
24
|
+
Keybinding(key=Keys.F2, description='Show process state UI', callback=hilda_client.ui_manager.show),
|
|
25
|
+
Keybinding(key=Keys.F3, description='Toggle enabling of stdout & stderr',
|
|
26
|
+
callback=hilda_client.toggle_enable_stdout_stderr),
|
|
27
|
+
Keybinding(key=Keys.F7, description='Step Into', callback=hilda_client.step_into),
|
|
28
|
+
Keybinding(key=Keys.F8, description='Step Over', callback=hilda_client.step_over),
|
|
29
|
+
Keybinding(key=Keys.F9, description='Continue',
|
|
30
|
+
callback=lambda _: (hilda_client.log_info('Sending continue'), hilda_client.cont())),
|
|
31
|
+
Keybinding(key=Keys.F10, description='Stop',
|
|
32
|
+
callback=lambda _: (hilda_client.log_info('Sending stop'), hilda_client.stop())),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
def load_ipython_extension(ipython):
|
|
7
37
|
def register_keybindings():
|
|
8
|
-
|
|
9
|
-
keys_mapping = {Keys.F1: hilda.ui_manager.show,
|
|
10
|
-
Keys.F2: hilda.toggle_enable_stdout_stderr,
|
|
11
|
-
Keys.F7: hilda.step_into,
|
|
12
|
-
Keys.F8: hilda.step_over,
|
|
13
|
-
Keys.F9: lambda _: (hilda.log_info('Sending continue'), hilda.cont()),
|
|
14
|
-
Keys.F10: lambda _: (hilda.log_info('Sending stop'), hilda.stop())}
|
|
15
|
-
|
|
38
|
+
hilda_client = ipython.user_ns['p']
|
|
16
39
|
insert_mode = ViInsertMode() | EmacsInsertMode()
|
|
17
40
|
registry = ipython.pt_app.key_bindings
|
|
18
41
|
|
|
19
|
-
for
|
|
20
|
-
registry.add_binding(
|
|
21
|
-
callback)
|
|
42
|
+
for keybind in get_keybindings(hilda_client):
|
|
43
|
+
registry.add_binding(
|
|
44
|
+
keybind.key, filter=(HasFocus(DEFAULT_BUFFER) & ~HasSelection() & insert_mode))(keybind.callback)
|
|
22
45
|
|
|
23
46
|
register_keybindings()
|
|
24
47
|
ipython.events.register('shell_initialized', register_keybindings)
|
hilda/objective_c_class.py
CHANGED
|
@@ -4,7 +4,6 @@ from collections import namedtuple
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from functools import partial
|
|
6
6
|
from typing import Any
|
|
7
|
-
from uuid import uuid4
|
|
8
7
|
|
|
9
8
|
from objc_types_decoder.decode import decode as decode_type
|
|
10
9
|
from objc_types_decoder.decode import decode_with_tail
|
|
@@ -188,19 +187,16 @@ class Class:
|
|
|
188
187
|
hilda.log_info('removing breakpoints')
|
|
189
188
|
for bp_id, bp in list(hilda.breakpoints.items()):
|
|
190
189
|
if 'group_uuid' in bp.options and bp.options.get('group_uuid', '') == options['group_uuid']:
|
|
191
|
-
hilda.
|
|
190
|
+
hilda.breakpoints.remove(bp_id)
|
|
192
191
|
captured = hilda.evaluate_expression('$arg1')
|
|
193
192
|
captured = captured.objc_symbol
|
|
194
193
|
hilda.captured_objects[options['name'].split(' ')[0].split('[')[1]] = captured
|
|
195
194
|
hilda.cont()
|
|
196
195
|
|
|
197
|
-
group_uuid = str(uuid4())
|
|
198
|
-
|
|
199
196
|
for method in self.methods:
|
|
200
197
|
if not method.is_class:
|
|
201
198
|
# only instance methods are relevant for capturing self
|
|
202
|
-
method.imp.bp(hook
|
|
203
|
-
name=f'-[{class_name} {method.name}]')
|
|
199
|
+
method.imp.bp(hook)
|
|
204
200
|
|
|
205
201
|
if sync:
|
|
206
202
|
self._client.cont()
|