[Security Solution] User can make Exceptions for Memory protection alerts (#102196)

This commit is contained in:
Esteban Beltran 2021-07-13 10:21:31 +02:00 committed by GitHub
parent 26bc0015ba
commit a4a7253d30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 644 additions and 50 deletions

View file

@ -14,9 +14,42 @@ export interface CodeSignature {
subject_name: string[];
trusted: string[];
}
export interface Token {
integrity_level_name: string;
}
export interface MemoryPe {
imphash?: string;
}
export interface StartAddressDetails {
allocation_base?: number;
allocation_protection?: string;
allocation_size?: number;
allocation_type?: string;
bytes_address?: number;
bytes_allocation_offset?: number;
bytes_compressed?: string;
bytes_compressed_present?: string;
mapped_path?: string;
mapped_pe_detected?: boolean;
memory_pe_detected?: boolean;
region_base?: number;
region_protection?: string;
region_size?: number;
region_state?: string;
strings?: string;
memory_pe?: MemoryPe;
}
export interface Ext {
code_signature?: CodeSignature[] | CodeSignature;
original?: Original;
token?: Token;
start_address_allocation_offset?: number;
start_address_bytes_disasm_hash?: string;
start_address_details?: StartAddressDetails;
}
export interface Hash {
md5?: string[];

View file

@ -30,6 +30,8 @@ import { ProcessEcs } from './process';
import { SystemEcs } from './system';
import { ThreatEcs } from './threat';
import { Ransomware } from './ransomware';
import { MemoryProtection } from './memory_protection';
import { Target } from './target_type';
export interface Ecs {
_id: string;
@ -63,4 +65,7 @@ export interface Ecs {
// This should be temporary
eql?: { parentId: string; sequenceNumber: string };
Ransomware?: Ransomware;
// eslint-disable-next-line @typescript-eslint/naming-convention
Memory_protection?: MemoryProtection;
Target?: Target;
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface MemoryProtection {
cross_session?: boolean;
feature?: string;
parent_to_child?: boolean;
self_injection?: boolean;
unique_key_v1?: string;
}

View file

@ -37,4 +37,5 @@ export interface ProcessParentData {
export interface Thread {
id?: number[];
start?: string[];
Ext?: Ext;
}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ProcessEcs } from '../process';
export interface Target {
process: ProcessEcs;
}

View file

@ -7,6 +7,7 @@
import uuid from 'uuid';
import seedrandom from 'seedrandom';
import { assertNever } from '@kbn/std';
import {
AlertEvent,
DataStream,
@ -387,6 +388,12 @@ const eventsDefaultDataStream = {
namespace: 'default',
};
enum AlertTypes {
MALWARE = 'MALWARE',
MEMORY_SIGNATURE = 'MEMORY_SIGNATURE',
MEMORY_SHELLCODE = 'MEMORY_SHELLCODE',
}
const alertsDefaultDataStream = {
type: 'logs',
dataset: 'endpoint.alerts',
@ -509,16 +516,15 @@ export class EndpointDocGenerator extends BaseDataGenerator {
data_stream: metadataDataStream,
};
}
/**
* Creates an alert from the simulated host represented by this EndpointDocGenerator
* Creates a malware alert from the simulated host represented by this EndpointDocGenerator
* @param ts - Timestamp to put in the event
* @param entityID - entityID of the originating process
* @param parentEntityID - optional entityID of the parent process, if it exists
* @param ancestry - an array of ancestors for the generated alert
* @param alertsDataStream the values to populate the data_stream fields when generating alert documents
*/
public generateAlert({
public generateMalwareAlert({
ts = new Date().getTime(),
entityID = this.randomString(10),
parentEntityID,
@ -619,37 +625,198 @@ export class EndpointDocGenerator extends BaseDataGenerator {
},
},
},
dll: [
{
pe: {
architecture: 'x64',
},
code_signature: {
subject_name: 'Cybereason Inc',
trusted: true,
},
dll: this.getAlertsDefaultDll(),
};
}
hash: {
md5: '1f2d082566b0fc5f2c238a5180db7451',
sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
/**
* Creates a memory alert from the simulated host represented by this EndpointDocGenerator
* @param ts - Timestamp to put in the event
* @param entityID - entityID of the originating process
* @param parentEntityID - optional entityID of the parent process, if it exists
* @param ancestry - an array of ancestors for the generated alert
* @param alertsDataStream the values to populate the data_stream fields when generating alert documents
*/
public generateMemoryAlert({
ts = new Date().getTime(),
entityID = this.randomString(10),
parentEntityID,
ancestry = [],
alertsDataStream = alertsDefaultDataStream,
alertType,
}: {
ts?: number;
entityID?: string;
parentEntityID?: string;
ancestry?: string[];
alertsDataStream?: DataStream;
alertType?: AlertTypes;
} = {}): AlertEvent {
const processName = this.randomProcessName();
const isShellcode = alertType === AlertTypes.MEMORY_SHELLCODE;
const newAlert: AlertEvent = {
...this.commonInfo,
data_stream: alertsDataStream,
'@timestamp': ts,
ecs: {
version: '1.6.0',
},
// disabling naming-convention to accommodate external field
// eslint-disable-next-line @typescript-eslint/naming-convention
Memory_protection: {
feature: isShellcode ? 'shellcode_thread' : 'signature',
self_injection: true,
},
event: {
action: 'start',
kind: 'alert',
category: 'malware',
code: isShellcode ? 'malicious_thread' : 'memory_signature',
id: this.seededUUIDv4(),
dataset: 'endpoint',
module: 'endpoint',
type: 'info',
sequence: this.sequence++,
},
file: {},
process: {
pid: 2,
name: processName,
start: ts,
uptime: 0,
entity_id: entityID,
executable: `C:/fake/${processName}`,
parent: parentEntityID ? { entity_id: parentEntityID, pid: 1 } : undefined,
hash: {
md5: 'fake md5',
sha1: 'fake sha1',
sha256: 'fake sha256',
},
Ext: {
ancestry,
code_signature: [
{
trusted: false,
subject_name: 'bad signer',
},
],
user: 'SYSTEM',
token: {
integrity_level_name: 'high',
},
malware_signature: {
all_names: 'Windows.Trojan.FakeAgent',
identifier: 'diagnostic-malware-signature-v1-fake',
},
},
},
dll: this.getAlertsDefaultDll(),
};
path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
Ext: {
compile_time: 1534424710,
mapped_address: 5362483200,
mapped_size: 0,
malware_classification: {
identifier: 'Whitelisted',
score: 0,
threshold: 0,
version: '3.0.0',
// shellcode_thread memory alert have an additional process field
if (isShellcode) {
newAlert.Target = {
process: {
thread: {
Ext: {
start_address_allocation_offset: 0,
start_address_bytes_disasm_hash: 'a disam hash',
start_address_details: {
allocation_type: 'PRIVATE',
allocation_size: 4000,
region_size: 4000,
region_protection: 'RWX',
memory_pe: {
imphash: 'a hash',
},
},
},
},
},
],
};
};
}
return newAlert;
}
/**
* Creates an alert from the simulated host represented by this EndpointDocGenerator
* @param ts - Timestamp to put in the event
* @param entityID - entityID of the originating process
* @param parentEntityID - optional entityID of the parent process, if it exists
* @param ancestry - an array of ancestors for the generated alert
* @param alertsDataStream the values to populate the data_stream fields when generating alert documents
*/
public generateAlert({
ts = new Date().getTime(),
entityID = this.randomString(10),
parentEntityID,
ancestry = [],
alertsDataStream = alertsDefaultDataStream,
}: {
ts?: number;
entityID?: string;
parentEntityID?: string;
ancestry?: string[];
alertsDataStream?: DataStream;
} = {}): AlertEvent {
const alertType = this.randomChoice(Object.values(AlertTypes));
switch (alertType) {
case AlertTypes.MALWARE:
return this.generateMalwareAlert({
ts,
entityID,
parentEntityID,
ancestry,
alertsDataStream,
});
case AlertTypes.MEMORY_SIGNATURE:
case AlertTypes.MEMORY_SHELLCODE:
return this.generateMemoryAlert({
ts,
entityID,
parentEntityID,
ancestry,
alertsDataStream,
alertType,
});
default:
return assertNever(alertType);
}
}
/**
* Returns the default DLLs used in alerts
*/
private getAlertsDefaultDll() {
return [
{
pe: {
architecture: 'x64',
},
code_signature: {
subject_name: 'Cybereason Inc',
trusted: true,
},
hash: {
md5: '1f2d082566b0fc5f2c238a5180db7451',
sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
},
path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
Ext: {
compile_time: 1534424710,
mapped_address: 5362483200,
mapped_size: 0,
malware_classification: {
identifier: 'Whitelisted',
score: 0,
threshold: 0,
version: '3.0.0',
},
},
},
];
}
/**

View file

@ -295,6 +295,31 @@ export type AlertEvent = Partial<{
}>;
}>;
}>;
// disabling naming-convention to accommodate external field
// eslint-disable-next-line @typescript-eslint/naming-convention
Memory_protection: Partial<{
feature: ECSField<string>;
self_injection: ECSField<boolean>;
}>;
Target: Partial<{
process: Partial<{
thread: Partial<{
Ext: Partial<{
start_address_allocation_offset: ECSField<number>;
start_address_bytes_disasm_hash: ECSField<string>;
start_address_details: Partial<{
allocation_type: ECSField<string>;
allocation_size: ECSField<number>;
region_size: ECSField<number>;
region_protection: ECSField<string>;
memory_pe: Partial<{
imphash: ECSField<string>;
}>;
}>;
}>;
}>;
}>;
}>;
process: Partial<{
command_line: ECSField<string>;
ppid: ECSField<number>;
@ -328,6 +353,10 @@ export type AlertEvent = Partial<{
>;
}>;
user: ECSField<string>;
malware_signature: Partial<{
all_names: ECSField<string>;
identifier: ECSField<string>;
}>;
}>;
}>;
file: Partial<{

View file

@ -2,10 +2,12 @@
"Endpoint.policy.applied.id",
"Target.process.Ext.services",
"Target.process.Ext.user",
"Target.process.executable",
"Target.process.hash.md5",
"Target.process.hash.sha1",
"Target.process.hash.sha256",
"Target.process.hash.sha512",
"Target.process.name",
"Target.process.parent.hash.md5",
"Target.process.parent.hash.sha1",
"Target.process.parent.hash.sha256",
@ -17,6 +19,14 @@
"Target.process.pe.original_file_name",
"Target.process.pe.product",
"Target.process.pgid",
"Target.process.thread.Ext.start_address_details.allocation_type",
"Target.process.thread.Ext.start_address_bytes_disasm_hash",
"Target.process.thread.Ext.start_address_allocation_offset",
"Target.process.thread.Ext.start_address_details.allocation_size",
"Target.process.thread.Ext.start_address_details.region_size",
"Target.process.thread.Ext.start_address_details.region_protection",
"Target.process.thread.Ext.start_address_details.memory_pe.imphash",
"Target.process.thread.Ext.start_address_bytes",
"agent.id",
"agent.type",
"agent.version",
@ -68,10 +78,13 @@
"host.type",
"process.Ext.services",
"process.Ext.user",
"process.Ext.code_signature",
"process.executable",
"process.hash.md5",
"process.hash.sha1",
"process.hash.sha256",
"process.hash.sha512",
"process.name",
"process.parent.hash.md5",
"process.parent.hash.sha1",
"process.parent.hash.sha256",
@ -88,5 +101,7 @@
"user.email",
"user.hash",
"user.id",
"Ransomware.feature"
"Ransomware.feature",
"Memory_protection.feature",
"Memory_protection.self_injection"
]

View file

@ -928,5 +928,172 @@ describe('Exception helpers', () => {
},
]);
});
test('it should return pre-populated memory signature items for event code `memory_signature`', () => {
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
_id: '123',
process: {
name: 'some name',
executable: 'some file path',
hash: {
sha256: 'some hash',
},
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Memory_protection: {
feature: 'signature',
},
event: {
code: 'memory_signature',
},
});
expect(defaultItems[0].entries).toEqual([
{
field: 'Memory_protection.feature',
operator: 'included',
type: 'match',
value: 'signature',
id: '123',
},
{
field: 'process.executable.caseless',
operator: 'included',
type: 'match',
value: 'some file path',
id: '123',
},
{
field: 'process.name.caseless',
operator: 'included',
type: 'match',
value: 'some name',
id: '123',
},
{
field: 'process.hash.sha256',
operator: 'included',
type: 'match',
value: 'some hash',
id: '123',
},
]);
});
test('it should return pre-populated memory shellcode items for event code `malicious_thread`', () => {
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
_id: '123',
process: {
name: 'some name',
executable: 'some file path',
Ext: {
token: {
integrity_level_name: 'high',
},
},
},
// eslint-disable-next-line @typescript-eslint/naming-convention
Memory_protection: {
feature: 'shellcode_thread',
self_injection: true,
},
event: {
code: 'malicious_thread',
},
Target: {
process: {
thread: {
Ext: {
start_address_allocation_offset: 0,
start_address_bytes_disasm_hash: 'a disam hash',
start_address_details: {
allocation_type: 'PRIVATE',
allocation_size: 4000,
region_size: 4000,
region_protection: 'RWX',
memory_pe: {
imphash: 'a hash',
},
},
},
},
},
},
});
expect(defaultItems[0].entries).toEqual([
{
field: 'Memory_protection.feature',
operator: 'included',
type: 'match',
value: 'shellcode_thread',
id: '123',
},
{
field: 'Memory_protection.self_injection',
operator: 'included',
type: 'match',
value: 'true',
id: '123',
},
{
field: 'process.executable.caseless',
operator: 'included',
type: 'match',
value: 'some file path',
id: '123',
},
{
field: 'process.name.caseless',
operator: 'included',
type: 'match',
value: 'some name',
id: '123',
},
{
field: 'process.Ext.token.integrity_level_name',
operator: 'included',
type: 'match',
value: 'high',
id: '123',
},
{
field: 'Target.process.thread.Ext.start_address_details',
type: 'nested',
entries: [
{
field: 'allocation_type',
operator: 'included',
type: 'match',
value: 'PRIVATE',
id: '123',
},
{
field: 'allocation_size',
operator: 'included',
type: 'match',
value: '4000',
id: '123',
},
{ field: 'region_size', operator: 'included', type: 'match', value: '4000', id: '123' },
{
field: 'region_protection',
operator: 'included',
type: 'match',
value: 'RWX',
id: '123',
},
{
field: 'memory_pe.imphash',
operator: 'included',
type: 'match',
value: 'a hash',
id: '123',
},
],
id: '123',
},
]);
});
});
});

View file

@ -496,6 +496,139 @@ export const getPrepopulatedRansomwareException = ({
};
};
export const getPrepopulatedMemorySignatureException = ({
listId,
ruleName,
eventCode,
listNamespace = 'agnostic',
alertEcsData,
}: {
listId: string;
listNamespace?: NamespaceType;
ruleName: string;
eventCode: string;
alertEcsData: Flattened<Ecs>;
}): ExceptionsBuilderExceptionItem => {
const { process } = alertEcsData;
return {
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
entries: addIdToEntries([
{
field: 'Memory_protection.feature',
operator: 'included',
type: 'match',
value: alertEcsData.Memory_protection?.feature ?? '',
},
{
field: 'process.executable.caseless',
operator: 'included',
type: 'match',
value: process?.executable ?? '',
},
{
field: 'process.name.caseless',
operator: 'included',
type: 'match',
value: process?.name ?? '',
},
{
field: 'process.hash.sha256',
operator: 'included',
type: 'match',
value: process?.hash?.sha256 ?? '',
},
]),
};
};
export const getPrepopulatedMemoryShellcodeException = ({
listId,
ruleName,
eventCode,
listNamespace = 'agnostic',
alertEcsData,
}: {
listId: string;
listNamespace?: NamespaceType;
ruleName: string;
eventCode: string;
alertEcsData: Flattened<Ecs>;
}): ExceptionsBuilderExceptionItem => {
const { process, Target } = alertEcsData;
return {
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
entries: addIdToEntries([
{
field: 'Memory_protection.feature',
operator: 'included',
type: 'match',
value: alertEcsData.Memory_protection?.feature ?? '',
},
{
field: 'Memory_protection.self_injection',
operator: 'included',
type: 'match',
value: String(alertEcsData.Memory_protection?.self_injection) ?? '',
},
{
field: 'process.executable.caseless',
operator: 'included',
type: 'match',
value: process?.executable ?? '',
},
{
field: 'process.name.caseless',
operator: 'included',
type: 'match',
value: process?.name ?? '',
},
{
field: 'process.Ext.token.integrity_level_name',
operator: 'included',
type: 'match',
value: process?.Ext?.token?.integrity_level_name ?? '',
},
{
field: 'Target.process.thread.Ext.start_address_details',
type: 'nested',
entries: [
{
field: 'allocation_type',
operator: 'included',
type: 'match',
value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '',
},
{
field: 'allocation_size',
operator: 'included',
type: 'match',
value:
String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '',
},
{
field: 'region_size',
operator: 'included',
type: 'match',
value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '',
},
{
field: 'region_protection',
operator: 'included',
type: 'match',
value:
String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '',
},
{
field: 'memory_pe.imphash',
operator: 'included',
type: 'match',
value:
String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '',
},
],
},
]),
};
};
/**
* Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping
*/
@ -537,26 +670,45 @@ export const defaultEndpointExceptionItems = (
const { event: alertEvent } = alertEcsData;
const eventCode = alertEvent?.code ?? '';
if (eventCode === 'ransomware') {
return getProcessCodeSignature(alertEcsData).map((codeSignature) =>
getPrepopulatedRansomwareException({
listId,
ruleName,
eventCode,
codeSignature,
alertEcsData,
})
);
switch (eventCode) {
case 'memory_signature':
return [
getPrepopulatedMemorySignatureException({
listId,
ruleName,
eventCode,
alertEcsData,
}),
];
case 'malicious_thread':
return [
getPrepopulatedMemoryShellcodeException({
listId,
ruleName,
eventCode,
alertEcsData,
}),
];
case 'ransomware':
return getProcessCodeSignature(alertEcsData).map((codeSignature) =>
getPrepopulatedRansomwareException({
listId,
ruleName,
eventCode,
codeSignature,
alertEcsData,
})
);
default:
// By default return the standard prepopulated Endpoint Exception fields
return getFileCodeSignature(alertEcsData).map((codeSignature) =>
getPrepopulatedEndpointException({
listId,
ruleName,
eventCode,
codeSignature,
alertEcsData,
})
);
}
// By default return the standard prepopulated Endpoint Exception fields
return getFileCodeSignature(alertEcsData).map((codeSignature) =>
getPrepopulatedEndpointException({
listId,
ruleName,
eventCode,
codeSignature,
alertEcsData,
})
);
};