Skip to content
This repository was archived by the owner on Dec 28, 2025. It is now read-only.

Commit ecffa7c

Browse files
authored
Merge pull request #61 from donato-fiore/iOS-18-Support
Add support to extract bins from iOS 18 dyld_shared_cache
2 parents d44855e + c2ab04c commit ecffa7c

File tree

16 files changed

+260
-75
lines changed

16 files changed

+260
-75
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ src/*.egg-info
55
venv
66

77
# Big files like the shared cache should be excluded from the repo
8-
binaries/
8+
binaries/
9+
10+
# Testing files
11+
build/
12+
.DS_Store

ConverterExplanation.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ Because the actual segments of the image are split across large distances, the r
3636
For people that want to contribute to this, here are some links for reference.
3737

3838
### Objective-C Runtime
39-
* https://opensource.apple.com/source/xnu/xnu-7195.81.3/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
40-
* https://opensource.apple.com/source/objc4/objc4-781/runtime/objc-runtime-new.h.auto.html
39+
* https://fergofrog.com/code/codebrowser/xnu/EXTERNAL_HEADERS/mach-o/loader.h.html
40+
* https://github.com/opensource-apple/objc4/blob/master/runtime/objc-runtime-new.h
4141

4242
### DYLD Cache
43-
* https://opensource.apple.com/source/dyld/dyld-832.7.3/dyld3/shared-cache/dyld_cache_format.h.auto.html
44-
* https://opensource.apple.com/source/dyld/dyld-832.7.3/dyld3/shared-cache/dsc_extractor.cpp.auto.html
43+
* https://github.com/opensource-apple/dyld/blob/master/launch-cache/dyld_cache_format.h
44+
* https://github.com/opensource-apple/dyld/blob/master/launch-cache/dsc_extractor.cpp
4545

4646
### Other Extractors
4747
* https://github.com/deepinstinct/dsc_fix/blob/master/dsc_fix.py

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![PyPI version](https://badge.fury.io/py/dyldextractor.svg)](https://badge.fury.io/py/dyldextractor)
22
# DyldExtractor
3-
Extract Binaries from Apple's Dyld Shared Cache to be useful in a disassembler. This tool only supports iOS, arm64.
3+
Extract Binaries from Apple's Dyld Shared Cache to be useful in a disassembler. This tool only supports iOS.
44

55
# Installation
66

bin/dyldex

100755100644
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import logging
77
import os
88
import sys
99
from typing import List, BinaryIO
10+
import resource
1011

1112
try:
1213
progressbar.streams
@@ -101,6 +102,8 @@ def _extractImage(
101102
"""
102103

103104
logger = logging.getLogger()
105+
106+
resource.setrlimit(resource.RLIMIT_NOFILE, (1024, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))
104107

105108
statusBar = progressbar.ProgressBar(
106109
prefix="{variables.unit} >> {variables.status} :: [",

bin/dyldex_all

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import pathlib
99
import signal
1010
import sys
1111
import progressbar
12+
import resource
1213

1314
from typing import (
1415
List,
@@ -116,6 +117,8 @@ def _extractImage(
116117
# setup logging
117118
logger = logging.getLogger(f"Worker: {outputPath}")
118119

120+
resource.setrlimit(resource.RLIMIT_NOFILE, (1024, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))
121+
119122
loggingStream = io.StringIO()
120123
handler = logging.StreamHandler(loggingStream)
121124
formatter = logging.Formatter(

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='dyldextractor',
5-
version='2.2.2',
5+
version='2.3',
66
description='Extract Binaries from Apple\'s Dyld Shared Cache',
77
long_description='file: README.md',
88
long_description_content_type='text/markdown',

src/DyldExtractor/cache_context.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import pathlib
21
from typing import (
3-
List,
42
Tuple,
53
BinaryIO
64
)
75

86
from DyldExtractor.file_context import FileContext
9-
from DyldExtractor.dyld.dyld_structs import (
10-
dyld_cache_header,
11-
dyld_cache_mapping_info,
12-
dyld_cache_image_info,
13-
dyld_subcache_entry,
14-
dyld_subcache_entry2,
15-
)
167

178

189
class CacheContext(FileContext):

src/DyldExtractor/converter/chained_fixups.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
import struct
22
from dataclasses import dataclass
3-
from typing import (
4-
Type,
5-
TypeVar,
6-
Union,
7-
Tuple,
8-
List
9-
)
103

114
from DyldExtractor.extraction_context import ExtractionContext
125
from DyldExtractor.macho.macho_context import MachOContext
136

147
from DyldExtractor.macho.macho_structs import (
158
LoadCommands,
16-
linkedit_data_command,
179
)
10+
1811
from DyldExtractor.macho.fixup_chains_structs import (
1912
dyld_chained_fixups_header,
2013
dyld_chained_starts_in_image,

src/DyldExtractor/converter/objc_fixer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ def _checkMethodNameStorage(self) -> None:
511511
# First the version number and then pointers
512512
verOff, ctx = self._dyldCtx.convertAddr(objcScoffs.addr)
513513
version = ctx.readFormat("<Q", verOff)[0]
514-
if version != 2 and version != 3:
514+
if version != 2 and version != 3 and version != 4:
515515
self._logger.warning(
516516
f"Unknown objc opt version: {version}, but continuing on."
517517
)

src/DyldExtractor/converter/slide_info.py

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
dyld_cache_mapping_info,
2020
dyld_cache_slide_info2,
2121
dyld_cache_slide_info3,
22-
dyld_cache_slide_pointer3
22+
dyld_cache_slide_info5,
23+
dyld_cache_slide_pointer3,
24+
dyld_cache_slide_pointer5
2325
)
2426

2527
from DyldExtractor.macho.macho_structs import (
@@ -29,14 +31,15 @@
2931

3032
_SlideInfoMap = {
3133
2: dyld_cache_slide_info2,
32-
3: dyld_cache_slide_info3
34+
3: dyld_cache_slide_info3,
35+
5: dyld_cache_slide_info5,
3336
}
3437

3538

3639
@dataclass
3740
class _MappingInfo(object):
3841
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]
4043
dyldCtx: DyldContext
4144
"""The context that the mapping info belongs to."""
4245
pass
@@ -261,6 +264,102 @@ def _rebasePage(
261264
pass
262265
pass
263266

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+
264363

265364
def _getMappingInfo(
266365
extractionCtx: ExtractionContext
@@ -385,7 +484,22 @@ def slideAddress(self, address: int) -> int:
385484
bottom43Bits = value51 & 0x000007FFFFFFFFFF
386485
return (top8Bits << 13) | bottom43Bits
387486

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+
388501
else:
502+
print(f"Unknown slide version: {slideInfo.version}.")
389503
return None
390504

391505
return None
@@ -467,5 +581,7 @@ def processSlideInfo(extractionCtx: ExtractionContext) -> None:
467581
_V2Rebaser(extractionCtx, info).run()
468582
elif info.slideInfo.version == 3:
469583
_V3Rebaser(extractionCtx, info).run()
584+
elif info.slideInfo.version == 5:
585+
_V5Rebaser(extractionCtx, info).run()
470586
else:
471587
logger.error("Unknown slide version.")

0 commit comments

Comments
 (0)