.evtxファイルをPythonで解析する

弁護士が裁判で労働時間を立証する際に使う…かどうかはともかくとして，Windowsのシステムログを記録したEVTX (Windows XML Event Log)ファイルを，WindowsのEvent Viewerアプリケーション以外で処理したい場面があろうかと思います。
[Python-Evtx](https://github.com/williballenthin/python-evtx) というPythonのライブラリがあるので，試してみましょう。

```sh
pip install python-evtx
```
なお，Python-Evtxのドキュメントはほとんど存在しない状況ですが，プロジェクトページの[scriptディレクトリ](https://github.com/williballenthin/python-evtx/tree/master/scripts)にサンプルスクリプトがあります。一番簡単なのは`evtx_dump.py`です。閑話休題。

# Python-Evtx導入
`evtx.Evtx(filepath)`すると，イベントログを扱うオブジェクトが得られます。
「withと一緒に使え」との指示があるので，どうやら処理は基本的に，`with evtx.Evtx(filepath) as log:`のwithブロック内に書くことになるっぽいです。
任意のレコードを取得したい場合は，`log.get_record(number)`あたりで取得することになりますが，全レコードを走査することが多いでしょうから，以下はそういう感じにします。

```python:test.py
import Evtx.Evtx as evtx

def main():
    # python test.py hoge.evtx で，hoge.evtxに処理をする。evtx_dump.pyのコピペ。
    import argparse    
    parser = argparse.ArgumentParser(
        description="Dump a binary EVTX file into XML.")
    parser.add_argument("evtx", type=str,
                        help="Path to the Windows EVTX event log file")
    args = parser.parse_args()

    # ここから本題
    with evtx.Evtx(args.evtx) as log:
        for record in log.records():
            # 個々のrecordに対する処理をする。

if __name__ == "__main__":
    main()
```
# 個々のレコードをXMLとして処理する。
EVTXはバイナリ形式らしいのですが，**個々のレコードは**`.lxml()`関数で，XMLを扱う[lxmlライブラリ](https://lxml.de)のElementオブジェクトとして取り出すことができます。（lxmlがインストールされていない方は，`pip install lxml`して下さい。）

```python
with evtx.Evtx(args.evtx) as log:
    #テスト段階では↓の方が良いかも。
#    elm = log.get_record(1).lxml()
#    print(elm)
    for record in log.records():
        elm = record.lxml()
        print(elm.tag)
# -> "<Element {http://schemas.microsoft.com/win/2004/08/events/event}Event at ID的な文字列>"がバーっと。
```
`print(record.xml())`してみると分かるのですが，個々のrecordは次のような構造を持っているようです。それぞれの内容については，Windowsのシステムログに詳しい人に聞いてみてください。（私は素人ですので…）

- Event
    - System
        - Provider
        - EventID #たぶんWindowsにおけるイベントID
        - Version
        - Level
        - Task
        - Opcode
        - Keywords
        - TimeCreated #たぶんログが出力された日時
        - EventRecordID
        - Correlation
        - Execution
        - Channel
        - Computer
        - Security
    - EventData
        - Data[@Name=SubjectUserSid]
        - Data[@Name=SubjectUserName]
        - Data[@Name=SubjectDomainName]
        - Data[@Name=SubjectLogonId]
        - Data[@Name=NewProcessId] #EventID=4688の場合?
        - Data[@Name=NewProcessName] #EventID=4688の場合?
        - Data[@Name=TokenElevationType]
        - Data[@Name=ProcessId]
        - Data[@Name=ProcessName] #EventID=4688以外の場合?
        - Data[@Name=CommandLine]

`record.lxml()`で取得したレコードは，上記の階層構造のXMLエレメントなので，lxmlのxpath関数でいい感じに取り出すことができます。
ただし，このElementツリーにはネームスペースが指定されているため，**xpath関数にはnamespacesを指定する必要があります**。
たとえばTimeCreatedを取得してみるなら，こんな感じ。

```python
# 上記のwith:とかfor:の中。
elm = log.get_record(200).lxml() #<-たとえば200番目のrecord
print(
    elm.xpath(
        "//event:TimeCreated",
        namespaces={"event":"http://schemas.microsoft.com/win/2004/08/events/event"}
    )[0].get("SystemTime") #TimeCreatedはSystemTime属性に入ってるっぽい。
)
# -> 2019-01-04 12:34:56.789012 みたいな感じ。
```
# あとはご自由に
私の場合，とりあえずExcelで処理してしまう方がいろいろとできて便利なので，print文（適宜，出力先を指定）で，タブ区切りテキスト(.tsv)に書き出して，Excelで読み込んであーだこーだ悩みます。
私の場合，システムログを読む知識がないのでEventIDとTimeCreatedだけを書き出していますが，圧倒的大多数はEventID==4688 (A new process has been created)なので，

```python
# forブロックの中。namespaceのschema定数は，別箇所で定義しとく。
if elm.xpath("//event:EventID", namespaces={"event":schema})[0].text == "4688":
    continue
```
って感じですっ飛ばしちゃいます。
そんでもって，.tsvに書き出したファイルをExcelで開いて，TimeCreatedの日付時刻の表示形式をそれっぽくして，EventIDに対して説明シートを参照（VLOOKUP）する列を付け加えれば，なんとなくそれっぽいものの出来上がり。
ちなみにEventIDの説明のリスト(Excel)は[Microsoftのサイト](https://www.microsoft.com/en-us/download/details.aspx?id=50034)からダウンロードできます。

#参考文献
[ElementTreeやlxmlで名前空間を含むXMLの要素を取得する (orangain flavor)](https://orangain.hatenablog.com/entry/namespaces-in-xpath)