scroogeを使ってみる
概要
下調べのため、Scalaでthriftを使ってみる。 Apache Thrift公式でScalaはサポートされてないので、twitter/scroogeを使うのが一般的らしい。
環境
Max OSX 10.10.5 Yosemite sbt 0.13.13
下準備
sbtのインストール
$ brew install sbt
sbtでプロジェクトを作る
`$ sbt new sbt/scala-seed.g8
build.sbtにscroogeの依存関係を書く
Scalaのバージョンとライブラリのバージョンについては The Central Repository Search Engine で調べる。 Scala2.12を使いたければ、 scrooge-coreは4.12.0以降、finagle-thriftは6.41.0以降にしないといけないが 2.12だとコンパイルが通らないので、2.11を使う。scrooge-coreとfinagle-thriftの最新版を使うと下のような エラーが出るので、バージョンはscroogeのドキュメントに合わせておく。
[error] /tmp/thrift2/target/scala-2.11/src_managed/main/tutorial/Operation.scala:50: class EnumUnknownOperation needs to be abstract, since method originalName in trait ThriftEnum of type=> String is not defined [error] case class EnumUnknownOperation(value: Int) extends tutorial.Operation {
scroogeの公式のドキュメントではlibthriftのversionが0.9.2になってるけど、sbt compileを実行すると evictedのwarningが出るので0.5.0-1にしている。
lazy val root = (project in file(".")). settings( inThisBuild(List( organization := "com.example", scalaVersion := "2.11.1", version := "0.1.0-SNAPSHOT" )), name := "Hello", libraryDependencies ++= Seq( "org.apache.thrift" % "libthrift" % "0.5.0-1", "com.twitter" %% "scrooge-core" % "4.6.0" exclude("com.twitter", "libthrift"), "com.twitter" %% "finagle-thrift" % "6.34.0" exclude("com.twitter", "libthrift") ) )
project/plugins.sbtの編集
project/plugins.sbtを作る maven-centralやmaven.twttr.comにscrooge-sbt-pluginの4.5.0はないように見えるけど どこから持ってきてるのだろう…
resolvers += "Twitter Repository" at "http://maven.twttr.com" addSbtPlugin("com.twitter" % "scrooge-sbt-plugin" % "4.5.0")
thriftファイルをsrc/main/thriftにおく
サンプルのthriftファイルは Apache Thrift - Index of tutorial/ の tutorial.thriftとshared.thriftを使う。
thriftからScalaのコードを生成
sbt compile
を実行すると、target/scala-2.11/main/の下にコードが生成される。
thriftを使ったコードを書いていく
クライアント側
src/main/scala/example/Hello.scalaができてると思うのでクライアントのコードに書き換えていく
thriftでクライアントを作る時はThrift.client.newIface
を使うらしいので、クライアントを作る。
あとはthriftで設定したメソッドが使えるのでそれを使う。
package example import com.twitter.finagle.Thrift import com.twitter.util.{Await, Duration} import tutorial.Calculator import tutorial.Calculator.Calculate object Hello extends Greeting with App { println(greeting) val client = Thrift.client.newIface[Calculator.FutureIface]("localhost:9090") client.ping() print(Await.result(client.add(1, 2), Duration.fromSeconds(3))) } trait Greeting { lazy val greeting: String = "hello" }
サーバー側
自動生成されたCalculatorを継承して、CalculatorImplに実装を作っていく
実装はテキトーでとりあえずping
とadd
以外はちゃんと実装してない。
クライアントとサーバーは別で起動するため、Appを継承してmain部分を作っておく
thriftからサーバを作るにはThrift.server.serveIface
を使って、引数に実装したCalculatorImplを渡してあげればよい。
package example import java.net.InetSocketAddress import com.twitter.finagle.Thrift import com.twitter.util.{Await, Duration, Future} import shared.SharedStruct import tutorial.{Calculator, Work} class CalculatorImpl extends Calculator[Future] { /** * A method definition looks like C code. It has a return type, arguments, * and optionally a list of exceptions that it may throw. Note that argument * lists and exception lists are specified using the exact same syntax as * field lists in struct or exception definitions. */ override def ping(): Future[Unit] = { println("ping") Future.value("ping") } override def add(num1: Int, num2: Int): Future[Int] = { Future.value(num1 + num2) } override def calculate(logid: Int, w: Work): Future[Int] = { Future.value(0) } /** * This method has a oneway modifier. That means the client only makes * a request and does not listen for any response at all. Oneway methods * must be void. */ override def zip(): Future[Unit] = { Future.value(null) } override def getStruct(key: Int): Future[SharedStruct] = { Future.value(null) } } object CalculatorServer extends App { val server = Thrift.server.serveIface("localhost:9090", new CalculatorImpl) Await.ready(server) }
実装が終わったら忘れずにコンパイル
sbt compile
実行
sbt runMainで実行していく。
sbt 'runMain example.CalculatorServer' sbt 'runMain example.Hello'
libvmiのサンプルコードを読んでみました
VMI(Virtual Machine Introspection)っていうOut-VMからIn-VMの情報を取得するやり方を調べていて、
XenとかKVMとかで使えるlibvmiっていうライブラリをちょっと読んでみました。
libvmiが何をしてくれるかというとvmmからGuest OSへのアドレス指定の際に必要になるGuest OS内の仮想アドレス->物理アドレスの変換やシンボル
RekallというMemory Forensic Toolのprofileをつかってアドレスを指定できたりします。
アーキテクチャ
1. VMMがlibvmiにシンボル名でデータを問い合わせます
2. アーキテクチャごとに用意されたシンボルとアドレスのテーブルからアーキテクチャに対応した仮想アドレスを取得します
3, 4. libvmiがGuest OSのPD(Page Directory)とPT(Page Table)を参照して、Guest OSの仮想アドレスをGuest OSの物理アドレスに変換します
5. 変換したGuest OSの物理アドレスを参照してGuest OSのデータを取得します
libvmiのサンプルソースを読む
examples/dump-memory.c
Guest OSの物理メモリをダンプするプログラムです。
while (address < size) { /* write memory to file */ if (PAGE_SIZE == vmi_read_pa(vmi, address, memory, PAGE_SIZE)) { /* memory mapped, just write to file */ size_t written = fwrite(memory, 1, PAGE_SIZE, f); // omitted } else { /* memory not mapped, write zeros to maintain offset */ size_t written = fwrite(zeros, 1, PAGE_SIZE, f); // omitted } address += PAGE_SIZE; }
vmi_read_paはlibvmiが提供するAPIで第1引数のaddressでGuest OSの物理アドレスを指定して、そこから第4引数のsize分データを第3引数のmemoryに読み取ります。
vmi_read_paは最終的にlibvmi/read.cのvmi_read()を呼ぶのですが、vmi_read_paは指定したアドレスを物理アドレスとして扱うので、特にアドレスの変換は行っていません。
access_context_t ctx = { .translate_mechanism = VMI_TM_NONE, .addr = paddr };
仮想アドレスやシンボルを使ってデータを読み取りたい場合は、内部でaccess_context_tの.addrや.translateを変更するのですが、これは別のAPIが提供されているのでライブラリのユーザーは気にしなくてよいです。
examples/va-pages.c
Guest OSのプロセスごとにPT(Page Table)をダンプするプログラムです。
SETUP_REG_EVENT(&cr3_event, CR3, VMI_REGACCESS_W, 0, cr3_callback); vmi_register_event(vmi, &cr3_event); while(!interrupted){ printf("Waiting for events...\n"); status = vmi_events_listen(vmi,500); if (status != VMI_SUCCESS) { printf("Error waiting for events, quitting...\n"); interrupted = -1; } }
プロセスの切替時にCR3レジスタが書き換わるので、
書き込みがあったときにコールバックが呼ばれるようにレジスタイベントを登録してます。
SETUP_REG_EVENTの第4引数に0を設定していますが、これはevent->reg_event.equalのフィルタを使いませんという意味です。
event->reg_event.equalに0以外が指定してあると指定した値に書き換わるときのみevent->reg_event.valueに値がセットされます。
event_response_t cr3_callback(vmi_instance_t vmi, vmi_event_t *event) { va_pages = vmi_get_va_pages(vmi, event->reg_event.value); GSList *loop = va_pages; while(loop) { page_info_t *page = loop->data; // Demonstrate using access_context_t access_context_t ctx = { .translate_mechanism = VMI_TM_PROCESS_DTB, .addr = page->vaddr, .dtb = event->reg_event.value, }; uint64_t test; if(VMI_FAILURE == vmi_read_64(vmi, &ctx, &test)) { // emitted } loop=loop->next; } free_va_pages(); return 0; }
次にコールバック部分です。vmi_get_va_pages()を呼び出してます。
vmi_get_va_pages()はIntel CPUでNO PAEのときは最終的にlibvmi/arch/intel.cのget_va_pages_nopae()を呼び出します。
この関数はPD(Page Directory)とPT(Page Table)を辿ってページの情報を取得します。
// emitted for(pgd_index = 0; pgd_index < PTRS_PER_NOPAE_PGD; pgd_index++, pgd_location += entry_size) { uint32_t pgd_entry = pgd_page[pgd_index]; if(ENTRY_PRESENT(vmi->os_type, pgd_entry)) { // emitted uint32_t pte_location = ptba_base_nopae(pgd_entry); // emitted uint32_t pte_index; for(pte_index = 0; pte_index < PTRS_PER_NOPAE_PTE; pte_index++, pte_location += entry_size) { uint32_t pte_entry = pt_page[pte_index]; if(ENTRY_PRESENT(vmi->os_type, pte_entry)) { page_info_t *p = g_malloc0(sizeof(page_info_t)); p->vaddr = pgd_base_vaddr + pte_index * VMI_PS_4KB; p->paddr = get_paddr_nopae(p->vaddr, pte_entry); p->size = VMI_PS_4KB; p->x86_legacy.pgd_location = pgd_location; p->x86_legacy.pgd_value = pgd_entry; p->x86_legacy.pte_location = pte_location; p->x86_legacy.pte_value = pte_entry; ret = g_slist_prepend(ret, p); } } } }
コールバックのこの部分
access_context_t ctx = { .translate_mechanism = VMI_TM_PROCESS_DTB, .addr = page->vaddr, .dtb = event->reg_event.value, };
今度は.addr = page->vaddrが指定されているので、
vmi_read_64(vmi, &ctx, &test)で指定したアドレスを仮想アドレスとして扱います。
event->reg_event.valueの値をCR3レジスタの値として仮想アドレスから物理アドレスに変換します。
TopCoderで使ってるプラグインの設定
プラグインの設定の仕方は
TopCoderでCodeProcessor+TZTester+FileEdit - Gulfweed
が詳しいのでこちらを参考にしてください。
ただ、この記事を見ていて、使い方のとこでつまづいたのでメモしておきます。
つまづいたのはこの部分です。
外部ファイルをローカルコンパイルして実行するとTZTesterが生成したテストコードが
実行されて結果が出る。
外部ファイルをローカルコンパイルしてみるのですが、
"Use Line Comments for Problem Description" 設定をオンにしているのに、問題文がコメントになっていないため、ローカルコンパイルに失敗します。
解決方法がよくわからないため(というより調査に時間を取りたくない)
問題文をHTMLとして出力するように設定を変更します。
こんな感じです。
あ、あと言うまでもないですが、テストコードをコンパイルするためには
#include <iostream> #include <sstream> using namespace std;
をテンプレートに追加してください
MacユーザーがTopCoderをはじめるうえでつまづくポイント
MacユーザーがいざTopCoderでArenaを開こうとするとJavaがインストールされてないって怒られることがあります。
結論だけ先に言うと、自分の環境では/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javawsからJREをうまく見つけられないみたいなので、他のパスにインストールされているjavawsを使うとうまく起動しました
(例えば/System/Library/Java/Support/Deploy.bundle/Contents/MacOS/javaws )。
それでJavaが入っているかチェックしてみると
「ChromeはJava 7をサポートしていません」と言われるので、他のブラウザ(SafariやFirefox)で確認してみます。
ちゃんとインストールされていますね。
んで、FirefoxからTopCoderのArenaを開いてみます。
お、さっきより先に進めますね。Java Web Startで起動するためのファイルがダウンロードされるようです。
OKをクリックして、ダウンロードして実行してみます。
おっと、何やら文句を言われました。
ふむふむ、Java Runtimeが入ってないと
でも、さっきJavaが入ってる確認はしたので、これはおかしいです。
では、先ほどダウンロードしたファイルを直接実行してみます。
Java Web Startで実行するので以下のコマンドでいけるはずです。
$ javaws <filename>
すると、さきほどと同じように文句を言われました。
$ javaws ~/Downloads/ContestAppletProd.jnlp No Java runtime present, requesting install. Unable to locate a Java Runtime to invoke.
javawsの場所を確認してみます。
$ which javaws /usr/bin/javaws
/usr/bin/javawsはシンボリックリンクなので、
参照先のファイルを調べます。
$ ls -l /usr/bin/javaws /usr/bin/javaws -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javaws
/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javawsにJava Web Startはあるみたいです。
Is there any way to reenable javaws with Java 6 on OSX Lion? - Stack Overflow ここを見てみると、Javaのインストールパスは複数あるようです。
/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/ /System/Library/Java/Support/Deploy.bundle/Contents/MacOS
/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands以外のパスのJava Web Startを使ってみます。
$ /System/Library/Java/Support/Deploy.bundle/Contents/MacOS/javaws ~/Downloads/ContestAppletProd.jnlp
すると正常に起動しました。
起動したので、原因は探りません。とりあえず、/System/Library/Frameworks/JavaVM.framework/Versions/Current/CommandsからJREをうまく見つけられていないようです。
セキュリティ関連本譲ります
引っ越しするので、もう読まなくなったセキュリティ関連本を譲ります。
送料のみ負担をお願いします。恵比寿あたりまで来てもらえれば、直接手渡しもできます。
できればお金のない学生さんに譲りたいなぁ。社会人は身銭を切って買ってくださいw。
とりあえず締め切りは1/24にしておきます。貰い手がいない場合は処分します。
ほしい方は
- ほしい本のタイトル
- 住所(手渡しがいい方はその旨)
- 名前(手渡しの方はいらないかな)
twitterで @drwtsn64 までDMください。
メールがいい方はdrwtsn64 あっと gmail.comまで連絡ください
※初心者とか中級者っていうのは僕の勝手な判断です
※Amazonのリンクのメディアがkindle版になってますけどペーパーのほうです
The Shellcoder's Handbook
初心者向け
古いけどいい本
The Shellcoder's Handbook: Discovering and Exploiting Security Holes
- 作者: Chris Anley,John Heasman,Felix Lindner,Gerardo Richarte
- 出版社/メーカー: Wiley
- 発売日: 2011/02/16
- メディア: Kindle版
- この商品を含むブログを見る
Practical Malware Analysis
初心者向け
下手なマルウェア解析のセミナーを受けるよりこの本をやったほうがいいです
Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software
- 作者: Michael Sikorski,Andrew Honig
- 出版社/メーカー: No Starch Press
- 発売日: 2012/02/24
- メディア: Kindle版
- この商品を含むブログを見る
Malware Analyst's Cookbook
初心者向け
解析ツールの紹介。レシピ本なので辞書的に使う感じ
Malware Analyst's Cookbook: Tools and Techniques for Fighting Malicious Code
- 作者: Michael Ligh,Steven Adair,Blake Hartstein,Matthew Richard
- 出版社/メーカー: Wiley
- 発売日: 2010/10/01
- メディア: Kindle版
- この商品を含むブログを見る
The Mac Hacker's Handbook
初心者向け
ちょっと古いけど、貴重なMacセキュリティ本
- 作者: Charlie Miller,Dino Dai Zovi
- 出版社/メーカー: Wiley
- 発売日: 2011/03/21
- メディア: Kindle版
- この商品を含むブログを見る
iOS Hacker's Handbook
初心者向け
何書いてたかあまり覚えてない
- 作者: Charlie Miller,Dion Blazakis,Dino DaiZovi,Stefan Esser,Vincenzo Iozzo,Ralf-Philip Weinmann
- 出版社/メーカー: Wiley
- 発売日: 2012/04/30
- メディア: Kindle版
- この商品を含むブログを見る
Rootkits: Subverting the Windows Kernel
初心者向け
ちょっと古いけど、いい本。rootkit.comが閉鎖されてしまったので
ほとんどリンク切れ
Rootkits: Subverting the Windows Kernel
- 作者: Greg Hoglund,Jamie Butler
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2005/07/22
- メディア: Kindle版
- この商品を含むブログを見る
The IDA Pro Book
初心者向け
第1版のほうです。情報が古いです
The IDA Pro Book: The Unofficial Guide to the World's Most Popular Disassembler
- 作者: Chris Eagle
- 出版社/メーカー: No Starch Press
- 発売日: 2009/08/20
- メディア: Kindle版
- この商品を含むブログを見る
A Guide to Kernel Exploitation
中級者向け
A Guide to Kernel Exploitation: Attacking the Core
- 作者: Enrico Perla B.Sc. Computer Science University of Torino M.Sc. Computer Science Trinity College Dublin,Massimiliano Oldani
- 出版社/メーカー: Syngress
- 発売日: 2010/09/29
- メディア: ペーパーバック
- クリック: 5回
- この商品を含むブログを見る
ActionScriptバイトコードをJIT化したネイティブコードのアドレスを特定する
これはカーネル/VM Advent Calendar 2013の記事です。
はじめに
セキュリティ関連の話です。今やソフトウェアセキュリティの分野としては
OSやコンパイラのセキュリティ機構(例えばDEP+ASLR)が充実してきていますので、
一昔前に比べると攻撃しづらい、成功しづらい状態になっています。
こういうセキュリティ機構を回避するために、近年はデータファイル(PDFなど)
が攻撃に使われていて、最近ではデータファイル系も
セキュリティ機構(例えば、Adobe Readerのサンドボックス)が充実しつつあります。
他のデータファイルに比べると対応が遅れているFlashファイルが2010年くらいから
攻撃に使われるようになりました。きっかけは2010年にBlackHat DCで発表された
JIT Sprayで、DEP+ASLRを回避できる画期的な攻撃手法でした。
これは既に最近のFlash Playerでは対策が入っています。
ここ数年様々なFlash Playerの脆弱性が発見されては、対策されている状態ですが
2013年に入っても脆弱性は発見されており、PoC(Proof of Concept)も出回っているようです。
これらの状況を追うためにはExploit解析するのが一番よいのですが、どこから手をつけたら
いいのかという感じです。Exploit解析のためにはネイティブコードを追う必要がありますので
この記事ではExploit解析の一つの山場となるswfファイルからネイティブコードに変換される
部分を説明したいと思います。
本題
Flashの脆弱性が発見されて、攻撃コード(およびPoC)が出回った時、どういうExploitテクニックが使われているか気になりますよね。
そういうときにswfファイルに含まれているabc部分をデコンパイルするとActionScriptとして内容を追えるのですが、
デコンパイラも完璧ではないし、そもそもActionScriptで書かれた攻撃コード見てもわかりづらいし、詳細はわからないし
最終的にはネイティブコードとしてどうなっているか知りたくなります。
まずはswfファイルがどういうふうに実行されるかを確認します。
ActionScriptをビルドしてswfファイルを作るのですが、実装部分はswfファイルにバイトコードとして存在してます。
swfファイルを再生するとFlash PlayerがswfファイルをパースしてAVMという仮想マシン上でバイトコードを実行します。
AVMにはJITコンパイラ(nanojit)が含まれていて、バイトコードは適宜ネイティブコードに変換されて実行されます。
ここで追いたいのはそのネイティブ部分です。
AVMがバイトコードをネイティブコードに変換して実行するまでにはいくつかフェーズがあります。
1. バイトコードを検証して、不正部分があれば終了
2. バイトコードをLIRに変換
3. nanojitがネイティブコードに変換
アーキテクチャはこちらで詳しく説明されています(記事書いてるときに見つけました)
steps to phantasien t(2008-05-06)
あ、あとFlash PlayerをIDAとかで見ていってもいいのですが、さすがにつらいので
TamarinというAVMのオープンソース実装も併用します
では、追っていきましょう
まずはじめに、swfファイルにmethod_bodyという部分があり、そこでバイトコード部分を管理しているようです。
この部分がAVM上でどうなっているかというと
パースされたあと、MethodInfoクラスで管理されます。
結論からいうと、nanojitでネイティブコードに変換された後のアドレスは
MethodInfo._implGPR
に保存されるので、そこが書き変わるとこをブレークポイント
設定して見てればいいということになります。
MethodInfoが作られたあと、バイトコードの検証に移ります。
MethodInfo.verify() {
...
// 長いので省略
...
}
ここで型のチェックなどが行われます。ここでチェックに漏れると
atom confusionとかtype confusionと呼ばれる脆弱性の原因になります
検証を追えるとnanojitに受け渡すため、バイトコードをLIRに変換します
interpBoxed() {
...
// 長いので省略
...
nanojitを使いネイティブコードに変換します
CodegenLIR::emitMD() { ... assm->beginAssembly(frag); assm->assemble(frag, &bufreader); assm->endAssembly(frag); ... union { GprMethodProc fp; void *vp; } u; u.vp = frag->code(); info->setNativeImpl(u.fp); // mark method as been JIT'd info->_flags |= MethodInfo::JIT_IMPL; ... }
info->setNativeImpl()でMethodInfo._implGPRに
ネイティブコードのアドレスが保存されます
ネイティブコードが実行されます
MethodInfo::verifyCoerceEnter() {
...
return f->invoke(env, argc, args);
}
変換されたネイティブコードはこんな感じです
... 00CAFEF1 mov dword ptr [ebp-88h],ecx 00CAFEF7 mov dword ptr [ebp-84h],edi 00CAFEFD mov dword ptr [ebp-80h],eax 00CAFF00 mov dword ptr [ebp-7Ch],0 00CAFF07 mov eax,dword ptr [ebx] 00CAFF09 push esi 00CAFF0A push 3 00CAFF0C push ebx 00CAFF0D call eax 00CAFF0F add esp,0Ch ...
これでネイティブコードに行き着けたので、Exploit解析に入れますね
Exploit解析はそれはそれで大変なのですが、ひとつの山場は超えました
ので、とりあえず目的達成です
注意する点
Flash Playerの実装はTamarinを使っていますが、拡張されていたりして、
データ構造やフローが異なることがあります
例えば、Flash Player 11.3.270.ocxでは
MethodInfoクラスの_method_idや_flagsのオフセットが違います
付録
解析の方法
swfファイルを使った攻撃パターンですが
swfファイルをサーバーに設置して、IEで閲覧とか
swfファイルをMS Officeに埋め込むことができるので、
Wordに脆弱性のあるswfファイル埋め込んで、メールに添付とか
がよくあると思います。
でも、解析する時にIEやWordがswfファイルを開いて攻撃コードを展開するとこまでデバッガで
追ってると死にます。IEもWordもサイズが大きいのでデバッグできる環境を整えるのも大変です(貧弱な環境だとすぐ固まります)。
それにIEはマルチプロセスだし、マルチプロセスをソースなしにデバッグとか
考えただけで恐ろしいです。
Adobe Flash Playerサポートセンターってとこで
スタンドアロンのFlash Playerをバージョンごとにダウンロードできるので、これを使うのがおすすめです。
ActiveX版を解析したい場合は、ocxファイルを使うexeを自分で書いてswfファイルを読み込ませる必要があります。
あと、Debug版はExploitが動かない場合があります(アンチデバッグ)。