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.thriftshared.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に実装を作っていく 実装はテキトーでとりあえずpingadd以外はちゃんと実装してない。

クライアントとサーバーは別で起動するため、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をつかってアドレスを指定できたりします。

アーキテクチャ

http://libvmi.com/assets/images/intro-detail.png

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として出力するように設定を変更します。

こんな感じです。

f:id:drwtsn64:20140204055450p:plain


あ、あと言うまでもないですが、テストコードをコンパイルするためには

#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 )。


f:id:drwtsn64:20140204050622p:plain

それでJavaが入っているかチェックしてみると

f:id:drwtsn64:20140204050817p:plain

ChromeJava 7をサポートしていません」と言われるので、他のブラウザ(SafariFirefox)で確認してみます。

f:id:drwtsn64:20140204051020p:plain

ちゃんとインストールされていますね。

んで、FirefoxからTopCoderのArenaを開いてみます。

f:id:drwtsn64:20140204051201p:plain

お、さっきより先に進めますね。Java Web Startで起動するためのファイルがダウンロードされるようです。

OKをクリックして、ダウンロードして実行してみます。

おっと、何やら文句を言われました。
ふむふむ、Java Runtimeが入ってないと

f:id:drwtsn64:20140204051426p:plain

でも、さっき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

すると正常に起動しました。

f:id:drwtsn64:20140204053151p:plain

起動したので、原因は探りません。とりあえず、/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

The Shellcoder's Handbook: Discovering and Exploiting Security Holes

Practical Malware Analysis

初心者向け
下手なマルウェア解析のセミナーを受けるよりこの本をやったほうがいいです

Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software

Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software

Malware Analyst's Cookbook

初心者向け
解析ツールの紹介。レシピ本なので辞書的に使う感じ

Malware Analyst's Cookbook: Tools and Techniques for Fighting Malicious Code

Malware Analyst's Cookbook: Tools and Techniques for Fighting Malicious Code

The Mac Hacker's Handbook

初心者向け
ちょっと古いけど、貴重なMacセキュリティ本

The Mac Hacker's Handbook

The Mac Hacker's Handbook

iOS Hacker's Handbook

初心者向け
何書いてたかあまり覚えてない

iOS Hacker's Handbook

iOS Hacker's Handbook

Rootkits: Subverting the Windows Kernel

初心者向け
ちょっと古いけど、いい本。rootkit.comが閉鎖されてしまったので
ほとんどリンク切れ

Rootkits: Subverting the Windows Kernel

Rootkits: Subverting the Windows Kernel

The IDA Pro Book

初心者向け
第1版のほうです。情報が古いです

The IDA Pro Book: The Unofficial Guide to the World's Most Popular Disassembler

The IDA Pro Book: The Unofficial Guide to the World's Most Popular Disassembler

A Guide to Kernel Exploitation

中級者向け

A Guide to Kernel Exploitation: Attacking the Core

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で書かれた攻撃コード見てもわかりづらいし、詳細はわからないし
最終的にはネイティブコードとしてどうなっているか知りたくなります。


f:id:drwtsn64:20131218110509p:plain


まずは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という部分があり、そこでバイトコード部分を管理しているようです。

f:id:drwtsn64:20131218113714p:plain

この部分が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が動かない場合があります(アンチデバッグ)。