DllPreloadingAttackChecker.zip/checker/main.cpp
#include <string.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <Windows.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/format.hpp>
#include <boost/optional.hpp>
const IMAGE_DOS_HEADER *getMz(const unsigned char * const data) {
return reinterpret_cast<const IMAGE_DOS_HEADER *>(data);
}
template<typename NT_HEADER>
const NT_HEADER *getPe(const unsigned char * const data, const IMAGE_DOS_HEADER &mz) {
return reinterpret_cast<const NT_HEADER *>(data + mz.e_lfanew);
}
template<typename NT_HEADER>
const IMAGE_SECTION_HEADER *getSectionHeader(const unsigned char * const data, const IMAGE_DOS_HEADER &mz, const NT_HEADER &pe) {
return reinterpret_cast<const IMAGE_SECTION_HEADER *>(
data
+ mz.e_lfanew
+ sizeof(NT_HEADER) - sizeof(NT_HEADER::OptionalHeader)
+ pe.FileHeader.SizeOfOptionalHeader);
}
template<typename NT_HEADER, WORD MAGIC>
bool LoadImage(const boost::filesystem::path &path, std::vector<unsigned char> &out) {
if (!boost::filesystem::exists(path)) {
return false;
}
boost::filesystem::ifstream ifs(path, std::ios::binary);
if (!ifs.is_open()) {
return false;
}
// MZヘッダーの読み込み
out.resize(sizeof(IMAGE_DOS_HEADER));
ifs.read(reinterpret_cast<char*>(&out.front()), out.size());
const IMAGE_DOS_HEADER *mz = getMz(&out.front());
if (!ifs.good()) {
return false;
}
// PEヘッダーの読み込み
out.resize(mz->e_lfanew + sizeof(NT_HEADER) - sizeof(NT_HEADER::OptionalHeader));
mz = getMz(&out.front());
ifs.seekg(0, std::ios::beg);
ifs.read(reinterpret_cast<char*>(&out.front()), out.size());
const NT_HEADER *pe = getPe<NT_HEADER>(&out.front(), *mz);
if (!ifs.good()) {
return false;
}
// PEオプショナルヘッダーからセクションリストまでの読み込み
out.resize(
mz->e_lfanew
+ sizeof(NT_HEADER) - sizeof(NT_HEADER::OptionalHeader)
+ pe->FileHeader.SizeOfOptionalHeader
+ sizeof(IMAGE_SECTION_HEADER) * pe->FileHeader.NumberOfSections);
mz = getMz(&out.front());
pe = getPe<NT_HEADER>(&out.front(), *mz);
ifs.seekg(0, std::ios::beg);
ifs.read(reinterpret_cast<char*>(&out.front()), out.size());
if (!ifs.good()) {
return false;
}
if (pe->OptionalHeader.Magic != MAGIC) {
return false;
}
// セクションの読み込み
const IMAGE_SECTION_HEADER *sectionList = getSectionHeader<NT_HEADER>(&out.front(), *mz, *pe);
for (unsigned int i = 0; i < pe->FileHeader.NumberOfSections; i++) {
if (sectionList[i].Misc.VirtualSize == 0) {
continue;
}
if (out.size() < sectionList[i].VirtualAddress + sectionList[i].Misc.VirtualSize) {
out.resize(sectionList[i].VirtualAddress + sectionList[i].Misc.VirtualSize);
mz = getMz(&out.front());
pe = getPe<NT_HEADER>(&out.front(), *mz);
sectionList = getSectionHeader<NT_HEADER>(&out.front(), *mz, *pe);
}
ifs.seekg(sectionList[i].PointerToRawData, std::ios::beg);
ifs.read(reinterpret_cast<char*>(&out[sectionList[i].VirtualAddress]), (std::min)(sectionList[i].Misc.VirtualSize, sectionList[i].SizeOfRawData));
if (!ifs.good()) {
return false;
}
}
return true;
}
template<typename NT_HEADER, WORD MAGIC>
boost::optional<std::vector<std::wstring> > GetDllList(const boost::filesystem::path &path) {
if (!boost::filesystem::exists(path)) {
return {};
}
std::vector<unsigned char> image;
if (!LoadImage<NT_HEADER, MAGIC>(path, image)) {
return {};
}
const unsigned char * const baseAddr = &image.front();
const IMAGE_DOS_HEADER &mz = *getMz(baseAddr);
const NT_HEADER &pe = *getPe<NT_HEADER>(baseAddr, mz);
const IMAGE_DATA_DIRECTORY &dir = pe.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (dir.Size == 0) {
return {};
}
std::vector<std::wstring> dllList;
const IMAGE_IMPORT_DESCRIPTOR *desc = reinterpret_cast<const IMAGE_IMPORT_DESCRIPTOR *>(baseAddr + dir.VirtualAddress);
for (; desc->FirstThunk != NULL; desc++) {
const char * const name = reinterpret_cast<const char *>(baseAddr + desc->Name);
wchar_t temp[256] = { 0 };
size_t converted;
::mbstowcs_s(&converted, temp, name, ::strlen(name));
dllList.push_back(temp);
}
return dllList;
}
boost::optional<std::vector<std::wstring> > GetDllList(const boost::filesystem::path &path, bool * const is32=nullptr) {
const auto result = GetDllList<IMAGE_NT_HEADERS32, IMAGE_NT_OPTIONAL_HDR32_MAGIC>(path);
if (result) {
if (is32) {
*is32 = true;
}
return result;
}
if (is32) {
*is32 = false;
}
return GetDllList<IMAGE_NT_HEADERS64, IMAGE_NT_OPTIONAL_HDR64_MAGIC>(path);
}
struct MY_IMAGE_LOAD_CONFIG_DIRECTORY32 {
DWORD Size;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
DWORD DeCommitFreeBlockThreshold;
DWORD DeCommitTotalFreeThreshold;
DWORD LockPrefixTable; // VA
DWORD MaximumAllocationSize;
DWORD VirtualMemoryThreshold;
DWORD ProcessHeapFlags;
DWORD ProcessAffinityMask;
WORD CSDVersion;
WORD DependentLoadFlags;
DWORD EditList; // VA
DWORD SecurityCookie; // VA
DWORD SEHandlerTable; // VA
DWORD SEHandlerCount;
DWORD GuardCFCheckFunctionPointer; // VA
DWORD GuardCFDispatchFunctionPointer; // VA
DWORD GuardCFFunctionTable; // VA
DWORD GuardCFFunctionCount;
DWORD GuardFlags;
IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
DWORD GuardAddressTakenIatEntryTable; // VA
DWORD GuardAddressTakenIatEntryCount;
DWORD GuardLongJumpTargetTable; // VA
DWORD GuardLongJumpTargetCount;
DWORD DynamicValueRelocTable; // VA
DWORD CHPEMetadataPointer;
DWORD GuardRFFailureRoutine; // VA
DWORD GuardRFFailureRoutineFunctionPointer; // VA
DWORD DynamicValueRelocTableOffset;
WORD DynamicValueRelocTableSection;
WORD Reserved2;
DWORD GuardRFVerifyStackPointerFunctionPointer; // VA
DWORD HotPatchTableOffset;
DWORD Reserved3;
DWORD EnclaveConfigurationPointer; // VA
DWORD VolatileMetadataPointer; // VA
};
struct MY_IMAGE_LOAD_CONFIG_DIRECTORY64 {
DWORD Size;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
ULONGLONG DeCommitFreeBlockThreshold;
ULONGLONG DeCommitTotalFreeThreshold;
ULONGLONG LockPrefixTable; // VA
ULONGLONG MaximumAllocationSize;
ULONGLONG VirtualMemoryThreshold;
ULONGLONG ProcessAffinityMask;
DWORD ProcessHeapFlags;
WORD CSDVersion;
WORD DependentLoadFlags;
ULONGLONG EditList; // VA
ULONGLONG SecurityCookie; // VA
ULONGLONG SEHandlerTable; // VA
ULONGLONG SEHandlerCount;
ULONGLONG GuardCFCheckFunctionPointer; // VA
ULONGLONG GuardCFDispatchFunctionPointer; // VA
ULONGLONG GuardCFFunctionTable; // VA
ULONGLONG GuardCFFunctionCount;
DWORD GuardFlags;
IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
ULONGLONG GuardAddressTakenIatEntryTable; // VA
ULONGLONG GuardAddressTakenIatEntryCount;
ULONGLONG GuardLongJumpTargetTable; // VA
ULONGLONG GuardLongJumpTargetCount;
ULONGLONG DynamicValueRelocTable; // VA
ULONGLONG CHPEMetadataPointer; // VA
ULONGLONG GuardRFFailureRoutine; // VA
ULONGLONG GuardRFFailureRoutineFunctionPointer; // VA
DWORD DynamicValueRelocTableOffset;
WORD DynamicValueRelocTableSection;
WORD Reserved2;
ULONGLONG GuardRFVerifyStackPointerFunctionPointer; // VA
DWORD HotPatchTableOffset;
DWORD Reserved3;
ULONGLONG EnclaveConfigurationPointer; // VA
ULONGLONG VolatileMetadataPointer; // VA
};
template<typename NT_HEADER, WORD MAGIC, typename LOAD_CONFIG_DIRECTORY>
boost::optional<bool> IsSearchSystemOnly(const boost::filesystem::path &path) {
if (!boost::filesystem::exists(path)) {
return {};
}
std::vector<unsigned char> image;
if (!LoadImage<NT_HEADER, MAGIC>(path, image)) {
return {};
}
const unsigned char * const baseAddr = &image.front();
const IMAGE_DOS_HEADER &mz = *getMz(baseAddr);
const NT_HEADER &pe = *getPe<NT_HEADER>(baseAddr, mz);
const IMAGE_DATA_DIRECTORY &dir = pe.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
if (dir.Size == 0) {
return false;
}
const LOAD_CONFIG_DIRECTORY * const config = reinterpret_cast<const LOAD_CONFIG_DIRECTORY *>(baseAddr + dir.VirtualAddress);
const unsigned int flags = config->DependentLoadFlags;
return flags != 0
&& (flags& (LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)) == 0
&& (flags&LOAD_LIBRARY_SEARCH_SYSTEM32) != 0;
}
boost::optional<bool> IsSearchSystemOnly(const boost::filesystem::path &path, bool * const is32 = nullptr) {
const auto result = IsSearchSystemOnly<IMAGE_NT_HEADERS32, IMAGE_NT_OPTIONAL_HDR32_MAGIC, MY_IMAGE_LOAD_CONFIG_DIRECTORY32>(path);
if (result) {
if (is32) {
*is32 = true;
}
return result;
}
if (is32) {
*is32 = false;
}
return IsSearchSystemOnly<IMAGE_NT_HEADERS64, IMAGE_NT_OPTIONAL_HDR64_MAGIC, MY_IMAGE_LOAD_CONFIG_DIRECTORY64>(path);
}
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _OBJECT_DIRECTORY_INFORMATION {
UNICODE_STRING Name;
UNICODE_STRING TypeName;
} OBJECT_DIRECTORY_INFORMATION, *POBJECT_DIRECTORY_INFORMATION;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;
PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef NTSTATUS(WINAPI *NtQueryDirectoryObject_T)(
_In_ HANDLE DirectoryHandle,
_Out_opt_ PVOID Buffer,
_In_ ULONG Length,
_In_ BOOLEAN ReturnSingleEntry,
_In_ BOOLEAN RestartScan,
_Inout_ PULONG Context,
_Out_opt_ PULONG ReturnLength
);
typedef NTSTATUS(WINAPI *NtOpenDirectoryObject_T)(
_Out_ PHANDLE DirectoryHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes
);
#define DIRECTORY_QUERY 0x0001
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define InitializeObjectAttributes(p, n, a, r, s) { \
(p)->Length = sizeof(OBJECT_ATTRIBUTES); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
typedef std::shared_ptr<std::remove_pointer<HANDLE>::type> SharedHandle;
boost::optional<std::vector<std::wstring> > GetKnownDLLs(const wchar_t * const knownDLLsName) {
const auto NtOpenDirectoryObject = reinterpret_cast<NtOpenDirectoryObject_T>(::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtOpenDirectoryObject"));
const auto NtQueryDirectoryObject = reinterpret_cast<NtQueryDirectoryObject_T>(::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtQueryDirectoryObject"));
UNICODE_STRING name;
wchar_t nameBuf[MAX_PATH];
::wcscpy_s(nameBuf, knownDLLsName);
name.Length = static_cast<unsigned short>(::wcslen(nameBuf) * sizeof(nameBuf[0]));
name.MaximumLength = name.Length;
name.Buffer = nameBuf;
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &name, 0, nullptr, nullptr);
HANDLE dirHandle;
if (!NT_SUCCESS(NtOpenDirectoryObject(&dirHandle, DIRECTORY_QUERY, &oa))) {
return {};
}
SharedHandle knownDllsDir(dirHandle, &::CloseHandle);
OBJECT_DIRECTORY_INFORMATION odi[1024];
ULONG context = 0;
ULONG returnLength;
if (!NT_SUCCESS(NtQueryDirectoryObject(knownDllsDir.get(), &odi, sizeof(odi), false, true, &context, &returnLength))
|| odi[0].Name.Buffer == nullptr)
{
return {};
}
std::vector<std::wstring> list;
const OBJECT_DIRECTORY_INFORMATION *cur = odi;
while (true) {
if (cur->Name.Buffer == nullptr) {
break;
}
list.push_back(cur->Name.Buffer);
cur++;
}
return list;
}
boost::optional<std::vector<std::wstring> > GetKnownDLLs(const bool is32) {
if (!is32) {
return GetKnownDLLs(L"\\KnownDlls");
}
const auto result = GetKnownDLLs(L"\\KnownDlls32");
if (result) {
return result;
}
return GetKnownDLLs(L"\\KnownDlls");
}
std::wstring &toLower(std::wstring &item) {
std::transform(item.begin(), item.end(), item.begin(), [](const wchar_t c) { return ::tolower(c); });
return item;
}
std::vector<std::wstring> &toLower(std::vector<std::wstring> &list) {
for (auto &item : list) {
toLower(item);
}
return list;
}
int main(const unsigned int argc, const char * const * argv) {
std::locale loc = std::locale("japanese").combine<std::numpunct<char> >(std::locale::classic()).combine<std::numpunct<wchar_t> >(std::locale::classic());
std::locale::global(loc);
std::wcout.imbue(loc);
std::cout.imbue(loc);
if (argc != 2) {
std::wcout << boost::wformat(L"usage: %s path") % boost::filesystem::path(argv[0]).filename().wstring();
return 1;
}
const auto srcPath = boost::filesystem::path(argv[1]);
bool is32;
boost::optional<std::vector<std::wstring> > result = GetDllList(srcPath, &is32);
if (!result) {
std::cout << "ファイルを開けないか、実行可能形式ファイルではありません\n";
std::cin.get();
return 1;
}
const auto &dllList = toLower(result.get());
boost::optional<std::vector<std::wstring> > result2 = GetKnownDLLs(is32);
if (!result2) {
std::cout << "KnownDLLsの取得に失敗しました\n";
std::cin.get();
return 1;
}
const auto &knownDLLs = toLower(result2.get());
typedef NTSTATUS(WINAPI *RtlGetVersion_T)(RTL_OSVERSIONINFOW *lpVersionInformation);
const RtlGetVersion_T RtlGetVersion = reinterpret_cast<RtlGetVersion_T>(::GetProcAddress(::GetModuleHandle("ntdll.dll"), "RtlGetVersion"));
RTL_OSVERSIONINFOW version;
if (!NT_SUCCESS(RtlGetVersion(&version))) {
std::cout << "Windowsのバージョン判定に失敗しました\n";
std::cin.get();
return 1;
}
const bool isWin10Rs1 = (
version.dwMajorVersion > 10 ||
version.dwMajorVersion == 10 && (
version.dwMinorVersion > 0 ||
version.dwMinorVersion == 0 && (
version.dwBuildNumber >= 14393
)
)
);
const std::wstring apiSetPrefixList[] = { L"api-ms-win-", L"ext-ms-win-" };
std::wstring filename = srcPath.filename().wstring();
toLower(filename);
const bool isSetup = filename == L"setup.exe";
const bool isSearchSystemOnly = isWin10Rs1 && *IsSearchSystemOnly(srcPath);
bool insecure = false;
for (const auto &name : dllList) {
const bool isKnownDLLs = std::find(knownDLLs.begin(), knownDLLs.end(), name) != knownDLLs.end();
bool isApiSet = false;
for (const auto &item : apiSetPrefixList) {
if (name.substr(0, item.length()) == item) {
isApiSet = true;
break;
}
}
bool isTwain = name == L"twain_32.dll";
if (isKnownDLLs || isApiSet || isTwain || isSetup || isSearchSystemOnly) {
std::wcout << boost::wformat(L"OK %s\n") % name;
} else {
std::wcout << boost::wformat(L"!!NG!! %s\n") % name;
insecure = true;
}
}
if (insecure) {
std::wcout << boost::wformat(L"DLLプリロード攻撃が可能です。\n");
} else {
std::wcout << boost::wformat(L"DLLプリロード攻撃には堅牢です。\n");
}
std::cin.get();
return 0;
}