DXライブラリのバッファオーバーフローの脆弱性が修正された
2015年11月、DXライブラリの書式指定描画関数におけるバッファオーバーフローの脆弱性をIPAに報告しました。2015年12月29日に修正バージョンVer 3.16が公開され、JVNでも情報が公開されました(JVN#49476817)。 最悪の場合、任意のコードが実行されます。一定の条件を満たしているアプリケーションのみが影響を受けますが、DXライブラリを利用してアプリケーションを開発されている方は、念のため最新バージョンでビルドしなおすことをお勧めします。
私としてはソフトウェアの脆弱性をIPAに報告するのは初めての経験なのですが、今後の参考のために、発見の経緯や気をつけたいことを記したいと思います。
脆弱性の内容
DXライブラリは、Windows向けのC++用ゲームライブラリです。DXライブラリ 管理人様の情報によると
DrawFormatString や printfDx などの書式指定文字列を引数とする関数で、書式に基づいて生成される文字列の長さが 1023文字( 関数によっては 2047文字 )を超えるとバッファオーバーフローが発生します。
スタック領域に対するメモリの不正なアクセスが発生してしまうので、引数として渡すデータを工夫することで任意のコードが実行できてしまいます。
と説明されています。具体的には以下のようなコードは脆弱性の影響を受けます。
char buf[4096]; /* (外部からbufに文字列を読み込むコード) */ DrawFormatString(0, 0, GetColor(255, 255, 255), "%s", buf);
これだけ見ると、『画面に描画するための文字列にそんなに大きなバッファを確保する訳がない』とか、『ライブラリの脆弱性というより2048文字以上処理できないという仕様であって、アプリケーション側の実装の問題では』と思われる方もいるかもしれません。しかし、DrawFormatStringのリファレンスにはそのようなことは書かれていませんし、下記のような実装がされているアプリケーションも十分考えられるため、DXライブラリの脆弱性として報告しました。
char buf[256]; /* (外部からbufに文字列を読み込むコード) */ DrawFormatString(0, 0, GetColor(255, 255, 255), buf); // format string attackが可能
std::string buf; /* (外部からbufに文字列を読み込むコード) */ DrawFormatString(0, 0, GetColor(255, 255, 255), "%s", buf.c_str());
具体的な名前は書きませんが、私は3つのフリーソフトでこの脆弱性が存在することを確認しました。
発見の経緯
2015年11月10日のことです。きっかけは、私が大学で所属している部活の部員が制作した音ゲーのテストプレイ中のことでした。非常に長い文字列を曲のタイトルに設定するとアプリケーションが異常終了することに気が付きました。 そのゲームは、DXライブラリを使って開発されており、異常終了の原因を調べているうちに、どうやらDrawFormatString関数の内部関数でバッファオーバーフローが発生していることがわかりました。
その日から実証コードと報告内容を作成し、10日後の11月20日に報告しました。
CL_vsprintf
JVNの解説に登場するCL_vsprintf()とは、名前から推測できるようにC言語の標準関数であるvsprintf()とほぼ同じ動作をするDXライブラリの内部関数です。DXライブラリの書式指定系関数はすべてこの関数を使って書式変換を行っています。vsprintf()は、書き込み先バッファのサイズをチェックしないため、バッファオーバーフローを引き起こしやすい関数の一つとして知られています。MSVCではvsprintf()を使用すると、この関数は安全ではないとして、コンパイラ警告 C4996が発生します。
複数のlibファイル
DXライブラリは、Windows 98を現在でもサポートしており、開発環境としてVisual C++ 6.0をサポートするために、Visual C++ 6.0でコンパイルされています*1。 このバージョンのVC++では、/GSや/SAFESEHといったコンパイラのセキュリティ機能はサポートされていません。DXライブラリは静的ライブラリであるため、アプリケーションのビルド時の/GSオプションの有無にかかわらず、stack canaryによるバッファオーバーランのチェックが行われません。このことは攻撃を容易にし、事態をより深刻にするように思います。
ただし、DXライブラリ Ver 3.14cより、Visual Studio 2012・2015でビルドされたライブラリが追加されており*2、/GSも有効になっています。 ソースコードのDxDataTypeWin.hを読むとわかるのですが、アプリケーションをビルドする時のVisual Studioのバージョンによってリンクされるlibファイルを切り替えるようになっています。Visual Studio 2010以下のMSVCでビルドすると、VC++6.0でコンパイルされたlibファイルとリンクされているため、注意が必要です。
実証コード
この脆弱性によって発生しうる脅威を説明するために、2種類のデモアプリケーションを作成しました。
- ファイルから読み込んだ文字列を描画するアプリケーション
- ネットワークから受信した文字列を描画するアプリケーション
どちらも本質的には同じなのですが、後者はリモートでの攻撃が可能なことを示すために(そのようなアプリケーションが実際に存在するかは別として)作成しました。
以下は、作成したデモアプリケーションの一つです。ファイルから文字列を読み込み、DrawFormatString()関数で描画します。
#include <Windows.h> #include <fstream> #include <string> #include "DxLib.h" #ifndef INPUT_FILE #define INPUT_FILE "input.txt" #endif int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { std::ifstream ifs(INPUT_FILE); std::string buf; if (!ifs.fail()) { getline(ifs, buf); ifs.close(); } SetUseDirectInputFlag(FALSE); // デバッガの動作を軽くするため、入力処理にDirectInputを使用しない ChangeWindowMode(TRUE); if (DxLib_Init() == -1) return 0; ClearDrawScreen(); DrawFormatString(0, 0, GetColor(255, 255, 255), "%s", buf.c_str()); WaitKey(); DxLib_End(); return 0; }
exploitは悪用可能なので公開しませんが、技術的に特に新しいことはしていません。 /GS有り、DEP有り、ASLR無し、SafeSEH無しなexeファイル向けにSEH overwrite+ROPのexploitを作成した程度です。
ユーザが気をつけたいこと
- 特別な理由がない限りアプリケーションは最新版を使用する
- 信頼できない入手先のセーブデータ、リプレイデータ、追加データを不用意に追加しない
- ネットワーク対応のアプリケーション・ゲームで不審なサーバーに接続しない
脆弱性攻撃が行われる場合、pdfファイルやdocxファイルからマルウェアに感染することもあります。そのソフトウェアに脆弱性が存在する場合、追加ファイルの拡張子がexeでないから安全とは言えません。 一般的なことしか書いていませんが、未知の脆弱性に備えて、DXライブラリで開発されたアプリケーションに限らず心がけたいところです。
開発者が気をつけたいこと
- [脆弱性の内容]に示したようなコードで書式文字列攻撃が可能な場合、この脆弱性の悪用が容易になります
- コンパイラは最新版を使いましょう。万が一、脆弱性があっても/GS(バッファのセキュリティチェック)、/NXCOMPAT(DEP)、/SAFESEHや/DYNAMICBASE(ASLR)といった機能のおかげで攻撃か困難になります
- ライブラリを過信しすぎない。std::stringはバッファオーバーフローしないと思っていると、思わぬ抜け穴があるものです
思うところ
管理人様は、少なくとも2008年にはDrawFormatStringがバッファオーバーフローすること自体は認識されていたようです*3。当時はそれが任意のコード実行につながると認識されていなかったのだと思います。 私も実際の攻撃手法を知ったのはここ2,3年のことです。
こんなブログ記事がありました( C言語簡単にクラッシュしすぎワロタw · DQNEO起業日記)。この方を名指ししたいわけではありませんが、この記事は小さなバッファに対してgets()を使うと簡単にクラッシュすることを指摘しています。 この指摘自体は正しいですが、バッファオーバーフローを単にクラッシュするだけと捕えているか、任意コードの実行につながると捕えているかの意識の差だと思いました。
ソフトバンクからIIJmioにMNPした話
2010年にソフトバンクと契約して以来、一度も契約を変更してきませんでした。ふと料金を見直してみると、3年間の学割も終了し、月々6,000円近くとなっています。そして世間では格安MVNOが流行っています。
幸いキャリアメールは実験にしか使っていなかったため、早い段階でMVNOへの移行は決定。IIJmioやOCNや楽天モバイル、BIGLOBEなどを検討しました。楽天モバイルは1,728円で高速通信が2.1GBついてくる点(他社は同価格帯で2GB)が、BIGLOBEはMVNOながらWi-Fiが利用でき、1,000円キャッシュバックキャンペーンもやっている点が良いなと思っていました。最終的にデータスイッチがあり、それを利用すれば月2GB以内に通信量を抑えられるであろうこと、IPv6対応(LTE端末持ってないが)が決め手となりIIJmioに決定しました。
追記: とか書いていながら、BIC SIMならIIJmioの特徴に加えてWi-Fiも使えることにはこれを書いた後から気づきました。
すぐにMNP予約番号を取得してIIJmioサイトから申し込むこともできますが、そうすると初期費用3,240円が掛かってしまいます。そこで「IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043」をAmazonにて2,418円で購入。音声契約なのでSIMカードは同封されていませんが、エントリーコードを入力することで初期費用なしで申し込み可能です。
余談ですが、翌日にAmazonの同ページを見ると29% OFF、翌々日(この記事書いてる日)に見ると31% OFFで2,250円になっていました(涙)。Amazonから200円クーポンを貰ったのでまあ良いかと。
1月16日の21時45分にポチって、翌朝9時12分に配達完了。配達まで11時間27分とは、ドラえもんの注文してから瞬時に品物が届く世界は着実に近づいていると感じます。
MNP予約番号申し込み
音声通話パックが届いたので早速、固定電話からMNPお問い合わせ窓口(0800-100-5533)に電話。まず音声ガイドに従って、MNP予約番号の手続きを進めます。オペレータに繋がる前に携帯電話番号と暗証番号による本人確認が行われます。
以下、担当者との会話うろ覚え。いたって丁寧でした。
担当者: 今回はMNPの転出でよろしいでしょうか。
僕: はい。
担当者: 今回、【電話番号】1回線の転出でよろしいでしょうか。
僕: はい。
担当者: お電話いただいているのは【僕の本名】様、ご本人でお間違いないですね。
僕: はい。
担当者: 今回転出先はドコモ、au、どちらになりますか。
僕: MVNOです。
担当者: よろしければ事業者をお聞かせ願いますでしょうか。
僕: IIJmioです。
担当者: よろしければ転出理由をお聞かせ願いますでしょうか。
僕: 安いので。まあ、値段ですね。
担当者: ありがとうございます。今回転出される場合、契約解除料 10,260円・事務手数料 2,160円、またIIJmioさんの方でも初期費用 3,240円がかかり、結構なご負担となってしまいますが、ソフトバンクに残っていただけるなら機種変更時にご利用いただける3万ポイントまたは、機種変更後の端末代金から24か月月々1,000円の割引をお付けすることができるのですが、ご検討いただけないでしょうか。
僕: もう転出を決めてしまったので。
担当者: さようでございますか。今回、ソフトバンクのグループ会社のY!mobileに変更していただける場合、通常発生する契約解除料、事務手数料を調整させていただくことができますが、ご検討いただけないでしょうか。
僕: 考えていないです。
担当者: さようでございますか。失礼いたしました。誠に残念ではございますが、お手続きさせていただきます。予約番号は携帯電話にショートメッセージでお送りさせていただきます。担当は○○がお受けいたしました。失礼いたします。
契約解除料や事務手数料の話をされても4か月もあれば元が取れますし、ポイントの話もiPhone 6発売時のMNPポイント等の情報から予想はしていたので冷静に拒否。
申し込みから回線停止まで
MNP予約番号を入手したら、IIJmioのサイトから申し込みます。申し込み開始から本人確認書類画像送信完了までは、免許証のスキャン画像が既に手元にあったこともあり、20分程でした。それから本人確認完了のメールまで3時間程。土曜日にもかかわらず対応が早いです。
1月17日 22時1分、「[IIJmio] MNP 転入による電話番号停止予定のお知らせ」という停止予告メールが届きます。翌日9時28分時点ではまだ使えました。
10時40分に確認したところ、3Gマークが消え、同49分までに圏外となりました。回線停止予告から約12時間40分で停止。
同時にMy SoftBankを確認すると、ほとんどのメニュー項目がグレーアウトしています。
という訳で、この記事を書いている時点でSIMカードの到着待ちです。IIJによれば新しいSIMカードは、電話回線の停止後通常2~3日程度で届くとのことだそうです。
1月19日追記: 「[IIJmio] ready to start」という件名で19日 6:18に発送兼利用開始日連絡のメールが届きました。同日の12:38にSIMカードがヤマトで配達完了しました。発送自体は18日の18:45には行われていたようです。