死んだサーバーで動いていたアプリの引っ越しの歌

記憶の欠片に描いたnginx.confを見つめて
跡切れた想い出重ねる 変わらない夢に 
Oh 死んだHDD

どれだけ涙を流せば
貴方を忘れられるだろう
Just tell me my life
何処まで歩いてみても
涙で明日が見えない

以上、X JAPANのRusty Nailの替え歌でした。

www.youtube.com

We are X!!

2017年のOSSへのコントリビューションまとめ

年末ということで今年のOSSへのコントリビューションを振り返る。来年はもっとやっていきたい。

Metasploit

github.com

GSoCにMetasploitで参加していたので、それで結構プルリクを送った。 GSoC 2017にmetasploitで採択された。 - 脱力系日記

Pull Requests · rapid7/metasploit-framework

後半でかいissueを解決しようとしてたけど、ハマって期間中にプルリクを送れなかった。再度トライしたい。

casein

caseinはRuby on RailsCMSを作るためのgemである。

github.com

scaffoldをするときに指定した名詞が複数形だったときエラーが出るので修正するプルリクを送った。 このgemはバイト先でよく使っている。

Pull Requests · russellquinn/casein

ldap3

ldap3はPythonからLDAPを扱うためのライブラリである。

github.com

サンプルコードが間違っていたのでプルリクを送った。

Pull Requests · cannatag/ldap3

dagger

daggerはMachO、ELF向けのLLVM IRへのデコンパイラLLVMをforkして作られている。

github.com

ビルド方法が間違ってたので修正した。

Pull Requests · repzret/dagger

McSema

IDAのAPIを使ったLLVM IRへのデコンパイラである。 「funded by and used in research for DARPA and the US Department of Defense.」なので強そう。

github.com

READMEのリンク切れを修正した。

Pull Requests · trailofbits/mcsema

Linux Insides

Linux Insideはlinuxの内部構造についてのフリーの本で有志の手でロシア語や中国語への翻訳が進められている。

github.com

これはOSSにプルリクを送ったというわけではないけど、日本語版のリポジトリを作って翻訳を進めている。

github.com

最近やれてないので、誰かプルリクしてほしい。

Linux Rootkit Internalsという題でLTをしてきた

12/23にあったCyber Wargame Christmas Party (大和セキュリティ勉強会)でLinux Rootkit Internalsという題でLTをしてきた。

atnd.org

speakerdeck.com

GitHubLinux rootkitのコードがいろいろ上がっていて、それが結構おもしろくて最近ちらちらコード読んでいたので、それらについて話してきた。 勢いのあるスライドを作りたい。 無事に終わってよかったです。 検知手法についてはまた後日...

機械学習を使っているアンチウイルスを機械学習を使ってバイパスする「gym-malware」の紹介

マルウェアの分類のために使われている機械学習のblind spotを潰すために、機械学習を使っているアンチウイルス機械学習を使ってバイパスする研究が最近活発になってきている。 この記事では、black hat USA 2017で発表されたBot-Vs-Bot-Evading-Machine-Learning-Malware-Detectionとその関連研究を紹介する。これは、情報セキュリティ系論文紹介 Advent Calendar 2017 - Adventarの18日目の記事である。

背景

機械学習マルウェアファミリを一般化できるので、シグネイチャを持たない未知のマルウェアの検出に利用されているが、機械学習のモデルには盲点(blind spot)があり、それにより誤認識を引き起こす。機械学習を用いた画像認識が、ノイズを混ぜられた画像を誤認識することは以前話題になった。

f:id:TAKEmaru:20171218164343p:plain
バスの画像をダチョウと認識している。https://www.popsci.com/byzantine-science-deceiving-artificial-intelligence

画像認識と同じくアンチウイルスも誤認識をすることがあり、それによってアンチウイルスをバイパスされてしまう。

関連研究

当然ながら、機械学習をバイパスするにはモデルの情報を知っているほうが楽で、モデルに関する情報がなければ難易度が上がる。しかしアンチウイルスソフトの開発時にどのようなモデルを使っているか知る術はない。

f:id:TAKEmaru:20171218180411p:plain
https://www.blackhat.com/docs/us-17/thursday/us-17-Anderson-Bot-Vs-Bot-Evading-Machine-Learning-Malware-Detection.pdfより引用

ターゲットとなる機械学習のモデルを知っている状態でそれをバイパスする研究には、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バイナリを作り出している。

github.com

OpenAI gymは強化学習におけるAction、Reward、Stateを以下のように定義している。

f:id:TAKEmaru:20171218200452p:plain
https://www.blackhat.com/docs/us-17/thursday/us-17-Anderson-Bot-Vs-Bot-Evading-Machine-Learning-Malware-Detection.pdfより引用

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

上のツイートではマルウェアの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ページを右クリックして出てくるコンテキストメニューをクリックすると、 f:id:TAKEmaru:20171123230054p:plain

このURLをツイートするwindowが出るが、 f:id:TAKEmaru:20171123230402p:plain

Windowsだとheightが足りずボタンが表示されなくなっていた。 f:id:TAKEmaru:20171123232458p:plain

原因は、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
});

golangでご覧!wて言いたくてgolangでHTTPサーバ書いた

golangでご覧!wて言いたくてHTTPサーバー書いた。PythonのSimpleHTTPServerっぽくなった。名前がgoranなので「golangのgoranでご覧!w」と言えるようになった。

tkmru/goran: simple http server.

雑に以下のようなかんじで使える。localhostにしないと使えないブラウザのAPIとかあって開発のときにシュッとHTTPサーバーを用意できると便利で一応そういう実用的な側面もある。

$ cd DOCUMENT_ROOT
$ goran
2017/11/07 23:22:29 Starting Goran HTTP Server
2017/11/07 23:22:29 Listen : http://127.0.0.1:8888
2017/11/07 23:22:29 RootDir: ./
2017/11/07 23:22:38 127.0.0.1:65208 GET /

いろいろオプションがあって、アドレス、ポート、ルートディレクトリ、configのパスを指定できる。

$ ./goran -h
Usage:
  -a string
        address to use (short) (default "127.0.0.1")
  -address string
        address to use (default "127.0.0.1")
  -c string
        config path to use (short)
  -config string
        config path to use
  -p uint
        port to use (short) (default 8888)
  -port uint
        port to use (default 8888)
  -r string
        root directory to use (short) (default "./")
  -root string
        root directory to use (default "./")

configファイルにはTOMLを使った。以下のように設定できる。

Port = 6000
Addr = "127.0.0.1"
rootDir = "/var/www/hoge"

Golangの勉強になってよかった。暇なときにworkerprocessとかキャッシュとかリバースプロキシの設定をできるようにしたい。

ActiveResourceを使ったRailsアプリをRedisで高速化した

ActiveResource とは

ActiveResourceはRESTful APIマッピングActiveRecord のモデルとして利用可能にするgemで、これを使うとActiveRecordでDB操作を行うのと同じようにRESTful APIを利用できます。

github.com

ボトルネックになることもある

ActiveResourceを頻繁に使うとAPIに過剰にアクセスしているのと同じこととなり、ボトルネックとなりえます。 以下は今回、チューニングの対象となったアプリのNginxのアクセスログkataribeでパースしたものです。

$ cat /var/log/nginx/access.log | kataribe
...
TOP 12 Slow Requests
 1  14.933  GET / HTTP/1.1
 2  14.898  GET / HTTP/1.1
 3  14.722  GET / HTTP/1.1
 4  14.692  GET / HTTP/1.1
 ...

rootへのGETリクエストが14秒台後半で非常に遅いことがわかります。これはActiveResourceを使って取ってきたデータを一覧するページで、ActiveResourceボトルネックとなっていると推測できました。

Redisでキャッシュする

オンメモリ KVSのDBであるRedisにキャッシュすることで高速化を図ります。Redisはメモリ上でデータを管理するため、Read/Writeともに高速でキャッシュとして適しています。ActiveResourceを使ってデータを引っ張ってくるところをキャッシュしてみました。

RedisとRailsアプリケーションの接続にはmperham/connection_pool: Generic connection pooling for Rubyを使いました。コネクションプールというのはDBのコネクションをあらかじめ一定数確立しておいて使いまわす手法で、これを使うとDBへの接続に必要となるオーバーヘッドをカットできWeb/DBの双方の負荷を下げることができます。また、Web/DB間の接続を使いまわすことで同時接続数を節約します。MongoDB gem、ActiveRecord gemは自前でコネクションプールを持っていますが、Redis gemは持っていないので、このgemを使って接続することでコネクションプールを使えるようにしました。以下がRedisへの接続のために追加したファイルです。

config/initializers/redis.rb

# frozen_string_literal: true

# Load the redis.yml configuration file
redis_config = YAML.load_file(Rails.root + 'config/redis.yml')[Rails.env]

Redis.current = ConnectionPool.new(size: 10, timeout: 5) do
  Redis.new host: redis_config['host'], port: redis_config['port']
end

config/redis.yml

default: &default
  host: localhost
  port: 6379

development:
  <<: *default

test:
  <<: *default

production:
  <<: *default

Redisへのアクセス

Redisへのアクセスはcontroller内で以下のように行えますが、ここで注意しないといけないのは、keyがハッシュであるオブジェクトをJSONに変換しRedisにいれると、Redisから取り出しJSONにした際にkeyがstringになるところです。with_indifferent_accessを使うなどして対処しましょう。

Redis.current.with do |redis|
  test_user = { :name => "tkmru", :email => "tkmru@hoge"}
  redis.set('test_user', test_user.to_json)
  test_user = JSON.parse(redis.get('test_user')) # {"name"=>"tkmru", "email"=>"tkmru@hoge"}
  p test_user[:name]                             # nil
  p test_user['name']                            # tkmru
  test_user = test_user.with_indifferent_access
  p test_user[:name]                             # tkmru
  p test_user['name']                            # tkmru
end

結果

$ cat /var/log/nginx/access.log | kataribe
...
TOP 10 Slow Requests
 1  3.566  GET /hoge/231 HTTP/1.1
 2  1.866  GET / HTTP/1.1
 3  1.482  GET /hoge/240 HTTP/1.1
 4  1.479  GET /hoge/226 HTTP/1.1
 5  1.439  GET / HTTP/1.1
 6  1.415  GET /hoge/238 HTTP/1.1
 7  1.410  GET / HTTP/1.1
 8  1.293  GET / HTTP/1.1
 9  1.119  GET /hoge/243 HTTP/1.1
...

14秒台後半だったrootへのGETリクエストが1.4秒ちょっとで終わりました。 /hoge 以下もキャッシュすればもっと早くできそう。

おわりに

今回は原因が自明であったため、kataribeによるNginxのアクセスログのプロファイリングしか行いませんでしたが、pt-query-digestによるMySQLのスロークエリの解析や、stackprofrblineprofによるRubyのコードのプロファイリングを行うとより詳細にボトルネックを見つけることが可能です。 また、Redisでキャッシュすることによる高速化は、ActiveResourceを使ったアプリケーションに限定されるテクニックではなく、様々なアプリケーションで使うことができます。やっていきましょう。