情報処理安全確保支援士 2018年 春期 午後1 問01
ソフトウェアの脆弱性に関する次の記述を読んで、設問1~9に答えよ。
V社は、従業員数100名のソフトウェア開発会社である。V社では、開発に関わる全員が情報セキュリティを意識した実装を行えるよう、開発経験が浅い従業員にセキュリティ教育を行っている。次は、任意の攻撃コードが実行され得る脆弱性について、開発チームのT主任が部下のUさんに教えていた時の会話である。
T主任:任意の攻撃コードが実行され得る脆弱性は幾つかある。確保済みメモリ領域を超えてデータを書き込んでしまうaと呼ばれる脆弱性の報告が以前から多かった。最近は解放したメモリ領域を後から使用してしまうbと呼ばれる脆弱性の報告も多くなってきている。
Uさん:bという脆弱性は具体的にはどのようなものなのですか。
T主任:例えば、図1のC++ソースコードからなるプログラム(以下、例示プログラムという)があったとする。例示プログラムは、図2に示すシステム構成の中で動作し、ノートと呼ぶメモ書き機能を実現するものであり、クライアントから利用者が自身の名前とメッセージを登録したり、それを他の利用者が参照したりする。例示プログラムでは、ノートはNote構造体で表現され、利用者の操作に応じて、NoteManagerクラスの各メンバ関数が個別に呼び出される。各メンバ関数では、ノートの生成(CreateNote)、利用者の名前の登録(RegisterName)、メッセージの登録(RegisterMsg)、ノートの登録内容表示(DisplayNote)、ノートの破棄(DeleteNote)を行う機能を実装している。例示プログラムにおいて、DeleteNoteメンバ関数内でm_noteの指すメモリ領域を解放しているが、仮にDeleteNoteメンバ関数が呼び出された直後にRegisterNameメンバ関数が呼び出されると、解放したm_noteの指すメモリ領域にアクセスできてしまう。これがbという脆弱性だ。例示プログラムでは、悪意をもつ利用者(以下、攻撃者という)の操作によって、任意の攻撃コードを実行され、サーバを乗っ取られてしまうおそれがある。


Uさん:解放したメモリ領域にアクセスされると、どのように攻撃コードが実行されるのですか。
T主任:例示プログラムにおいて、図3に示す(1)~(3)の順でメンバ関数の呼出しが行われたとしよう。その場合、図3の(1)で確保されていたNote構造体用のメモリ領域と、図3の(3)で確保されたchar[8]用のメモリ領域が同じアドレスに割り当てられる可能性がある。その場合、①RegisterName メンバ関数内で読み込まれる攻撃者からの入力値によって、元々Note構造体用であったメモリ領域が上書きされる。このときの攻撃者からの入力値がうまく細工されていると、②次に RegisterName メンバ関数が呼ばれた際、その際に読み込まれる攻撃者からの入力値が、攻撃者の指定したアドレスに書き込まれることになる。このように、攻撃者からの入力値が攻撃者の指定したアドレスに書き込まれる場合には攻撃コードが実行され得る。

Uさん:もう少し具体的に説明してください。
T主任:関数テーブルの例で説明しよう。ここでいう関数テーブルとは、プログラム中で呼び出している共有ライブラリに含まれる関数(以下、ライブラリ関数という)の実行コードの先頭アドレスが記録されたテーブルだ。ライブラリ関数の呼出し時には、図4に示すように関数テーブルに記録された実行コードの先頭アドレスの値を参照してライブラリ関数の実行コードに処理が遷移する。

T主任:攻撃者が既に、攻撃コードをメモリ上に書き込んでいるとしよう。この状態で、例えば、攻撃コードが存在するアドレスを関数テーブルに書き込まれた場合、関数の呼出し時に関数テーブルが参照されると、攻撃コードに処理が遷移してしまう。
Uさん:例示プログラムを攻撃する場合だと、関数テーブルに書き込むアドレスは具体的にどのような値になりますか。
T主任:例えば、RegisterMsg メンバ関数の呼出しによって m_note->msg が指し示すメモリ領域に攻撃コードが書き込まれていて、その先頭アドレスが0x0b123400 と分かっていたとする。その場合、関数テーブルが表1に示すようになっていたとすると、アドレスc番地に値dを書き込むことによって、次にCreateNoteメンバ関数が呼び出された際、攻撃コードに処理が遷移することになる。

Uさん:RegisterMsgメンバ関数の呼出しによって入力された攻撃コードはe領域に書き込まれるので、データ実行防止と呼ばれる機能が有効化されていた場合、実行されませんよね。
T主任:確かにそのとおりだ。ただし、③関数テーブルに書き込むアドレスとして、例えば、共有ライブラリ内のメモリアドレスを選べば、データ実行防止が有効化されていた場合でも、攻撃者が任意の処理を実行できる可能性がある。例示プログラムにおいて、共有ライブラリ内のメモリアドレスが表2のようになっていたとすると、関数テーブルに書き込むアドレスをf番地にすることによって、データ実行防止が有効化されていた場合でも、/bin/shを起動して任意のシェルコマンドを実行できる可能性がある。


Uさん:共有ライブラリ内のメモリアドレスは、表2のように前もって知ることができるものなのですか。
T主任:共有ライブラリをメモリに読み込む際、それを配置するアドレスを毎回ランダムに選ぶASLR(Address Space Layout Randomization)と呼ばれるセキュリティ機能がある。この機能が有効な場合、共有ライブラリ内のメモリアドレスを前もって知ることは難しい。しかし、例示プログラムにおいて、図3の(3)の状態をつくり出せれば、RegisterNameメンバ関数とgメンバ関数を利用することによって、ASLRが有効化されていた場合でも、共有ライブラリ内のメモリアドレスを特定できる可能性がある。データ実行防止やASLRなどは効果的な機能ではあるが、根本的な対策にはならない。やはり脆弱性そのものを修正することが重要だ。
Uさん:では、bの脆弱性を修正するには、例示プログラムではどうすればよいのでしょうか。
T主任:図1の39行目の直後にhという1文を加えればよいだろう。
Uさんは、今回学んだことをコードレビューの観点として生かしていくことにした。
設問1:
本文中のa、bに入れる適切な脆弱性名を、解答群の中から選び、記号で答えよ。
解答群
ア:CSRF
イ:SQLインジェクション
ウ:Use-After-Free
エ:クロスサイトスクリプティング
オ:コマンドインジェクション
カ:バッファオーバフロー
キ:フォーマットストリングバグ
ク:レースコンディション
模範解答
a:カ
b:ウ
解説
解答の論理構成
- 【問題文】では
「確保済みメモリ領域を超えてデータを書き込んでしまうaと呼ばれる脆弱性」
と説明しています。確保領域を超える書込みは典型的なバッファオーバフローであり、解答群「カ:バッファオーバフロー」が一致します。 - 同じく【問題文】で
「最近は解放したメモリ領域を後から使用してしまうbと呼ばれる脆弱性」
と記載されています。解放後の領域を再利用してしまう欠陥は Use-After-Free を指すため、解答群「ウ:Use-After-Free」が該当します。 - 以上より
a=「カ:バッファオーバフロー」
b=「ウ:Use-After-Free」
が妥当です。
誤りやすいポイント
- 「確保済みメモリ領域を超えてデータを書き込む」というキーワードから、他のインジェクション系(例:フォーマットストリングバグ)と混同する。
- 解放後に再利用される状況を「ダングリングポインタ」とだけ覚えており、正式名称「Use-After-Free」を選択できない。
- SQL インジェクションやクロスサイトスクリプティングといった Web 系攻撃を脆弱性全般のキーワードとして連想し、メモリ管理の問題であることを見落とす。
FAQ
Q: バッファオーバフローはスタックとヒープどちらも対象になりますか?
A: はい。確保した領域(スタック変数・ヒープ領域)を超えて書き込む行為はどちらでもバッファオーバフローと呼ばれます。
A: はい。確保した領域(スタック変数・ヒープ領域)を超えて書き込む行為はどちらでもバッファオーバフローと呼ばれます。
Q: Use-After-Free は二重解放(Double Free)と同じですか?
A: 異なります。Double Free は同じポインタを2回解放する操作ミス、Use-After-Free は「解放後そのメモリを再利用してアクセス」する点が問題です。
A: 異なります。Double Free は同じポインタを2回解放する操作ミス、Use-After-Free は「解放後そのメモリを再利用してアクセス」する点が問題です。
Q: 脆弱性修正の基本方針は?
A: バッファオーバフローでは境界チェックや安全な API の使用、Use-After-Free ではポインタを NULL にする・スマートポインタを使うなど、根本原因を除去する実装改善が必須です。
A: バッファオーバフローでは境界チェックや安全な API の使用、Use-After-Free ではポインタを NULL にする・スマートポインタを使うなど、根本原因を除去する実装改善が必須です。
関連キーワード: バッファオーバフロー、Use-After-Free, メモリ管理、ASLR, データ実行防止
設問2:
本文中の下線②のようになるためには、本文中の下線①で読み込まれる攻撃者からの入力値はどのような値である必要があるか。攻撃者の指定したアドレスを0x12345678、改行コードを0x0aとした場合について、入力値の具体的なバイト列を14字以内の16進数文字列で答えよ。ここで、アドレスは32ビットであり、バイトオーダがリトルエンディアンのバイトマシンによって扱われるものとする。
模範解答
785634120a
解説
解答の論理構成
-
前提整理
- 設問は「本文中の下線②のようになるためには、本文中の下線①で読み込まれる攻撃者からの入力値はどのような値である必要があるか。」と尋ねています。
- 攻撃者が目指すのは「攻撃者の指定したアドレスに書き込まれる」ことです(下線②)。
- 指定アドレスは 0x12345678、バイトオーダは「リトルエンディアン」です。
-
書き換える標的
- 「Note 構造体」の先頭 4 バイトは name へのポインタです(T主任の説明より)。
- Delete した領域に再確保した char[8] が重なり、RegisterName の %7s で 最大 7 バイト が書き込まれます。
- 従って、最初の 4 バイトでポインタを書き換えれば、次回の RegisterName で入力バッファが 0x12345678 を指すようになります。
-
リトルエンディアンでの並び
- 0x12345678 → 下位バイトから 78 56 34 12 の順にメモリへ格納されます。
-
ストリング終端と改行
- %7s はトークン終端を示す改行 0x0a に遭遇すると読取を終了し、 直後に自動で NUL(0x00) を追加します。
- したがって攻撃者が送るバイト列は
「78 56 34 12 0a」
(4 バイトのアドレス値+改行)の 5 バイトで十分です。
-
16 進数文字列での回答
- 大文字小文字は問われていません。
- 連続した 10 文字で「785634120a」となります。
誤りやすいポイント
- エンディアンを逆にして「12345678」で答えてしまう。
- 改行を忘れ、終端コードを 0x0a ではなく 0x00 と誤解する。
- %7s の制限から 8 バイト全てを入力しようとしてしまう。
- 16 進数表記に 0x を付けてしまい、問題指示「16進数文字列」に反する。
FAQ
Q: なぜ NUL 0x00 を含めなくて良いのですか?
A: NUL は scanf("%s") が自動で付与するため、攻撃者が送信する必要はありません。
A: NUL は scanf("%s") が自動で付与するため、攻撃者が送信する必要はありません。
Q: 5 バイトでは char[8] の残り 3 バイトが未使用になりますが問題ありませんか?
A: 目的はポインタ先頭 4 バイトを書き換えることなので、残りは 0x00 で初期化されても攻撃に支障はありません。
A: 目的はポインタ先頭 4 バイトを書き換えることなので、残りは 0x00 で初期化されても攻撃に支障はありません。
Q: %7s で非表示文字を入力しても受け付けられるのですか?
A: %s は空白・改行・タブでトークンを区切るだけなので、0x01〜0x7F の制御文字を含むバイト列も読み込まれます。
A: %s は空白・改行・タブでトークンを区切るだけなので、0x01〜0x7F の制御文字を含むバイト列も読み込まれます。
関連キーワード: Use‐after‐free, リトルエンディアン、関数テーブル、ASLR, データ実行防止
設問3:
本文中のcに入れる適切なアドレスを表1中の(ア)〜(シ)から選び、記号で答えよ。
模範解答
c:(エ)
解説
解答の論理構成
-
攻撃者の目的は「次に CreateNote メンバ関数が呼び出された際、攻撃コードに処理が遷移」させることです。
引用:【問題文】「その場合、関数テーブルが表1に示すようになっていたとすると、アドレスc番地に値dを書き込むことによって、次に CreateNote メンバ関数が呼び出された際、攻撃コードに処理が遷移することになる。」 -
したがって c には「CreateNote メンバ関数が実行時に参照する関数テーブルの格納場所」を選ぶ必要があります。CreateNote は new Note() を実行するので、new の実行コード先頭アドレスを格納している表1のエントリが対象です。
-
表1を確認すると new の実行コード先頭アドレスを保持している行は
(エ) 0x08049e40 です。
引用:【表1】「(エ) 0x08049e40 … new の実行コードの先頭アドレス」。 -
よって c に入るのは (エ)。模範解答「c:(エ)」と合致します。
誤りやすいポイント
- new[] と new を取り違え、(オ) を選んでしまう。CreateNote が呼ぶのは配列確保ではなく単一オブジェクト確保の new。
- アドレスではなく値列(例えば (コ) 0xf7cff150)を選択しようとするミス。設問は「アドレスc番地」を問うているので、左欄のアドレスを選ぶ。
- delete 系を選択する早とちり。CreateNote 内では解放ではなく確保が行われる。
FAQ
Q: なぜ scanf や printf のエントリではないのですか?
A: CreateNote 内で呼ばれているのはメモリ確保処理だけで入出力処理は行われていません。そのため攻撃対象となる関数テーブルのエントリも new のものになります。
A: CreateNote 内で呼ばれているのはメモリ確保処理だけで入出力処理は行われていません。そのため攻撃対象となる関数テーブルのエントリも new のものになります。
Q: (エ) を書き換えると必ず攻撃に成功しますか?
A: ASLR や DEP など環境依存の防御機構が働くと成功率は下がります。問題文でも後段で ASLR やデータ実行防止について解説しているとおり、脆弱性修正が根本対策です。
A: ASLR や DEP など環境依存の防御機構が働くと成功率は下がります。問題文でも後段で ASLR やデータ実行防止について解説しているとおり、脆弱性修正が根本対策です。
Q: new ではなく new[] が呼ばれるケースはありますか?
A: 例示プログラムでは RegisterName・RegisterMsg 内で new char[ ] を使いますが、CreateNote では配列確保を行っていないため該当しません。
A: 例示プログラムでは RegisterName・RegisterMsg 内で new char[ ] を使いますが、CreateNote では配列確保を行っていないため該当しません。
関連キーワード: use-after-free, 関数テーブル改ざん、動的メモリ確保、ASLR, データ実行防止
設問4:
本文中のdに入れる適切な値を16進数で答えよ。
模範解答
d:0x0b123400
解説
解答の論理構成
- 問題文には、攻撃コードが置かれる位置について次の記述があります。
「RegisterMsg メンバ関数の呼出しによって m_note->msg が指し示すメモリ領域に攻撃コードが書き込まれていて、その先頭アドレスが0x0b123400 と分かっていたとする。」
- さらに、T主任は次のように説明しています。
「アドレスc番地に値dを書き込むことによって、次に CreateNote メンバ関数が呼び出された際、攻撃コードに処理が遷移することになる。」
- ここで“値d”とは、「攻撃コードが存在するアドレス」を意味します。前掲引用①より攻撃コードの先頭アドレスは 0x0b123400 と確定しています。
- よって、関数テーブルに書き込む “値d” も 0x0b123400 になります。
誤りやすいポイント
- 「アドレスc番地」と「値d」を取り違える。書き込むのはアドレス値(0x0b123400)であり、書き込み先が関数テーブルのアドレスcです。
- system など共有ライブラリ内のアドレスと混同し、表2の値を選んでしまう。設問では “攻撃コードを直接指す値” を求めています。
- 10 進数や 0x の付け忘れなど、16 進表記の形式ミス。
FAQ
Q: d はなぜシェルコードではなくアドレスなのですか?
A: 関数テーブルには“実行コードの先頭アドレス”を格納するからです。ここに攻撃コードそのものではなく、その場所を示すポインタ(0x0b123400)を書き込みます。
A: 関数テーブルには“実行コードの先頭アドレス”を格納するからです。ここに攻撃コードそのものではなく、その場所を示すポインタ(0x0b123400)を書き込みます。
Q: もし ASLR が有効なら 0x0b123400 は毎回変わりますか?
A: ASLR が対象にするのは共有ライブラリやスタックなどです。問題文の前提では m_note->msg はヒープ上に確保されており、ASLR の影響を受けないため固定アドレスとして扱えます。
A: ASLR が対象にするのは共有ライブラリやスタックなどです。問題文の前提では m_note->msg はヒープ上に確保されており、ASLR の影響を受けないため固定アドレスとして扱えます。
Q: 16 進数表記で 0B123400 と大文字にしてもよいですか?
A: 試験では一般に 0x プレフィックス付き小文字が推奨されます。余計な減点を避けるために “0x0b123400” と記載しましょう。
A: 試験では一般に 0x プレフィックス付き小文字が推奨されます。余計な減点を避けるために “0x0b123400” と記載しましょう。
関連キーワード: Use After Free, 関数テーブル、任意コード実行、ヒープメモリ、ポインタ改ざん
設問5:
本文中のeに入れるメモリ領域の名称を答えよ。
模範解答
e:ヒープ
解説
解答の論理構成
- 問題文では、攻撃コードが格納される場所を
「Uさん:RegisterMsgメンバ関数の呼出しによって入力された攻撃コードはe領域に書き込まれるので」と説明しています。 - RegisterMsg メンバ関数のコードは
m_note->msg = new char[100];
でバッファを確保しています。new 演算子が返すアドレスは C++ において ヒープ領域 です。 - したがって、入力された攻撃コードが入るのはヒープであり、e に入る適切な用語は「ヒープ」です。
誤りやすいポイント
- スタックと混同する
char buf[100] のような自動変数と違い、new で確保したメモリはスタックではなくヒープに配置されます。 - グローバル領域と誤認する
Note *m_note; はクラスのメンバであり、実際の文字列バッファは実行時に new で動的確保されます。 - 「データ実行防止が効く=実行不可領域」と短絡する
DEP が有効でもヒープに配置されたデータは実行禁止属性となるだけで、関数テーブル書換えなど制御フロー hijack によってコード実行は可能です。
FAQ
Q: なぜ new を使うとヒープに配置されるのですか?
A: C/C++ のメモリモデルでは、new(または malloc)が呼ばれると OS のヒープ管理領域から要求バイト数分を確保し、その先頭アドレスを返します。スタックやグローバル領域は使用しません。
A: C/C++ のメモリモデルでは、new(または malloc)が呼ばれると OS のヒープ管理領域から要求バイト数分を確保し、その先頭アドレスを返します。スタックやグローバル領域は使用しません。
Q: delete m_note; 後にポインタを NULL にしないと何が問題になりますか?
A: 解放済みアドレスを保持したままなので再利用時に未定義動作を引き起こし、b として示された「解放したメモリ領域を後から使用してしまう」ユースアフタフリー脆弱性になります。
A: 解放済みアドレスを保持したままなので再利用時に未定義動作を引き起こし、b として示された「解放したメモリ領域を後から使用してしまう」ユースアフタフリー脆弱性になります。
Q: DEP を回避する方法として共有ライブラリの関数アドレスを書き込む手口は一般に何と呼ばれますか?
A: 既存コードへのジャンプを利用する ROP(Return Oriented Programming)や PLT/GOT オーバライドなど、コードリユース攻撃に分類されます。
A: 既存コードへのジャンプを利用する ROP(Return Oriented Programming)や PLT/GOT オーバライドなど、コードリユース攻撃に分類されます。
関連キーワード: ヒープ、ユースアフタフリー、メモリ破壊、データ実行防止、ASLR
設問6:
本文中の下線③の理由を、45字以内で述べよ。
模範解答
ライブラリ関数はデータ実行防止の対象ではないメモリ領域に配置されているから
解説
解答の論理構成
- 問題文は、データ実行防止(DEP)が有効でも攻撃が成立する状況として、
「③関数テーブルに書き込むアドレスとして、例えば、共有ライブラリ内のメモリアドレスを選べば、データ実行防止が有効化されていた場合でも、攻撃者が任意の処理を実行できる可能性がある」
と述べています。 - DEP は “実行可能でないはずの領域” を実行不可にしてコード実行を防ぐ仕組みです。逆に言えば 既に実行可能フラグが立っている領域、すなわちプログラムや共有ライブラリのコード領域は対象外です。
- 共有ライブラリのコードは OS ローダが 実行属性付き でメモリへ配置します。したがって、そのアドレスを関数テーブルへ上書きすれば処理が遷移しても DEP に阻まれません。
- よって、「ライブラリ関数はデータ実行防止の対象ではないメモリ領域に配置されているから」という結論になります。
誤りやすいポイント
- DEP が「すべての実行を禁止する」と誤解し、ライブラリ領域も保護対象と考えてしまう。
- ASLR と DEP を混同し、アドレスさえ分かれば DEP も回避できると思わない、という逆の思い込み。
- 「関数テーブルを書き換える=コードインジェクション」と短絡し、既存コード流用(Return-to-libc 型)という視点を見落とす。
FAQ
Q: DEP が有効なら攻撃者が注入したシェルコードは実行できないのでは?
A: そのとおりですが、本問は「共有ライブラリ内のコードを呼び出す」方式です。ライブラリ領域は実行可能なので DEP を回避できます。
A: そのとおりですが、本問は「共有ライブラリ内のコードを呼び出す」方式です。ライブラリ領域は実行可能なので DEP を回避できます。
Q: ライブラリ関数を呼び出すだけで任意コード実行になるのですか?
A: system("/bin/sh") のように OS コマンドを呼ぶ関数へ制御を渡せば、攻撃者はシェルを取得し自由に操作できます。
A: system("/bin/sh") のように OS コマンドを呼ぶ関数へ制御を渡せば、攻撃者はシェルを取得し自由に操作できます。
Q: ASLR が有効でも共有ライブラリのアドレスは分かるのですか?
A: 本文中にあるように RegisterName などを使ってリークを行えば、実行時に割り振られたアドレスを得られる場合があります。
A: 本文中にあるように RegisterName などを使ってリークを行えば、実行時に割り振られたアドレスを得られる場合があります。
関連キーワード: データ実行防止、共有ライブラリ、ASLR, 関数テーブル、メモリ保護
設問7:
本文中のfに入れる適切なアドレスを表2中の(ア)〜(ク)から選び、記号で答えよ。
模範解答
f:(ア)
解説
解答の論理構成
- 攻撃者の最終目標は「/bin/sh を起動して任意のシェルコマンドを実行」することです。これは OS にコマンドを渡すライブラリ関数 system() を悪用するのが定番手法です。
- 本文には「共有ライブラリ内のメモリアドレスが表2のようになっていたとすると、関数テーブルに書き込むアドレスをf番地にすることによって、データ実行防止が有効化されていた場合でも、/bin/shを起動して任意のシェルコマンドを実行できる可能性がある。」とあります。
- 表2を確認すると、system の実行コードは「(ア) 0xf7cc8da0」と明示されています。
- したがって、「/bin/sh を起動」させるために関数テーブルへ書き込むべきアドレス f は system の実行コード先頭アドレスである (ア) 0xf7cc8da0 です。
- 他の候補(printf や new など)は OS コマンド実行能力を持たないため要件を満たしません。以上より解答は (ア) となります。
誤りやすいポイント
- /bin/sh の文字列アドレス「(ク) 0xf7de99ab」を選ぶ誤答
- 文字列は実行可能コードではなく、これを書き込んでも制御は遷移しません。
- printf や scanf を選ぶ誤答
- どちらもシェルを起動する機能がなく、意図した「任意コマンド実行」には直結しません。
- 表1と表2を混同する
- 表1は「現在の関数テーブル」、表2は「ライブラリ内のアドレス一覧」です。書き込む先(表1)と書き込む値(表2)を区別しましょう。
FAQ
Q: なぜ system() に直接ジャンプするだけでシェルが起動するのですか?
A: 例示プログラムでは既に "/bin/sh" という引数がメモリ上に配置されている前提で説明しています。system() が呼び出される際、この文字列が第一引数として渡されるよう細工されているため、シェルが起動します。
A: 例示プログラムでは既に "/bin/sh" という引数がメモリ上に配置されている前提で説明しています。system() が呼び出される際、この文字列が第一引数として渡されるよう細工されているため、シェルが起動します。
Q: データ実行防止が有効なら攻撃コードを実行できないのでは?
A: 本問は「実行可能な共有ライブラリ上のコードを再利用する」Return-to-libc 型の攻撃です。実行権限付き領域にある system() を呼び出すため、データ実行防止を回避できます。
A: 本問は「実行可能な共有ライブラリ上のコードを再利用する」Return-to-libc 型の攻撃です。実行権限付き領域にある system() を呼び出すため、データ実行防止を回避できます。
Q: ASLR でアドレスが毎回変わるのに、どうやって system() のアドレスを知るのですか?
A: 本文にあるように「RegisterName メンバ関数とgメンバ関数を利用」して情報漏えいを起こし、実行時に実際のアドレスを取得する方法が示唆されています。
A: 本文にあるように「RegisterName メンバ関数とgメンバ関数を利用」して情報漏えいを起こし、実行時に実際のアドレスを取得する方法が示唆されています。
関連キーワード: ユースアフターフリー、関数テーブル書き換え、データ実行防止、ASLR, Return-to-libc
設問8:
本文中のgに入れるメンバ関数の名前を答えよ。
模範解答
g:DisplayNote
解説
解答の論理構成
- 【問題文】には
「例示プログラムにおいて、図3の(3)の状態をつくり出せれば、RegisterNameメンバ関数とgメンバ関数を利用することによって、ASLRが有効化されていた場合でも、共有ライブラリ内のメモリアドレスを特定できる可能性がある。」
とあります。ここで RegisterName は“入力”を受け付ける機能です。 - 共有ライブラリ内のアドレスを“特定”するには、ユーザ入力で細工した値をプログラムに表示させ、画面上から読み取れる必要があります。
- C++ ソースコード(図1)を見ると、表示を行うメンバ関数は
「void DisplayNote(){
if(m_note && m_note->name) printf("Name: %s\n"、m_note->name);
if(m_note && m_note->msg) printf("Message: %s\n"、m_note->msg);
}」
だけです。 - RegisterName で細工した名前やポインタを格納し、DisplayNote でその値を printf させれば、ASLR 下でも実際に配置されたアドレスが漏えいします。
- よって、g に入るメンバ関数名は「DisplayNote」となります。
誤りやすいポイント
- CreateNote を選んでしまう
→ CreateNote はメモリ確保のみで表示処理がないため、アドレスを観測できません。 - RegisterMsg と誤答する
→ RegisterMsg も入力専用であり、アドレスを“漏えい”させる動作はありません。 - 「DisplayNote」の大文字・小文字を混同する
→ クラスメソッド名は【問題文】で示されたとおり「DisplayNote」です。
FAQ
Q: なぜ DisplayNote の printf でアドレスが漏れるのですか?
A: RegisterName で m_note->name に細工したポインタ値を書き込むと、DisplayNote が printf("Name: %s") を実行した際、そのポインタを文字列アドレスとして参照しようとします。この時にクラッシュせず読める領域であれば実アドレスが画面に現れるため、ASLR 下でも基底アドレス推定が可能になります。
A: RegisterName で m_note->name に細工したポインタ値を書き込むと、DisplayNote が printf("Name: %s") を実行した際、そのポインタを文字列アドレスとして参照しようとします。この時にクラッシュせず読める領域であれば実アドレスが画面に現れるため、ASLR 下でも基底アドレス推定が可能になります。
Q: DisplayNote だけでは任意コード実行に至りませんか?
A: DisplayNote は情報漏えい用途で使われ、得たアドレスを基に関数テーブル書換えなど次の段階の攻撃を行います。任意コード実行は RegisterName 等を再度呼び出して関数テーブルへ細工が行われた時点で発生します。
A: DisplayNote は情報漏えい用途で使われ、得たアドレスを基に関数テーブル書換えなど次の段階の攻撃を行います。任意コード実行は RegisterName 等を再度呼び出して関数テーブルへ細工が行われた時点で発生します。
Q: 対策としては何を修正すべきですか?
A: 【問題文】で示されたとおり、「図1の39行目の直後にhという1文を加えればよい」とされており、具体的には解放後にポインタを NULL に設定して Use-After-Free を防止します。
A: 【問題文】で示されたとおり、「図1の39行目の直後にhという1文を加えればよい」とされており、具体的には解放後にポインタを NULL に設定して Use-After-Free を防止します。
関連キーワード: Use-after-Free, ASLR, 情報漏えい、関数ポインタ書換、データ実行防止
設問9:
本文中のhに入れる適切なソースコードを答えよ。
模範解答
h:m_note=NULL;
解説
解答の論理構成
- 脆弱性の原因を確認
【問題文】では「解放したメモリ領域を後から使用してしまうbと呼ばれる脆弱性の報告も多くなってきている。」とあり、これは “use-after-free” です。さらに「DeleteNoteメンバ関数内でm_noteの指すメモリ領域を解放しているが、仮にDeleteNoteメンバ関数が呼び出された直後にRegisterNameメンバ関数が呼び出されると、解放したm_noteの指すメモリ領域にアクセスできてしまう。」と具体的な再利用シナリオが示されています。 - 何が欠けているかを特定
DeleteNote は 37~39 行目で
• delete[] m_note->name;
• delete[] m_note->msg;
• delete m_note;
を実行していますが、ポインタを無効化していません。このため m_note は“解放済みメモリを指すダングリングポインタ”になり、次の呼び出しで誤って再利用されます。 - 有効な修正策
使い終わったポインタをただちに NULL に設定しておけば、後続処理で if(m_note && …) の判定が偽になりアクセスは行われません。問題文も「図1の39行目の直後にhという1文を加えればよいだろう。」と指摘しています。 - 挿入すべきコード
ダングリングポインタを防ぐ標準的手法は「ポインタを NULL にする」。よって h には
m_note=NULL;
を追加するのが最適解です。
誤りやすいポイント
- 「delete m_note; だけで十分」と思い込み、NULL 化を忘れる。
- m_note->name や m_note->msg を個別に NULL にしても、根本の m_note を放置するケース。
- nullptr(C++11 以降)と混同し、問題文の「32ビット環境」「NULL」を変えてしまう。原文どおり「m_note=NULL;」が必要です。
FAQ
Q: delete 後に必ず NULL 化しなければいけませんか。
A: 条件分岐でポインタの生存を判定している設計では必須です。本問題のように if(m_note && …) が多用されている場合、NULL 化しないと use-after-free になります。
A: 条件分岐でポインタの生存を判定している設計では必須です。本問題のように if(m_note && …) が多用されている場合、NULL 化しないと use-after-free になります。
Q: m_note を NULL にしただけで m_note->name などは放置して良いのですか。
A: 39 行目以前に delete[] m_note->name; と delete[] m_note->msg; で解放済みです。m_note が NULL になればそれらのメンバへの経路も断たれます。
A: 39 行目以前に delete[] m_note->name; と delete[] m_note->msg; で解放済みです。m_note が NULL になればそれらのメンバへの経路も断たれます。
Q: nullptr を使う方が安全ですか。
A: 近代的 C++ ではそうですが、試験は原文「NULL」を指定しているため答案も「m_note=NULL;」が正しいです。
A: 近代的 C++ ではそうですが、試験は原文「NULL」を指定しているため答案も「m_note=NULL;」が正しいです。
関連キーワード: use-after-free, ダングリングポインタ、NULL初期化、メモリ解放、ポインタ安全


