FFRIエンジニアブログ

株式会社FFRIセキュリティのエンジニアが執筆する技術者向けブログです

インターンシップ報告 3

インターンシップのテーマ

「機械学習を用いたマルウェア検知手法をパイパスする、adversarialマルウェア自動生成プログラムの作成」

ご挨拶

この度FFRIの基礎技術研究部で、2週間のインターンシップに参加させていただいた石川です。本ブログ記事では、インターンシップで取り組んだ内容や感想についてご報告させていただきます。

インターンシップの応募動機

FFRIのインターンシップに応募しようと思った理由は、大きく2つあります。1つ目の理由は、インターンシップのテーマが、自身の研究領域であり、長年関心を寄せている分野と一致していたからです。その分野とは「セキュリティ×機械学習」で、自分は大学院で機械学習アルゴリズムを開発し、それをフィッシングサイトの検知に応用する研究を行っていました。FFRIのインターンシップにおける具体的なテーマは、「機械学習による検知手法をパイパスする、adversarialマルウェアを作ってみる」というもので、大変興味深く、自身の研究にも活用できると思い、応募させていただきました。

2つ目の理由は、FFRIという会社に以前から興味を持っていたからです。実は初めてFFRIを知ったのは、セキュリティ関連の株を調査していたときで、Black Hat等で発表をしている、非常に技術力の高い会社であることが分かりました。また後ほど、セキュリティ業界の第一線で活躍されている著名な方々が、FFRI出身だったことを知り、そのような技術力の高い人材が集まる環境から刺激を受けたいと思い、参加させていただきました。

インターンシップで取り組んだこと

  1. マルウェアの検体に対してstringsコマンドで取得できる文字列を特徴量とする、マルウェア分類器の作成。
  2. (White-box attackの仮定の下で、1.の分類器に対して、あるマルウェアを選択したときに)adversarialとなる(クリーンウェアに偽装できる)ような追加データを自動で生成するプログラムの作成。

対象分野の背景

近年adversarial examplesに関する研究が盛んに行われていますが、マルウェアを対象としたものはまだそれ程ありません。

そこで今回のように、マルウェアに含まれるASCII文字列を特徴量とする分類器に対する、adversarial attackを考える場合は、自然言語処理(NLP)分野の手法が参考にしました。

例えば、NLP分野におけるadversarial attackの研究としては、トップ国際会議であるNDSS 2018に採択されている以下の論文があります。

Jinfeng Li, Shouling Ji, et al. "TEXTBUGGER: Generating Adversarial Text Against Real-world Applications"

しかし、このようなNLP分野の研究では、adversarial attackとしてテキスト中の任意の文字列を編集(挿入・削除・置換等)することを許容する例がありますが、マルウェアはプログラムとして正常に動作する必要があるため、今回のケースではデータの末尾に文字列を追加することのみを許容することにしました。(オレオレパッカーを作ればAPIコールを隠すなど、より複雑な操作が可能ですが、今回は簡単に行うことができる操作に限定しました。)

データセット

今回はFFRI Dataset 2019の中から、マルウェアとクリーンウェアそれぞれについて、ランダムに1000検体ずつサンプリングし、全体のデータセットとしました。また訓練データには全体の70%を用い、検証データには全体の30%を用いました。FFRI Dataset 2019の全データを使用していない理由は、比較的短いインターンシップ期間において、大規模データの処理にかかる時間を短縮するためです。

取り組んだことの詳細

1. マルウェア判別器の作成

1-1. 特徴量の抽出

今回は特徴量の表現形式として、Feature HashingとBag of Words(BoW)の両方を試し、より精度の良かったBoWを用いました。

またBoWについては、今回のケースでは分類器としてランダムフォレストを用いたため、TF-IDFによる重み付けが有効でないことが(実際の結果からも)分かっており、そのままのBoWを用いています。またN-gramを用いなかった理由は、NLPで扱うようなテキストデータと比べて、隣接する文字列同士の関係性が比較的弱いと考えたからです。

1-2. 分類器の選択

分類器として、ロジスティック回帰、SVM、k近傍法、ランダムフォレストを試した結果、最も精度の高かったランダムフォレストを採用しました。ランダムフォレストの精度は、生成する決定木の数が100のときに90%程度、10のときに85%程度で、分類器の必要条件をぎりぎり達成しているだろうということで、次の工程に移りました。

2. White-box attackを実行するプログラムの作成

2-1. 攻撃者モデルの設定

今回は攻撃者モデルとして、ターゲットの機械学習モデルについて任意の情報を取得可能とする、White-boxモデルを採用しました。White-boxモデルは、攻撃者に非常に有利な状況設定なため、もし可能であればBlack-boxな攻撃手法も試したかったのですが、自分の実装力と時間の都合上、White-box攻撃のみを検証しました。

2-2. 攻撃手法の考案

メンターの方に紹介していただいた、テキストデータや決定木に対するadversarial attackに関する論文を読み、手法を考えました。 しかしながら、「背景」のところで説明したように、論文で紹介されている手法をそのまま今回のケースに適用することは難しかったため、最終的には以下の手法を実装しました。 - 攻撃対象であるランダムフォレストの学習モデルの一つ一つの決定木を見て、もし特定の文字列を追加することで、分類結果をクリーンウェアに変更できるならば、そのような文字列を検体のマルウェアに追加する。

2-3. 提案手法の実装

2-2の攻撃手法を、アルゴリズムに変換し、Pythonのプログラムとして実装しました。

イメージを掴んでもらうために、以下の画像を用いて、大まかなアルゴリズムのデザインを説明したいと思います。

実装したアルゴリズムのInputとOutputは次のようになります。 - Input: ランダムフォレストの学習モデル(生成された全ての決定木の情報)、ターゲットなるマルウェアの検体の特徴量 - Output: マルウェアの検体にappendすべき文字列

  1. ランダムフォレストの一つの決定木(画像中の木)を根から走査していき、条件分岐箇所で「検体が持っている元々の特徴量がしきい値を下回っている場合」(画像中で青色でハイライトされている枝)に赤色の枝を付け足すことで、黄色の丸(クリーンウェア)に到達できるパスがあるかを、深さ優先探索(DFS)で行う。

  2. もしそのようなパスが存在していた場合には、そのパスと検体が持っている元々の特徴量を比べて、あとから付け足した特徴量を、appendすべき文字列として配列append_stringsに追加。

  3. 1.--2.の処理を、ランダムフォレストが生成する全ての決定木に対して行い、最後にappend_stringsを出力。

今回の場合、決定木の各ノードは、マルウェアとクリーンウェアを分離するための「条件分岐」に完全に対応しているため、葉でないノードは必ず「左の子ノード」と「右の子ノード」を持ち、ある葉ノードの兄弟ノードは必ず自身と異なるラベルを持ちます。このようなデータ構造についての強い仮定があるため、今回比較的シンプルなアルゴリズムで、望んだ処理を実現することができました。また実装としましては、深さ優先探索はスタックで実現し、全ての必要な情報の取得は、sklearn.tree.DecisionTreeClassifierのattributesを使用しました。

ここで具体例を交えて、アルゴリズムの挙動を説明したいと思います。上記の画像では、根から青丸で囲った葉へのパス(パス1)と、根から赤丸で囲った葉へのパス(パス2)の、2つのパスがハイライトされています。今対象としているマルウェア検体が、実際はパス1を通って、緑色の丸(マルウェア)に分類されている場合、赤色の四角で囲った特徴量である[417, 25846, 21421]に対応する文字列を、各条件分岐のしきい値を超えるまで、検体ファイルの末尾にappendすることで、パス2を通るように誘導し、クリーンウェアに偽装することができます。この画像の決定木のケースでは、検体が持っている元々の特徴量に関係なく、[417, 25846, 21421]という特徴量を加えることで、クリーンウェアに偽装できることを分かっていただけたと思います。一方で決定木の形状によっては、検体が持っている元々の特徴量をどのように増加させても、クリーンウェアになるようなパスに誘導できないケースもあり、そのような場合には空文字列を出力するようにしています。

2-4. 検証実験の結果

提案手法においては、攻撃対象のランダムフォレストに対して、生成する決定木の数が4以下のときには、100%の成功率でWhite-box攻撃が可能なことを検証しました。しかしながら、決定木の数が5以上のときには、成功率が5%程度に下がってしまうことを確認しており、ここからランダムフォレストのそれぞれの決定木に「依存関係」があるのではないかという、結論に至りました。詳しくは、「今後の課題」で述べたいと思います。

今後の課題

今回の提案手法は、ランダムフォレストで生成されるそれぞれの決定木を独立に捉え、個々の決定木に対して予測結果が改変されるように、文字列を加えていく手法となっています。この手法の問題点は、生成される決定木に依存関係が存在していた場合には、ある決定木の条件分岐への介入が他の決定木の条件分岐を変更してしまう可能性があるということです。実際、今回の実験では、決定木の数が5以上のときに、成功率が急激に下がることを確認しました。

これは原理的には、操作として各特徴量の値の増加は行えても、減少はできないため、ある木への介入は、別の木のパス変更の自由度を下げるという意味で、悪影響しか及ぼさないからだと考えられます。また前述の通り、ランダムフォレストは全ての決定木の多数決で予測を行うため、木が増えれば増えるほど、少数の木のパスを誘導できたとしても、その効果が限定的になります。提案アルゴリズムにはこのような課題があるため、もし機会があれば、生成される全ての決定木を同時に見て、全体最適な意味で、追加すべき文字列を出力できるアルゴリズムを考案したいと思いました。またブログを執筆中の現段階においては、残念ながら時間が足りず、実際のマルウェアバイナリの改変による攻撃実験まではたどり着けませんでしたが、こちらにつきましてもインターンシップの残りの時間で可能であれば、トライしてみようと思っています。

インターンシップの感想

今回一番楽しかった点は、提案手法をアルゴリズムに落とし込むところで、自分で考案したアルゴリズムが意図した動作をしたときには、達成感を感じました。(ただし時間の都合もあり、アルゴリズムは計算量的に最適なものではなく、実装が簡単で動作確認がしやすいものを選択しました。)一方苦しかった点は、Pythonがあまり得意でなく、実装上のバグ取りに必要以上に時間を取られてしまったことです。しかしながら、最終的にメンターの方に色々と手助けをしていただき、正常に動作するプログラムを作成することができました。

日記風ハイライト

1--2日目:AWSでの環境構築

初めてのAWS。メンターの方につきっきりで指導してもらい、何とかデータを分析できる環境を構築。

3--4日目:分類器の作成

Python書くの久しぶりだなーと思い、前処理に苦戦しながらも、sklearnで基本的な分類器を実装。

5--6日目:攻撃手法の考案(サーベイ+学習モデルの可視化)

リモートワークで、メンターの方に教えてもらったadversarial attackの論文を読む。 前述のTEXTBUGGERは、シンプルな手法で、実アプリケーションの攻撃に成功しており、自分がNLP分野の研究者だったら、こういう研究をしてみたいと感じる。 その後、とりあえず学習モデルを眺めてみようと思い、dtreevizでランダムフォレストを可視化。決定木の枝が左にすごい伸びているアンバランスな木が生成されていて、それでも相対的に高い精度を達成しているランダムフォレストすごいなと感じる。 リモートワーク中に不摂生で体調を崩したこともあり、当初の最低目標達成に対して弱気になるが、基礎技術研究部の方に励まされ、とりあえずトライしてみようという気持ちになる。

7--8日目:攻撃手法の実装

出社スタイルに戻り、まずは可視化した学習モデルを基に、全てのマルウェアに対して、同じ文字列を追加することで、偽装成功率を向上させるようなプログラムを作成。その後、偽装成功率向上のため「それぞれのマルウェアに対して、別々の文字列を追加するプログラム」を作成してみたらと、メンターの方から助言をいただく。そのようなアルゴリズムはすぐには思いつかなかったものの、一晩考えて思いつき、2時間程で実装できたときには、非常に達成感を感じた。ただしその後、プログラムに何らかのバグがあることが判明し、バグ取りに時間を費やす。

9日目:攻撃手法の評価とブログ執筆

バグ取りが終わらず、インターンシップ期間中にプログラムの完成は無理かもしれないと思ったが、メンターの方にソースコードを全部見てもらい、プログラム冒頭のところでバグを発見。しかしながら、偽装成功率は向上せず、その他にバグも見つからない事態に。八方塞がりの状態から、メンターの方の助言で、ランダムフォレストで生成する決定木の数を、2--4個に減らしてみたところ、偽装成功率100%を達成。すなわち、プログラムとしては意図していたものが出来ていたものの、当初懸念していた各決定木同士に依存関係があったために、上手く行っていなかったことが分かる。