非公式ですが東方鬼形獣のバグを直して回るパッチ作ったので公開します。
直るバグは以下の通り
- オオワシ妖夢の攻撃力上限が他より100低い
- 実績から再生できるEDが違う
- カワウソ妖夢1面の誤字
- 実績38の誤字
- replayフォルダ以下にもなぜかsnapshotフォルダがある
- リプレイでクリア系実績が解放される
非公式ですが東方鬼形獣のバグを直して回るパッチ作ったので公開します。
直るバグは以下の通り
今回はControl Flow Guard(解説記事)の派生技術をご紹介。
Windowsの32bitアプリケーションはアドレスランダマイズが不足しており危険だという話はPCセキュリティ界隈では比較的よく聞く話です。
その中でも特に脆弱なのがdllのアドレスランダマイズで、過去の記事でも触れたとおり比較的軽微なメモリ漏洩でも関数のアドレスが推測可能になるのが現状です。
そういった現状を踏まえ任意コード実行の脆弱性を少しでも難しくするために生み出された技術、それが今回紹介するCFG export suppression(以降CFG ES)です。
以降はサンプルコード前提で進みます。
本機能を有効にする/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で対抗しましょう。
/guard:cf が有効でないなら有効にします。
※本アプリでは最初から有効になっています。
次にexe側のリンカオプションで /guard:exportsuppressを設定するだけです。
今回も設定済みの物をフォルダ2に入れてあるので実行すると、今回はクラッシュしました。
「proc関数はGetProcAddressされていないのにアドレスを知っているのはおかしい、攻撃だな!」
との判断により強制終了された結果です。
このチェックによりエラーとされない方法は以下の通りです。
一般的な使い方をしていれば回避する方が難しいのがよくわかるかと思います。
上記以外で不正に取得したアドレスでcallやjmpすることが難しくなるというわけですね。
このチェックは/guard:cfの機能により行われるために/guard:cfも有効でないといけません。
GetProcAddressを呼び出してしまえば許可されてしまうため、100%の防御というよりは攻撃者の手札を減らす類のもの、場合により気休め程度の効果にしかならないことも。
リンクするdllすべてでControl Flow Guardが有効でなければ効果が薄いのと、一定より古いOSではそもそも無視される問題があり頼りづらい。
Buffer Overflowなど任意コード実行を目指す攻撃は動的アドレスへのcallやjmpの利用を目指すことがあるため、そういった攻撃を防げるのは水際防御としては有効そう。
とはいえreturnアドレスなど他にも利用できるものは多いため、やはり単独で使用できるものではなくSafeSEHやBuffer Security Checkなどの技術との併用は必須。
構造上Control Flow Guardとの併用が必須であり、Control Flow GuardはJITエンジンなど食い合わせが悪い機能が多く採用が難しいという問題がある、採用する際は十分テストを行い問題ないことを確認したい。
メモリや計算量にオーバーヘッドが存在するため速度がダイレクトに響く分野での採用も難しい。
総じてメリットは小さくデメリットは大きい傾向がある技術、採用はほかの技術を優先したい。
Control Flow Guardの派生技術ということで取り上げましたがいかがだったでしょうか。
CFG ESのかかったバイナリは手元ではEdgeしか発見できていないレア物だったりもします。
無条件で使えるものではないのは確かですが、ここまで誰も使わないほど有用性が低い技術でもないと思うので、少しでも採用アプリが増えてくれたらうれしいんですがどうでしょうね?
ではまた。
今回は被害を防ぐのではなく被害を抑える系技術のご紹介。
攻撃を防ぐ技術は数えきれないほど存在しますが、それでも100%完全に攻撃を防ぐことは現代社会においては非現実的とされています。
そのため攻撃を防ぐことだけ考え、攻撃成功した後のことを考えなかった場合、あっという間に全てを乗っ取られ取り返しのつかない事態になってしまいます。
そこで攻撃が成功しても被害の拡大を防ぐ技術についても日々研究されています。
今回紹介するのもそういった方向の技術の1つで、Arbitrary Code Guard(以降ACG)です。
以降はサンプルコード前提で進みます。
フォルダ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で失敗させます。
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を有効にしておくことで、マルウェアに制御を奪われても被害拡大を防ぐことが出来ます。
結論から言うと防御には使えません、以下で実演します。
Target.exe内でACGを有効にします。
入れた物がフォルダ3に用意してあるので実行します。
攻撃成功しました。
このように、あくまで自身が他者を攻撃しないようにするだけで、他者からの攻撃を防ぐ用途では使えません。
結論から言うと外せません。
void disableACG() {
PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy;
policy.Flags = 0;
policy.ProhibitDynamicCode = 0x00;
policy.ReservedFlags = 0;
::SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy, sizeof(policy));
}
フォルダ4に上記修正を入れました。
しかし攻撃は失敗します。
普通の方法では後から外すことはできません。
一般的なプログラマー視点であれば実用性はほぼないとみてよい。
ACGを導入してもファイルシステム経由でのdllインジェクションなどがありうるため、基本的に単独では意味が薄いのも痛い。
ユーザーがとても多く、防御策も軒並み全部採用している状態でなおセキュリティを高める場合にようやく選択肢に上る程度。
上記に該当するのは例えばブラウザなど。
とはいえそれほどデメリットもきつくないので、食い合わせの悪い技術(JITコンパイラなど)を使わないならば採用してもよい。
今回は啓蒙というよりは紹介で、特に採用必要性のない技術でした。
とはいえブラウザ保護などには役立っているので、世の中はこういうものまで含めた積み重ねで安全が担保されているのだなと思っていただければ。
本記事でACGとは何かがちょっとでも伝わっていれば幸い。
ではまた。