From ad40aac921656431aa818b22114d5353b11ddd38 Mon Sep 17 00:00:00 2001 From: Clive Chan Date: Wed, 3 Jul 2024 18:07:33 -0700 Subject: [PATCH 1/4] Add automatic finding of offsets from identityservicesd by translating find_fat_binary_offsets.py to Go --- find_offsets/find_offsets.go | 294 +++++++++++++++++++++++++++++++++++ nac/nac.go | 59 ++++++- 2 files changed, 348 insertions(+), 5 deletions(-) create mode 100644 find_offsets/find_offsets.go diff --git a/find_offsets/find_offsets.go b/find_offsets/find_offsets.go new file mode 100644 index 0000000..96cdda7 --- /dev/null +++ b/find_offsets/find_offsets.go @@ -0,0 +1,294 @@ +package find_offsets + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "os/exec" + "strconv" + "strings" +) + +const ( + FAT_MAGIC = 0xcafebabe + MACHO_MAGIC_32 = 0xfeedface + MACHO_MAGIC_64 = 0xfeedfacf +) + +type Architecture struct { + Name string + CPUType uint32 + CPUSubtype uint32 + CPUSubtypeCaps uint32 + Offset uint32 + Size uint32 + Align uint32 + ValidMachOHeader bool +} + +type HexStrings map[string]map[string]string + +var ( + HexStringsModern = HexStrings{ + "x86_64": { + "ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)": "554889e54157415641554154534883ec28..89..48897dd04c8b3d", + "NACInitAddress": "554889e541574156415541545350b87818", + "NACKeyEstablishmentAddress": "554889e54157415641554154534881ec..010000488b05......00488b00488945d04885", + "NACSignAddress": "554889e54157415641554154534881ec..030000........................................................................................................................................................................................48....48..........................................................................................................89............................................................", + }, + "arm64e": { + "ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)": "7f2303d5ffc301d1fc6f01a9fa6702a9f85f03a9f65704a9f44f05a9fd7b06a9fd830191f30301aa....00........f9..0280b9..68..f8....00........f9....80b9..68..f8....00........f9..01..eb....0054f40300aa............................................................................................................................80b96d6a6df89f010deb....0054..0380b96d6a6df8................................................", + "NACInitAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd43019109..8352....00..10....f91f0a3fd6ff0740d1ff....d1....00..08....f9080140f9a8....f8......d2......f2......f2......f2e9", + "NACKeyEstablishmentAddress": "7f2303d5ff....d1fc6f..a9fa67..a9f85f..a9f657..a9f44f..a9fd7b..a9fd..0591....00..08....f9080140f9a8....f8......52", + "NACSignAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd430191ff....d1................08....f9......................................................................................................................................f2......f2......................d2", + }, + } +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run test.go ") + return + } + + filePath := os.Args[1] + + architectures, err := ScanMachOFATBinary(filePath) + if err != nil { + fmt.Println("Error:", err) + return + } + + printArchInfo(architectures) + + symbol := "_IDSProtoKeyTransparencyTrustedServiceReadFrom" + fmt.Println("\n-= Found Symbol Offsets =-") + for _, arch := range architectures { + offset := getSymbolOffset(filePath, symbol, arch.Name) + if offset != "" { + fmt.Printf("Offset of %s in architecture %s: %s\n", symbol, arch.Name, offset) + } else { + fmt.Printf("Symbol %s not found in architecture %s.\n", symbol, arch.Name) + } + } + + fmt.Println("") + + searchResults := SearchInArchitectures(filePath, architectures, HexStringsModern) + printSearchResults(searchResults, architectures, " (with pure Go fixed sequence search + regex)") +} + +func ScanMachOFATBinary(filePath string) (map[int]Architecture, error) { + architectures := make(map[int]Architecture) + + data, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + + magic := binary.BigEndian.Uint32(data[0:4]) + if magic != FAT_MAGIC { + offset := uint32(0x0) + isValidMachO := validateMachOHeader(data, offset) + architectures[0] = Architecture{ + Name: "x86_64", + CPUType: 0x0, + CPUSubtype: 0x0, + CPUSubtypeCaps: 0x0, + Offset: offset, + Size: uint32(len(data)), + Align: 0, + ValidMachOHeader: isValidMachO, + } + return architectures, nil + } + + numArchs := binary.BigEndian.Uint32(data[4:8]) + for i := 0; i < int(numArchs); i++ { + archOffset := 8 + i*20 + archInfo := data[archOffset : archOffset+20] + + cpuType := binary.BigEndian.Uint32(archInfo[0:4]) + cpuSubtypeFull := binary.BigEndian.Uint32(archInfo[4:8]) + offset := binary.BigEndian.Uint32(archInfo[8:12]) + size := binary.BigEndian.Uint32(archInfo[12:16]) + align := binary.BigEndian.Uint32(archInfo[16:20]) + + cpuSubtype := cpuSubtypeFull & 0x00FFFFFF + cpuSubtypeCaps := (cpuSubtypeFull >> 24) & 0xFF + + archName := getArchName(cpuType, cpuSubtype, cpuSubtypeCaps) + isValidMachO := validateMachOHeader(data, offset) + + architectures[i] = Architecture{ + Name: archName, + CPUType: cpuType, + CPUSubtype: cpuSubtype, + CPUSubtypeCaps: cpuSubtypeCaps, + Offset: offset, + Size: size, + Align: align, + ValidMachOHeader: isValidMachO, + } + } + + return architectures, nil +} + +func getArchName(cpuType, cpuSubtype, cpuSubtypeCaps uint32) string { + archNames := map[[3]uint32]string{ + {16777223, 3, 0}: "x86_64", + {16777228, 2, 128}: "arm64e", + } + key := [3]uint32{cpuType, cpuSubtype, cpuSubtypeCaps} + if name, ok := archNames[key]; ok { + return name + } + return fmt.Sprintf("Unknown (Type: %d, Subtype: %d, Subtype Capability: %d)", cpuType, cpuSubtype, cpuSubtypeCaps) +} + +func validateMachOHeader(data []byte, offset uint32) bool { + magic := binary.LittleEndian.Uint32(data[offset : offset+4]) + return magic == MACHO_MAGIC_32 || magic == MACHO_MAGIC_64 +} + +func printArchInfo(architectures map[int]Architecture) { + fmt.Println("-= Universal Binary Sections =-") + for i, arch := range architectures { + fmt.Printf("Architecture %d (%s):\n", i, arch.Name) + fmt.Printf(" CPU Type: %d (0x%x)\n", arch.CPUType, arch.CPUType) + fmt.Printf(" CPU Subtype: %d (0x%x)\n", arch.CPUSubtype, arch.CPUSubtype) + fmt.Printf(" CPU Subtype Capability: %d (0x%x)\n", arch.CPUSubtypeCaps, arch.CPUSubtypeCaps) + fmt.Printf(" Offset: 0x%x (Valid Mach-O Header: %v)\n", arch.Offset, arch.ValidMachOHeader) + fmt.Printf(" Size: %d\n", arch.Size) + fmt.Printf(" Align: %d\n", arch.Align) + } +} + +func getSymbolOffset(binaryPath, symbol, arch string) string { + archFlag := "--arch=x86_64" + if arch == "arm64e" { + archFlag = "--arch=arm64e" + } + cmd := exec.Command("/usr/bin/nm", "--defined-only", "--extern-only", archFlag, binaryPath) + output, err := cmd.Output() + if err != nil { + fmt.Printf("Error executing nm: %s\n", err) + return "" + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) >= 3 && parts[2] == symbol { + return fmt.Sprintf("0x%s", parts[0][len(parts[0])-6:]) + } + } + return "" +} + +func SearchInArchitectures(filePath string, architectures map[int]Architecture, hexStrings HexStrings) map[int]map[string][]int { + searchResults := make(map[int]map[string][]int) + + data, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Println("Error reading file:", err) + return searchResults + } + + for i, arch := range architectures { + if !arch.ValidMachOHeader { + fmt.Printf("Warning: Skipping architecture %d (%s) due to invalid Mach-O header.\n", i, arch.Name) + continue + } + + binaryData := data[arch.Offset : arch.Offset+arch.Size] + + archHexStrings, ok := hexStrings[arch.Name] + if !ok { + continue + } + + searchResults[i] = make(map[string][]int) + for name, hexString := range archHexStrings { + matches := searchWithFixedSequencesAndRegex(binaryData, hexString) + if len(matches) > 0 { + searchResults[i][name] = matches + } + } + } + return searchResults +} + +func searchWithFixedSequencesAndRegex(binaryData []byte, hexString string) []int { + var results []int + + hexString = strings.ReplaceAll(hexString, " ", "") + hexString = strings.ToLower(hexString) + + longestFixed := findLongestFixedSequence(hexString) + fixedBytes := hexStringToBytes(longestFixed) + + start := 0 + for start < len(binaryData) { + index := bytes.Index(binaryData[start:], fixedBytes) + if index == -1 { + break + } + + if matchHexPattern(binaryData[start+index:], hexString) { + results = append(results, start+index) + } + start += index + len(fixedBytes) // Move start position past the found fixedBytes + } + return results +} + +func findLongestFixedSequence(hexPattern string) string { + wildcardIndex := strings.Index(hexPattern, "..") + if wildcardIndex != -1 { + return hexPattern[:wildcardIndex] + } + return hexPattern +} + +func hexStringToBytes(hexString string) []byte { + b, _ := hex.DecodeString(hexString) + return b +} + + +func matchHexPattern(data []byte, hexPattern string) bool { + for i := 0; i < len(hexPattern); i += 2 { + bytePattern := hexPattern[i : i+2] + if bytePattern == ".." { + continue + } + byteValue, err := strconv.ParseUint(bytePattern, 16, 8) + if err != nil { + return false + } + if data[i/2] != byte(byteValue) { + return false + } + } + return true +} + +func printSearchResults(searchResults map[int]map[string][]int, architectures map[int]Architecture, suffix string) { + fmt.Printf("-= Found Hex Offsets%s =-\n", suffix) + for archIndex, results := range searchResults { + archName := architectures[archIndex].Name + fmt.Printf("Architecture %d (%s):\n", archIndex, archName) + for name, offsets := range results { + offsetStrings := make([]string, len(offsets)) + for i, offset := range offsets { + offsetStrings[i] = fmt.Sprintf("0x%x", offset) + } + fmt.Printf(" %s: %s\n", name, strings.Join(offsetStrings, "; ")) + } + } +} diff --git a/nac/nac.go b/nac/nac.go index 6f673bd..dca3270 100644 --- a/nac/nac.go +++ b/nac/nac.go @@ -16,10 +16,12 @@ import ( "runtime" "unsafe" + "github.com/beeper/mac-registration-provider/find_offsets" "github.com/beeper/mac-registration-provider/versions" ) const identityservicesd = "/System/Library/PrivateFrameworks/IDS.framework/identityservicesd.app/Contents/MacOS/identityservicesd" +const symbol = "IDSProtoKeyTransparencyTrustedServiceReadFrom" var nacInitAddr, nacKeyEstablishmentAddr, nacSignAddr unsafe.Pointer @@ -59,14 +61,34 @@ func Load() error { offs = offsets[hash].x86 } if offs.ReferenceSymbol == "" { - return NoOffsetsError{ - Hash: hex.EncodeToString(hash[:]), - Version: versions.Current.SoftwareVersion, - BuildID: versions.Current.SoftwareBuildID, - Arch: runtime.GOARCH, + // Call the FindOffsets function directly + newOffsets, err := FindOffsets(identityservicesd) + if err != nil { + return fmt.Errorf("failed to find offsets: %v", err) + } + + if runtime.GOARCH == "arm64" { + offs = newOffsets.arm64 + } else { + offs = newOffsets.x86 + } + + if offs.ReferenceSymbol == "" { + return NoOffsetsError{ + Hash: hex.EncodeToString(hash[:]), + Version: versions.Current.SoftwareVersion, + BuildID: versions.Current.SoftwareBuildID, + Arch: runtime.GOARCH, + } } } + fmt.Printf("Reference Symbol: %s\n", offs.ReferenceSymbol) + fmt.Printf("Reference Address: %06x\n", offs.ReferenceAddress) + fmt.Printf("NAC Init Address: %06x\n", offs.NACInitAddress) + fmt.Printf("NAC Key Establishment Address: %06x\n", offs.NACKeyEstablishmentAddress) + fmt.Printf("NAC Sign Address: %06x\n", offs.NACSignAddress) + handle := C.dlopen(C.CString(identityservicesd), C.RTLD_LAZY) if handle == nil { return fmt.Errorf("failed to load %s: %v", identityservicesd, C.GoString(C.dlerror())) @@ -82,6 +104,33 @@ func Load() error { return nil } +func FindOffsets(filePath string) (imdOffsetTuple, error) { + architectures, err := find_offsets.ScanMachOFATBinary(filePath) + if err != nil { + return imdOffsetTuple{}, err + } + + searchResults := find_offsets.SearchInArchitectures(filePath, architectures, find_offsets.HexStringsModern) + offsets := imdOffsetTuple{ + x86: imdOffsets{ + ReferenceSymbol: symbol, + ReferenceAddress: searchResults[0]["ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)"][0], + NACInitAddress: searchResults[0]["NACInitAddress"][0], + NACKeyEstablishmentAddress: searchResults[0]["NACKeyEstablishmentAddress"][0], + NACSignAddress: searchResults[0]["NACSignAddress"][0], + }, + arm64: imdOffsets{ + ReferenceSymbol: symbol, + ReferenceAddress: searchResults[1]["ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)"][0], + NACInitAddress: searchResults[1]["NACInitAddress"][0], + NACKeyEstablishmentAddress: searchResults[1]["NACKeyEstablishmentAddress"][0], + NACSignAddress: searchResults[1]["NACSignAddress"][0], + }, + } + + return offsets, nil +} + func MeowMemory() func() { runtime.LockOSThread() pool := C.meowMakePool() From 9ab52c42d122af076223606971b36d5df1afc729 Mon Sep 17 00:00:00 2001 From: Alex Bason Date: Wed, 11 Dec 2024 07:34:23 -0500 Subject: [PATCH 2/4] Remove deprecated io/ioutil use --- find_offsets/find_offsets.go | 48 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/find_offsets/find_offsets.go b/find_offsets/find_offsets.go index 96cdda7..916bb22 100644 --- a/find_offsets/find_offsets.go +++ b/find_offsets/find_offsets.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "encoding/hex" "fmt" - "io/ioutil" "os" "os/exec" "strconv" @@ -13,9 +12,9 @@ import ( ) const ( - FAT_MAGIC = 0xcafebabe - MACHO_MAGIC_32 = 0xfeedface - MACHO_MAGIC_64 = 0xfeedfacf + FAT_MAGIC = 0xcafebabe + MACHO_MAGIC_32 = 0xfeedface + MACHO_MAGIC_64 = 0xfeedfacf ) type Architecture struct { @@ -35,15 +34,15 @@ var ( HexStringsModern = HexStrings{ "x86_64": { "ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)": "554889e54157415641554154534883ec28..89..48897dd04c8b3d", - "NACInitAddress": "554889e541574156415541545350b87818", + "NACInitAddress": "554889e541574156415541545350b87818", "NACKeyEstablishmentAddress": "554889e54157415641554154534881ec..010000488b05......00488b00488945d04885", - "NACSignAddress": "554889e54157415641554154534881ec..030000........................................................................................................................................................................................48....48..........................................................................................................89............................................................", + "NACSignAddress": "554889e54157415641554154534881ec..030000........................................................................................................................................................................................48....48..........................................................................................................89............................................................", }, "arm64e": { "ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom)": "7f2303d5ffc301d1fc6f01a9fa6702a9f85f03a9f65704a9f44f05a9fd7b06a9fd830191f30301aa....00........f9..0280b9..68..f8....00........f9....80b9..68..f8....00........f9..01..eb....0054f40300aa............................................................................................................................80b96d6a6df89f010deb....0054..0380b96d6a6df8................................................", - "NACInitAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd43019109..8352....00..10....f91f0a3fd6ff0740d1ff....d1....00..08....f9080140f9a8....f8......d2......f2......f2......f2e9", + "NACInitAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd43019109..8352....00..10....f91f0a3fd6ff0740d1ff....d1....00..08....f9080140f9a8....f8......d2......f2......f2......f2e9", "NACKeyEstablishmentAddress": "7f2303d5ff....d1fc6f..a9fa67..a9f85f..a9f657..a9f44f..a9fd7b..a9fd..0591....00..08....f9080140f9a8....f8......52", - "NACSignAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd430191ff....d1................08....f9......................................................................................................................................f2......f2......................d2", + "NACSignAddress": "7f2303d5fc6fbaa9fa6701a9f85f02a9f65703a9f44f04a9fd7b05a9fd430191ff....d1................08....f9......................................................................................................................................f2......f2......................d2", }, } ) @@ -84,7 +83,7 @@ func main() { func ScanMachOFATBinary(filePath string) (map[int]Architecture, error) { architectures := make(map[int]Architecture) - data, err := ioutil.ReadFile(filePath) + data, err := os.ReadFile(filePath) if err != nil { return nil, err } @@ -193,7 +192,7 @@ func getSymbolOffset(binaryPath, symbol, arch string) string { func SearchInArchitectures(filePath string, architectures map[int]Architecture, hexStrings HexStrings) map[int]map[string][]int { searchResults := make(map[int]map[string][]int) - data, err := ioutil.ReadFile(filePath) + data, err := os.ReadFile(filePath) if err != nil { fmt.Println("Error reading file:", err) return searchResults @@ -260,22 +259,21 @@ func hexStringToBytes(hexString string) []byte { return b } - func matchHexPattern(data []byte, hexPattern string) bool { - for i := 0; i < len(hexPattern); i += 2 { - bytePattern := hexPattern[i : i+2] - if bytePattern == ".." { - continue - } - byteValue, err := strconv.ParseUint(bytePattern, 16, 8) - if err != nil { - return false - } - if data[i/2] != byte(byteValue) { - return false - } - } - return true + for i := 0; i < len(hexPattern); i += 2 { + bytePattern := hexPattern[i : i+2] + if bytePattern == ".." { + continue + } + byteValue, err := strconv.ParseUint(bytePattern, 16, 8) + if err != nil { + return false + } + if data[i/2] != byte(byteValue) { + return false + } + } + return true } func printSearchResults(searchResults map[int]map[string][]int, architectures map[int]Architecture, suffix string) { From bff52c74f2b7803aabf642bbe707e689895531fd Mon Sep 17 00:00:00 2001 From: Alex Bason Date: Wed, 11 Dec 2024 07:58:11 -0500 Subject: [PATCH 3/4] Add an option to find offsets --- find_offsets/find_offsets.go | 9 +-------- main.go | 9 +++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/find_offsets/find_offsets.go b/find_offsets/find_offsets.go index 916bb22..48db55b 100644 --- a/find_offsets/find_offsets.go +++ b/find_offsets/find_offsets.go @@ -47,14 +47,7 @@ var ( } ) -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run test.go ") - return - } - - filePath := os.Args[1] - +func PrintOffsets(filePath string) { architectures, err := ScanMachOFATBinary(filePath) if err != nil { fmt.Println("Error:", err) diff --git a/main.go b/main.go index fe84b3d..7726c04 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/beeper/mac-registration-provider/find_offsets" "github.com/beeper/mac-registration-provider/nac" "github.com/beeper/mac-registration-provider/versions" ) @@ -34,9 +35,17 @@ var jsonOutput = flag.Bool("json", false, "Output JSON instead of text") var submitUserAgent = fmt.Sprintf("mac-registration-provider/%s go/%s macOS/%s", Commit[:8], strings.TrimPrefix(runtime.Version(), "go"), versions.Current.SoftwareVersion) var once = flag.Bool("once", false, "Generate a single validation data, print it to stdout and exit") var checkCompatibility = flag.Bool("check-compatibility", false, "Check if offsets for the current OS version are available and exit") +var shouldFindOffsets = flag.Bool("find-offsets", false, "Find offsets in the specified binary") +var identityServiceDPath = flag.String("identityservicesd", "/System/Library/PrivateFrameworks/IDS.framework/identityservicesd.app/Contents/MacOS/identityservicesd", "Path to the identityservicesd binary") func main() { flag.Parse() + + if *shouldFindOffsets { + find_offsets.PrintOffsets(*identityServiceDPath) + return + } + var urls []string if *submitInterval > 0 { urls = flag.Args() From 652d9e1057bbf53f795a266f47384658117bd5a9 Mon Sep 17 00:00:00 2001 From: Alex Bason Date: Wed, 11 Dec 2024 08:06:59 -0500 Subject: [PATCH 4/4] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e4eb5e..bbd3ae2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ not have access to Beeper Cloud, you can use this to generate an iMessage registration code and use it in Beeper Mini. ## Supported MacOS versions -The tool is currently quite hacky, so it only works on specific versions of macOS. +~~The tool is currently quite hacky, so it only works on specific versions of macOS.~~ * Intel: 10.14.6, 10.15.1 - 10.15.7, 11.5 - 11.7, 12.7.1, 13.3.1, 13.5 - 13.6.4, 14.0 - 14.3 * Apple Silicon: 12.7.1, 13.3.1, 13.5 - 13.6.4, 14.0 - 14.3 @@ -40,3 +40,4 @@ with Beeper is Relay, which is the default. * `-submit-interval` - The interval to submit data at (required). * `-submit-token` - A bearer token to include when submitting data (defaults to no auth). * `-once` - generate a single registration data, print it to stdout and exit +* `-find-offsets` - find the offsets of the relevant bytes in the binary. This is used for debugging.