diff options
| author | auth12 <[email protected]> | 2021-07-04 01:15:09 +0100 |
|---|---|---|
| committer | auth12 <[email protected]> | 2021-07-04 01:15:09 +0100 |
| commit | 661b73df47caae2cc62a9a2f7b85eb925ff1f80b (patch) | |
| tree | 79ab09cc3b9877b52fe32186ba3c0c354d0120bd | |
| download | sysmap-661b73df47caae2cc62a9a2f7b85eb925ff1f80b.tar.xz sysmap-661b73df47caae2cc62a9a2f7b85eb925ff1f80b.zip | |
initial commit
| -rw-r--r-- | .gitignore | 45 | ||||
| -rw-r--r-- | .gitmodules | 9 | ||||
| m--------- | modules/linuxpe | 0 | ||||
| m--------- | modules/phnt | 0 | ||||
| m--------- | modules/spdlog | 0 | ||||
| -rw-r--r-- | sysmap.sln | 31 | ||||
| -rw-r--r-- | sysmap/src/context.h | 8 | ||||
| -rw-r--r-- | sysmap/src/include.h | 28 | ||||
| -rw-r--r-- | sysmap/src/io.h | 41 | ||||
| -rw-r--r-- | sysmap/src/main.cpp | 59 | ||||
| -rw-r--r-- | sysmap/src/mapper/apiset.h | 41 | ||||
| -rw-r--r-- | sysmap/src/mapper/pe.h | 118 | ||||
| -rw-r--r-- | sysmap/src/mapper/process.h | 458 | ||||
| -rw-r--r-- | sysmap/src/mapper/syscalls.h | 116 | ||||
| -rw-r--r-- | sysmap/src/mapper/util.h | 52 | ||||
| -rw-r--r-- | sysmap/sysmap.vcxproj | 163 | ||||
| -rw-r--r-- | sysmap/sysmap.vcxproj.filters | 48 | ||||
| -rw-r--r-- | sysmap/sysmap.vcxproj.user | 6 |
18 files changed, 1223 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bb30db --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +#Auto generated files +.vs/* +sysmap/obj/* + +#Visual studio files +*.tlog + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj +*.ipch +*.suo +*.db + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +.DS_Store +*.sqlite +*.pdb diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5f79fb2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "modules/spdlog"] + path = modules/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "modules/linuxpe"] + path = modules/linuxpe + url = https://github.com/can1357/linux-pe.git +[submodule "modules/phnt"] + path = modules/phnt + url = https://github.com/processhacker/phnt.git diff --git a/modules/linuxpe b/modules/linuxpe new file mode 160000 +Subproject db2b7af6e6beae1bc391ff8f8e5c97b963dc325 diff --git a/modules/phnt b/modules/phnt new file mode 160000 +Subproject 190fbb00f1d3f541441ee0bc6f220ee9eff5e64 diff --git a/modules/spdlog b/modules/spdlog new file mode 160000 +Subproject 21413e599a8bae53bb8e49360849bfae32334a7 diff --git a/sysmap.sln b/sysmap.sln new file mode 100644 index 0000000..e6074a1 --- /dev/null +++ b/sysmap.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31402.337 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sysmap", "sysmap\sysmap.vcxproj", "{BF998D5B-57F5-42EB-B644-2B38EC4CC048}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Debug|x64.ActiveCfg = Debug|x64 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Debug|x64.Build.0 = Debug|x64 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Debug|x86.ActiveCfg = Debug|Win32 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Debug|x86.Build.0 = Debug|Win32 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Release|x64.ActiveCfg = Release|x64 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Release|x64.Build.0 = Release|x64 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Release|x86.ActiveCfg = Release|Win32 + {BF998D5B-57F5-42EB-B644-2B38EC4CC048}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {699B3DD4-B2CD-4599-8072-353A0DFED075} + EndGlobalSection +EndGlobal diff --git a/sysmap/src/context.h b/sysmap/src/context.h new file mode 100644 index 0000000..cb604eb --- /dev/null +++ b/sysmap/src/context.h @@ -0,0 +1,8 @@ +#pragma once + +struct mapper_context_t { + std::vector<util::module_data_t> local_modules; + std::string win_path; +}; + +extern mapper_context_t g_ctx;
\ No newline at end of file diff --git a/sysmap/src/include.h b/sysmap/src/include.h new file mode 100644 index 0000000..ebd4953 --- /dev/null +++ b/sysmap/src/include.h @@ -0,0 +1,28 @@ +#pragma once + +#include <algorithm> +#include <array> +#include <chrono> +#include <cstring> +#include <fstream> +#include <functional> +#include <iostream> +#include <memory> +#include <sstream> +#include <string> +#include <thread> +#include <vector> +#include <unordered_map> +#include <map> +#include <utility> +#include <filesystem> + +#include <phnt_windows.h> +#include <phnt.h> + +using namespace std::chrono_literals; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t;
\ No newline at end of file diff --git a/sysmap/src/io.h b/sysmap/src/io.h new file mode 100644 index 0000000..b498065 --- /dev/null +++ b/sysmap/src/io.h @@ -0,0 +1,41 @@ +#pragma once + +#include <spdlog/fmt/fmt.h> +#include <spdlog/spdlog.h> +#include <spdlog/sinks/basic_file_sink.h> +#include <spdlog/sinks/stdout_color_sinks.h> + +enum log_lvl { + trace = 0, + debug, + info, + warn, + error, + critical +}; + +namespace io { + template<log_lvl T, typename... Args> + void log(std::string_view msg, Args... params) { + spdlog::log(static_cast<spdlog::level::level_enum>(T), msg.data(), std::forward<Args>(params)...); + } + + static std::vector<u8> read_file(std::string_view name) { + std::ifstream file(name.data(), std::ios::binary); + if (!file.good()) { + return {}; + } + + std::vector<u8> out; + + file.seekg(0, std::ios::end); + std::streampos length = file.tellg(); + file.seekg(0, std::ios::beg); + + out.resize(length); + + file.read((char*)out.data(), length); + + return out; + } +};
\ No newline at end of file diff --git a/sysmap/src/main.cpp b/sysmap/src/main.cpp new file mode 100644 index 0000000..871be35 --- /dev/null +++ b/sysmap/src/main.cpp @@ -0,0 +1,59 @@ +#include "include.h" +#include "io.h" +#include "mapper/util.h" +#include "mapper/pe.h" + +#include "context.h" + +#include "mapper/syscalls.h" +#include "mapper/apiset.h" + +#include "mapper/process.h" + +mapper_context_t g_ctx; +syscalls_t g_syscalls; +apiset_t g_apiset; + + +int main(int argc, char* argv[]) { + std::vector<std::string> args; + + for (int i = 1; i < argc; ++i) { + args.emplace_back(argv[i]); + } + + if (args.size() < 2) { + io::log<critical>("Invalid arguments specified."); + return 0; + } + + spdlog::set_pattern("[%^%l%$] %v"); + + for (auto& arg : args) { + if (arg == "--debug") { + spdlog::set_level(spdlog::level::debug); + } + } + + g_ctx.local_modules = std::move(util::get_modules()); + + auto ntdll = g_ctx.local_modules[1]; + + g_ctx.win_path = ntdll.full_path.substr(0, ntdll.full_path.size() - ntdll.name.size()); + + g_syscalls.init(); + + process::process_x64_t proc; + if (NT_SUCCESS(proc.attach(args[0]))) { + io::log<info>("attached!"); + + proc.modules = proc.get_modules(); + + proc.map(io::read_file(args[1])); + + proc.close(proc.handle); + } + + std::cin.get(); + return 0; +}
\ No newline at end of file diff --git a/sysmap/src/mapper/apiset.h b/sysmap/src/mapper/apiset.h new file mode 100644 index 0000000..2797f8f --- /dev/null +++ b/sysmap/src/mapper/apiset.h @@ -0,0 +1,41 @@ +#pragma once + +struct apiset_t { + std::unordered_map<std::string, std::vector<std::string>> apiset_map; + + apiset_t() { + auto map = util::get_teb()->ProcessEnvironmentBlock->ApiSetMap; + auto map_ptr = uintptr_t(map); + + for (size_t i = 0; i < map->Count; i++) { + auto hash_entry = reinterpret_cast<API_SET_HASH_ENTRY*>(map_ptr + map->HashOffset + (i * sizeof(API_SET_HASH_ENTRY))); + + auto namespace_entry = reinterpret_cast<API_SET_NAMESPACE_ENTRY*>(map_ptr + map->EntryOffset + (hash_entry->Index * sizeof(API_SET_NAMESPACE_ENTRY))); + + std::wstring name(reinterpret_cast<wchar_t*>(map_ptr + namespace_entry->NameOffset), namespace_entry->NameLength / sizeof(wchar_t)); + + auto val_entry = reinterpret_cast<API_SET_VALUE_ENTRY*>(map_ptr + namespace_entry->ValueOffset); + + for (size_t j = 0; j < namespace_entry->ValueCount; j++, val_entry++) { + std::wstring val(reinterpret_cast<wchar_t*>(map_ptr + val_entry->ValueOffset), val_entry->ValueLength / sizeof(wchar_t)); + if (val.empty()) { + continue; + } + + apiset_map[util::to_multibyte(name)].emplace_back(util::to_multibyte(val)); + } + } + } + + std::string resolve(std::string_view mod) { + for (auto& [api, host] : apiset_map) { + if (mod.find(api) != std::string::npos) { + return host.front().compare(mod.data()) != 0 ? host.front() : host.back(); + } + } + + return mod.data(); + } +}; + +extern apiset_t g_apiset;
\ No newline at end of file diff --git a/sysmap/src/mapper/pe.h b/sysmap/src/mapper/pe.h new file mode 100644 index 0000000..9841cb2 --- /dev/null +++ b/sysmap/src/mapper/pe.h @@ -0,0 +1,118 @@ +#pragma once + +#include <linuxpe> + +namespace pe { + struct export_data_t { + uintptr_t func_rva; + std::string f_mod; + std::string f_func; + }; + + struct image_t { + uintptr_t base; + std::unordered_map<std::string, export_data_t> exports; + std::vector<win::section_header_t> sections; + + image_t() : base{ 0 } { } + image_t(uintptr_t b) : base{ b } { + auto image = reinterpret_cast<win::image_x64_t*>(b); + + auto nt = image->get_nt_headers(); + + for (auto &sec : nt->sections()) { + sections.emplace_back(sec); + } + + auto export_dir = b + nt->optional_header.data_directories.export_directory.rva; + auto export_size = nt->optional_header.data_directories.export_directory.size; + + auto exp = reinterpret_cast<win::export_directory_t*>(export_dir); + + if (exp->num_functions == 0) return; + + auto names = reinterpret_cast<uint32_t*>(b + exp->rva_names); + auto funcs = reinterpret_cast<uint32_t*>(b + exp->rva_functions); + auto ords = reinterpret_cast<uint16_t*>(b + exp->rva_name_ordinals); + + if (!names || !funcs || !ords) return; + + for (size_t i{}; i < exp->num_names; i++) { + std::string name = reinterpret_cast<const char*>(b + names[i]); + + export_data_t ret; + + ret.func_rva = funcs[ords[i]]; + + uintptr_t proc_addr = b + funcs[ords[i]]; + if (proc_addr > export_dir && proc_addr < export_dir + export_size) { + std::string forwarded_name = reinterpret_cast<char*>(proc_addr); + + size_t delim = forwarded_name.find('.'); + if (delim == std::string::npos) continue; + + ret.f_mod = forwarded_name.substr(0, delim + 1); + ret.f_mod.append("dll"); + + std::transform(ret.f_mod.begin(), ret.f_mod.end(), ret.f_mod.begin(), ::tolower); + + ret.f_func = forwarded_name.substr(delim + 1); + } + + + exports[name] = ret; + } + } + }; + + struct import_data_t { + std::string name; + uintptr_t rva; + }; + + struct raw_image_t { + win::nt_headers_x64_t* nt; + + std::unordered_map<std::string, std::vector<import_data_t>> imports; + std::unordered_map<uintptr_t, std::vector<win::reloc_entry_t>> relocs; + std::vector<win::section_header_t> sections; + + raw_image_t(std::vector<u8>& buffer) { + auto image = reinterpret_cast<win::image_x64_t*>(buffer.data()); + + nt = image->get_nt_headers(); + + for (auto& sec : nt->sections()) { + sections.emplace_back(sec); + } + + auto import_rva = nt->optional_header.data_directories.import_directory.rva; + + auto desc = image->rva_to_ptr<win::import_directory_t>(import_rva); + + for (uint32_t i = 0; i < desc->rva_name; i = desc->rva_name, ++desc) { + std::string mod = image->rva_to_ptr<char>(desc->rva_name); + + auto thunk = image->rva_to_ptr<win::image_thunk_data_x64_t>(desc->rva_original_first_thunk); + + for (uint32_t index = 0; thunk->address; index += sizeof(u64), ++thunk) { + auto named_import = image->rva_to_ptr<win::image_named_import_t>(thunk->address); + + if (!thunk->is_ordinal) { + std::transform(mod.begin(), mod.end(), mod.begin(), ::tolower); + + imports[mod].emplace_back(import_data_t{ reinterpret_cast<const char*>(named_import->name), desc->rva_first_thunk + index }); + } + } + } + + auto reloc_dir = image->rva_to_ptr<win::reloc_directory_t>(nt->optional_header.data_directories.basereloc_directory.rva); + + for (auto* block = &reloc_dir->first_block; block->base_rva; block = block->next()) { + for (auto& entry : *block) { + relocs[block->base_rva].emplace_back(entry); + } + } + } + }; +};
\ No newline at end of file diff --git a/sysmap/src/mapper/process.h b/sysmap/src/mapper/process.h new file mode 100644 index 0000000..77f7185 --- /dev/null +++ b/sysmap/src/mapper/process.h @@ -0,0 +1,458 @@ +#pragma once + + +namespace process { + struct process_info_t { + std::string name; + u16 pid; + }; + + struct process_iterator { + void* buf = nullptr; + + void* get_next() { + static ptrdiff_t offset = offsetof(SYSTEM_PROCESS_INFORMATION, NextEntryOffset); + + auto next_entry = *reinterpret_cast<uint32_t*>(uintptr_t(buf) + offset); + + if (next_entry == 0) { + return nullptr; + } + + buf = reinterpret_cast<void*>(uintptr_t(buf) + next_entry); + + return buf; + } + }; + + std::vector<process_info_t> get_processes() { + std::vector<uint8_t> buf; + std::vector<process_info_t> out; + + static auto nt_query = g_syscalls.get<decltype(&NtQuerySystemInformation)>("NtQuerySystemInformation"); + + ULONG size; + while (nt_query(SystemProcessInformation, &buf[0], static_cast<ULONG>(buf.size()), &size) == STATUS_INFO_LENGTH_MISMATCH) { + buf.resize(size); + }; + + process_iterator iter{ buf.data() }; + + void* ptr = iter.buf; + while (ptr) { + auto pi = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(ptr); + + std::wstring s{ pi->ImageName.Buffer, pi->ImageName.Length / sizeof(wchar_t) }; + if (s.empty()) { + ptr = iter.get_next(); + + continue; + } + + out.emplace_back(process_info_t{ util::to_multibyte(s), reinterpret_cast<u16>(pi->UniqueProcessId) }); + + ptr = iter.get_next(); + } + + return out; + } + + struct process_x64_t { + process_info_t info; + HANDLE handle; + + std::vector<util::module_data_t> modules; + + NTSTATUS open_handle() { + static auto nt_open = g_syscalls.get<decltype(&NtOpenProcess)>("NtOpenProcess"); + CLIENT_ID cid = { HANDLE(info.pid), 0 }; + OBJECT_ATTRIBUTES oa; + oa.Length = sizeof(oa); + oa.Attributes = 0; + oa.RootDirectory = 0; + oa.SecurityDescriptor = 0; + oa.ObjectName = 0; + oa.SecurityQualityOfService = 0; + + auto ret = nt_open(&handle, PROCESS_ALL_ACCESS, &oa, &cid); + + io::log<debug>("NtOpenProcess on {}, returned {:x}.", info.pid, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS attach(std::string_view process_name) { + auto processes = get_processes(); + auto it = std::find_if(processes.begin(), processes.end(), [&](process_info_t& info) { + return process_name.compare(info.name) == 0; + }); + + while (it == processes.end()) { + std::this_thread::sleep_for(3s); + + processes = get_processes(); + + it = std::find_if(processes.begin(), processes.end(), [&](process_info_t& info) { + return process_name.compare(info.name) == 0; + }); + } + + info = *it; + + return open_handle(); + } + + NTSTATUS read(uintptr_t addr, void* buf, size_t size) { + static auto nt_read = g_syscalls.get<decltype(&NtReadVirtualMemory)>("NtReadVirtualMemory"); + + auto ret = nt_read(handle, reinterpret_cast<void*>(addr), buf, size, nullptr); + + //io::log<debug>("NtReadVirtualMemory at {:x}, buf {:x}, size {:x}, returned {:x}.", addr, uintptr_t(buf), size, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS write(uintptr_t addr, void* buf, size_t size) { + static auto nt_write = g_syscalls.get<decltype(&NtWriteVirtualMemory)>("NtWriteVirtualMemory"); + + auto ret = nt_write(handle, reinterpret_cast<void*>(addr), buf, size, nullptr); + io::log<debug>("NtWriteVirtualMemory at {:x}, buf {:x}, size {:x}, returned {:x}.", addr, uintptr_t(buf), size, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS protect(uintptr_t addr, size_t size, uint32_t new_protection, uint32_t* old_protection) { + static auto nt_protect = g_syscalls.get<decltype(&NtProtectVirtualMemory)>("NtProtectVirtualMemory"); + + void* addr_cast = reinterpret_cast<void*>(addr); + auto ret = nt_protect(handle, &addr_cast, &size, new_protection, (PULONG)old_protection); + + io::log<debug>("NtProtectVirtualMemory at {:x}, size {:x}, new_protection {:x}, old_protection {:x}, returned {:x}.", addr, size, new_protection, *old_protection, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS query_info(PROCESSINFOCLASS inf, void* dat, size_t size) { + static auto nt_info = g_syscalls.get<decltype(&NtQueryInformationProcess)>("NtQueryInformationProcess"); + + auto ret = nt_info(handle, inf, dat, static_cast<ULONG>(size), nullptr); + + io::log<debug>("NtQueryInformationProcess with {:x}, dat {:x}, size {:x}, returned {:x}.", (int)inf, uintptr_t(dat), size, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS alloc(uintptr_t* out, size_t size, uint32_t type, uint32_t protection) { + static auto nt_alloc = g_syscalls.get<decltype(&NtAllocateVirtualMemory)>("NtAllocateVirtualMemory"); + + void* base = nullptr; + auto ret = nt_alloc(handle, &base, 0, &size, type, protection); + *out = uintptr_t(base); + + io::log<debug>("NtAllocateVirtualMemory allocated at {:x}, size {:x}, type {:x}, protection {:x}, returned {:x}.", *out, size, type, protection, ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS close(HANDLE handle) { + static auto nt_close = g_syscalls.get<decltype(&NtClose)>("NtClose"); + + auto ret = nt_close(handle); + io::log<debug>("NtClose on {:x}, returned {:x}.", uintptr_t(handle), ret & 0xFFFFFFFF); + + return ret; + } + + NTSTATUS get_peb(uintptr_t* out) { + PROCESS_BASIC_INFORMATION info; + auto status = query_info(ProcessBasicInformation, &info, sizeof(info)); + + *out = uintptr_t(info.PebBaseAddress); + + return status; + } + + std::vector<util::module_data_t> get_modules() { + uintptr_t peb_ptr{ 0 }; + get_peb(&peb_ptr); + + PEB peb; + read(peb_ptr, &peb, sizeof(peb)); + + uintptr_t head = uintptr_t(peb.Ldr) + offsetof(PEB_LDR_DATA, InLoadOrderModuleList); + + LIST_ENTRY64 entry; + read(head, &entry, sizeof(entry)); + + LDR_DATA_TABLE_ENTRY ldr_entry; + std::vector<util::module_data_t> ret; + + for (auto i = entry.Flink; i != head;) { + read(i, &ldr_entry, sizeof(ldr_entry)); + + i = uintptr_t(ldr_entry.InLoadOrderLinks.Flink); + + std::wstring ws; + ws.resize(ldr_entry.BaseDllName.Length); + + read(uintptr_t(ldr_entry.BaseDllName.Buffer), &ws[0], ws.size()); + + auto name = util::to_multibyte(ws); + + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + ret.emplace_back(util::module_data_t{ name, uintptr_t(ldr_entry.DllBase), ldr_entry.SizeOfImage }); + } + + return ret; + } + + uintptr_t get_module_export(util::module_data_t* module_info, std::string_view func) { + std::vector<u8> mapped_module(module_info->size); + + auto status = read(module_info->base, mapped_module.data(), mapped_module.size()); + if (!NT_SUCCESS(status)) { + return {}; + } + + pe::image_t img(uintptr_t(mapped_module.data())); + + return img.exports[func.data()].func_rva; + } + + auto get_module_exports(util::module_data_t* module_info) -> std::unordered_map<std::string, pe::export_data_t> { + if (!module_info->base) { + return {}; + } + + std::vector<u8> mapped_module(module_info->size); + + auto status = read(module_info->base, mapped_module.data(), mapped_module.size()); + if (!NT_SUCCESS(status)) { + return {}; + } + + pe::image_t img(uintptr_t(mapped_module.data())); + + return img.exports; + } + + util::module_data_t map_module(std::string_view name) { + auto resolved_name = g_apiset.resolve(name); + auto it = std::find_if(modules.begin(), modules.end(), [&](util::module_data_t& data) { + return data.name == resolved_name; + }); + + if (it != modules.end()) { + return *it; + } + + auto file = io::read_file(g_ctx.win_path.append(resolved_name)); + if (file.empty()) { + io::log<critical>("failed to read {}", resolved_name); + return {}; + } + + pe::raw_image_t image(file); + auto nt = image.nt; + + std::vector<u8> mapped_image(nt->optional_header.size_image); + + std::memcpy(&mapped_image[0], &file[0], nt->optional_header.size_headers); + for (auto& sec : image.sections) { + std::memcpy(&mapped_image[sec.virtual_address], &file[sec.ptr_raw_data], sec.size_raw_data); + } + + uintptr_t allocation_base{ 0 }; + auto status = alloc(&allocation_base, mapped_image.size(), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + if (!NT_SUCCESS(status)) { + io::log<critical>("failed to allocate memory for {}", resolved_name); + return {}; + } + + io::log<log_lvl::info>("mapping {} to {:x}", resolved_name, allocation_base); + + io::log<log_lvl::info>("fixing {} relocations...", resolved_name); + + auto delta = allocation_base - nt->optional_header.image_base; + if (delta > 0) { + for (auto& [block_rva, entries] : image.relocs) { + for (auto& e : entries) { + if (e.type == win::rel_based_high_low || e.type == win::rel_based_dir64) { + *reinterpret_cast<u64*>(&mapped_image[block_rva + e.offset]) += delta; + } + } + } + } + + io::log<log_lvl::info>("resolving {} imports...", resolved_name); + for (auto& [mod, funcs] : image.imports) { + auto mod_data = map_module(mod); + auto exports = get_module_exports(&mod_data); + + for (auto& f : funcs) { + auto exp_data = exports[f.name]; + + uintptr_t proc_addr{ 0 }; + if (!exp_data.f_mod.empty()) { + auto f_mod_data = map_module(exp_data.f_mod); + + proc_addr = f_mod_data.base + get_module_export(&f_mod_data, exp_data.f_func); + + io::log<debug>("{}!{}->{}!{}->{:x}", mod, f.name, exp_data.f_mod, exp_data.f_func, proc_addr); + + *reinterpret_cast<u64*>(&mapped_image[f.rva]) = proc_addr; + + continue; + } + + proc_addr = mod_data.base + exports[f.name].func_rva; + + io::log<debug>("{}!{}->{:x}", mod, f.name, proc_addr); + + *reinterpret_cast<u64*>(&mapped_image[f.rva]) = proc_addr; + } + } + + + io::log<log_lvl::info>("writing {} image...", resolved_name); + status = write(allocation_base, mapped_image.data(), mapped_image.size()); + if (!NT_SUCCESS(status)) { + io::log<critical>("failed to write mapped image for {}", resolved_name); + return {}; + } + + io::log<log_lvl::info>("fixing {} section permissions...", resolved_name); + for (auto& sec : image.sections) { + uintptr_t addr = allocation_base + sec.virtual_address; + uint32_t prot; + if (sec.characteristics.mem_execute) { + prot = PAGE_EXECUTE; + + if (sec.characteristics.mem_read) { + prot = PAGE_EXECUTE_READ; + } + + if (sec.characteristics.mem_write) { + prot = PAGE_EXECUTE_READWRITE; + } + } + else { + prot = PAGE_NOACCESS; + if (sec.characteristics.mem_read) { + prot = PAGE_READONLY; + } + + if (sec.characteristics.mem_write) { + prot = PAGE_READWRITE; + } + } + + uint32_t old_protection; + status = protect(addr, sec.size_raw_data, prot, &old_protection); + if (!NT_SUCCESS(status)) { + io::log<critical>("failed to set section permissions on {} for {}", sec.name.to_string(), resolved_name); + continue; + } + } + + io::log<log_lvl::info>("successfully mapped {}", resolved_name); + + return util::module_data_t{ resolved_name, allocation_base, mapped_image.size() }; + } + + util::module_data_t map(std::vector<u8> file) { + pe::raw_image_t image(file); + auto nt = image.nt; + + std::vector<u8> mapped_image(nt->optional_header.size_image); + + for (auto& sec : image.sections) { + std::memcpy(&mapped_image[sec.virtual_address], &file[sec.ptr_raw_data], sec.size_raw_data); + } + + uintptr_t allocation_base{ 0 }; + auto status = alloc(&allocation_base, mapped_image.size(), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + if (!NT_SUCCESS(status)) { + io::log<critical>("failed to allocate memory for target image"); + return {}; + } + + io::log<log_lvl::info>("mapping target image to {:x}", allocation_base); + + const u16 headers_size = 4096; + + io::log<log_lvl::info>("fixing target image relocations..."); + + auto delta = allocation_base - nt->optional_header.image_base; + if (delta > 0) { + for (auto& [block_rva, entries] : image.relocs) { + for (auto& e : entries) { + if (e.type == win::rel_based_high_low || e.type == win::rel_based_dir64) { + *reinterpret_cast<u64*>(&mapped_image[block_rva + e.offset]) += delta - headers_size; + } + } + } + } + + io::log<log_lvl::info>("resolving target image imports..."); + for (auto& [mod, funcs] : image.imports) { + auto mod_data = map_module(mod); + auto exports = get_module_exports(&mod_data); + + for (auto& f : funcs) { + auto exp_data = exports[f.name]; + + uintptr_t proc_addr{ 0 }; + if (!exp_data.f_mod.empty()) { + auto f_mod_data = map_module(exp_data.f_mod); + + proc_addr = f_mod_data.base + get_module_export(&f_mod_data, exp_data.f_func); + + io::log<debug>("{}!{}->{}!{}->{:x}", mod, f.name, exp_data.f_mod, exp_data.f_func, proc_addr); + + *reinterpret_cast<u64*>(&mapped_image[f.rva]) = proc_addr; + + continue; + } + + proc_addr = mod_data.base + exports[f.name].func_rva; + + io::log<debug>("{}!{}->{:x}", mod, f.name, proc_addr); + + *reinterpret_cast<u64*>(&mapped_image[f.rva]) = proc_addr; + } + } + + + io::log<log_lvl::info>("writing target image..."); + status = write(allocation_base, mapped_image.data() + headers_size, mapped_image.size() - headers_size); + if (!NT_SUCCESS(status)) { + io::log<critical>("failed to write mapped image"); + return {}; + } + + static std::vector<u8> shellcode = { 0x48, 0x83, 0xEC, 0x28, 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC2,0x01, 0x00, 0x00, 0x00, 0x4D, 0x31, 0xC0, + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3 }; + + *reinterpret_cast<u64*>(&shellcode[6]) = allocation_base; + *reinterpret_cast<u64*>(&shellcode[26]) = allocation_base + nt->optional_header.entry_point - headers_size; + + uintptr_t shellcode_base; + alloc(&shellcode_base, shellcode.size(), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + io::log<log_lvl::info>("writing shellcode at {:x}...", shellcode_base); + + write(shellcode_base, shellcode.data(), shellcode.size()); + + CreateRemoteThread(handle, 0, 0, (LPTHREAD_START_ROUTINE)shellcode_base, 0, 0, 0); + + io::log<log_lvl::info>("mapped target image"); + + return util::module_data_t{ "", allocation_base, mapped_image.size() }; + } + }; +}
\ No newline at end of file diff --git a/sysmap/src/mapper/syscalls.h b/sysmap/src/mapper/syscalls.h new file mode 100644 index 0000000..d7b917d --- /dev/null +++ b/sysmap/src/mapper/syscalls.h @@ -0,0 +1,116 @@ +#pragma once + +struct syscalls_t { + void* call_table; + std::vector<uint8_t> stub; + + std::unordered_map<std::string, u16> syscalls; + + syscalls_t() : call_table{nullptr} {} + + void init() { + auto ntdll_base = g_ctx.local_modules[1].base; + auto ntdll = pe::image_t(ntdll_base); + + u32 max_index = 0; + for (auto& [name, exp_data] : ntdll.exports) { + auto fn = reinterpret_cast<uint8_t*>(ntdll_base + exp_data.func_rva); + auto size = get_size(fn); + + if (!is_valid(fn, size)) { + continue; + } + + if (stub.empty()) { + for (size_t i = 0; i < size; i++) { + if (fn[i] == x64::test_imm8) { // skip <test byte ptr ds:[7FFE0308],1> and <jne ntdll.7FFF70550395> + i += 9; + continue; + } + + stub.emplace_back(fn[i]); + } + } + + u32 idx = get_idx(fn, size); + + if (idx > max_index) + max_index = idx; + + syscalls[name] = idx; + } + + size_t table_size = stub.size() * (max_index + 1); + + call_table = VirtualAlloc(0, table_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + + if (!call_table) { + io::log<critical>("failed to allocated syscall call table."); + return; + } + + io::log<debug>("syscall call table allocated at {:x}.", uintptr_t(call_table)); + + std::memset(call_table, x64::nop, table_size); + + for (auto& [hash, index] : syscalls) { + uintptr_t func_dest = uintptr_t(call_table) + (index * stub.size()); + std::memcpy(reinterpret_cast<void*>(func_dest), stub.data(), stub.size()); + + *reinterpret_cast<u32*>(func_dest + 4) = index; + } + + DWORD old; + VirtualProtect(call_table, table_size, PAGE_EXECUTE, &old); + } + + template< typename T = void* > + __forceinline T get(std::string_view fn) { + return reinterpret_cast<T>(uintptr_t(call_table) + (syscalls[fn.data()] * stub.size())); + } + + + uint16_t get_idx(u8 *fn, size_t size) { + for (size_t i = 0; i < size; i++) { + auto op = fn[i]; + if (op == x64::mov_imm16) { + return *reinterpret_cast<u32*>(&fn[i + 1]); + } + } + + return 0; + } + + size_t get_size(const u8* func) { + for (size_t i = 0; i < 64; i++) { + auto op = func[i]; + if (op == x64::retn) { + return i + 1; + } + } + + return 0; + } + + bool is_valid(u8* func, size_t size) { + // mov r10, rcx + u32 a = func[0] + func[1] + func[2]; + if (a != 0x1a8) { + return false; + } + + for (size_t i = 0; i < size; i++) { + auto cur = func[i]; + auto next = func[i + 1]; + + // syscall + if (cur == 0x0f && next == 0x05) { + return true; + } + } + + return false; + } +}; + +extern syscalls_t g_syscalls;
\ No newline at end of file diff --git a/sysmap/src/mapper/util.h b/sysmap/src/mapper/util.h new file mode 100644 index 0000000..f50192b --- /dev/null +++ b/sysmap/src/mapper/util.h @@ -0,0 +1,52 @@ +#pragma once + +namespace util { + struct module_data_t { + std::string name; + uintptr_t base; + size_t size; + std::string full_path; + }; + + std::string to_multibyte(std::wstring_view str) { + return std::filesystem::path(str.data()).string(); + } + + std::wstring to_wide(std::string_view str) { + return std::filesystem::path(str.data()).wstring(); + } + + TEB* get_teb() { + return reinterpret_cast<TEB*>(__readgsqword(0x30)); + } + + std::vector<module_data_t> get_modules() { + std::vector<module_data_t> ret{}; + + auto* list = &get_teb()->ProcessEnvironmentBlock->Ldr->InMemoryOrderModuleList; + + for (auto i = list->Flink; i != list; i = i->Flink) { + auto entry = CONTAINING_RECORD(i, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (!entry) + continue; + + auto name = util::to_multibyte(entry->BaseDllName.Buffer); + std::transform(name.begin(), name.end(), name.begin(), tolower); + + auto full_path = util::to_multibyte(entry->FullDllName.Buffer); + + ret.emplace_back(module_data_t{name, uintptr_t(entry->DllBase), entry->SizeOfImage, full_path}); + } + + return ret; + } +}; + +namespace x64 { + enum inst : uint8_t { + retn = 0xC3, + mov_imm16 = 0xB8, + nop = 0x90, + test_imm8 = 0xF6 + }; +};
\ No newline at end of file diff --git a/sysmap/sysmap.vcxproj b/sysmap/sysmap.vcxproj new file mode 100644 index 0000000..a42f5d0 --- /dev/null +++ b/sysmap/sysmap.vcxproj @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>16.0</VCProjectVersion> + <Keyword>Win32Proj</Keyword> + <ProjectGuid>{bf998d5b-57f5-42eb-b644-2b38ec4cc048}</ProjectGuid> + <RootNamespace>sysmap</RootNamespace> + <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir> + <IntDir>obj\$(Platform)\$(Configuration)\</IntDir> + <IncludePath>$(SolutionDir)modules\phnt\;$(SolutionDir)modules\linuxpe\includes\;$(SolutionDir)modules\spdlog\include\;$(IncludePath)</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <LanguageStandard>stdcpplatest</LanguageStandard> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <UACExecutionLevel>RequireAdministrator</UACExecutionLevel> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="src\main.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="src\context.h" /> + <ClInclude Include="src\io.h" /> + <ClInclude Include="src\mapper\apiset.h" /> + <ClInclude Include="src\mapper\pe.h" /> + <ClInclude Include="src\mapper\process.h" /> + <ClInclude Include="src\mapper\syscalls.h" /> + <ClInclude Include="src\include.h" /> + <ClInclude Include="src\mapper\util.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/sysmap/sysmap.vcxproj.filters b/sysmap/sysmap.vcxproj.filters new file mode 100644 index 0000000..0d19da0 --- /dev/null +++ b/sysmap/sysmap.vcxproj.filters @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="src\main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="src\include.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\mapper\util.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\mapper\syscalls.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\mapper\pe.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\io.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\context.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\mapper\process.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="src\mapper\apiset.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/sysmap/sysmap.vcxproj.user b/sysmap/sysmap.vcxproj.user new file mode 100644 index 0000000..966b4ff --- /dev/null +++ b/sysmap/sysmap.vcxproj.user @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ShowAllFiles>true</ShowAllFiles> + </PropertyGroup> +</Project>
\ No newline at end of file |