diff --git a/example/dllmain.cpp b/example/dllmain.cpp new file mode 100644 index 0000000..fd1a3cc --- /dev/null +++ b/example/dllmain.cpp @@ -0,0 +1,7 @@ +#include "unload.h" + +void main(pimage_data this_image) +{ + MessageBoxA(NULL, "Hello World!", "INFO", NULL); + ZERO_DLL(this_image->image_base, this_image->image_size); +} \ No newline at end of file diff --git a/example/example.vcxproj b/example/example.vcxproj new file mode 100644 index 0000000..127657d --- /dev/null +++ b/example/example.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {f449b39d-4975-4478-b873-2747cc3dd7ef} + helloworld + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;HELLOWORLD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;HELLOWORLD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;HELLOWORLD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;HELLOWORLD_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + stdcpp17 + + + Windows + true + true + true + false + main + + + + + + + + Document + 0 + 3 + + + + + + + + + + \ No newline at end of file diff --git a/example/example.vcxproj.filters b/example/example.vcxproj.filters new file mode 100644 index 0000000..cbee3f0 --- /dev/null +++ b/example/example.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {8715f0df-0970-4661-86c1-bcdca85e5726} + + + + + Source Files + + + + + Source Files + + + + + header + + + \ No newline at end of file diff --git a/example/example.vcxproj.user b/example/example.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/example/example.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/example/unload.asm b/example/unload.asm new file mode 100644 index 0000000..af9e202 --- /dev/null +++ b/example/unload.asm @@ -0,0 +1,8 @@ +.code +; void unload_and_free(void* base, std::size_t size, void* zero_memory, void* return_addr); +unload_and_free proc + push r9 ; return to whatever was calling the function that called this routine. + jmp r8 ; address of zero memory +unload_and_free endp +public unload_and_free +end \ No newline at end of file diff --git a/example/unload.h b/example/unload.h new file mode 100644 index 0000000..21b21cc --- /dev/null +++ b/example/unload.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include + +extern "C" void unload_and_free(void* base, std::size_t size, void* zero_memory, void* return_addr); +#define ZERO_DLL(image_base, image_size) \ + unload_and_free(image_base, image_size, GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlZeroMemory"), _ReturnAddress()); + +typedef struct _image_data +{ + void* image_base; + std::size_t image_size; +}image_data, * pimage_data; \ No newline at end of file diff --git a/inject/inject.vcxproj b/inject/inject.vcxproj new file mode 100644 index 0000000..5bc0aca --- /dev/null +++ b/inject/inject.vcxproj @@ -0,0 +1,151 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {6f7f81b0-5db0-4912-89c6-5b3f534a8ad8} + inject + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/inject/inject.vcxproj.filters b/inject/inject.vcxproj.filters new file mode 100644 index 0000000..7e9cee2 --- /dev/null +++ b/inject/inject.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/inject/inject.vcxproj.user b/inject/inject.vcxproj.user new file mode 100644 index 0000000..d24a191 --- /dev/null +++ b/inject/inject.vcxproj.user @@ -0,0 +1,7 @@ + + + + C:\Users\xerox\source\repos\inject\x64\Release\hello-world.dll + WindowsLocalDebugger + + \ No newline at end of file diff --git a/inject/main.cpp b/inject/main.cpp new file mode 100644 index 0000000..1bece62 --- /dev/null +++ b/inject/main.cpp @@ -0,0 +1,16 @@ +#include +#include "nozzle.hpp" + +int __cdecl main(int argc, char** argv) +{ + if (argc < 2) + { + std::cerr << "[!] please specify a dll path" << std::endl; + return -1; + } + + nozzle::injector test_inject(argv[1], util::get_process_id(L"notepad.exe")); + std::cout << "[+] injected at: " << test_inject.inject() << std::endl; + std::cout << "[+] calling entry point, thread handle: " << test_inject.call_entry() << std::endl; + std::cin.get(); +} diff --git a/inject/nozzle.hpp b/inject/nozzle.hpp new file mode 100644 index 0000000..b196f67 --- /dev/null +++ b/inject/nozzle.hpp @@ -0,0 +1,519 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Dbghelp.lib") +#define FIND_NT_HEADER(x) reinterpret_cast( uint64_t(x) + reinterpret_cast(x)->e_lfanew ) +#define RET_CHK(x)\ +if (!x)\ +{\ +LOG_LAST_ERROR();\ +return false;\ +}\ + +// +// coded by paracord. +// see: https://github.com/haram/splendid_implanter/blob/master/splendid_implanter/win_utils.hpp +// +namespace util +{ + using uq_handle = std::unique_ptr; + inline void open_binary_file(const std::string& file, std::vector& data) + { + std::ifstream fstr(file, std::ios::binary); + fstr.unsetf(std::ios::skipws); + fstr.seekg(0, std::ios::end); + + const auto file_size = fstr.tellg(); + + fstr.seekg(NULL, std::ios::beg); + data.reserve(static_cast(file_size)); + data.insert(data.begin(), std::istream_iterator(fstr), std::istream_iterator()); + } + + inline uint32_t get_process_id(const std::wstring_view process_name) + { + // open a system snapshot of all loaded processes + uq_handle snap_shot{ CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0), &CloseHandle }; + + if (snap_shot.get() == INVALID_HANDLE_VALUE) + return NULL; + + PROCESSENTRY32W process_entry{ sizeof(PROCESSENTRY32W) }; + + // enumerate through processes + for (Process32FirstW(snap_shot.get(), &process_entry); Process32NextW(snap_shot.get(), &process_entry); ) + if (std::wcscmp(process_name.data(), process_entry.szExeFile) == NULL) + return process_entry.th32ProcessID; + + return NULL; + } + + inline std::pair get_module_data(HANDLE process_handle, const std::wstring_view module_name) + { + auto loaded_modules = std::make_unique(64); + DWORD loaded_module_sz = 0; + + // enumerate all modules by handle, using size of 512 since the required size is in bytes, and an HMODULE is 8 bytes large. + if (!EnumProcessModules(process_handle, loaded_modules.get(), 512, &loaded_module_sz)) + return {}; + + for (auto i = 0u; i < loaded_module_sz / 8u; i++) + { + wchar_t file_name[MAX_PATH] = L""; + + // get the full working path for the current module + if (!GetModuleFileNameExW(process_handle, loaded_modules.get()[i], file_name, _countof(file_name))) + continue; + + // module name returned will be a full path, check only for file name sub string. + if (std::wcsstr(file_name, module_name.data()) != nullptr) + return { loaded_modules.get()[i], file_name }; + } + + return {}; + } + + inline std::vector get_file_data(const HANDLE file_handle, const std::wstring_view file_path) + { + const auto file_size = std::filesystem::file_size(file_path); + std::vector file_bytes{}; + file_bytes.resize(file_size); + + DWORD bytes_read = 0; + if (!ReadFile(file_handle, file_bytes.data(), static_cast(file_size), &bytes_read, nullptr)) + return {}; + + return file_bytes; + } + + inline bool enable_privilege(const std::wstring_view privilege_name) + { + HANDLE token_handle = nullptr; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token_handle)) + return false; + + LUID luid{}; + if (!LookupPrivilegeValueW(nullptr, privilege_name.data(), &luid)) + return false; + + TOKEN_PRIVILEGES token_state{}; + token_state.PrivilegeCount = 1; + token_state.Privileges[0].Luid = luid; + token_state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(token_handle, FALSE, &token_state, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr)) + return false; + + CloseHandle(token_handle); + return true; + } +} + +namespace nozzle +{ + // + // class programmed by wlan + // link: https://github.com/not-wlan/drvmap/blob/master/drvmap/drv_image.hpp + // + class pe_image + { + std::vector m_image; + std::vector m_image_mapped; + PIMAGE_DOS_HEADER m_dos_header = nullptr; + PIMAGE_NT_HEADERS64 m_nt_headers = nullptr; + PIMAGE_SECTION_HEADER m_section_header = nullptr; + + public: + pe_image() {}; + pe_image(std::uint8_t* image, std::size_t size); + pe_image(std::vector image); + size_t size() const; + uintptr_t entry_point() const; + void map(); + static bool process_relocation(size_t image_base_delta, uint16_t data, uint8_t* relocation_base); + void relocate(uintptr_t base) const; + + template + __forceinline T* get_rva(const unsigned long offset) + { + return (T*)::ImageRvaToVa(m_nt_headers, m_image.data(), offset, nullptr); + } + + void fix_imports(const std::function get_module, const std::function get_function); + void* data(); + size_t header_size(); + }; + + pe_image::pe_image(std::uint8_t* image, std::size_t size) + { + m_image = std::vector(image, image + size); + m_dos_header = reinterpret_cast(image); + m_nt_headers = reinterpret_cast((uintptr_t)m_dos_header + m_dos_header->e_lfanew); + m_section_header = reinterpret_cast((uintptr_t)(&m_nt_headers->OptionalHeader) + m_nt_headers->FileHeader.SizeOfOptionalHeader); + } + + pe_image::pe_image(std::vector image) + : m_image(std::move(image)) + { + m_dos_header = reinterpret_cast(m_image.data()); + m_nt_headers = reinterpret_cast((uintptr_t)m_dos_header + m_dos_header->e_lfanew); + m_section_header = reinterpret_cast((uintptr_t)(&m_nt_headers->OptionalHeader) + m_nt_headers->FileHeader.SizeOfOptionalHeader); + } + + size_t pe_image::size() const + { + return m_nt_headers->OptionalHeader.SizeOfImage; + } + + uintptr_t pe_image::entry_point() const + { + return m_nt_headers->OptionalHeader.AddressOfEntryPoint; + } + + void pe_image::map() + { + + m_image_mapped.clear(); + m_image_mapped.resize(m_nt_headers->OptionalHeader.SizeOfImage); + std::copy_n(m_image.begin(), m_nt_headers->OptionalHeader.SizeOfHeaders, m_image_mapped.begin()); + + for (size_t i = 0; i < m_nt_headers->FileHeader.NumberOfSections; ++i) + { + const auto& section = m_section_header[i]; + const auto target = (uintptr_t)m_image_mapped.data() + section.VirtualAddress; + const auto source = (uintptr_t)m_dos_header + section.PointerToRawData; + std::copy_n(m_image.begin() + section.PointerToRawData, section.SizeOfRawData, m_image_mapped.begin() + section.VirtualAddress); + } + } + + bool pe_image::process_relocation(uintptr_t image_base_delta, uint16_t data, uint8_t* relocation_base) + { +#define IMR_RELOFFSET(x) (x & 0xFFF) + + switch (data >> 12 & 0xF) + { + case IMAGE_REL_BASED_HIGH: + { + const auto raw_address = reinterpret_cast(relocation_base + IMR_RELOFFSET(data)); + *raw_address += static_cast(HIWORD(image_base_delta)); + break; + } + case IMAGE_REL_BASED_LOW: + { + const auto raw_address = reinterpret_cast(relocation_base + IMR_RELOFFSET(data)); + *raw_address += static_cast(LOWORD(image_base_delta)); + break; + } + case IMAGE_REL_BASED_HIGHLOW: + { + const auto raw_address = reinterpret_cast(relocation_base + IMR_RELOFFSET(data)); + *raw_address += static_cast(image_base_delta); + break; + } + case IMAGE_REL_BASED_DIR64: + { + auto UNALIGNED raw_address = reinterpret_cast(relocation_base + IMR_RELOFFSET(data)); + *raw_address += image_base_delta; + break; + } + case IMAGE_REL_BASED_ABSOLUTE: // No action required + case IMAGE_REL_BASED_HIGHADJ: // no action required + { + break; + } + default: + { + throw std::runtime_error("gay relocation!"); + return false; + } + + } +#undef IMR_RELOFFSET + + return true; + } + + void pe_image::relocate(uintptr_t base) const + { + if (m_nt_headers->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) + return; + + ULONG total_count_bytes; + const auto nt_headers = ImageNtHeader((void*)m_image_mapped.data()); + auto relocation_directory = (PIMAGE_BASE_RELOCATION)::ImageDirectoryEntryToData(nt_headers, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &total_count_bytes); + auto image_base_delta = static_cast(static_cast(base) - (nt_headers->OptionalHeader.ImageBase)); + auto relocation_size = total_count_bytes; + + void* relocation_end = reinterpret_cast(relocation_directory) + relocation_size; + while (relocation_directory < relocation_end) + { + auto relocation_base = ::ImageRvaToVa(nt_headers, (void*)m_image_mapped.data(), relocation_directory->VirtualAddress, nullptr); + auto num_relocs = (relocation_directory->SizeOfBlock - 8) >> 1; + auto relocation_data = reinterpret_cast(relocation_directory + 1); + + for (unsigned long i = 0; i < num_relocs; ++i, ++relocation_data) + { + if (process_relocation(image_base_delta, *relocation_data, (uint8_t*)relocation_base) == FALSE) + return; + } + relocation_directory = reinterpret_cast(relocation_data); + } + } + + template + __forceinline T* ptr_add(void* base, uintptr_t offset) + { + return (T*)(uintptr_t)base + offset; + } + + void pe_image::fix_imports(const std::function get_module, const std::function get_function) + { + + ULONG size; + auto import_descriptors = static_cast(::ImageDirectoryEntryToData(m_image.data(), FALSE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size)); + + if (!import_descriptors) + return; + + for (; import_descriptors->Name; import_descriptors++) + { + IMAGE_THUNK_DATA* image_thunk_data; + const auto module_name = get_rva(import_descriptors->Name); + const auto module_base = get_module(module_name); + + if (import_descriptors->OriginalFirstThunk) + image_thunk_data = get_rva(import_descriptors->OriginalFirstThunk); + else + image_thunk_data = get_rva(import_descriptors->FirstThunk); + + auto image_func_data = get_rva(import_descriptors->FirstThunk); + for (; image_thunk_data->u1.AddressOfData; image_thunk_data++, image_func_data++) + { + uintptr_t function_address; + const auto image_import_by_name = get_rva(*(DWORD*)image_thunk_data); + const auto name_of_import = static_cast(image_import_by_name->Name); + function_address = get_function(module_base, name_of_import); + image_func_data->u1.Function = function_address; + } + } + } + + void* pe_image::data() + { + return m_image_mapped.data(); + } + + size_t pe_image::header_size() + { + return m_nt_headers->OptionalHeader.SizeOfHeaders; + } + + class injector + { + public: + injector() + {}; + injector(void* pe_image, std::size_t size, unsigned pid); + injector(std::vector image_buffer, unsigned pid); + injector(char* path, unsigned pid); + + void* inject(); + HANDLE call_entry(); + void set_target(unsigned pid); + void set_target(std::wstring proc_name); + + void* get_pe_image() const; + void* get_allocated_base() const; + unsigned get_target() const; + private: + pe_image image; + unsigned target_pid; + std::vector image_buffer; + HANDLE target_handle; + void* alloc_base; + + void write(void* addr, void* buffer, std::size_t size); + void read(void* addr, void* buffer, std::size_t size); + + template + T read(void* addr) + { + if (!addr) + return {}; + T buffer; + read(addr, &buffer, sizeof(T)); + return buffer; + } + + template + void write(void* addr, const T& data) + { + if (!addr) + return; + write(addr, (void*)&data, sizeof(T)); + } + }; + + injector::injector(void* pe_image, std::size_t size, unsigned pid) + : + target_pid(pid), + target_handle(::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) + {} + + injector::injector(char* path, unsigned pid) + : + target_pid(pid), + target_handle(::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) + { + std::vector image_buffer; + util::open_binary_file(path, image_buffer); + this->image_buffer = image_buffer; + } + + injector::injector(std::vector image_buffer, unsigned pid) + : + image_buffer(image_buffer), + target_pid(pid), + target_handle(::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) + {} + + void* injector::inject() + { + image = pe_image(image_buffer); + + // + // only resolves globally mapped dll imports. + // + static const auto _get_module = [](std::string_view module_name) -> std::uintptr_t + { + assert( + !stricmp(module_name, "kernel32.dll") || + !stricmp(module_name, "user32.dll") || + !stricmp(module_name, "ntdll.dll"), + "import from dll %s is invalid, only globally mapped dlls can be resolved!", + module_name + ); + return reinterpret_cast(LoadLibraryA(module_name.data())); + }; + + // + // only resolves ntdll.dll, kernel32.dll, and user32.dll imports + // + static const auto _get_function = [](std::uintptr_t module_base, const char* module_name) -> std::uintptr_t + { + return reinterpret_cast(GetProcAddress(reinterpret_cast(module_base), module_name)); + }; + + alloc_base = VirtualAllocEx( + target_handle, + NULL, + image.size(), + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE + ); + + if (!alloc_base) + return NULL; + + image.fix_imports(_get_module, _get_function); + image.map(); + image.relocate(reinterpret_cast(alloc_base)); + write(alloc_base, image.data(), image.size()); + return alloc_base; + } + + HANDLE injector::call_entry() + { + // + // create thread to call entry point. + // + SECURITY_ATTRIBUTES sec_attr{}; + DWORD tid; + + struct image_data + { + void* image_base; + std::size_t image_size; + }; + + const image_data this_image_data{ alloc_base, image.size() }; + const auto remote_param = + VirtualAllocEx( + target_handle, + NULL, + sizeof(image_data), + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE + ); + + write(remote_param, this_image_data); + return CreateRemoteThread( + target_handle, + &sec_attr, + NULL, + (LPTHREAD_START_ROUTINE)(reinterpret_cast(alloc_base) + image.entry_point()), + remote_param, + NULL, + &tid + ); + } + + void* injector::get_allocated_base() const + { + return alloc_base; + } + + void injector::set_target(unsigned pid) + { + target_pid = pid; + } + + void injector::set_target(std::wstring proc_name) + { + target_pid = util::get_process_id(proc_name); + } + + void* injector::get_pe_image() const + { + return (void*)image_buffer.data(); + } + + unsigned injector::get_target() const + { + return target_pid; + } + + void injector::write(void* addr, void* buffer, std::size_t size) + { + SIZE_T bytes_written; + ::WriteProcessMemory( + target_handle, + addr, + buffer, + size, + &bytes_written + ); + } + + void injector::read(void* addr, void* buffer, std::size_t size) + { + SIZE_T bytes_read; + ::ReadProcessMemory( + target_handle, + addr, + buffer, + size, + &bytes_read + ); + } +} \ No newline at end of file diff --git a/nozzle.sln b/nozzle.sln new file mode 100644 index 0000000..1f294d9 --- /dev/null +++ b/nozzle.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inject", "inject\inject.vcxproj", "{6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example\example.vcxproj", "{F449B39D-4975-4478-B873-2747CC3DD7EF}" +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 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Debug|x64.ActiveCfg = Debug|x64 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Debug|x64.Build.0 = Debug|x64 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Debug|x86.ActiveCfg = Debug|Win32 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Debug|x86.Build.0 = Debug|Win32 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Release|x64.ActiveCfg = Release|x64 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Release|x64.Build.0 = Release|x64 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Release|x86.ActiveCfg = Release|Win32 + {6F7F81B0-5DB0-4912-89C6-5B3F534A8AD8}.Release|x86.Build.0 = Release|Win32 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Debug|x64.ActiveCfg = Debug|x64 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Debug|x64.Build.0 = Debug|x64 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Debug|x86.ActiveCfg = Debug|Win32 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Debug|x86.Build.0 = Debug|Win32 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Release|x64.ActiveCfg = Release|x64 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Release|x64.Build.0 = Release|x64 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Release|x86.ActiveCfg = Release|Win32 + {F449B39D-4975-4478-B873-2747CC3DD7EF}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BF43159C-A602-4CEC-AB2B-78F9D51F49D2} + EndGlobalSection +EndGlobal