Rails appのコードを変更したときに、Guardに自動でRuboCop、RSpecを実行してもらう
Guardとは
Guardはファイルに変更があったときに、タスクを走らせてくれるgemで、コードを変更したときに、静的なコードチェックをしてくれるRuboCopやRSpecで書かれたテストを実行させることができる。RuboCopやテストコードを随時走らせてコーディングしていくと、問題を即座に把握できるので、プルリク前にあわててテストコードを走らせて修正....というハメに陥らなくて済む。
導入方法
Gemfile
Gemfileに以下のgemを追加。RSpec、RuboCopの導入はすでに終わっているものとする。
group :development do ... gem 'guard-rspec' gem 'guard-rubocop' ... end
インストールも忘れずに。
$ bundle install
Guardfile
Guardの設定ファイルを作成する必要がある。以下のコマンドで生成できる。
$ bundle exec guard init rspec $ bundle exec guard init rubocop
このようなGuardfileになる。このファイルを変更することで、監視対象とするファイルを変更したり、rspecが落ちた時の RuboCopの実行をスキップしたりできる。
guard :rubocop, cli: '--rails' do watch(/.+\.rb$/) watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } end guard :rspec, cmd: 'bundle exec rspec' do require 'guard/rspec/dsl' dsl = Guard::RSpec::Dsl.new(self) # RSpec files rspec = dsl.rspec watch(rspec.spec_helper) { rspec.spec_dir } watch(rspec.spec_support) { rspec.spec_dir } watch(rspec.spec_files) # Ruby files ruby = dsl.ruby dsl.watch_spec_files_for(ruby.lib_files) # Rails files rails = dsl.rails(view_extensions: %w(erb haml slim)) dsl.watch_spec_files_for(rails.app_files) dsl.watch_spec_files_for(rails.views) watch(rails.controllers) do |m| [ rspec.spec.call("routing/#{m[1]}_routing"), rspec.spec.call("controllers/#{m[1]}_controller"), rspec.spec.call("acceptance/#{m[1]}") ] end watch(%r{^app/api/(.+)\.rb$}) { |m| "spec/api/#{m[1]}_spec.rb" } # Rails config changes watch(rails.spec_helper) { rspec.spec_dir } watch(rails.routes) { "#{rspec.spec_dir}/routing" } watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } # Turnip features and steps watch(%r{^spec/features/(.+)\.feature$}) watch(%r{^spec/steps/(.+)_steps\.rb$}) do |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/features' end end
rspecが落ちた時に RuboCopの実行をスキップしたい場合は、 以下のようにGuardfileを変更するとよい。
# This group allows to skip running RuboCop when RSpec failed. group :red_green_refactor, halt_on_fail: true do guard :rspec do # ... end guard :rubocop do # ... end end
Guardを実行してファイルを監視
実行するだけ。
$ bundle exec guard
おわりに
Guardを使って開発しているとプルリクする前にあわててテストを実行しなくてよくて便利。 快適railsライフを送りましょう。
LLVM bitcodeのCFGを生成する
LLVM bitcodeとは
LLVM内ではLLVM IRという中間言語表現が用いられる。 ソースコードをLLVM IRコードに変換したあと、そのLLVM IRコードをターゲットのアーキテクチャのバイナリに変換...という流れでコンパイルは行われる。 LLVM bitcode は LLVMの独自バイナリフォーマットで、LLVM IRと相互変換可能である。 LLVM IRは.llファイル、LLVM bitcodeは.bcファイルでそれぞれ表される。
$ llvm-as sample.ll # LLVM IRをLLVM bitcodeに変換 $ llvm-dis sample.bc # LLVM bitcodeをLLVM IRに変換
LLVM bitcodeはclangに-emit-llvmオプションを指定することで生成できる。
$ clang -emit-llvm -c -g test.c
CFGを生成する
optコマンドにより、dot-cfg passを実行することでLLVM bitcodeからdotファイルを生成する。 passはLLVM内で機能を構成する1単位であり、 最適化機構などはpassとして実装され、各passはoptコマンドのオプションで指定することで実行できる。 dotファイルにはグラフ構造が書き込まれており、Graphviz内のdotコマンドを使うことで画像として出力することができる。 macだとGraphvizのインストールはHomebrewでできる。
ソースコード
ここではたまたま手元にあったDynamic Opaque Predicateを施されたCのソースを使った。 Dynamic Opaque Predicateは難読化の一種で、どの分岐を通っても最終的に同じ処理を行うよう分岐を施す難読化である。 Opaque Predicateについては、 LOOPや Generalized Dynamic Opaque Predicatesがくわしい。
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { if(argc != 2) exit(1); int x = atoi(argv[1]); int y = 0; if (x % 2 == 0) { y = x + 2; } else { y = x + 100; } if (y % 2 == 0) { y = y + 100; } else { y = y + 2; } printf("y = %d\n", y); return 0; }
コマンド
$ clang -emit-llvm -m32 -c -g test.c $ opt test.bc -dot-cfg $ dot -Tpng cfg.main.dot > test.png
生成されたCFG
死んだサーバーで動いていたアプリの引っ越しの歌
記憶の欠片に描いたnginx.confを見つめて 跡切れた想い出重ねる 変わらない夢に Oh 死んだHDD どれだけ涙を流せば 貴方を忘れられるだろう Just tell me my life 何処まで歩いてみても 涙で明日が見えない
以上、X JAPANのRusty Nailの替え歌でした。
We are X!!
2017年のOSSへのコントリビューションまとめ
年末ということで今年のOSSへのコントリビューションを振り返る。来年はもっとやっていきたい。
Metasploit
GSoCにMetasploitで参加していたので、それで結構プルリクを送った。 GSoC 2017にmetasploitで採択された。 - 脱力系日記
Pull Requests · rapid7/metasploit-framework
後半でかいissueを解決しようとしてたけど、ハマって期間中にプルリクを送れなかった。再度トライしたい。
casein
caseinはRuby on RailsでCMSを作るためのgemである。
scaffoldをするときに指定した名詞が複数形だったときエラーが出るので修正するプルリクを送った。 このgemはバイト先でよく使っている。
Pull Requests · russellquinn/casein
ldap3
ldap3はPythonからLDAPを扱うためのライブラリである。
サンプルコードが間違っていたのでプルリクを送った。
Pull Requests · cannatag/ldap3
dagger
daggerはMachO、ELF向けのLLVM IRへのデコンパイラでLLVMをforkして作られている。
ビルド方法が間違ってたので修正した。
Pull Requests · repzret/dagger
McSema
IDAのAPIを使ったLLVM IRへのデコンパイラである。 「funded by and used in research for DARPA and the US Department of Defense.」なので強そう。
READMEのリンク切れを修正した。
Pull Requests · trailofbits/mcsema
Linux Insides
Linux Insideはlinuxの内部構造についてのフリーの本で有志の手でロシア語や中国語への翻訳が進められている。
これはOSSにプルリクを送ったというわけではないけど、日本語版のリポジトリを作って翻訳を進めている。
最近やれてないので、誰かプルリクしてほしい。
機械学習を使っているアンチウイルスを機械学習を使ってバイパスする「gym-malware」の紹介
マルウェアの分類のために使われている機械学習のblind spotを潰すために、機械学習を使っているアンチウイルスを機械学習を使ってバイパスする研究が最近活発になってきている。 この記事では、black hat USA 2017で発表されたBot-Vs-Bot-Evading-Machine-Learning-Malware-Detectionとその関連研究を紹介する。これは、情報セキュリティ系論文紹介 Advent Calendar 2017 - Adventarの18日目の記事である。
背景
機械学習はマルウェアファミリを一般化できるので、シグネイチャを持たない未知のマルウェアの検出に利用されているが、機械学習のモデルには盲点(blind spot)があり、それにより誤認識を引き起こす。機械学習を用いた画像認識が、ノイズを混ぜられた画像を誤認識することは以前話題になった。
画像認識と同じくアンチウイルスも誤認識をすることがあり、それによってアンチウイルスをバイパスされてしまう。
関連研究
当然ながら、機械学習をバイパスするにはモデルの情報を知っているほうが楽で、モデルに関する情報がなければ難易度が上がる。しかしアンチウイルスソフトの開発時にどのようなモデルを使っているか知る術はない。
ターゲットとなる機械学習のモデルを知っている状態でそれをバイパスする研究には、Androidマルウェア向けのAdversarial Perturbations Against Deep Neural Networks for Malware Classification、マルウェアのC2サーバーとの通信を確立するDGA(domain generation algorithms)の検出/生成を目的としたDeepDGAがある。
また、ターゲットとなる機械学習のモデルが未知の状態でBlack-Box Attackを行う先行研究には、PDFマルウェアをターゲットとしたuvasrg/EvadeML: An evolutionary framework for evading machine learning-based malware classifiers.、 GANでPEバイナリの生成を試みるGenerating Adversarial Malware Examples for Black-Box Attacks Based on GANがある。Generating Adversarial Malware Examples for Black-Box Attacks Based on GANでは実行ファイルとしてのフォーマットを保つことは出来ていない。
gym-malware
この研究では、OpenAI gymという強化学習を行うプラットフォームの上にマルウェアを生み出す環境をgym-malwareとして実装することでアンチウイルスをバイパスするPEバイナリを作り出している。
OpenAI gymは強化学習におけるAction、Reward、Stateを以下のように定義している。
Action
- 使用されないIATに関数を追加する
- 既存のセクション名を操作する
- 新しい(未使用の)セクションを作成する
- セクションの最後の余分なスペースにバイト列を追加する
- 元のエントリポイントにただジャンプするだけの新しいエントリポイントを作成する
- 署名を操作する(中断する)
- デバッグ情報を操作する
- バイナリをパックまたはアンパックする
- PEヘッダのチェックサムを変更する
- PEファイルの最後にバイト列を追加する
ここでのPEフォーマットに関する操作にはLIEF - Library to Instrument Executable Formatsが用いられている。
Reward
- アンチウイルスにかけた結果
ここでのアンチウイルスには、10万個のマルウェアと良性のバイナリを訓練し、受信者動作特性スコア(ROC AUC)が0.96になる勾配ブーストされた自前の決定ツリーモデルが用いられている。
State
以下のような静的なPEバイナリの属性を2350次元に圧縮している。
実験結果
VirusShareで配布されているマルウェアを用いて、15時間で100K回の試行を行った。 black box attackにより生み出されたマルウェアの非検出率(16%)がscore-based attack (機械学習モデルがスコアを返す)のもの(14%)よりも高かった。
また、生成した20個のサンプルバイナリをVirusTotalにアップロードし、検出率の中央値が31/63から18/63に下がったことを確認した。
今後
機械学習を用いられているセキュリティ製品は増えてきており、機械学習のモデルのblind spotを探す研究動向は今後注視していきたい。
おまけ1
How to bypass 11 antivirus with 2 bytes (e.g.: capcom.sys) pic.twitter.com/VwhtsX3sqa
— zerosum0x0🦉 (@zerosum0x0) 2017年10月31日
上のツイートではマルウェアのPEヘッダの実行に影響しない部分を変更しバイパスを試みているがCylanceは検出している。 こういうケースにはシグネイチャベースより機械学習を使う方が有用そう。
おまけ2
Fully undetectable backdooring PE file
上のリンクでは適当なバイナリ(ここでは7zip)に新しくセクション作ってshellcode置いて、そこに飛ぶようにパッチ当ててもアンチウイルスは検知しないという実験をしている。いい話。
chrome.windows.createで作られるwindowはフレームを含むのでOS毎にサイズを変えないといけない
最近、メインのOSをWindowsに移しつつあって、以前つくったChrome拡張の favurl - Chrome ウェブストア をWindowsで使ってみると、URL をツイートするwindowがうまく表示されなかった。ここでのwindowはbrowser_actionで出るpopup、つまりツールバーのアイコンをクリックしたときに出る、manifest.jsonで指定したpopupのwindowではなく、chrome.windows.create()を使って作ったwindowのことである。参考: chrome extentionのpopupを別窓で表示する。 - 脱力系日記
この拡張の場合だと、webページを右クリックして出てくるコンテキストメニューをクリックすると、
このURLをツイートするwindowが出るが、
Windowsだとheightが足りずボタンが表示されなくなっていた。
原因は、chrome.windows.createで指定するheight、widthがフレームを含んだサイズなのに、フレームの大きさがOS毎に違うからだった。異なるOSで見たときにフレームを除いた部分のサイズが変わってしまい見た目が崩壊する。フレームの大きさがOS毎に違うというのがどういうことなのかイメージしにくいが、閉じるボタンや拡大ボタンがあるバーの大きさがOS毎に異なるため、フレーム全体の大きさが変わってしまうということだ。
これにはwindowを作成する前にOSを確認し、OSによってheightとwidthを変えることで対処できる。コードは以下のようになった。
let windowHeight = 107; let windowWidth = 450; if (navigator.platform.indexOf("Win") != -1) { windowHeight = 140; } chrome.windows.create({ url : 'tweet.html', focused : true, type : 'popup', height : windowHeight, width : windowWidth });