Skip to content

Commit

Permalink
SNTBlockMessage: add more template options (#1337)
Browse files Browse the repository at this point in the history
* update event detail url

* refactor template mappings

* re-enable testEventDetailURLForFileAccessEvent

* null

* missed one

* update comment
  • Loading branch information
tburgin committed May 7, 2024
1 parent 0f5e551 commit b53818f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 155 deletions.
2 changes: 1 addition & 1 deletion Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ objc_library(
],
deps = [
":CertificateHelpers",
"@MOLCertificate",
":SNTStoredEvent",
],
)

Expand Down
152 changes: 71 additions & 81 deletions Source/common/SNTBlockMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTSystemInfo.h"

static id ValueOrNull(id value) {
return value ?: [NSNull null];
}

@implementation SNTBlockMessage

+ (NSAttributedString *)formatMessage:(NSString *)message {
Expand Down Expand Up @@ -123,35 +127,79 @@ + (NSString *)stringFromHTML:(NSString *)html {
}

+ (NSString *)replaceFormatString:(NSString *)str
withDict:(NSDictionary<NSString *, NSString * (^)()> *)replacements {
withDict:(NSDictionary<NSString *, NSString *> *)replacements {
__block NSString *formatStr = str;

[replacements
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString * (^computeValue)(), BOOL *stop) {
NSString *value = computeValue();
if (value) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
}
}];
[replacements enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
if ((id)value != [NSNull null]) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
}
}];

return formatStr;
}

// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The following "format strings" will be replaced in the URL, if they are present:
//
// The following "format strings" will be replaced in the URL provided by
// `+eventDetailURLForEvent:customURL:templateMapping:`.
//
// %file_identifier% - The SHA-256 of the binary being executed.
// %bundle_or_file_identifier% - The hash of the bundle containing this file or the file itself,
// if no bundle hash is present.
// %file_bundle_id% - The bundle id of the binary, if any.
// %team_id% - The Team ID if present in the signature information.
// %signing_id% - The Signing ID if present in the signature information.
// %cdhash% - If signed, the CDHash.
// %username% - The executing user's name.
// %machine_id% - The configured machine ID for this host.
// %hostname% - The machine's FQDN.
// %uuid% - The machine's UUID.
// %serial% - The machine's serial number.
//
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
+ (NSDictionary *)eventDetailTemplateMappingForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
return @{
@"%file_sha%" : ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
@"%file_identifier%" : ValueOrNull(event.fileSHA256),
@"%bundle_or_file_identifier%" :
ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
@"%username%" : ValueOrNull(event.executingUser),
@"%file_bundle_id%" : ValueOrNull(event.fileBundleID),
@"%team_id%" : ValueOrNull(event.teamID),
@"%signing_id%" : ValueOrNull(event.signingID),
@"%cdhash%" : ValueOrNull(event.cdhash),
@"%machine_id%" : ValueOrNull(config.machineID),
@"%hostname%" : ValueOrNull([SNTSystemInfo longHostname]),
@"%uuid%" : ValueOrNull([SNTSystemInfo hardwareUUID]),
@"%serial%" : ValueOrNull([SNTSystemInfo serialNumber]),
};
}

//
// Everything from `+eventDetailTemplateMappingForEvent:` with the following file access
// specific templates.
//
// %rule_version% - The version of the rule that was violated.
// %rule_name% - The name of the rule that was violated.
// %accessed_path% - The path accessed by the binary.
//
+ (NSDictionary *)fileAccessEventDetailTemplateMappingForEvent:(SNTFileAccessEvent *)event {
NSMutableDictionary *d = [self eventDetailTemplateMappingForEvent:event].mutableCopy;
[d addEntriesFromDictionary:@{
@"%rule_version%" : ValueOrNull(event.ruleVersion),
@"%rule_name%" : ValueOrNull(event.ruleName),
@"%accessed_path%" : ValueOrNull(event.accessedPath),
}];
return d;
}

// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The "format strings" in `templateMapping` will be replaced in the URL, if they are present.
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event
customURL:(NSString *)url
templateMapping:(NSDictionary *)templateMapping {
SNTConfigurator *config = [SNTConfigurator configurator];

NSString *formatStr = url;
Expand All @@ -166,26 +214,7 @@ + (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)
return nil;
}

// Disabling clang-format. See comment in `eventDetailURLForFileAccessEvent:customURL:`
// clang-format off
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
[NSDictionary dictionaryWithObjectsAndKeys:
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
@"%file_sha%",
^{ return event.fileSHA256; }, @"%file_identifier%",
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
@"%bundle_or_file_identifier%",
^{ return event.executingUser; }, @"%username%",
^{ return config.machineID; }, @"%machine_id%",
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
nil];
// clang-format on

formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:kvReplacements];

formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:templateMapping];
NSURL *u = [NSURL URLWithString:formatStr];
if (!u) {
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
Expand All @@ -194,55 +223,16 @@ + (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)
return u;
}

// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
// The following "format strings" will be replaced in the URL, if they are present:
//
// %rule_version% - The version of the rule that was violated.
// %rule_name% - The name of the rule that was violated.
// %file_identifier% - The SHA-256 of the binary being executed.
// %accessed_path% - The path accessed by the binary.
// %username% - The executing user's name.
// %machine_id% - The configured machine ID for this host.
// %hostname% - The machine's FQDN.
// %uuid% - The machine's UUID.
// %serial% - The machine's serial number.
//
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url {
if (!url.length || [url isEqualToString:@"null"]) {
return nil;
}

SNTConfigurator *config = [SNTConfigurator configurator];

// Clang format goes wild here. If you use the container literal syntax `@{}` with a block value
// type, it seems to break the clang format on/off functionality and breaks formatting for the
// remainder of the file.
// Using `dictionaryWithObjectsAndKeys` and disabling clang format as a workaround.
// clang-format off
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
[NSDictionary dictionaryWithObjectsAndKeys:
^{ return event.ruleVersion; }, @"%rule_version%",
^{ return event.ruleName; }, @"%rule_name%",
^{ return event.fileSHA256; }, @"%file_identifier%",
^{ return event.accessedPath; }, @"%accessed_path%",
^{ return event.executingUser; }, @"%username%",
^{ return config.machineID; }, @"%machine_id%",
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
nil];
// clang-format on

NSString *formatStr = [SNTBlockMessage replaceFormatString:url withDict:kvReplacements];

NSURL *u = [NSURL URLWithString:formatStr];
if (!u) {
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
}
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
return [self eventDetailURLForEvent:event
customURL:url
templateMapping:[self eventDetailTemplateMappingForEvent:event]];
}

return u;
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url {
return [self eventDetailURLForEvent:event
customURL:url
templateMapping:[self fileAccessEventDetailTemplateMappingForEvent:event]];
}

@end
46 changes: 37 additions & 9 deletions Source/common/SNTBlockMessageTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,29 @@ - (void)testEventDetailURLForEvent {

se.fileSHA256 = @"my_fi";
se.executingUser = @"my_un";
se.fileBundleID = @"s.n.t";
se.cdhash = @"abc";
se.teamID = @"SNT";
se.signingID = @"SNT:s.n.t";

NSString *url = @"http://"
@"localhost?fs=%file_sha%&fi=%file_identifier%&bfi=%bundle_or_file_identifier%&"
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
@"un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl =
@"http://"
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&bfi=my_fi&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
NSString *wantUrl = @"http://"
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";

NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];

// Set fileBundleHash and test again for newly expected values
se.fileBundleHash = @"my_fbh";

wantUrl = @"http://"
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";

gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];

Expand All @@ -74,15 +82,22 @@ - (void)testEventDetailURLForFileAccessEvent {
fae.ruleVersion = @"my_rv";
fae.ruleName = @"my_rn";
fae.fileSHA256 = @"my_fi";
fae.fileBundleID = @"s.n.t";
fae.cdhash = @"abc";
fae.teamID = @"SNT";
fae.signingID = @"SNT:s.n.t";
fae.accessedPath = @"my_ap";
fae.executingUser = @"my_un";

NSString *url = @"http://"
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&ap=%accessed_"
@"path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl =
NSString *url =
@"http://"
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&"
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
@"ap=%accessed_path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
NSString *wantUrl = @"http://"
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&"
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
@"ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";

NSURL *gotUrl = [SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:url];

Expand All @@ -92,4 +107,17 @@ - (void)testEventDetailURLForFileAccessEvent {
XCTAssertNil([SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:@"null"]);
}

- (void)testEventDetailURLMissingDetails {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];

se.fileSHA256 = @"my_fi";

NSString *url = @"http://localhost?fi=%file_identifier%";
NSString *wantUrl = @"http://localhost?fi=my_fi";

NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];

XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
}

@end
50 changes: 2 additions & 48 deletions Source/common/SNTFileAccessEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

#import <Foundation/Foundation.h>

#import <MOLCertificate/MOLCertificate.h>
#import "Source/common/SNTStoredEvent.h"

///
/// Represents an event stored in the database.
///
@interface SNTFileAccessEvent : NSObject <NSSecureCoding>
@interface SNTFileAccessEvent : SNTStoredEvent <NSSecureCoding>

///
/// The watched path that was accessed
Expand All @@ -32,57 +32,11 @@
@property NSString *ruleVersion;
@property NSString *ruleName;

///
/// The SHA256 of the process that accessed the path
///
@property NSString *fileSHA256;

///
/// The path of the process that accessed the watched path
///
@property NSString *filePath;

///
/// If the process is part of a bundle, the name of the application
///
@property NSString *application;

///
/// If the executed file was signed, this is the Team ID if present in the signature information.
///
@property NSString *teamID;

///
/// If the executed file was signed, this is the Signing ID if present in the signature information.
///
@property NSString *signingID;

///
/// The user who executed the binary.
///
@property NSString *executingUser;

///
/// The process ID of the binary being executed.
///
@property NSNumber *pid;

///
/// The parent process ID of the binary being executed.
///
@property NSNumber *ppid;

///
/// The name of the parent process.
///
@property NSString *parentName;

///
/// If the executed file was signed, this is an NSArray of MOLCertificate's
/// representing the signing chain.
///
@property NSArray<MOLCertificate *> *signingChain;

///
/// A string representing the publisher based on the signingChain
///
Expand Down
16 changes: 0 additions & 16 deletions Source/common/SNTFileAccessEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,7 @@ - (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(accessedPath);
ENCODE(ruleVersion);
ENCODE(ruleName);
ENCODE(fileSHA256);
ENCODE(filePath);
ENCODE(application);
ENCODE(teamID);
ENCODE(teamID);
ENCODE(pid);
ENCODE(ppid);
ENCODE(parentName);
ENCODE(signingChain);
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
Expand All @@ -68,15 +60,7 @@ - (instancetype)initWithCoder:(NSCoder *)decoder {
DECODE(accessedPath, NSString);
DECODE(ruleVersion, NSString);
DECODE(ruleName, NSString);
DECODE(fileSHA256, NSString);
DECODE(filePath, NSString);
DECODE(application, NSString);
DECODE(teamID, NSString);
DECODE(teamID, NSString);
DECODE(pid, NSNumber);
DECODE(ppid, NSNumber);
DECODE(parentName, NSString);
DECODEARRAY(signingChain, MOLCertificate);
}
return self;
}
Expand Down

0 comments on commit b53818f

Please sign in to comment.