Skip to content

Commit

Permalink
Merge pull request #61 from donato-fiore/iOS-18-Support
Browse files Browse the repository at this point in the history
Add support to extract bins from iOS 18 dyld_shared_cache
  • Loading branch information
arandomdev authored Dec 27, 2024
2 parents d44855e + c2ab04c commit ecffa7c
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 75 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ src/*.egg-info
venv

# Big files like the shared cache should be excluded from the repo
binaries/
binaries/

# Testing files
build/
.DS_Store
8 changes: 4 additions & 4 deletions ConverterExplanation.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ Because the actual segments of the image are split across large distances, the r
For people that want to contribute to this, here are some links for reference.

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

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

### Other Extractors
* https://github.com/deepinstinct/dsc_fix/blob/master/dsc_fix.py
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![PyPI version](https://badge.fury.io/py/dyldextractor.svg)](https://badge.fury.io/py/dyldextractor)
# DyldExtractor
Extract Binaries from Apple's Dyld Shared Cache to be useful in a disassembler. This tool only supports iOS, arm64.
Extract Binaries from Apple's Dyld Shared Cache to be useful in a disassembler. This tool only supports iOS.

# Installation

Expand Down
3 changes: 3 additions & 0 deletions bin/dyldex
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import logging
import os
import sys
from typing import List, BinaryIO
import resource

try:
progressbar.streams
Expand Down Expand Up @@ -101,6 +102,8 @@ def _extractImage(
"""

logger = logging.getLogger()

resource.setrlimit(resource.RLIMIT_NOFILE, (1024, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))

statusBar = progressbar.ProgressBar(
prefix="{variables.unit} >> {variables.status} :: [",
Expand Down
3 changes: 3 additions & 0 deletions bin/dyldex_all
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import pathlib
import signal
import sys
import progressbar
import resource

from typing import (
List,
Expand Down Expand Up @@ -116,6 +117,8 @@ def _extractImage(
# setup logging
logger = logging.getLogger(f"Worker: {outputPath}")

resource.setrlimit(resource.RLIMIT_NOFILE, (1024, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))

loggingStream = io.StringIO()
handler = logging.StreamHandler(loggingStream)
formatter = logging.Formatter(
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='dyldextractor',
version='2.2.2',
version='2.3',
description='Extract Binaries from Apple\'s Dyld Shared Cache',
long_description='file: README.md',
long_description_content_type='text/markdown',
Expand Down
9 changes: 0 additions & 9 deletions src/DyldExtractor/cache_context.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import pathlib
from typing import (
List,
Tuple,
BinaryIO
)

from DyldExtractor.file_context import FileContext
from DyldExtractor.dyld.dyld_structs import (
dyld_cache_header,
dyld_cache_mapping_info,
dyld_cache_image_info,
dyld_subcache_entry,
dyld_subcache_entry2,
)


class CacheContext(FileContext):
Expand Down
9 changes: 1 addition & 8 deletions src/DyldExtractor/converter/chained_fixups.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import struct
from dataclasses import dataclass
from typing import (
Type,
TypeVar,
Union,
Tuple,
List
)

from DyldExtractor.extraction_context import ExtractionContext
from DyldExtractor.macho.macho_context import MachOContext

from DyldExtractor.macho.macho_structs import (
LoadCommands,
linkedit_data_command,
)

from DyldExtractor.macho.fixup_chains_structs import (
dyld_chained_fixups_header,
dyld_chained_starts_in_image,
Expand Down
2 changes: 1 addition & 1 deletion src/DyldExtractor/converter/objc_fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def _checkMethodNameStorage(self) -> None:
# First the version number and then pointers
verOff, ctx = self._dyldCtx.convertAddr(objcScoffs.addr)
version = ctx.readFormat("<Q", verOff)[0]
if version != 2 and version != 3:
if version != 2 and version != 3 and version != 4:
self._logger.warning(
f"Unknown objc opt version: {version}, but continuing on."
)
Expand Down
122 changes: 119 additions & 3 deletions src/DyldExtractor/converter/slide_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
dyld_cache_mapping_info,
dyld_cache_slide_info2,
dyld_cache_slide_info3,
dyld_cache_slide_pointer3
dyld_cache_slide_info5,
dyld_cache_slide_pointer3,
dyld_cache_slide_pointer5
)

from DyldExtractor.macho.macho_structs import (
Expand All @@ -29,14 +31,15 @@

_SlideInfoMap = {
2: dyld_cache_slide_info2,
3: dyld_cache_slide_info3
3: dyld_cache_slide_info3,
5: dyld_cache_slide_info5,
}


@dataclass
class _MappingInfo(object):
mapping: Union[dyld_cache_mapping_info, dyld_cache_mapping_and_slide_info]
slideInfo: Union[dyld_cache_slide_info2, dyld_cache_slide_info3]
slideInfo: Union[dyld_cache_slide_info2, dyld_cache_slide_info3, dyld_cache_slide_info5]
dyldCtx: DyldContext
"""The context that the mapping info belongs to."""
pass
Expand Down Expand Up @@ -261,6 +264,102 @@ def _rebasePage(
pass
pass

class _V5Rebaser(object):

def __init__(
self,
extractionCtx: ExtractionContext,
mappingInfo: _MappingInfo
) -> None:
super().__init__()

self.statusBar = extractionCtx.statusBar
self.machoCtx = extractionCtx.machoCtx

self.dyldCtx = mappingInfo.dyldCtx
self.mapping = mappingInfo.mapping
self.slideInfo = mappingInfo.slideInfo

def run(self) -> None:
self.statusBar.update(unit="Slide Info Rebaser")

pageStartsOff = self.slideInfo._fileOff_ + len(self.slideInfo)
pageStarts = self.dyldCtx.getBytes(
pageStartsOff,
self.slideInfo.page_starts_count * 2
)
pageStarts = [page[0] for page in struct.iter_unpack("<H", pageStarts)]

for segment in self.machoCtx.segmentsI:
self._rebaseSegment(pageStarts, segment.seg)
pass
pass

def _rebaseSegment(
self,
pageStarts: Tuple[int],
segment: segment_command_64
) -> None:
# Check if the segment is included in the mapping
if not (
segment.vmaddr >= self.mapping.address
and segment.vmaddr < self.mapping.address + self.mapping.size
):
return

ctx = self.machoCtx.ctxForAddr(segment.vmaddr)

# Get the indices of relevant pageStarts
dataStart = self.mapping.address
pageSize = self.slideInfo.page_size

startAddr = segment.vmaddr - dataStart
startIndex = int(startAddr / pageSize)

endAddr = ((segment.vmaddr + segment.vmsize) - dataStart) + pageSize
endIndex = int(endAddr / pageSize)
endIndex = min(endIndex, len(pageStarts))

for i in range(startIndex, endIndex):
page = pageStarts[i]

if page == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE:
continue
else:
pageOff = (i * pageSize) + self.mapping.fileOffset
self._rebasePage(ctx, pageOff, page)

self.statusBar.update(status="Rebasing Pages")
pass

def _rebasePage(
self,
ctx: MachOContext,
pageOffset: int,
delta: int
) -> None:
locOff = pageOffset

while True:
locOff += delta
locInfo = dyld_cache_slide_pointer5(self.dyldCtx.file, locOff)

# It appears the delta encoded in the pointers are 64-bit jumps...
delta = locInfo.regular.next * 8

if locInfo.auth.auth:
newValue = self.slideInfo.value_add + locInfo.auth.runtimeOffset
else:
newValue = self.slideInfo.value_add + locInfo.regular.runtimeOffset

ctx.writeBytes(locOff, struct.pack("<Q", newValue))

if delta == 0:
break
pass
pass
pass


def _getMappingInfo(
extractionCtx: ExtractionContext
Expand Down Expand Up @@ -385,7 +484,22 @@ def slideAddress(self, address: int) -> int:
bottom43Bits = value51 & 0x000007FFFFFFFFFF
return (top8Bits << 13) | bottom43Bits

# arm64e pointer (iOS 18+, macOS 14.4+)
elif slideInfo.version == 5:
slideInfo = dyld_cache_slide_pointer5(context.file, offset)

if slideInfo.auth.auth:
value = info.slideInfo.value_add + slideInfo.auth.runtimeOffset
else:
value = info.slideInfo.value_add + slideInfo.regular.runtimeOffset

if value == 0x180000000:
return 0x0

return value

else:
print(f"Unknown slide version: {slideInfo.version}.")
return None

return None
Expand Down Expand Up @@ -467,5 +581,7 @@ def processSlideInfo(extractionCtx: ExtractionContext) -> None:
_V2Rebaser(extractionCtx, info).run()
elif info.slideInfo.version == 3:
_V3Rebaser(extractionCtx, info).run()
elif info.slideInfo.version == 5:
_V5Rebaser(extractionCtx, info).run()
else:
logger.error("Unknown slide version.")
11 changes: 11 additions & 0 deletions src/DyldExtractor/converter/stub_fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,17 @@ def _addToMap(stubName: bytes, stubAddr: int):
self._logger.warning(f"Encountered a resolver at {hex(stubAddr)} while fixing stubs") # noqa
continue

elif stubFormat == _StubFormat.AuthStubBRAA:
# only need to relink symbol pointer
symPtrOff = self._dyldCtx.convertAddr(symPtrAddr)[0]

if not symbolPtrFile:
symbolPtrFile = self._machoCtx.ctxForAddr(symPtrAddr)
pass

symbolPtrFile.writeBytes(symPtrOff, struct.pack("<Q", stubAddr))
continue

else:
self._logger.error(f"Unknown stub format: {stubFormat}, at {hex(stubAddr)}") # noqa
continue
Expand Down
1 change: 1 addition & 0 deletions src/DyldExtractor/dyld/dyld_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
DYLD_CACHE_SLIDE_PAGE_ATTR_END = 0x8000 # last chain entry for page

DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE = 0xFFFF # page has no rebasing
DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE = 0xFFFF # page has no rebasing
1 change: 0 additions & 1 deletion src/DyldExtractor/dyld/dyld_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
BinaryIO
)

from DyldExtractor.file_context import FileContext
from DyldExtractor.dyld.dyld_structs import (
dyld_cache_header,
dyld_cache_mapping_info,
Expand Down
Loading

0 comments on commit ecffa7c

Please sign in to comment.