diff --git a/Sources/MachOKit/Extension/Sequence+.swift b/Sources/MachOKit/Extension/Sequence+.swift index 558b129..6e2c2a3 100644 --- a/Sources/MachOKit/Extension/Sequence+.swift +++ b/Sources/MachOKit/Extension/Sequence+.swift @@ -310,3 +310,17 @@ extension Sequence where Element == MachOImage.Symbol { return nil } } + +extension Sequence where Element == CodeSignCodeDirectory { + public var bestHashTyped: CodeSignCodeDirectory? { + let hashTypes: [CodeSignHashType] = [ + .sha384, + .sha256, + .sha256_truncated, + .sha1 + ] + return self.min(by: { lhs, rhs in + hashTypes.firstIndex(of: lhs.hashType)! < hashTypes.firstIndex(of: rhs.hashType)! + }) + } +} diff --git a/Sources/MachOKit/MachOFile+CodeSign.swift b/Sources/MachOKit/MachOFile+CodeSign.swift new file mode 100644 index 0000000..1100a15 --- /dev/null +++ b/Sources/MachOKit/MachOFile+CodeSign.swift @@ -0,0 +1,140 @@ +// +// MachOFile+CodeSign.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +extension MachOFile { + public struct CodeSign { + public let data: Data + public let isSwapped: Bool // bigEndian => false + } +} + +extension MachOFile.CodeSign { + init(data: Data) { + self.data = data + self.isSwapped = CFByteOrderGetCurrent() != CFByteOrderBigEndian.rawValue + } +} + +extension MachOFile.CodeSign: CodeSignProtocol { + public var superBlob: CodeSignSuperBlob? { + data.withUnsafeBytes { + guard let basePtr = $0.baseAddress else { return nil } + var layout = basePtr.assumingMemoryBound(to: CS_SuperBlob.self).pointee + if isSwapped { layout = layout.swapped } + return .init( + layout: layout, + offset: 0 + ) + } + } + + public var codeDirectories: [CodeSignCodeDirectory] { + data.withUnsafeBytes { bufferPointer in + guard let baseAddress = bufferPointer.baseAddress, + let superBlob else { + return [] + } + let blobIndices = superBlob.blobIndices(in: self) + return blobIndices + .compactMap { + let offset: Int = numericCast($0.offset) + let ptr = baseAddress.advanced(by: offset) + let _blob = ptr.assumingMemoryBound(to: CS_GenericBlob.self).pointee + let blob = CodeSignGenericBlob( + layout: isSwapped ? _blob.swapped : _blob + ) + guard blob.magic == .codedirectory else { + return nil + } + return ( + ptr.assumingMemoryBound(to: CS_CodeDirectory.self).pointee, + offset + ) + } + .map { + isSwapped ? .init(layout: $0.swapped, offset: $1) + : .init(layout: $0, offset: $1) + } + } + } + + public var requirementsBlob: CodeSignSuperBlob? { + guard let superBlob else { + return nil + } + let blobIndices = superBlob.blobIndices(in: self) + guard let index = blobIndices.first( + where: { $0.type == .requirements } + ) else { + return nil + } + return data.withUnsafeBytes { bufferPointer in + guard let baseAddress = bufferPointer.baseAddress else { + return nil + } + let offset: Int = numericCast(index.offset) + let ptr = baseAddress.advanced(by: offset) + var _blob = ptr + .assumingMemoryBound(to: CS_SuperBlob.self) + .pointee + if isSwapped { _blob = _blob.swapped } + + return .init( + layout: _blob, + offset: offset + ) + } + } +} + +extension MachOFile.CodeSign { + /// Get blob data as `Data` + /// - Parameters: + /// - superBlob: SuperBlob to which index belongs + /// - index: Index of the blob to be gotten + /// - includesGenericInfo: A boolean value that indicates whether the data defined in the ``CodeSignGenericBlob``, such as magic and length, are included or not. + /// - Returns: Data of blob + /// + /// Note that when converting from this data to other blob models, byte swapping must be performed appropriately for the ``MachOFile.CodeSign.isSwapped`` parameter. + public func blobData( + in superBlob: CodeSignSuperBlob, + at index: CodeSignBlobIndex, + includesGenericInfo: Bool = true + ) -> Data? { + data.withUnsafeBytes { bufferPointer in + guard let baseAddress = bufferPointer.baseAddress else { + return nil + } + let offset: Int = numericCast(superBlob.offset) + numericCast(index.offset) + guard let _blob: CodeSignGenericBlob = .load( + from: baseAddress, + offset: offset, + isSwapped: isSwapped + ) else { return nil } + + let data = Data( + bytes: baseAddress.advanced(by: offset), + count: numericCast(_blob.length) + ) + if includesGenericInfo { + return data + } else { + return data.advanced(by: CodeSignGenericBlob.layoutSize) + } + } + } + + public func blobIndices( + of superBlob: CodeSignSuperBlob + ) -> AnySequence { + superBlob.blobIndices(in: self) + } +} diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index 2a7a6c6..4334d19 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -412,3 +412,17 @@ extension MachOFile { return false } } + +extension MachOFile { + public var codeSign: CodeSign? { + guard let info = loadCommands.codeSignature else { + return nil + } + let data = fileHandle.readData( + offset: UInt64(headerStartOffset) + numericCast(info.dataoff), + size: numericCast(info.datasize) + ) + + return .init(data: data) + } +} diff --git a/Sources/MachOKit/MachOImage+CodeSign.swift b/Sources/MachOKit/MachOImage+CodeSign.swift new file mode 100644 index 0000000..d881974 --- /dev/null +++ b/Sources/MachOKit/MachOImage+CodeSign.swift @@ -0,0 +1,171 @@ +// +// MachOImage+CodeSign.swift +// +// +// Created by p-x9 on 2024/03/10. +// +// + +import Foundation + +extension MachOImage { + public struct CodeSign { + public let basePointer: UnsafeRawPointer + public let codeSignatureSize: Int + public let isSwapped: Bool // bigEndian => false + } +} + +extension MachOImage.CodeSign: CodeSignProtocol { + init?( + codeSignature: linkedit_data_command, + linkedit: SegmentCommand64, + vmaddrSlide: Int + ) { + guard let linkeditStartPtr = linkedit.startPtr( + vmaddrSlide: vmaddrSlide + ) else { + return nil + } + + let start = linkeditStartPtr + .advanced(by: -numericCast(linkedit.fileoff)) + .advanced(by: numericCast(codeSignature.dataoff)) + let size: Int = numericCast(codeSignature.datasize) + let isSwapped = CFByteOrderGetCurrent() != CFByteOrderBigEndian.rawValue + + self.init( + basePointer: start, + codeSignatureSize: size, + isSwapped: isSwapped + ) + } + + init?( + codeSignature: linkedit_data_command, + linkedit: SegmentCommand, + vmaddrSlide: Int + ) { + guard let linkeditStartPtr = linkedit.startPtr( + vmaddrSlide: vmaddrSlide + ) else { + return nil + } + + let start = linkeditStartPtr + .advanced(by: -numericCast(linkedit.fileoff)) + .advanced(by: numericCast(codeSignature.dataoff)) + .assumingMemoryBound(to: UInt8.self) + let size: Int = numericCast(codeSignature.datasize) + let isSwapped = CFByteOrderGetCurrent() != CFByteOrderBigEndian.rawValue + + self.init( + basePointer: start, + codeSignatureSize: size, + isSwapped: isSwapped + ) + } +} + +extension MachOImage.CodeSign { + public var superBlob: CodeSignSuperBlob? { + var layout = basePointer + .assumingMemoryBound(to: CS_SuperBlob.self) + .pointee + if isSwapped { layout = layout.swapped } + return .init( + layout: layout, + offset: 0 + ) + } + + public var codeDirectories: [CodeSignCodeDirectory] { + guard let superBlob else { + return [] + } + let blobIndices = superBlob.blobIndices(in: self) + return blobIndices + .compactMap { + let offset: Int = numericCast($0.offset) + let ptr = basePointer.advanced(by: offset) + let _blob = ptr.assumingMemoryBound(to: CS_GenericBlob.self).pointee + let blob = CodeSignGenericBlob( + layout: isSwapped ? _blob.swapped : _blob + ) + guard blob.magic == .codedirectory else { + return nil + } + return ( + ptr.assumingMemoryBound(to: CS_CodeDirectory.self).pointee, + offset + ) + } + .map { + isSwapped ? .init(layout: $0.swapped, offset: $1) + : .init(layout: $0, offset: $1) + } + } + + public var requirementsBlob: CodeSignSuperBlob? { + guard let superBlob else { + return nil + } + let blobIndices = superBlob.blobIndices(in: self) + guard let index = blobIndices.first( + where: { $0.type == .requirements } + ) else { + return nil + } + + let offset: Int = numericCast(index.offset) + let ptr = basePointer.advanced(by: offset) + var _blob = ptr + .assumingMemoryBound(to: CS_SuperBlob.self) + .pointee + if isSwapped { _blob = _blob.swapped } + + return .init( + layout: _blob, + offset: offset + ) + } +} + +extension MachOImage.CodeSign { + /// Get blob data as `Data` + /// - Parameters: + /// - superBlob: SuperBlob to which index belongs + /// - index: Index of the blob to be gotten + /// - includesGenericInfo: A boolean value that indicates whether the data defined in the ``CodeSignGenericBlob``, such as magic and length, are included or not. + /// - Returns: Data of blob + /// + /// Note that when converting from this data to other blob models, byte swapping must be performed appropriately for the ``MachOImage.CodeSign.isSwapped`` parameter. + public func blobData( + in superBlob: CodeSignSuperBlob, + at index: CodeSignBlobIndex, + includesGenericInfo: Bool = true + ) -> Data? { + let offset: Int = numericCast(superBlob.offset) + numericCast(index.offset) + guard let _blob: CodeSignGenericBlob = .load( + from: basePointer, + offset: offset, + isSwapped: isSwapped + ) else { return nil } + + let data = Data( + bytes: basePointer.advanced(by: offset), + count: numericCast(_blob.length) + ) + if includesGenericInfo { + return data + } else { + return data.advanced(by: CodeSignGenericBlob.layoutSize) + } + } + + public func blobIndices( + of superBlob: CodeSignSuperBlob + ) -> AnySequence { + superBlob.blobIndices(in: self) + } +} diff --git a/Sources/MachOKit/MachOImage.swift b/Sources/MachOKit/MachOImage.swift index 07e678c..6620901 100644 --- a/Sources/MachOKit/MachOImage.swift +++ b/Sources/MachOKit/MachOImage.swift @@ -543,3 +543,28 @@ extension MachOImage { return nil } } + +extension MachOImage { + public var codeSign: CodeSign? { + guard let vmaddrSlide, + let codeSignature = loadCommands.codeSignature, + codeSignature.datasize > 0 else { + return nil + } + + if let linkedit = loadCommands.linkedit64 { + return .init( + codeSignature: codeSignature.layout, + linkedit: linkedit, + vmaddrSlide: vmaddrSlide + ) + } else if let linkedit = loadCommands.linkedit { + return .init( + codeSignature: codeSignature.layout, + linkedit: linkedit, + vmaddrSlide: vmaddrSlide + ) + } + return nil + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+codeLimit64.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+codeLimit64.swift new file mode 100644 index 0000000..f7d92b1 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+codeLimit64.swift @@ -0,0 +1,52 @@ +// +// CodeSignCodeDirectory+codeLimit64.swift +// +// +// Created by p-x9 on 2024/03/11. +// +// + +import Foundation + +extension CodeSignCodeDirectory { + public func codeLimit64(in signature: MachOFile.CodeSign) -> CodeLimit64? { + guard isSupportsCodeLimit64 else { + return nil + } + let layout: CS_CodeDirectory_CodeLimit64? = signature.data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + return baseAddress + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_CodeLimit64.self) + .pointee + } + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} + +extension CodeSignCodeDirectory { + public func codeLimit64(in signature: MachOImage.CodeSign) -> CodeLimit64? { + guard isSupportsCodeLimit64 else { + return nil + } + let layout: CS_CodeDirectory_CodeLimit64? = signature.basePointer + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_CodeLimit64.self) + .pointee + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+executableSegment.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+executableSegment.swift new file mode 100644 index 0000000..3f665dc --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+executableSegment.swift @@ -0,0 +1,54 @@ +// +// CodeSignCodeDirectory+executableSegment.swift +// +// +// Created by p-x9 on 2024/03/11. +// +// + +import Foundation + +extension CodeSignCodeDirectory { + public func executableSegment(in signature: MachOFile.CodeSign) -> ExecutableSegment? { + guard isSupportsExecSegment else { + return nil + } + let layout: CS_CodeDirectory_ExecSeg? = signature.data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + return baseAddress + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .advanced(by: CodeLimit64.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_ExecSeg.self) + .pointee + } + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} + +extension CodeSignCodeDirectory { + public func executableSegment(in signature: MachOImage.CodeSign) -> ExecutableSegment? { + guard isSupportsExecSegment else { + return nil + } + let layout: CS_CodeDirectory_ExecSeg? = signature.basePointer + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .advanced(by: CodeLimit64.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_ExecSeg.self) + .pointee + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+runtime.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+runtime.swift new file mode 100644 index 0000000..3d75da0 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+runtime.swift @@ -0,0 +1,93 @@ +// +// CodeSignCodeDirectory+runtime.swift +// +// +// Created by p-x9 on 2024/03/11. +// +// + +import Foundation + +extension CodeSignCodeDirectory { + public func runtime(in signature: MachOFile.CodeSign) -> Runtime? { + guard isSupportsRuntime else { + return nil + } + let layout: CS_CodeDirectory_Runtime? = signature.data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + return baseAddress + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .advanced(by: CodeLimit64.layoutSize) + .advanced(by: ExecutableSegment.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_Runtime.self) + .pointee + } + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } + + public func runtime(in signature: MachOImage.CodeSign) -> Runtime? { + guard isSupportsRuntime else { + return nil + } + let layout: CS_CodeDirectory_Runtime? = signature.basePointer + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .advanced(by: TeamIdOffset.layoutSize) + .advanced(by: CodeLimit64.layoutSize) + .advanced(by: ExecutableSegment.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_Runtime.self) + .pointee + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} + +extension CodeSignCodeDirectory { + public func preEncryptHash( + forSlot index: Int, + in signature: MachOFile.CodeSign + ) -> Data? { + guard 0 <= index, + index < Int(layout.nCodeSlots), + let runtime = runtime(in: signature), + runtime.preEncryptOffset != 0 else { + return nil + } + let size: Int = numericCast(layout.hashSize) + let offset = offset + + numericCast(runtime.preEncryptOffset) + + index * size + return signature.data[offset ..< offset + size] + } + + public func preEncryptHash( + forSlot index: Int, + in signature: MachOImage.CodeSign + ) -> Data? { + guard 0 <= index, + index < Int(layout.nCodeSlots), + let runtime = runtime(in: signature), + runtime.preEncryptOffset != 0 else { + return nil + } + let size: Int = numericCast(layout.hashSize) + let offset = offset + + numericCast(runtime.preEncryptOffset) + + index * size + return Data( + bytes: signature.basePointer.advanced(by: offset), + count: size + ) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+scatter.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+scatter.swift new file mode 100644 index 0000000..1c8c3a2 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+scatter.swift @@ -0,0 +1,50 @@ +// +// CodeSignCodeDirectory+scatter.swift +// +// +// Created by p-x9 on 2024/03/11. +// +// + +import Foundation + +extension CodeSignCodeDirectory { + public func scatterOffset(in signature: MachOFile.CodeSign) -> ScatterOffset? { + guard isSupportsScatter else { + return nil + } + let layout: CS_CodeDirectory_Scatter? = signature.data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + return baseAddress + .advanced(by: offset) + .advanced(by: layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_Scatter.self) + .pointee + } + guard let layout else { return nil } + + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} + +extension CodeSignCodeDirectory { + public func scatterOffset(in signature: MachOImage.CodeSign) -> ScatterOffset? { + guard isSupportsScatter else { + return nil + } + let layout: CS_CodeDirectory_Scatter? = signature.basePointer + .advanced(by: offset) + .advanced(by: layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_Scatter.self) + .pointee + guard let layout else { return nil } + + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+teamID.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+teamID.swift new file mode 100644 index 0000000..76271bc --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory+teamID.swift @@ -0,0 +1,77 @@ +// +// CodeSignCodeDirectory+teamID.swift +// +// +// Created by p-x9 on 2024/03/11. +// +// + +import Foundation + +extension CodeSignCodeDirectory { + public func teamIdOffset(in signature: MachOFile.CodeSign) -> TeamIdOffset? { + guard isSupportsScatter else { + return nil + } + let layout: CS_CodeDirectory_TeamID? = signature.data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + return baseAddress + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_TeamID.self) + .pointee + } + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } + + public func teamId(in signature: MachOFile.CodeSign) -> String? { + guard let teamIdOffset = teamIdOffset(in: signature), + teamIdOffset.teamOffset != 0 else { + return nil + } + return signature.data.withUnsafeBytes { + let baseAddress = $0.baseAddress! + let ptr = baseAddress + .advanced(by: offset) + .advanced(by: Int(teamIdOffset.teamOffset)) + .assumingMemoryBound(to: CChar.self) + return String(cString: ptr) + } + } +} + +extension CodeSignCodeDirectory { + public func teamIdOffset(in signature: MachOImage.CodeSign) -> TeamIdOffset? { + guard isSupportsScatter else { + return nil + } + let layout: CS_CodeDirectory_TeamID? = signature.basePointer + .advanced(by: offset) + .advanced(by: layoutSize) + .advanced(by: ScatterOffset.layoutSize) + .assumingMemoryBound(to: CS_CodeDirectory_TeamID.self) + .pointee + guard let layout else { return nil } + return .init( + layout: signature.isSwapped ? layout.swapped : layout + ) + } + + public func teamId(in signature: MachOImage.CodeSign) -> String? { + guard let teamIdOffset = teamIdOffset(in: signature), + teamIdOffset.teamOffset != 0 else { + return nil + } + let ptr = signature.basePointer + .advanced(by: offset) + .advanced(by: Int(teamIdOffset.teamOffset)) + .assumingMemoryBound(to: CChar.self) + return String(cString: ptr) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory.swift b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory.swift new file mode 100644 index 0000000..c0fde68 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeDirectory/CodeSignCodeDirectory.swift @@ -0,0 +1,311 @@ +// +// CodeSignCodeDirectory.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC +import CommonCrypto + +public struct CodeSignCodeDirectory: LayoutWrapper { + public typealias Layout = CS_CodeDirectory + + public var layout: Layout + public let offset: Int // offset from start of linkedit_data +} + +extension CodeSignCodeDirectory { + public var magic: CodeSignMagic! { + .init(rawValue: layout.magic) + } + + public var hashType: CodeSignHashType! { + .init(rawValue: UInt32(layout.hashType)) + } + +} +extension CodeSignCodeDirectory { + public func identifier(in signature: MachOFile.CodeSign) -> String { + signature.data.withUnsafeBytes { + bufferPointer in + guard let baseAddress = bufferPointer.baseAddress else { + return "" + } + return String( + cString: baseAddress + .advanced(by: offset) + .advanced(by: numericCast(layout.identOffset)) + .assumingMemoryBound(to: CChar.self) + ) + } + } + + public func identifier(in signature: MachOImage.CodeSign) -> String { + String( + cString: signature.basePointer + .advanced(by: offset) + .advanced(by: numericCast(layout.identOffset)) + .assumingMemoryBound(to: CChar.self) + ) + } +} + +extension CodeSignCodeDirectory { + public func hash( + in signature: MachOFile.CodeSign + ) -> Data? { + let data = signature.data[offset ..< offset + numericCast(layout.length)] + let length: CC_LONG = numericCast(layout.length) + + return data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + switch hashType { + case .sha1: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) + CC_SHA1(baseAddress, length, &digest) + return Data(digest) + case .sha256, .sha256_truncated: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + CC_SHA256(baseAddress, length, &digest) + return Data(digest) + case .sha384: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH)) + CC_SHA384(baseAddress, length, &digest) + return Data(digest) + case .none: + return nil + } + } + } + + public func hash( + in signature: MachOImage.CodeSign + ) -> Data? { + let data = Data( + bytes: signature.basePointer.advanced(by: offset), + count: numericCast(layout.length) + ) + let length: CC_LONG = numericCast(layout.length) + + return data.withUnsafeBytes { + guard let baseAddress = $0.baseAddress else { + return nil + } + switch hashType { + case .sha1: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) + CC_SHA1(baseAddress, length, &digest) + return Data(digest) + case .sha256, .sha256_truncated: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + CC_SHA256(baseAddress, length, &digest) + return Data(digest) + case .sha384: + var digest = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH)) + CC_SHA384(baseAddress, length, &digest) + return Data(digest) + case .none: + return nil + } + } + } +} + +extension CodeSignCodeDirectory { + public func hash( + forSlot index: Int, + in signature: MachOFile.CodeSign + ) -> Data? { + guard -Int(layout.nSpecialSlots) <= index, + index < Int(layout.nCodeSlots) else { + return nil + } + let size: Int = numericCast(layout.hashSize) + let offset = offset + numericCast(layout.hashOffset) + index * size + return signature.data[offset ..< offset + size] + } + + public func hash( + forSlot index: Int, + in signature: MachOImage.CodeSign + ) -> Data? { + guard -Int(layout.nSpecialSlots) <= index, + index < Int(layout.nCodeSlots) else { + return nil + } + let size: Int = numericCast(layout.hashSize) + let offset = offset + numericCast(layout.hashOffset) + index * size + return Data( + bytes: signature.basePointer.advanced(by: offset), + count: size + ) + } + +} + +extension CodeSignCodeDirectory { + public func hash( + for specialSlot: CodeSignSpecialSlotType, + in signature: MachOFile.CodeSign + ) -> Data? { + hash(forSlot: -specialSlot.rawValue, in: signature) + } + + public func hash( + for specialSlot: CodeSignSpecialSlotType, + in signature: MachOImage.CodeSign + ) -> Data? { + hash(forSlot: -specialSlot.rawValue, in: signature) + } +} + +extension CodeSignCodeDirectory { + public var isSupportsScatter: Bool { + layout.version >= CS_SUPPORTSSCATTER + } + + public var isSupportsTeamID: Bool { + layout.version >= CS_SUPPORTSTEAMID + } + + public var isSupportsCodeLimit64: Bool { + layout.version >= CS_SUPPORTSCODELIMIT64 + } + + public var isSupportsExecSegment: Bool { + layout.version >= CS_SUPPORTSEXECSEG + } + + public var isSupportsRuntime: Bool { + layout.version >= CS_SUPPORTSRUNTIME + } + + public var isSupportsLinkage: Bool { + layout.version >= CS_SUPPORTSLINKAGE + } +} + +extension CodeSignCodeDirectory { + public struct ScatterOffset: LayoutWrapper { + public typealias Layout = CS_CodeDirectory_Scatter + + public var layout: Layout + } + + public struct TeamIdOffset: LayoutWrapper { + public typealias Layout = CS_CodeDirectory_TeamID + + public var layout: Layout + } + + public struct CodeLimit64: LayoutWrapper { + public typealias Layout = CS_CodeDirectory_CodeLimit64 + + public var layout: Layout + } + + public struct ExecutableSegment: LayoutWrapper { + public typealias Layout = CS_CodeDirectory_ExecSeg + + public var layout: Layout + + public var flags: CodeSignExecSegmentFlags { + .init(rawValue: layout.execSegFlags) + } + } + + public struct Runtime: LayoutWrapper { + public typealias Layout = CS_CodeDirectory_Runtime + + public var layout: Layout + + public var runtime: Version { + .init(layout.runtime) + } + } +} + +extension CS_CodeDirectory { + var isSwapped: Bool { + magic < 0xfade0000 + } + + var swapped: CS_CodeDirectory { + .init( + magic: magic.byteSwapped, + length: length.byteSwapped, + version: version.byteSwapped, + flags: flags.byteSwapped, + hashOffset: hashOffset.byteSwapped, + identOffset: identOffset.byteSwapped, + nSpecialSlots: nSpecialSlots.byteSwapped, + nCodeSlots: nCodeSlots.byteSwapped, + codeLimit: codeLimit.byteSwapped, + hashSize: hashSize.byteSwapped, + hashType: hashType.byteSwapped, + platform: platform.byteSwapped, + pageSize: pageSize.byteSwapped, + spare2: spare2.byteSwapped, + end_earliest: end_earliest + ) + } +} + +extension CS_CodeDirectory_Scatter { + var swapped: CS_CodeDirectory_Scatter { + .init( + scatterOffset: scatterOffset.byteSwapped, + end_withScatter: end_withScatter + ) + } +} + +extension CS_CodeDirectory_TeamID { + var swapped: CS_CodeDirectory_TeamID { + .init( + teamOffset: teamOffset.byteSwapped, + end_withTeam: end_withTeam + ) + } +} + +extension CS_CodeDirectory_CodeLimit64 { + var swapped: CS_CodeDirectory_CodeLimit64 { + .init( + spare3: spare3.byteSwapped, + codeLimit64: codeLimit64.byteSwapped + ) + } +} + +extension CS_CodeDirectory_ExecSeg { + var swapped: CS_CodeDirectory_ExecSeg { + .init( + execSegBase: execSegBase.byteSwapped, + execSegLimit: execSegLimit.byteSwapped, + execSegFlags: execSegFlags.byteSwapped, + end_withExecSeg: end_withExecSeg + ) + } +} + +extension CS_CodeDirectory_Runtime { + var swapped: CS_CodeDirectory_Runtime { + .init( + runtime: runtime.byteSwapped, + preEncryptOffset: preEncryptOffset.byteSwapped, + end_withPreEncryptOffset: end_withPreEncryptOffset + ) + } +} + +// TODO: scatter vector +// So far I have not been able to find a binary where the scatter exists. +// https://github.com/apple-oss-distributions/libsecurity_codesigning/blob/f2cc42c7b45d1c0d69f1551bd5b84adccf5fa821/lib/codedirectory.h#L223C2-L228C4 + +// TODO: Version 0x20600 (Linkage) diff --git a/Sources/MachOKit/Model/Codesign/CodeSignBlobIndex.swift b/Sources/MachOKit/Model/Codesign/CodeSignBlobIndex.swift new file mode 100644 index 0000000..191bc55 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignBlobIndex.swift @@ -0,0 +1,31 @@ +// +// CodeSignBlobIndex.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +public struct CodeSignBlobIndex: LayoutWrapper { + public typealias Layout = CS_BlobIndex + + public var layout: Layout +} + +extension CodeSignBlobIndex { + public var type: CodeSignSlot! { + .init(rawValue: layout.type) + } +} + +extension CS_BlobIndex { + var swapped: CS_BlobIndex { + .init( + type: type.byteSwapped, + offset: offset.byteSwapped + ) + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignExecSegmentFlags.swift b/Sources/MachOKit/Model/Codesign/CodeSignExecSegmentFlags.swift new file mode 100644 index 0000000..69cb924 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignExecSegmentFlags.swift @@ -0,0 +1,112 @@ +// +// CodeSignExecSegmentFlags.swift +// +// +// Created by p-x9 on 2024/03/06. +// +// + +import Foundation +import MachOKitC + +public struct CodeSignExecSegmentFlags: BitFlags { + public typealias RawValue = UInt64 + + public var rawValue: RawValue + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } +} + +extension CodeSignExecSegmentFlags { + /// CS_EXECSEG_MAIN_BINARY + public static let main_binary = CodeSignExecSegmentFlags( + rawValue: Bit.main_binary.rawValue + ) + /// CS_EXECSEG_ALLOW_UNSIGNED + public static let allow_unsigned = CodeSignExecSegmentFlags( + rawValue: Bit.allow_unsigned.rawValue + ) + /// CS_EXECSEG_DEBUGGER + public static let debugger = CodeSignExecSegmentFlags( + rawValue: Bit.debugger.rawValue + ) + /// CS_EXECSEG_JIT + public static let jit = CodeSignExecSegmentFlags( + rawValue: Bit.jit.rawValue + ) + /// CS_EXECSEG_SKIP_LV + public static let skip_lv = CodeSignExecSegmentFlags( + rawValue: Bit.skip_lv.rawValue + ) + /// CS_EXECSEG_CAN_LOAD_CDHASH + public static let can_load_cdhash = CodeSignExecSegmentFlags( + rawValue: Bit.can_load_cdhash.rawValue + ) + /// CS_EXECSEG_CAN_EXEC_CDHASH + public static let can_exec_cdhash = CodeSignExecSegmentFlags( + rawValue: Bit.can_exec_cdhash.rawValue + ) +} + +extension CodeSignExecSegmentFlags { + public enum Bit: CaseIterable { + /// CS_EXECSEG_MAIN_BINARY + case main_binary + /// CS_EXECSEG_ALLOW_UNSIGNED + case allow_unsigned + /// CS_EXECSEG_DEBUGGER + case debugger + /// CS_EXECSEG_JIT + case jit + /// CS_EXECSEG_SKIP_LV + case skip_lv + /// CS_EXECSEG_CAN_LOAD_CDHASH + case can_load_cdhash + /// CS_EXECSEG_CAN_EXEC_CDHASH + case can_exec_cdhash + } +} + +extension CodeSignExecSegmentFlags.Bit: RawRepresentable { + public typealias RawValue = UInt64 + + public init?(rawValue: RawValue) { + switch rawValue { + case RawValue(CS_EXECSEG_MAIN_BINARY): self = .main_binary + case RawValue(CS_EXECSEG_ALLOW_UNSIGNED): self = .allow_unsigned + case RawValue(CS_EXECSEG_DEBUGGER): self = .debugger + case RawValue(CS_EXECSEG_JIT): self = .jit + case RawValue(CS_EXECSEG_SKIP_LV): self = .skip_lv + case RawValue(CS_EXECSEG_CAN_LOAD_CDHASH): self = .can_load_cdhash + case RawValue(CS_EXECSEG_CAN_EXEC_CDHASH): self = .can_exec_cdhash + default: return nil + } + } + public var rawValue: RawValue { + switch self { + case .main_binary: RawValue(CS_EXECSEG_MAIN_BINARY) + case .allow_unsigned: RawValue(CS_EXECSEG_ALLOW_UNSIGNED) + case .debugger: RawValue(CS_EXECSEG_DEBUGGER) + case .jit: RawValue(CS_EXECSEG_JIT) + case .skip_lv: RawValue(CS_EXECSEG_SKIP_LV) + case .can_load_cdhash: RawValue(CS_EXECSEG_CAN_LOAD_CDHASH) + case .can_exec_cdhash: RawValue(CS_EXECSEG_CAN_EXEC_CDHASH) + } + } +} + +extension CodeSignExecSegmentFlags.Bit: CustomStringConvertible { + public var description: String { + switch self { + case .main_binary: "CS_EXECSEG_MAIN_BINARY" + case .allow_unsigned: "CS_EXECSEG_ALLOW_UNSIGNED" + case .debugger: "CS_EXECSEG_DEBUGGER" + case .jit: "CS_EXECSEG_JIT" + case .skip_lv: "CS_EXECSEG_SKIP_LV" + case .can_load_cdhash: "CS_EXECSEG_CAN_LOAD_CDHASH" + case .can_exec_cdhash: "CS_EXECSEG_CAN_EXEC_CDHASH" + } + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignGenericBlob.swift b/Sources/MachOKit/Model/Codesign/CodeSignGenericBlob.swift new file mode 100644 index 0000000..4c23796 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignGenericBlob.swift @@ -0,0 +1,66 @@ +// +// CodeSignGenericBlob.swift +// +// +// Created by p-x9 on 2024/03/04. +// +// + +import Foundation +import MachOKitC + +public struct CodeSignGenericBlob: LayoutWrapper { + public typealias Layout = CS_GenericBlob + + public var layout: Layout +} + +extension CodeSignGenericBlob { + public var isSwapped: Bool { + layout.isSwapped + } + + public var magic: CodeSignMagic { + .init(rawValue: isSwapped ? layout.magic.byteSwapped : layout.magic)! + } + + public var length: Int { + numericCast(isSwapped ? layout.length.byteSwapped : layout.length) + } +} + +extension CodeSignGenericBlob { + static func load( + from baseAddress: UnsafeRawPointer, + offset: Int, + isSwapped: Bool + ) -> CodeSignGenericBlob? { + let ptr = baseAddress.advanced(by: offset) + var _magic = ptr + .assumingMemoryBound(to: UInt32.self) + .pointee + if isSwapped { _magic = _magic.byteSwapped } + guard CodeSignMagic(rawValue: _magic) != nil else { + return nil + } + let layout = ptr + .assumingMemoryBound(to: CS_GenericBlob.self) + .pointee + return .init( + layout: isSwapped ? layout.swapped : layout + ) + } +} + +extension CS_GenericBlob { + var isSwapped: Bool { + magic < 0xfade0000 + } + + var swapped: CS_GenericBlob { + var swapped = CS_GenericBlob() + swapped.magic = magic.byteSwapped + swapped.length = length.byteSwapped + return swapped + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignHashType.swift b/Sources/MachOKit/Model/Codesign/CodeSignHashType.swift new file mode 100644 index 0000000..848176c --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignHashType.swift @@ -0,0 +1,52 @@ +// +// CodeSignHashType.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +public enum CodeSignHashType { + case sha1 + case sha256 + case sha256_truncated + case sha384 +} + +extension CodeSignHashType: RawRepresentable { + public typealias RawValue = UInt32 + + public init?(rawValue: UInt32) { + switch rawValue { + case CS_HASHTYPE_SHA1: self = .sha1 + case CS_HASHTYPE_SHA256: self = .sha256 + case CS_HASHTYPE_SHA256_TRUNCATED: self = .sha256_truncated + case CS_HASHTYPE_SHA384: self = .sha384 + default: + return nil + } + } + + public var rawValue: UInt32 { + switch self { + case .sha1: CS_HASHTYPE_SHA1 + case .sha256: CS_HASHTYPE_SHA256 + case .sha256_truncated: CS_HASHTYPE_SHA256_TRUNCATED + case .sha384: CS_HASHTYPE_SHA384 + } + } +} + +extension CodeSignHashType: CustomStringConvertible { + public var description: String { + switch self { + case .sha1: "CS_HASHTYPE_SHA1" + case .sha256: "CS_HASHTYPE_SHA256" + case .sha256_truncated: "CS_HASHTYPE_SHA256_TRUNCATED" + case .sha384: "CS_HASHTYPE_SHA384" + } + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignMagic.swift b/Sources/MachOKit/Model/Codesign/CodeSignMagic.swift new file mode 100644 index 0000000..338ab3d --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignMagic.swift @@ -0,0 +1,84 @@ +// +// CodeSignMagic.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +public enum CodeSignMagic { + /// CSMAGIC_REQUIREMENT + case requirement + /// CSMAGIC_REQUIREMENTS + case requirements + /// CSMAGIC_CODEDIRECTORY + case codedirectory + /// CSMAGIC_EMBEDDED_SIGNATURE + case embedded_signature + /// CSMAGIC_EMBEDDED_SIGNATURE_OLD + case embedded_signature_old + /// CSMAGIC_EMBEDDED_ENTITLEMENTS + case embedded_entitlements + /// CSMAGIC_EMBEDDED_DER_ENTITLEMENTS + case embedded_der_entitlements + /// CSMAGIC_DETACHED_SIGNATURE + case detached_signature + /// CSMAGIC_BLOBWRAPPER + case blobwrapper + /// CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT + case embedded_launch_constraint +} + +extension CodeSignMagic: RawRepresentable { + public typealias RawValue = UInt32 + + public init?(rawValue: RawValue) { + switch rawValue { + case RawValue(CSMAGIC_REQUIREMENT): self = .requirement + case RawValue(CSMAGIC_REQUIREMENTS): self = .requirements + case RawValue(CSMAGIC_CODEDIRECTORY): self = .codedirectory + case RawValue(CSMAGIC_EMBEDDED_SIGNATURE): self = .embedded_signature + case RawValue(CSMAGIC_EMBEDDED_SIGNATURE_OLD): self = .embedded_signature_old + case RawValue(CSMAGIC_EMBEDDED_ENTITLEMENTS): self = .embedded_entitlements + case RawValue(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS): self = .embedded_der_entitlements + case RawValue(CSMAGIC_DETACHED_SIGNATURE): self = .detached_signature + case RawValue(CSMAGIC_BLOBWRAPPER): self = .blobwrapper + case RawValue(CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT): self = .embedded_launch_constraint + default: return nil + } + } + public var rawValue: RawValue { + switch self { + case .requirement: RawValue(CSMAGIC_REQUIREMENT) + case .requirements: RawValue(CSMAGIC_REQUIREMENTS) + case .codedirectory: RawValue(CSMAGIC_CODEDIRECTORY) + case .embedded_signature: RawValue(CSMAGIC_EMBEDDED_SIGNATURE) + case .embedded_signature_old: RawValue(CSMAGIC_EMBEDDED_SIGNATURE_OLD) + case .embedded_entitlements: RawValue(CSMAGIC_EMBEDDED_ENTITLEMENTS) + case .embedded_der_entitlements: RawValue(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + case .detached_signature: RawValue(CSMAGIC_DETACHED_SIGNATURE) + case .blobwrapper: RawValue(CSMAGIC_BLOBWRAPPER) + case .embedded_launch_constraint: RawValue(CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT) + } + } +} + +extension CodeSignMagic: CustomStringConvertible { + public var description: String { + switch self { + case .requirement: "CSMAGIC_REQUIREMENT" + case .requirements: "CSMAGIC_REQUIREMENTS" + case .codedirectory: "CSMAGIC_CODEDIRECTORY" + case .embedded_signature: "CSMAGIC_EMBEDDED_SIGNATURE" + case .embedded_signature_old: "CSMAGIC_EMBEDDED_SIGNATURE_OLD" + case .embedded_entitlements: "CSMAGIC_EMBEDDED_ENTITLEMENTS" + case .embedded_der_entitlements: "CSMAGIC_EMBEDDED_DER_ENTITLEMENTS" + case .detached_signature: "CSMAGIC_DETACHED_SIGNATURE" + case .blobwrapper: "CSMAGIC_BLOBWRAPPER" + case .embedded_launch_constraint: "CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT" + } + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignSlot.swift b/Sources/MachOKit/Model/Codesign/CodeSignSlot.swift new file mode 100644 index 0000000..9e98ec8 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignSlot.swift @@ -0,0 +1,119 @@ +// +// CodeSignSlot.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +public enum CodeSignSlot { + /// CSSLOT_CODEDIRECTORY + case codedirectory + /// CSSLOT_INFOSLOT + case infoslot + /// CSSLOT_REQUIREMENTS + case requirements + /// CSSLOT_RESOURCEDIR + case resourcedir + /// CSSLOT_APPLICATION + case application + /// CSSLOT_ENTITLEMENTS + case entitlements + /// CSSLOT_DER_ENTITLEMENTS + case der_entitlements + /// CSSLOT_LAUNCH_CONSTRAINT_SELF + case launch_constraint_self + /// CSSLOT_LAUNCH_CONSTRAINT_PARENT + case launch_constraint_parent + /// CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE + case launch_constraint_responsible + /// CSSLOT_LIBRARY_CONSTRAINT + case library_constraint + /// CSSLOT_ALTERNATE_CODEDIRECTORIES + case alternate_codedirectories + /// CSSLOT_ALTERNATE_CODEDIRECTORY_MAX + case alternate_codedirectory_max + /// CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT + case alternate_codedirectory_limit + /// CSSLOT_SIGNATURESLOT + case signatureslot + /// CSSLOT_IDENTIFICATIONSLOT + case identificationslot + /// CSSLOT_TICKETSLOT + case ticketslot +} + +extension CodeSignSlot: RawRepresentable { + public typealias RawValue = UInt32 + + public init?(rawValue: RawValue) { + switch rawValue { + case RawValue(CSSLOT_CODEDIRECTORY): self = .codedirectory + case RawValue(CSSLOT_INFOSLOT): self = .infoslot + case RawValue(CSSLOT_REQUIREMENTS): self = .requirements + case RawValue(CSSLOT_RESOURCEDIR): self = .resourcedir + case RawValue(CSSLOT_APPLICATION): self = .application + case RawValue(CSSLOT_ENTITLEMENTS): self = .entitlements + case RawValue(CSSLOT_DER_ENTITLEMENTS): self = .der_entitlements + case RawValue(CSSLOT_LAUNCH_CONSTRAINT_SELF): self = .launch_constraint_self + case RawValue(CSSLOT_LAUNCH_CONSTRAINT_PARENT): self = .launch_constraint_parent + case RawValue(CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE): self = .launch_constraint_responsible + case RawValue(CSSLOT_LIBRARY_CONSTRAINT): self = .library_constraint + case RawValue(CSSLOT_ALTERNATE_CODEDIRECTORIES): self = .alternate_codedirectories + case RawValue(CSSLOT_ALTERNATE_CODEDIRECTORY_MAX): self = .alternate_codedirectory_max + case RawValue(CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT): self = .alternate_codedirectory_limit + case RawValue(CSSLOT_SIGNATURESLOT): self = .signatureslot + case RawValue(CSSLOT_IDENTIFICATIONSLOT): self = .identificationslot + case RawValue(CSSLOT_TICKETSLOT): self = .ticketslot + default: return nil + } + } + public var rawValue: RawValue { + switch self { + case .codedirectory: RawValue(CSSLOT_CODEDIRECTORY) + case .infoslot: RawValue(CSSLOT_INFOSLOT) + case .requirements: RawValue(CSSLOT_REQUIREMENTS) + case .resourcedir: RawValue(CSSLOT_RESOURCEDIR) + case .application: RawValue(CSSLOT_APPLICATION) + case .entitlements: RawValue(CSSLOT_ENTITLEMENTS) + case .der_entitlements: RawValue(CSSLOT_DER_ENTITLEMENTS) + case .launch_constraint_self: RawValue(CSSLOT_LAUNCH_CONSTRAINT_SELF) + case .launch_constraint_parent: RawValue(CSSLOT_LAUNCH_CONSTRAINT_PARENT) + case .launch_constraint_responsible: RawValue(CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE) + case .library_constraint: RawValue(CSSLOT_LIBRARY_CONSTRAINT) + case .alternate_codedirectories: RawValue(CSSLOT_ALTERNATE_CODEDIRECTORIES) + case .alternate_codedirectory_max: RawValue(CSSLOT_ALTERNATE_CODEDIRECTORY_MAX) + case .alternate_codedirectory_limit: RawValue(CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT) + case .signatureslot: RawValue(CSSLOT_SIGNATURESLOT) + case .identificationslot: RawValue(CSSLOT_IDENTIFICATIONSLOT) + case .ticketslot: RawValue(CSSLOT_TICKETSLOT) + } + } +} + +extension CodeSignSlot: CustomStringConvertible { + public var description: String { + switch self { + case .codedirectory: "CSSLOT_CODEDIRECTORY" + case .infoslot: "CSSLOT_INFOSLOT" + case .requirements: "CSSLOT_REQUIREMENTS" + case .resourcedir: "CSSLOT_RESOURCEDIR" + case .application: "CSSLOT_APPLICATION" + case .entitlements: "CSSLOT_ENTITLEMENTS" + case .der_entitlements: "CSSLOT_DER_ENTITLEMENTS" + case .launch_constraint_self: "CSSLOT_LAUNCH_CONSTRAINT_SELF" + case .launch_constraint_parent: "CSSLOT_LAUNCH_CONSTRAINT_PARENT" + case .launch_constraint_responsible: "CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE" + case .library_constraint: "CSSLOT_LIBRARY_CONSTRAINT" + case .alternate_codedirectories: "CSSLOT_ALTERNATE_CODEDIRECTORIES" + case .alternate_codedirectory_max: "CSSLOT_ALTERNATE_CODEDIRECTORY_MAX" + case .alternate_codedirectory_limit: "CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT" + case .signatureslot: "CSSLOT_SIGNATURESLOT" + case .identificationslot: "CSSLOT_IDENTIFICATIONSLOT" + case .ticketslot: "CSSLOT_TICKETSLOT" + } + } +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignSpecialSlotType.swift b/Sources/MachOKit/Model/Codesign/CodeSignSpecialSlotType.swift new file mode 100644 index 0000000..bd21609 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignSpecialSlotType.swift @@ -0,0 +1,38 @@ +// +// CodeSignSpecialSlotType.swift +// +// +// Created by p-x9 on 2024/03/04. +// +// + +import Foundation + +/// Special slot in code directory +/// +/// https://github.com/apple-oss-distributions/Security/blob/ef677c3d667a44e1737c1b0245e9ed04d11c51c1/OSX/libsecurity_codesigning/lib/codedirectory.h#L86 +public enum CodeSignSpecialSlotType: Int { + /// Info.plist + case infoSlot = 1 + /// internal requirements + case requirementsSlot = 2 + /// resource directory + case resourceDirSlot = 3 + /// Application specific slot + case topDirectorySlot = 4 + /// embedded entitlement configuration/ + case entitlementSlot = 5 + /// for use by disk rep/ + case repSpecificSlot = 6 + /// DER repreesentation of entitlements/ + case entitlementDERSlot = 7 + /// DER representation of LWCR on self/ + case launchConstraintSelf = 8 + /// DER representation of LWCR on the parent/ + case launchConstraintParent = 9 + /// DER representation of LWCR on the responsible process/ + case launchConstraintResponsible = 10 + /// DER representation of LWCR on libraries loaded in the process/ + case libraryConstraint = 11 + // (add further primary slot numbers here) +} diff --git a/Sources/MachOKit/Model/Codesign/CodeSignSuperBlob.swift b/Sources/MachOKit/Model/Codesign/CodeSignSuperBlob.swift new file mode 100644 index 0000000..48a03b5 --- /dev/null +++ b/Sources/MachOKit/Model/Codesign/CodeSignSuperBlob.swift @@ -0,0 +1,81 @@ +// +// CodeSignSuperBlob.swift +// +// +// Created by p-x9 on 2024/03/03. +// +// + +import Foundation +import MachOKitC + +public struct CodeSignSuperBlob: LayoutWrapper { + public typealias Layout = CS_SuperBlob + + public var layout: Layout + public let offset: Int // offset from start of linkedit_data +} + +extension CodeSignSuperBlob { + public var magic: CodeSignMagic { + .init(rawValue: layout.magic)! + } + + public var count: Int { + numericCast(layout.count) + } +} + +extension CodeSignSuperBlob { + /// Get indices of this SuperBlob + /// - Parameter signature: ``MachOFile.CodeSign`` to which this SuperBlob belongs. + /// - Returns: indices of this superBlob + public func blobIndices( + in signature: MachOFile.CodeSign + ) -> AnySequence { + let offset = offset + layoutSize + + return AnySequence( + DataSequence( + data: signature.data.advanced(by: offset), + numberOfElements: count + ).lazy.map { + .init(layout: signature.isSwapped ? $0.swapped : $0) + } + ) + } + + /// Get indices of this SuperBlob + /// - Parameter signature: ``MachOImage.CodeSign`` to which this SuperBlob belongs. + /// - Returns: indices of this superBlob + public func blobIndices( + in signature: MachOImage.CodeSign + ) -> AnySequence { + let offset = offset + layoutSize + + return AnySequence( + MemorySequence( + basePointer: signature.basePointer + .advanced(by: offset) + .assumingMemoryBound(to: CS_BlobIndex.self), + numberOfElements: count + ).lazy.map { + .init(layout: signature.isSwapped ? $0.swapped : $0) + } + ) + } +} + +extension CS_SuperBlob { + var isSwapped: Bool { + magic < 0xfade0000 + } + + var swapped: CS_SuperBlob { + var swapped = CS_SuperBlob() + swapped.magic = magic.byteSwapped + swapped.length = length.byteSwapped + swapped.count = count.byteSwapped + return swapped + } +} diff --git a/Sources/MachOKit/Protocol/CodeSignProtocol.swift b/Sources/MachOKit/Protocol/CodeSignProtocol.swift new file mode 100644 index 0000000..37f92b5 --- /dev/null +++ b/Sources/MachOKit/Protocol/CodeSignProtocol.swift @@ -0,0 +1,130 @@ +// +// CodeSignProtocol.swift +// +// +// Created by p-x9 on 2024/03/10. +// +// + +import Foundation + +public protocol CodeSignProtocol { + var superBlob: CodeSignSuperBlob? { get } + /// Sequence of code directories + var codeDirectories: [CodeSignCodeDirectory] { get } + /// Best hash type code directory + var codeDirectory: CodeSignCodeDirectory? { get } + /// Entitlements embedded in the MachO binary + var embeddedEntitlements: [String: Any]? { get } + /// DER-encoded entitlements data embedded in MachO binary + var embeddedDEREntitlementsData: Data? { get } + var signatureData: Data? { get } + var requirementsBlob: CodeSignSuperBlob? { get } + var requirementsData: [Data] { get } + + /// Get blob data as `Data` + /// - Parameters: + /// - superBlob: SuperBlob to which index belongs + /// - index: Index of the blob to be gotten + /// - includesGenericInfo: A boolean value that indicates whether the data defined in the ``CodeSignGenericBlob``, such as magic and length, are included or not. + /// - Returns: Data of blob + func blobData( + in superBlob: CodeSignSuperBlob, + at index: CodeSignBlobIndex, + includesGenericInfo: Bool + ) -> Data? + + /// Get indices of specified SuperBlob + /// - Parameter superBlob: SuperBlob to get indices + /// - Returns: indices of superBlob + func blobIndices( + of superBlob: CodeSignSuperBlob + ) -> AnySequence +} + +extension CodeSignProtocol { + public var codeDirectory: CodeSignCodeDirectory? { + codeDirectories.bestHashTyped + } + + /// Entitlements data embedded in the MachO binary + public var embeddedEntitlementsData: Data? { + guard let superBlob else { + return nil + } + let blobIndices = blobIndices(of: superBlob) + guard let index = blobIndices.first( + where: { $0.type == .entitlements } + ) else { + return nil + } + return blobData( + in: superBlob, + at: index, + includesGenericInfo: false + ) + } + + /// Entitlements embedded in the MachO binary + public var embeddedEntitlements: [String: Any]? { + guard let embeddedEntitlementsData else { + return nil + } + guard let entitlements = try? PropertyListSerialization.propertyList( + from: embeddedEntitlementsData, + format: nil + ) else { + return nil + } + return entitlements as? [String: Any] + } + + /// DER-encoded entitlements data embedded in MachO binary + public var embeddedDEREntitlementsData: Data? { + guard let superBlob else { + return nil + } + let blobIndices = blobIndices(of: superBlob) + guard let index = blobIndices.first( + where: { $0.type == .der_entitlements } + ) else { + return nil + } + return blobData( + in: superBlob, + at: index, + includesGenericInfo: false + ) + } + + public var signatureData: Data? { + guard let superBlob else { + return nil + } + let blobIndices = blobIndices(of: superBlob) + guard let index = blobIndices.first( + where: { $0.type == .signatureslot } + ) else { + return nil + } + return blobData( + in: superBlob, + at: index, + includesGenericInfo: false + ) + } + + public var requirementsData: [Data] { + guard let requirementsBlob else { + return [] + } + let indices = blobIndices(of: requirementsBlob) + return indices.compactMap { + blobData( + in: requirementsBlob, + at: $0, + includesGenericInfo: true + ) + } + } +} diff --git a/Sources/MachOKit/Protocol/LoadCommandsProtocol.swift b/Sources/MachOKit/Protocol/LoadCommandsProtocol.swift index 287f38a..634ba4c 100644 --- a/Sources/MachOKit/Protocol/LoadCommandsProtocol.swift +++ b/Sources/MachOKit/Protocol/LoadCommandsProtocol.swift @@ -100,4 +100,9 @@ extension LoadCommandsProtocol { infos(of: LoadCommand.encryptionInfo64) .first { _ in true } } + + var codeSignature: LoadCommandInfo? { + infos(of: LoadCommand.codeSignature) + .first { _ in true } + } } diff --git a/Sources/MachOKit/Protocol/MachORepresentable.swift b/Sources/MachOKit/Protocol/MachORepresentable.swift index 9239bb7..009d4a9 100644 --- a/Sources/MachOKit/Protocol/MachORepresentable.swift +++ b/Sources/MachOKit/Protocol/MachORepresentable.swift @@ -21,6 +21,7 @@ public protocol MachORepresentable { associatedtype FunctionStarts: Sequence associatedtype DataInCode: Sequence associatedtype DyldChainedFixups: DyldChainedFixupsProtocol + associatedtype CodeSign: CodeSignProtocol /// A boolean value that indicates whether MachO is a 64-bit architecture. var is64Bit: Bool { get } @@ -95,6 +96,7 @@ public protocol MachORepresentable { var functionStarts: FunctionStarts? { get } var dataInCode: DataInCode? { get } var dyldChainedFixups: DyldChainedFixups? { get } + var codeSign: CodeSign? { get } /// Find the symbol closest to the address at the specified offset. /// diff --git a/Sources/MachOKitC/include/codesign.h b/Sources/MachOKitC/include/codesign.h new file mode 100644 index 0000000..37e2a94 --- /dev/null +++ b/Sources/MachOKitC/include/codesign.h @@ -0,0 +1,332 @@ +// +// codesign.h +// +// +// Created by p-x9 on 2024/03/03. +// +// + +// ref: https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/kern/cs_blobs.h + +#ifndef _KERN_CODESIGN_H_ +#define _KERN_CODESIGN_H_ + +#include +#include + +/* code signing attributes of a process */ +#define CS_VALID 0x00000001 /* dynamically valid */ +#define CS_ADHOC 0x00000002 /* ad hoc signed */ +#define CS_GET_TASK_ALLOW 0x00000004 /* has get-task-allow entitlement */ +#define CS_INSTALLER 0x00000008 /* has installer entitlement */ + +#define CS_FORCED_LV 0x00000010 /* Library Validation required by Hardened System Policy */ +#define CS_INVALID_ALLOWED 0x00000020 /* (macOS Only) Page invalidation allowed by task port policy */ + +#define CS_HARD 0x00000100 /* don't load invalid pages */ +#define CS_KILL 0x00000200 /* kill process if it becomes invalid */ +#define CS_CHECK_EXPIRATION 0x00000400 /* force expiration checking */ +#define CS_RESTRICT 0x00000800 /* tell dyld to treat restricted */ + +#define CS_ENFORCEMENT 0x00001000 /* require enforcement */ +#define CS_REQUIRE_LV 0x00002000 /* require library validation */ +#define CS_ENTITLEMENTS_VALIDATED 0x00004000 /* code signature permits restricted entitlements */ +#define CS_NVRAM_UNRESTRICTED 0x00008000 /* has com.apple.rootless.restricted-nvram-variables.heritable entitlement */ + +#define CS_RUNTIME 0x00010000 /* Apply hardened runtime policies */ +#define CS_LINKER_SIGNED 0x00020000 /* Automatically signed by the linker */ + +#define CS_ALLOWED_MACHO (CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | \ +CS_RESTRICT | CS_ENFORCEMENT | CS_REQUIRE_LV | CS_RUNTIME | CS_LINKER_SIGNED) + +#define CS_EXEC_SET_HARD 0x00100000 /* set CS_HARD on any exec'ed process */ +#define CS_EXEC_SET_KILL 0x00200000 /* set CS_KILL on any exec'ed process */ +#define CS_EXEC_SET_ENFORCEMENT 0x00400000 /* set CS_ENFORCEMENT on any exec'ed process */ +#define CS_EXEC_INHERIT_SIP 0x00800000 /* set CS_INSTALLER on any exec'ed process */ + +#define CS_KILLED 0x01000000 /* was killed by kernel for invalidity */ +#define CS_NO_UNTRUSTED_HELPERS 0x02000000 /* kernel did not load a non-platform-binary dyld or Rosetta runtime */ +#define CS_DYLD_PLATFORM CS_NO_UNTRUSTED_HELPERS /* old name */ +#define CS_PLATFORM_BINARY 0x04000000 /* this is a platform binary */ +#define CS_PLATFORM_PATH 0x08000000 /* platform binary by the fact of path (osx only) */ + +#define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ +#define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */ +#define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code (will go away with rdar://problem/28322552) */ +#define CS_DATAVAULT_CONTROLLER 0x80000000 /* has Data Vault controller entitlement */ + +#define CS_ENTITLEMENT_FLAGS (CS_GET_TASK_ALLOW | CS_INSTALLER | CS_DATAVAULT_CONTROLLER | CS_NVRAM_UNRESTRICTED) + +/* executable segment flags */ + +#define CS_EXECSEG_MAIN_BINARY 0x1 /* executable segment denotes main binary */ +#define CS_EXECSEG_ALLOW_UNSIGNED 0x10 /* allow unsigned pages (for debugging) */ +#define CS_EXECSEG_DEBUGGER 0x20 /* main binary is debugger */ +#define CS_EXECSEG_JIT 0x40 /* JIT enabled */ +#define CS_EXECSEG_SKIP_LV 0x80 /* OBSOLETE: skip library validation */ +#define CS_EXECSEG_CAN_LOAD_CDHASH 0x100 /* can bless cdhash for execution */ +#define CS_EXECSEG_CAN_EXEC_CDHASH 0x200 /* can execute blessed cdhash */ + +/* + * Magic numbers used by Code Signing + */ +enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ + CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ + CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ + CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ + CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ + CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* embedded DER encoded entitlements */ + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ + CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ + CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT = 0xfade8181, /* Light weight code requirement */ + + CS_SUPPORTSSCATTER = 0x20100, + CS_SUPPORTSTEAMID = 0x20200, + CS_SUPPORTSCODELIMIT64 = 0x20300, + CS_SUPPORTSEXECSEG = 0x20400, + CS_SUPPORTSRUNTIME = 0x20500, + CS_SUPPORTSLINKAGE = 0x20600, + + CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_DER_ENTITLEMENTS = 7, + CSSLOT_LAUNCH_CONSTRAINT_SELF = 8, + CSSLOT_LAUNCH_CONSTRAINT_PARENT = 9, + CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE = 10, + CSSLOT_LIBRARY_CONSTRAINT = 11, + + CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ + + CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ + CSSLOT_IDENTIFICATIONSLOT = 0x10001, + CSSLOT_TICKETSLOT = 0x10002, + + CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ + CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ + + CS_HASHTYPE_SHA1 = 1, + CS_HASHTYPE_SHA256 = 2, + CS_HASHTYPE_SHA256_TRUNCATED = 3, + CS_HASHTYPE_SHA384 = 4, + + CS_SHA1_LEN = 20, + CS_SHA256_LEN = 32, + CS_SHA256_TRUNCATED_LEN = 20, + + CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ + CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ + + /* + * Currently only to support Legacy VPN plugins, and Mac App Store + * but intended to replace all the various platform code, dev code etc. bits. + */ + CS_SIGNER_TYPE_UNKNOWN = 0, + CS_SIGNER_TYPE_LEGACYVPN = 5, + CS_SIGNER_TYPE_MAC_APP_STORE = 6, + + CS_SUPPL_SIGNER_TYPE_UNKNOWN = 0, + CS_SUPPL_SIGNER_TYPE_TRUSTCACHE = 7, + CS_SUPPL_SIGNER_TYPE_LOCAL = 8, + + CS_SIGNER_TYPE_OOPJIT = 9, + + /* Validation categories used for trusted launch environment */ + CS_VALIDATION_CATEGORY_INVALID = 0, + CS_VALIDATION_CATEGORY_PLATFORM = 1, + CS_VALIDATION_CATEGORY_TESTFLIGHT = 2, + CS_VALIDATION_CATEGORY_DEVELOPMENT = 3, + CS_VALIDATION_CATEGORY_APP_STORE = 4, + CS_VALIDATION_CATEGORY_ENTERPRISE = 5, + CS_VALIDATION_CATEGORY_DEVELOPER_ID = 6, + CS_VALIDATION_CATEGORY_LOCAL_SIGNING = 7, + CS_VALIDATION_CATEGORY_ROSETTA = 8, + CS_VALIDATION_CATEGORY_OOPJIT = 9, + CS_VALIDATION_CATEGORY_NONE = 10, +}; + +/* The set of application types we support for linkage signatures */ +enum { + CS_LINKAGE_APPLICATION_INVALID = 0, + CS_LINKAGE_APPLICATION_ROSETTA = 1, + + /* XOJIT has been renamed to OOP-JIT */ + CS_LINKAGE_APPLICATION_XOJIT = 2, + CS_LINKAGE_APPLICATION_OOPJIT = 2, +}; + +/* The set of application sub-types we support for linkage signatures */ +enum { + /* + * For backwards compatibility with older signatures, the AOT sub-type is kept + * as 0. + */ + CS_LINKAGE_APPLICATION_ROSETTA_AOT = 0, + + /* OOP-JIT sub-types -- XOJIT type kept for external dependencies */ + CS_LINKAGE_APPLICATION_XOJIT_PREVIEWS = 1, + CS_LINKAGE_APPLICATION_OOPJIT_INVALID = 0, + CS_LINKAGE_APPLICATION_OOPJIT_PREVIEWS = 1, + CS_LINKAGE_APPLICATION_OOPJIT_MLCOMPILER = 2, + CS_LINKAGE_APPLICATION_OOPJIT_TOTAL, +}; + +/* Integer to string conversion of OOP-JIT types */ +static const char *oop_jit_conversion[CS_LINKAGE_APPLICATION_OOPJIT_TOTAL] = { + [CS_LINKAGE_APPLICATION_OOPJIT_INVALID] = NULL, + [CS_LINKAGE_APPLICATION_OOPJIT_PREVIEWS] = "previews", + [CS_LINKAGE_APPLICATION_OOPJIT_MLCOMPILER] = "ml-compiler", +}; + +#define KERNEL_HAVE_CS_CODEDIRECTORY 1 +#define KERNEL_CS_CODEDIRECTORY_HAVE_PLATFORM 1 + +/* + * C form of a CodeDirectory. + */ +typedef struct __CodeDirectory { + uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ + uint32_t length; /* total length of CodeDirectory blob */ + uint32_t version; /* compatibility version */ + uint32_t flags; /* setup and mode flags */ + uint32_t hashOffset; /* offset of hash slot element at index zero */ + uint32_t identOffset; /* offset of identifier string */ + uint32_t nSpecialSlots; /* number of special hash slots */ + uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ + uint32_t codeLimit; /* limit to main image signature range */ + uint8_t hashSize; /* size of each hash in bytes */ + uint8_t hashType; /* type of hash (cdHashType* constants) */ + uint8_t platform; /* platform identifier; zero if not platform binary */ + uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ + uint32_t spare2; /* unused (must be zero) */ + + char end_earliest[0]; +// +// /* Version 0x20100 */ +// uint32_t scatterOffset; /* offset of optional scatter vector */ +// char end_withScatter[0]; +// +// /* Version 0x20200 */ +// uint32_t teamOffset; /* offset of optional team identifier */ +// char end_withTeam[0]; +// +// /* Version 0x20300 */ +// uint32_t spare3; /* unused (must be zero) */ +// uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ +// char end_withCodeLimit64[0]; +// +// /* Version 0x20400 */ +// uint64_t execSegBase; /* offset of executable segment */ +// uint64_t execSegLimit; /* limit of executable segment */ +// uint64_t execSegFlags; /* executable segment flags */ +// char end_withExecSeg[0]; +// +// /* Version 0x20500 */ +// uint32_t runtime; +// uint32_t preEncryptOffset; +// char end_withPreEncryptOffset[0]; +// +// /* Version 0x20600 */ +// uint8_t linkageHashType; +// uint8_t linkageApplicationType; +// uint16_t linkageApplicationSubType; +// uint32_t linkageOffset; +// uint32_t linkageSize; +// char end_withLinkage[0]; + + /* followed by dynamic content as located by offset fields above */ +} CS_CodeDirectory +__attribute__ ((aligned(1))); + +/* + * Structure of an embedded-signature SuperBlob + */ + +typedef struct __BlobIndex { + uint32_t type; /* type of entry */ + uint32_t offset; /* offset of entry */ +} CS_BlobIndex +__attribute__ ((aligned(1))); + +typedef struct __SC_SuperBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of SuperBlob */ + uint32_t count; /* number of index entries following */ + CS_BlobIndex index[]; /* (count) entries */ + /* followed by Blobs in no particular order as indicated by offsets in index */ +} CS_SuperBlob +__attribute__ ((aligned(1))); + +#define KERNEL_HAVE_CS_GENERICBLOB 1 +typedef struct __SC_GenericBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of blob */ + char data[]; +} CS_GenericBlob +__attribute__ ((aligned(1))); + +typedef struct __SC_Scatter { + uint32_t count; // number of pages; zero for sentinel (only) + uint32_t base; // first page number + uint64_t targetOffset; // offset in target + uint64_t spare; // reserved +} SC_Scatter +__attribute__ ((aligned(1))); + + +/* + * Defined launch types + */ +__enum_decl(cs_launch_type_t, uint8_t, { + CS_LAUNCH_TYPE_NONE = 0, + CS_LAUNCH_TYPE_SYSTEM_SERVICE = 1, + CS_LAUNCH_TYPE_SYSDIAGNOSE = 2, + CS_LAUNCH_TYPE_APPLICATION = 3, +}); + +struct launch_constraint_data { + cs_launch_type_t launch_type; +}; +typedef struct launch_constraint_data* launch_constraint_data_t; + +typedef struct __CodeDirectory_Scatter { + uint32_t scatterOffset; /* offset of optional scatter vector */ + char end_withScatter[0]; +} __attribute__((packed)) +CS_CodeDirectory_Scatter; + +typedef struct __CodeDirectory_TeamID { + uint32_t teamOffset; /* offset of optional team identifier */ + char end_withTeam[0]; +} __attribute__((packed)) +CS_CodeDirectory_TeamID; + +typedef struct __CodeDirectory_CodeLimit64 { + uint32_t spare3; /* unused (must be zero) */ + uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ +} __attribute__((packed)) +CS_CodeDirectory_CodeLimit64; + +typedef struct __CodeDirectory_ExecSeg { + uint64_t execSegBase; /* offset of executable segment */ + uint64_t execSegLimit; /* limit of executable segment */ + uint64_t execSegFlags; /* executable segment flags */ + char end_withExecSeg[0]; +} __attribute__((packed)) +CS_CodeDirectory_ExecSeg; + +typedef struct __CodeDirectory_Runtime { + uint32_t runtime; + uint32_t preEncryptOffset; + char end_withPreEncryptOffset[0]; +} __attribute__((packed)) +CS_CodeDirectory_Runtime; + +#endif /* _KERN_CODESIGN_H */ diff --git a/Tests/MachOKitTests/MachOFilePrintTests.swift b/Tests/MachOKitTests/MachOFilePrintTests.swift index 4b8a396..88dd0f7 100644 --- a/Tests/MachOKitTests/MachOFilePrintTests.swift +++ b/Tests/MachOKitTests/MachOFilePrintTests.swift @@ -19,14 +19,18 @@ final class MachOFilePrintTests: XCTestCase { // let path = "/System/Applications/Calculator.app/Contents/MacOS/Calculator" let path = "/System/Applications/Freeform.app/Contents/MacOS/Freeform" let url = URL(fileURLWithPath: path) - guard let file = try? MachOKit.loadFromFile(url: url), - case let .fat(fatFile) = file, - let machOs = try? fatFile.machOFiles() else { + guard let file = try? MachOKit.loadFromFile(url: url) else { XCTFail("Failed to load file") return } - self.fat = fatFile - self.machO = machOs[1] + switch file { + case let .fat(fatFile): + self.fat = fatFile + self.machO = try! fatFile.machOFiles()[0] + case let .machO(machO): + self.machO = machO + } + } func testHeader() throws { @@ -574,3 +578,104 @@ extension MachOFilePrintTests { } } } + +extension MachOFilePrintTests { + func testCodeSign() { + guard let codeSign = machO.codeSign else { + return + } + guard let superBlob = codeSign.superBlob else { + return + } + let indices = superBlob.blobIndices(in: codeSign) + print( + indices.compactMap(\.type) + ) + } + + func testCodeSignEntitlements() { + guard let codeSign = machO.codeSign else { + return + } + guard let entitlements = codeSign.embeddedEntitlements else { + return + } + print(entitlements) + } + + func testCodeSignCodeDirectories() { + guard let codeSign = machO.codeSign else { + return + } + let directories = codeSign.codeDirectories + + /* Identifier */ + let identifiers = directories + .compactMap { + $0.identifier(in: codeSign) + } + print( + "identifier:", + identifiers + ) + + /* CD Hash */ + let cdHashes = directories + .compactMap { + $0.hash(in: codeSign) + }.map { + $0.map { String(format: "%02x", $0) }.joined() + } + print( + "CDHash:", + cdHashes + ) + + /* Page Hashes*/ + // let pageHashes = directories + // .map { directory in + // (-Int(directory.nSpecialSlots)..