mirror of
https://github.com/stormshield/HiveSwarming
synced 2026-06-08 17:37:30 +00:00
HiveSwarming - version 1.0
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
.vs
|
||||
*.vcxproj.user
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include <windows.h>
|
||||
#include "Constants.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
void DeleteHiveLogFiles
|
||||
(
|
||||
_In_ const std::wstring &HiveFilePath
|
||||
)
|
||||
{
|
||||
for (auto& Ext : Constants::Hives::LogFileExtensions)
|
||||
{
|
||||
std::wstring LogFilePath = HiveFilePath + Ext;
|
||||
DWORD Attributes = GetFileAttributesW(LogFilePath.c_str());
|
||||
if (Attributes != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
Attributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
||||
Attributes &= ~FILE_ATTRIBUTE_SYSTEM;
|
||||
SetFileAttributesW(LogFilePath.c_str(), Attributes);
|
||||
DeleteFileW(LogFilePath.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReportError
|
||||
(
|
||||
_In_ const HRESULT ErrorCode,
|
||||
_In_ const std::wstring& Context
|
||||
)
|
||||
{
|
||||
LPWSTR MessageBuffer = NULL;
|
||||
DWORD FmtResult = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
|
||||
NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPWSTR)&MessageBuffer, 0, NULL);
|
||||
|
||||
if (!Context.empty())
|
||||
{
|
||||
std::wcerr << Context << L":" << std::endl;
|
||||
}
|
||||
|
||||
if (FmtResult == 0)
|
||||
{
|
||||
std::wcerr << L"ERROR " << std::hex << std::setw(8) << ErrorCode << L" (could not format message)";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wcerr << L"ERROR " << std::hex << std::setw(8) << ErrorCode << L" (" << MessageBuffer << L")";
|
||||
LocalFree((PVOID)MessageBuffer);
|
||||
MessageBuffer = NULL;
|
||||
}
|
||||
|
||||
std::wcerr << std::endl << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/// @brief Delete .LOG1 and .LOG2 system files that were created when loading an application hive
|
||||
/// @param[in] HiveFilePath Path to the hive file
|
||||
void DeleteHiveLogFiles
|
||||
(
|
||||
_In_ const std::wstring &HiveFilePath
|
||||
);
|
||||
|
||||
/// @brief Report an error
|
||||
/// @param[in] ErrorCode HRESULT value
|
||||
/// @param[in] Context Optional context
|
||||
void ReportError
|
||||
(
|
||||
_In_ const HRESULT ErrorCode,
|
||||
_In_ const std::wstring& Context = std::wstring{}
|
||||
);
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
/// Program constants
|
||||
namespace Constants {
|
||||
|
||||
/// Command-line constants
|
||||
namespace Program {
|
||||
/// Switch for converting a hive to a .reg file
|
||||
static const std::wstring HiveToRegFileSwitch { L"--hive-to-reg-file" };
|
||||
|
||||
/// Switch for converting a .reg file to a hive
|
||||
static const std::wstring RegFileToHiveSwitch { L"--reg-file-to-hive" };
|
||||
};
|
||||
|
||||
/// Program defaults
|
||||
namespace Defaults {
|
||||
/// Default Root registry path in generated .reg files
|
||||
static const std::wstring ExportKeyPath { L"(HiveRoot)" };
|
||||
};
|
||||
|
||||
/// Hive-specific constants
|
||||
namespace Hives {
|
||||
/// Extensions of extra files that are generated when manipulating registry hives
|
||||
static const std::vector<std::wstring> LogFileExtensions{ L".LOG1", L".LOG2" };
|
||||
};
|
||||
|
||||
/// .reg file-specific constants
|
||||
namespace RegFiles {
|
||||
/// New lines used in .reg files
|
||||
static const std::wstring NewLines { L"\r\n" };
|
||||
|
||||
/// Preamble found in .reg files
|
||||
static const std::wstring Preamble { L"\ufeff" // Byte Order Mark
|
||||
L"Windows Registry Editor Version 5.00\r\n"
|
||||
L"\r\n" };
|
||||
|
||||
/// Character used at start of line beginning a registry key path
|
||||
static const WCHAR KeyOpening { L'[' };
|
||||
|
||||
/// Character delimiting parent key from its child in a registry path
|
||||
static const WCHAR PathSeparator { L'\\' };
|
||||
|
||||
/// Character used at end of line closing a registry key path
|
||||
static const WCHAR KeyClosing { L']' };
|
||||
|
||||
/// Character used for default registry values
|
||||
static const WCHAR DefaultValue { L'@' };
|
||||
|
||||
/// Character used for separating value declaration from its content
|
||||
static const WCHAR ValueNameSeparator { L'=' };
|
||||
|
||||
/// Sequence used for describing a DWORD rendition
|
||||
static const std::wstring DwordPrefix { L"dword:" };
|
||||
|
||||
/// Sequence used for describing a hexadecimal rendition
|
||||
static const std::wstring HexPrefix { L"hex" };
|
||||
|
||||
/// Character used when beginning the value type for a hexadecimal rendition
|
||||
static const WCHAR HexTypeSpecOpening { L'(' };
|
||||
|
||||
/// Character used when ending the value type for a hexadecimal rendition
|
||||
static const WCHAR HexTypeSpecClosing { L')' };
|
||||
|
||||
/// Character at the beginning of the hexadecimal rendition
|
||||
static const WCHAR HexSuffix { L':' };
|
||||
|
||||
/// Byte separator in hexadecimal renditions
|
||||
static const WCHAR HexByteSeparator { L',' };
|
||||
|
||||
/// Desired maximal line length when dumping hexadecimal rendition of registry values
|
||||
static const SIZE_T HexWrappingLimit = 80u;
|
||||
|
||||
/// Count of leading spaces when wrapping a hexadecimal rendition
|
||||
static const SIZE_T HexNewLineLeadingSpaces = 2u;
|
||||
|
||||
/// Sequence used when continuing a hexadecimal rendition on a new line
|
||||
static const std::wstring HexByteNewLine { L"\\\r\n\u0020\u0020" };
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/// Internal representation of a registry value.
|
||||
struct RegistryValue {
|
||||
/// Name of the registry value. May not contain null character
|
||||
std::wstring Name;
|
||||
|
||||
/// Type of the registry value. Usually a small number.
|
||||
DWORD Type;
|
||||
|
||||
/// Binary representation of the underlying data as a byte buffer.
|
||||
std::vector<BYTE> BinaryValue;
|
||||
};
|
||||
|
||||
/// Internal representation of a registry key.
|
||||
struct RegistryKey {
|
||||
/// Name of the registry key. May contain any character except backslash
|
||||
std::wstring Name;
|
||||
|
||||
/// Container of subkeys
|
||||
std::vector<RegistryKey> Subkeys;
|
||||
|
||||
/// Container of values
|
||||
std::vector<RegistryValue> Values;
|
||||
};
|
||||
|
||||
/// @brief Create an internal representation of a registry key from a registry hive (binary) file
|
||||
/// @param[in] HiveFilePath Path to the registry hive
|
||||
/// @param[in] RootName Path to the root key for export
|
||||
/// @param[out] RegKey Internal structure
|
||||
/// @return HRESULT semantics
|
||||
/// @note For the moment, system files that are created (.LOG1, .LOG2) are not cleaned up.
|
||||
_Must_inspect_result_
|
||||
HRESULT HiveToInternal
|
||||
(
|
||||
_In_ const std::wstring &HiveFilePath,
|
||||
_In_ const std::wstring &RootName,
|
||||
_Out_ RegistryKey& RegKey
|
||||
);
|
||||
|
||||
/// @brief Create a .reg file from the internal representation of a registry key
|
||||
/// @param[in] RegKey Representation of the registry key
|
||||
/// @param[in] OutputFilePath Path of the desired output file
|
||||
/// @return HRESULT semantics
|
||||
/// @note #OutputFilePath is overwritten if it already exists
|
||||
_Must_inspect_result_
|
||||
HRESULT InternalToRegfile
|
||||
(
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const std::wstring &OutputFilePath
|
||||
);
|
||||
|
||||
/// @brief Create a hive file from the internal representation of a registry key
|
||||
/// @param[in] RegKey Representation of the registry key
|
||||
/// @param[in] OutputFilePath Path of the desired output file
|
||||
/// @return HRESULT semantics
|
||||
/// @note #OutputFilePath is overwritten if it already exists
|
||||
_Must_inspect_result_
|
||||
HRESULT InternalToHive
|
||||
(
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const std::wstring &OutputFilePath
|
||||
);
|
||||
|
||||
/// @brief Create an internal representation of a registry key from a registry .reg (text) file
|
||||
/// @param[in] RegFilePath Path to the registry .reg file
|
||||
/// @param[out] RegKey Internal structure
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
HRESULT RegfileToInternal
|
||||
(
|
||||
_In_ const std::wstring& RegFilePath,
|
||||
_Out_ RegistryKey& RegKey
|
||||
);
|
||||
@@ -0,0 +1,101 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include "Conversions.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include "Constants.h"
|
||||
|
||||
/// @brief Program entry point
|
||||
/// @param[in] Argc Command line token count, including program name
|
||||
/// @param[in] Argv Tokens of the command line (#Argc valid entries)
|
||||
/// @retval EXIT_SUCCESS Program execution successful
|
||||
/// @retval EXIT_FAILURE Program execution failed
|
||||
int wmain(
|
||||
INT CONST Argc,
|
||||
LPCWSTR CONST* CONST Argv
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
|
||||
RegistryKey InternalStruct;
|
||||
|
||||
auto Usage = [&]()
|
||||
{
|
||||
std::wcerr << L"Usage: " << std::endl <<
|
||||
L"\t" << Argv[0] << L" " << Constants::Program::HiveToRegFileSwitch << L" <HiveFile> <RegFile>" << std::endl <<
|
||||
L"\t" << Argv[0] << L" " << Constants::Program::RegFileToHiveSwitch << L" <RegFile> <HiveFile>" << std::endl <<
|
||||
std::endl;
|
||||
};
|
||||
|
||||
if (Argc <= 1)
|
||||
{
|
||||
Usage();
|
||||
Result = S_OK;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (Constants::Program::HiveToRegFileSwitch == Argv[1])
|
||||
{
|
||||
if (Argc != 4)
|
||||
{
|
||||
Usage();
|
||||
Result = E_INVALIDARG;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
const std::wstring HivePath { Argv[2] };
|
||||
const std::wstring RegPath { Argv[3] };
|
||||
|
||||
Result = HiveToInternal(HivePath, Constants::Defaults::ExportKeyPath, InternalStruct);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = InternalToRegfile(InternalStruct, RegPath);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
else if (Constants::Program::RegFileToHiveSwitch == Argv[1])
|
||||
{
|
||||
if (Argc != 4)
|
||||
{
|
||||
Usage();
|
||||
Result = E_INVALIDARG;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
const std::wstring RegPath { Argv[2] };
|
||||
const std::wstring HivePath { Argv[3] };
|
||||
|
||||
Result = RegfileToInternal(RegPath, InternalStruct);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Serializing registry file" + RegPath);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = InternalToHive(InternalStruct, HivePath);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Writing hive file " + HivePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Usage();
|
||||
Result = E_INVALIDARG;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = S_OK;
|
||||
|
||||
Cleanup:
|
||||
return FAILED(Result) ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29609.76
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HiveSwarming", "HiveSwarming.vcxproj", "{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Debug|x64.Build.0 = Debug|x64
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Debug|x86.Build.0 = Debug|Win32
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Release|x64.ActiveCfg = Release|x64
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Release|x64.Build.0 = Release|x64
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Release|x86.ActiveCfg = Release|Win32
|
||||
{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FD1888F9-D331-4CD9-A58D-0C0F01F6735B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="RegfileToInternal.cpp" />
|
||||
<ClCompile Include="HiveToInternal.cpp" />
|
||||
<ClCompile Include="InternalToRegfile.cpp" />
|
||||
<ClCompile Include="InternalToHive.cpp" />
|
||||
<ClCompile Include="HiveSwarming.cpp" />
|
||||
<ClCompile Include="CommonFunctions.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Constants.h" />
|
||||
<ClInclude Include="Conversions.h" />
|
||||
<ClInclude Include="CommonFunctions.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{00DC3B6B-A592-4736-9CA9-130E4DF06CD4}</ProjectGuid>
|
||||
<RootNamespace>HiveSwarming</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="HiveSwarming.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RegfileToInternal.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HiveToInternal.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="InternalToHive.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="InternalToRegfile.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CommonFunctions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Constants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Conversions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CommonFunctions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,186 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include "Conversions.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include <sstream>
|
||||
|
||||
/// @brief Create an internal representation of a registry key from the handle to a registry key
|
||||
/// @param[in] Hkey the registry key
|
||||
/// @param[in] KeyName Name of the key #Hkey is a handle to
|
||||
/// @param[out] RegKey Representation of the key
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
static HRESULT HkeyToInternal
|
||||
(
|
||||
_In_ const HKEY Hkey,
|
||||
_In_ const std::wstring& KeyName,
|
||||
_Out_ RegistryKey& RegKey
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
DWORD SubkeyCount = 0;
|
||||
DWORD MaximalSubKeyLength = 0;
|
||||
DWORD ValueCount = 0;
|
||||
DWORD MaximalValueNameLength = 0;
|
||||
DWORD MaximalValueLength = 0;
|
||||
LPWSTR SubKeyNameBuffer = NULL;
|
||||
LPWSTR ValueNameBuffer = NULL;
|
||||
PBYTE ValueBuffer = NULL;
|
||||
|
||||
if (Hkey == NULL)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Null HKEY");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegQueryInfoKeyW(Hkey, NULL, 0, NULL, &SubkeyCount, &MaximalSubKeyLength,
|
||||
NULL, &ValueCount, &MaximalValueNameLength, &MaximalValueLength, NULL, NULL));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Getting information on HKEY - Current key name: " + KeyName);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
SubKeyNameBuffer = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (1 + MaximalSubKeyLength) * sizeof(WCHAR));
|
||||
if (SubKeyNameBuffer == NULL)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Allocating space for subkey names - Current key name: " + KeyName);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
ValueNameBuffer = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (1 + MaximalValueNameLength) * sizeof(WCHAR));
|
||||
if (ValueNameBuffer == NULL)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Allocating space for value names - Current key name: " + KeyName);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
ValueBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MaximalValueLength);
|
||||
if (ValueBuffer == NULL)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Allocating space for values - Current key name: " + KeyName);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
RegKey.Name = KeyName;
|
||||
|
||||
for (DWORD ValueIndex = 0; ValueIndex < ValueCount; ++ValueIndex)
|
||||
{
|
||||
DWORD ValueNameLength = MaximalValueNameLength + 1;
|
||||
DWORD ValueType = 0;
|
||||
DWORD DataLength = MaximalValueLength + 1;
|
||||
Result = HRESULT_FROM_WIN32(RegEnumValueW(Hkey, ValueIndex, ValueNameBuffer, &ValueNameLength,
|
||||
NULL, &ValueType, ValueBuffer, &DataLength));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Getting value at index " << ValueIndex << L" - Current key name: " << KeyName;
|
||||
ReportError(Result, ErrorMessageStream.str());
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
RegistryValue NewValue;
|
||||
NewValue.Name.assign(ValueNameBuffer, ValueNameLength);
|
||||
NewValue.Type = ValueType;
|
||||
NewValue.BinaryValue.assign(ValueBuffer, ValueBuffer + DataLength);
|
||||
|
||||
RegKey.Values.emplace_back(std::move(NewValue));
|
||||
}
|
||||
|
||||
for (DWORD SubkeyIndex = 0; SubkeyIndex < SubkeyCount; ++SubkeyIndex)
|
||||
{
|
||||
HKEY HSubkey = NULL;
|
||||
DWORD SubkeyNameLength = MaximalSubKeyLength + 1;
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegEnumKeyExW(Hkey, SubkeyIndex, SubKeyNameBuffer, &SubkeyNameLength, NULL, NULL, NULL, NULL));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Getting subkey name at index " << SubkeyIndex << L" - Current key name: " << KeyName;
|
||||
ReportError(Result, ErrorMessageStream.str());
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegOpenKeyExW(Hkey, SubKeyNameBuffer, 0, KEY_ALL_ACCESS, &HSubkey));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Opening subkey named " << SubKeyNameBuffer << L" at index " << SubkeyIndex << L" - Current key name: " << KeyName;
|
||||
ReportError(Result, ErrorMessageStream.str());
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
std::wstring SubkeyName;
|
||||
SubkeyName.assign(SubKeyNameBuffer, SubkeyNameLength);
|
||||
|
||||
RegistryKey NewKey;
|
||||
Result = HkeyToInternal(HSubkey, SubkeyName, NewKey);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Getting contents of subkey named " << SubkeyName << L" - Current key name: " << KeyName;
|
||||
ReportError(Result, ErrorMessageStream.str());
|
||||
goto Cleanup;
|
||||
}
|
||||
RegKey.Subkeys.emplace_back(std::move(NewKey));
|
||||
|
||||
RegCloseKey(HSubkey);
|
||||
}
|
||||
Cleanup:
|
||||
if (SubKeyNameBuffer)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)SubKeyNameBuffer);
|
||||
SubKeyNameBuffer = NULL;
|
||||
}
|
||||
if (ValueNameBuffer)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)ValueNameBuffer);
|
||||
ValueNameBuffer = NULL;
|
||||
}
|
||||
if (ValueBuffer)
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)ValueBuffer);
|
||||
ValueBuffer = NULL;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// non-static function: documented in header.
|
||||
_Must_inspect_result_
|
||||
HRESULT HiveToInternal
|
||||
(
|
||||
_In_ const std::wstring& HiveFilePath,
|
||||
_In_ const std::wstring& RootName,
|
||||
_Out_ RegistryKey& RegKey
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
HKEY HiveKey = NULL;
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegLoadAppKeyW(HiveFilePath.c_str(), &HiveKey, KEY_ALL_ACCESS, REG_PROCESS_APPKEY, 0));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Loading hive file " + HiveFilePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = HkeyToInternal(HiveKey, RootName, RegKey);
|
||||
|
||||
Cleanup:
|
||||
if (HiveKey != NULL)
|
||||
{
|
||||
RegCloseKey(HiveKey);
|
||||
HiveKey = NULL;
|
||||
|
||||
DeleteHiveLogFiles(HiveFilePath);
|
||||
}
|
||||
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include "Conversions.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
/// @brief Fill an empty HKEY with internal structures representing a registry key and dependencies
|
||||
/// @param[in] RegKey Registry key to dump into the HKEY
|
||||
/// @param[in] KeyHandle Handle to the key
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
static HRESULT InternalToHkey(
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const HKEY KeyHandle
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
HKEY SubkeyHandle = NULL;
|
||||
|
||||
if (KeyHandle == NULL)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid Parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
for (auto &Value : RegKey.Values)
|
||||
{
|
||||
if (Value.BinaryValue.size() > MAXDWORD)
|
||||
{
|
||||
ReportError(E_UNEXPECTED, L"Binary value is too long (name: " + Value.Name + L")");
|
||||
Result = E_UNEXPECTED;
|
||||
goto Cleanup;
|
||||
}
|
||||
Result = HRESULT_FROM_WIN32(RegSetValueExW(KeyHandle, Value.Name.c_str(), 0, Value.Type,
|
||||
Value.BinaryValue.data(), static_cast<DWORD>(Value.BinaryValue.size())));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not set value " + Value.Name + L" of key " + RegKey.Name);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &Key : RegKey.Subkeys)
|
||||
{
|
||||
if (SubkeyHandle != NULL)
|
||||
{
|
||||
RegCloseKey(SubkeyHandle);
|
||||
SubkeyHandle = NULL;
|
||||
}
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegCreateKeyExW(KeyHandle, Key.Name.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE,
|
||||
KEY_ALL_ACCESS, NULL, &SubkeyHandle, NULL));
|
||||
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not create subkey " + Key.Name + L" of key " + RegKey.Name);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = InternalToHkey(Key, SubkeyHandle);
|
||||
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not render subkey " + Key.Name + L" of key " + RegKey.Name);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
Result = S_OK;
|
||||
Cleanup:
|
||||
if (SubkeyHandle != NULL)
|
||||
{
|
||||
RegCloseKey(SubkeyHandle);
|
||||
SubkeyHandle = NULL;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
// non-static function: documented in header.
|
||||
_Must_inspect_result_
|
||||
HRESULT InternalToHive
|
||||
(
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const std::wstring& OutputFilePath
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
HKEY HiveKey = NULL;
|
||||
|
||||
if (!DeleteFileW(OutputFilePath.c_str()))
|
||||
{
|
||||
if (GetLastError() != ERROR_FILE_NOT_FOUND)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Could not delete hive file " + OutputFilePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
Result = HRESULT_FROM_WIN32(RegLoadAppKeyW(OutputFilePath.c_str(), &HiveKey, KEY_ALL_ACCESS, REG_PROCESS_APPKEY, 0));
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not create empty hive file " + OutputFilePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = InternalToHkey(RegKey, HiveKey);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not render internal structure to hive");
|
||||
}
|
||||
|
||||
Result = S_OK;
|
||||
|
||||
Cleanup:
|
||||
if (HiveKey != NULL)
|
||||
{
|
||||
RegCloseKey(HiveKey);
|
||||
HiveKey = NULL;
|
||||
|
||||
DeleteHiveLogFiles(OutputFilePath);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include "Constants.h"
|
||||
#include "Conversions.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
/// @brief Perform global replacement of substring in a std::wstring
|
||||
/// @param[in,out] String String on which to perform substitutions
|
||||
/// @param[in] Pattern Substring that will be replaced by #Replacement in #String
|
||||
/// @param[in] Replacement What is inserted in place of #Substring in #String
|
||||
static VOID GlobalStringSubstitute(
|
||||
_Inout_ std::wstring& String,
|
||||
_In_ const std::wstring& Pattern,
|
||||
_In_ const std::wstring& Replacement
|
||||
)
|
||||
{
|
||||
size_t Position = String.find(Pattern);
|
||||
while (Position != String.npos)
|
||||
{
|
||||
String.replace(Position, Pattern.length(), Replacement);
|
||||
Position = String.find(Pattern, Position + Replacement.length());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Append binary contents of a std::wstring to an opened file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] String Input string
|
||||
/// @note The string buffer is dumped to the file “as is”.
|
||||
/// Null characters may be appended to the file if the string buffer includes some of them.
|
||||
_Must_inspect_result_
|
||||
static HRESULT WriteStringBufferToFile
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const std::wstring String
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
|
||||
if (String.length() >= MAXDWORD / sizeof(decltype(String)::traits_type::char_type))
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
|
||||
ReportError(Result, L"Writing data that is too large");
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
{
|
||||
const DWORD BytesToWrite = static_cast<DWORD>(String.length() * sizeof(decltype(String)::traits_type::char_type));
|
||||
DWORD BytesWritten = 0;
|
||||
|
||||
if (!WriteFile(OutFileHandle, (LPCVOID)String.c_str(), BytesToWrite, &BytesWritten, NULL))
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Could not write to output file");
|
||||
return Result;
|
||||
}
|
||||
if (BytesWritten != BytesToWrite)
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
ReportError(Result, L"Bytes not fully written to file");
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/// @brief Render the contents of a registry value in a .reg file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] FirstLineSizeSoFar How many characters have already been written on the line when dumping
|
||||
/// the name of the value and the equal sign.
|
||||
/// This is used for mimicking .reg format line breaks before lines over 80 characters
|
||||
/// @param[in] RegValue Representation of the registry value
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
static HRESULT RenderBinaryValue
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const SIZE_T FirstLineSizeSoFar,
|
||||
_In_ const RegistryValue& RegValue
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
SIZE_T CurLineSizeSoFar = FirstLineSizeSoFar;
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
std::wostringstream BinaryRenditionStream;
|
||||
BinaryRenditionStream << Constants::RegFiles::HexPrefix;
|
||||
if (RegValue.Type != REG_BINARY)
|
||||
{
|
||||
BinaryRenditionStream << Constants::RegFiles::HexTypeSpecOpening << std::hex << RegValue.Type << Constants::RegFiles::HexTypeSpecClosing;
|
||||
}
|
||||
BinaryRenditionStream << Constants::RegFiles::HexSuffix;
|
||||
|
||||
CurLineSizeSoFar += BinaryRenditionStream.str().length();
|
||||
|
||||
for (std::vector<BYTE>::const_iterator CurByteIt = RegValue.BinaryValue.cbegin(); CurByteIt != RegValue.BinaryValue.cend(); ++CurByteIt)
|
||||
{
|
||||
BinaryRenditionStream << std::hex << std::setfill(L'0') << std::setw(2) << *CurByteIt;
|
||||
CurLineSizeSoFar += 2;
|
||||
if (CurByteIt + 1 != RegValue.BinaryValue.cend())
|
||||
{
|
||||
BinaryRenditionStream << Constants::RegFiles::HexByteSeparator;
|
||||
CurLineSizeSoFar += 1;
|
||||
if (CurLineSizeSoFar > Constants::RegFiles::HexWrappingLimit - 4)
|
||||
{
|
||||
// adding "xx,\" would go over 80 characters, reg export typically breaks line here.
|
||||
BinaryRenditionStream << Constants::RegFiles::HexByteNewLine;
|
||||
CurLineSizeSoFar = Constants::RegFiles::HexNewLineLeadingSpaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryRenditionStream << Constants::RegFiles::NewLines;
|
||||
|
||||
Result = WriteStringBufferToFile(OutFileHandle, BinaryRenditionStream.str());
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not write to output file");
|
||||
return Result;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/// @brief Render the contents of a REG_DWORD registry value in a .reg file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] FirstLineSizeSoFar Use for fallback to #RenderBinaryValue
|
||||
/// @param[in] RegValue Representation of the registry value
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
static HRESULT RenderDwordValue
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const SIZE_T FirstLineSizeSoFar,
|
||||
_In_ const RegistryValue& RegValue
|
||||
)
|
||||
{
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
if (RegValue.Type != REG_DWORD)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (RegValue.BinaryValue.size() != sizeof(DWORD))
|
||||
{
|
||||
return RenderBinaryValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
|
||||
std::wostringstream DwordRenditionStream;
|
||||
DwordRenditionStream << L"dword:";
|
||||
DwordRenditionStream << std::hex << std::setw(8) << std::setfill(L'0') << *(DWORD*)(RegValue.BinaryValue.data());
|
||||
DwordRenditionStream << Constants::RegFiles::NewLines;
|
||||
|
||||
return WriteStringBufferToFile(OutFileHandle, DwordRenditionStream.str());
|
||||
|
||||
}
|
||||
|
||||
/// @brief Render the contents of a REG_SZ registry value in a .reg file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] FirstLineSizeSoFar Use for fallback to #RenderBinaryValue
|
||||
/// @param[in] RegValue Representation of the registry value
|
||||
/// @return HRESULT semantics
|
||||
/// @note This falls back to binary rendition if the REG_SZ does not meet requirements such as:
|
||||
/// - REG_SZ values should be terminated by a null character
|
||||
/// - REG_SZ values may not contain other null characters
|
||||
/// - REG_SZ value sizes should be a multiple of 2 as they store WCHAR-based values.
|
||||
_Must_inspect_result_
|
||||
static HRESULT RenderStringValue
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const SIZE_T FirstLineSizeSoFar,
|
||||
_In_ const RegistryValue& RegValue
|
||||
)
|
||||
{
|
||||
std::wstring WstringValue;
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
if (RegValue.Type != REG_SZ)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
if (RegValue.BinaryValue.size() % sizeof(WCHAR) != 0)
|
||||
{
|
||||
return RenderBinaryValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
|
||||
WstringValue.assign((PWCHAR) RegValue.BinaryValue.data(), RegValue.BinaryValue.size() / sizeof(WCHAR));
|
||||
if (WstringValue.empty())
|
||||
{
|
||||
return RenderBinaryValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
|
||||
if ( WstringValue.empty()
|
||||
|| WstringValue.back() != L'\0'
|
||||
|| std::count(WstringValue.cbegin(), WstringValue.cend() - 1, L'\0') != 0
|
||||
)
|
||||
{
|
||||
return RenderBinaryValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
|
||||
// Now we can render the value as REG_SZ: Remove null character
|
||||
WstringValue.pop_back();
|
||||
|
||||
GlobalStringSubstitute(WstringValue, L"\\", L"\\\\");
|
||||
GlobalStringSubstitute(WstringValue, L"\"", L"\\\"");
|
||||
GlobalStringSubstitute(WstringValue, L"\n", L"\r\n");
|
||||
|
||||
std::wostringstream StringRenditionStream;
|
||||
StringRenditionStream << L"\"" << WstringValue << L"\"" << Constants::RegFiles::NewLines;
|
||||
return WriteStringBufferToFile(OutFileHandle, StringRenditionStream.str());
|
||||
}
|
||||
|
||||
/// @brief Render a registry value and its contents in a .reg file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] RegValue Representation of the registry value
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
HRESULT RenderRegistryValue
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const RegistryValue& RegValue
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
SIZE_T FirstLineSizeSoFar = 0;
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
std::wstring EscapedName = RegValue.Name;
|
||||
GlobalStringSubstitute(EscapedName, L"\\", L"\\\\");
|
||||
GlobalStringSubstitute(EscapedName, L"\"", L"\\\"");
|
||||
GlobalStringSubstitute(EscapedName, L"\n", L"\r\n");
|
||||
std::wostringstream Str;
|
||||
if (EscapedName.empty())
|
||||
{
|
||||
Str << Constants::RegFiles::DefaultValue << Constants::RegFiles::ValueNameSeparator;
|
||||
}
|
||||
else
|
||||
{
|
||||
Str << L"\"" << EscapedName << L"\"=";
|
||||
}
|
||||
Result = WriteStringBufferToFile(OutFileHandle, Str.str());
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not write data to file");
|
||||
return Result;
|
||||
}
|
||||
|
||||
FirstLineSizeSoFar += Str.str().length();
|
||||
|
||||
if (RegValue.Type == REG_DWORD)
|
||||
{
|
||||
return RenderDwordValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
if (RegValue.Type == REG_SZ)
|
||||
{
|
||||
return RenderStringValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
return RenderBinaryValue(OutFileHandle, FirstLineSizeSoFar, RegValue);
|
||||
}
|
||||
|
||||
/// @brief Render a registry key and its values and subkeys in a .reg file
|
||||
/// @param[in] OutFileHandle Handle to the file
|
||||
/// @param[in] RegKey Representation of the registry key
|
||||
/// @param[in] PathSoFar Path to this key
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
HRESULT RenderRegistryKey
|
||||
(
|
||||
_In_ const HANDLE OutFileHandle,
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const std::wstring &PathSoFar
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
if (OutFileHandle == NULL || OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ReportError(E_HANDLE, L"Invalid parameter");
|
||||
return E_HANDLE;
|
||||
}
|
||||
|
||||
std::wstring NewPath;
|
||||
if (PathSoFar.empty())
|
||||
{
|
||||
NewPath = RegKey.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
NewPath = PathSoFar + Constants::RegFiles::PathSeparator + RegKey.Name;
|
||||
}
|
||||
|
||||
{
|
||||
std::wostringstream KeySpecStream;
|
||||
KeySpecStream << Constants::RegFiles::KeyOpening << NewPath << Constants::RegFiles::KeyClosing << Constants::RegFiles::NewLines;
|
||||
|
||||
Result = WriteStringBufferToFile(OutFileHandle, KeySpecStream.str());
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(E_HANDLE, L"Could not write to output file");
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
for (const RegistryValue &Value : RegKey.Values)
|
||||
{
|
||||
Result = RenderRegistryValue(OutFileHandle, Value);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(E_HANDLE, L"Could not render registry value" + Value.Name);
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Result = WriteStringBufferToFile(OutFileHandle, Constants::RegFiles::NewLines );
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(E_HANDLE, L"Could not write to output file");
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
for (const RegistryKey &Key : RegKey.Subkeys)
|
||||
{
|
||||
Result = RenderRegistryKey(OutFileHandle, Key, NewPath);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(E_HANDLE, L"Could not render registry key" + Key.Name);
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// non-static function: documented in header.
|
||||
_Must_inspect_result_
|
||||
HRESULT InternalToRegfile
|
||||
(
|
||||
_In_ const RegistryKey& RegKey,
|
||||
_In_ const std::wstring& OutputFilePath
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
HANDLE OutFileHandle = INVALID_HANDLE_VALUE;
|
||||
|
||||
OutFileHandle = CreateFileW(OutputFilePath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (OutFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Could not open file " + OutputFilePath + L" for writing");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
{
|
||||
Result = WriteStringBufferToFile(OutFileHandle, Constants::RegFiles::Preamble);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not write to output file");
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
Result = RenderRegistryKey(OutFileHandle, RegKey, std::wstring{});
|
||||
if (FAILED(Result))
|
||||
{
|
||||
ReportError(Result, L"Could not render registry key");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Result = S_OK;
|
||||
|
||||
Cleanup:
|
||||
if (OutFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(OutFileHandle);
|
||||
OutFileHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright 2020 Stormshield
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
(C) 2020 Stormshield
|
||||
|
||||
HiveSwarming - Conversions between registry hive and registry export formats
|
||||
without a need for special privileges.
|
||||
|
||||
USAGE
|
||||
-----
|
||||
|
||||
HiveSwarming.exe --reg-file-to-hive <export.reg> <hive_file>
|
||||
HiveSwarming.exe --hive-to-reg-file <hive_file> <export.reg>
|
||||
|
||||
EXIT CODE
|
||||
---------
|
||||
0 means success, other values mean failure.
|
||||
Failure will be accompanied by some output on standard error.
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
Q. Why this name?
|
||||
A. Registry vocabulary tends to follow beekeeping terms. Swarming is a term
|
||||
that perfectly applies to a migration of your data.
|
||||
|
||||
See also: https://devblogs.microsoft.com/oldnewthing/20030808-00/?p=42943
|
||||
|
||||
Q. Any limitations?
|
||||
A. Yes. First, when you load a hive file using the API RegLoadAppKeyW, all
|
||||
security descriptors for all keys inside the hive should be identical.
|
||||
Otherwise it will fail.
|
||||
Second, if a key name contains a closing bracket followed by a newline
|
||||
character, your .reg file is not parseable. This limitation is also valid
|
||||
for standard .reg files
|
||||
|
||||
Q. Is the .reg file compatible with reg.exe import?
|
||||
A. Yes. However the generated .reg file has [(HiveRoot)] as root key. You will
|
||||
have to substitute it globally to make it importable at any desired location.
|
||||
When converting back, it is not necessary to keep [(HiveRoot)] as the root
|
||||
key, but the only requirement is that all keys descend of the first one.
|
||||
|
||||
Q. What are requirements for .reg files?
|
||||
A. .reg files must:
|
||||
- be encoded as UTF-16, Little-Endian, with a Byte Order Mark
|
||||
- Use \r\n for line endings
|
||||
- Start with "Windows Registry Editor Version 5.00" and one blank line
|
||||
only, followed by the first key
|
||||
- Have all keys be descendants of the first key
|
||||
- Have no trailing or leading spaces on lines
|
||||
- Have no blank lines between a key and its last value
|
||||
- Have a blank line after the last value of a key (including last key)
|
||||
- Be importable to the registry
|
||||
Some third party software, like RegView, will generate invalid files. For
|
||||
example double quotes inside strings will be left unescaped; export file
|
||||
encoding will use a multi-byte character set, and Unicode data will be lost.
|
||||
Those files are not supported.
|
||||
|
||||
Q. Some random system, hidden files appeared in the process.
|
||||
A. This is a consequence of the internals of registry hives. We try to delete
|
||||
these files once the conversions are done, but it is possible that
|
||||
something failed (HANDLE still opened for example). You may probably delete
|
||||
these files themselves if your hive is not mounted anywhere and not in use
|
||||
by any process.
|
||||
|
||||
Q. Do you accept pull requests?
|
||||
A. They are welcome and will be reviewed.
|
||||
@@ -0,0 +1,522 @@
|
||||
// (C) Stormshield 2020
|
||||
// Licensed under the Apache license, version 2.0
|
||||
// See LICENSE.txt for details
|
||||
|
||||
#include "Constants.h"
|
||||
#include "Conversions.h"
|
||||
#include "CommonFunctions.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string_view>
|
||||
|
||||
/// @brief Consume a list of registry values in a .reg file
|
||||
/// @param[in,out] ValueList String view to the beginning of a list of values.
|
||||
/// Updated to the next token after the list of values, newlines having been consumed.
|
||||
/// @param[in] Values Container for the read values
|
||||
/// @return HRESULT semantics
|
||||
_Must_inspect_result_
|
||||
static HRESULT ValueListToInternal
|
||||
(
|
||||
_Inout_ std::wstring_view& ValueList,
|
||||
_Inout_ std::vector<RegistryValue>& Values
|
||||
)
|
||||
{
|
||||
auto ConsumeChars = [&ValueList](SIZE_T CharCount)
|
||||
{
|
||||
ValueList.remove_prefix(CharCount);
|
||||
};
|
||||
|
||||
auto HasChar = [&ValueList](CONST WCHAR ExpectedChar) -> bool
|
||||
{
|
||||
bool ReturnValue = !ValueList.empty() && ValueList[0] == ExpectedChar;
|
||||
if (ReturnValue)
|
||||
{
|
||||
ValueList.remove_prefix(1);
|
||||
}
|
||||
return ReturnValue;
|
||||
};
|
||||
|
||||
auto HasString = [&ValueList](const std::wstring& ExpectedString) -> bool
|
||||
{
|
||||
bool ReturnValue = ValueList.length() >= ExpectedString.length() && std::equal(ExpectedString.cbegin(), ExpectedString.cend(), ValueList.cbegin());
|
||||
if (ReturnValue)
|
||||
{
|
||||
ValueList.remove_prefix(ExpectedString.length());
|
||||
}
|
||||
return ReturnValue;
|
||||
};
|
||||
|
||||
auto EndOfStream = [&ValueList]() -> bool { return ValueList.empty(); };
|
||||
|
||||
do
|
||||
{
|
||||
if (ValueList.empty())
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Values are all consumed when getting a new line.
|
||||
if (HasString(Constants::RegFiles::NewLines))
|
||||
{
|
||||
while (HasString(Constants::RegFiles::NewLines))
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
RegistryValue Value;
|
||||
|
||||
if (HasChar(Constants::RegFiles::DefaultValue))
|
||||
{
|
||||
Value.Name.clear();
|
||||
}
|
||||
else if (HasChar(L'"'))
|
||||
{
|
||||
// We must have a value name here
|
||||
size_t ClosingQuotePos = 0;
|
||||
std::wostringstream ValueNameStream;
|
||||
while (true)
|
||||
{
|
||||
if (ClosingQuotePos >= ValueList.length())
|
||||
{
|
||||
ReportError(E_UNEXPECTED, L"Looking for closing quotation mark");
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
if (ValueList[ClosingQuotePos] == L'"')
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (ValueList[ClosingQuotePos] == L'\\')
|
||||
{
|
||||
if (ClosingQuotePos + 1 >= ValueList.length())
|
||||
{
|
||||
ReportError(E_UNEXPECTED, L"Backslash at end of buffer");
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
ValueNameStream << ValueList[ClosingQuotePos + 1];
|
||||
ClosingQuotePos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
ValueNameStream << ValueList[ClosingQuotePos];
|
||||
ClosingQuotePos += 1;
|
||||
}
|
||||
}
|
||||
Value.Name = ValueNameStream.str();
|
||||
ConsumeChars(ClosingQuotePos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError(E_UNEXPECTED, L"Value name should be literal @ or begin with double quote");
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (EndOfStream())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value " << Value.Name << L" - Missing data";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
if (!HasChar(Constants::RegFiles::ValueNameSeparator))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Missing = sign";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (EndOfStream())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - No data after = sign";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
if (HasString(Constants::RegFiles::DwordPrefix))
|
||||
{
|
||||
Value.Type = REG_DWORD;
|
||||
DWORD DwordValue;
|
||||
if (ValueList.length() <= 8)
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Buffer less than 8 characters after dword declaration";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
{
|
||||
std::wstring ReadValue{ &ValueList[0], 8 };
|
||||
std::wistringstream DwordReadingStream{ ReadValue, std::ios_base::in };
|
||||
DwordReadingStream >> std::hex >> std::setw(8) >> std::setfill(L'0') >> DwordValue;
|
||||
|
||||
std::wostringstream DwordVerificationStream;
|
||||
DwordVerificationStream << std::hex << std::setw(8) << std::setfill(L'0') << DwordValue;
|
||||
|
||||
std::wstring VerificationString{ DwordVerificationStream.str() };
|
||||
if (_wcsnicmp(VerificationString.c_str(), ReadValue.c_str(), 8) != 0)
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Could not parse dword from string " << ReadValue;
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
Value.BinaryValue.resize(sizeof(DWORD));
|
||||
CopyMemory(Value.BinaryValue.data(), &DwordValue, sizeof(DwordValue));
|
||||
|
||||
ConsumeChars(8);
|
||||
|
||||
if (!HasString(Constants::RegFiles::NewLines))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Dword value not followed by \\r\\n";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
}
|
||||
else if (HasString(Constants::RegFiles::HexPrefix))
|
||||
{
|
||||
// general case
|
||||
if (ValueList.empty())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - End of buffer after hex declaration";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
// in the general case, (xx) indicates the value type:
|
||||
// "myvalue"=hex(xx):...
|
||||
// for REG_BINARY, (xx) is omitted:
|
||||
// "myvalue"=hex:...
|
||||
// If we find the opening parenthese, we parse the registry type.
|
||||
if (HasChar(Constants::RegFiles::HexTypeSpecOpening))
|
||||
{
|
||||
size_t ClosingParenPos = ValueList.find_first_not_of(L"0123456789abcdefABCDEF");
|
||||
if (ClosingParenPos == ValueList.npos || ClosingParenPos == 0 || ValueList[ClosingParenPos] != Constants::RegFiles::HexTypeSpecClosing)
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Could not find closing parenthesis";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
{
|
||||
std::wistringstream ValueTypeInStream{ std::wstring{ &ValueList[0], ClosingParenPos }, std::ios_base::in };
|
||||
ValueTypeInStream >> std::hex >> Value.Type;
|
||||
}
|
||||
ConsumeChars(ClosingParenPos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Value.Type = REG_BINARY;
|
||||
}
|
||||
|
||||
if (!HasChar(Constants::RegFiles::HexSuffix))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Missing : sign after hex declaration";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (ValueList.empty())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - End of data while reading binary value";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
if (HasString(Constants::RegFiles::NewLines))
|
||||
{
|
||||
// end of binary data
|
||||
break;
|
||||
}
|
||||
else if (HasChar(Constants::RegFiles::HexByteSeparator))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (HasString(Constants::RegFiles::HexByteNewLine))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (ValueList.length() >= 2 && isxdigit(ValueList[0]) && isxdigit(ValueList[1]))
|
||||
{
|
||||
{
|
||||
int ByteVal;
|
||||
std::wistringstream HexValueInStream{ std::wstring{ &ValueList[0], 2 }, std::ios_base::in };
|
||||
HexValueInStream >> std::hex >> ByteVal;
|
||||
Value.BinaryValue.push_back(static_cast<BYTE>(ByteVal));
|
||||
}
|
||||
ConsumeChars(2);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Expecting two hexadecimal digits";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HasChar(L'"'))
|
||||
{
|
||||
Value.Type = REG_SZ;
|
||||
size_t ClosingQuotePos = 0;
|
||||
std::wostringstream ValueString;
|
||||
while (true)
|
||||
{
|
||||
if (ClosingQuotePos >= ValueList.length())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Could not find end of string value";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
if (ValueList[ClosingQuotePos] == L'"')
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (ValueList[ClosingQuotePos] == L'\\')
|
||||
{
|
||||
if (ClosingQuotePos + 1 >= ValueList.length())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - End of data after escape character";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
ValueString << ValueList[ClosingQuotePos + 1];
|
||||
ClosingQuotePos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
ValueString << ValueList[ClosingQuotePos];
|
||||
ClosingQuotePos += 1;
|
||||
}
|
||||
}
|
||||
ConsumeChars(ClosingQuotePos + 1);
|
||||
|
||||
// we must store a terminator in the registry value
|
||||
std::wstring SzVal = ValueString.str() + L'\0';
|
||||
Value.BinaryValue.resize(SzVal.length() * sizeof(WCHAR));
|
||||
CopyMemory(Value.BinaryValue.data(), SzVal.c_str(), Value.BinaryValue.size());
|
||||
|
||||
if (!HasString(Constants::RegFiles::NewLines))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Value name " << Value.Name << L" - Value not followed by new line";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
Values.emplace_back(std::move(Value));
|
||||
} while (TRUE);
|
||||
}
|
||||
|
||||
/// @brief Serializes the keys from a .reg file to internal representation
|
||||
/// @param[in,out] RegList Remainder of the .reg file before and after serialization
|
||||
/// Updated to the next token after all keys (and their values) that
|
||||
/// begin with #PathPrefix, newlines having been consumed.
|
||||
/// @param[in] PathPrefix Prefix to all keys that are to be read
|
||||
/// @param[in,out] RegKeys Container for all read keys
|
||||
_Must_inspect_result_
|
||||
static HRESULT RegListToInternal
|
||||
(
|
||||
_Inout_ std::wstring_view& RegList,
|
||||
_In_ const std::wstring& PathPrefix,
|
||||
_Inout_ std::vector<RegistryKey>& RegKeys
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
static const std::wstring KeyClosingAtEOL = Constants::RegFiles::KeyClosing + Constants::RegFiles::NewLines;
|
||||
|
||||
if (RegList.empty())
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Reading key beginning with prefix " << (PathPrefix.empty() ? L"<empty>" : PathPrefix.c_str()) << L" - Expecting content";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (RegList[0] != Constants::RegFiles::KeyOpening)
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Reading key beginning with prefix " << (PathPrefix.empty() ? L"<empty>" : PathPrefix.c_str()) << L" - Line does not begin with opening bracket";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
auto EndKeyPos = RegList.find(KeyClosingAtEOL, 1);
|
||||
if (EndKeyPos == RegList.npos)
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Reading key beginning with prefix " << (PathPrefix.empty() ? L"<empty>" : PathPrefix.c_str()) << L" - Could not find closing bracket followed by new line";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
const std::wstring_view KeyPath{ &RegList[1], EndKeyPos - 1 };
|
||||
|
||||
if (KeyPath.length() <= PathPrefix.length() || !std::equal(PathPrefix.cbegin(), PathPrefix.cend(), KeyPath.cbegin()))
|
||||
{
|
||||
// not a sub-key for our caller. maybe a parent caller could handle this.
|
||||
break;
|
||||
}
|
||||
const std::wstring_view KeyName{ &KeyPath[PathPrefix.length()], KeyPath.length() - PathPrefix.length() };
|
||||
RegistryKey NewKey;
|
||||
NewKey.Name = KeyName;
|
||||
|
||||
RegList.remove_prefix(EndKeyPos + 1);
|
||||
RegList.remove_prefix(Constants::RegFiles::NewLines.length());
|
||||
Result = ValueListToInternal(RegList, NewKey.Values);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Reading key " << PathPrefix << KeyName << L" - Could not read values";
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return Result;
|
||||
}
|
||||
|
||||
if (!RegList.empty())
|
||||
{
|
||||
std::wstring NewPrefix = std::wstring{ KeyPath } + Constants::RegFiles::PathSeparator;
|
||||
Result = RegListToInternal(RegList, NewPrefix, NewKey.Subkeys);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Checking if there are keys beginning with " << NewPrefix;
|
||||
ReportError(E_UNEXPECTED, ErrorMessageStream.str());
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
RegKeys.emplace_back(std::move(NewKey));
|
||||
} while (!RegList.empty());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// non-static function: documented in header.
|
||||
_Must_inspect_result_
|
||||
HRESULT RegfileToInternal
|
||||
(
|
||||
_In_ const std::wstring& RegFilePath,
|
||||
_Out_ RegistryKey& RegKey
|
||||
)
|
||||
{
|
||||
HRESULT Result = E_FAIL;
|
||||
HANDLE InFileHandle = INVALID_HANDLE_VALUE;
|
||||
LARGE_INTEGER FileSize;
|
||||
std::wstring FileContents;
|
||||
std::vector<RegistryKey> KeysInFile;
|
||||
|
||||
InFileHandle = CreateFileW(RegFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (InFileHandle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Opening file " + RegFilePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (!GetFileSizeEx(InFileHandle, &FileSize))
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
ReportError(Result, L"Getting file size of " + RegFilePath);
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (FileSize.HighPart > 0)
|
||||
{
|
||||
Result = E_OUTOFMEMORY;
|
||||
ReportError(Result, L"File " + RegFilePath + L" is too large (larger than 4GB)");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
if (FileSize.LowPart % sizeof(WCHAR) != 0)
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
ReportError(Result, L"File " + RegFilePath + L" should have an even size because it is expected to hold WCHAR code units only");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// resize buffer
|
||||
FileContents.resize(FileSize.LowPart / sizeof(WCHAR), L'\0');
|
||||
|
||||
{
|
||||
DWORD BytesRead = 0;
|
||||
if (!ReadFile(InFileHandle, (PVOID)(&FileContents[0]), FileSize.LowPart, &BytesRead, NULL))
|
||||
{
|
||||
Result = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto Cleanup;
|
||||
}
|
||||
if (BytesRead != FileSize.LowPart)
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
ReportError(Result, L"File " + RegFilePath + L" could not be read to buffer");
|
||||
goto Cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::wstring_view Remainder { FileContents };
|
||||
|
||||
if (Remainder.length() < Constants::RegFiles::Preamble.length() || !std::equal(Constants::RegFiles::Preamble.cbegin(), Constants::RegFiles::Preamble.cend(), FileContents.cbegin()))
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
ReportError(Result, L"File " + RegFilePath + L" preamble not found");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
Remainder.remove_prefix(Constants::RegFiles::Preamble.length());
|
||||
|
||||
Result = RegListToInternal(Remainder, std::wstring{}, KeysInFile);
|
||||
if (FAILED(Result))
|
||||
{
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// at first level, should have exactly one registry key
|
||||
if (KeysInFile.size() != 1)
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
ReportError(Result, L"Multiple root keys were found in the registry file");
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
// whole file should have been slurped.
|
||||
if (Remainder.length() != 0)
|
||||
{
|
||||
Result = E_UNEXPECTED;
|
||||
std::wostringstream ErrorMessageStream;
|
||||
ErrorMessageStream << L"Conversion to internal structure left " << Remainder.length() << L" code units in the file unparsed";
|
||||
ReportError(Result, ErrorMessageStream.str());
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
RegKey = KeysInFile[0];
|
||||
}
|
||||
|
||||
Result = S_OK;
|
||||
|
||||
Cleanup:
|
||||
if (InFileHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(InFileHandle);
|
||||
InFileHandle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
Reference in New Issue
Block a user