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;
}
} |
// 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;
} |
// 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などは省略
} |
// 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つ増えました。
この記事のカテゴリ