Go言語でSIGMAのハンズオン(Software Designの記事)をやってみた

# はじめに

SIGMAはログ分析のためのシグネチャーフォーマットです。ログからサイバー攻撃の形跡を見つけるための検索方法を特定のSIEMなどの環境に依存しない形式で記述できるものです。

https://sigmahq.io

この解説が「Software Design」の2024年11月号

https://gihyo.jp/magazine/SD/archive/2024/202411

に掲載されていました。(P158)

この中で紹介されているハンズオンをGo言語で試してみた時の記録です。

# sigma-goパッケージ

Go言語でSIGMAを扱うパケージは２つ見つかりました。今回使ったのはsigma-go

https://github.com/bradleyjkemp/sigma-go

です。

# ハンズオン#1 Windows whoami検知

Windowsの環境で

```terminal
>whoami /priv
```
というコマンドを実行したことを検知するハンズオンです。
Aurora Liteという商用EDRの無償版で試していますが、これをGO言語のsigma-goで検知してみました。

## ログの準備

検知対象のログはWindowsの環境で簡単に作成できます。

### sysmonのインストール

まず、プロセスの起動、停止をイベントログに記録するため、sysmonをWindowsにインストールします。

https://learn.microsoft.com/ja-jp/sysinternals/downloads/sysmon

にダウンロード先やインストール方法が記載されいます。

### whoamiを実行

sysmonをインストールした後、Powweshellなどで

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874862/23ff9473-dca3-ad13-2ee6-80971fac46f3.png)

のように実行します。

###  ログの確認と保存

whoamiを実行した後、イベントビューアーでログを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874862/cad71f0a-cca2-dc4c-7e7b-b2a712afa74d.png)


ログは、Applications and Services Logs/Microsoft/Windows/Sysmon/Operational にあります。（赤の矢印）
whoamiを検索すれば、ログが見つかるはずです。見つけたら「すべてのログを名前をつけて保存」を実行します。

sysmon.evtxなどです。これが、対象のログです。

## SIGMAルール

ルールは、

https://github.com/SigmaHQ/sigma

のrulesのディレクトリからダウンロードできます。

```
rules/proc_creation_win_whoami_priv_discovery.yml
```

です。

このルールのままではsigma-goでエラーになったので少し変更しました。

```yaml
title: Security Privileges Enumeration Via Whoami.EXE
id: 97a80ec7-0e2f-4d05-9ef4-65760e634f6b
status: test
description: Detects a whoami.exe executed with the /priv command line flag instructing the tool to show all current user privileges. This is often used after a privilege escalation attempt.
references:
    - https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/whoami
author: Florian Roth (Nextron Systems)
date: 2021-05-05
modified: 2023-02-28
tags:
    - attack.privilege-escalation
    - attack.discovery
    - attack.t1033
logsource:
    category: process_creation
    product: windows
detection:
    selection_img:
        Image|endswith: '\whoami.exe'
    selection_cli:
        CommandLine|contains:
            - ' /priv'
            - ' -priv'
    condition: all of selection_*
falsepositives:
    - Unknown
level: high

```

変更したのは

```diff_yaml
detection:
    selection_img:
-        - Image|endswith: '\whoami.exe'
-        - OriginalFileName: 'whoami.exe'
+        Image|endswith: '\whoami.exe'
```

です。

:::note alert
-で始まる配列の中にキーバリュー（マップ）を書くとsigma-goが認識できないためです。ロジックを工夫すれば、同じ条件で書き換えできます。OriginalFileNameというのが不要なので削除しました。
:::

## 検知プログラム


```go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/0xrawsec/golang-evtx/evtx"

	"github.com/bradleyjkemp/sigma-go"
	"github.com/bradleyjkemp/sigma-go/evaluator"
)

func main() {
	//  ルールを読み込む
	c, err := os.ReadFile("./rules/proc_creation_win_whoami_priv_discovery.yml")
	if err != nil {
		log.Fatalln(err)
	}
	r, err := sigma.ParseRule(c)
	if err != nil {
		log.Fatalln(err)
	}
	ev := evaluator.ForRule(r, evaluator.WithConfig(sigma.Config{
		FieldMappings: map[string]sigma.FieldMapping{
			"Image":             {TargetNames: []string{"$.Event.EventData.Image"}},
			"CommandLine":       {TargetNames: []string{"$.Event.EventData.CommandLine"}},
			"ParentProcessName": {TargetNames: []string{"$.Event.EventData.ParentProcessName"}},
			"NewProcessName":    {TargetNames: []string{"$.Event.EventData.NewProcessName"}},
		},
	}))
	evtx.SetDebug(true)
	// イベントログを読み込む
	ef, err := evtx.OpenDirty("./testdata/sysmon.evtx")
	if err != nil {
		log.Fatalln(err)
	}
	for e := range ef.FastEvents() {
		if e == nil {
			break
		}
		// sigma-goで扱いやすいデータ形式に変換
		var je interface{}
		err := json.Unmarshal(evtx.ToJSON(e), &je)
		if err != nil {
			log.Fatalln(err)
		}
		r, err := ev.Matches(context.Background(), je)
		if err != nil {
			log.Fatalln(err)
		}
		if r.Match {
			// 見つけたログを出力
			fmt.Printf("%+v: %s\n", r, string(evtx.ToJSON(e)))
		}
	}
}

```

evtxを読むためには、

https://github.com/0xrawsec/golang-evtx

のパッケージを使っています。

フィールド名を変換するために

```go
	ev := evaluator.ForRule(r, evaluator.WithConfig(sigma.Config{
		FieldMappings: map[string]sigma.FieldMapping{
			"Image":             {TargetNames: []string{"$.Event.EventData.Image"}},
			"CommandLine":       {TargetNames: []string{"$.Event.EventData.CommandLine"}},
			"ParentProcessName": {TargetNames: []string{"$.Event.EventData.ParentProcessName"}},
			"NewProcessName":    {TargetNames: []string{"$.Event.EventData.NewProcessName"}},
		},
	}))
```
の部分でフィールドマッピングを設定しています。

:::note info
Imageは、JSONPATJでEvent.EventData.Imageのところにあるという意味で、階層化された
データでも扱えます。
:::


```go
		// sigma-goで扱いやすいデータ形式に変換
		var je interface{}
		err := json.Unmarshal(evtx.ToJSON(e), &je)
		if err != nil {
			log.Fatalln(err)
		}
		r, err := ev.Matches(context.Background(), je)
  ```

の部分は、データ形式を変換する裏技です。もっと良い方法があるかもしれませんが、
単純に型変換するとエラーになったので、この方法にしています。

## 実行結果

```terminal

$ go run main.go 
{Match:true SearchResults:map[selection_cli:true selection_img:true] ConditionResults:[true]}: {"Event":{"EventData":{"CommandLine":"\"C:\\WINDOWS\\system32\\whoami.exe\" /priv","Company":"Microsoft Corporation","CurrentDirectory":"C:\\Users\\ymi\\","Description":"whoami - displays logged on user information","FileVersion":"10.0.22621.1 (WinBuild.160101.0800)","Hashes":"SHA256=574BC2A2995FE2B1F732CCD39F2D99460ACE980AF29EFDF1EB0D3E888BE7D6F0","Image":"C:\\Windows\\System32\\whoami.exe","IntegrityLevel":"Medium","LogonGuid":"AC7A0DE1-15DD-670B-4D7D-BB0500000000","LogonId":"0x05bb7d4d","OriginalFileName":"whoami.exe","ParentCommandLine":"\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ","ParentImage":"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe","ParentProcessGuid":"AC7A0DE1-76E1-6715-6349-000000003100","ParentProcessId":"2388","ParentUser":"YMIRYZ\\ymi","ProcessGuid":"AC7A0DE1-2EA0-672B-1C15-070000003100","ProcessId":"4516","Product":"Microsoft® Windows® Operating System","RuleName":"-","TerminalSessionId":"2","User":"YMIRYZ\\ymi","UtcTime":"2024-11-06 08:53:52.063"},"System":{"Channel":"Microsoft-Windows-Sysmon/Operational","Computer":"YMIRYZ","Correlation":{},"EventID":"1","EventRecordID":"1739","Execution":{"ProcessID":"15836","ThreadID":"17760"},"Keywords":"0x8000000000000000","Level":"4","Opcode":"0","Provider":{"Guid":"5770385F-C22A-43E0-BF4C-06F5698FFBD9","Name":"Microsoft-Windows-Sysmon"},"Security":{"UserID":"S-1-5-18"},"Task":"1","TimeCreated":{"SystemTime":"2024-11-06T08:53:52.0892215Z"},"Version":"5"}}}

```

のように検知できました。


# ハンズオン#2 :攻撃ログの分析

記事ではWMIを悪用した攻撃をChainsawというツールで検知する方法を紹介していますが、Go言語のsigma-goで試してみました。

## ログの準備

こちらは、Windows環境で実際に再現するのは難しいので、

https://github.com/sans-blue-team/DeepBlueCLI

からダウンロードできます。

```
evtx/sliver-security.evtx
```

です。

### SIGMAルール

```yaml

title: Suspicius Cmd Execution via WMI
description: Detects suspicious command execution via Windows Management Instrumentation(WMI)
logsource:
  category: presess_creation
  product: windows
detection:
  selection1:
    ParentProcessName|contains: 'WmiPrvSE.exe'
  selection2:
    NewProcessName|contains: 
      - 'rundll32.exe'
      - 'msbuild.exe'
      - 'powershell.exe'
      - 'cmd.exe'
      - 'mshta.exe'
  condition: selection1 and selection2
level: critical

```

このルールもsigma-goの制約（配列の下は文字列）に合わせるため書き換えています。

## 検知コード

検知コードは、ハンズオン#1とまったく同じで、読み込むファイル名だけ変更しました。

```diff_go
func main() {
	//  ルールを読み込む
-	c, err := os.ReadFile("./rules/proc_creation_win_whoami_priv_discovery.yml")
+	c, err := os.ReadFile("./rules/hanson2.yml")
	if err != nil {
		log.Fatalln(err)
	}
|
  evtx.SetDebug(true)
	// イベントログを読み込む
-	ef, err := evtx.OpenDirty("./testdata/sysmon.evtx")
+	ef, err := evtx.OpenDirty("./testdata/sliver-security.evtx")
	if err != nil {

```

## 実行結果

```terminal
$ go run main.go h2
{Match:true SearchResults:map[selection1:true selection2:true] ConditionResults:[true]}: {"Event":{"EventData":{"CommandLine":"cmd.exe /Q /c cd \\ 1\u003e \\\\127.0.0.1\\ADMIN$\\__1684434503.6905713 2\u003e\u00261","MandatoryLabel":"S-1-16-12288","NewProcessId":"0x00002d80","NewProcessName":"C:\\Windows\\System32\\cmd.exe","ParentProcessName":"C:\\Windows\\System32\\wbem\\WmiPrvSE.exe","ProcessId":"0x00000e4c","SubjectDomainName":"GALACTICA","SubjectLogonId":"0x000003e4","SubjectUserName":"VALKYRIE$","SubjectUserSid":"S-1-5-20","TargetDomainName":"VALKYRIE","TargetLogonId":"0x03e2f5f3","TargetUserName":"fgaeta","TargetUserSid":"S-1-0-0","TokenElevationType":"%%1936"},"System":{"Channel":"Security","Computer":"VALKYRIE.galactica.cc","Correlation":{},"EventID":"4688","EventRecordID":"245081","Execution":{"ProcessID":"4","ThreadID":"5912"},"Keywords":"0x8020000000000000","Level":"0","Opcode":"0","Provider":{"Guid":"54849625-5478-4994-A5BA-3E3B0328C30D","Name":"Microsoft-Windows-Security-Auditing"},"Security":{},"Task":"13312","TimeCreated":{"SystemTime":"2023-05-18T18:28:24.1090439Z"},"Version":"2"}}}
```


検知できたようです。

# デモ#1: ネットワークログ(zeek)の分析

zeekのログから外部ネットワークとの通信を検知するデモです。記事ではElastic Stackを使っていますが、Go言語のsigma-goで試してみました。


## ログの準備

ログは、

https://github.com/brimdata/zed-sample-data

からダウンロードできます。
https://github.com/brimdata/zed-sample-data/blob/main/zeek-json/conn.json.gz
のファイルです。

## SIGMAルール

検知するためのルールは、

```yaml
title: Public Accesible IP Addaree
status: test
description: Detects connections from external IPs
logsource:
  product: zeek
detection:
  selection:
    id.orig_h|cidr:
      - '10.0.0.0/8'
      - '172.16.0.0/12'
      - '192.168.0.0/16'
  condition: not selection
level: high
```

です。

:::note warn
このcidrというキーは、IPアドレスの範囲を指定するものです。
sigma-goのソースコードを読んで対応しているのを確認しました。
他のsigmaパッケージは対応していませんでした。
:::

### 検知プログラム

```go
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/bradleyjkemp/sigma-go"
	"github.com/bradleyjkemp/sigma-go/evaluator"
)

func main() {
	c, err := os.ReadFile("./rules/zeek.yml")
	if err != nil {
		log.Fatalln(err)
	}
	r, err := sigma.ParseRule(c)
	if err != nil {
		log.Fatalln(err)
	}
	e := evaluator.ForRule(r)
	fp, err := os.Open("./testdata/conn.json")
	if err != nil {
		log.Fatalln(err)
	}
	defer fp.Close()
	scanner := bufio.NewScanner(fp)
	for scanner.Scan() {
		l := scanner.Text()
		var i map[string]interface{}
		err := json.Unmarshal([]byte(l), &i)
		if err != nil {
			log.Fatalln(err)
		}
		ret, err := e.Matches(context.Background(), i)
		if err != nil {
			log.Fatalln(err)
		}
		if ret.Match {
			log.Printf("ret=%v l=%s", ret, l)
		}
	}
}

```

evtxではなくJSONのログなので、１行ずつ読み込で処理しています。

## 実行結果

```terminal
$ go run main.go
2024/11/08 06:05:01 ret={true map[selection:false] [true]} l={"_path":"conn","_write_ts":"2018-03-24T17:18:11.352693Z","ts":"2018-03-24T17:17:02.350685Z","uid":"C02MQ23wnJMnNy7p3i","id.orig_h":"134.71.1.17","id.orig_p":3,"id.resp_h":"10.47.22.25","id.resp_p":1,"proto":"icmp","duration":8.9999520778656,"orig_bytes":112,"resp_bytes":0,"conn_state":"OTH","local_orig":false,"local_resp":true,"missed_bytes":0,"orig_pkts":4,"orig_ip_bytes":224,"resp_pkts":0,"resp_ip_bytes":0}

```

検知できました。

# 余談

sigma-goを使っていろいろ検知する方法を習得したので、このパッケージを使って

https://note.com/twsnmp/n/nd90978e728c0

や

https://note.com/twsnmp/n/na5c62f59833d

のTWSNMPシリーズにSIGMAによるログ検知機能をつけようと思っています。