WTF!?

オンサイトCTFのWriteupとか書いてく.

セキュリティ・キャンプ全国大会2015に応募した

セキュリティ・キャンプ全国大会2015に応募した.
去年の募集の時点で存在は知っていたが,応募用紙を見て挫折.
1年間CTFやら何やらやってたら応募用紙をある程度埋められるようになってて嬉しかった.

共通問題1

セキュリティ・キャンプに応募した自分なりの理由とセキュリティ・キャンプで学んだことを何に役立てたいかを教えてください。

バイナリ読むのが大好きだとか,セキュキャンでは出会い厨になりたいとか書いた.

共通問題2

セキュリティに関することで、過去に自分が経験したことや、ニュースなどで知ったことの中から、最も印象に残っていることを教えてください。また、その印象に残った理由も教えてください。

授業で作ったプログラムに脆弱性があって云々の話をした.

共通問題3

その他に自己アピールしたいことがあれば自由に書いてください。(たとえば、あなたが希望する講座を受講する上で、どのような技術力を持っているか、部活動、技術ブログ、GitHub、ソフトウェア開発、プログラミングコンテスト、勉強会での発表・運営などの実績や熱意があれば、あるだけ書いてください。)

CTFやってるとか書いた.

選択問題2

ある機械語をobjdumpにより逆アセンブルしたところ、以下の結果が得られました。このダンプに見られる問題点を指摘してください。

00000000 <.text>:
   0:   b8 de 07 00 00       mov   $0x7de,%eax
   5:   3d df 07 00 00       cmp   $0x7df,%eax
   a:   75 06                jne   0x12
   c:   89 c0                mov   %eax,%eax
   e:   ff e3                jmp   *%ebx
  10:   75 01                jne   0x13
  12:   e9 5b ba 0e 00       jmp   0xeba72
  17:   00 00                add   %al,(%eax)
  19:   b9 00 00 00 00       mov   $0x0,%ecx
  1e:   bb 01 00 00 00       mov   $0x1,%ebx
  23:   b8 04 00 00 00       mov   $0x4,%eax
  28:   cd 80                int   $0x80
  2a:   b8 01 00 00 00       mov   $0x1,%eax
  2f:   cd 80                int   $0x80

x86CISCとして設計されたので,可変長命令を用いている.そのため,エントリポイントが異なる場合異なる処理を行う.
上記のダンプはtextセクションの先頭をエントリポイントとして逆アセンブルしている.
しかし,上記のプログラムを先頭から実行すると必ずjmp 0xeba72でSegmentation Faultを起こし,異常終了してしまう.
また上記のダンプには奇妙な命令がいくつか出てきている.

  10:   75 01                jne   0x13
  12:   e9 5b ba 0e 00       jmp   0xeba72
  17:   00 00                add   %al,(%eax)

である.もしEFLAGSのZFがセットされていない状態でjne 0x13を実行した場合,次に実行されるのは5bから始まる命令である.
つまり,上記のダンプでは解析できない処理を行うことになる.そこで,<.text+0x13>をエントリポイントとして逆アセンブルをすると次のようになる.

   0:   5b                   pop   %ebx
   1:   ba 0e 00 00 00       mov   $0xe,%edx
   6:   b9 00 00 00 00       mov   $0x0,%ecx
   b:   bb 01 00 00 00       mov   $0x1,%ebx
  10:   b8 04 00 00 00       mov   $0x4,%eax
  15:   cd 80                int   0x80
  17:   b8 01 00 00 00       mov   $0x1,%eax
  1c:   cd 80                int   0x80

これを実行すると1つ目のsystem callでwrite(1, null, 0xe);,2つ目のsystem callで_exit();を行う.
この動作は問題文のダンプからは読み取ることが出来ない.これが問題点である.
このように途中の命令バイトにジャンプすることで解析しにくくする技法マルウェアにも用いられている.

選択問題3

過去に作成したプログラムのうち最も気に入っているものについて答えてください。 ここでいうプログラムは、Webサービスやモバイルアプリ、サーバ/デスクトップアプリケーションあるいはOS、VMなどといったソフトウェア全般のことです。 (1)どのようなソフトウェアであるかを教えてください (2)何の目的のためにそれを作ろうと思ったのか、理由を教えてください (3)開発するにあたって工夫したところを教えてください (4)新たな課題、今後勉強してみたいと思っている内容を書いてください

去年の高専プロコンで作ったものについて書いた.

選択問題5

以下のようなC言語の関数functionがあるとします。

void function(int *array, int n) {
  int i;
  for(i = 0; i < n; i++) {
    array[i] = i * n;
  }
}

上記プログラムをコンパイルした結果の一例 (i386)は以下となりました。

00000000 <function>:
   0:   56                   push   %esi
   1:   53                   push   %ebx
   2:   8b 5c 24 0c          mov    0xc(%esp),%ebx
   6:   8b 4c 24 10          mov    0x10(%esp),%ecx
   a:   85 c9                test   %ecx,%ecx
   c:   7e 18                jle    26 <function+0x26>
   e:   89 ce                mov    %ecx,%esi
  10:   ba 00 00 00 00       mov    $0x0,%edx
  15:   b8 00 00 00 00       mov    $0x0,%eax
  1a:   89 14 83             mov    %edx,(%ebx,%eax,4)
  1d:   83 c0 01             add    $0x1,%eax
  20:   01 f2                add    %esi,%edx
  22:   39 c8                cmp    %ecx,%eax
  24:   75 f4                jne    1a <function+0x1a>
  26:   5b                   pop    %ebx
  27:   5e                   pop    %esi
  28:   c3                   ret  

このとき以下の(1)~(5)の設問について、回答と好きなだけ深い考察を記述してください。知らない点は、調査したり自分で想像して書いてもらっても結構です。どうしてもわからない部分は、具体的にここがわかりませんと記述しても良いです。(1)~(2)の回答は必ず答えてください。(3)~(5)の回答は任意です。わかることを書いてください。CPU やコンパイラは特定の実装を例に説明しても良いですし、理想を自由に考えても良いです。

(1)【必須】上記の C 言語のプログラムはどのような動作をしますか。また、この関数を呼び出して利用する main 関数の例を作成してください。 (2)【必須】上記のアセンブリコードを、いくつかのブロックに分割して、おおまかに何をしている部分かを説明してください。もし、上記のアセンブリが気に入らないのであれば、好きなアーキテクチャコンパイラアセンブル結果を載せて説明しても良いです。 (3)【任意】 コンパイラソースコードの関数を解釈して、ターゲットのアーキテクチャのバイナリを生成するまで、どのように内部で処理を行っていると思いますか。(キーワード: 構文解析、変数、引数、呼出規約、レジスタ、スタック、アセンブラ、命令セット) (4)【任意】CPU の内部では、プログラムのバイナリはどのように解釈され実行されていると思いますか。(キーワード: フェッチ、デコード、オペコード、オペランド、命令パイプライン、回路) (5)【任意】現在の CPU やコンパイラの不満点があれば自由に記述してください。

(1)
このfunction関数は第一引数にint型配列のポインタを受け取り,第二引数でint型の値を受け取る.
arrayの先頭からn番目の要素まで,インデックス × n の値を代入する.
つまり,この関数を用いることで 0 〜 n * (n - 1)までのnの倍数を生成できる.
main関数の例:

int
main()
{
    int i;
    int n;
    int *array;

    printf("Please input natural number > ");
    scanf("%d", &n);
    if(n <= 0) return 0;
    array = (int *) malloc(sizeof(int) * n);
    if(array == NULL) return 0;
    function(array, n);
    for(i = 0; i < n; i++){
      printf("%d\n", array[i]);
    }
    return 0;
}

(2)
上記のアセンブリが気に入らないのでアーキテクチャx86コンパイラgcc version 4.9.2 (Debian 4.9.2-10)を用いる.

ソースコードとの対応
080483cb <function>:
 80483cb: 55                    push   ebp                      ;
 80483cc: 89 e5                 mov    ebp,esp                  ;
 80483ce: 83 ec 10              sub    esp,0x10                 ; int i;
 80483d1: c7 45 fc 00 00 00 00  mov    DWORD PTR [ebp-0x4],0x0  ; i = 0;
 80483d8: eb 1c                 jmp    80483f6 <function+0x2b>  ;
 80483da: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ;
 80483dd: 8d 14 85 00 00 00 00  lea    edx,[eax*4+0x0]          ;
 80483e4: 8b 45 08              mov    eax,DWORD PTR [ebp+0x8]  ;
 80483e7: 01 c2                 add    edx,eax                  ;
 80483e9: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ;
 80483ec: 0f af 45 0c           imul   eax,DWORD PTR [ebp+0xc]  ;
 80483f0: 89 02                 mov    DWORD PTR [edx],eax      ; array[i] = i * n;
 80483f2: 83 45 fc 01           add    DWORD PTR [ebp-0x4],0x1  ;
 80483f6: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ; i++;
 80483f9: 3b 45 0c              cmp    eax,DWORD PTR [ebp+0xc]  ;
 80483fc: 7c dc                 jl     80483da <function+0xf>   ; for文内の i < n;
 80483fe: c9                    leave                           ;
 80483ff: c3                    ret                             ;

詳しい動作の説明
080483cb <function>:
 80483cb: 55                    push   ebp                      ; 呼び出し元のebpをスタックに積む
 80483cc: 89 e5                 mov    ebp,esp                  ; ebpにespを転送する
 80483ce: 83 ec 10              sub    esp,0x10                 ; espから0x10を引くことでスタックに16バイトの領域を確保する
 80483d1: c7 45 fc 00 00 00 00  mov    DWORD PTR [ebp-0x4],0x0  ; ebp-4からebpまでの4バイトにdword幅で0を転送する
                                                                ; ebp-4からebpまでの領域はソースコードの int i と対応している
 80483d8: eb 1c                 jmp    80483f6 <function+0x2b>  ; 80483f6へジャンプする
 80483da: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ; eaxにebp-4からebpまでの内容を転送する
 80483dd: 8d 14 85 00 00 00 00  lea    edx,[eax*4+0x0]          ; edxにeax*4を保存
 80483e4: 8b 45 08              mov    eax,DWORD PTR [ebp+0x8]  ; eaxにebp+0x8からebp+0xcまでの内容(第一引数)を転送
 80483e7: 01 c2                 add    edx,eax                  ; edxにeaxとの和を保存する
 80483e9: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ; eaxにebp-0x4からebpまでの内容(int i)を転送
 80483ec: 0f af 45 0c           imul   eax,DWORD PTR [ebp+0xc]  ; eaxにebp+0xcからebp+0x10までの内容(第二引数 n)との積を保存する
 80483f0: 89 02                 mov    DWORD PTR [edx],eax      ; edxに保存されているアドレス(array[i])にeaxを転送する
 80483f2: 83 45 fc 01           add    DWORD PTR [ebp-0x4],0x1  ; ebp-0x4からebpまでの内容(int i)に1を加算する
 80483f6: 8b 45 fc              mov    eax,DWORD PTR [ebp-0x4]  ; eaxにebp-4からebpまでの内容を転送する
 80483f9: 3b 45 0c              cmp    eax,DWORD PTR [ebp+0xc]  ; eaxとebp+0xcからebp+0x10までの内容(第二引数 n)を比較し,
 80483fc: 7c dc                 jl     80483da <function+0xf>   ; eaxの方が小さかったら80483daへジャンプする
 80483fe: c9                    leave                           ; espにebpを転送し,saved ebpをebpレジスタにpopして復帰させる.
 80483ff: c3                    ret                             ; スタックの一番上に保存されているリターンアドレスにジャンプする

(3)
コンパイラはバイナリを生成するまで字句解析,構文解析,意味解析,最適化,バイナリ生成という流れで処理を行っている.
まず字句解析ではソースコードの文字列を予約語や識別子などのトークンに分割する.
次に構文解析では字句解析で分割されたトークンを構文規則を考慮して構文木を生成する.またこのとき,プログラムが文法的に正しいかをチェックする.
意味解析では構文解析によって生成された構文木をもとに中間コード(逆ポーランド記法やPコードなど)を生成する.
最適化では中間コードに存在する無駄な処理(使われていない変数や無駄な代入など)を削除することで,実行速度の高速化や使用メモリ領域の削減をする.
そして最適化された中間コードをアセンブリ機械語に変換して実行バイナリのフォーマットに従い生成する.
このとき,変数の宣言をもとにスタック領域や初期化データ領域,未初期化データ領域の確保. また,関数が呼び出されていたら引数を呼出規約に基いてレジスタやスタックを介して関数に渡して呼び出す処理を生成している.

(4)
CPUはまず命令がメモリから外部バスインタフェースを介して制御ユニットに読み込まれる.この動作をフェッチと呼ぶ.
次にフェッチされた命令は制御ユニットにあるデコーダーで,各種演算命令やデータ転送命令といった具体的な情報に変換される.
この作業をデコードと呼び,変換された命令は制御情報となる.これは,演算ユニットをどのように動作させて演算させるかをコントロールする情報である.
制御情報が演算ユニットに渡されると演算の対象となるデータを,メモリーから外部バスインタフェースを通して演算ユニット内のレジスタに読み込み,演算を実行する.
演算が終了すると結果をレジスタに書き込む.レジスターに書き込まれた結果は,メモリーに出力される.この動作をライトバックという.
このフェッチ,デコード,実行,ライトバックの一連の流れがCPUがプログラムのバイナリを実行する動きである.
更に,実際のCPUは命令パイプラインという仕組みで複数の命令を同時に処理している.
命令パイプラインとは,先程のフェッチをある命令が終えたらすぐに次の命令がフェッチを始め,フェッチを終えた方の命令がデコードをし始めるという仕組みである.
こうすることで全工程を一つとして処理するよりも高速化できる.また工程を細分化するとより高速になる.

(5)
特に不満点はない.
しかし,バイナリ解析する立場で見た時,
gccを最適化無しでコンパイルすると素直なバイナリを吐いてくれるが,
clangなどで最適化したバイナリはgccの最適化無しとは比べ物にならないほど動作が複雑になり,
手動デコンパイルがとても難しくなることは悩ましい.

選択問題8

gccが持つ-fno-stack-protectorは、どのようなセキュリティ機能を無効にするオプションであるのか、またこの機能により、どういった脆弱性からソフトウェアを守れるのかをそれぞれ記述してください。

-fno-stack-protectorはStack Smashing Protector( 以降SSPと呼ぶ )というセキュリティ機能を無効にするオプションである.
SSPが有効になっている状態でビルドされたプログラムの関数はローカル変数とsaved ebpの間にcanaryと呼ばれる,32bitの時はword幅,64bitの時はqword幅の値が置かれる.
関数のローカル変数確保直後にThread Local Storage( 以降TLSと呼ぶ )に保存されてる値をcanaryにコピーし,
関数からのreturn直前にcanaryとTLSに保存されている値を比較し,canaryが変化していたらstack_chk_fail関数を呼ぶ.
stack_chk_fail関数は内部でexit関数を読んでおり,関数の呼び出し元へreturnすることはない.
つまり,Stack buffer overflowという脆弱性からプログラムを守ることができる.
canaryの値は/dev/urandomから生成されているので予測することは不可能である.
ただしforkしたとき,子プロセスは親プロセスと同じメモリ領域を共有するため,canaryの値が必ず同じものになる.
BOF脆弱性を持ち,リクエストごとにforkするようなプログラムを想定する.BOFによってcanaryの最下位1バイトを書き換える.
もし,書き換えたcanaryの値が異なってた場合異常終了を起こし,合っていた場合正常に終了する. これを256回試行すればcanaryの最下位バイトを特定することができる.同様の操作を繰り返すことでcanary全体の値も特定することが可能である.
32bitのプログラムの場合,1024回の試行.64bitプログラムの場合,2048回の試行で特定可能で,これは実現可能な回数である.
また,-fstack-protector-allオプションが指定されていない場合, ローカル変数に文字配列が存在しない若しくは文字配列長が8バイト未満である関数にはcanaryを生成しないことも留意しておかなければならない.
SSPはStackに対する書き換えを検知するものなので,Format String BugやHeap Over Flowといった脆弱性に対する攻撃を防ぐことは出来ない.
更に,Stack buffer overflowに対する攻撃を緩和することは可能であるが,BOF自体を予防できるわけではないことを理解することが重要である.

選択問題9

以下のコードは、与えられたテキスト内からURLらしき文字列を探して、それらを<a>要素でリンクにしたHTMLを生成するJavaScriptの関数であるとします。攻撃者が引数 text の中身を自由に制御可能な場合、このコードにはどのような問題点があるか、またこのコードを修正するとすればどのようにすればよいか、自分なりに考察して書いてください。

function makeUrlLinks( text ){
 var html = text.replace( /[\w]+:\/\/[\w\.\-]+\/[^\r\n \t<>"']*/g, function( url ){
   return "<a href=" + url + ">" + url + "</a>";
   } );
 document.getElementById( "output" ).innerHTML = html;
}

もしtextに<img onerror='document.location="http://example.com"' src=x></img>という文字列が渡された場合,http://example.comに遷移する.
これはmakeUrlLinksでDOMに書き込まれた文字列がimgタグとして認識され,エラー時に実行されるonerrorハンドラのスクリプトが実行されたからである.
つまり上記のコードはDOM Based XSSという脆弱性を持っている.
この脆弱性が存在するとhttponly属性のついていないcookieを窃取したり,
偽のログインフォームを表示してパスワード入力を促すことでパスワードを奪うことが可能である.
上記のコードを修正するには出力の際にエスケープ処理を施すのが有効的である.
以下に修正例を示す.

function makeUrlLinks( text ){
 text = text.replace( /&/g, "&amp;").replace( /</g, "&lt;").replace( />/g, "&gt;").replace( /"/g, "&quot;").replace( /'/g, "&#x27;");
 var html = text.replace( /[\w]+:\/\/[\w\.\-]+\/[^\r\n \t<>"']*/g, function( url ){
   return "<a href=" + url + ">" + url + "</a>";
   } );
 document.getElementById( "output" ).innerHTML = html;
}

選択問題10

アンチデバッグ、難読化といった単語をキーワードとする技術について、あなたが知っていること、調べたことを具体的に記述してください。基本的にPCのソフトウェアにおける技術を想定していますが、他端末、またはハードウェアに関する内容でもかまいません。

アンチデバッグwindowsにのネイティブコードにに関するもののみ書いた.
難読化はネイティブコード,マネージコード及びJavaバイトコードjavascriptの3つの難読化技術について触れた.
あまりに長いので省略.

選択問題11

下記バイナリを解析し、判明した情報を自由に記述してください

D4 C3 B2 A1 02 00 04 00 00 00 00 00 00 00 00 00 
00 00 04 00 01 00 00 00 88 EB 40 54 A2 BE 09 00 
52 00 00 00 52 00 00 00 22 22 22 22 22 22 11 11 
11 11 11 11 08 00 45 00 00 44 1A BD 40 00 80 06 
3A 24 C0 A8 92 01 C0 A8 92 80 10 26 01 BB 86 14 
7E 80 08 B3 C8 21 50 18 00 FC 0D 0E 00 00 18 03 
03 00 17 01 0E FB 06 F6 CD A3 69 DC CA 0B 99 FF 
1D 26 09 E1 52 8F 71 77 45 FA

先頭4バイトにD4 C3 B2 A1という特徴的なバイト列が現れている.これはpcap形式のファイルのシグネチャである.
試しに上記のバイナリをファイルにしてWiresharkで開いた.するとHeartbeat Requestのパケットであることが分かった.
Secure Sockets Layerに注目してみる.Secure Sockets Layerの中身のバイナリを抜き出すと, "18 03 03 00 17 01 0E FB 06 F6 CD A3 69 DC CA 0B 99 FF 1D 26 09 E1 52 8F 71 77 45 FA"である.
これを意味するごとに区切ると次の通りである.

18 : Content Type: Heartbeat (24)
03 03 : Version: TLS 1.2 (0x0303)
00 17 : Length: 23
01 : Type: Request (1)
0E FB : Payload Length: 3835
06 F6 CD A3 : Payload (4 bytes)
69 DC CA 0B 99 FF 1D 26 09 E1 52 8F 71 77 45 FA : Padding and HMAC (16 bytes)

Payload Lengthと実際に送られているPayloadのサイズに差があることが分かる.
これはCVE-2014-0160,OpenSSLのHeartbleedという脆弱性を狙った攻撃のパケットである.
Heartbleedの脆弱性を持ったOpenSSLのクライアントはHeartbeat RequestのPayloadをヒープ領域に保存し,
そこからPayload Length分読み込んでレスポンスメッセージを作成する.
このことを利用し実際のPayloadのサイズよりもPayload Lengthを大きくしたパケットを送りつけることで, ヒープ領域に存在するデータを抜き出すことができる脆弱性がHeartbleedである. 上記のパケットがHeartbleedの脆弱性が存在するクライアントに送信された場合,3835 - 4 = 3831[byte]のデータが抜き取られる.
Heartbleedは他のユーザのデータやクライアントの秘密鍵が盗み取られる可能性が存在するため,とても重大な脆弱性である.

感想

選択問題9とかはweb力の足り無さを感じた.
あとネットワーク系の問題に答えられなかったのでもっと勉強しなければという感じ.
応募用紙を埋めていく過程で色々学ぶことができた.
今の自分が出せるものは出しきったのであとは運に任せるしかない.