//go:build darwin && cgo package dyld /* #cgo CFLAGS: -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include #cgo CFLAGS: -Wno-nullability-completeness #cgo LDFLAGS: -ldl #include #include #include #include #include int dsc_extract(void *f, const char* shared_cache_file_path, const char* extraction_root_path){ int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path, void (^progress)(unsigned current, unsigned total)); extractor_proc = f; int result = (*extractor_proc)(shared_cache_file_path, extraction_root_path, ^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); }); return result; } */ import "C" import ( "bytes" "fmt" "os" "path/filepath" "strings" "time" "unsafe" "github.com/apex/log" "github.com/blacktop/go-macho" "github.com/blacktop/go-plist" "github.com/blacktop/ipsw/internal/utils" "github.com/pkg/errors" ) var xcodePaths = []string{ "/Applications/Xcode-beta.app", "/Applications/Xcode.app", } // LibHandle represents an open handle to a library type LibHandle struct { Handle unsafe.Pointer Libname string } type XCodeInfoPlist struct { ExtractorVersion string `plist:"DSC Extractor Version,omitempty"` DateCollected time.Time `plist:"DateCollected,omitempty"` XCodeVersion string `plist:"Version,omitempty"` } type XCodeAppInfoPlist struct { CFBundleShortVersionString string `plist:"CFBundleShortVersionString,omitempty"` } // GetHandle returns a handle to a library func GetHandle(libs []string) *LibHandle { for _, name := range libs { libname := C.CString(name) defer C.free(unsafe.Pointer(libname)) handle := C.dlopen(libname, C.RTLD_LAZY) if handle != nil { utils.Indent(log.Debug, 2)(fmt.Sprintf("Using bundle: %s", name)) h := &LibHandle{ Handle: handle, Libname: name, } return h } } return nil } // GetSymbolPointer takes a symbol name and returns a pointer to the symbol. func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) { sym := C.CString(symbol) defer C.free(unsafe.Pointer(sym)) C.dlerror() p := C.dlsym(l.Handle, sym) e := C.dlerror() if e != nil { return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e))) } return p, nil } // Close closes a LibHandle. func (l *LibHandle) Close() error { C.dlerror() C.dlclose(l.Handle) e := C.dlerror() if e != nil { return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e))) } return nil } // Split extracts all the dyld_shared_cache libraries func Split(dyldSharedCachePath, destinationPath, xcodePath string, xcodeCache bool) error { var xcodeVersion string var xcodePathProvided bool if len(xcodePath) == 0 { var err error xcodePath, err = utils.GetXCodePath() if err != nil { return fmt.Errorf("failed to get Xcode path: %v", err) } xcodePathProvided = false } else { if !strings.HasSuffix(xcodePath, "Contents/Developer") { xcodePath = filepath.Join(xcodePath, "Contents/Developer") } xcodePathProvided = true } dscExtractorPath := filepath.Join(xcodePath, "Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle") _, err := os.Stat(dscExtractorPath) if err != nil { err_msg := "failed to find DSC extractor library in %s" if !xcodePathProvided { err_msg += " (use --xcode to specify the path to Xcode)" } return fmt.Errorf(err_msg, dscExtractorPath) } dscExtractor := GetHandle([]string{dscExtractorPath}) if dscExtractor == nil { err_msg := "unable to open a handle to the DSC extractor library in %s" if !xcodePathProvided { err_msg += " (use --xcode to specify the path to Xcode)" } return fmt.Errorf(err_msg, dscExtractorPath) } if xcodeCache { // get ExtractorVersion extVer := "1040.2.2.0.0" fat, err := macho.OpenFat(dscExtractor.Libname) switch err { case nil: // successfully opened fat mach-o if len(fat.Arches) > 0 { if sv := fat.Arches[0].SourceVersion(); sv != nil { extVer = sv.Version.String() } } fat.Close() case macho.ErrNotFat: // fall back on thin mach-o loading m, err := macho.Open(dscExtractor.Libname) if err != nil { return fmt.Errorf("failed to open mach-o %s, %v", dscExtractor.Libname, err) } if sv := m.SourceVersion(); sv != nil { extVer = sv.Version.String() } m.Close() default: // failed to load fat mach-o return fmt.Errorf("failed to open fat mach-o %s: %v", dscExtractor.Libname, err) } // get XCodeVersion xcodeContentPath := strings.TrimSuffix(dscExtractor.Libname, "/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle") xcodeContentPath = filepath.Join(xcodeContentPath, "Info.plist") data, err := os.ReadFile(xcodeContentPath) if err != nil { return fmt.Errorf("failed to read %s: %v", xcodeContentPath, err) } appInfo := XCodeAppInfoPlist{} if err := plist.NewDecoder(bytes.NewReader(data)).Decode(&appInfo); err != nil { return fmt.Errorf("failed to decode %s: %v", xcodeContentPath, err) } xcodeVersion = "14.0" if len(appInfo.CFBundleShortVersionString) > 0 { xcodeVersion = appInfo.CFBundleShortVersionString } // write Info.plist infoPlistPath := filepath.Join(destinationPath, "Info.plist") data, err = plist.MarshalIndent(XCodeInfoPlist{ ExtractorVersion: extVer, DateCollected: time.Now(), XCodeVersion: xcodeVersion, }, plist.XMLFormat, "\t") if err != nil { return fmt.Errorf("failed to marshal Xcode cache Info.plist: %v", err) } if err := os.WriteFile(infoPlistPath, data, 0644); err != nil { return fmt.Errorf("failed to write %s: %v", infoPlistPath, err) } destinationPath = filepath.Join(destinationPath, "Symbols") dscCopyPath := filepath.Join(destinationPath, "private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld/") matches, err := filepath.Glob(filepath.Join(filepath.Dir(dyldSharedCachePath), "dyld_shared_cache_*")) if err != nil { return fmt.Errorf("failed to glob dyld_shared_cache_*: %v", err) } if err := os.MkdirAll(dscCopyPath, 0750); err != nil { return fmt.Errorf("failed to create output directory %s: %v", dscCopyPath, err) } for _, match := range matches { f, err := os.Create(filepath.Join(dscCopyPath, ".copied_"+filepath.Base(match))) if err != nil { return fmt.Errorf("failed to create .copied_%s: %v", match, err) } if err := f.Close(); err != nil { return fmt.Errorf("failed to close %s: %v", f.Name(), err) } if err := utils.Copy(match, filepath.Join(dscCopyPath, filepath.Base(match))); err != nil { return fmt.Errorf("failed to copy %s to %s: %v", match, dscCopyPath, err) } } } extractorProc, err := dscExtractor.GetSymbolPointer("dyld_shared_cache_extract_dylibs_progress") if err != nil { return fmt.Errorf("failed to get symbol 'dyld_shared_cache_extract_dylibs_progress' pointer: %v", err) } dscPath := C.CString(dyldSharedCachePath) defer C.free(unsafe.Pointer(dscPath)) destPath := C.CString(destinationPath) defer C.free(unsafe.Pointer(destPath)) result := C.dsc_extract(extractorProc, dscPath, destPath) if result != 0 { return fmt.Errorf("failed to run dsc_extract: returned %d", result) } if err := dscExtractor.Close(); err != nil { return fmt.Errorf("failed to close dylib %s: %v", dscExtractor.Libname, err) } return nil }