柿の種至上主義

やっていく

セキュキャン(2017)応募用紙晒す

応募用紙を晒します。

とっても恥ずかしいです。「あっこいつ変なこと書いてる(クスッ」とか思ってください。

(引用の仕方とかちゃんとしてないのは本当に良くないと思います(反省している))

(追記:20170615)

オッ

目次

共-1 (1)

1 .

 リーダブルなLISP処理系を作りました。この処理系は、初めて言語実装に触れるという人が難なく理解できることを目標として作られました。この目標を達成するために必要な要素として、以下の項目を意識して開発しました。

  ・可読性を失うようなテクニックは使用しない

  ・可読性を損ねない範囲でコード量を少なくする

 1つめに関して、この処理系は初めて処理系を読んでみる人に読まれることを想定していため、まず読みやすいコードでなければなりません。ところどころマクロを使用していますが、その箇所はマクロを使用したほうが読みやすくなると判断したためです。

 2つめに関して、初めての人にとってコード量は、今から読み始めるか否かを決定する重要な要素だと考えます。コード量でコードのわかりやすさや性能が計れるものではないと思いますが、初めて処理系を読む人にとっては取っ掛かりやすさは最も重要と考えても良いと判断しました。

 以下でコードを公開しています。

https://github.com/shinkwhek/microlisp

2 .

 弊校の学園祭で行われたメイン企画(3つの学科がそれぞれの技術を活かして一つの作品を創り上げる企画)にて、プロジェクションマッピングブースに携わりました。プロジェクションマッピングブースの他にはゲームブースなどがあり、互いに連携して映像が変化します。インタラクティブがテーマとなっており、この作品を見る人がゲームを通じて作品に干渉できるようになっています。

 以下は作品の動画へのリンクです。

https://www.youtube.com/watch?v=tylQ3OpYGl4

共-1 (2)

1 . LISP処理系

 C言語で、標準ライブラリ以外使わずフルスクラッチで書きました。lexやyaccを使うことも考えましたが、試しにパーサーを書いてみたりS式の表現方法を模索していく中で、標準ライブラリのみでも簡潔に書けることに気付き、標準ライブラリのみで書こうと決めました。「自身の勉強のため」という側面も勿論あります。

 課題や追加したい機能として、

  ・GC

  ・コンパイル機能

があります。

 1つめのGCに関して、処理系の性能という観点でGCは必須だと考えますが、この処理系は動的にメモリを確保したら解放せずそのままです。GCの実装にはmark-and-sweepを採用するのが良いかと考えています。理由として、

 ・LISPのデータ構造は再帰下降的でルートから順番に調べていく処理との相性が良い    ・可読性の高さが前提となるため、あまり複雑なアルゴリズムでないほうが良い

と判断したためです。

 2つめのコンパイル機能に関して、私自身がそうであるように、言語実装となるとインタプリタのみでは飽き足らずコンパイルをしてみたくなると思います。そんな需要にも答えられるようコンパイル機能をつけたいと考えています。しかし動的型付けの言語でコンパイルを行う際、各値の型情報をどのように表せばいいのかわかっていません。現段階の構想として、各値用のメモリを確保する際に数ビット分余分に確保して(もしくは先頭に確保して)、そこで型を表現するのが良いのではないかと考えています。

2 . メイン企画内のプロジェクションマッピング

 Unity上で映像を作成しました。言語はC#です。マッピングにはMeshWarpServerを使いました。反省として、ゲームブースとの連携がうまく行かなかったことが上げられます。時間の問題もありデバッグできずに本番を迎えてしまいました。教室を一つ借りていたこともあり、本番直後すぐに解体しなければならず、最後までバグの原因を特定できなかったのが惜しいです。

共-1 (3)

当時ブログを書いていなかったので公開しているコードや動画へのリンクを貼ります。

言語処理系: https://github.com/shinkwhek/microlisp

メイン企画(プロジェクションマッピング): https://www.youtube.com/watch?v=tylQ3OpYGl4

新しいブログを用意しました。用意したばかりなので中身は空です。http://shinkehek.hatenadiary.com/

共-2 (1)

 言語処理系における「環境」の実装です。上記のLISP処理系を作っていくなかで、そもそも環境の概念を知らなかったのでどうすれば変数の情報を持っておくことができるのかわかりませんでした。((環境の概念を知るまで、環境のようなものを行き当たりばったりで実装していましたが、結果的には現在実装された環境ととても似ている形になりました)

 今ぶつかっているもので、川合秀実氏著『30日でできる!OS自作入門』付属のtolsetというツール群を用いずにgcc、nasm、lnでOSの開発ができないか模索しています。

共-2 (2)

 まずはじめに他の人の実装を読んで言語実装の雰囲気を掴もうということでrui314さんのminilispやPeter NorvigさんのLispyを読んでみました。そのなかで環境なるものの存在を知りました。はじめのうちは見様見真似で実装してみて、最後は自身の処理系で書きやすい方法を考えて書き直しました。環境含めプログラミング言語を形式的に定義することができることを知ったのは、とりあえず処理系が完成したあと、Twitterで教えてもらった『プログラム意味論』(1994,横内寛文氏著)を読んだときです。

 tolsetに準ずる機能を持つツールの自作に関しては調査中です。今わかっていることとして、川合秀実氏著『30日でできる!OS自作入門』付属のプロジェクトの中に入っているasmhead.nasを再実装しなければならないことがあります。ブートローダ本体はNASMでそのまま書き直せば良さそうです。

共-2 (3)

 まずは既存の言語処理系がどのように動いているのかを知るのが一番だと考えます。あまり大きな処理系だと全体像を掴むのに時間がかかってしまう可能性があるので、なるべく小さい処理系を選んだほうが良いと思います。

 大きな処理系を読めば知りたい情報と同時に様々知見を同時に得ることができると思いますが、ここでは壁を経験している人がよりスムーズに問題を解決できるように寄り道をし過ぎることの無い様にしないといけないと考えて、小さい処理系を読むのを勧めることにしました。

 独自のデータ構造を使っていくときに、データ構造を忘れたりなどを理由にリークを起こすことが多くあるので、デバッグを行うときはvalgrindやgdbなどを使用すると良いことを伝えます。

共-3 (1)

Dトラックを中心にカーネルマルウェアに関する講義を受講したいと考えています。私がプログラミングを始めたきっかけは、カーネルマルウェア、セキュリティといった単語への漠然とした憧れからでした。そして今でもその分野への興味は変わっていませんが、言語実装ばかりしていた自覚があります。これについては共通問題3_2で言及します。

 さて、具体的に受講したい講義は、

・D1 Linuxカーネルを理解して学ぶ脆弱性入門

・D2~3 カーネルエクスプロイトによるシステム権限奪取

・D4 マルウェア x 機械学習

・D5 The Anatomy of Malware

・E6~7 インシデントレスポンスで攻撃者を追いかけろ

です。

各講義を選択した理由は以下のとおりです。

 D1に関して、基本的な知識が足りていない私にとってカーネルの内部構造や脆弱性の基本的な解説がされる講義は重要であり、必ず受講したいと思ったためです。

 D2~3に関して、「システムの権限を奪われないようにしろ」といったことを何度も耳にして来ましたが、実際のシステム権限奪取に用いられる技術やその対応策を何も知らないままでいることにもどかしさを感じているため、セキュリティ意識の向上のためにもカーネルエクスプロイトの基本を知る必要があると考えたためです。また、それ以上にカーネル脆弱性やそれを利用した攻撃、成されている対応策に関心があるためです。

 D4に関して、マルウェアそのものに関する知識だけでなく、多様なアプローチによってどうマルウェアを捉えるかといった議論は、マルウェア解析の目標であると思っています。D4の講義は機械学習によるマルウェアへのアプローチということで受講したいと考えました。

 D5に関して、実際にマルウェアのコードを読み、その結果を活用するという体験ができるということに興味を持ち、受講したいと考えました。

 E6~7に関して、攻撃に対して対抗する側が実際に何を行っているのか、それを行うための解析技術を知りたいという理由で受講したいと考えました。

共-3 (2)

まずキャンプへの応募を決める前の私の状態について書きます。セキュリティに漠然とした興味を持ったのがプログラミングを初めたきっかけでしたが、今までやってきたことは

・ゲーム開発やインタラクティブアート

・言語実装

などでセキュリティに関して特別学ぶといったことはありませんでした。何かプログラムを書くときも、とりわけセキュリティを意識した開発はしてこなかった自覚があります。しかしセキュリティに対する興味を忘れることはなく、いつか勉強しよういつか勉強しようと思いながらここまで来たというのが正直なところです。

 さて本題ですが、このキャンプでセキュリティは勿論、自身のこれからの開発をよりセキュアにしていく技術を身に着けたいと考えています。何か学んだことに関して、如何にそれを道具として、自らの手足としてこれからに活かすことができるかが重要だと考えています。得た知識、技術を余さず開発に取り入れ、キャンプの全てを血肉としてやろうという気概を持って参加します。

 さらに、セキュリティの専門家や、他の参加者との関わりから、互いに刺激し切磋琢磨し合えるようなライバルや仲間を増やしていきたいと思っています。

 このセキュリティ・キャンプによって、今まで溜め込んだ漠然としたセキュリティに関する興味が具体性を持って顕現させ、私の中にセキュリティ分野の黎明の凱旋を訪れさせようと思っています。

選択A-1

まず、まとめを書きます。

 ・この通信の意図するもの:/etc/passwdを抜き取るためにコマンド'cat /etc/passwd'を実行させようとしている。

 ・何の脆弱性を狙っているか:Apache Struts2脆弱性を狙っている。struts2-rest-showcase/orders.xhtmlをリクエストしていることから判断した。リクエストを送る際にContent-Typeに'cat /etc/passwd'を含む攻撃コードを追加することでそのコードを実行させようとしている。

 ・通信フローに欠けている箇所にはどのような内容が想定されるか:1)No.11の通信の前に、193.168.74.130から193.168.74.1へのSeq,Ackの値の観点で辻褄が合うような通信が欠けている。詳しい内容はわからなかった。2)また、その箇所で何かが原因で通信の順序が入れ替わっている可能性も考えられる。

 次に、回答するまでの過程を示すため、当時の仮定、わかったこと、そこから想定したものを時系列順にまとめることにしました。作業ログとも言えます。

==== ==== ====

 pcapファイルとは何かから調べ、パケットを解析するためのファイル形式だと知る。

 wiresharkを用いてパケット解析を行うことにした。


 No.1~3の通信より、まず192.168.74.1がSeq=0,Ack=0を、次に192.168.74.130がSeq=0,Ack=1を、最後に192.168.74.1がSeq=1,Ack=1を相手に送っていることから、これは3ウェイハンドシェークと呼ばれる通信が正常に確立されたか確認するルーティーンであることがわかった。さらに通信する相手が192.168.74.1であること、自分(サーバ)が192.168.74.130であることがわかった。


 No.4の通信から、/struts2-rest-showcase/orders.xhtmlを要求していることを確認できた。

 このことから、Apache Struts2の何かしらの脆弱性を利用しようとしているのではないかと推測する。


 恐らく、No.5でNo.4の要求に従い相手にデータを送信している。

 No.6のACKは正常にデータを受け取ったことを示しているはずだが、Ack=1である理由はわからなかった。No.7ではAck=1641と増えているので、(1641 - 1)より、1640byteのデータを受け取ったと説明ができる。しかし、No.6の通信が間にあることを無視するわけにはいかない。ここで3つのことを推測してみる。

 ・相手がデータを送ってくる直前に何かを送ろうとしたが、タイミングが悪く順番通りにいかなかった。

 ・こちらか相手側のエラーで正しくデータが送られなかった。(ならばNo.7の通信は何かという話になる)

 ・そもそもデータを受け取ったらとりあえずAck=1を送るという仕組みがある。(相手のAckはシーケンス番号と受信したbyte数を足した値になるはずなので考えにくい)


 以降192.168.74.1からの一方的な送信であるため、192.168.74.130の想定していない通信をしていると考える。想定したいない通信となると以下の2つのことが考えられる。

 ・攻撃を目的とした通信。

 ・攻撃のつもりはないが、何かを試してみた結果、192.168.74.130の想定外なところを突いてしまった。

まず、No.9で192.168.74.1が[FIN,ACK]を送っていることが気になった。もしこの後、No.10で192.168.74.130が[ACK]、No.11で続けて[FIN,ACK]、最後に192.168.74.1が[ACK]を送っていれば、これは通信の終わりを示す。しかしそうなっていないので別の可能性を考える。

 前述したようにApache Struts2脆弱性を利用して何かをしようとしていると考えると、この部分で何かしらの攻撃をしようとしているのではないかと考えることができる。

 Apache Struts2脆弱性を調べた結果、悪意のあるリクエストを送ることで任意のコードを実行することができるということがわかった。具体的には、Content-Lengthに大きな値を入れてエラーを起こしContent-dispositionにコードを書いたり、Content-Typeに任意のコードを書いて実行するなどがあるらしい。具体的に何をしようとしているかまではわからなかった。


 No.4の通信を確認するとContent-Typeに何かしらのコードのようなものを見て取れた。生データの中に(#cmd=‘cat /etc/passwd’)を発見。

 すなわちこの攻撃は、Apache2 Struts2脆弱性を利用して/etc/passwdを読み取ることを目的とした攻撃であることがわかる。

==== ==== ====

 次に、通信フローのうち欠けている箇所を想定する。

現状、No.5まではわかっていてNo.6,7は解読中。それ以降はわかっていないので、まずはNo.6,7を読むことにする。


 ここでNo.5とNo.8に注目して、No.5では192.168.74.130から192.168.74.1へSeq=1,Ack=1093の通信が、No.8では192.168.74.1から192.168.74.130へSeq=1093,Act=1464の通信が成されていることから、もし、No.8の通信がNo.6の通信ならば(1464-1093)の371byteのデータのやり取りがあったということで頷けるが、やはりNo.6,7の通信の意味はわからなかった。


 No.11の通信に注目すると、TCP Previous segment not capturedと書いてある。つまり、前の通信が正しく届いていないということである。No.11以前の通信を見てもそれらしい通信は見て取れないことから、通信フローの欠けている通信はNo.11以前の192.168.74.130からの通信であると予想する。恐らくTCP通信の辻褄が合うような(Seq,Ackの値の観点で)通信が想定される。詳しい内容まではわからなかった。

 また、通信の順番が入れ替わり、そのためエラーが起きたような振る舞いをしていることも考えられる。

選択A-4

 C言語のprintf()関数、また、UNIXのfork()システムコールについて考察するにあたって、1)予想 2)調査・実験 3)まとめ 4)予想とのすり合わせ のように述べていきたいと思います。

==== ==== ==== ====

[1] C言語のprintf()関数について

 1)—- —- —- 予想 —- —- —-

 printf()関数内に任意の文字列を出力するインラインアセンブラが書かれていると予想

 2)—- —- —- 調査・実験 —- —- —-

 K&Rを参照した結果、printf()関数はfprintf()関数の特殊な場合、すなちfprintf(stdout,…)であることがわかった。fprintf()関数はstdio.h内で定義されていて、

 int fprintf(FILE * restrict stream,

       const char * restrict format, ...);

という形式になっている。そして、fprintf()関数はvfprintf()関数を用いることで実装することが可能である。

(実際に実装してみたもの

https://gist.github.com/shinkwhek/cf97b58cfa7e226d6341e2bf6db5697b )

 さらに内部の処理を知るためにvfprintf()関数と各種マクロについて調査する。

まずva_listについて、これは可変個引数を格納するための変数を定義する。実際には

   typedef void * va_list;

というように汎用ポインタとなっている。

 次にva_start()マクロとva_end()マクロについて、これはそれぞれva_listを初期化、終了するものである。

 最後にvfprintf()について、glibcにあるvfprintf.cを眺めたところ、どうやら文字の出力はoutchar(),outstring()というマクロが行っているのではないかと予想できた。さらにそのマクロの実装を見てみると、PUTC ()によって出力されているように予想できる。

PUTC()は以下のように定義されていた。

#define PUTC(C, F) _IO_putc_unlocked (C, F)

PUTC()の動作を知るためにさらに_IO_putc_unlocked()の定義を見てみる。

_IO_putc_unlocked()はlibio/libio.hの中にあり、次のように定義されていた。

#define _IO_putc_unlocked (_ch, _fp) \

    (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \

    ? __overflow (_fp, (unsigned char) (_ch)) \

    : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))

これについて解読する。

まず_IO_BE()について、

GNUC >= 3なら

#define _IO_BE(expr,res) __builtin_expect ((expr),res)

それ以外は

#define _IO_BE(expr,res) (expr)

となっている。

順番に、GNUCはわからなかった。コンパイルするときのオプションのようなのもだと予想する。次に、__builtin_expect()もよくわからなかった。exprが帰されるのが標準だと仮定して進めることにする。

ファイルポインタのIO_write_ptrとIO_write_endを比較している。これらはFILE構造体のメンバとして定義されておりそれぞれ、現在の出力位置と出力可能なエリアの終わりを表している。すなわちこの比較は、現在書き込み可能な場所にいるかどうかを判断していることがわかる。

そして、書き込み可能”でない”場所にいた場合と書き込み可能な場合の処理に分かれており、

書き込み可能でない場合:overflow (fp, (unsigned char) (ch))

書き込み可能な場合  :(unsigned char) (*(fp)->IO_write_ptr++ = (_ch))

となっている。

__overflowは恐らくバッファに書き込めなくなった際の例外処理のようなものだろうと予想する。

そして、書き込み可能な場合はIO_write_ptrにchを代入して_IO_write_ptrは次の位置へ移動している。

このことから文字はバッファとして出力されていることがわかる。

 3) —- —- —- まとめ —- —- —-

 ・printf()はfprintf()の特別な場合である。

 ・fprintf()は主にva_listに関するマクロとvfprintf()で構成されている。

 ・va_listに関するマクロは可変引数を扱うためのものである。

 ・va_listに関するマクロの詳しい内容はわからなかった。

 ・vfprintf()関数について深く探ってみると最終的には_IO_putc_unlocked()というマクロに行き着き、そこでは文字をバッファとして出力していることがわかる。

 4) —- —- —- 予想とのすり合わせ —- —- —-

 ・予想では、printf()はインラインアセンブラで画面出力用のコードが書かれているとしたが、実際はfprintf()の特別な場合であり、バッファリングして文字を出力していた。これによって任意のストリームに書き込むことが可能になる。printf()はfprintf(stdout,…)の場合と同じだった。

 ・予想の実装では画面出力しかできなかったが、実際の実装ではfprintf()で抽象化することによって出力の幅を拡げている。

==== ==== ==== ====

[2] UNIXのfork()というシステムコールについて

 1) —- —- —- 予想 —- —- —-

 fork()が行われた地点でのプロセスをそのまま複製している。そのため既に行った処理を二度行うようなことはない。

 2) —- —- —- 調査・実験 —- —- —-

 まずはfork()がどんなシステムコールなのか知るために仕様を読むことと簡単なコードを書いてみることにした。

https://gist.github.com/shinkwhek/5264a1b055eaabbb1df25177dfaf1092

 結果は、

1

fork():21572

pid:21572

b

fork():0

pid:21572

a

これにより、親プロセス->子プロセスの順で実行されているのがわかる。"1"という出力が一回しか出ていないことから予想が大方正しそうということもわかる。

 では実装を読んでいく。以降、情報が断片的になる。

まずfork.S内で__libc_forkを呼んでいるらしい。

そのなかでどのような処理を経ていくのかはわからなかったが、最終的にはsys_clone関数を呼ぶというのはわかった。さらにその中でdo_fork関数を呼んでいる。

このdo_fork関数では、pidmap_tという構造体で定義された何かしらのデータを獲得している。これはPIDの使用状況を管理するためのものらしい。

task_structという構造体が存在し、ここに現在のプロセスをコピーする。

具体的にプロセスをコピーする方法はわからなかった。

 3) —- —- —- まとめ —- —- —-

 ・fork()を行うと最終的にはdo_fork()関数に行き着く

 ・複数の構造体でプロセスを表現し格納できるようにしている

 ・task_structに現在のプロセスをコピーしている

 ・具体的にプロセスをコピーする方法はわからなかった

 4) —- —- —- 予想とのすり合わせ —- —- —-

 プロセスをコピーする点では合っていたが、どうもプロセスをデータ構造で表現する方法があるらしい。しかし私にはプロセスそのものがどういったものなのかという知識が欠けておりプロセスを表すデータ構造がどのようなものかわからなかった。

==== ==== ==== ====

選択A-5

[回答1 : プログラムの実行により発生する不具合の説明]

 100個のleaked-keyringの参照を解放しないまま残してしまう。

 keyctl(KEYCTL_JOIN_SESSION_KEYRING,name)を実行した際に実行される関数join_session_keyring内のバグが原因。join_session_keyringは既に存在する鍵の名前を引数として実行されたとき、新しく鍵を作り、それを解放しないままエラー処理に向かってしまう。

[回答2 : root権限昇格を行うエクスプロイト]

—-[コード]

 まずコード全体の方針を立てた後、実際に書いたコードの解説を付ける予定だったが、CVE-2016-0728の理解が足らずにそこでの攻撃コード[2]を大いに参考にする形になってしまった。以下、現在の私の解釈による方針。

(方針)

— use-after-free攻撃を行う。

なぜuse-after-freeか?

1 . usageフィールド(オブジェクトの参照カウントを格納するもの)はint型(32ビット)である。

2 . 1.がオーバーラップして0になってもそれを判定する術がない。

3 . 1.を0x100000000にすると、カーネルがそのオブジェクトはもう必要ないと判断し、解放する可能性がある。

4 . 同プロセス内で3.で解放した参照を使用しようとすると(このとき使う参照は予め用意しておいた)、カーネルは解放したメモリか、再割り当てしたメモリを参照する。

5 . 1~5.によって[回答1]のバグを利用しFree After Useを達成可能。

以上がuse-after-free攻撃を行う理由。

— 具体的な手順

1 . キーオブジェクトの参照を持つ

2 . 同じオブジェクトでオーバーフローを起こす (今回用いるバグを利用)

3 . オブジェクトを解放

4 . 3.で解放した箇所に、任意のコードを割り当てる

5 . 1.で持った参照を利用し、4.のコードを実行

— 手順の解説

1 .は正規に参照を持つだけ。

2 .について、int型(32ビット)なので、232回以上ループを回してusegeフィールドの使用量をゼロにする必要がある。

3 .について、オーバーフローさせることでGCを働かせ、オブジェクトを解放する。詳しい理由はわからなかったが、join_session_keyringを呼び出すたびにsleep(1)を使う必要がある。sleep(232)は実行できないので、分割アルゴリズムを使う。

4 .について、今、解放されたオブジェクトへの参照を持っているので、これに上から任意のオブジェクトを割り当てる。具体的にはmsgsnd,msggetを使って、例えば0xb8がキーオブジェクトのサイズで0x30がヘッダーのサイズだった場合、サイズが0xb8-0x30のメッセージを送り返すことで、キーオブジェクトの下位0x88ビットの制御する。

5 .について、revoke関数ポインタにroot権限で実行させる役割を担わせることができるらしいが、その詳しい背景はわからなかった。恐らく、revokeに"/bin/sh"を埋め込んだ箇所へ飛ぶようなバイナリを用意するのだろう。

コードへのリンク:https://gist.github.com/shinkwhek/b01b59efa4d94923518ba1047184f84f

—- [実験結果1]

 下記の[環境1]で記した開発環境で以上のコードを実行してみたが、うまくroot権限昇格が行われなかった。

—- [考察1]

気になったところとして、

 ・初期のuidとeuidの値がそれぞれ501だったことと

 ・最後に表示されたuidとeuidの値が初期と変わらなかったこと

がある。

 一つ目に関しては、これは実ユーザと実行したユーザを表すので特に問題はない。しかし二つ目に関して、値がゼロになっていないということはrootユーザとして"/bin/sh"が実行されていないことを意味する。"/bin/sh"は実行されていることから、revokeもうまく割り当てられていると仮定すると、システムレベルでroot権限昇格が成されないようなものが既に用意されているのではないかと予想する。しかしカーネルは3.8.1で、バグを再現できる条件は満たしているはず。(実際、選-A-5に用意されたコードを実行することで/proc/keysに鍵が溜まっていくことも確認した)

 カーネルを3.18.1にしてコードを実行してみたが、同じ結果が得られた。

 さらに調べた結果、この攻撃の対象はContOS6.9は含まれていなかったため、7に上げ再度実行してみた。

—- [実験結果2]

 初期uid,euidは共に1000、revokeを呼んだ時点で値は変化しなかった。つまり、0にならなかった(うまくroot権限昇格されなかった)

—- [考察2]

 文献[2]ではカーネル3.18とされている。実験3ではそのカーネルのバージョンでやってみる。

—- [実験結果3]

 実験結果2と全く同じ結果に終わった。なぜ再現できなかったかはわからなかった。

—- [環境1]

CentOS6.9, Linux 3.8.1, Vagrant

—- [環境2]

CentOS7, Linux 3.10.0-514.16.1.el7.x86_64, Vagrant

—- [環境3]

CentOS7, Linux 3.18.1, Vagrant

[回答3 : 対策案]

・メモリを確保する(させる)方法を工夫する。つまり、順番にわかりやすくメモリを確保していくのではなく、確保する場所を予測為難いようなアルゴリズムを採用する。

(

 バッファオーバーフローなどならこの方法も効果があるが(次に確保する位置を前回確保した位置からなるべく遠い場所に決定するようにするなど)、ヒープオーバーフローのようなjump先を加工するような攻撃には通用しない。use-after-freeも同様に

)

・確保するメモリの前後に踏み込んだらシステムレベルで警告を出すようなものを仕込む。

(

 メモリを確保すると同時にメモリの前後に数ビット分(サイズは可変)埋め込む。これにより、確保したメモリの位置が悟られにくく、さらにそのメモリを踏むと警告を出すように出来ればuse-after-freeのような攻撃への対策にもなる。

)

・プログラムを実行するためには必ずsudoしなければいけないようにする。

(

 その環境で開発しているユーザが、その環境のsudo件を持っている、もしくはrootユーザであることは絶対ではないのであまり現実的でない。

)


以下は調査してたときのメモです。左の番号は私がメモを読み返すときの目印にするためのものです。

1 . Linux3.8~4.4の脆弱性は、関数join_session_keyring()が関係する。

2 . 1 .とその不明な入力によってバッファオーバーフローを起こす脆弱性

3 . CVE-2016-0728

4 . keyring(という鍵管理ツール?)が原因

5 . keyring facilityはカーネルのセキュリティデータ、認証キー、暗号などを保存・隠蔽する。

6 . keystl syscall(これは、add_key,request_key,そして、keyctlを含む) でユーザは5.のデータを管理したりkeyringを恣意的に扱える。

7 . keyctl(KEYCTL_JOIN_SESSION_KEYRING, name)でkeyringを作れる。そして名前を割り当てる。もしくはNULLを吐く。

8 . keyringオブジェクトは同じ名前のものをプロセス間で共有する。

9 . 8 .に関して、もし既に同じ名前のsession keyringが存在していたら、

 ”””新しいものに置き換えられる””(me:後々大切になりそう)

10 . もしオブジェクトがプロセス間で共有されている場合、usageフィールドに格納された内部参照が増える。

11 . リークは、”””現在のセッションのkeyringを’’まったく同じ’’ものと置き換えるときに発生する”””。

12 . (key_putの呼び出しを飛ばし、find_keyring_by_nameで増えた参照をリークする)error2ラベルに飛ぶ。

[ me ]

 12 .がエラーの原因。全く同じ名前のkeyringを作ろうとしたときkey_putされないままerror処理まで行ってしまうというもの。

 そして、課題のコードがそのバグを直接的に起こすものである。

“leaked-keyring"という名のオブジェクトを作りまくる。

13 . “Exploiting the Bug”

13 .1 . メモリリークが直接的な原因がバグである可能性がある

13 .2 . usage fieldの型はatomic_t (int 32-bits)

13 .3 . 理論的には整数値はいつでもオーバーフロー可能、参照カウントをオーバーフローさせる方法があるように特定の方法でバグを活用できる

13 .4 . 使用フィールドがオーバーラップして0にならないようにするためのチェックは行われない。

13 .5 . プロセスがカーネルに0x100000000の同じオブジェクトへの参照をリークさせたなら、カーネルはオブジェクトは参照されなくなったと判断し、解放する。

13 .6 . 同じプロセスが片方の正当な参照を持っており、カーネルがオブジェクトを解放したあとに使うとき、同じプロセスはカーネルに参照の割り当ての解除、または新しいメモリの確保をさせる。

13 .7 . 私たちは同じバグを用いることで”use-after-free”を達成できる。

14 . 以下、実際のコードのoutline

14 .{1} . keyオブジェクトへの参照を(正当に)持つ

14 .{2} . 同じオブジェクトのusageフィールド?をオーバーフロー

14 .{3} . 解放されたkeyringオブジェクトを獲得

14 .{4} . 解放されたkeyringオブジェクトによって使用されていたメモリ上で、ユーザが制御する別のkernel objectをuser-spaceから割り当てる

14 .{5} . 古いkey objectへの参照を使ってtrigger codeを実行する

{1}はmanpage見れば解決

15 . 以下、14.の項目をさらに深く見ていく

15 .1 . Overflowing usage Refcount

 このstepで実際にバグを拡張する。

 usage fieldはint型、これは最大値が232であることを意味する。

 usage fieldをオーバーフローさせるには、snippetを232回ループさせてusege fieldを0にする。

15 .2 . Freeing keying object

 keyringオブジェクトへの参照を保持したまま、keyringオブジェクトを解放するための方法がいくつかある。

・keyring usage fieldを0にしてオーバーフローさせたあと、keyring subsystem内のGCによって解放させる (usage counterが0になる瞬間解放)

join_session_keyring()関数をみると、prepare_credsは現在のkey linkをインクリメントし、abort_credsまたはcommit_credsはそれぞれ減らす。問題なのは、abort_creadsがkeyring’s usageをデクリメントしないこと。しかし、rcu jobによって呼び出される。これが意味するところは、usage counterがオーバーフローしたかどうか知らずにそれをオーバーフローさせることができること。これは、各join_session_keyringのあとにsleep(1)を使えば解決できる。が、もちろんsleep(232)を実行することはできない。

これの回避策として、divide-and-conquer algorithmを使用する。すなわち、231-1を呼び出した後、230-1などの後にスリープ状態にする。こうすればrefcountの最大値が呼び出されていなくても値を二倍にすることができる。

16 . Use After Free

 ヒープ領域に対する攻撃という点でヒープオーバーフローと同じ。

 ヒープ管理とヒープ領域のアドレスを記録する変数の不整合を使う。

参考文献

[1] Linux Kernel 3.8 Session Keyring Reference Count process_keys.c join_session_keyring() buffer overflow (URL: https://vuldb.com/?id.80353 )

[2] Analysis and Exploitation of a Linux Kernel Vulnerability (CVE-2016-0728) (URL: http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/ )

[3] https://gist.github.com/PerceptionPointTeam/e9b47cf6a7240ac7b8c5

[4] Beyond Zero-day Attacks (URL: www.atmarkit.co.jp/ait/series/1568/ )

終わり

黒歴史晒しは以上でおしまいです。