- fixed blue screen issues with NtShutdownSystem

- kernel_ctx is now singleton, thus allowing many kernel_ctx's.
merge-requests/1/head
xerox 5 years ago
parent f06dfdb370
commit fbce69c77e

@ -28,22 +28,14 @@ For more information, please refer to <http://unlicense.org>
!!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/ */
#include "drv_image.h"
#include <cassert> #include <cassert>
#include <fstream> #include <fstream>
#include "../drv_image/drv_image.h"
namespace physmeme namespace physmeme
{ {
drv_image::drv_image(std::vector<std::uint8_t>& image) drv_image::drv_image(std::vector<uint8_t> image) : m_image(std::move(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<std::uint8_t>(image, image + size);
m_dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(m_image.data()); m_dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(m_image.data());
assert(m_dos_header->e_magic == IMAGE_DOS_SIGNATURE); assert(m_dos_header->e_magic == IMAGE_DOS_SIGNATURE);
m_nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS64>((uintptr_t)m_dos_header + m_dos_header->e_lfanew); m_nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS64>((uintptr_t)m_dos_header + m_dos_header->e_lfanew);
@ -74,9 +66,9 @@ namespace physmeme
const auto target = (uintptr_t)m_image_mapped.data() + section.VirtualAddress; const auto target = (uintptr_t)m_image_mapped.data() + section.VirtualAddress;
const auto source = (uintptr_t)m_dos_header + section.PointerToRawData; 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); 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", &section.Name[0], (void*)source, (void*)target, section.SizeOfRawData); if constexpr(physmeme_debugging)
#endif printf("[+] copying [%s] 0x%p -> 0x%p [0x%04X]\n", &section.Name[0], (void*)source, (void*)target, section.SizeOfRawData);
} }
} }
@ -127,7 +119,7 @@ namespace physmeme
} }
void drv_image::relocate(uintptr_t base) const void drv_image::relocate(void* base) const
{ {
if (m_nt_headers->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) if (m_nt_headers->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
return; return;
@ -135,7 +127,7 @@ namespace physmeme
ULONG total_count_bytes; ULONG total_count_bytes;
const auto nt_headers = ImageNtHeader((void*)m_image_mapped.data()); 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 relocation_directory = (PIMAGE_BASE_RELOCATION)::ImageDirectoryEntryToData(nt_headers, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &total_count_bytes);
auto image_base_delta = static_cast<uintptr_t>(static_cast<uintptr_t>(base) - (nt_headers->OptionalHeader.ImageBase)); auto image_base_delta = static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(base) - (nt_headers->OptionalHeader.ImageBase));
auto relocation_size = total_count_bytes; 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 // This should check (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) too but lots of drivers do not have it set due to WDK defaults
@ -143,9 +135,8 @@ namespace physmeme
if (!doRelocations) if (!doRelocations)
{ {
#if PHYSMEME_DEBUGGING true if constexpr (physmeme_debugging)
printf("no relocations needed\n"); printf("[+] no relocations needed\n");
#endif
return; return;
} }
@ -165,9 +156,8 @@ namespace physmeme
{ {
if (process_relocation(image_base_delta, *relocation_data, (uint8_t*)relocation_base) == FALSE) if (process_relocation(image_base_delta, *relocation_data, (uint8_t*)relocation_base) == FALSE)
{ {
#if PHYSMEME_DEBUGGING true if constexpr (physmeme_debugging)
printf("failed to relocate!"); printf("[+] failed to relocate!");
#endif
return; return;
} }
} }
@ -190,9 +180,8 @@ namespace physmeme
if (import_descriptors == nullptr) if (import_descriptors == nullptr)
{ {
#if PHYSMEME_DEBUGGING true if constexpr (physmeme_debugging)
printf("no imports!\n"); printf("[+] no imports!\n");
#endif
return; return;
} }
@ -203,9 +192,10 @@ namespace physmeme
const auto module_name = get_rva<char>(import_descriptors->Name); const auto module_name = get_rva<char>(import_descriptors->Name);
const auto module_base = get_module(module_name); const auto module_base = get_module(module_name);
assert(module_base != 0); assert(module_base != 0);
#if PHYSMEME_DEBUGGING true
printf("processing module: %s [0x%I64X]\n", module_name, module_base); if constexpr (physmeme_debugging)
#endif printf("[+] processing module: %s [0x%I64X]\n", module_name, module_base);
if (import_descriptors->OriginalFirstThunk) if (import_descriptors->OriginalFirstThunk)
image_thunk_data = get_rva<IMAGE_THUNK_DATA>(import_descriptors->OriginalFirstThunk); image_thunk_data = get_rva<IMAGE_THUNK_DATA>(import_descriptors->OriginalFirstThunk);
else else
@ -222,9 +212,9 @@ namespace physmeme
const auto image_import_by_name = get_rva<IMAGE_IMPORT_BY_NAME>(*(DWORD*)image_thunk_data); const auto image_import_by_name = get_rva<IMAGE_IMPORT_BY_NAME>(*(DWORD*)image_thunk_data);
const auto name_of_import = static_cast<char*>(image_import_by_name->Name); const auto name_of_import = static_cast<char*>(image_import_by_name->Name);
function_address = get_function(module_name, name_of_import); function_address = get_function(module_name, name_of_import);
#if PHYSMEME_DEBUGGING true
printf("function: %s [0x%I64X]\n", name_of_import, function_address); if constexpr (physmeme_debugging)
#endif printf("[+] function: %s [0x%I64X]\n", name_of_import, function_address);
assert(function_address != 0); assert(function_address != 0);
image_func_data->u1.Function = function_address; image_func_data->u1.Function = function_address;
} }

@ -40,9 +40,7 @@ For more information, please refer to <http://unlicense.org>
#include <functional> #include <functional>
#include <DbgHelp.h> #include <DbgHelp.h>
#include <variant> #include <variant>
#include "../util/nt.hpp"
#define PHYSMEME_DEBUGGING 1
#pragma comment(lib, "Dbghelp.lib") #pragma comment(lib, "Dbghelp.lib")
namespace physmeme namespace physmeme
@ -56,13 +54,12 @@ namespace physmeme
PIMAGE_SECTION_HEADER m_section_header = nullptr; PIMAGE_SECTION_HEADER m_section_header = nullptr;
public: public:
drv_image(std::uint8_t* image, std::size_t size); explicit drv_image(std::vector<uint8_t> image);
drv_image(std::vector<std::uint8_t>& image);
size_t size() const; size_t size() const;
uintptr_t entry_point() const; uintptr_t entry_point() const;
void map(); void map();
static bool process_relocation(size_t image_base_delta, uint16_t data, uint8_t* relocation_base); static bool process_relocation(size_t image_base_delta, uint16_t data, uint8_t* relocation_base);
void relocate(uintptr_t base) const; void relocate(void* base) const;
template<typename T> template<typename T>
__forceinline T* get_rva(const unsigned long offset) __forceinline T* get_rva(const unsigned long offset)
@ -70,7 +67,10 @@ namespace physmeme
return (T*)::ImageRvaToVa(m_nt_headers, m_image.data(), offset, nullptr); return (T*)::ImageRvaToVa(m_nt_headers, m_image.data(), offset, nullptr);
} }
void fix_imports(const std::function<uintptr_t(std::string_view)> get_module, const std::function<uintptr_t(const char*, const char*)> get_function); void fix_imports(
const std::function<uintptr_t(std::string_view)> get_module,
const std::function<uintptr_t(const char*, const char*)> get_function
);
void* data(); void* data();
size_t header_size(); size_t header_size();
}; };

@ -2,13 +2,11 @@
namespace physmeme namespace physmeme
{ {
/*
Author: xerox
Date: 4/19/2020
*/
kernel_ctx::kernel_ctx() kernel_ctx::kernel_ctx()
: psyscall_func(NULL), ntoskrnl_buffer(NULL)
{ {
if (psyscall_func.load() || nt_page_offset || ntoskrnl_buffer)
return;
nt_rva = reinterpret_cast<std::uint32_t>( nt_rva = reinterpret_cast<std::uint32_t>(
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
@ -16,16 +14,27 @@ namespace physmeme
true true
)); ));
nt_page_offset = nt_rva % 0x1000; nt_page_offset = nt_rva % page_size;
ntoskrnl_buffer = reinterpret_cast<std::uint8_t*>(LoadLibraryA("C:\\Windows\\System32\\ntoskrnl.exe")); ntoskrnl_buffer = reinterpret_cast<std::uint8_t*>(
LoadLibraryA(ntoskrnl_path)
);
#if PHYSMEME_DEBUGGING if constexpr (physmeme_debugging)
std::cout << "[+] page offset of " << syscall_hook.first << " is: " << std::hex << nt_page_offset << std::endl; {
#endif printf("[+] page offset of %s is 0x%llx\n", syscall_hook.first.data(), nt_page_offset);
printf("[+] ntoskrnl_buffer: 0x%p\n", ntoskrnl_buffer);
}
if (!ntoskrnl_buffer || !nt_rva)
{
if constexpr (physmeme_debugging)
printf("[!] ntoskrnl_buffer was 0x%p, nt_rva was 0x%p\n", ntoskrnl_buffer, nt_rva);
return;
}
std::vector<std::thread> search_threads; std::vector<std::thread> search_threads;
//--- for each physical memory range, make a thread to search it //--- for each physical memory range, make a thread to search it
for (auto ranges : pmem_ranges) for (auto ranges : util::pmem_ranges)
search_threads.emplace_back(std::thread( search_threads.emplace_back(std::thread(
&kernel_ctx::map_syscall, &kernel_ctx::map_syscall,
this, this,
@ -36,17 +45,10 @@ namespace physmeme
for (std::thread& search_thread : search_threads) for (std::thread& search_thread : search_threads)
search_thread.join(); search_thread.join();
#if PHYSMEME_DEBUGGING if constexpr (physmeme_debugging)
std::cout << "[+] psyscall_func: " << std::hex << std::showbase << psyscall_func.load() << std::endl; printf("[+] psyscall_func: 0x%p\n", psyscall_func.load());
#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 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 the physical memory range is less then or equal to 2mb
@ -57,7 +59,7 @@ namespace physmeme
{ {
// scan every page of the physical memory range // scan every page of the physical memory range
for (auto page = page_va; page < page_va + end; page += 0x1000) for (auto page = page_va; page < page_va + end; page += 0x1000)
if (!psyscall_func) // keep scanning until its found if (!psyscall_func.load()) // keep scanning until its found
if (!memcmp(reinterpret_cast<void*>(page), ntoskrnl_buffer + nt_rva, 32)) if (!memcmp(reinterpret_cast<void*>(page), ntoskrnl_buffer + nt_rva, 32))
{ {
psyscall_func.store((void*)page); psyscall_func.store((void*)page);
@ -106,12 +108,6 @@ namespace physmeme
} }
} }
/*
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) void* kernel_ctx::allocate_pool(std::size_t size, POOL_TYPE pool_type)
{ {
static const auto ex_alloc_pool = static const auto ex_alloc_pool =
@ -119,17 +115,14 @@ namespace physmeme
"ntoskrnl.exe", "ntoskrnl.exe",
"ExAllocatePool" "ExAllocatePool"
); );
if (ex_alloc_pool)
return syscall<ExAllocatePool>(ex_alloc_pool, pool_type, size);
return NULL;
}
/* return syscall<ExAllocatePool>(
Author: xerox ex_alloc_pool,
Date: 4/19/2020 pool_type,
size
);
}
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) void* kernel_ctx::allocate_pool(std::size_t size, ULONG pool_tag, POOL_TYPE pool_type)
{ {
static const auto ex_alloc_pool_with_tag = static const auto ex_alloc_pool_with_tag =
@ -137,76 +130,58 @@ namespace physmeme
"ntoskrnl.exe", "ntoskrnl.exe",
"ExAllocatePoolWithTag" "ExAllocatePoolWithTag"
); );
if (ex_alloc_pool_with_tag)
return syscall<ExAllocatePoolWithTag>(ex_alloc_pool_with_tag, pool_type, size, pool_tag);
}
/* return syscall<ExAllocatePoolWithTag>(
Author: xerox ex_alloc_pool_with_tag,
Date: 4/19/2020 pool_type,
size,
pool_tag
);
}
read kernel memory void kernel_ctx::read_kernel(void* addr, void* buffer, std::size_t size)
*/
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 = static const auto mm_copy_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"MmCopyMemory" "RtlCopyMemory"
); );
if (mm_copy_memory)
syscall<MmCopyMemory>(
mm_copy_memory,
reinterpret_cast<void*>(buffer),
MM_COPY_ADDRESS{ (void*)addr },
size,
MM_COPY_MEMORY_VIRTUAL,
&amount_copied
);
}
/* syscall<decltype(&memcpy)>(
Author: xerox mm_copy_memory,
Date: 4/19/2020 buffer,
addr,
size
);
}
write kernel memory, this doesnt write to read only memory! void kernel_ctx::write_kernel(void* addr, void* buffer, std::size_t size)
*/
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 = static const auto mm_copy_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"MmCopyMemory" "RtlCopyMemory"
); );
if (mm_copy_memory)
syscall<MmCopyMemory>(
mm_copy_memory,
reinterpret_cast<void*>(addr),
MM_COPY_ADDRESS{ buffer },
size,
MM_COPY_MEMORY_VIRTUAL,
&amount_copied
);
}
/* syscall<decltype(&memcpy)>(
Author: xerox mm_copy_memory,
Date: 4/19/2020 addr,
buffer,
size
);
}
zero driver header void kernel_ctx::zero_kernel_memory(void* addr, std::size_t size)
*/
void kernel_ctx::zero_kernel_memory(std::uintptr_t addr, std::size_t size)
{ {
static const auto rtl_zero_memory = static const auto rtl_zero_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"RtlZeroMemory" "RtlZeroMemory"
); );
syscall<decltype(&RtlSecureZeroMemory)>( syscall<decltype(&RtlSecureZeroMemory)>(
rtl_zero_memory, rtl_zero_memory,
reinterpret_cast<void*>(addr), addr,
size size
); );
} }

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <windows.h> #include <windows.h>
#include <iostream>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <thread> #include <thread>
@ -9,32 +10,69 @@
#include "../physmeme/physmeme.hpp" #include "../physmeme/physmeme.hpp"
#include "../util/hook.hpp" #include "../util/hook.hpp"
#if PHYSMEME_DEBUGGING
#include <iostream>
#endif
/*
Author: xerox
Date: 4/19/2020
this namespace contains everything needed to interface with the kernel
*/
namespace physmeme namespace physmeme
{ {
//
// offset of function into a physical page
// used for comparing bytes when searching
//
inline std::uint16_t nt_page_offset{};
//
// rva of nt function we are going to hook
//
inline std::uint32_t nt_rva{};
//
// base address of ntoskrnl (inside of this process)
//
inline const std::uint8_t* ntoskrnl_buffer{};
//
// mapping of a syscalls physical memory (for installing hooks)
//
inline std::atomic<void*> psyscall_func{};
//
// you can edit this how you choose, im hooking NtShutdownSystem.
//
inline const std::pair<std::string_view, std::string_view> syscall_hook = { "NtShutdownSystem", "ntdll.dll" };
class kernel_ctx class kernel_ctx
{ {
public: public:
//
// default constructor
//
kernel_ctx(); kernel_ctx();
//
// allocate kernel pool of desired size and type
//
void* allocate_pool(std::size_t size, POOL_TYPE pool_type = NonPagedPool); void* allocate_pool(std::size_t size, POOL_TYPE pool_type = NonPagedPool);
//
// allocate kernel pool of size, pool tag, and type
//
void* allocate_pool(std::size_t size, ULONG pool_tag = 'MEME', 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); // read kernel memory with RtlCopyMemory
//
void read_kernel(void* addr, void* buffer, std::size_t size);
void zero_kernel_memory(std::uintptr_t addr, std::size_t size); //
// write kernel memory with RtlCopyMemory
//
void write_kernel(void* addr, void* buffer, std::size_t size);
//
// zero kernel memory using RtlZeroMemory
//
void zero_kernel_memory(void* addr, std::size_t size);
template <class T> template <class T>
T read_kernel(std::uintptr_t addr) T read_kernel(void* addr)
{ {
if (!addr) if (!addr)
return {}; return {};
@ -44,7 +82,7 @@ namespace physmeme
} }
template <class T> template <class T>
void write_kernel(std::uintptr_t addr, const T& data) void write_kernel(void* addr, const T& data)
{ {
if (!addr) if (!addr)
return {}; return {};
@ -55,14 +93,19 @@ namespace physmeme
// use this to call any function in the kernel // use this to call any function in the kernel
// //
template <class T, class ... Ts> template <class T, class ... Ts>
PVOID syscall(void* addr, Ts ... args) std::invoke_result_t<T, Ts...> syscall(void* addr, Ts ... args)
{ {
auto proc = GetProcAddress(GetModuleHandleA("ntdll.dll"), syscall_hook.first.data()); static const auto proc =
GetProcAddress(
GetModuleHandleA("ntdll.dll"),
syscall_hook.first.data()
);
if (!proc || !psyscall_func || !addr) if (!proc || !psyscall_func || !addr)
return reinterpret_cast<PVOID>(STATUS_INVALID_PARAMETER); return {};
hook::make_hook(psyscall_func, addr); hook::make_hook(psyscall_func, addr);
PVOID result = reinterpret_cast<PVOID>(reinterpret_cast<T>(proc)(args ...)); auto result = reinterpret_cast<T>(proc)(args ...);
hook::remove(psyscall_func); hook::remove(psyscall_func);
return result; return result;
} }
@ -73,31 +116,5 @@ namespace physmeme
// find and map the physical page of a syscall into this process // find and map the physical page of a syscall into this process
// //
void map_syscall(std::uintptr_t begin, std::uintptr_t end) const; void map_syscall(std::uintptr_t begin, std::uintptr_t end) const;
//
// mapping of a syscalls physical memory (for installing hooks)
//
mutable std::atomic<void*> psyscall_func;
//
// you can edit this how you choose, im hooking NtShutdownSystem.
//
const std::pair<std::string_view, std::string_view> syscall_hook = { "NtShutdownSystem", "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;
}; };
} }

@ -16,6 +16,23 @@ namespace physmeme
physmeme::drv_image image(raw_driver); physmeme::drv_image image(raw_driver);
physmeme::kernel_ctx ctx; physmeme::kernel_ctx ctx;
//
// we dont need the driver loaded anymore
//
physmeme::unload_drv();
//
// allocate memory in the kernel for the driver
//
const auto pool_base = ctx.allocate_pool(image.size(), NonPagedPool);
printf("[+] allocated 0x%llx at 0x%p\n", image.size(), pool_base);
if (!pool_base)
{
printf("[!] allocation failed!\n");
return -1;
}
// //
// lambdas used for fixing driver image // lambdas used for fixing driver image
// //
@ -29,70 +46,42 @@ namespace physmeme
return reinterpret_cast<std::uintptr_t>(util::get_module_export(base, name)); return reinterpret_cast<std::uintptr_t>(util::get_module_export(base, name));
}; };
//
// allocate memory in the kernel for the driver
//
std::uintptr_t pool_base = reinterpret_cast<std::uintptr_t>(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
if (!pool_base)
return false;
// //
// fix the driver image // fix the driver image
// //
image.fix_imports(_get_module, _get_export_name); image.fix_imports(_get_module, _get_export_name);
#if PHYSMEME_DEBUGGING true printf("[+] fixed imports\n");
std::cout << "[+] fixed imports" << std::endl;
#endif
image.map(); image.map();
#if PHYSMEME_DEBUGGING true printf("[+] sections mapped in memory\n");
std::cout << "[+] sections mapped in memory" << std::endl;
#endif
image.relocate(pool_base); image.relocate(pool_base);
#if PHYSMEME_DEBUGGING true printf("[+] relocations fixed\n");
std::cout << "[+] relocations fixed" << std::endl;
#endif
// //
// copy driver into the kernel // 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()); ctx.write_kernel(pool_base, image.data(), image.size());
// //
// driver entry params // driver entry
// //
auto entry_point = pool_base + image.entry_point(); auto entry_point = reinterpret_cast<std::uintptr_t>(pool_base) + image.entry_point();
auto size = image.size();
// //
// call driver entry // call driver entry
// //
auto result = ctx.syscall<DRIVER_INITIALIZE>(reinterpret_cast<void*>(entry_point), pool_base, image.size()); auto result = ctx.syscall<DRIVER_INITIALIZE>(
#if PHYSMEME_DEBUGGING true reinterpret_cast<void*>(entry_point),
std::cout << "[+] driver entry returned: " << std::hex << result << std::endl; reinterpret_cast<std::uintptr_t>(pool_base),
#endif image.size()
);
printf("[+] driver entry returned: 0x%p\n", result);
// //
// zero header of driver // zero driver headers
// //
ctx.zero_kernel_memory(pool_base, image.header_size()); 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();
return !result; // 0x0 means STATUS_SUCCESS return !result; // 0x0 means STATUS_SUCCESS
} }

@ -90,7 +90,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -105,7 +105,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -120,7 +120,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
@ -140,7 +140,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>

@ -4,53 +4,20 @@
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include "../util/util.hpp"
namespace physmeme namespace physmeme
{ {
//--- ranges of physical memory //
static std::map<std::uintptr_t, std::size_t> pmem_ranges; // please code this function depending on your method of physical read/write.
//
//--- 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( static std::uintptr_t map_phys(
std::uintptr_t addr, std::uintptr_t addr,
std::size_t size std::size_t size
) )
{ {
//--- ensure the validity of the address we are going to try and map //--- ensure the validity of the address we are going to try and map
if (!is_valid(addr)) if (!util::is_valid(addr))
return NULL; return NULL;
static const auto map_phys_ptr = static const auto map_phys_ptr =
@ -59,9 +26,9 @@ namespace physmeme
return map_phys_ptr ? map_phys_ptr(addr, size) : false; return map_phys_ptr ? map_phys_ptr(addr, size) : false;
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static bool unmap_phys( static bool unmap_phys(
std::uintptr_t addr, std::uintptr_t addr,
std::size_t size std::size_t size
@ -73,9 +40,9 @@ namespace physmeme
return unmap_phys_ptr ? unmap_phys_ptr(addr, size) : false; return unmap_phys_ptr ? unmap_phys_ptr(addr, size) : false;
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static HANDLE load_drv() static HANDLE load_drv()
{ {
static const auto load_driver_ptr = static const auto load_driver_ptr =
@ -89,9 +56,9 @@ namespace physmeme
return CreateFileA("\\\\.\\PhyMem", 0xC0000000, 3u, 0i64, 3u, 0x80u, 0i64); return CreateFileA("\\\\.\\PhyMem", 0xC0000000, 3u, 0i64, 3u, 0x80u, 0i64);
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static bool unload_drv() static bool unload_drv()
{ {
static const auto unload_driver_ptr = static const auto unload_driver_ptr =
@ -100,5 +67,5 @@ namespace physmeme
return unload_driver_ptr ? unload_driver_ptr() : false; return unload_driver_ptr ? unload_driver_ptr() : false;
} }
inline HANDLE drv_handle = load_drv(); static HANDLE drv_handle = load_drv();
} }

@ -3,17 +3,18 @@
#include <winternl.h> #include <winternl.h>
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ntdll.lib")
#define MM_COPY_MEMORY_PHYSICAL 0x1 constexpr bool physmeme_debugging = true;
#define MM_COPY_MEMORY_VIRTUAL 0x2 constexpr auto ntoskrnl_path = "C:\\Windows\\System32\\ntoskrnl.exe";
#define PHYSMEME_DEBUGGING 1 constexpr auto page_size = 0x1000;
constexpr auto PAGE_SIZE = 0x1000;
constexpr auto STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
constexpr auto SystemModuleInformation = 11; constexpr auto SystemModuleInformation = 11;
constexpr auto SystemHandleInformation = 16; constexpr auto SystemHandleInformation = 16;
constexpr auto SystemExtendedHandleInformation = 64; constexpr auto SystemExtendedHandleInformation = 64;
#define MM_COPY_MEMORY_PHYSICAL 0x1
#define MM_COPY_MEMORY_VIRTUAL 0x2
typedef struct _SYSTEM_HANDLE typedef struct _SYSTEM_HANDLE
{ {
PVOID Object; PVOID Object;

@ -1,17 +1,54 @@
#pragma once
#include <Windows.h> #include <Windows.h>
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
#include <iterator> #include <iterator>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <map>
#include <vector> #include <vector>
#include <ntstatus.h>
#include <winternl.h>
#include "nt.hpp" #include "nt.hpp"
/*
This code stinks, its the worst code in this project and needs to be recoded in a later time.
*/
namespace util namespace util
{ {
//--- ranges of physical memory
static std::map<std::uintptr_t, std::size_t> 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;
})();
// this was taken from wlan's drvmapper: // this was taken from wlan's drvmapper:
// https://github.com/not-wlan/drvmap/blob/98d93cc7b5ec17875f815a9cb94e6d137b4047ee/drvmap/util.cpp#L7 // https://github.com/not-wlan/drvmap/blob/98d93cc7b5ec17875f815a9cb94e6d137b4047ee/drvmap/util.cpp#L7
static void open_binary_file(const std::string& file, std::vector<uint8_t>& data) static void open_binary_file(const std::string& file, std::vector<uint8_t>& data)

@ -0,0 +1,11 @@
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.pdb
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\map_driver.obj
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\kernel_ctx.obj
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\drv_image.obj
c:\users\interesting\desktop\physmeme-master\x64\release\physmeme-lib.lib
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\cl.command.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\cl.read.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\cl.write.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\lib-link.read.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\lib-link.write.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme-lib\x64\release\physmeme-lib.tlog\lib.command.1.tlog

@ -0,0 +1,4 @@
 drv_image.cpp
kernel_ctx.cpp
map_driver.cpp
physmeme-lib.vcxproj -> C:\Users\interesting\Desktop\physmeme-master\x64\Release\physmeme-lib.lib

@ -0,0 +1,2 @@
PlatformToolSet=v142:VCToolArchitecture=Native32Bit:VCToolsVersion=14.25.28610:TargetPlatformVersion=10.0.18362.0:
Release|x64|C:\Users\interesting\Desktop\physmeme-master\|

@ -28,10 +28,9 @@ For more information, please refer to <http://unlicense.org>
!!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!! This code was created by not-wlan (wlan). all credit for this header and source file goes to him !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/ */
#include "drv_image.h"
#include <cassert> #include <cassert>
#include <fstream> #include <fstream>
#include "../drv_image/drv_image.h"
namespace physmeme namespace physmeme
{ {
@ -67,7 +66,9 @@ namespace physmeme
const auto target = (uintptr_t)m_image_mapped.data() + section.VirtualAddress; const auto target = (uintptr_t)m_image_mapped.data() + section.VirtualAddress;
const auto source = (uintptr_t)m_dos_header + section.PointerToRawData; 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); std::copy_n(m_image.begin() + section.PointerToRawData, section.SizeOfRawData, m_image_mapped.begin() + section.VirtualAddress);
printf("copying [%s] 0x%p -> 0x%p [0x%04X]\n", &section.Name[0], (void*)source, (void*)target, section.SizeOfRawData);
if constexpr(physmeme_debugging)
printf("[+] copying [%s] 0x%p -> 0x%p [0x%04X]\n", &section.Name[0], (void*)source, (void*)target, section.SizeOfRawData);
} }
} }
@ -108,7 +109,6 @@ namespace physmeme
} }
default: default:
{ {
throw std::runtime_error("gay relocation!");
return false; return false;
} }
@ -119,7 +119,7 @@ namespace physmeme
} }
void drv_image::relocate(uintptr_t base) const void drv_image::relocate(void* base) const
{ {
if (m_nt_headers->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) if (m_nt_headers->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
return; return;
@ -127,7 +127,7 @@ namespace physmeme
ULONG total_count_bytes; ULONG total_count_bytes;
const auto nt_headers = ImageNtHeader((void*)m_image_mapped.data()); 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 relocation_directory = (PIMAGE_BASE_RELOCATION)::ImageDirectoryEntryToData(nt_headers, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &total_count_bytes);
auto image_base_delta = static_cast<uintptr_t>(static_cast<uintptr_t>(base) - (nt_headers->OptionalHeader.ImageBase)); auto image_base_delta = static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(base) - (nt_headers->OptionalHeader.ImageBase));
auto relocation_size = total_count_bytes; 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 // This should check (DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) too but lots of drivers do not have it set due to WDK defaults
@ -135,7 +135,8 @@ namespace physmeme
if (!doRelocations) if (!doRelocations)
{ {
printf("no relocations needed\n"); if constexpr (physmeme_debugging)
printf("[+] no relocations needed\n");
return; return;
} }
@ -155,7 +156,8 @@ namespace physmeme
{ {
if (process_relocation(image_base_delta, *relocation_data, (uint8_t*)relocation_base) == FALSE) if (process_relocation(image_base_delta, *relocation_data, (uint8_t*)relocation_base) == FALSE)
{ {
printf("failed to relocate!"); if constexpr (physmeme_debugging)
printf("[+] failed to relocate!");
return; return;
} }
} }
@ -178,7 +180,8 @@ namespace physmeme
if (import_descriptors == nullptr) if (import_descriptors == nullptr)
{ {
printf("no imports!\n"); if constexpr (physmeme_debugging)
printf("[+] no imports!\n");
return; return;
} }
@ -190,7 +193,8 @@ namespace physmeme
const auto module_base = get_module(module_name); const auto module_base = get_module(module_name);
assert(module_base != 0); assert(module_base != 0);
printf("processing module: %s [0x%I64X]\n", module_name, module_base); if constexpr (physmeme_debugging)
printf("[+] processing module: %s [0x%I64X]\n", module_name, module_base);
if (import_descriptors->OriginalFirstThunk) if (import_descriptors->OriginalFirstThunk)
image_thunk_data = get_rva<IMAGE_THUNK_DATA>(import_descriptors->OriginalFirstThunk); image_thunk_data = get_rva<IMAGE_THUNK_DATA>(import_descriptors->OriginalFirstThunk);
@ -208,7 +212,9 @@ namespace physmeme
const auto image_import_by_name = get_rva<IMAGE_IMPORT_BY_NAME>(*(DWORD*)image_thunk_data); const auto image_import_by_name = get_rva<IMAGE_IMPORT_BY_NAME>(*(DWORD*)image_thunk_data);
const auto name_of_import = static_cast<char*>(image_import_by_name->Name); const auto name_of_import = static_cast<char*>(image_import_by_name->Name);
function_address = get_function(module_name, name_of_import); function_address = get_function(module_name, name_of_import);
printf("function: %s [0x%I64X]\n", name_of_import, function_address);
if constexpr (physmeme_debugging)
printf("[+] function: %s [0x%I64X]\n", name_of_import, function_address);
assert(function_address != 0); assert(function_address != 0);
image_func_data->u1.Function = function_address; image_func_data->u1.Function = function_address;
} }

@ -40,6 +40,7 @@ For more information, please refer to <http://unlicense.org>
#include <functional> #include <functional>
#include <DbgHelp.h> #include <DbgHelp.h>
#include <variant> #include <variant>
#include "../util/nt.hpp"
#pragma comment(lib, "Dbghelp.lib") #pragma comment(lib, "Dbghelp.lib")
namespace physmeme namespace physmeme
@ -58,7 +59,7 @@ namespace physmeme
uintptr_t entry_point() const; uintptr_t entry_point() const;
void map(); void map();
static bool process_relocation(size_t image_base_delta, uint16_t data, uint8_t* relocation_base); static bool process_relocation(size_t image_base_delta, uint16_t data, uint8_t* relocation_base);
void relocate(uintptr_t base) const; void relocate(void* base) const;
template<typename T> template<typename T>
__forceinline T* get_rva(const unsigned long offset) __forceinline T* get_rva(const unsigned long offset)
@ -66,7 +67,10 @@ namespace physmeme
return (T*)::ImageRvaToVa(m_nt_headers, m_image.data(), offset, nullptr); return (T*)::ImageRvaToVa(m_nt_headers, m_image.data(), offset, nullptr);
} }
void fix_imports(const std::function<uintptr_t(std::string_view)> get_module, const std::function<uintptr_t(const char*, const char*)> get_function); void fix_imports(
const std::function<uintptr_t(std::string_view)> get_module,
const std::function<uintptr_t(const char*, const char*)> get_function
);
void* data(); void* data();
size_t header_size(); size_t header_size();
}; };

@ -2,13 +2,11 @@
namespace physmeme namespace physmeme
{ {
/*
Author: xerox
Date: 4/19/2020
*/
kernel_ctx::kernel_ctx() kernel_ctx::kernel_ctx()
: psyscall_func(NULL), ntoskrnl_buffer(NULL)
{ {
if (psyscall_func.load() || nt_page_offset || ntoskrnl_buffer)
return;
nt_rva = reinterpret_cast<std::uint32_t>( nt_rva = reinterpret_cast<std::uint32_t>(
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
@ -16,16 +14,27 @@ namespace physmeme
true true
)); ));
nt_page_offset = nt_rva % 0x1000; nt_page_offset = nt_rva % page_size;
ntoskrnl_buffer = reinterpret_cast<std::uint8_t*>(LoadLibraryA("C:\\Windows\\System32\\ntoskrnl.exe")); ntoskrnl_buffer = reinterpret_cast<std::uint8_t*>(
LoadLibraryA(ntoskrnl_path)
);
#if PHYSMEME_DEBUGGING if constexpr (physmeme_debugging)
std::cout << "[+] page offset of " << syscall_hook.first << " is: " << std::hex << nt_page_offset << std::endl; {
#endif printf("[+] page offset of %s is 0x%llx\n", syscall_hook.first.data(), nt_page_offset);
printf("[+] ntoskrnl_buffer: 0x%p\n", ntoskrnl_buffer);
}
if (!ntoskrnl_buffer || !nt_rva)
{
if constexpr (physmeme_debugging)
printf("[!] ntoskrnl_buffer was 0x%p, nt_rva was 0x%p\n", ntoskrnl_buffer, nt_rva);
return;
}
std::vector<std::thread> search_threads; std::vector<std::thread> search_threads;
//--- for each physical memory range, make a thread to search it //--- for each physical memory range, make a thread to search it
for (auto ranges : pmem_ranges) for (auto ranges : util::pmem_ranges)
search_threads.emplace_back(std::thread( search_threads.emplace_back(std::thread(
&kernel_ctx::map_syscall, &kernel_ctx::map_syscall,
this, this,
@ -36,17 +45,10 @@ namespace physmeme
for (std::thread& search_thread : search_threads) for (std::thread& search_thread : search_threads)
search_thread.join(); search_thread.join();
#if PHYSMEME_DEBUGGING if constexpr (physmeme_debugging)
std::cout << "[+] psyscall_func: " << std::hex << std::showbase << psyscall_func.load() << std::endl; printf("[+] psyscall_func: 0x%p\n", psyscall_func.load());
#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 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 the physical memory range is less then or equal to 2mb
@ -57,7 +59,7 @@ namespace physmeme
{ {
// scan every page of the physical memory range // scan every page of the physical memory range
for (auto page = page_va; page < page_va + end; page += 0x1000) for (auto page = page_va; page < page_va + end; page += 0x1000)
if (!psyscall_func) // keep scanning until its found if (!psyscall_func.load()) // keep scanning until its found
if (!memcmp(reinterpret_cast<void*>(page), ntoskrnl_buffer + nt_rva, 32)) if (!memcmp(reinterpret_cast<void*>(page), ntoskrnl_buffer + nt_rva, 32))
{ {
psyscall_func.store((void*)page); psyscall_func.store((void*)page);
@ -106,12 +108,6 @@ namespace physmeme
} }
} }
/*
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) void* kernel_ctx::allocate_pool(std::size_t size, POOL_TYPE pool_type)
{ {
static const auto ex_alloc_pool = static const auto ex_alloc_pool =
@ -119,17 +115,14 @@ namespace physmeme
"ntoskrnl.exe", "ntoskrnl.exe",
"ExAllocatePool" "ExAllocatePool"
); );
if (ex_alloc_pool)
return syscall<ExAllocatePool>(ex_alloc_pool, pool_type, size);
return NULL;
}
/* return syscall<ExAllocatePool>(
Author: xerox ex_alloc_pool,
Date: 4/19/2020 pool_type,
size
);
}
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) void* kernel_ctx::allocate_pool(std::size_t size, ULONG pool_tag, POOL_TYPE pool_type)
{ {
static const auto ex_alloc_pool_with_tag = static const auto ex_alloc_pool_with_tag =
@ -137,76 +130,58 @@ namespace physmeme
"ntoskrnl.exe", "ntoskrnl.exe",
"ExAllocatePoolWithTag" "ExAllocatePoolWithTag"
); );
if (ex_alloc_pool_with_tag)
return syscall<ExAllocatePoolWithTag>(ex_alloc_pool_with_tag, pool_type, size, pool_tag);
}
/* return syscall<ExAllocatePoolWithTag>(
Author: xerox ex_alloc_pool_with_tag,
Date: 4/19/2020 pool_type,
size,
pool_tag
);
}
read kernel memory void kernel_ctx::read_kernel(void* addr, void* buffer, std::size_t size)
*/
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 = static const auto mm_copy_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"MmCopyMemory" "RtlCopyMemory"
); );
if (mm_copy_memory)
syscall<MmCopyMemory>(
mm_copy_memory,
reinterpret_cast<void*>(buffer),
MM_COPY_ADDRESS{ (void*)addr },
size,
MM_COPY_MEMORY_VIRTUAL,
&amount_copied
);
}
/* syscall<decltype(&memcpy)>(
Author: xerox mm_copy_memory,
Date: 4/19/2020 buffer,
addr,
size
);
}
write kernel memory, this doesnt write to read only memory! void kernel_ctx::write_kernel(void* addr, void* buffer, std::size_t size)
*/
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 = static const auto mm_copy_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"MmCopyMemory" "RtlCopyMemory"
); );
if (mm_copy_memory)
syscall<MmCopyMemory>(
mm_copy_memory,
reinterpret_cast<void*>(addr),
MM_COPY_ADDRESS{ buffer },
size,
MM_COPY_MEMORY_VIRTUAL,
&amount_copied
);
}
/* syscall<decltype(&memcpy)>(
Author: xerox mm_copy_memory,
Date: 4/19/2020 addr,
buffer,
size
);
}
zero driver header void kernel_ctx::zero_kernel_memory(void* addr, std::size_t size)
*/
void kernel_ctx::zero_kernel_memory(std::uintptr_t addr, std::size_t size)
{ {
static const auto rtl_zero_memory = static const auto rtl_zero_memory =
util::get_module_export( util::get_module_export(
"ntoskrnl.exe", "ntoskrnl.exe",
"RtlZeroMemory" "RtlZeroMemory"
); );
syscall<decltype(&RtlSecureZeroMemory)>( syscall<decltype(&RtlSecureZeroMemory)>(
rtl_zero_memory, rtl_zero_memory,
reinterpret_cast<void*>(addr), addr,
size size
); );
} }

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <windows.h> #include <windows.h>
#include <iostream>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <thread> #include <thread>
@ -9,34 +10,69 @@
#include "../physmeme/physmeme.hpp" #include "../physmeme/physmeme.hpp"
#include "../util/hook.hpp" #include "../util/hook.hpp"
#define PHYSMEME_DEBUGGING 1
#if PHYSMEME_DEBUGGING
#include <iostream>
#endif
/*
Author: xerox
Date: 4/19/2020
this namespace contains everything needed to interface with the kernel
*/
namespace physmeme namespace physmeme
{ {
//
// offset of function into a physical page
// used for comparing bytes when searching
//
inline std::uint16_t nt_page_offset{};
//
// rva of nt function we are going to hook
//
inline std::uint32_t nt_rva{};
//
// base address of ntoskrnl (inside of this process)
//
inline const std::uint8_t* ntoskrnl_buffer{};
//
// mapping of a syscalls physical memory (for installing hooks)
//
inline std::atomic<void*> psyscall_func{};
//
// you can edit this how you choose, im hooking NtShutdownSystem.
//
inline const std::pair<std::string_view, std::string_view> syscall_hook = { "NtShutdownSystem", "ntdll.dll" };
class kernel_ctx class kernel_ctx
{ {
public: public:
//
// default constructor
//
kernel_ctx(); kernel_ctx();
//
// allocate kernel pool of desired size and type
//
void* allocate_pool(std::size_t size, POOL_TYPE pool_type = NonPagedPool); void* allocate_pool(std::size_t size, POOL_TYPE pool_type = NonPagedPool);
//
// allocate kernel pool of size, pool tag, and type
//
void* allocate_pool(std::size_t size, ULONG pool_tag = 'MEME', 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); // read kernel memory with RtlCopyMemory
//
void read_kernel(void* addr, void* buffer, std::size_t size);
//
// write kernel memory with RtlCopyMemory
//
void write_kernel(void* addr, void* buffer, std::size_t size);
void zero_kernel_memory(std::uintptr_t addr, std::size_t size); //
// zero kernel memory using RtlZeroMemory
//
void zero_kernel_memory(void* addr, std::size_t size);
template <class T> template <class T>
T read_kernel(std::uintptr_t addr) T read_kernel(void* addr)
{ {
if (!addr) if (!addr)
return {}; return {};
@ -46,7 +82,7 @@ namespace physmeme
} }
template <class T> template <class T>
void write_kernel(std::uintptr_t addr, const T& data) void write_kernel(void* addr, const T& data)
{ {
if (!addr) if (!addr)
return {}; return {};
@ -57,14 +93,19 @@ namespace physmeme
// use this to call any function in the kernel // use this to call any function in the kernel
// //
template <class T, class ... Ts> template <class T, class ... Ts>
PVOID syscall(void* addr, Ts ... args) std::invoke_result_t<T, Ts...> syscall(void* addr, Ts ... args)
{ {
auto proc = GetProcAddress(GetModuleHandleA("ntdll.dll"), syscall_hook.first.data()); static const auto proc =
GetProcAddress(
GetModuleHandleA("ntdll.dll"),
syscall_hook.first.data()
);
if (!proc || !psyscall_func || !addr) if (!proc || !psyscall_func || !addr)
return reinterpret_cast<PVOID>(STATUS_INVALID_PARAMETER); return {};
hook::make_hook(psyscall_func, addr); hook::make_hook(psyscall_func, addr);
PVOID result = reinterpret_cast<PVOID>(reinterpret_cast<T>(proc)(args ...)); auto result = reinterpret_cast<T>(proc)(args ...);
hook::remove(psyscall_func); hook::remove(psyscall_func);
return result; return result;
} }
@ -75,31 +116,5 @@ namespace physmeme
// find and map the physical page of a syscall into this process // find and map the physical page of a syscall into this process
// //
void map_syscall(std::uintptr_t begin, std::uintptr_t end) const; void map_syscall(std::uintptr_t begin, std::uintptr_t end) const;
//
// mapping of a syscalls physical memory (for installing hooks)
//
mutable std::atomic<void*> psyscall_func;
//
// you can edit this how you choose, im hooking NtTraceControl.
//
const std::pair<std::string_view, std::string_view> syscall_hook = { "NtShutdownSystem", "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;
}; };
} }

@ -1,28 +1,43 @@
#include <windows.h>
#include <iostream>
#include <fstream>
#include "kernel_ctx/kernel_ctx.h" #include "kernel_ctx/kernel_ctx.h"
#include "drv_image/drv_image.h" #include "drv_image/drv_image.h"
/*
Author: xerox
Date: 4/19/2020
*/
int __cdecl main(int argc, char** argv) int __cdecl main(int argc, char** argv)
{ {
if (argc < 2) if (argc < 2)
{ {
std::cout << "[-] invalid use, please provide a path to a driver" << std::endl; perror("[-] invalid use, please provide a path to a driver\n");
return -1; return -1;
} }
std::vector<std::uint8_t> drv_buffer; std::vector<std::uint8_t> drv_buffer;
util::open_binary_file(argv[1], drv_buffer); util::open_binary_file(argv[1], drv_buffer);
if (!drv_buffer.size())
{
perror("[-] invalid drv_buffer size\n");
return -1;
}
physmeme::drv_image image(drv_buffer); physmeme::drv_image image(drv_buffer);
physmeme::kernel_ctx ctx; physmeme::kernel_ctx ctx;
//
// we dont need the driver loaded anymore
//
physmeme::unload_drv();
//
// allocate memory in the kernel for the driver
//
const auto pool_base = ctx.allocate_pool(image.size(), NonPagedPool);
printf("[+] allocated 0x%llx at 0x%p\n", image.size(), pool_base);
if (!pool_base)
{
printf("[!] allocation failed!\n");
return -1;
}
// //
// lambdas used for fixing driver image // lambdas used for fixing driver image
// //
@ -36,50 +51,42 @@ int __cdecl main(int argc, char** argv)
return reinterpret_cast<std::uintptr_t>(util::get_module_export(base, name)); return reinterpret_cast<std::uintptr_t>(util::get_module_export(base, name));
}; };
//
// allocate memory in the kernel for the driver
//
std::uintptr_t pool_base = reinterpret_cast<std::uintptr_t>(ctx.allocate_pool(image.size(), NonPagedPool));
std::cout << "[+] allocated " << std::hex << std::showbase << image.size() << " at: " << std::hex << std::showbase << pool_base << std::endl;
// //
// fix the driver image // fix the driver image
// //
image.fix_imports(_get_module, _get_export_name); image.fix_imports(_get_module, _get_export_name);
std::cout << "[+] fixed imports" << std::endl; printf("[+] fixed imports\n");
image.map(); image.map();
std::cout << "[+] sections mapped in memory" << std::endl; printf("[+] sections mapped in memory\n");
image.relocate(pool_base); image.relocate(pool_base);
std::cout << "[+] relocations fixed" << std::endl; printf("[+] relocations fixed\n");
// //
// copy driver into the kernel // 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()); ctx.write_kernel(pool_base, image.data(), image.size());
// //
// driver entry params // driver entry
// //
auto entry_point = pool_base + image.entry_point(); auto entry_point = reinterpret_cast<std::uintptr_t>(pool_base) + image.entry_point();
auto size = image.size();
// //
// call driver entry // call driver entry
// //
auto result = ctx.syscall<DRIVER_INITIALIZE>(reinterpret_cast<void*>(entry_point), pool_base, image.size()); auto result = ctx.syscall<DRIVER_INITIALIZE>(
std::cout << "[+] driver entry returned: " << std::hex << result << std::endl; reinterpret_cast<void*>(entry_point),
reinterpret_cast<std::uintptr_t>(pool_base),
image.size()
);
printf("[+] driver entry returned: 0x%p\n", result);
// //
// zero header of driver // zero driver headers
// //
ctx.zero_kernel_memory(pool_base, image.header_size()); ctx.zero_kernel_memory(pool_base, image.header_size());
std::cout << "[+] zero'ed driver's pe header" << std::endl; printf("[=] press enter to close\n");
//
// close and unload vuln drivers
//
std::cout << "[=] press enter to close" << std::endl;
physmeme::unload_drv();
std::cin.get(); std::cin.get();
} }

@ -90,7 +90,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -105,7 +105,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions> <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
@ -120,7 +120,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
@ -140,7 +140,7 @@
<ClCompile> <ClCompile>
<PrecompiledHeader> <PrecompiledHeader>
</PrecompiledHeader> </PrecompiledHeader>
<WarningLevel>Level3</WarningLevel> <WarningLevel>TurnOffAllWarnings</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>

@ -4,53 +4,20 @@
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include "../util/util.hpp"
namespace physmeme namespace physmeme
{ {
//--- ranges of physical memory //
static std::map<std::uintptr_t, std::size_t> pmem_ranges; // please code this function depending on your method of physical read/write.
//
//--- 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( static std::uintptr_t map_phys(
std::uintptr_t addr, std::uintptr_t addr,
std::size_t size std::size_t size
) )
{ {
//--- ensure the validity of the address we are going to try and map //--- ensure the validity of the address we are going to try and map
if (!is_valid(addr)) if (!util::is_valid(addr))
return NULL; return NULL;
static const auto map_phys_ptr = static const auto map_phys_ptr =
@ -59,9 +26,9 @@ namespace physmeme
return map_phys_ptr ? map_phys_ptr(addr, size) : false; return map_phys_ptr ? map_phys_ptr(addr, size) : false;
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static bool unmap_phys( static bool unmap_phys(
std::uintptr_t addr, std::uintptr_t addr,
std::size_t size std::size_t size
@ -73,9 +40,9 @@ namespace physmeme
return unmap_phys_ptr ? unmap_phys_ptr(addr, size) : false; return unmap_phys_ptr ? unmap_phys_ptr(addr, size) : false;
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static HANDLE load_drv() static HANDLE load_drv()
{ {
static const auto load_driver_ptr = static const auto load_driver_ptr =
@ -89,9 +56,9 @@ namespace physmeme
return CreateFileA("\\\\.\\PhyMem", 0xC0000000, 3u, 0i64, 3u, 0x80u, 0i64); return CreateFileA("\\\\.\\PhyMem", 0xC0000000, 3u, 0i64, 3u, 0x80u, 0i64);
} }
/* //
please code this function depending on your method of physical read/write. // please code this function depending on your method of physical read/write.
*/ //
static bool unload_drv() static bool unload_drv()
{ {
static const auto unload_driver_ptr = static const auto unload_driver_ptr =
@ -100,5 +67,5 @@ namespace physmeme
return unload_driver_ptr ? unload_driver_ptr() : false; return unload_driver_ptr ? unload_driver_ptr() : false;
} }
inline HANDLE drv_handle = load_drv(); static HANDLE drv_handle = load_drv();
} }

@ -3,16 +3,18 @@
#include <winternl.h> #include <winternl.h>
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ntdll.lib")
#define MM_COPY_MEMORY_PHYSICAL 0x1 constexpr bool physmeme_debugging = true;
#define MM_COPY_MEMORY_VIRTUAL 0x2 constexpr auto ntoskrnl_path = "C:\\Windows\\System32\\ntoskrnl.exe";
constexpr auto page_size = 0x1000;
constexpr auto PAGE_SIZE = 0x1000;
constexpr auto STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
constexpr auto SystemModuleInformation = 11; constexpr auto SystemModuleInformation = 11;
constexpr auto SystemHandleInformation = 16; constexpr auto SystemHandleInformation = 16;
constexpr auto SystemExtendedHandleInformation = 64; constexpr auto SystemExtendedHandleInformation = 64;
#define MM_COPY_MEMORY_PHYSICAL 0x1
#define MM_COPY_MEMORY_VIRTUAL 0x2
typedef struct _SYSTEM_HANDLE typedef struct _SYSTEM_HANDLE
{ {
PVOID Object; PVOID Object;

@ -1,17 +1,54 @@
#pragma once
#include <Windows.h> #include <Windows.h>
#include <cstdint> #include <cstdint>
#include <string_view> #include <string_view>
#include <iterator> #include <iterator>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <map>
#include <vector> #include <vector>
#include <ntstatus.h>
#include <winternl.h>
#include "nt.hpp" #include "nt.hpp"
/*
This code stinks, its the worst code in this project and needs to be recoded in a later time.
*/
namespace util namespace util
{ {
//--- ranges of physical memory
static std::map<std::uintptr_t, std::size_t> 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;
})();
// this was taken from wlan's drvmapper: // this was taken from wlan's drvmapper:
// https://github.com/not-wlan/drvmap/blob/98d93cc7b5ec17875f815a9cb94e6d137b4047ee/drvmap/util.cpp#L7 // https://github.com/not-wlan/drvmap/blob/98d93cc7b5ec17875f815a9cb94e6d137b4047ee/drvmap/util.cpp#L7
static void open_binary_file(const std::string& file, std::vector<uint8_t>& data) static void open_binary_file(const std::string& file, std::vector<uint8_t>& data)

Binary file not shown.

@ -0,0 +1,15 @@
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\vc142.pdb
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\main.obj
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\kernel_ctx.obj
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\drv_image.obj
c:\users\interesting\desktop\physmeme-master\x64\release\physmeme.exe
c:\users\interesting\desktop\physmeme-master\x64\release\physmeme.pdb
c:\users\interesting\desktop\physmeme-master\x64\release\physmeme.ipdb
c:\users\interesting\desktop\physmeme-master\x64\release\physmeme.iobj
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\cl.command.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\cl.read.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\cl.write.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\link.command.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\link.read.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\link.write.1.tlog
c:\users\interesting\desktop\physmeme-master\physmeme\x64\release\physmeme.tlog\physmeme.write.1u.tlog

@ -0,0 +1,8 @@
 drv_image.cpp
kernel_ctx.cpp
main.cpp
Generating code
Compiler switch has changed, fall back to full compilation.
All 684 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
Finished generating code
physmeme.vcxproj -> C:\Users\interesting\Desktop\physmeme-master\x64\Release\physmeme.exe

@ -0,0 +1,2 @@
PlatformToolSet=v142:VCToolArchitecture=Native32Bit:VCToolsVersion=14.25.28610:TargetPlatformVersion=10.0.18362.0:
Release|x64|C:\Users\interesting\Desktop\physmeme-master\|

Binary file not shown.
Loading…
Cancel
Save