FIRAppDistributionMachO.m 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <CommonCrypto/CommonHMAC.h>
  17. #include <mach-o/arch.h>
  18. #import <mach-o/fat.h>
  19. #import <mach-o/loader.h>
  20. #import "FIRAppDistributionMachO+Private.h"
  21. #import "FIRAppDistributionMachOSlice+Private.h"
  22. @interface FIRAppDistributionMachO ()
  23. @property(nonatomic, copy) NSFileHandle* file;
  24. @property(nonatomic, copy) NSMutableArray* slices;
  25. @end
  26. @implementation FIRAppDistributionMachO
  27. - (instancetype)initWithPath:(NSString*)path {
  28. self = [super init];
  29. if (self) {
  30. _file = [NSFileHandle fileHandleForReadingAtPath:path];
  31. _slices = [NSMutableArray new];
  32. [self extractSlices];
  33. }
  34. return self;
  35. }
  36. - (void)extractSlices {
  37. uint32_t magicValue;
  38. struct fat_header fheader;
  39. NSData* data = [self.file readDataOfLength:sizeof(fheader)];
  40. [data getBytes:&fheader length:sizeof(fheader)];
  41. magicValue = CFSwapInt32BigToHost(fheader.magic);
  42. // Check to see if the file is a FAT binary (has multiple architectures)
  43. if (magicValue == FAT_MAGIC) {
  44. uint32_t archCount = CFSwapInt32BigToHost(fheader.nfat_arch);
  45. NSUInteger archOffsets[archCount];
  46. // Gather the offsets for each architecture
  47. for (uint32_t i = 0; i < archCount; i++) {
  48. struct fat_arch arch;
  49. data = [self.file readDataOfLength:sizeof(arch)];
  50. [data getBytes:&arch length:sizeof(arch)];
  51. archOffsets[i] = CFSwapInt32BigToHost(arch.offset);
  52. }
  53. // Iterate the slices based on the offsets we extracted above
  54. for (uint32_t i = 0; i < archCount; i++) {
  55. FIRAppDistributionMachOSlice* slice = [self extractSliceAtOffset:archOffsets[i]];
  56. if (slice) [_slices addObject:slice];
  57. }
  58. } else {
  59. // If the binary is not FAT, we're dealing with a single architecture
  60. FIRAppDistributionMachOSlice* slice = [self extractSliceAtOffset:0];
  61. if (slice) [_slices addObject:slice];
  62. }
  63. }
  64. - (FIRAppDistributionMachOSlice*)extractSliceAtOffset:(NSUInteger)offset {
  65. [self.file seekToFileOffset:offset];
  66. struct mach_header header;
  67. NSData* data = [self.file readDataOfLength:sizeof(header)];
  68. [data getBytes:&header length:sizeof(header)];
  69. uint32_t magicValue = CFSwapInt32BigToHost(header.magic);
  70. // If we didn't read a valid magic value, something is wrong
  71. // Bail out immediately to prevent reading random data
  72. if (magicValue != MH_CIGAM_64 && magicValue != MH_CIGAM) {
  73. return nil;
  74. }
  75. // If the binary is 64-bit, read the reserved bit and discard it
  76. if (magicValue == MH_CIGAM_64) {
  77. uint32_t reserved;
  78. [self.file readDataOfLength:sizeof(reserved)];
  79. }
  80. for (uint32_t i = 0; i < header.ncmds; i++) {
  81. struct load_command lc;
  82. data = [self.file readDataOfLength:sizeof(lc)];
  83. [data getBytes:&lc length:sizeof(lc)];
  84. if (lc.cmd != LC_UUID) {
  85. // Move to the next load command
  86. [self.file seekToFileOffset:[self.file offsetInFile] + lc.cmdsize - sizeof(lc)];
  87. continue;
  88. }
  89. // Re-read the load command, but this time as a UUID command
  90. // so we can easily fetch the UUID
  91. [self.file seekToFileOffset:[self.file offsetInFile] - sizeof(lc)];
  92. struct uuid_command uc;
  93. data = [self.file readDataOfLength:sizeof(uc)];
  94. [data getBytes:&uc length:sizeof(uc)];
  95. NSUUID* uuid = [[NSUUID alloc] initWithUUIDBytes:uc.uuid];
  96. const NXArchInfo* arch = NXGetArchInfoFromCpuType(header.cputype, header.cpusubtype);
  97. return [[FIRAppDistributionMachOSlice alloc] initWithArch:arch uuid:uuid];
  98. }
  99. // If we got here, something is wrong. We iterated the load commands
  100. // and didn't find a UUID load command. This means the binary is corrupt.
  101. return nil;
  102. }
  103. - (NSString*)codeHash {
  104. NSMutableString* prehashedString = [NSMutableString new];
  105. NSArray* sortedSlices =
  106. [_slices sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
  107. return [[obj1 architectureName] compare:[obj2 architectureName]];
  108. }];
  109. for (FIRAppDistributionMachOSlice* slice in sortedSlices) {
  110. [prehashedString appendString:[slice uuidString]];
  111. }
  112. return [self sha1:prehashedString];
  113. }
  114. - (NSString*)sha1:(NSString*)prehashedString {
  115. NSData* data = [prehashedString dataUsingEncoding:NSUTF8StringEncoding];
  116. uint8_t digest[CC_SHA1_DIGEST_LENGTH];
  117. CC_SHA1(data.bytes, (int)data.length, digest);
  118. NSMutableString* hashedString = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
  119. for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) [hashedString appendFormat:@"%02x", digest[i]];
  120. return hashedString;
  121. }
  122. @end