Basic SecCap / サイバー攻防基礎演習

2024/09/24-26開催予定 @ 静岡大学情報学部

1. はじめに

まず最初に十分理解していいただきたいのは,以下の実験内容は教育目的であって絶対に悪用しないでください.

インターネット上には,ポートスキャン方法やクラッキング方法など様々な情報が氾濫していますが,本実験環境のローカルネットワーク内における実験用サーバに対してのみ試してみて構いませんが,そのからくりを理解できていない人は周囲に迷惑をかける可能性もありますので十分注意して実施してください.外部サーバや他人のPCに対して試してみるということは絶対にしないでください.

学内のトラフィックは,情報基盤センターのツールで監視されています.皆さんはコンピュータネットワークの授業で,通信の仕組み(接続元IPアドレス・ポート番号,プロトコル種別,接続先IPアドレス・ポート番号の5つの情報で通信フローは識別可能)を学んでいるわけですので,どのような仕組みで監視できるのか認識しているものと思いますが,意図せぬウィルス感染や不正アクセスでも情報基盤センターから連絡は来ますので,十分注意して以下の課題に取り組んでください.皆さんのノートPCのMACアドレスは入学時に学内DBへ登録されているはずです.実験機材のMACアドレスもこちらで把握しています.また,学内DHCP(無線であろうが有線であろうが)でIPアドレスをもらっている場合,どのMACアドレスへいつからいつまでどのIPアドレスを割り振ったかログに記録されてます.学内にはネットワーク機器の運用規則がありますので,遵守するとともに敢えて抜け道を探すようなことは絶対にしないでください.もし抜け道に気づいてしまった場合は,真摯に教えてください.将来のネットワークエンジニアの卵として良い方向での成長を楽しみにしたいと思います.

また,もし何か意図せぬことが発生しているような気がする場合は,「学内ネットへ接続しているルータWAN側のLANケーブルを抜く」ということでお願いします.

よく,「ホームページが改竄(かいざん)された」というニュースや「セキュリティホールが見つかったのでソフトウェアアップデートしましょう」というような周知があるかと思いますが,これまでに実装したSimple HTTPサーバにもセキュリティホールがあります(というかあまり深く考えずにネットワークプログラムを開発すると,セキュリティホールだらけになりますので注意してください).

ここでは,セキュリティホールの基本であるバッファオーバフローを利用した攻撃方法について理解し,実際に以下のステップを実験用端末で試してみてセキュリティホールを実感してください.

  • バッファオーバーフロー
  • バッファオーバーランで任意のコマンドを実行
  • バッファオーバーランでリモートからシェルに接続

上から順番に少しずつ攻撃を高度化したものになります.つまり,一番下に示すリモートサーバのシェルに接続できれば,ホームページの改竄もできてしまいます.

なお,この実験は静岡大学情報学部情報科学科カリキュラム,情報科学実験Aの内容をベースにしています.以下のURLを参考に予習を進めてください.

静岡大学情報学部|情報科学実験A
https://ohkilab.github.io/SU-CSexpA/

2. 配布機材と事前準備

※情報科学実験A等で既にRaspberry Piのセットアップが完了している場合は以下の手順はスキップして構いません

本実験ではRaspberry Piを利用します.必要なソフトウェアはインストール済ですが,演習開始までに以下の事前準備を終わらせておいてください.(静岡大学生かつ情報科学実験Aを履修済みの方はセットアップ済Raspiをお持ちかと思います.その場合以下は読み飛ばしてもらっても構いません)

配布機材
  • Raspberry Pi 4 Model B 2GB(組立済)
  • Raspberry Pi 用 MicroSD(環境設定済)
  • Raspberry Pi 用電源
  • LANケーブル 1本
  • クッションケース
事前準備
◆ 無線ネットワークへの接続

接続用の無線ネットワーク情報は当日お伝えします.情報に従って無線ネットワークに接続してください

◆ SimpleHttpサーバーの準備

情報科学実験AでSimpleHttpサーバーをすでに作成してありプログラムをお持ちの方はこの準備をする必要がありませんので,読み飛ばしていただいて構いません.

本実習で使用するSimpleHttpサーバのプログラムは以下のリンクからダウンロードすることができます.
SimpleHttpサーバのプログラムのダウンロード

以上が終わったら事前準備は完了です.では演習を始めていきましょう!

◆ 補足)WPA Enterprise を使用する無線ネットワーク(WRL-SUCCES-S3 / eduroam 等)に繋ぐ場合

以下を参考としてください

3. バッファオーバーフロー

プログラムが管理しているメモリ上に確保するバッファ領域に対して,脆弱性のある実装をしているとバッファ領域の上限を超えた部分に情報を格納(バッファオーバーフロー)してしまうことがあります.これによりプログラムが意図した通りに動作しなくなってしまう場合があります.詳細は「バッファオーバーフロー」で検索して調べてみてください.

まずは以下の簡単なプログラムについて考えてみましょう.以下のプログラムは任意の文字列1つを引数として取ることができます.試しに色々な引数を入れて実行してみてください.引数を長くしていくとある長さから先で実行結果がSegmentation Faultとなることがわかったはずです.この時,プログラムの中では何が起こっているのでしょうか?

GDBを用いた確認

gdb(GNUデバッガ,後述)を使いながら実行時のメモリの内容を確認してみましょう.たとえば以下のような情報を確認するとよいでしょう.

  • プログラム内の各変数のアドレス
  • リターンアドレスが格納されているアドレス
  • 各変数・リターンアドレスの位置関係
    • 特にbufferとリターンアドレスの位置関係

gdbはGNUデバッガの略称で,GNUソフトウェア・システムで動く標準のデバッガです.利用方法については,たとえば,下記のサイト等を参照しながら進めてみてください.

gcc+gdbによるプログラムのデバッグ
https://rat.cis.k.hosei.ac.jp/article/devel/debugongccgdb1.html
Debugging with GDB (htmlと400ページ以上のPDF.より詳しく知りたい方はこちら)
https://sourceware.org/gdb/current/onlinedocs/gdb.html
注意点

昨今のOSはデフォルトでは以下の対策が施されていますので,今回は敢えて体験できるよう以下の設定を解除してから実施してください.その他にも最新のセキュリティ対策が施されている場合がありますので,状況に応じて解除して実施してください.

  • アドレス空間レイアウトのランダマイズ化(ASLR: Address Space Layout Randomization)を実験のために無効化:
  • % sudo sysctl –w kernel.randomize_va_space=0
  • データ実行防止 (DEP: Data Execution Prevention)とスタック保護(SSP: stack-smashing protection)を実験のために無効化:
  • % gcc –z execstack –fno-stack-protector xxxx.c

また,本演習で利用するRaspberry PiはARMアーキテクチャを使用しています.このため,「バッファオーバーフロー」等で検索して出てくるページに記載の内容とはレジスタ名等が異なることがあります.各自,ARMとx86/x64アーキテクチャの違いを意識して演習に取り組んでください.たとえば,違いをまとめたページとして以下のようなものがありますので参考にしてください.

Assembly Basics Cheatsheet | Azeria Labs
https://azeria-labs.com/assembly-basics-cheatsheet/
理解度確認問題

上記のプログラムは任意の長さの文字列を入力可能です.適当に長い文字を入力すればバッファオーバーフローが起きるのが確認できたと思います.では,バッファオーバーフローによりリターンアドレスを上書きするには最低何文字の文字列を入力すればよいでしょうか?

  • ヒント1:gdbではリンクレジスタの値をinfo registerで確認できます.またfindコマンドを使ってスタックポインタから適当な範囲の検索を行うことでリンクレジスタのアドレスを知ることもできます
  • ヒント2:入力する値がわかれば(たとえばAAAA...なら16進数で0x41414141...)その値をfindで検索することでbufferの先頭アドレスを知ることもできます.
  • ヒント3:入力に必要な文字数=(リンクレジスタのアドレス - bufferの先頭アドレス)から知ることができますよね?

4. バッファオーバーランで任意のコマンドを実行

バッファオーバーフローを利用して,リターンアドレスを上書きし,任意の関数を呼び出すことができます.まずは以下のようなサイトを参考にしながら,バッファオーバーランの仕組みを理解しましょう.

バッファオーバーラン ~その1・こうして起こる~ (Web Archive)
https://www.ipa.go.jp/security/awareness/vendor/programmingv1/pdf/b06_01.pdf

たとえば,以下のようなプログラムにおいて,パスワード(shizuokaやenpit)以外の文字列を入力してgrantedの文字を出力するにはどうしたらよいでしょうか?

GDBを用いた確認

gdb(GNUデバッガ,後述)を使いながら実行時のメモリの内容を確認してみましょう.

  • ヒント1:disas main等を行ってprintfを何回も行っている場所に注目してみてください
  • ヒント2:grantedを呼び出している行のアドレスがわかったらそれをリターンアドレスに上書きするような入力を生成してみましょう
  • ヒント2:たとえば以下のような方法で引数に任意のHEX値を含めることができます
    % ./auth_overflow $(python -c 'print("A" * 10 + "\x16\x85\x04..")')
     ※Aの数やアドレスは適当です
    % ./auth_overflow $(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*24 + b"\x16\x85\x04..")')
                  Python3な人はこっち
理解度確認問題
  • この方法以外に,auth_flagを1に書き換える方法もあります.試してみましょう!
  • (発展・できれば調べてみましょう)上記のように任意のアドレスを上書きしたとしても granted の文字が出力されたあと Segmentation Fault が出てしまったと思います.今回はそれでも構いませんが,その理由について考えてみましょう.また,Segmentation Faultを回避する方法はありそうでしょうか?

5. シェルコードによる任意コマンドの実行

リターン先のアドレスを上書きするイメージがわかったでしょうか?このリターン先のアドレスに,サーバ側のシェル(/bin/sh等)を起動するコードを配置しておけば,任意のコマンドを実行させることができるようになります.まずは以下のような簡単なプログラムにおけるバッファオーバーフロー脆弱性を利用して,シェルを起動するエクスプロイトを書いてみましょう.

例えば以下のようなサイトもありますので,参考にして実現してみてください.

Linux ARM用のシェルコードを書いてみる
http://inaz2.hatenablog.com/entry/2015/03/06/020437

要約すると,次のようなデータを送り込めばよいことになります.gdbを使用してメモリ上の値を分析しながら実施すると具体的なイメージがわくと思います(ちなみにSSP (Stack-Smashing Protection)も無効にしておきましょう).

  • シェルコード(シェルを起動するコード)を作成する
  • データ中にシェルコードを含める
  • バッファ長の適切な位置にシェルコードの先頭アドレスを入れておく

・・・とはいえ,最初はシェルコードの作成を自分で行うことは難しいかもしれません.現時点でハードルが高いと感じられた方については以下にサンプルのシェルコードを掲載しておきますので,まずはこちらを参考にしてどのようにしてシェルコードを実行するか?考えてみてください.プログラム中に変数の値として組み込む,引数として与える等色々やり方はあると思います.それぞれ実行の困難性や現実性に違いはありますが,今回は特に気にせず進めてよいとします.

シェルコードのサンプル(改行が入っているので注意)
\x01\x70\x8f\xe2\x17\xff\x2f\xe1
\x04\xa7\x03\xcf\x52\x40\x07\xb4
\x68\x46\x05\xb4\x69\x46\x0b\x27
\x01\xdf\x01\x01\x2f\x62\x69\x6e
\x2f\x2f\x73\x68
理解度確認問題
  • たとえばPythonやRuby等,他の言語からエクスプロイトを実行する方法について考えてみてください.バッファ先頭アドレスの自動取得等色々工夫してみてもよいでしょう.
  • (発展)今回は/bin/shを起動するシェルコードを使いましたが,他のコマンドを実行できるようにシェルを書き換えられるでしょうか?

6. バッファオーバーランでリモートからシェルに接続

本演習の最終目標は,クライアント側で入力したコマンドをサーバへ送信&実行させ,そのサーバ側での実行結果の出力をクライアント側へ送信&表示させるように実装し,リモートからサーバ上の任意のコマンドを実行することです(もちろん通常は実行したコマンドがログとして残りますので,世の中のよくできたクラックツールは様々な工夫が施されておりますし,いたちごっこですのでサイバー攻防の発展は凄まじいですが).

今回対象とするコードは最も単純な機能のみを実装したSimpleHTTPサーバとします.静岡大学の学生さんで情報科学実験Aを受講された皆さんは実験で作成したSimple HTTP Serverを利用してくれて構いません. それ以外の方は以下にソースコードが用意されていますのでそちらを使ってください.ネットワークプログラミングについて忘れてしまった方も以下のURLなどを参考に復習しておきましょう.

Simple HTTPサーバ
https://seccap.inf.shizuoka.ac.jp/pbl/files/simplehttp.zip
静岡大学情報学部|情報科学実験A
https://ohkilab.github.io/SU-CSexpA/

演習を進めていくためのヒント

手順の概要

例えば,サーバ側で実行させるシェルコードで,ディスクリプタを複製できるdup2()システムコールを実行させてaccept()で受け付けたソケットのディスクリプタを標準入出力へ複製してからexecve()するようにしておくといった方法があります.このような手法はTCP Bind Shellとも呼ばれます.具体的なシェルコードの作成方法等についてはたとえば以下のサイト等が参考になるでしょう.

TCP Bind Shell in Assembly (ARM 32-bit) | Azeria Labs
https://azeria-labs.com/tcp-bind-shell-in-assembly-arm-32-bit/
SimpleHttpサーバー特有の問題

SimpleHttpサーバーはexp1_http_session関数でクライアントからデータを受け取り,そのデータをexp1_parse_header関数に渡してHTTPヘッダの解析を行う,という流れで処理を行います.この間のバッファ受け渡しにおいていくつか注意しておくべき点があります.

  • exp1_http_sessionにおいてrecv引数のバッファサイズが2048バイトとなっている点
    • オーバーフローに必要なデータサイズはいくつか?まずはリターンアドレスとbufferのアドレスがどれだけ離れているか調べてみましょう
  • exp1_parse_header関数においてバッファサイズが1024バイトとなっている点
    • exp1_http_sessionから2048バイトのデータを渡すとその時点でSegfaultしてしまいます
  • exp1_parse_headerのパースが正常に終了しない場合にexp1_http_sessionが終了しない点
    • exp1_parse_headerが1を返さないとexp1_http_sessionのwhileループが終了しない(入力に「\r」を明示的に含める等工夫する必要がある)
FAQ:バッファのアドレスが変わって先頭アドレスが推定できない?

バッファ先頭アドレスの推定がうまくいかない場合は,gdbを実行する際に起動済みプロセスにattachする形で調べてみてください(なぜこんなことをするのか?にも理由があります.時間に余裕がある人は調べてみましょう).どうしても難しい場合はソースコード中で printf("%p", ...); するように書き換えてもよいとします.どの変数のアドレスを調べるかは自分で考えてみましょう.

% gdb
(gdb) attach <pid>
(gdb) b 9 <-- 任意
(gdb) c
pidはpsコマンド等で確認できます.
攻撃コード作成の雛形(検討がつかない人向けヒント)

以下にエクスプロイトコードの雛形を示しておきます.参考にしながら進めてみてください.

7. 発展課題(取り組める方のみ)

リモートからのシェル接続に成功した方,おめでとうございます!時間が残っている人は引き続いて理解度を深めるために以下の課題に取り組んでみてください.

発展課題1:リモートシェル接続プログラムの機能拡張(難易度:易)

上で作成したTCP Bind Shellのプログラムを元に以下のような課題に取り組んでみてください

  • 接続先IPアドレス・待受ポート等を変更可能にする
  • TCP Bind ShellでなくTCP Reverse Shellによる接続を可能にするプログラムを作成してみる(別プログラムで作ってもOK)
発展課題2(難易度:高)

最初に述べたように昨今のOSはASLR,SSP,DEPといったように今回のような攻撃を困難とするような対策が多く施されています.では,これらの対策を1つないし複数有効とした場合について,以下を検討してみてください.

  • ある対策(たとえばASLR)を有効とした場合,今回のような攻撃が困難になるのはなぜか?
  • 対策を回避する方法はあるか?
  • 対策を回避する手段の実装(SimpleHttpサーバでなく,簡単なコードを例としても構いません)

最後にいくつか参考になるURLを記載してありますので,そちらなどを参考にしながら可能な限り進めてください

8. 自由課題(グループ内で分担してなるべく取り組んで欲しい)

今回の課題と関連した内容について,自由に調査や実装を行ってみてください.たとえば,以下のような内容について調べたり,実装してみたりして報告してみるなどがあります.もちろんこれ以外でも大丈夫ですのでみなさんの自由な発想による取り組みを期待しています!

  • buffer overflowが関連する脆弱性を探し,解説・実演する
  • buffer overflow関連CVE
    https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=buffer+overflow

    多くの場合Raspberry Piでは対策済みのバージョンがインストールされていると思いますが,古いバージョンにダウングレードして検証用コードを作成し,実行してみる等を行ってみるとよいでしょう.

  • 2022年には著名なLinux用コマンドラインツールcURLにヒープオーバーフローの脆弱性が発見され話題となった。どのような脆弱性でどのような影響が起こり得たのか。ヒープオーバーフローとバッファオーバーフローの違いは何か。など。
  • NSAの報告によると,近年のプログラム言語のうち,C#, Go, Java, Python, Rust, Swift等は「メモリ安全」なプログラム言語と言われている.それぞれどのような保護がされているのか?バッファオーバーフローに対してどのように安全なのか?また欠点はあるか?など
    参考URL:U.S. and International Partners Issue Recommendations to Secure Software Products Through Memory Safety
    https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/3608324/us-and-international-partners-issue-recommendations-to-secure-software-products/
  • buffer overflowを自動的に検知するための方法・ツール等にはどのようなものがあるか?

9. 最終発表と本演習の評価について

最終発表では,皆さんが本演習で学んだことを発表してもらいます.以下の表を参考に,それぞれのグループがどこまで,どのようなことを達成できたかをまとめ,発表してください.本演習では発表内容をもとに,グループごとの進行度に応じて以下のように評価を行います.時間の目安を参考に,進行が遅れているなと思ったグループはTAにアドバイスを求める等積極的に行なってみるように心がけてください.また,各課題の達成点は達成度に応じて評価します.たとえ,明確なゴールに辿り着かなかったとしても,その過程での試行錯誤について発表して頂ければそれ自体を評価しますので,ぜひ高い目標にチャレンジしてみてください.

時間の目安 進行度 達成点
9/24 11:00 (必須)バッファオーバーフローの実行 +30点
9/24 14:00 (必須)バッファオーバーランを使ったコマンド実行に成功 +15点
9/24 17:30 (必須)リモートシェルの起動に成功(とりあえず動かす) +15点
9/25 発表終了まで (選択)発展課題1の実施 +15点
(選択)発展課題2の実施 +15点
(選択)自由課題の実施 +15点
(必須)最終発表の実施(わかりやすさ等で評価) +15点

選択式の発展課題や自由課題は複数取り組んでも構いません.教員の評価による加点も含めた合計点を最終得点とします.

10. 参考URL

基礎課題向け
発展課題向け