非公式東方鬼形獣バグ修正パッチリリース

非公式ですが東方鬼形獣のバグを直して回るパッチ作ったので公開します。

th17patch.zip

直るバグは以下の通り

  • オオワシ妖夢の攻撃力上限が他より100低い
  • 実績から再生できるEDが違う
  • カワウソ妖夢1面の誤字
  • 実績38の誤字
  • replayフォルダ以下にもなぜかsnapshotフォルダがある
  • リプレイでクリア系実績が解放される

エクスプロイトを書きつつ学ぶWindowsセキュリティー機能 ~CFG export suppression~

今回はControl Flow Guard(解説記事)の派生技術をご紹介。

忙しい人向けCFG export suppressionまとめ:

  • dllのexport関数のアドレスを正規の手段以外で得てcallするとクラッシュする機能
  • 正規の手段は以下の通り
    • 静的リンク(Import Address Tableなど)経由
    • GetProcAddress関数
    • dllにexport以外で関数ポインタなどで受け渡す機能が備わっていた
  • 基本的な機構はControl Flow Guardと一緒
    • 具体的にはコンパイラが動的なcallやjmpの直前にチェック関数を挿入している
  • 有効化するにはexeとdllのすべてで/guard:cfを指定し、exeのリンカに /guard:exportsuppress を指定する

前置き

Windowsの32bitアプリケーションはアドレスランダマイズが不足しており危険だという話はPCセキュリティ界隈では比較的よく聞く話です。

その中でも特に脆弱なのがdllのアドレスランダマイズで、過去の記事でも触れたとおり比較的軽微なメモリ漏洩でも関数のアドレスが推測可能になるのが現状です。

そういった現状を踏まえ任意コード実行の脆弱性を少しでも難しくするために生み出された技術、それが今回紹介するCFG export suppression(以降CFG ES)です。

以降はサンプルコード前提で進みます。

cfg_es.zip

注意事項

本機能を有効にする/guard:exportsuppressは2019/09/17現在、MSDN上では非公開なオプションであり正式に提供されている機能ではない可能性があります。

今後正式に提供されるとしても挙動など変更になっている可能性を念頭においてご利用ください。

攻撃:情報漏洩と任意コード実行

話をシンプルにするためにexport関数のアドレスを漏洩する関数を持つdllを用意します。

// ただのexport関数
extern "C" __declspec(dllexport) void proc() {
   std::cout << "Arbitrary code execution\n";
}

// procのアドレスを漏洩する脆弱な関数
extern "C" __declspec(dllexport) __declspec(guard(ignore)) unsigned int leak() {
   return reinterpret_cast<unsigned int>(proc);
}

leak関数はprocの関数を漏洩しています。

関数ポインタの取得ではない旨を示すために__declspec(guard(ignore))するのを忘れずに。
※関数ポインタの取得とみなされるとCFG ESの対象外となるため

攻撃対象のproc関数はただ標準出力にメッセージを出すだけの関数です。

攻撃側は既に脆弱性を突かれている前提でleak関数の返り値を実行するのを直に実装します。

int main(const int argc, const char * const * const argv) {
   const HMODULE dll = ::LoadLibraryA("dll.dll");
   unsigned int addr = reinterpret_cast<unsigned int (*)()>(::GetProcAddress(dll, "leak"))();
   reinterpret_cast<void (*)()>(addr)();
   std::cin.get();
   return 0;
}

実際に上記のソースコードと実行ファイルがフォルダ1に入れてあるので実行してみましょう、特に問題もなくproc関数が実行できてしまいます。

ではCFG ESで対抗しましょう。

防御:CFG ES

/guard:cf が有効でないなら有効にします。
※本アプリでは最初から有効になっています。

次にexe側のリンカオプションで /guard:exportsuppressを設定するだけです。

今回も設定済みの物をフォルダ2に入れてあるので実行すると、今回はクラッシュしました。

「proc関数はGetProcAddressされていないのにアドレスを知っているのはおかしい、攻撃だな!」

との判断により強制終了された結果です。

このチェックによりエラーとされない方法は以下の通りです。

  • GetProcAddressによりアドレスを取得する
  • 静的リンクして関数を呼び出す
  • dll内部で関数ポインタとして取得されたものを受け取る
  • /guard:cfを解除する(セキュリティが低下します)

一般的な使い方をしていれば回避する方が難しいのがよくわかるかと思います。

上記以外で不正に取得したアドレスでcallやjmpすることが難しくなるというわけですね。

このチェックは/guard:cfの機能により行われるために/guard:cfも有効でないといけません。

まとめ

  • CFG ESは/guard:cfと/guard:exportsuppressをつければ有効になる
  • 有効になると不正に入手したexport関数アドレスは実行できない

実用性

GetProcAddressを呼び出してしまえば許可されてしまうため、100%の防御というよりは攻撃者の手札を減らす類のもの、場合により気休め程度の効果にしかならないことも。

リンクするdllすべてでControl Flow Guardが有効でなければ効果が薄いのと、一定より古いOSではそもそも無視される問題があり頼りづらい。

Buffer Overflowなど任意コード実行を目指す攻撃は動的アドレスへのcallやjmpの利用を目指すことがあるため、そういった攻撃を防げるのは水際防御としては有効そう。

とはいえreturnアドレスなど他にも利用できるものは多いため、やはり単独で使用できるものではなくSafeSEHBuffer Security Checkなどの技術との併用は必須。

構造上Control Flow Guardとの併用が必須であり、Control Flow GuardはJITエンジンなど食い合わせが悪い機能が多く採用が難しいという問題がある、採用する際は十分テストを行い問題ないことを確認したい。

メモリや計算量にオーバーヘッドが存在するため速度がダイレクトに響く分野での採用も難しい。

総じてメリットは小さくデメリットは大きい傾向がある技術、採用はほかの技術を優先したい。

終わりに

Control Flow Guardの派生技術ということで取り上げましたがいかがだったでしょうか。

CFG ESのかかったバイナリは手元ではEdgeしか発見できていないレア物だったりもします。

無条件で使えるものではないのは確かですが、ここまで誰も使わないほど有用性が低い技術でもないと思うので、少しでも採用アプリが増えてくれたらうれしいんですがどうでしょうね?

ではまた。

エクスプロイトを書きつつ学ぶWindowsセキュリティー機能 ~Arbitrary Code Guard~

今回は被害を防ぐのではなく被害を抑える系技術のご紹介。

忙しい人向けArbitrary Code Guardまとめ

  • 自身からメモリアクセス権限を変更する権限を取り上げる技術
  • 具体的にはEXECUTEの新規付与禁止と既存EXECUTEメモリにWRITE付与禁止
  • 自身がマルウェアに乗っ取られた場合に他プロセスへの攻撃を難しくする
    • 具体的には他プロセスへの悪意あるコードの注入
  • 一度設定したら解除不能
  • 不特定多数の相手と通信するようなマルウェア被害が強く懸念されるアプリで被害最小化を図る場合向けの技術

前置き

攻撃を防ぐ技術は数えきれないほど存在しますが、それでも100%完全に攻撃を防ぐことは現代社会においては非現実的とされています。

そのため攻撃を防ぐことだけ考え、攻撃成功した後のことを考えなかった場合、あっという間に全てを乗っ取られ取り返しのつかない事態になってしまいます。

そこで攻撃が成功しても被害の拡大を防ぐ技術についても日々研究されています。

今回紹介するのもそういった方向の技術の1つで、Arbitrary Code Guard(以降ACG)です。

以降はサンプルコード前提で進みます。

ArbitraryCodeGuard.zip

例その1:メモリ書き換えによるコード注入

フォルダ1内のTarget.exeを攻撃します。

Target.exeの実装は以下の通り。

#include <iostream>

int main() {
   std::cin.get();
}

ただ入力待ちするだけのいたって善良な子です。

こいつにdllを注入します。

今回は注入に成功したことが分かればいいので特に悪事を働かせたりはしません。

#include <iostream>

#include <Windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
       std::cout << "Injection!" << std::endl;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

ただロード時に「Injection!」と出力するだけです。

次に攻撃者ですが、既に侵入に成功している想定で普通に攻撃を実装します。

   unsigned char code[] = {
      0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &"dll.dll")
      0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &::LoadLibraryA)
      0x58, // POP EAX
      0xFF, 0xD0, // JMP EAX
      0xC3, // RETN
   };

長くなる上ACGとは関係がないので端折りますが、要するに上記のコードを相手プロセス内に配置して実行するコードです。

内容的には::LoadLibraryA(“dll.dll”);に相当します。

ではTarget.exe>Attacker.exeの順に起動してみましょう。

上記のようにTarget.exe側にInjection!と出力されたはずです。

※普通に攻撃処理なのでアンチウイルスなどに止められる可能性があります。

次はこの攻撃をACGで失敗させます。

例その2:ACG実演

ACGとはメモリアクセス権(読み書き実行)の変更権限を制限する機能です。

攻撃者に侵入される前のアプリでACGを有効にしていたと仮定します。

void enableACG() {
   PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy;
   policy.Flags = 0;
   policy.ProhibitDynamicCode = 0x01;
   policy.ReservedFlags = 0;
   ::SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy, sizeof(policy));
}

上記でACGが有効になるのでmainの頭で呼んでおきます。

上記の変更が入ったものがフォルダ2に入っているので実行します。

失敗しました。

これは実行権限付きメモリー確保に失敗したためです。

実行権限が付与できなければいくらコードを書き込めても実行させることが出来ず任意コード実行まで到達できません。

このようにACGを有効にしておくことで、マルウェアに制御を奪われても被害拡大を防ぐことが出来ます。

例その3:拡大阻止ではなく防御には使えないのか

結論から言うと防御には使えません、以下で実演します。

Target.exe内でACGを有効にします。

入れた物がフォルダ3に用意してあるので実行します。

攻撃成功しました。

このように、あくまで自身が他者を攻撃しないようにするだけで、他者からの攻撃を防ぐ用途では使えません。

例その4:攻撃者にACGは外されないのか

結論から言うと外せません。

void disableACG() {
   PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy;
   policy.Flags = 0;
   policy.ProhibitDynamicCode = 0x00;
   policy.ReservedFlags = 0;
   ::SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy, sizeof(policy));
}

フォルダ4に上記修正を入れました。

しかし攻撃は失敗します。

普通の方法では後から外すことはできません。

まとめ

  • ACGは自身のメモリアクセス権変更権限を取り上げる技術
  • 有効にすると他プロセスに任意コードを書き込んで実行させるのは難しい
  • 自身に制約をかけ被害拡大阻止するだけで防御には使えない
  • 一度設定したら解除不能

その他詳細

  • 厳密には以下の権限が失われる
    • EXECUTE属性の新規発行
    • EXECUTE属性持ちにWRITE属性付与
    • EXECUTE_READWRITEを指定すること自体
      • EXECUTE_READWRITE =>EXECUTE_READWRITE(権限変更なし)もなぜか問答無用で失敗するように
  • 自分自身への付与も該当
    • ただしLoadLibraryでのDLL割り当てなどカーネル(他者)を経由する場合は問題ない

実用性

一般的なプログラマー視点であれば実用性はほぼないとみてよい。

ACGを導入してもファイルシステム経由でのdllインジェクションなどがありうるため、基本的に単独では意味が薄いのも痛い。

ユーザーがとても多く、防御策も軒並み全部採用している状態でなおセキュリティを高める場合にようやく選択肢に上る程度。

上記に該当するのは例えばブラウザなど。

とはいえそれほどデメリットもきつくないので、食い合わせの悪い技術(JITコンパイラなど)を使わないならば採用してもよい。

終わりに

今回は啓蒙というよりは紹介で、特に採用必要性のない技術でした。

とはいえブラウザ保護などには役立っているので、世の中はこういうものまで含めた積み重ねで安全が担保されているのだなと思っていただければ。

本記事でACGとは何かがちょっとでも伝わっていれば幸い。

ではまた。