From c677b8dd0abdded14c039c16913132197d591de7 Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Sun, 10 Apr 2016 16:06:56 -0500 Subject: [PATCH] Dump Windows version and arch information (including ConEmu hook version) --- src/agent/Agent.cc | 2 + src/agent/subdir.mk | 1 + src/debugserver/subdir.mk | 1 + src/libwinpty/winpty.cc | 3 + src/shared/OsModule.h | 18 +++- src/shared/WindowsVersion.cc | 185 ++++++++++++++++++++++++++++++++++- src/shared/WindowsVersion.h | 1 + src/winpty.gyp | 2 + 8 files changed, 207 insertions(+), 6 deletions(-) diff --git a/src/agent/Agent.cc b/src/agent/Agent.cc index be7ac46..9963fe9 100644 --- a/src/agent/Agent.cc +++ b/src/agent/Agent.cc @@ -30,6 +30,7 @@ #include "../shared/winpty_snprintf.h" #include "../shared/WinptyAssert.h" #include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" #include #include #include @@ -126,6 +127,7 @@ Agent::Agent(LPCWSTR controlPipeName, m_ptySize(initialCols, initialRows) { trace("Agent starting..."); + dumpWindowsVersion(); m_bufferData.resize(BUFFER_LINE_COUNT); diff --git a/src/agent/subdir.mk b/src/agent/subdir.mk index a7cc917..50fcf98 100644 --- a/src/agent/subdir.mk +++ b/src/agent/subdir.mk @@ -41,6 +41,7 @@ AGENT_OBJECTS = \ build/agent/shared/StringUtil.o \ build/agent/shared/WindowsVersion.o \ build/agent/shared/WinptyAssert.o \ + build/agent/shared/WinptyException.o \ build/agent/shared/WinptyVersion.o build/winpty-agent.exe : $(AGENT_OBJECTS) diff --git a/src/debugserver/subdir.mk b/src/debugserver/subdir.mk index bf8ccbd..7f30c81 100644 --- a/src/debugserver/subdir.mk +++ b/src/debugserver/subdir.mk @@ -26,6 +26,7 @@ DEBUGSERVER_OBJECTS = \ build/debugserver/debugserver/DebugServer.o \ build/debugserver/shared/DebugClient.o \ build/debugserver/shared/OwnedHandle.o \ + build/debugserver/shared/StringUtil.o \ build/debugserver/shared/WindowsSecurity.o \ build/debugserver/shared/WindowsVersion.o \ build/debugserver/shared/WinptyAssert.o \ diff --git a/src/libwinpty/winpty.cc b/src/libwinpty/winpty.cc index 6354f82..4fd003c 100644 --- a/src/libwinpty/winpty.cc +++ b/src/libwinpty/winpty.cc @@ -35,6 +35,7 @@ #include "../shared/StringBuilder.h" #include "../shared/StringUtil.h" #include "../shared/WindowsSecurity.h" +#include "../shared/WindowsVersion.h" #include "../shared/WinptyException.h" // TODO: Error handling, handle out-of-memory. @@ -332,6 +333,8 @@ static bool verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) WINPTY_API winpty_t *winpty_open(int cols, int rows) { + dumpWindowsVersion(); + winpty_t *pc = new winpty_t; // Start pipes. diff --git a/src/shared/OsModule.h b/src/shared/OsModule.h index 18578ca..769da54 100755 --- a/src/shared/OsModule.h +++ b/src/shared/OsModule.h @@ -21,15 +21,29 @@ #ifndef OS_MODULE_H #define OS_MODULE_H +#include + #include "DebugClient.h" #include "WinptyAssert.h" +#include "WinptyException.h" class OsModule { HMODULE m_module; public: - OsModule(const wchar_t *fileName) { + enum class LoadErrorBehavior { Abort, Throw }; + OsModule(const wchar_t *fileName, + LoadErrorBehavior behavior=LoadErrorBehavior::Abort) { m_module = LoadLibraryW(fileName); - ASSERT(m_module != NULL); + if (behavior == LoadErrorBehavior::Abort) { + ASSERT(m_module != NULL); + } else { + if (m_module == nullptr) { + const auto err = GetLastError(); + throwWindowsError( + (L"LoadLibraryW error: " + std::wstring(fileName)).c_str(), + err); + } + } } ~OsModule() { FreeLibrary(m_module); diff --git a/src/shared/WindowsVersion.cc b/src/shared/WindowsVersion.cc index d3f2eb4..6adc47d 100755 --- a/src/shared/WindowsVersion.cc +++ b/src/shared/WindowsVersion.cc @@ -21,10 +21,18 @@ #include "WindowsVersion.h" #include +#include +#include +#include #include +#include "DebugClient.h" +#include "OsModule.h" +#include "StringBuilder.h" +#include "StringUtil.h" #include "WinptyAssert.h" +#include "WinptyException.h" namespace { @@ -33,7 +41,7 @@ typedef std::tuple Version; // This function can only return a version up to 6.2 unless the executable is // manifested for a newer version of Windows. See the MSDN documentation for // GetVersionEx. -Version getWindowsVersion() { +OSVERSIONINFOEX getWindowsVersionInfo() { // Allow use of deprecated functions (i.e. GetVersionEx). We need to use // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP. // Having two code paths makes code harder to test, and it's not obvious how @@ -44,16 +52,109 @@ Version getWindowsVersion() { #pragma warning(push) #pragma warning(disable:4996) #endif - OSVERSIONINFO info = {}; + OSVERSIONINFOEX info = {}; info.dwOSVersionInfoSize = sizeof(info); - const auto success = GetVersionEx(&info); + const auto success = GetVersionEx(reinterpret_cast(&info)); ASSERT(success && "GetVersionEx failed"); - return Version(info.dwMajorVersion, info.dwMinorVersion); + return info; #ifdef _MSC_VER #pragma warning(pop) #endif } +Version getWindowsVersion() { + const auto info = getWindowsVersionInfo(); + return Version(info.dwMajorVersion, info.dwMinorVersion); +} + +struct ModuleNotFound : WinptyException { + virtual const wchar_t *what() const WINPTY_NOEXCEPT { + return L"ModuleNotFound"; + } +}; + +// Throws WinptyException on error. +std::wstring getSystemDirectory() { + wchar_t systemDirectory[MAX_PATH]; + const UINT size = GetSystemDirectory(systemDirectory, MAX_PATH); + if (size == 0) { + throwWindowsError(L"GetSystemDirectory failed"); + } else if (size >= MAX_PATH) { + throwWinptyException( + L"GetSystemDirectory: path is longer than MAX_PATH"); + } + return systemDirectory; +} + +#define GET_VERSION_DLL_API(name) \ + const auto p ## name = \ + reinterpret_cast( \ + versionDll.proc(#name)); \ + if (p ## name == nullptr) { \ + throwWinptyException(L ## #name L" is missing"); \ + } + +// Throws WinptyException on error. +VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) { + // version.dll is not a conventional KnownDll, so if we link to it, there's + // a danger of accidentally loading a malicious DLL. In a more typical + // application, perhaps we'd guard against this security issue by + // controlling which directories this code runs in (e.g. *not* the + // "Downloads" directory), but that's harder for the winpty library. + OsModule versionDll( + (getSystemDirectory() + L"\\version.dll").c_str(), + OsModule::LoadErrorBehavior::Throw); + GET_VERSION_DLL_API(GetFileVersionInfoSizeW); + GET_VERSION_DLL_API(GetFileVersionInfoW); + GET_VERSION_DLL_API(VerQueryValueW); + DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr); + if (!size) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + throw ModuleNotFound(); + } else { + throwWindowsError( + (L"GetFileVersionInfoSizeW failed on " + path).c_str()); + } + } + std::unique_ptr versionBuffer(new char[size]); + if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) { + throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str()); + } + VS_FIXEDFILEINFO *versionInfo = nullptr; + UINT versionInfoSize = 0; + if (!pVerQueryValueW( + versionBuffer.get(), L"\\", + reinterpret_cast(&versionInfo), &versionInfoSize) || + versionInfo == nullptr || + versionInfoSize != sizeof(VS_FIXEDFILEINFO) || + versionInfo->dwSignature != 0xFEEF04BD) { + throwWinptyException((L"VerQueryValueW failed on " + path).c_str()); + } + return *versionInfo; +} + +uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwProductVersionMS) << 32) | + (static_cast(info.dwProductVersionLS)); +} + +uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwFileVersionMS) << 32) | + (static_cast(info.dwFileVersionLS)); +} + +std::string versionToString(uint64_t version) { + StringBuilder b(32); + b << ((uint16_t)(version >> 48)); + b << '.'; + b << ((uint16_t)(version >> 32)); + b << '.'; + b << ((uint16_t)(version >> 16)); + b << '.'; + b << ((uint16_t)(version >> 0)); + return b.str_moved(); +} + } // anonymous namespace // Returns true for Windows Vista (or Windows Server 2008) or newer. @@ -65,3 +166,79 @@ bool isAtLeastWindowsVista() { bool isAtLeastWindows8() { return getWindowsVersion() >= Version(6, 2); } + +#define WINPTY_IA32 1 +#define WINPTY_X64 2 + +#if defined(_M_IX86) || defined(__i386__) +#define WINPTY_ARCH WINPTY_IA32 +#elif defined(_M_X64) || defined(__x86_64__) +#define WINPTY_ARCH WINPTY_X64 +#endif + +typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process); + +void dumpWindowsVersion() { + if (!isTracingEnabled()) { + return; + } + const auto info = getWindowsVersionInfo(); + StringBuilder b; + b << info.dwMajorVersion << '.' << info.dwMinorVersion + << '.' << info.dwBuildNumber << ' ' + << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor + << ' '; + switch (info.wProductType) { + case VER_NT_WORKSTATION: b << "Client"; break; + case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break; + case VER_NT_SERVER: b << "Server"; break; + default: + b << "product=" << info.wProductType; break; + } + b << ' '; +#if WINPTY_ARCH == WINPTY_IA32 + b << "IA32"; + OsModule kernel32(L"kernel32.dll"); + IsWow64Process_t *pIsWow64Process = + reinterpret_cast( + kernel32.proc("IsWow64Process")); + if (pIsWow64Process != nullptr) { + BOOL result = false; + const BOOL success = pIsWow64Process(GetCurrentProcess(), &result); + if (!success) { + b << " WOW64:error"; + } else if (success && result) { + b << " WOW64"; + } + } else { + b << " WOW64:missingapi"; + } +#elif WINPTY_ARCH == WINPTY_X64 + b << "X64"; +#endif + const auto dllVersion = [](const wchar_t *dllPath) -> std::string { + try { + const auto info = getFixedFileInfo(dllPath); + StringBuilder fb(64); + fb << utf8FromWide(dllPath) << ':'; + fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/' + << "P:" << versionToString(productVersionFromInfo(info)); + return fb.str_moved(); + } catch (const ModuleNotFound &e) { + return utf8FromWide(dllPath) + ":none"; + } catch (const WinptyException &e) { + trace("Error getting %s version: %s", + utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str()); + return utf8FromWide(dllPath) + ":error"; + } + }; + b << ' ' << dllVersion(L"kernel32.dll"); + // ConEmu provides a DLL that hooks many Windows APIs, especially console + // APIs. Its existence and version number could be useful in debugging. +#if WINPTY_ARCH == WINPTY_IA32 + b << ' ' << dllVersion(L"ConEmuHk.dll"); +#elif WINPTY_ARCH == WINPTY_X64 + b << ' ' << dllVersion(L"ConEmuHk64.dll"); +#endif + trace("Windows version: %s", b.c_str()); +} diff --git a/src/shared/WindowsVersion.h b/src/shared/WindowsVersion.h index 40111e4..ee6e96d 100755 --- a/src/shared/WindowsVersion.h +++ b/src/shared/WindowsVersion.h @@ -23,5 +23,6 @@ bool isAtLeastWindowsVista(); bool isAtLeastWindows8(); +void dumpWindowsVersion(); #endif // WINPTY_SHARED_WINDOWS_VERSION_H diff --git a/src/winpty.gyp b/src/winpty.gyp index 7c444d1..a6c74e4 100644 --- a/src/winpty.gyp +++ b/src/winpty.gyp @@ -165,6 +165,8 @@ 'shared/OwnedHandle.cc', 'shared/OsModule.h', 'shared/StringBuilder.h', + 'shared/StringUtil.cc', + 'shared/StringUtil.h', 'shared/WindowsSecurity.h', 'shared/WindowsSecurity.cc', 'shared/WindowsVersion.h',