YARA-L 2.0 言語の構文

このセクションでは、YARA-L 構文の主な要素について説明します。YARA-L 2.0 言語の概要もご覧ください。

コメント

C の場合と同様、コメントは 2 つのスラッシュ文字(// comment)により指定します。コメントが複数行にわたる場合は、スラッシュ アスタリスク(/* comment */)を使用してコメントアウトします。

定数

整数、文字列、浮動小数点数、正規表現の定数がサポートされています。文字列定数の場合は、二重引用符を使用します。正規表現定数には /regex/ を使用します。

# 文字は条件セクションの特殊文字です。イベント名またはプレースホルダ変数名の前で使用する場合は、すべての events セクション条件を満たす固有のイベントまたは値の数を表します。

YARA-L を使用すると、net.ip_in_range_cidr() ステートメントを使用するサブネットワーク内のすべての IP アドレスにわたり UDM イベントを検索できます。IPv4 と IPv6 の両方がサポートされています。

IP アドレスの範囲を検索するには、IP UDM フィールドとクラスレス ドメイン間ルーティング(CIDR)範囲を指定します。YARA-L は、単一および繰り返しの IP アドレス フィールドの両方を処理できます。

IPv4 の例:

net.ip_in_range_cidr($e.principal.ip, "192.0.2.0/24")

IPv6 の例:

net.ip_in_range_cidr($e.network.dhcp.yiaddr, "2001:db8::/32")

net.ip_in_range_cidr() ステートメントを使用したルールの例については、サンプルのルール: IP アドレスの範囲内の単一イベントをご覧ください。

演算子

YARA-L では次の演算子を使用できます。

オペレーター Description
= 等しい
!= 等しくない
< 次より小さい
<= 以下
> 次より大きい
>= 以上

文字列の引用符

YARA-L 2.0 で文字列を囲むには、次のいずれかの引用符を使用します。ただし、引用符で囲まれたテキストは、使用する引用符に応じて解釈が異なります。

  1. 二重引用符(")- 通常の文字列に使用します。エスケープ文字を含める必要があります。たとえば、「hello\tworld」の場合、\t はタブとして解釈されます。

  2. バッククォート(`)- すべての文字を文字どおりに解釈するために使用します。例: `hello\tworld` の場合、\t はタブとして解釈されません。

正規表現では、多くの場合、文字列にバックスラッシュ(\)文字を含める必要があります。ただし、二重引用符を使用する場合は、バックスラッシュ文字をバックスラッシュ文字でエスケープする必要がありますが、読みづらくなります。

たとえば、次の 2 つの式は等価です。

  • re.regex($e.network.email.from, `.*altostrat\.com`)
  • re.regex($e.network.email.from, ".*altostrat\\.com")

読みやすくするために、正規表現の文字列にはバッククォート文字を使用することをおすすめします。

変数

YARA-L 2.0 では、すべての変数が $<variable name> として表されます。

次のタイプの変数を定義できます。

  • イベント変数 - イベントのグループを正規化された形式(UDM)で表します。events セクションで、イベント変数の条件を指定します。変数の後ろにあるイベント フィールドを使用して、イベント変数を識別します。イベント フィールドは、.<field name> のチェーンで表されます(例: $e.field1.field2)。イベント フィールド チェーンは常に最上位の UDM から始まります。

  • 一致変数 - match セクションで宣言します。一致変数は、一意の変数セットのセット(および期間)ごとに 1 行が返されるため、クエリのグループ フィールドになります。ルールが一致を見つけると、一致変数の値が返されます。各一致変数が表す内容を events セクションで指定します。

  • プレースホルダ変数 - events セクションで宣言して定義します。プレースホルダ変数は、match 変数に似ています。ただし、condition セクションでプレースホルダ変数を使用して、一致条件を指定できます。

推移結合条件を使用して、イベント フィールド間の関係を宣言するために、一致変数とプレースホルダ変数を使用します(詳細については、events セクションの構文をご覧ください)。

events セクションの構文

events セクションに述語のリストを表示して、次の項目を指定します。

  • 各一致変数またはプレースホルダ変数が表すもの
  • 単一のイベント変数に対するフィルタ条件
  • 2 つのイベント変数に対する結合条件。

変数の宣言

変数を宣言する場合は、次の構文を使用します。

  • EVENT_FIELD = VAR
  • VAR = EVENT_FIELD

次の例に示すように、どちらも同等です。

  • $e.source.hostname = $hostname
  • $userid = $e.principal.user.userid

この宣言は、この変数がイベント変数に指定されたフィールドを表すことを示します。イベント フィールドが配列フィールドの場合、一致変数は配列内の任意の値を表すことができます。1 つの一致変数またはプレースホルダ変数に複数のイベント フィールドを割り当てることもできます。これは推移的な結合条件です。

たとえば、次のようになります。

  • $e1.source.ip = $ip
  • $e2.target.ip = $ip

これは次と同等です。

  • $e1.source.ip = $ip
  • $e1.source.ip = $e2.target.ip

イベント変数のフィルタ条件

単一のイベント変数に対するフィルタ条件には、次の構文を使用します。

  • [EVENT_FIELD] [OP] [CONST]
  • [CONST] [OP] [EVENT_FIELD]

どちらも同等ですが、読みやすくするために前者([EVENT_FIELD] [OP] [CONST])を使用することをおすすめします。

例:

  • $e.source.hostname = "host1234"
  • $e.source.port < 1024
  • 1024 < $e.source.port

この述語はイベント変数のフィルタとして使用されます。つまり、イベント変数で表されるイベント グループがこの条件を満たす必要があります。

イベント変数の結合条件

2 つのイベント変数の結合条件を表すには、次の構文を使用します。

[EVENT_FIELD] [OP] [EVENT_FIELD]

例:

  • $e1.source.hostname = $e2.target.hostname
  • $e1.metadata.timestamp < $e2.metadata.timestamp

この述語は、条件と 2 つのイベント変数を結合するために使用されます。

無効な述語の例を以下に示します。

  • $e.source.hostname != $hostname //comparison over match/placeholder var
  • $hostname != "host1234" //comparison over match/placeholder var
  • $var1 //variable itself does not mean anything

論理演算子

論理 and と論理 or の演算子は、次の例に示すように events セクションで使用できます。

  • $e.metadata.event_type = "NETWORK_DNS" or $e.metadata.event_type = "NETWORK_DHCP"
  • ($e.metadata.event_type = "NETWORK_DNS" and $e.principal.ip = "192.0.2.12") or ($e.metadata.event_type = "NETWORK_DHCP" and $e.principal.mac = "AB:CD:01:10:EF:22")
  • not $e.metadata.event_type = "NETWORK_DNS"

デフォルトでは、優先度の高い順に notandor となります。

たとえば、"a or b and c""a or (b and c)" と評価されます。必要に応じて、括弧を使用して優先順位を変更できます。

一致セクションの構文

一致条件を確認する前に、一致セクションでグループ イベントの一致変数を一覧表示します。これらのフィールドは、一致するたびに返されます。

  • 各一致変数が表す内容を events セクションで指定します。
  • over キーワードに続くイベントを相関させる期間を指定します。期間外のイベントは無視されます。
  • 期間を指定するには、構文 <number><s/m/h/d> を使用します。ここで、s/m/h/d は秒、分、時、日を表します。
  • 指定できる最小時間は 1 分です。
  • 指定できる最大時間は 48 時間です。

有効な match の例を次に示します。

$var1, $var2 over 5m

このステートメントは、ルールが一致した場合に $var1$var2events セクションで定義)を返します。指定する時間は 5 分です。間隔が 5 分を超えるイベントは相関されないため、ルールで無視されます。

有効な match の別の例を示します。

$user over 1h

次のステートメントは、ルールが一致した場合に $user を返します。指定する期間は 1 時間です。1 時間を超えるイベントは相関されません。これらのイベントは、ルールで検出対象と見なされません。

有効な match の別の例を示します。

$source_ip, $target_ip, $hostname over 2m

次のステートメントは、ルールが一致した場合に $source_ip$target_ip$hostname を返します。指定する期間は 2 分です。間隔が 2 分を超えるイベントは相関されません。これらのイベントは、ルールで検出対象と見なされません。

次の例は、無効な match を示しています。

  • var1, var2 over 5m // invalid variable name
  • $user 1h // missing keyword

condition セクションの構文

condition セクションで、events セクションで定義されているイベントと変数に一致する条件を指定します。ここで一致する述語が一覧表示され、キーワード and または or に結合されます。

次の条件は境界条件です。関連付けられたイベント変数を強制的に存在するようにします。つまり、検出によりイベントが少なくとも 1 回出現する必要があります。

  • $var // equivalent to #var > 0
  • #var > n // where n is >= 0
  • #var >= m // where m > 0

次の条件は境界なし条件です。関連付けられたイベント変数が存在しないようにします。つまり、検出でイベントのオカレンスがない可能性があります。これにより、不在ルールを作成できます。このルールでは、変数の存在ではなく、変数の不在を検索します。

  • !$var // equivalent to #var = 0
  • #var < n // where n is > 0
  • #var <= m // where m >= 0

次の例では、変数(イベント変数またはプレースホルダ変数)の特殊文字 # が、その変数の個々のイベントまたは値の数を表します。

$e and #port > 50 or #event1 > 2 or #event2 > 1 or #event3 > 0

次の不在の例も有効で、$event1 とは異なるイベントが 3 つ以上存在し、$event2 とは異なるイベントがない場合に true と評価されます。

#event1 > 2 and !$event2

無効な述語の例を以下に示します。

  • $e, #port > 50 // incorrect keyword usage
  • $e or #port < 50 // or keyword not supported with non-bounding conditions

options セクションの構文

options セクションでは、ルールのオプションを指定します。指定できる唯一のオプションは allow_zero_values です。このオプションを true として構成すると、検索結果で不特定の UDM 値を一致変数値として返すことができます。未指定(ゼロとも呼ばれる)の値とは、UDM フィールドでの空の値です。たとえば、空の文字列、数値の 0、ブール値の false、UNSPECIFIED 列挙型などです。options セクションは、ルールの最後で condition セクションの後に指定する必要があります。

例: ルール全体に対して allow_zero_values を構成する

次の例では allow_zero_valuestrue として指定されているため、$e1$e2 のホスト名には空の文字列値を含めることができます。

rule AllowZeroValues {
  meta:
  events:
    $e1.metadata.event_type = "NETWORK_DNS"
    $e1.principal.hostname = $hostname

    $e2.metadata.event_type = "NETWORK_HTTP"
    $e2.principal.hostname = $hostname
  match:
    $hostname over 15m
  condition:
    $e1 and $e2
  options:
    allow_zero_values = true
}

正規表現

YARA-L 2.0 では、次のいずれかの構文を使用して正規表現を定義できます。

  • YARA 構文の使用 - イベントに関連します。この構文の一般的な表現は次のとおりです。$e.field = /regex/
  • YARA-L 構文の使用 - 次のパラメータを受け取る関数として使用します。
    • 正規表現が適用されるフィールド。
    • 文字列として指定された正規表現。文字列の後に nocase 修飾子を使用すると、検索で大文字小文字の区別が無視されるよう指定できます。この構文の一般的な表現は次のとおりです。re.regex($e.field, `regex`)

YARA-L 2.0 で正規表現を定義するときには、次の点に注意してください。

  • いずれの場合も、指定した正規表現に一致する部分文字列が文字列に含まれている場合、述語は true です。正規表現の先頭または末尾に .* を追加する必要はありません。
  • 文字列と完全に一致する場合、またはプレフィックスまたはサフィックスのみと一致する場合は、正規表現に ^(開始)と $(終了)のアンカー文字を含めます。たとえば、/^full$/"full" と正確に一致しますが、/full/"fullest""lawfull""joyfully" と一致します。
  • UDM フィールドの関連ログに改行文字が含まれている場合、regexp は UDM フィールドの最初の行にのみ一致します。完全な UDM フィールド マッチングを適用するには、(?s) を正規表現に追加します。たとえば、/.*allUDM.*//(?s).allUDM.*/ に置き換えます。

生の文字列

YARA-L 2.0 を使用して、企業のイベントデータ内の生の文字列を検索できます。

文字列を検索する場合は、文字列を二重引用符(`)ではなくバッククォート文字(`)で囲みます。

バッククォートは、文字列の内容が文字どおりに解釈されることを示します(エスケープ文字は解析されません)。

型チェック

インターフェース内でルールを作成する際に、Chronicle は YARA-L 構文に対して型チェックを行います。表示される型チェックエラーは、ルールが意図したとおりに機能するように修正する際に活用できます。

無効な述語の例を以下に示します。

// $e.target.port is of type integer which cannot be compared to a string.
$e.target.port = "80"

// "LOGIN" is not a valid event_type enum value.
$e.metadata.event_type = "LOGIN"

デフォルトでゼロ値を除外する

イベント フィールドが空の場合、ゼロ値がイベント フィールドに指定されます。

ゼロ値の例を次に示します。

  • 文字列フィールドが空の文字列 "" である
  • 整数フィールドが 0 である
  • ブール値フィールドが false である
  • Enum フィールドが、対応する UNSPECIFIED 値である
// principal.hostname is a string field, and will be
// the empty string if unpopulated.
$e.principal.hostname

// target.port is an integer field, and will be zero if unpopulated.
$e.target.port

デフォルトでは、一致変数は検出でゼロ値を返しません。その他の参照されるイベント フィールドの場合、そのような条件を明示的に指定しない限り、ゼロは除外されません。

rule ExcludeZeroValues {
  meta:
    "author" = "noone@google.com"
  events:
    $e1.metadata.event_type = "NETWORK_DNS"
    $e1.principal.hostname = $hostname

    // $e1.principal.user.userid may be empty string.
    $e1.principal.user.userid != "Guest"

    $e2.metadata.event_type = "NETWORK_HTTP"
    $e2.principal.hostname = $hostname

    // $e2.target.asset_id cannot be empty string as explicitly specified.
    $e2.target.asset_id != ""

  match:
    // $hostname cannot be empty string.
    $hostname over 1h

  condition:
    $e1 and $e2
}

この動作により、ルールによって生成される偽陽性の数が減り、検出の質が向上します。

ゼロ値除外を無効にする

偽陽性率が高くなる可能性があるルールや、ゼロ値を使用しないと機能しないルールごとにゼロ値除外機能を無効にできます。このようなケースは非常にまれです。この動作を無効にしてゼロ値を受け入れる方法については、オプション セクションの構文をご覧ください。

Functions

このセクションでは、Chronicle が Detection Engine でサポートする YARA-L 2.0 関数について説明します。

これらの関数は、ルール内の次の領域で使用できます。

文字列関数

Chronicle は、次の文字列操作関数をサポートしています。

  • string.concat(a, b)
  • string.to_lower(stringText)
  • string.to_upper(stringText)
  • string.base64_decode(encodedString)

以降のセクションでは、それぞれの使用方法について説明します。

文字列または整数を連結する

2 つの文字列、2 つの整数、またはそれらの組み合わせを連結した値を返します。

strings.concat(a, b)

この関数は、文字列または整数の 2 つの引数を取り、文字列として連結された 2 つの値を返します。整数は連結する前に文字列にキャストされます。引数には、リテラルまたはイベント フィールドを指定できます。両方の引数がフィールドである場合、2 つの属性は同じイベントからのものでなければなりません。

次の例には、引数として文字列変数と文字列リテラルが含まれています。

"google-test" = strings.concat($e.principal.hostname, "-test")

次の例には、引数として文字列変数と整数変数が含まれています。principal.hostname と principal.port は、同じイベント $e からのもので、文字列を返すために連結されます。

"google80" = strings.concat($e.principal.hostname, $e.principal.port)

次の例では、イベント $e1 の principal.port と、イベント $e2 のプリンシパルのホスト名を連結しようとします。引数が異なるイベント変数であるため、コンパイラ エラーが返されます。

// returns a compiler error
"test" = strings.concat($e1.principal.port, $e2.principal.hostname)

文字列を大文字または小文字に変換する

これらの関数は、すべての文字を大文字または小文字に変更した後、文字列テキストを返します。

  • string.to_lower(stringText)
  • string.to_upper(stringText)
"test@google.com" = strings.to_lower($e.network.email.from)
"TEST@GOOGLE.COM" = strings.to_upper($e.network.email.to)

文字列を Base64 でデコードする

エンコードされた文字列の Base64 デコードされたバージョンを含む文字列を返します。

strings.base64_decode(encodedString)

この関数は、base64 でエンコードされた 1 つの文字列を引数として受け取ります。encodedString が Base64 でエンコードされた有効な文字列でない場合、この関数は encodedString をそのまま返します。

この例は、principal.domain.name が "dGVzdA==" であり、文字列 "test" の base64 エンコードである場合、True を返します。

"test" = strings.base64_decode($e.principal.domain.name)

正規表現関数

Chronicle は、正規表現セクションで説明した正規表現の照合に加え、次の正規表現関数をサポートしています。

  • re.capture(stringText, regex)
  • re.replace(stringText、replaceRegex、replaceText)

正規表現のキャプチャ

引数で指定された正規表現パターンを使用して、文字列からデータをキャプチャ(抽出)します。

re.capture(stringText, regex)

この関数は次の 2 つの引数を取ります。

  • stringText: 検索する元の文字列。
  • regex: 検索するパターンを示す正規表現。

正規表現には、0 または 1 つのキャプチャ グループをかっこで囲むことができます。正規表現にキャプチャ グループがない場合、この関数は最初に一致した部分文字列を返します。正規表現に 1 つのキャプチャ グループが含まれている場合、キャプチャ グループの最初の一致部分文字列が返されます。2 つ以上のキャプチャ グループを定義すると、コンパイラ エラーが返されます。

この例では、$e.principal.hostname に「aaa1bbaa2」が含まれていれば、この関数は最初のインスタンスを返すため、True になります。この例にはキャプチャ グループはありません。

"aaa1" = re.capture($e.principal.hostname, "a+[1-9]")

この例では、メール内の @ 記号より後ろのすべての部分をキャプチャします。$e.network.email.from フィールドが test@google.com の場合、この例では google.com を返します。この例には、キャプチャ グループが 1 つ含まれています。

"google.com" = re.capture($e.network.email.from , "@(.*)")

正規表現の置換

正規表現の置換を行います。

re.replace(stringText, replaceRegex, replacementText)

この関数は次の 3 つの引数を取ります。

  • stringText: 元の文字列。
  • replaceRegex: 検索するパターンを示す正規表現。
  • substitutionText: 各一致に挿入するテキスト。

元の stringText から派生した新しい文字列を返します。replaceRegex のパターンと一致するすべての部分文字列が replacementText の値に置き換えられます。replacementText で、バックスラッシュでエスケープされた数字(\1 ~\9)を使用して、replaceRegex パターン中の対応する括弧で囲まれたグループに一致するテキストを挿入ます。一致するテキスト全体を参照するには、\0 を使用します。

この関数は、重複しない一致を置き換えるもので、最初に見つかったものを優先的に置き換えます。たとえば、re.replace("banana", "ana", "111") は、文字列「b111na」を返します。

この例では、メール内の @ 記号の後のすべての部分をキャプチャし、comorg に置き換えて結果を返します。ネストされた関数が使用されていることに注意します。

"email@google.org" = re.replace($e.network.email.from, "com", "org")

次の例では、replaceText 引数でバックスラッシュでエスケープされた数字を使用して、replaceRegex パターンとの一致を参照します。

"test1.com.google" = re.replace(
                       $e.principal.hostname, // holds "test1.test2.google.com"
                       "test2.([a-z]*).([a-z]*).*",
                       "\\2.\\1"  // \\1 holds "google", \\2 holds "com"
                     )

日付関数

Chronicle は、以下の日付関連の関数をサポートしています。

  • timestamp.get_minute(unix_seconds [, time_zone])
  • timestamp.get_hour(unix_seconds [, time_zone])
  • timestamp.get_day_of_week(unix_seconds [, time_zone])
  • timestamp.get_week(unix_seconds [, time_zone])
  • timestamp.current_seconds()

Chronicle は、unix_seconds 引数として負の整数をサポートしています。負の整数は、Unix エポックより前の時刻を表します。無効な整数(オーバーフローする値など)を指定すると、-1 が返されます。これは珍しいシナリオです。

YARA-L 2 では負の整数リテラルがサポートされていないため、次の演算子(小さいまたは大きい)を使用してこの条件を確認してください。例:

0 > timestamp.get_hour(123)

時間抽出

[0, 59] の範囲の整数を返します。

timestamp.get_minute(unix_seconds [, time_zone])

次の関数は、時間を表す [0, 23] の範囲の整数を返します。

timestamp.get_hour(unix_seconds [, time_zone])

次の関数は、日曜日から始まる曜日を表す [1, 7] の範囲の整数を返します。たとえば、1 = 日曜日です。2 = 月曜日など

timestamp.get_day_of_week(unix_seconds [, time_zone])

次の関数は、年の週を表す範囲 [0, 53] の整数を返します。週は日曜日から始まります。その年の最初の日曜日より前の日付は、第 0 週になります。

timestamp.get_week(unix_seconds [, time_zone])

これらの時間抽出関数には同じ引数があります。

  • unix_seconds は、$e.metadata.event_timestamp.seconds などの Unix エポックからの経過秒数を表す整数か、その値を含むプレースホルダです。
  • time_zone は省略可能で、time_zone を表す文字列です。省略した場合、デフォルトは「GMT」です。文字列リテラルを使用してタイムゾーンを指定できます。次のオプションがあります。

この例では、time_zone 引数が省略されているため、デフォルトは「GMT」です。

$ts = $e.metadata.collected_timestamp.seconds

timestamp.get_hour($ts) = 15

次の例では、文字列リテラルを使用して time_zone を定義しています。

$ts = $e.metadata.collected_timestamp.seconds

2 = timestamp.get_day_of_week($ts, "America/Los_Angeles")

以下に、他の有効な time_zone 指定子の例を示します。これらは、時間抽出関数の 2 番目の引数として渡すことができます。

  • "America/Los_Angeles" または "-08:00""PST" はサポートされていません)
  • "America/New_York" または "-05:00""EST" はサポートされていません)
  • "Europe/London"
  • "UTC"
  • "GMT"

現在のタイムスタンプ

現在の時刻を Unix 秒数で表す整数を返します。これは検出のタイムスタンプとほぼ同じで、ルールが実行されるタイミングに基づきます。

timestamp.current_seconds()

次の例は、証明書が 24 時間を超えて期限切れになった場合に True を返します。現在の Unix 秒を減算して、より大きい演算子を使用して比較することで、時間差を計算します。

86400 < timestamp.current_seconds() - $e.network.tls.certificate.not_after

数学関数

絶対値。

整数式の絶対値を返します。

math.abs(intExpression)

この例では、最初に到達したイベントに関係なく、イベントの間隔が 5 分を超える場合は True を返します。ネスト関数を使用した例をご覧ください。

5 < timestamp.get_minute(
      math.abs($e1.metadata.event_timestamp.seconds
               - $e2.metadata.event_timestamp.seconds
      )
    )