FGSMを使ってマルウェア検知器(MalConv)を回避する
これはAizu Advent Calendar 2019の11日目の記事です。(遅れて申し訳ありません…🙇♂️🙇♂️🙇♂️)
前の人は id:xatu0202 さんで,
次は id:shota-df-412 さんです.
はじめに
昨今、観測されるマルウェアの数は膨大になり、シグネチャベースのマルウェア検知・分類は難しくなっています。そのため、機械学習や深層学習を使用してマルウェア検知・分類を行う研究が盛んに行われていますが、MalConvというディープラーニングモデルをご存知でしょうか。
この記事では、FGSMという手法を用いてMalConvというCNNベースのマルウェア検知器を回避してみます。
※Adversarial Examplesの検証を目的としており、不正な攻撃を助長するものではありません。
MalConvとは
MalConvは、2017年にNVIDIAの研究チームらにより発表された Malware Detection by Eating a Whole EXEで提唱されています。それまで、ニューラルネットワークをマルウェア検知器に適用している研究はほとんどなく、実行ファイル全体を入力として受け取ってマルウェア検知している研究は存在していませんでした。また、マルウェア検知に使用する特徴量の抽出には多くのドメイン知識が必要でしたが、MalConvはそのようなドメイン知識の使用の最小化を目的とし、実行ファイルのraw byte sequenceのみを入力として受け取るようなCNNベースのマルウェア検知器であり、高い精度でマルウェアを検知できています。
それまで手作業で行ってきた特徴抽出の必要がなくなり、実行ファイルのみを入力として与えることで、モデル自身が特徴量を学習してマルウェアを検知することができます。そんな素晴らしい技術ですが、果たして欠点はないのでしょうか?
Adversarial Examplesについて
機械学習のモデルの精度を考慮する際、Adversarial Examplesの存在は欠かせません。
有名なのはこの画像ですが、パンダと分類されるべき画像に摂動というものを加えることでテナガザルと分類させています。これは非常に重要な問題であり、機械学習のモデルを実世界に適用する際に様々な被害が考えられます。
Adversarial Examplesについては以下が詳しいです。
PEファイルにおけるAdversarial Examplesを考える
MalConvの論文では、データセットにPEファイルを使用していましたが、PEファイルにおけるAdversarial Examplesは通常のAdversarial Examplesとなにが異なるのでしょうか。
通常は画像全体にノイズがかかるような形で摂動を加えるのですが、PEファイルでも同様の行為をしてしまうと機能性が失われてしまうという問題があります。例えば、実行に必要な多くの情報が含まれているPEヘッダーの不用意な変更は、プログラムをクラッシュさせることに繋がり、実行ファイルとして動作しなくなってしまう恐れがあります。他の領域においても同様で、1ビット書き換わるだけでクラッシュしてしまう危険性が増加するため慎重に扱う必要があります。
ではどのようにしてAdversarial Examplesを作るのかというと、
- 新しいセクションを作る
- ファイルの末尾に追加する
などの手法が取られています。1ではLIEFというライブラリがよく使われていて、PEに限らずELFやMachOなどのフォーマットをパースしたり変更を加えることが可能です。
2ですが、これはPEファイルに限った話ではありませんが、PEファイルの末尾にどのようなバイト列を追加しても問題なく動作するため、任意のバイト列を追加する場所としてファイルの末尾が使用されます。
これらの手法を用いて、benignと判定されるような特徴を追加していく、というのがPEファイルにおけるAdversarial Examplesの基本です。
Machine Learning Static Evasion Competitionとは
今年のDEFCON AI Villageで開催されていたコンペであり、MalConvを含めた3つのモデルによる検知を回避する技術を競います。最終的に50個のmaliciousなPEファイルを回避させる必要があります。また、white-box attackを考えるので、ソースコードは与えられます。
以下のブログでは、3つのモデルに対するwrite upが紹介されています。面白いのでおすすめ。
Fast Gradient Sign Method(FGSM)を使ってMalConvを回避してみる
Fast Gradient Sign Method(FGSM)とはAdversarial Examplesを作る手法の一つであり、重みを固定して入力を変化させながらAdversarial Examplesを求める方法よりも、誤差逆伝播を使って一度勾配を計算するだけなので高速ということらしいです。
これも詳しくは、上で掲載したサイト( はじめてのAdversarial Example )に載っています。
前置きがだいぶ長くなりましたが、FGSMを使ってMalConvを回避(FGSM attack)してみます。MalConvは上記のMachine Learning Static Evasion Competitionで使われていたモデル(EMBER 2018 binariesで学習させたもの)を使用します。(EMBERはEndgameが2018年4月に公開したオープンソースのデータセットです)
Machine Learning Static Evasion Competitionで使用されたコードは以下にあります。
今回はMalConvによる検知のみを回避するので、他の2つのモデルは扱いません。models.pyをMalConvのみ使うように変更して、結果だけでなくbenignとmaliciousそれぞれのrateも出力するようにしました。
import torch import torch.nn.functional as F from MalConv import MalConv import numpy as np MALCONV_MODEL_PATH = 'models/malconv/malconv.checkpoint' class MalConvModel(object): def __init__(self, model_path, thresh=0.5, name='malconv'): self.model = MalConv(channels=256, window_size=512, embd_size=8).train() weights = torch.load(model_path,map_location='cpu') self.model.load_state_dict( weights['model_state_dict']) self.thresh = thresh self.__name__ = name def predict(self, bytez): _inp = torch.from_numpy( np.frombuffer(bytez,dtype=np.uint8)[np.newaxis,:] ) with torch.no_grad(): outputs = F.softmax( self.model(_inp), dim=-1) print('benign: {}'.format(outputs[0][0].item())) print('malicious: {}'.format(outputs[0][1].item())) return outputs.detach().numpy()[0,1] > self.thresh if __name__ == '__main__': import sys with open(sys.argv[1],'rb') as infile: bytez = infile.read() # thresholds are set here malconv = MalConvModel(MALCONV_MODEL_PATH, thresh=0.5) print(f'{malconv.__name__}: {malconv.predict(bytez)}')
試しにこの学習済みモデルでマルウェアを検知してみます。検体はtheZooにあるTrojanWin32.Duqu.Stuxnetを使用しました。
$ python3 models.py win32.exe benign: 0.0066443816758692265 malicious: 0.9933556318283081 malconv: True
99%maliciousだと判定されています。これをbenignと判定させることが目的です。
FGSM attackの実装
FGSM attackを実装するにあたって、今年の1月に発表された Deceiving End-to-End Deep Learning Malware Detectors using Adversarial Examples を参考にしました。
完成したものがこちらです。
実行してみます。
$ python3 fgsm_attack.py win32.exe [1] Benign: 0.0066236 , Malicious: 0.99338 Loss: 1.3036 Reconstruction phase: 100%|████████████████████████████████████████████████████████████████████████████████████████████████| 640/640 [00:08<00:00, 79.76it/s] sum of perturbation: 84646.0 [2] Benign: 0.52506 , Malicious: 0.47494 Loss: 0.66841 Reconstruction phase: 100%|████████████████████████████████████████████████████████████████████████████████████████████████| 640/640 [00:07<00:00, 80.97it/s] sum of perturbation: 84640.0 Evasion rates: 0.52506 win32_AEs.exe has been created.
(摂動の値の合計値も出力しているのは、この値に変化がない場合は上手く生成できていない可能性があるからです…。epsが低い場合や摂動のサイズを小さい値で固定してテストしてみた時に発生していました。)
作成されたAdversarial ExamplesをMalConvで判定してみます。
$ python3 models.py win32_AEs.exe benign: 0.9517970681190491 malicious: 0.04820294678211212 malconv: False
見事にbenignだと判定されました。追加された摂動を見てみます。
ファイルの末尾に追加された一見ランダムに見えるこのバイト列ですが、MalConvではこの数百バイトが原因でmaliciousと判定されるべきファイルがbenignだと判定されてしまいました。
Deceiving End-to-End Deep Learning Malware Detectors using Adversarial Examples で行われていた方法は単純で、 c + (c − len(x) mod c): (cはconv size、xは入力ファイル)
で求まったサイズのランダムバイト列(0~255の範囲)を生成し、それを入力ファイルの末尾に加えたものをMalConvで判定させ、benignである識別率が0.5を上回るまでFGSMを用いて摂動を変更していくだけです。
最後に
Machine Learning Static Evasion Competitionのwrite upを書いていた方は10万バイトの0xA9を追加したり、benignなファイルの文字列を追加したりしていましたが、FGSM attackの場合は1000バイト以下のバイト列を末尾に付与するだけでMalConvによる検知を回避することができました。もちろん、コンペにおいては他の2つのモデルによる検知も同時に回避する必要があるのでこれだけでは不十分だと思いますが。
また、詳しくは言及しませんが、Adversarial Trainingという方法を使うとAdversarial Examplesに対してある程度ロバストになることも知られています。
実際に世の中で使用されているCylance製のアンチウイルスにおいて、このAdversarial Examplesが問題になったこともあるため、見過ごすことのできない課題の一つになっていることは確かです。
参考
https://devblogs.nvidia.com/malware-detection-neural-networks/
https://github.com/yuxiaorun/MalConv-Adversarial/blob/master/src/model.py