From ade1f452ba7e49a6283cd032632bca309ca8baa4 Mon Sep 17 00:00:00 2001 From: "Alkenso (Vladimir Vashurkin)" Date: Tue, 17 Sep 2024 20:55:25 +0300 Subject: [PATCH] Add support of ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE --- .../ESNativeTypeDescriptions.swift | 2 + .../ESMessage/ESConverter.swift | 35 +++++++++++-- .../ESMessage/ESTypes.swift | 52 +++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESClient - Native/ESNativeTypeDescriptions.swift b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESClient - Native/ESNativeTypeDescriptions.swift index 1de70a2..861b0f0 100644 --- a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESClient - Native/ESNativeTypeDescriptions.swift +++ b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESClient - Native/ESNativeTypeDescriptions.swift @@ -328,6 +328,8 @@ extension es_event_type_t: SpellbookEndpointSecurity.ESNativeType { return "ES_EVENT_TYPE_NOTIFY_OD_DELETE_GROUP" case ES_EVENT_TYPE_NOTIFY_XPC_CONNECT: return "ES_EVENT_TYPE_NOTIFY_XPC_CONNECT" + case ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE: + return "ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE" default: return nil } diff --git a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESConverter.swift b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESConverter.swift index 91c6d27..b0b7a41 100644 --- a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESConverter.swift +++ b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESConverter.swift @@ -118,10 +118,18 @@ public extension ESConverter { ESThread(threadID: es.thread_id) } - func esThreadState(_ es: es_thread_state_t) throws -> ESThreadState { + func esThreadState(_ es: es_thread_state_t) -> ESThreadState { ESThreadState(flavor: es.flavor, state: esToken(es.state)) } + func esSignedFileInfo(_ es: es_signed_file_info_t) -> ESSignedFileInfo { + .init( + cdHash: withUnsafeBytes(of: es.cdhash) { Data($0) }, + teamID: esString(es.team_id), + signingID: esString(es.signing_id) + ) + } + func esAuthResult(_ es: es_result_t) throws -> ESAuthResult { switch es.result_type { case ES_RESULT_TYPE_AUTH: @@ -402,7 +410,7 @@ public extension ESConverter { case ES_EVENT_TYPE_NOTIFY_TRACE: return .trace(esEvent(trace: event.trace)) case ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE: - return try .remoteThreadCreate(esEvent(remote_thread_create: event.remote_thread_create)) + return .remoteThreadCreate(esEvent(remote_thread_create: event.remote_thread_create)) case ES_EVENT_TYPE_AUTH_REMOUNT: return .remount(esEvent(remount: event.remount)) case ES_EVENT_TYPE_NOTIFY_REMOUNT: @@ -499,6 +507,8 @@ public extension ESConverter { return .odDeleteGroup(esEvent(odDeleteGroup: event.od_delete_group.pointee)) case ES_EVENT_TYPE_NOTIFY_XPC_CONNECT: return .xpcConnect(esEvent(xpcConnect: event.xpc_connect.pointee)) + case ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE: + return try .gatekeeperUserOverride(esEvent(gatekeeperUserOverride: event.gatekeeper_user_override.pointee)) default: throw CommonError.invalidArgument(arg: "es_event_type_t", invalidValue: type) } @@ -697,8 +707,8 @@ public extension ESConverter { .init(source: esFile(es.source)) } - func esEvent(remote_thread_create es: es_event_remote_thread_create_t) throws -> ESEvent.RemoteThreadCreate { - try .init(target: esProcess(es.target), threadState: es.thread_state.map(\.pointee).flatMap(esThreadState)) + func esEvent(remote_thread_create es: es_event_remote_thread_create_t) -> ESEvent.RemoteThreadCreate { + .init(target: esProcess(es.target), threadState: es.thread_state.map(\.pointee).flatMap(esThreadState)) } func esEvent(remount es: es_event_remount_t) -> ESEvent.Remount { @@ -1215,4 +1225,21 @@ public extension ESConverter { func esEvent(xpcConnect es: es_event_xpc_connect_t) -> ESEvent.XPCConnect { .init(serviceName: esString(es.service_name), serviceDomainType: es.service_domain_type) } + + func esEvent(gatekeeperUserOverride es: es_event_gatekeeper_user_override_t) throws -> ESEvent.GatekeeperUserOverride { + let file: ESEvent.GatekeeperUserOverride.File = switch es.file_type { + case ES_GATEKEEPER_USER_OVERRIDE_FILE_TYPE_FILE: .file(esFile(es.file.file)) + case ES_GATEKEEPER_USER_OVERRIDE_FILE_TYPE_PATH: .path(esString(es.file.file_path)) + default: + throw CommonError.invalidArgument( + arg: "es_gatekeeper_user_override_file_type_t", + invalidValue: es.file_type + ) + } + return .init( + file: file, + sha256: es.sha256.flatMap { withUnsafeBytes(of: $0.pointee) { Data($0) } }, + signing_info: es.signing_info.flatMap { esSignedFileInfo($0.pointee) } + ) + } } diff --git a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESTypes.swift b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESTypes.swift index 51f4240..049000b 100644 --- a/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESTypes.swift +++ b/EndpointSecurity/Sources/SpellbookEndpointSecurity/ESMessage/ESTypes.swift @@ -129,6 +129,26 @@ public struct ESThreadState: Equatable, Codable { } } +/// Information from a signed file. If the file is a multiarchitecture binary, only the +/// signing information for the native host architecture is reported. I.e. the CDHash +/// from the AArch64 slice if the host is AArch64. +public struct ESSignedFileInfo: Equatable, Codable { + /// Code Directory Hash + public var cdHash: Data + + /// Team Identifier, if available in the signing information. + public var teamID: String + + /// Signing Identifier, if available in the signing information. + public var signingID: String + + public init(cdHash: Data, teamID: String, signingID: String) { + self.cdHash = cdHash + self.teamID = teamID + self.signingID = signingID + } +} + public struct ESAuthResult: Equatable, Codable, RawRepresentable { public static func auth(_ auth: Bool) -> ESAuthResult { .init(rawValue: auth ? .max : 0) } public static func flags(_ flags: UInt32) -> ESAuthResult { .init(rawValue: flags) } @@ -287,6 +307,7 @@ public enum ESEvent: Equatable, Codable { case xpMalwareDetected(XPMalwareDetected) case xpMalwareRemediated(XPMalwareRemediated) case xpcConnect(XPCConnect) + case gatekeeperUserOverride(GatekeeperUserOverride) } public extension ESEvent { @@ -1843,6 +1864,37 @@ public extension ESEvent { self.serviceDomainType = serviceDomainType } } + + /// Notification for a gatekeeper_user_override events. + /// + /// - Note: This event type does not support caching (notify-only). + /// - Note: Hashes are calculated in usermode by Gatekeeper. There is no guarantee that + /// any other program including the kernel will observe the same file at the reported path. + /// Furthermore there is no guarantee that the CDHash is valid or that it matches the containing binary. + struct GatekeeperUserOverride: Equatable, Codable { + /// Describes the target file that is being overridden by the user + public var file: File + + /// SHA256 of the file. Provided if the filesize is less than 100MB. + public var sha256: Data? + + /// Signing Information, available if the file has been signed. + public var signing_info: ESSignedFileInfo? + + /// The type of the file field. + /// If Endpoint security can't lookup the file at event submission + /// it will emit a path instead of an `es_file_t`. + public enum File: Equatable, Codable { + case path(String) + case file(ESFile) + } + + public init(file: File, sha256: Data?, signing_info: ESSignedFileInfo?) { + self.file = file + self.sha256 = sha256 + self.signing_info = signing_info + } + } } extension ESEvent.Create.Destination {