1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
|
import os
import ctypes
import dataclasses
import traceback
import binaryninja
from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString
from . import _sharedcachecore as sccore
from .sharedcache_enums import *
@dataclasses.dataclass
class DSCMemoryMapping:
name: str
vmAddress: int
size: int
def __str__(self):
return repr(self)
def __repr__(self):
return f"<DSCMemoryMapping '{self.name}': {self.vmAddress:x}+{self.size:x}>"
@dataclasses.dataclass
class LoadedRegion:
name: str
headerAddress: int
mappings: list[DSCMemoryMapping]
def __str__(self):
return repr(self)
def __repr__(self):
return f"<LoadedRegion {self.name} @ {self.headerAddress:x}>"
@dataclasses.dataclass
class DSCBackingCacheMapping:
vmAddress: int
size: int
fileOffset: int
def __str__(self):
return repr(self)
def __repr__(self):
return f"<DSCBackingCacheMapping {self.vmAddress:x}+{self.size:x} @ {self.fileOffset:x}"
@dataclasses.dataclass
class DSCBackingCache:
path: str
cacheType: BackingCacheType
mappings: list[DSCBackingCacheMapping]
def __str__(self):
return repr(self)
def __repr__(self):
cache_type_str = 'Unknown'
if self.cacheType == BackingCacheType.BackingCacheTypePrimary:
cache_type_str = 'Primary'
elif self.cacheType == BackingCacheType.BackingCacheTypeSecondary:
cache_type_str = 'Secondary'
elif self.cacheType == BackingCacheType.BackingCacheTypeSymbols:
cache_type_str = 'Symbols'
return f"<DSCBackingCache {self.path} {cache_type_str} | {len(self.mappings)} mappings>"
@dataclasses.dataclass
class DSCImageMemoryMapping:
filePath: str
name: str
vmAddress: int
size: int
loaded: bool
rawViewOffset: int
def __str__(self):
return repr(self)
def __repr__(self):
return f"<DSCImageMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>"
@dataclasses.dataclass
class DSCImage:
name: str
headerAddress: int
mappings: list[DSCImageMemoryMapping]
def __str__(self):
return repr(self)
def __repr__(self):
return f"<DSCImage {self.name} @ {self.headerAddress:x}>"
@dataclasses.dataclass
class DSCSymbol:
name: str
image: str
address: int
def __str__(self):
return repr(self)
def __repr__(self):
return f"<DSCSymbol {self.name} @ {self.address:x} ({self.image}>"
class SharedCache:
"""
SharedCache is the primary class for interacting with the shared cache processor and DSCView metadata.
You can create a SharedCache object from a BinaryView object by calling `SharedCache(bv)`, where `bv` is the BinaryView.
By default `bv` in the console will return the instance of the BinaryView that is currently open, \
so in the UI, you can use `dsc = SharedCache(bv)` to create a SharedCache object in the scripting console.
Methods and attributes in this class have documentation which can be viewed by typing `SharedCache.method_or_attribute_name?` in the console.
"""
def __init__(self, view):
self.handle = sccore.BNGetSharedCache(view.handle)
def load_image_with_install_name(self, install_name, skip_loading_objective_c = False) -> bool:
"""
Locate an image with the provided install name and load it into the shared cache view
:param install_name: Install name of the image
:param skip_loading_objective_c: Whether to skip process Objective-C information for this image. Default false.
:return:
"""
return sccore.BNDSCViewLoadImageWithInstallName(self.handle, install_name, skip_loading_objective_c)
def load_section_at_address(self, addr) -> bool:
"""
Load a singular section at the provided address into the shared cache view.
This will partial-load the image, only mapping the requested segment containing this section.
Image info will still be processed, but will only be applied to mapped regions.
:param addr: Address within the section
:return:
"""
return sccore.BNDSCViewLoadSectionAtAddress(self.handle, addr)
def load_image_containing_address(self, addr, skip_loading_objective_c = False) -> bool:
"""
Load the image containing the provided address into the shared cache view.
:param addr: Address within the image to load
:param skip_loading_objective_c: Whether to skip processing Objective-C information for this image. Default false.
:return:
"""
return sccore.BNDSCViewLoadImageContainingAddress(self.handle, addr, skip_loading_objective_c)
def process_objc_sections_for_image_with_install_name(self, install_name) -> bool:
"""
Process Objective-C information for the image with the provided install name.
:param install_name: Install name of the image
:return:
"""
return sccore.BNDSCViewProcessObjCSectionsForImageWithInstallName(self.handle, install_name, False)
def process_all_objc_sections(self) -> bool:
"""
Process Objective-C information for all images in the shared cache view.
:return:
"""
return sccore.BNDSCViewProcessAllObjCSections(self.handle)
@property
def caches(self) -> list[DSCBackingCache]:
"""
Get all backing caches in the shared cache.
:return:
"""
count = ctypes.c_ulonglong()
value = sccore.BNDSCViewGetBackingCaches(self.handle, count)
if value is None:
return []
result = []
for i in range(count.value):
mappings = []
for j in range(value[i].mappingCount):
mapping = DSCBackingCacheMapping(
value[i].mappings[j].vmAddress,
value[i].mappings[j].size,
value[i].mappings[j].fileOffset
)
mappings.append(mapping)
result.append(DSCBackingCache(
value[i].path,
value[i].cacheType,
mappings
))
sccore.BNDSCViewFreeBackingCaches(value, count)
return result
@property
def images(self) -> list[DSCImage]:
"""
Get all images in the shared cache
:return:
"""
count = ctypes.c_ulonglong()
value = sccore.BNDSCViewGetAllImages(self.handle, count)
if value is None:
return []
result = []
for i in range(count.value):
mappings = []
for j in range(value[i].mappingCount):
mapping = DSCImageMemoryMapping(
value[i].mappings[j].filePath,
value[i].mappings[j].name,
value[i].mappings[j].vmAddress,
value[i].mappings[j].size,
value[i].mappings[j].loaded,
value[i].mappings[j].rawViewOffset
)
mappings.append(mapping)
result.append(DSCImage(
value[i].name,
value[i].headerAddress,
mappings
))
sccore.BNDSCViewFreeAllImages(value, count)
return result
@property
def loaded_regions(self) -> list[LoadedRegion]:
"""
Get all loaded regions in the shared cache
The internal logic for loading images treats a region as 'loaded' whenever
that region has been mapped into memory, and, if it's located within an image, header information has been applied to that region.
Individual segments within an image can be loaded independently of the image itself.
Only once all regions of an image are loaded will the header processor refuse to run on that region.
:return:
"""
count = ctypes.c_ulonglong()
value = sccore.BNDSCViewGetLoadedRegions(self.handle, count)
if value is None:
return []
result = []
for i in range(count.value):
mapping = DSCMemoryMapping(
value[i].name,
value[i].vmAddress,
value[i].size,
)
result.append(mapping)
sccore.BNDSCViewFreeLoadedRegions(value, count)
return result
def load_all_symbols_and_wait(self) -> list[DSCSymbol]:
"""
Load all symbols in the shared cache. This will block on the current thread waiting for processing to finish.
While all functions in this API are synchronous, this function can be particularly slow due to the large number
of symbols in the shared cache. "and_wait" is appended to the function name to indicate that this function
will block until processing is complete, and for performant applications, you should consider calling this
function in a separate thread and waiting on its return. An example of this is provided in the shared cache
triage view.
This may take several seconds if this is the first time this function is called. Subsequent calls will be faster.
In UI-based API usage, it is likely that the triage view will have already performed this operation, and calls
to this function will be much faster.
:return: A list of all symbols in the shared cache
"""
count = ctypes.c_ulonglong()
value = sccore.BNDSCViewLoadAllSymbolsAndWait(self.handle, count)
if value is None:
return []
result = []
for i in range(count.value):
sym = DSCSymbol(
value[i].name,
value[i].image,
value[i].address
)
result.append(sym)
sccore.BNDSCViewFreeSymbols(value, count)
return result
@property
def image_names(self) -> list[str]:
"""
Get all image names in the shared cache
:return:
"""
count = ctypes.c_ulonglong()
value = sccore.BNDSCViewGetInstallNames(self.handle, count)
if value is None:
return []
result = []
for i in range(count.value):
result.append(value[i].decode('utf-8'))
BNFreeStringList(value, count)
return result
@property
def state(self) -> DSCViewState:
"""
Get the current image state of the shared cache view. Useful for checking if images have been loaded yet or not.
:return:
"""
return DSCViewState(sccore.BNDSCViewGetState(self.handle))
def get_name_for_address(self, address) -> str:
"""
Get the "name" for the provided address. Specifically, the name of the memory region this address lies in.
If this lies within an image segment, this will be in the format image_name + "::" + segment_name.
It may also be the name of a branch pool or other non-image region.
This is the API call utilized on the first dynamic entry in the right-click context menu.
:param address: address to check
:return:
"""
name = sccore.BNDSCViewGetNameForAddress(self.handle, address)
if name is None:
return ""
result = name
return result
def get_image_name_for_address(self, address) -> str:
"""
Return the install name for the image containing the provided address.
If the address is not within an image, this will return an empty string.
This is the API call used in the second dynamic entry in the right-click context menu.
:param address: address to check
:return:
"""
name = sccore.BNDSCViewGetImageNameForAddress(self.handle, address)
if name is None:
return ""
result = name
return result
def find_symbol_at_addr_and_apply_to_addr(self, symbol_address, target_address, trigger_reanalysis) -> None:
"""
This is primarily a function utilized for automated backwards symbol propagation for stubs in the workflow, however
it is passed through here as well in the event you need to use it to do something similar, or want to create
your own version of the workflow.
This is currently a blocking function.
This will check the cache for a symbol located at symbol_address, and apply it to target_address, appending a
`j_` to the front of the symbol name copy at `target_address`.
It will additionally backwards-propagate type information that was specifically applied via TypeLibrary to the stub.
This includes calling conventions and can be seen in stubs pointing to objc_release_x[register] functions.
This check will not run if:
- The symbol address and target address are the same
- The target address has already been given a name by this function
:param symbol_address: Symbol address to check
:param target_address: Target address to apply symbol name and type to
:param trigger_reanalysis: Whether to reanalyze the function at target_address if the function already existed.
:return: None
"""
sccore.BNDSCFindSymbolAtAddressAndApplyToAddress(self.handle, symbol_address, target_address, trigger_reanalysis)
|