|
19 | 19 | dyld_cache_mapping_info, |
20 | 20 | dyld_cache_slide_info2, |
21 | 21 | dyld_cache_slide_info3, |
22 | | - dyld_cache_slide_pointer3 |
| 22 | + dyld_cache_slide_info5, |
| 23 | + dyld_cache_slide_pointer3, |
| 24 | + dyld_cache_slide_pointer5 |
23 | 25 | ) |
24 | 26 |
|
25 | 27 | from DyldExtractor.macho.macho_structs import ( |
|
29 | 31 |
|
30 | 32 | _SlideInfoMap = { |
31 | 33 | 2: dyld_cache_slide_info2, |
32 | | - 3: dyld_cache_slide_info3 |
| 34 | + 3: dyld_cache_slide_info3, |
| 35 | + 5: dyld_cache_slide_info5, |
33 | 36 | } |
34 | 37 |
|
35 | 38 |
|
36 | 39 | @dataclass |
37 | 40 | class _MappingInfo(object): |
38 | 41 | mapping: Union[dyld_cache_mapping_info, dyld_cache_mapping_and_slide_info] |
39 | | - slideInfo: Union[dyld_cache_slide_info2, dyld_cache_slide_info3] |
| 42 | + slideInfo: Union[dyld_cache_slide_info2, dyld_cache_slide_info3, dyld_cache_slide_info5] |
40 | 43 | dyldCtx: DyldContext |
41 | 44 | """The context that the mapping info belongs to.""" |
42 | 45 | pass |
@@ -261,6 +264,102 @@ def _rebasePage( |
261 | 264 | pass |
262 | 265 | pass |
263 | 266 |
|
| 267 | +class _V5Rebaser(object): |
| 268 | + |
| 269 | + def __init__( |
| 270 | + self, |
| 271 | + extractionCtx: ExtractionContext, |
| 272 | + mappingInfo: _MappingInfo |
| 273 | + ) -> None: |
| 274 | + super().__init__() |
| 275 | + |
| 276 | + self.statusBar = extractionCtx.statusBar |
| 277 | + self.machoCtx = extractionCtx.machoCtx |
| 278 | + |
| 279 | + self.dyldCtx = mappingInfo.dyldCtx |
| 280 | + self.mapping = mappingInfo.mapping |
| 281 | + self.slideInfo = mappingInfo.slideInfo |
| 282 | + |
| 283 | + def run(self) -> None: |
| 284 | + self.statusBar.update(unit="Slide Info Rebaser") |
| 285 | + |
| 286 | + pageStartsOff = self.slideInfo._fileOff_ + len(self.slideInfo) |
| 287 | + pageStarts = self.dyldCtx.getBytes( |
| 288 | + pageStartsOff, |
| 289 | + self.slideInfo.page_starts_count * 2 |
| 290 | + ) |
| 291 | + pageStarts = [page[0] for page in struct.iter_unpack("<H", pageStarts)] |
| 292 | + |
| 293 | + for segment in self.machoCtx.segmentsI: |
| 294 | + self._rebaseSegment(pageStarts, segment.seg) |
| 295 | + pass |
| 296 | + pass |
| 297 | + |
| 298 | + def _rebaseSegment( |
| 299 | + self, |
| 300 | + pageStarts: Tuple[int], |
| 301 | + segment: segment_command_64 |
| 302 | + ) -> None: |
| 303 | + # Check if the segment is included in the mapping |
| 304 | + if not ( |
| 305 | + segment.vmaddr >= self.mapping.address |
| 306 | + and segment.vmaddr < self.mapping.address + self.mapping.size |
| 307 | + ): |
| 308 | + return |
| 309 | + |
| 310 | + ctx = self.machoCtx.ctxForAddr(segment.vmaddr) |
| 311 | + |
| 312 | + # Get the indices of relevant pageStarts |
| 313 | + dataStart = self.mapping.address |
| 314 | + pageSize = self.slideInfo.page_size |
| 315 | + |
| 316 | + startAddr = segment.vmaddr - dataStart |
| 317 | + startIndex = int(startAddr / pageSize) |
| 318 | + |
| 319 | + endAddr = ((segment.vmaddr + segment.vmsize) - dataStart) + pageSize |
| 320 | + endIndex = int(endAddr / pageSize) |
| 321 | + endIndex = min(endIndex, len(pageStarts)) |
| 322 | + |
| 323 | + for i in range(startIndex, endIndex): |
| 324 | + page = pageStarts[i] |
| 325 | + |
| 326 | + if page == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE: |
| 327 | + continue |
| 328 | + else: |
| 329 | + pageOff = (i * pageSize) + self.mapping.fileOffset |
| 330 | + self._rebasePage(ctx, pageOff, page) |
| 331 | + |
| 332 | + self.statusBar.update(status="Rebasing Pages") |
| 333 | + pass |
| 334 | + |
| 335 | + def _rebasePage( |
| 336 | + self, |
| 337 | + ctx: MachOContext, |
| 338 | + pageOffset: int, |
| 339 | + delta: int |
| 340 | + ) -> None: |
| 341 | + locOff = pageOffset |
| 342 | + |
| 343 | + while True: |
| 344 | + locOff += delta |
| 345 | + locInfo = dyld_cache_slide_pointer5(self.dyldCtx.file, locOff) |
| 346 | + |
| 347 | + # It appears the delta encoded in the pointers are 64-bit jumps... |
| 348 | + delta = locInfo.regular.next * 8 |
| 349 | + |
| 350 | + if locInfo.auth.auth: |
| 351 | + newValue = self.slideInfo.value_add + locInfo.auth.runtimeOffset |
| 352 | + else: |
| 353 | + newValue = self.slideInfo.value_add + locInfo.regular.runtimeOffset |
| 354 | + |
| 355 | + ctx.writeBytes(locOff, struct.pack("<Q", newValue)) |
| 356 | + |
| 357 | + if delta == 0: |
| 358 | + break |
| 359 | + pass |
| 360 | + pass |
| 361 | + pass |
| 362 | + |
264 | 363 |
|
265 | 364 | def _getMappingInfo( |
266 | 365 | extractionCtx: ExtractionContext |
@@ -385,7 +484,22 @@ def slideAddress(self, address: int) -> int: |
385 | 484 | bottom43Bits = value51 & 0x000007FFFFFFFFFF |
386 | 485 | return (top8Bits << 13) | bottom43Bits |
387 | 486 |
|
| 487 | + # arm64e pointer (iOS 18+, macOS 14.4+) |
| 488 | + elif slideInfo.version == 5: |
| 489 | + slideInfo = dyld_cache_slide_pointer5(context.file, offset) |
| 490 | + |
| 491 | + if slideInfo.auth.auth: |
| 492 | + value = info.slideInfo.value_add + slideInfo.auth.runtimeOffset |
| 493 | + else: |
| 494 | + value = info.slideInfo.value_add + slideInfo.regular.runtimeOffset |
| 495 | + |
| 496 | + if value == 0x180000000: |
| 497 | + return 0x0 |
| 498 | + |
| 499 | + return value |
| 500 | + |
388 | 501 | else: |
| 502 | + print(f"Unknown slide version: {slideInfo.version}.") |
389 | 503 | return None |
390 | 504 |
|
391 | 505 | return None |
@@ -467,5 +581,7 @@ def processSlideInfo(extractionCtx: ExtractionContext) -> None: |
467 | 581 | _V2Rebaser(extractionCtx, info).run() |
468 | 582 | elif info.slideInfo.version == 3: |
469 | 583 | _V3Rebaser(extractionCtx, info).run() |
| 584 | + elif info.slideInfo.version == 5: |
| 585 | + _V5Rebaser(extractionCtx, info).run() |
470 | 586 | else: |
471 | 587 | logger.error("Unknown slide version.") |
0 commit comments