エクスプロイトを書きつつ学ぶ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とは何かがちょっとでも伝わっていれば幸い。

ではまた。