Declare and set variables

The filter language supports the declaration and assignment of custom variables.

Data types

The following table describes the built-in scalar data types that are supported by the language.

Type name

Description

Value representation

Coercion rules

uint32

unsigned 32-bit value

3

uint32 → uint64

uint32 → double

uint32 → bool

uint64

unsigned 64-bit value

5

uint64 → bool

double

double value

4.0

double → bool

bool

Boolean value

TRUE

FALSE

None

string

A string value

"a string"

None

ipaddr

IP version 4 or 6 address

IP address constants (IPv4 and IPv6) are represented in code using a quoted string with the ip() casting operator.

ip("192.168.1.0/24")

ip("192.168.1.3")

ip("fe80::ed12:dd3a:f496:0000/16")

ip("fe80::ed12:dd3a:f496:b829")

None

sha256digest

A SHA256 hash digest

SHA256 constants are represented in code using a quoted string with the sha256 casting operator.

sha256("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")

None

md5digest

An MD5 hash digest

MD5 constants are represented in code using a quoted string with the MD5 casting operator.

md5("d41d8cd98f00b204e9800998ecf8427e")

None

pid

A process identifier

This type cannot be assigned to.

pid → uint32

 


‎The following table describes various complex data types that are supported by the language.

Type name

Description

Value representation

threshold

Threshold variables provide a mechanism for detecting the occurrence of a fixed number of events within a specified time period. For more information about how to use threshold variables, see Using Threshold Variables.

None

set. Identified by "[]" following the variable name of scalar variable definition.

Sets are collections of scalar data types. Scalar values are inserted and removed from a set. Sets are also tested for the inclusion of a scalar value. For more information about how to use set variables, see Using Threshold Variables.

None

time_range

Represents a range of time with a starting hour and minute and ending hour and minute. For more information about using time_range variables, see Using Time Range Matching.

None

Variable initialization

Variables are declared within rule groups and initialized when declared. The right side of a variable initialization statement references only constants or other user-defined variables, and event attributes.

Example:

rulegroup Privileged_Logons

{

    # declare a set used to hold constant data

global string priv_accounts[3] = { "admin", "root", "oper"};

#
‎ # Alert on remote logins into privileged accounts
‎ #
‎ alert when session.event == SESSION_LOGON and

session.type == SESSION_TYPE_REMOTE and

priv_accounts contains session.username;

‎};

rulegroup Process_Injection
‎{
‎ uint32 mask = (PROCESS_VM_OPERATION | PROCESS_VM_WRITE);

‎ #
‎ # Some unsigned process is injecting into a non-child process
‎ #

alert when (object.targetobjecttype == OBJECT_TYPE_PROCESS ) and
‎                 (
‎              ( object.accessmask & mask) ) == mask) and
‎              ( object.sourcepid != getppid ( object.targetpid ) )and
‎              ( ( curproc.exe.sig == SIG_STATUS_INVALID ) or ( curproc.exe.sig == SIG_STATUS_NO_SIG )
‎                 );
‎};


‎ 

The following table describes the default initialization values for various variable types.

Type name

Value

Description

uint32

0

 

uint64

0

 

double

0.0

 

bool

false

 

string

""

empty string

ipaddr

ip(0.0.0.0)

IPv4 host address

sha256digest

sha256("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")

SHA256 hash of empty string

md5digest

md5("d41d8cd98f00b204e9800998ecf8427e")

MD5 hash of empty string

Some variable types, including time_range and threshold, must be initialized when declared. There is no default initialization for variables of those types, and it is a compile error not to initialize the variables at declare time.

Variable scope

A variable’s scope is specified when the variable is declared.

The following two variable types are supported:

Per-process

Global

For per-process variables, a separate copy of the variable value is maintained for each process in the state engine. Values written to that variable by process X see only that process. The use of the local specifier marks a variable as local. For global variables, a single copy of the variable value is maintained for all processes in the state engine. An update to a global variable by process X is seen by all other processes. The use of the global specifier marks a variable as global. Local is the default if no scope is specified.

Examples:

# Global string set variable to keep track of the most recent 128 name queries performed on the system.
‎global string gNameQuerySet[128] = {};

# Local bool variable to track whether the process has attempted injection into other processes.
‎local bool injectionAttempted = FALSE; 

# Global variable to track last 8 removable media device inserted.
‎global string gRemovableMediaSerialIds[8] = {};

An important difference between local and global variables pertains to their accessibility during rule execution. Events like process events or file events are associated with a specific process, for example, the process that is starting or exiting, or the process performing the file operation. These events provide a process context. However, certain event types are system activities that are not associated with a specific process. These events include logon events, print events, and media events, and the events provide no process context. Because local variables are per-process, local variables can only be accessed within a rule that is processing an event that supplies a process context. When processing event types that do not include a process context, the rule will not be able to read or write local variables. Global variables are accessed when processing both events that provide a process context and those that do not. The compiler produces a warning (for more information about this topic, see Compilation Warnings) when compiling a rule that accesses a local variable but does not include a reference to an event type with a process context.

Variable storage class

The storage class of a variable is specified when the variable is declared.

Temporary

Variables declared as temporary are reinitialized each time a new event is processed by the filter engine. Temporary variables are useful for signaling a condition during the processing of an event and are indicated by using the temp keyword in the declaration.

temp local bInjectionEvent = FALSE;

Persistent

Variables declared as persistent retain their values when a new rule set is loaded assuming a variable of the same name and same type exists in the new ruleset. If a variable is used to track some interesting process state over time, you may wish to retain the value of that variable even when a new rule set is loaded onto the endpoint agent. An example might be tracking the list of files that a process has written to. Persistent variables are indicated by using the persist keyword in the declaration.

persist local fileWriteList[128] = {};

Non-persistent

This is the default behavior if no lifetime is explicitly specified when the variable is declared.

The use of the nonpersist specifier indicates that the variable is not persistent. The variable’s value does not persist across rule set reloads. If nonpersist is specified or no specifier is provided with the variable declaration, then the variable is treated as nonpersist.

Variable access

A rule set consists of one or more rule groups. Rule groups allow rules to be grouped logically and also serve as namespaces for variable declaration. By default, when a variable is declared in a rule group, the variable’s name is referenced in other rule groups, allowing rules in other rule groups to read or write the variable. The use of the private specifier when declaring a variable indicates that other rule groups are not able to see that variable declaration. The use of the public specifier indicates that a variable is visible and readable or writable by rules in other rule groups. If neither specifier is provided, the variable is treated as public.

Set variables at rule run time

Variables are set at run time using the set action. The following example keeps a count of the total number of files written by a process and keeps a set of the last 128 file names written by a process.

Example:

rulegroup filewrites{
‎ local fileWriteList[128] = {};
‎ local fileWriteCount = 0;
‎ set { SetInsert(fileWriteList, file.path); fileWriteCount+=1;}
‎ when (file.event == FILE_WRITE);
‎}

Process contexts

There are situations where it is convenient to set or query the value of a local variable associated with a process context other than the one indicated by the event being processed. The context operator is used to accomplish this and can be used anywhere in the language where an identifier is used. An example of using the context operator follows.

The first parameter to the context operator is an identifier name of type uint32 indicating a process id. The second parameter is the identifier name that is to be accessed. This example uses the context operator to propagate a taint flag between parent and child processes. When a process is started, the process reads the taint flag from the parent process using the context operator.

rulegroup fileReaders {

local bool tainted = false;

#
‎# apply a marker to file reader processes
‎#

set {tainted = TRUE;}
‎when (process.state == PROCESS_STARTED &&
‎     (
‎       strstr(process.path,"winword.exe",false)||
‎       strstr(process.path,"excel.exe",false) ||
‎       strstr(process.path,"powerpnt.exe",false) ||
‎       strstr(process.path,"AcroRd32.exe",false)
‎      )
‎     );

 

#
‎# propagate the tainted flag from parent to child processes
‎#

set {tainted = TRUE;}
‎when (process.state == PROCESS_STARTED && context[process.ppid, tainted] == TRUE);

};‎ 

 

The following example shows a context operator that is used on the left side of an assignment operation, watching for possible process injection and sets a value in both the process performing the injection and the process that was injected into.

rulegroup injectionTracking{

 

temp bool bNewInjection = false;
‎local bool bInjector = false;
‎local bool bInjectee = false;
‎global uint32 injectionAcccessMask = (PROCESS_VM_OPERATION | PROCESS_VM_WRITE);


‎set { bInjector = true;
‎      context[object.targetpid, bInjectee] = true;
‎    }

when ( object.targetobjecttype == OBJECT_TYPE_PROCESS ) and
‎     ( ( object.accessmask & injectionAcccessMask ) == injectionAcccessMask ) and
‎     ( object.sourcepid != getppid ( object.targetpid ) ) and

  ( object.sourcepid != object.targetpid) and
‎     ( (curproc.exe.sig == SIG_STATUS_INVALID ) or
‎ ( curproc.exe.sig == SIG_STATUS_NO_SIG ) );

# alert if an injection just occurred
‎alert when (bNewInjection == TRUE);

};