CreateProcess時、PROC_THREAD_ATTRIBUTE_MITIGATION_POLICYでプロセスに対する一部の挙動を変更することが可能です。その中にWindows 10のいつからか、DLLの検索パスについての定数が増えています。前々から気になっていたので、試してみました。

その定数は、UpdateProcThreadAttributeに記載のある以下です。

  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_DEFER
  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON
  • PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_OFF

まず、題材となるプログラムを作ります。これをコンパイルして、app.exeを作ります。iphlpapi.dllを何も考えずにLoadLibrayしているのは、もちろんわざとです。効果を確認するため、セキュリティ上問題のある書き方にしています。

// cl /MT /EHsc app.cpp /link /subsystem:console,5.01
 
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <windows.h>
#include <iphlpapi.h>
 
int main()
{
  auto hmod = LoadLibrary(L"iphlpapi.dll");
  if (auto pfn = reinterpret_cast<decltype(GetTcpTable2)*>(
    GetProcAddress(hmod, "GetTcpTable2")))
  {
    ULONG size = 0;
    pfn(nullptr, &size, 0);
    std::cout << size << std::endl;
  }
}

では、偽のiphlpapi.dllを作りましょう。次のソースコードをコンパイルします。

// cl iphlpapi.cpp user32.lib /LD
 
#define UNICODE
#define _UNICODE
#include <windows.h>
 
#pragma comment(linker, "/export:GetTcpTable2=_GetTcpTable2@12")
 
extern "C" __declspec(dllexport)
ULONG WINAPI GetTcpTable2(void*, ULONG*, BOOL)
{
  MessageBox(nullptr, L"やっほー", L"evil bit=1", MB_OK);
  return ERROR_INVALID_PARAMETER;
}

さっきのapp.exeと同じフォルダに、偽iphlpapi.dllを置き、app.exeを実行します。すると、メッセージボックスが表示されます。

さて、例のフラグ…_ALWAYS_ONを試します。CreateProcessを呼び出すlauncher.exeを作ります。

// cl /MT launcher.cpp
 
#define UNICODE
#define _UNICODE
 
#include <iostream>
#include <memory>
#include <cstdint>
#include <cstdlib>
#include <windows.h>
 
int main()
{
  WCHAR cmdLine[] = L"app.exe";
 
  SIZE_T bufSize = 0;
  if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &bufSize)
    && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  {
    std::quick_exit(1);
  }
  auto buf = std::make_unique<BYTE[]>(bufSize);
  auto attrList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.get());
  if (!InitializeProcThreadAttributeList(attrList, 1, 0, &bufSize))
  {
    std::quick_exit(1);
  }
  std::uint64_t value = PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON;
  if (!UpdateProcThreadAttribute(
    attrList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
    &value, sizeof value, nullptr, nullptr))
  {
    std::quick_exit(1);
  }
  STARTUPINFOEX si{};
  si.StartupInfo.cb = sizeof si;
  si.lpAttributeList = attrList;
  PROCESS_INFORMATION pi{};
  if (CreateProcess(nullptr, cmdLine, nullptr, nullptr, FALSE,
    EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &pi))
  {
    WaitForSingleObject(pi.hProcess, INFINITE);
  }
  // DeleteProcThreadAttributeListなどは省略
}

これをコンパイルして実行すると、見事メッセージボックスは表示されませんでした。

なお、定数…_DEFERを指定して、app.exe内でPreferSystem32Images = 1を指定してSetProcessMitigationPolicyを呼び出せば同様にsystem32優先になると予想していたのですが、これはうまくいきませんでした。偽iphlpapi.dllが読み込まれる結果となりました。やり方にミスがあったのでしょうか。

この方法、ご覧のとおりCreateProcessを呼び出す側での対処です。アプリ側に手を入れずに効果を発揮させることが可能で、しかもアプリ(app.exe)側のプロセス内で無効化できないはずという点で強力です。逆に、JVNTA#91240916: Windows アプリケーションによる DLL 読み込みやコマンド実行に関する問題UNLHA32.DLLで作成された自己解凍書庫における任意のDLL読み込みに関する脆弱性)への対処に使うのは大変手間がかかるでしょう。

というわけで、DLL検索パスの指定方法がまた1つ増えました。


スポンサード リンク

この記事のカテゴリ

  • ⇒ System32優先でDLLを探す指定を試してみた
  • ⇒ System32優先でDLLを探す指定を試してみた