BeautifulSoup+Pythonで、マルウェア動的解析サイトからWebスクレイピング

# はじめに
JoeSandboxというマルウェアを解析してレポートを出力してくれるサイトがあります。
https://www.joesandbox.com

JoeSandboxには色々バージョンがありますが、Cloud Basicというバージョンであれば無料でマルウェア解析ができます。
さらにCloud Basicで解析されたレポートは公開されますので、他の人の分析結果レポートを見ることもできます。

今回はマルウェアの分析結果レポートをBeautifulSoup+PythonでWebスクレイピングし、プロセス情報を取得してみたいと思います。
ちなみにCloud Basic以外のバージョンですとWeb APIが利用できますが、Cloud Basicでは利用できないようです。

# JoeSandboxについて
分析画面です。この画面でマルウェアを指定し、色々なオプションなどを設定したのちに分析を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/f611bfa6-8164-a25b-197e-59620b13048a.png)

分析結果の一覧画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/47ecc08b-3cdd-c392-e03c-ed2a00574844.png)

分析結果の詳細画面です。色々な分析結果が出力されていますので、分析結果の一部分の画面キャプチャです。
https://www.joesandbox.com/analysis/175872/0/html#startup

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/64d76aeb-053c-b649-0ff9-62afdd061cc9.png)

# 今回やりたいこと
今回は分析結果の詳細画面から、マルウェアのプロセスの一連の流れを抽出したいと思います。
具体的には以下の部分の情報を抽出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/0616de51-4068-316b-0274-da3e6cd7b4bf.png)

出力結果は以下のようにしようと思います。プロセスの親子関係がわかるようにインデントをつけた状態で出力しようと思います。

```text
レポート番号：xxxxx 実行日時
        15KD , Mixed Grade.exe
         schtasks.exe
          conhost.exe
         schtasks.exe
          conhost.exe
         RegAsm.exe
          vbc.exe
          vbc.exe
```

# コード
以下に記載の内容を繋げたら動くようにしています。

## 必要なライブラリをインポート
BeautifulSoup以外にrequests, os, datetime, pytz もインポートします。

```python
import requests
from bs4 import BeautifulSoup
import os
import datetime, pytz
```

## 抽出するレポート番号の指定
複数のレポートから情報を抽出するため、fromとtoを入力するように準備します。ここではテスト用に1つだけ指定しています。

```python
report_num_from = 175872
report_num_to = 175872
```

## 指定されたページのクロール
レポートのURLは以下のようになっています。この「175872」の部分がレポート番号のようですので、この番号をループするようにしたいと思います。
https://www.joesandbox.com/analysis/175872/0/html

```python
def find_process_name(report_num_from, report_num_to):
    
    for i in range(report_num_from, report_num_to + 1):
        try:
            process_names = []
            process_names.append('\n[report number]:{} {}'.format(i, datetime.datetime.now(pytz.timezone('Asia/Tokyo'))))
            
            target_url = 'https://www.joesandbox.com/analysis/' + str(i) + '/0/html'
            response = requests.get(target_url)
            soup = BeautifulSoup(response.text, 'lxml')
```

## JoeSandboxの状態の確認
JoeSandboxはマルウェアの分析結果により、malicious, suspicious, clean, unknownと分けられているようです。
マルウェアのプロセス情報を取得したいので、ここではmaliciousのみ抽出しようと思います。以下はmaliciousの画面例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/dab7af86-5b5c-c593-aab4-7b55b63b94a9.png)

上記のmaliciousの情報を情報を取得してみようと思います。
該当のコードは以下でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/83115ad5-fe2a-c417-3680-5a7bd262dfbc.png)

`img`で`id`が`analysisDetectionStatus`の`alt`に情報が指定されるようです。
この情報を取得するには`soup.find('img', id='analysisDetectionStatus').get('alt')`と記載すれば良いので、それを組み込みます。

```python
            detection = soup.find('img', id='analysisDetectionStatus').get('alt')
```

## プロセス情報の取得
以下はプロセス情報の画面例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/1a2ed4e3-6a58-b893-eeb0-4c851b1c0d83.png)

このプロセス情報を取得してみようと思います。
該当のコードは以下でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/feef58e5-a90b-8890-0296-f7def7c43e20.png)

中身を確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402872/77c51cf6-c4a2-7d8a-fa01-ec42fff8ad9d.png)

どうやらプロセス情報はテーブルに格納されているようです。
`div`の`id`が`startup1`の中身を配列に格納して、その中でプロセス情報を取得しようと思います。
ちなみにプロセス情報は`<a href`の次に格納されていましたので、以下のようにしました。
もっとスマートなやり方がありそうですが・・・。

```python
            if detection == 'malicious':            
                startup = soup.find('div', id='startup1')

                flag = False
                for line in startup.prettify().split('\n'):
                    if flag == True:
                        process_names.append(line)
                        flag = False
                    if '<a href' in line:
                        flag = True
                        
            else:
                process_names.append('not malicious')
```

## 例外処理
例外処理の部分も忘れずに記載します。
処理の最後に`save_file`関数（この後記載）を呼び出します。

```python

        # Report number does not exist
        except IndexError as e:
            process_names.append('ERROR:{}'.format(e))

        except Exception as e:
            process_names.append('ERROR:{}'.format(e))
            
        finally:
            save_file(process_names)
```

## ファイル書き込み
ファイルに書き込む処理です。
別に関数にしなくても良いですが今後ファイル書き出し以外の処理に変更しようと思うので、書き換えやすいように関数にしました。
同じフォルダに`output.txt`を作成して書き込みます。

```python
def save_file(process_names):        
    with open('./output.txt', 'a') as f:
        for x in process_names:
            f.write(str(x) + "\n")
```

## 処理の実行
関数を実行します。
これで完了です。

```python
find_process_name(report_num_from, report_num_to)
```

# 完成したコード
今までのコードをつなげて、コメントを追加しました。
当たり前のことですがfromとtoで指定する範囲はほどほどにしてください。

```python

# Extract process name from JoeSandbox analysis result.
# 
# How to use:
# 1. Enter the first and last report number you want to analyze.
#      -> report_num_from and report_num_to
# 2. Result is saved as output.txt in the same folder.

import requests
from bs4 import BeautifulSoup
import os
import datetime, pytz

report_num_from = 175872
report_num_to = 175874

def find_process_name(report_num_from, report_num_to):
    """
    Extract process name from JoeSandbox analysis result.
    
    Parameters
    ----------
    report_num_from : int
        First report number to analyze
    report_num_to : int
        Last report number to analyze
    """
    
    for i in range(report_num_from, report_num_to + 1):
        try:
            process_names = []
            process_names.append('\n[report number]:{} {}'.format(i, datetime.datetime.now(pytz.timezone('Asia/Tokyo'))))
            
            target_url = 'https://www.joesandbox.com/analysis/' + str(i) + '/0/html'
            response = requests.get(target_url)
            soup = BeautifulSoup(response.text, 'lxml')

            # check JoeSandbox Detection (malicious, suspicious, clean, unknown)
            detection = soup.find('img', id='analysisDetectionStatus').get('alt')

            if detection == 'malicious':            
                # 'startup1' is a table with process names
                startup = soup.find('div', id='startup1')

                # process name is next to '<a href'
                flag = False
                for line in startup.prettify().split('\n'):
                    if flag == True:
                        process_names.append(line)
                        flag = False
                    if '<a href' in line:
                        flag = True
                        
            else:
                process_names.append('not malicious')

        # Report number does not exist
        except IndexError as e:
            process_names.append('ERROR:{}'.format(e))

        except Exception as e:
            process_names.append('ERROR:{}'.format(e))
            
        finally:
            save_file(process_names)

def save_file(process_names):
    """
    Save the extraction results to a file.
    File I/O is a function because it may change.

    Parameters
    ----------
    process_names : list of str
        List containing process names.
    """
        
    with open('./output.txt', 'a') as f:
        for x in process_names:
            f.write(str(x) + "\n")

find_process_name(report_num_from, report_num_to)
```

# 実行結果
上記のコードを実行すると以下のような結果が得られます。

```text
[report number]:175872 2019-10-12 22:14:58.066360+09:00
        15KD , Mixed Grade.exe
         schtasks.exe
          conhost.exe
         schtasks.exe
          conhost.exe
         RegAsm.exe
          vbc.exe
          vbc.exe

[report number]:175873 2019-10-12 22:15:04.034550+09:00
not malicious

[report number]:175874 2019-10-12 22:15:11.023350+09:00
        EXCEL.EXE

```

# 関連
Joe Sandboxに関する詳細な説明は以下にまとめています。

Joe Sandboxでマルウェアの動的解析をする方法
https://qiita.com/hanzawak/items/ec665e0f96dc65f3def3

BeautifulSoup+Pythonで、マルウェア動的解析サイトからPowerShellコマンド取得
https://qiita.com/hanzawak/items/15a88c4953be4f8159da