Winlogbeat 7.2.0 新機能: ScriptプロセッサのモジュールをJavascriptで作ってみる

#はじめに
前記事の[「Winlogbeat 7.2.0の新機能: JavascriptプロセッサのSysmonモジュールを使ってイベントログを採取してみる」](https://qiita.com/rhpenguin/items/41d0dcba40ce713c7f23)の続編です。

今回の記事ではその[Javascriptプロセッサ](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/processor-script.html)機能を利用してWindowsイベントログをElasticsearchへ転送する前にWinlogbeat側（エンドポイント側）でフィルタまたは加工する独自モジュールをJavascriptで書いてみます。

* hello, worldをフィールドへ書き込むHelloWorldモジュール
* PowerShell実行イベントログのBase64エンコードされたコマンド部分(-encオプション)をデコードしてからElasticsearchへ転送するモジュール

※Javascriptプロセッサはまだ新しい機能ですので仕様変更も今後予想されます。詳細は[公式リファレンス](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/processor-script.html)を参照してください。

※本記事で扱うサンプルモジュールは[こちら](https://github.com/rhpenguin/Winlogbeat-javascript-processor-example)へ置いておきます。

#評価環境
![log_network_sysmon_winlogbeat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362125/6fded994-10a0-bc55-c583-a2cd41551ead.png)

#利用するソフトウェア
- OS: Ubuntu 18.04
- DNSサーバ: Dnsmasq (2.79)
- Elasticsearch (6.6.0) 
- Kibana (6.6.0)
- Winlogbeat (7.2.0)
- Sysmon (v10.2)
- VMware Workstation Player等のVMソフトウェア
- Windows 10 (クライアントPC)

インストール方法や評価環境の構築の詳細は[「標的型攻撃に対するJPCERT/CCのおすすめログ設定をElasticsearchで構築してみる - エンドポイントログ編（その２- Winlogbeat/Elasticsearch）」](https://qiita.com/rhpenguin/items/6dc4c3f82cd87122958c)または[前記事](https://qiita.com/rhpenguin/items/41d0dcba40ce713c7f23)をご参照ください。

#Hello Worldモジュール
まずはWindowsイベントログに「greeting: "hello, world!"」というフィールドを追加する単純なモジュールを書いてみましょう。以下のように非常にシンプルなコードを記述しファイル名を**winlogbeat-helloworld.js**として保存します。

※UTF-8で保存した方がいいと思います。

```javascript:winlogbeat-helloworld.js
function process(evt) {
    evt.Put("greeting","hello, world");
    return;
}
```

###HelloWorldモジュールを配置
この自作モジュールのJavascriptファイル(winlogbeat-helloworld.js)を他のSecurityやSysmon標準モジュールと同じフォルダ構成で以下のように配置します。

```text
C:\Program Files\winlogbeat-7.2.0-windows-x86_64\module\helloworld\config\winlogbeat-helloworld.js
```
「**C:\Program Files\winlogbeat-7.2.0-windows-x86_64**はWinlogbeat」のインストールディレクトリ、「**helloworld\config**」がモジュール用に作成したサブフォルダです。

このモジュールをWinlogbeatへ組み込んでイベントログをElasticsearchへ転送すると以下のように**greeting**フィールドが追加されています。

```json-doc:Elasticsearchへ加工されたイベントログ
...
{
    "_index" : "winlogbeat-7.2.0-2019.07.14",
    "_type" : "doc",
    "_id" : "klT27WsBgpTg8iKCzG_m",
    "_score" : 0.0,
    "_source" : {
         "@timestamp" : "2019-07-14T00:51:42.880Z",
         "message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 00:51:42.880\nProcessGuid: {22052e76-7c9e-5d2a-0000-00106f766e00}\nProcessId: 7720\nImage: C:\\Windows\\System32\\PING.EXE\nFileVersion: 10.0.17763.1 (WinBuild.160101.0800)\nDescription: TCP/IP Ping Command\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: ping.exe\nCommandLine: \"C:\\WINDOWS\\system32\\PING.EXE\" www.youtube.com\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxxxxxx\\xxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=741AD992403C78A8A7DBD97C74FDA06594A247E9E2FA05A40BB6945403A90056,IMPHASH=8C3BE1286CDAD6AC1136D0BB6C83FF41\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ",
...
         "greeting" : "hello, world",
...
    }
},
...
```

###自作モジュールを設定ファイルへ追加
Winlogbeatの設定ファイル（例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\winlogbeat.yml）へモジュール(winlogbeat-helloworld.js)を追加します。今回の例ではSysmonイベントに対してフィルタ・加工処理を行います。そのため**winlogbeat.event_logs**の「name: Microsoft-Windows-Sysmon/Operational」フィールド配下へscriptプロセッサとして指定します。

```yml:winlogbeat.yml
#======================= Winlogbeat specific options ===========================
...
winlogbeat.event_logs:
  - name: Application
    ignore_older: 72h

  - name: System
...
  - name: Microsoft-Windows-Sysmon/Operational
    processors:
      - script:
          lang: javascript
          id: sysmon
          file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js
      - script:
          lang: javascript
          id: helloworld
          file: ${path.home}/module/helloworld/config/winlogbeat-helloworld.js
...
```

###編集した設定ファイルをテスト
作成したモジュールと編集した設定内容をテストしておきます。

```text:
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
```
Elasticsearchへのネットワーク接続テストも行っておきましょう。

```text:
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test output -e -d "*"
```

###Winlogbeatを再起動

Winlogbeatサービスを再起動します。

```text:
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> Restart-Service winlogbeat
```

Elasticsearchへ転送されたイベントログに上述のように**greeting**フィールドが追加されます。

###HelloWorldモジュールの詳細

```javascript:winlogbeat-helloworld.js
function process(evt) {
    evt.Put("greeting","hello, world");
    return;
}
```

モジュール記述は上記のように簡単です。process関数を記述するだけでWinlogbeatがイベントログ毎に呼び出してくれます。

各Windowsイベントログは元のXML形式からJSON形式へ変換された後で、process関数の引数へEventオブジェクト(**evt**引数)として一つずつ渡されます。Eventオブジェクトにはイベントログを操作するためのメソッドが用意されており、フィルタや加工処理に量できます。例えばHelloWorldモジュールではこのEventオブジェクトのメソッドである**evt.Put("フィールド名","フィールド値")**と呼び出すことでイベントログ内へJSONフィールドを挿入追加しています。

※Eventオブジェクトの実体は[こちら](https://github.com/elastic/beats/blob/master/libbeat/processors/script/javascript/beatevent_v0.go)です。

##Eventオブジェクトのメソッド
[リファレンス](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/processor-script.html)によれば以下のメソッドがEventオブジェクトへ定義されています。

|メソッド&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|説明|
|---|---|
|Get("フィールド名")|イベントログ(JSON)のフィールドを取得。戻り値はスカラー、オブジェクトまたはnull(見つからなかった場合)。取得するフィールド名を指定しない場合にはイベントログ全体がオブジェクトとして取得されます。|
|Put("フィールド名", "フィールド値")|イベントログ(Json)へフィールドを追加。すでにフィールドが存在する場合には値が上書きされます。追加されるフィールド値としてスカラー以外に設定可能なものについては後ほど検証してみます|
|AppendTo("フィールド名", "フィールド値")|配列形式のフィールドへ要素を追加します。ただし追加できるのは文字列値だけです。|
|Rename("旧フィールド名", "新フィールド名")|フィールド名をリネーム。|
|Delete("フィールド名")|フィールドを削除。|
|Cancel()|イベントログ全体を削除|
|Tag("タグ値")|タグフィールドへ（設定されていなければ）追加。|

またイベントログ(Json)の各フィールドへはメソッドを利用しなくても**<イベントオブジェクト>.fields.<フィールド名>**という形式で直接アクセスすることもできます。

```javascript:helloworld.js
function process(evt) {
    // イベントオブジェクトのフィールドへ直接アクセス
    evt.fields.greeting = "hello, world";
    return;
}
```

#モジュールのデバッグ方法

###テストコードを記述

モジュールのJavascriptコード内に**test()**関数をテストプログラムとして記述することで、デバッグやテストを行うことができます。

```javascript:winlogbeat-helloworld.js
// 実装したコード
function process(evt) {
    evt.Put("greeting","hello, world");
    return;
}

// テスト内容を記述する関数
function test() {
    //テストデータ（イベントログ）を生成
    var evt = new Event({
        message: "Windows event log message...",
    });

    // テストデータで実装コードをテスト
    process(evt);

    // テスト期待値の判定
    if (evt.Get("greeting") === "goodbye cruel world") {
        // 期待値と異なる場合には例外を投げる
        throw "expected greeting === hello, world";
    }
}
```

この**test()**関数はWinlogbeatがモジュールをローディングするときに実行されます。そのため通常の設定ファイルテスト手順と同様に、以下のように**test config**を指定してwinlogbeat.exeを実行することでデバッグやテストを行うことができます。

```text:テストOKの場合
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
```

では以下のように**greeting**へ「goodbye cruel world」と代わりに出力するようにしてみましょう。

```javascript:winlogbeat-helloworld.js
// 実装したコード
function process(evt) {
    // hello, worldを別の値にしてみる
    evt.Put("greeting","goodbye cruel world");
    return;
}

// テスト内容を記述する関数
function test() {
    //Javascriptのオブジェクト型でテストデータ（イベントログ/JSON）を生成
    var evt = new Event({
        message: "Windows event log message...",
    });

    // テストデータで実装コードをテスト
    process(evt);

    // テスト期待値の判定
    if (evt.Get("greeting") === "goodbye cruel world") {
        // 期待値と異なる場合には例外を投げる
        throw "expected greeting === hello, world";
    }
}
```

以下のように「failed in test() function: expected greeting === hello, world at test」と例外が投げられエラーとなります。

```text:テストNGの場合
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Exiting: Failed to create new event log. 
failed in processor.javascript: failed in test() 
function: expected greeting === hello, world at test 
(C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js:12:15(22))
```

ちなみにこのテストエラーのままでは以下のようにWinlogbeatサービスは起動しません。

```text
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> Start-Service winlogbeat
Start-Service : 次のエラーのため、サービス 'winlogbeat (winlogbeat)' を開始できません: コンピューター '.' でサービス 'w
inlogbeat' を開始できません。
発生場所 行:1 文字:1
+ Start-Service winlogbeat
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.ServiceProcess.ServiceController:ServiceController) [Start-Service],
   ServiceCommandException
    + FullyQualifiedErrorId : CouldNotStartService,Microsoft.PowerShell.Commands.StartServiceCommand
```
PowerShellのStart-Serviceコマンドレットはエラーを返します。

###未定義またはgojaで未サポートのJavascript API呼び出し
WinlogbeatのJavascriptプロセッサのエンジンは[goja](https://github.com/dop251/goja)です。そのため通常のWebブラウザでサポートされているJavascriptのAPIが利用可能とは限らない点に注意が必要です。
※gojaはECMAScript 5.1ベース。

以下は未定義の関数hoge()を呼び出した例です。以下のようにエラー(「hoge is not defined at process」)となります。

```javascript:winlogbeat-helloworld.js
function process(evt) {
    evt.Put("greeting","hello, world");
    // 未定義またはgoja未サポートのAPI呼び出し
    hoge();
    return;
}

function test() {
    var evt = new Event({
        message: "Windows event log message...",
    });
    process(evt);
    if (evt.Get("greeting") === "goodbye cruel world") {
        throw "expected greeting === hello, world";
    }
}
```

```text:テスト実行結果
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Exiting: Failed to create new event log. 
failed in processor.javascript: 
failed in test() function: ReferenceError: hoge is not defined at process 
(C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js:3:9(10))
```

ただし、**test()**関数の延長でその未定義関数呼び出しのコード部分が実行されない場合には、エラーとはならないので注意が必要です。

```javascript:winlogbeat-helloworld.js
function process(evt) {
    evt.Put("greeting","hello, world");
    // 未定義関数呼び出し部分が実行されない場合
    if(false){
        hoge();
    }
    return;
}

function test() {
    var evt = new Event({
        message: "Windows event log message...",
    });
    process(evt);
    if (evt.Get("greeting") === "goodbye cruel world") {
        throw "expected greeting === hello, world";
    }
}
```

```text:テストOKになる
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
```

###Winlogbeatサービスへのモジュールのローディングを確認する
Winlogbeatの設定ファイルでログレベルをdebugへ変更することでモジュールのローディングを確認することができます。

```yml:winlogbeat.yml
...
#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: error, warning, info, debug
# 以下をコメントアウト
logging.level: debug
...
```

Winlogbeat(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\logs\winlogbeat)のログへ以下のように出力されます。

```text:Winlogbeatのログ
2019-07-14T12:15:25.733+0900 DEBUG [processors] processors/processor.go:93	
Generated new processors: 
script=[type=javascript, id=, sources=C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/sysmon/config/winlogbeat-sysmon.js], 
script=[type=javascript, id=, sources=C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js]
```
winlogbeat-helloworld.jsがScriptプロセッサのモジュールとして認識されています。


###console.log()関数でデバッグ情報などをログへ書き出す

[consoleモジュール](https://github.com/elastic/beats/tree/master/libbeat/processors/script/javascript/module/console)をローディングすることで、Javascriptでおなじみ**console.log()**関数も使えます。ただし、出力先はWinlogbeatのイベントログファイルになります。

####console.xxx()の記述例:
* console.debug("DEBUG: hello, world!");
* console.log("INFO: hello, world!");
* console.info("INFO: hello, world! %j", evt.fields);
* console.warn("WARN: [%s]", evt.fields.message);
* console.error("ERROR: %j", evt.fields);

以下はconsole.log()関数のサンプルプログラムです。

```javascript:winlogbeat-helloworld.js
// consoleモジュールをローディング
var console = require("console");

function process(evt) {
    // Debugログでイベント（オブジェクト）全体を出力
    console.debug("process() called %j", evt.fields);
    evt.Put("greeting","goodbye cruel world");
    return;
}

function test() {
    var evt = new Event({
        message: "Windows event log message...",
        winlog : {
            provider_name : "Microsoft-Windows-Sysmon"
        }
    });
    process(evt);
    if (evt.Get("greeting") === "goodbye cruel world") {
        // Errorログを出力
        console.error("test() NG! %s",evt.fields.greeting);
    }else{
        console.log("test() OK!");
    }
    console.info("test() Done");
}
```

こちらもWinlogbeat(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\logs\winlogbeat)のログへ以下のように出力されます。

```text:Winlogbeatログ
...
2019-07-14T14:18:45.023+0900 DEBUG [processor.javascript] 
console/console.go:48	process() called {"message":"Windows event log message...","winlog":{"provider_name":"Microsoft-Windows-Sysmon"}}
2019-07-14T14:18:45.023+0900 ERROR [processor.javascript]
console/console.go:54	test() NG! goodbye cruel world
2019-07-14T14:18:45.023+0900 INFO [processor.javascript]
console/console.go:50	test() Done
...
```

#イベントログをフィルタや加工するパイプラインを作成

[processorモジュール](https://github.com/elastic/beats/blob/master/libbeat/processors/script/javascript/module/processor/processor.go)を利用すると複数の関数で定義された処理を数珠つなぎにして、イベントログのフィルタや加工処理をパイプライン化することができます。

```javascript:winlogbeat-helloworld.js

var console = require("console");
// processorモジュールをローディング
var processor = require("processor");

function process(evt) {

    // 処理１: helloメッセージをイベントログへ追加
    var addHello = function(evt) {
        evt.Put("hello","hello, world");
        console.debug("addHello() OK");
    }

    // 処理２: goodbyeメッセージをイベントログへ追加
    var addGoodbye = function(evt) {
        evt.Put("goodbye","goodbye cruel world");
        console.debug("addGoodbye () OK");
    }

    // processorモジュールのChain()メソッドでパイプラインを生成
    var pipeline = new processor.Chain()
        .Add(addHello)   // 処理１をパイプラインへ追加
        .Add(addGoodbye) // 処理２をパイプラインへ追加
        .Build()         // パイプラインを組み立て

    // パイプラインを実行
    pipeline.Run(evt);
    return;
}

function test() {
    var evt = new Event({
        message: "Windows event log message...",
        winlog : {
            provider_name : "Microsoft-Windows-Sysmon"
        }
    });
    process(evt);
    if (evt.Get("hello") !== "hello, world") {
        throw "expected goodbye !== hello, world";
    }
    if (evt.Get("goodbye") !== "goodbye cruel world") {
        throw "expected goodbye !== goodbye cruel world";
    }
    console.debug("test() Done");
}
```

パイプラインで処理１と処理２を実行し加工されたイベントログは以下のようになります。helloとgoodbyeのフィールドが追加されています。

```json-doc:パイプラインによる処理結果
      {
        "_index" : "winlogbeat-7.2.0-2019.07.14",
        "_type" : "doc",
        "_id" : "YFQS72sBgpTg8iKCw3VG",
        "_score" : 0.0,
        "_source" : {
          "@timestamp" : "2019-07-14T06:01:51.413Z",
          "hello" : "hello, world",
          "goodbye" : "goodbye cruel world",
...
          "message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 06:01:51.413\nProcessGuid: {22052e76-c54f-5d2a-0000-001031f3a900}\nProcessId: 4132\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" -c \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.yml\" -path.home \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\" -path.data \"C:\\ProgramData\\winlogbeat\" -path.logs \"C:\\ProgramData\\winlogbeat\\logs\"\nCurrentDirectory: C:\\WINDOWS\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {22052e76-11b2-5d2a-0000-0020e7030000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-11b2-5d2a-0000-00102cae0000}\nParentProcessId: 632\nParentImage: C:\\Windows\\System32\\services.exe\nParentCommandLine: C:\\WINDOWS\\system32\\services.exe",
...
}
```

###他のプロセッサをパイプラインへつなぐ
Winlogbeatの[標準プロセッサ](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/filtering-and-enhancing-data.html)をこのパイプラインへつなぐことももちろんできます。

例えば以下の例では[Convertプロセッサ](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/convert.html)を利用してフィールド名を大文字の文字列値へ変更しています。

```javascript

var console = require("console");
// processorモジュールをローディング
var processor = require("processor");

function process(evt) {
    var addHello = function(evt) {
        evt.Put("hello","hello, world");
    }

    var addGoodbye = function(evt) {
        evt.Put("goodbye","goodbye cruel world");
    }

    var pipeline = new processor.Chain()
        .Add(addHello)
        .Add(addGoodbye)
        .Convert({ // Convertプロセッサをつなげる
            fields: [
                {from: "hello", to: "HELLO"}, // フィールド名を大文字の値へ変更
                {from: "goodbye", to: "GOODBYE"}
            ],
            mode: "rename",
            ignore_missing: true,
            fail_on_error: false,
        })
        .Build()

    pipeline.Run(evt);
    return;
}

function test() {
    var evt = new Event({
        message: "Windows event log message...",
        winlog : {
            provider_name : "Microsoft-Windows-Sysmon"
        }
    });
    process(evt);
    if (evt.Get("HELLO") !== "hello, world") {
        throw "expected goodbye !== hello, world";
    }
    if (evt.Get("GOODBYE") !== "goodbye cruel world") {
        throw "expected goodbye !== goodbye cruel world";
    }
}
```

```json-doc
 {
   "_index" : "winlogbeat-7.2.0-2019.07.14",
   "_type" : "doc",
   "_id" : "cFQ472sBgpTg8iKC8nZ3",
   "_score" : 0.0,
   "_source" : {
      "@timestamp" : "2019-07-14T06:42:59.844Z",
      "message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 06:42:59.844\nProcessGuid: {22052e76-cef3-5d2a-0000-001054bbb200}\nProcessId: 2156\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" test config\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxxxxxx\\xxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ",
...
      "HELLO" : "hello, world",
      "GOODBYE" : "goodbye cruel world",
...
    }
 },
```

ちなみに標準のSysmonモジュールでもこのConvertプロセッサをパイプラインにつなげてWindowsイベントログのフィールド名を[Elastic Common Schema (ECS)](https://www.elastic.co/jp/blog/introducing-the-elastic-common-schema)へ変更するのに利用しています。

なお利用できる標準プロセッサの最新情報やラベル名は[こちら](https://github.com/elastic/beats/blob/master/libbeat/processors/script/javascript/module/processor/processor.go)の**constructors**定義を見るとわかります（次期Winlogbeat 7.3.0ではBase64デコードプロセッサがサポートされそう？？？ UTF-8だけでなくUTF-16leの場合などもサポートされると嬉しいですね・・・笑）。

#その他の標準モジュール

consoleモジュールとprocessorモジュールについてご紹介しましたが、他にもイベントログ解析に便利なモジュールが提供されています。

###pathモジュール
 [pathモジュール](https://github.com/elastic/beats/tree/master/libbeat/processors/script/javascript/module/path)はWindowsファイルパスをパーシングしてくれるモジュールです。

|メソッド&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|説明|
|---|---|
|basename|ファイルパスからベース名（例：Exeファイル名）を取り出す|
|dirname|ファイルパスからフォルダ名を取り出す|
|extname|ファイル拡張子を取り出す|
|isAbsolute|絶対パスかどうかを判定|
|normalize|パスを正規化。例: 「C:\\Windows\\system32\\..\\system32\\system32.dll」を「C:\Windows\system32\system32.dll」へ変換|

###winlogbeatモジュール

|メソッド&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|説明|
|---|---|
|splitCommandLine|コマンドライン文字列をMicrosoft社から提供されている[CommandLineToArgvW関数](https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw)でパースおよび分割し配列化|

#いろいろな値型でフィールドへ追加してみる

[Eventオブジェクト](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/processor-script.html)の**Put()**メソッドでいろいろな形式で値を追加してみました。

```javascript:winlogbeat-helloworld.js
var console = require("console");
var processor = require("processor");

function process(evt) {

  var putValueByMethod = function(evt) {

    var numberValue = 1;
    evt.Put("numberValue", numberValue);

    var stringValue = "hello, world";
    evt.Put("stringValue", stringValue);

    var boolValue = false
    evt.Put("boolValue", boolValue);

    var nullValue = null
    evt.Put("nullValue", nullValue);

    var undefinedValue = undefined
    evt.Put("undefinedValue", undefinedValue);

    var arrayValue = ["element1", "element2", "element3"];
    evt.Put("arrayValue", arrayValue);

    var objectValue = {
      key1: "value1",
      key21: "value2"
    };
    evt.Put("objectValue", objectValue);

    var objectArrayValue = [{
      KEY1: "VALUE1",
      KEY2: "VALUE2"
    }, {
      KEY3: "VALUE3",
      KEY4: "VALUE4"
    }]
    evt.Put("objectArrayValue", objectArrayValue);

    var objectObjectValue = {
      Key1: {
        Key2: "Value2",
        Key3: 1,
        Key4: true,
        Key5: [1, 2, 3, 4]
      }
    }
    evt.Put("objectObjectValue", objectObjectValue);
  }

  var putValueToFields = function(evt) {
    var objectObjectValueToFields = {
      Key1: {
        Key2: "ばりゅー１",
        Key3: 1,
        Key4: true,
        Key5: [1, 2, 3, 4]
      }
    }
    evt.fields.objectObjectValueToFields = objectObjectValueToFields;
  }

  var pipeline = new processor.Chain()
    .Add(putValueByMethod)
    .Add(putValueToFields)
    .Build()

  pipeline.Run(evt);
  return;
}

function test() {
  var evt = new Event({});
  process(evt);
  console.log("evt.fields: %j", evt.fields);
}
```

Elasticsearchへ格納された形式は以下です。特に問題なさそうですね！

```json-doc
{
  "_index" : "winlogbeat-7.2.0-2019.07.14",
  "_type" : "doc",
  "_id" : "HVSD72sBgpTg8iKCsXnz",
  "_score" : 0.0,
  "_source" : {
    "@timestamp" : "2019-07-14T08:05:09.682Z",
...
    "stringValue" : "hello, world",
    "arrayValue" : [
      "element1",
      "element2",
      "element3"
    ],
    "nullValue" : null,
    "undefinedValue" : null,
    "objectArrayValue" : [
      {
        "KEY1" : "VALUE1",
        "KEY2" : "VALUE2"
      },
      {
        "KEY4" : "VALUE4",
        "KEY3" : "VALUE3"
      }
    ],
    "objectObjectValueToFields" : {
      "Key1" : {
        "Key2" : "ばりゅー１",
        "Key3" : 1,
        "Key4" : true,
        "Key5" : [
          1,
          2,
          3,
          4
        ]
      }
    },
    "objectObjectValue" : {
      "Key1" : {
        "Key2" : "Value2",
        "Key3" : 1,
        "Key4" : true,
        "Key5" : [
          1,
          2,
          3,
          4
        ]
      }
    },
    "numberValue" : 1,
    "boolValue" : false,
    "objectValue" : {
      "key1" : "value1",
      "key21" : "value2"
    },
    "message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 08:05:09.682\nProcessGuid: {22052e76-e235-5d2a-0000-00109799d600}\nProcessId: 5372\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" -c \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.yml\" -path.home \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\" -path.data \"C:\\ProgramData\\winlogbeat\" -path.logs \"C:\\ProgramData\\winlogbeat\\logs\"\nCurrentDirectory: C:\\WINDOWS\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {22052e76-11b2-5d2a-0000-0020e7030000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-11b2-5d2a-0000-00102cae0000}\nParentProcessId: 632\nParentImage: C:\\Windows\\System32\\services.exe\nParentCommandLine: C:\\WINDOWS\\system32\\services.exe",
...
  }
},
```

#サンプルモジュールを書いてみる
HelloWorldよりも少し実用的？なサンプルモジュールを書いてみます（笑）

###PowerShellのBase64エンコードされたコマンドのデコードモジュール

PowerShellにはEncodedCommandオプション(-EncodedCommandまたは-enc)を利用してコマンド列をBase64文字列としてパッキングしてくれる機能があります。しかしこの形式でPowerShellのコマンドラインがSysmonログに含まれると分析しづらい時があります。そこでPowerShell.exeのオプションに-encオプションが指定された場合、そのオプション値のBase64文字列をデコードしてからElasticsearchへ転送するモジュールを作成してみます。

###Base64エンコードされたコマンド例

```text:エンコード前
powershell.exe -executionpolicy bypass Resolve-DnsName -Name www.youtube.com
```

```text:エンコード後
powershell.exe -executionpolicy bypass -enc "UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
```
複数のコマンドや引数からなる「Resolve-DnsName -Name www\.youtube\.com」の部分がBase64エンコードされて「UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA=」と一かたまりにパッキングされています。

###Base64デコードライブラリ
Base64デコードプロセッサは（Winlogbeatの[開発リポジトリ](https://github.com/elastic/beats/pull/11914)をのぞくと近い将来もしかしたら標準提供されるかもしれませんが）7.2には含まれていないため、Base64デコードライブラリとして[js-base64](https://github.com/dankogai/js-base64)を利用します。ただしES6のimport文が使えないのでモジュールソースコードへそのまま組み込みます（泣）。

###サンプルソース
サンプルソースは以下の通りです。js-base64の部分は省略してありますが、HelloWorldモジュールを含めて[こちら](https://github.com/rhpenguin/Winlogbeat-javascript-procesessor-example)へあげておきます。

```javascript:winlogbeat-psdecode.js
...
function process(evt) {
    // prosessorモジュールをローディング
    var processor = require("processor");
    // winlogbeatモジュールをローディング
    var winlogbeat = require("winlogbeat");

    // パイプライン処理１：-encオプション値があればBase64デコード
    var decodePsCode = function(evt) {
      var processName = evt.Get("process.name");
      var args = evt.Get("process.args");
      if ( !processName || processName.length < 1 || 
           !args || args.length < 3 || 
           processName.toLowerCase() !== "powershell.exe" ) {
          return;
      }
      for (var i = 0; i < args.length; i++) {  
          if (args[i].toLowerCase().indexOf("-enc") != -1 && i < (args.length - 1)) {
              var decodedArg = Base64.decode(args[i+1]);
              if( decodedArg && decodedArg.length > 0 ){
                evt.Put("winlog.decodedPsCode",decodedArg.replace(/\u0000/gi,'')); //ズルしてる・・・
                break;
              }
          }
      }
    }

    // パイプライン処理２：Base64デコードされたコマンドをパースして配列へ格納
    var splitPsCode = function(evt) {
        var psArgs = evt.Get("winlog.decodedPsCode");
        if (!psArgs) {
            return;
        }
        evt.Put("winlog.decodedPsCodeArgs", winlogbeat.splitCommandLine(psArgs));
    }

    // パイプラインを作成
    var pipeline = new processor.Chain()
        .Add(decodePsCode) // パイプライン処理１を登録
        .Add(splitPsCode)  // パイプライン処理２を登録
        .Build()

    // パイプライン処理を実行
    pipeline.Run(evt);
    return;
}

// テスト関数
function test() {
    var console = require("console");

    var evt = new Event({
      process : {
        name : "powershell.exe",
        args : [
          "powershell.exe",
          "-executionpolicy",
          "bypass",
          "-enc",
          "UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
        ]
      },
      winlog: {
        event_id: 1
      }
    });
    console.debug("evt: %j",evt.fields);

    process(evt);
    console.debug("evt: %s",evt.Get("winlog.decodedPsCode"));
    console.debug("evt: %j",evt.Get("winlog.decodedPsCodeArgs"));

    if (evt.Get("winlog.decodedPsCode") !== "Resolve-DnsName -Name www.youtube.com") {
        throw "expected winlog.decodedPsCode === Resolve-DnsName -Name www.youtube.com";
    }

    console.debug("test() Done.");
}
```

###自作モジュールを設定ファイルへ追加
Winlogbeatの設定ファイル（例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\winlogbeat.yml）へモジュール(winlogbeat-helloworld.js)を追加します。今回はSysmonイベントに対してフィルタ・加工処理を行うため**winlogbeat.event_logs**の「name: Microsoft-Windows-Sysmon/Operational」フィールド配下へscriptプロセッサとして追加します。

```yml:winlogbeat.yml
#======================= Winlogbeat specific options ===========================
...
winlogbeat.event_logs:
  - name: Application
    ignore_older: 72h

  - name: System
...
  - name: Microsoft-Windows-Sysmon/Operational
    processors:
      - script:
          lang: javascript
          id: sysmon
          file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js
      - script:
          lang: javascript
          id: psdecode
          file: ${path.home}/module/psdecode/config/winlogbeat-psdecode.js
...
```

作成したモジュールと編集した設定内容をテストしておきましょう。

```text:
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
```

###実行結果
実行した結果は以下の通りです。**winlog.decodedPsCode**フィールドにBase64デコードした結果、そして**winlog.decodedPsCodeArgs**フィールドにそれをパースした結果が格納されています。

```json-doc:PowerShellのBase64コマンドのデコード結果(抜粋)
...
{
  "_index" : "winlogbeat-7.2.0-2019.07.14",
  "_type" : "doc",
  "_id" : "eVTF72sBgpTg8iKCSXqf",
  "_score" : 0.0,
  "_source" : {
    "@timestamp" : "2019-07-14T09:16:49.667Z",
...
    "winlog" : {
...
      "decodedPsCode" : "Resolve-DnsName -Name www.youtube.com",
      "decodedPsCodeArgs" : [
        "Resolve-DnsName",
        "-Name",
        "www.youtube.com"
      ],
...
    },
...
    "process" : {
      "pid" : 4824,
      "executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
      "args" : [
        "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "-executionpolicy",
        "bypass",
        "-enc",
        "UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
      ],
...
},
```

```json-doc:PowerShellのBase64コマンドのデコード結果(イベントログ全体)
...
{
  "_index" : "winlogbeat-7.2.0-2019.07.14",
  "_type" : "doc",
  "_id" : "eVTF72sBgpTg8iKCSXqf",
  "_score" : 0.0,
  "_source" : {
    "@timestamp" : "2019-07-14T09:16:49.667Z",
    "user" : {
      "domain" : "xxxxx",
      "name" : "xxxxx"
    },
    "host" : {
      "os" : {
        "family" : "windows",
        "name" : "Windows 10 Pro",
        "kernel" : "10.0.17763.615 (WinBuild.160101.0800)",
        "build" : "17763.615",
        "platform" : "windows",
        "version" : "10.0"
      },
      "id" : "22052e76-721a-4007-86f6-6346e89d0c86",
      "hostname" : "xxxxx",
      "architecture" : "x86_64",
      "name" : "xxxxx"
    },
    "agent" : {
      "ephemeral_id" : "d34dbafc-8002-4bcc-8f51-8e1911fcd0f0",
      "hostname" : "xxxxx",
      "id" : "d4bdf3a8-1d7e-4c36-a259-2a2451b56656",
      "version" : "7.2.0",
      "type" : "winlogbeat"
    },
    "winlog" : {
      "process" : {
        "thread" : {
          "id" : 3908
        },
        "pid" : 2720
      },
      "record_id" : 1744334,
      "channel" : "Microsoft-Windows-Sysmon/Operational",
      "event_data" : {
        "FileVersion" : "10.0.17763.1 (WinBuild.160101.0800)",
        "LogonId" : "0xa8970",
        "TerminalSessionId" : "1",
        "Description" : "Windows PowerShell",
        "IntegrityLevel" : "High",
        "Product" : "Microsoft® Windows® Operating System",
        "OriginalFileName" : "PowerShell.EXE",
        "Company" : "Microsoft Corporation",
        "LogonGuid" : "{22052e76-11ef-5d2a-0000-002070890a00}"
      },
      "provider_name" : "Microsoft-Windows-Sysmon",
      "task" : "Process Create (rule: ProcessCreate)",
      "api" : "wineventlog",
      "decodedPsCode" : "Resolve-DnsName -Name www.youtube.com",
      "decodedPsCodeArgs" : [
        "Resolve-DnsName",
        "-Name",
        "www.youtube.com"
      ],
      "provider_guid" : "{5770385f-c22a-43e0-bf4c-06f5698ffbd9}",
      "opcode" : "情報",
      "computer_name" : "xxxxx",
      "version" : 5,
      "event_id" : 1,
      "user" : {
        "identifier" : "S-1-5-18",
        "name" : "SYSTEM",
        "domain" : "NT AUTHORITY",
        "type" : "User"
      }
    },
    "process" : {
      "entity_id" : "{22052e76-f301-5d2a-0000-00102a5ce600}",
      "pid" : 4824,
      "executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
      "args" : [
        "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "-executionpolicy",
        "bypass",
        "-enc",
        "UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
      ],
      "working_directory" : "C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\",
      "parent" : {
        "entity_id" : "{22052e76-670e-5d2a-0000-001062cf5900}",
        "pid" : 5152,
        "executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "args" : [
          "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
        ],
        "name" : "powershell.exe"
      },
      "name" : "powershell.exe"
    },
    "event" : {
      "created" : "2019-07-14T09:16:51.554Z",
      "kind" : "event",
      "code" : 1,
      "action" : "Process Create (rule: ProcessCreate)"
    },
    "hash" : {
      "sha256" : "de96a6e69944335375dc1ac238336066889d9ffc7d73628ef4fe1b1b160ab32c",
      "imphash" : "741776aaccfc5b71ff59832dcdcace0f"
    },
    "ecs" : {
      "version" : "1.0.0"
    },
    "log" : {
      "level" : "情報"
    },
    "message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 09:16:49.667\nProcessGuid: {22052e76-f301-5d2a-0000-00102a5ce600}\nProcessId: 4824\nImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nFileVersion: 10.0.17763.1 (WinBuild.160101.0800)\nDescription: Windows PowerShell\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: PowerShell.EXE\nCommandLine: \"C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -executionpolicy bypass -enc UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA=\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxx\\xxxxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=DE96A6E69944335375DC1AC238336066889D9FFC7D73628EF4FE1B1B160AB32C,IMPHASH=741776AACCFC5B71FF59832DCDCACE0F\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" "
  }
},
...
```

#まとめ

Winlogbeat 7.2.0の新機能であるJavascriptプロセッサ機能を使って独自モジュールを書いてみました。様々な処理をパイプライン化するのも容易であり、応用範囲が広がりそうですね。何よりも設定をJavascriptの構文で柔軟にサクッと書けるので非常にお手軽で便利です。

※Javascriptプロセッサはまだ新しい機能ですので仕様変更も今後予想されます。詳細は[公式リファレンス](https://www.elastic.co/guide/en/beats/winlogbeat/7.2/processor-script.html)を参照してください。