他人の空似自作物置場

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;
}