diff --git a/xtils.sln b/xtils.sln new file mode 100644 index 0000000..d455256 --- /dev/null +++ b/xtils.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xtils", "xtils\xtils.vcxproj", "{47343E59-10B7-4F29-B7A0-561B106F7D0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47343E59-10B7-4F29-B7A0-561B106F7D0B}.Release|x64.ActiveCfg = Release|x64 + {47343E59-10B7-4F29-B7A0-561B106F7D0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5FCE9B1F-FCF9-4D0A-9B5E-207C218B537E} + EndGlobalSection +EndGlobal diff --git a/xtils/hello-world-x64.dll b/xtils/hello-world-x64.dll new file mode 100644 index 0000000..1f6b286 Binary files /dev/null and b/xtils/hello-world-x64.dll differ diff --git a/xtils/main.cpp b/xtils/main.cpp new file mode 100644 index 0000000..fbdd72a --- /dev/null +++ b/xtils/main.cpp @@ -0,0 +1,52 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include "xtils.hpp" + +int __cdecl main(int argc, const char** argv) +{ + auto utils = xtils::um_t::get_instance(); + const auto explorer_pid = + utils->get_pid(L"explorer.exe"); + + const auto explorer_module_base = + utils->get_process_base(utils->get_handle(explorer_pid).get()); + + std::printf("> explorer pid = 0x%x, module base = 0x%p\n", + explorer_pid, explorer_module_base); + + std::map modules; + if (!utils->get_modules(explorer_pid, modules)) + { + std::printf("[!] failed to get modules...\n"); + return -1; + } + + std::printf("> user32.dll base = 0x%p\n", + modules[L"user32.dll"]); + + const auto [notepad_handle, notepad_pid, notepad_base] = + utils->start_exec("C:\\Windows\\System32\\notepad.exe"); + + std::printf("> notepad handle = 0x%x, notepad pid = 0x%x, notepad_base = 0x%p\n", + notepad_handle, notepad_pid, notepad_base); + + const auto module_base = utils->load_lib(notepad_handle, + (std::filesystem::current_path() + .string() + "\\hello-world-x64.dll").c_str()); + + std::printf("> module base = 0x%p\n", module_base); + auto km_utils = xtils::km_t::get_instance(); + + km_utils->each_module( + [](PRTL_PROCESS_MODULE_INFORMATION kmodule_info, const char* module_name) -> bool + { + std::printf("> module name = %s, module base = 0x%p\n", + module_name, kmodule_info->ImageBase); + + return true; + } + ); + + std::printf("> ntoskrnl base = 0x%p\n", km_utils->get_base("ntoskrnl.exe")); +} \ No newline at end of file diff --git a/xtils/xtils.hpp b/xtils/xtils.hpp new file mode 100644 index 0000000..1bb6870 --- /dev/null +++ b/xtils/xtils.hpp @@ -0,0 +1,523 @@ +#pragma once +#define _CRT_SECURE_NO_WARNINGS +#pragma comment(lib, "ntdll.lib") + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define LOG_SIG "[xtils]" +#define LOG(...) \ +{ \ + char buff[256]; \ + snprintf(buff, sizeof buff, LOG_SIG ## __VA_ARGS__); \ + OutputDebugStringA(buff); \ +} + +#define NT_HEADER(x) reinterpret_cast( uint64_t(x) + reinterpret_cast(x)->e_lfanew ) + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, * PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, * PRTL_PROCESS_MODULES; + + +namespace xtils +{ + using uq_handle = std::unique_ptr; + + class um_t + { + using module_callback_t = std::function; + using module_map_t = std::map; + + public: + static auto get_instance() -> um_t* { static um_t obj; return &obj; } + + auto get_modules(std::uint32_t pid, module_map_t& module_map) -> bool + { + uq_handle snapshot = { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid), &CloseHandle }; + + if (snapshot.get() == INVALID_HANDLE_VALUE) + return false; + + MODULEENTRY32 module_info = { sizeof MODULEENTRY32 }; + Module32First(snapshot.get(), &module_info); + + // lowercase the module name... + std::for_each(module_info.szModule, + module_info.szModule + wcslen(module_info.szModule) * 2, + [](wchar_t& c) { c = ::towlower(c); }); + + module_map[module_info.szModule] = reinterpret_cast(module_info.modBaseAddr); + + for (Module32First(snapshot.get(), &module_info); Module32Next(snapshot.get(), &module_info);) + { + // lowercase the module name... + std::for_each(module_info.szModule, + module_info.szModule + wcslen(module_info.szModule) * 2, + [](wchar_t& c) { c = ::towlower(c); }); + + module_map[module_info.szModule] = + reinterpret_cast(module_info.modBaseAddr); + } + + return true; + } + + void each_module(std::uint32_t pid, module_callback_t callback) + { + module_map_t module_map; + if (!get_modules(pid, module_map)) + return; + + for (auto& [module_name, module_base] : module_map) + if (!callback(module_name, module_base)) + break; + } + + // https://github.com/PierreCiholas/GetBaseAddress/blob/master/main.cpp#L7 + auto get_process_base(HANDLE proc_handle)->std::uintptr_t + { + HMODULE lph_modules[1024]; + DWORD needed = 0u; + + if (!EnumProcessModules(proc_handle, lph_modules, sizeof(lph_modules), &needed)) + return {}; + + TCHAR mod_name[MAX_PATH]; + if (!GetModuleFileNameEx(proc_handle, lph_modules[0], mod_name, sizeof(mod_name) / sizeof(TCHAR))) + return {}; + + return reinterpret_cast(lph_modules[0]); + } + + auto get_pid(const wchar_t* proc_name) -> std::uint32_t + { + uq_handle snapshot = { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL), &CloseHandle }; + + if (snapshot.get() == INVALID_HANDLE_VALUE) + return {}; + + PROCESSENTRY32W process_entry{ sizeof(PROCESSENTRY32W) }; + Process32FirstW(snapshot.get(), &process_entry); + if (!std::wcscmp(proc_name, process_entry.szExeFile)) + return process_entry.th32ProcessID; + + for (Process32FirstW(snapshot.get(), &process_entry); Process32NextW(snapshot.get(), &process_entry); ) + if (!std::wcscmp(proc_name, process_entry.szExeFile)) + return process_entry.th32ProcessID; + + return {}; + } + + auto get_handle(const wchar_t* proc_name, DWORD access = PROCESS_ALL_ACCESS) -> uq_handle + { + std::uint32_t pid = 0u; + if (!(pid = get_pid(proc_name))) + return { NULL, &CloseHandle }; + + return { OpenProcess(access, FALSE, pid), &CloseHandle }; + } + + auto get_handle(std::uint32_t pid, DWORD access = PROCESS_ALL_ACCESS)->uq_handle + { + if (!pid) return { NULL, &CloseHandle }; + return { OpenProcess(access, FALSE, pid), &CloseHandle }; + } + + auto load_lib(HANDLE proc_handle, const char* dll_path) -> std::uintptr_t + { + const auto dll_path_page = + VirtualAllocEx( + proc_handle, + nullptr, + 0x1000, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE + ); + + if (!dll_path_page) + return {}; + + SIZE_T handled_bytes; + if (!WriteProcessMemory(proc_handle, dll_path_page, + dll_path, strlen(dll_path), &handled_bytes)) + return {}; + + // +6 for string address + // +16 for LoadLibrary address... + unsigned char jmp_code[] = + { + 0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28 + 0x48, 0xB9, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // mov rcx, &dllpath + 0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // mov rax, &LoadLibraryA + 0xFF, 0xD0, // call rax + 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 + 0x48, 0x89, 0x05, 0x01, 0x00, 0x00, 0x00, // mov [rip+1], rax + 0xC3 // ret + }; + + *reinterpret_cast(&jmp_code[6]) = + reinterpret_cast(dll_path_page); + + *reinterpret_cast(&jmp_code[16]) = + reinterpret_cast(&LoadLibraryA); + + const auto jmp_code_page = + VirtualAllocEx( + proc_handle, + nullptr, + 0x1000, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE + ); + + if (!jmp_code_page) + return {}; + + if (!WriteProcessMemory(proc_handle, + jmp_code_page, jmp_code, sizeof jmp_code, &handled_bytes)) + return {}; + + DWORD tid = 0u; + auto thandle = CreateRemoteThread(proc_handle, nullptr, + NULL, (LPTHREAD_START_ROUTINE)jmp_code_page, nullptr, NULL, &tid); + + if (thandle == INVALID_HANDLE_VALUE) + return {}; + + WaitForSingleObject(thandle, INFINITE); + + // read the base address out of the shellcode... + std::uintptr_t module_base = 0u; + if (!ReadProcessMemory(proc_handle, reinterpret_cast( + reinterpret_cast(jmp_code_page) + sizeof jmp_code), + &module_base, sizeof module_base, &handled_bytes)) + return {}; + + return module_base; + } + + auto start_exec(const char* image_path, char* cmdline = nullptr, + bool suspend = false) -> std::tuple + { + STARTUPINFOA info = { sizeof info }; + PROCESS_INFORMATION proc_info; + + if (!CreateProcessA(image_path, cmdline, nullptr, + nullptr, false, + suspend ? CREATE_SUSPENDED | CREATE_NEW_CONSOLE : CREATE_NEW_CONSOLE, + nullptr, nullptr, &info, &proc_info + )) + return { {}, {}, {} }; + + Sleep(1); // sleep just for a tiny amount of time so that get_process_base works... + return { proc_info.hProcess, proc_info.dwProcessId, get_process_base(proc_info.hProcess) }; + } + + std::uintptr_t scan(std::uintptr_t base, std::uint32_t size, const char* pattern, const char* mask) + { + static const auto check_mask = + [&](const char* base, const char* pattern, const char* mask) -> bool + { + for (; *mask; ++base, ++pattern, ++mask) + if (*mask == 'x' && *base != *pattern) + return false; + return true; + }; + + size -= strlen(mask); + for (auto i = 0; i <= size; ++i) + { + void* addr = (void*)&(((char*)base)[i]); + if (check_mask((char*)addr, pattern, mask)) + return reinterpret_cast(addr); + } + return {}; + } + + private: + explicit um_t() {} + }; + + class km_t + { + using kmodule_callback_t = std::function; + public: + static auto get_instance() -> km_t* { static km_t obj; return &obj; }; + auto get_base(const char* drv_name)->std::uintptr_t + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + auto status = NtQuerySystemInformation( + static_cast(0xB), + buffer, buffer_size, &buffer_size); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation( + static_cast(0xB), + buffer, buffer_size, &buffer_size); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + const auto current_module_name = + std::string(reinterpret_cast( + modules->Modules[idx].FullPathName) + + modules->Modules[idx].OffsetToFileName); + + if (!_stricmp(current_module_name.c_str(), drv_name)) + { + const auto result = + reinterpret_cast( + modules->Modules[idx].ImageBase); + + VirtualFree(buffer, NULL, MEM_RELEASE); + return result; + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + + } + + void each_module(kmodule_callback_t callback) + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + auto status = NtQuerySystemInformation( + static_cast(0xB), + buffer, buffer_size, &buffer_size); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation( + static_cast(0xB), + buffer, buffer_size, &buffer_size); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + return; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + auto full_path = std::string( + reinterpret_cast( + modules->Modules[idx].FullPathName)); + + if (full_path.find("\\SystemRoot\\") != std::string::npos) + full_path.replace(full_path.find("\\SystemRoot\\"), + sizeof("\\SystemRoot\\") - 1, std::string(getenv("SYSTEMROOT")).append("\\")); + + else if (full_path.find("\\??\\") != std::string::npos) + full_path.replace(full_path.find("\\??\\"), + sizeof("\\??\\") - 1, ""); + + if (!callback(&modules->Modules[idx], full_path.c_str())) + { + VirtualFree(buffer, NULL, MEM_RELEASE); + return; + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return; + } + + + auto get_export(const char* drv_name, const char* export_name)->std::uintptr_t + { + void* buffer = nullptr; + DWORD buffer_size = NULL; + + NTSTATUS status = NtQuerySystemInformation( + static_cast(0xB), + buffer, + buffer_size, + &buffer_size + ); + + while (status == STATUS_INFO_LENGTH_MISMATCH) + { + VirtualFree(buffer, 0, MEM_RELEASE); + buffer = VirtualAlloc(nullptr, buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + status = NtQuerySystemInformation( + static_cast(0xB), + buffer, + buffer_size, + &buffer_size + ); + } + + if (!NT_SUCCESS(status)) + { + VirtualFree(buffer, 0, MEM_RELEASE); + return NULL; + } + + const auto modules = static_cast(buffer); + for (auto idx = 0u; idx < modules->NumberOfModules; ++idx) + { + // find module and then load library it + const std::string current_module_name = + std::string(reinterpret_cast( + modules->Modules[idx].FullPathName) + + modules->Modules[idx].OffsetToFileName + ); + + if (!_stricmp(current_module_name.c_str(), drv_name)) + { + auto full_path = std::string( + reinterpret_cast( + modules->Modules[idx].FullPathName)); + + full_path.replace(full_path.find("\\SystemRoot\\"), + sizeof("\\SystemRoot\\") - 1, std::string(getenv("SYSTEMROOT")).append("\\")); + + const auto module_base = + LoadLibraryExA( + full_path.c_str(), + NULL, + DONT_RESOLVE_DLL_REFERENCES + ); + + const auto image_base = + reinterpret_cast( + modules->Modules[idx].ImageBase); + + // free the RTL_PROCESS_MODULES buffer... + VirtualFree(buffer, NULL, MEM_RELEASE); + + const auto rva = + reinterpret_cast( + GetProcAddress(module_base, export_name)) - + reinterpret_cast(module_base); + + return image_base + rva; + } + } + + VirtualFree(buffer, NULL, MEM_RELEASE); + return NULL; + + } + private: + explicit km_t() {} + }; + + class pe_t + { + using section_callback_t = std::function; + public: + static auto get_instance() -> pe_t* { static pe_t obj; return &obj; } + + // returns an std::vector containing all of the bytes of the section + // and also the RVA from the image base to the beginning of the section... + auto get_section(std::uintptr_t module_base, + const char* section_name) -> std::pair, std::uint32_t> + { + const auto nt_headers = reinterpret_cast( + reinterpret_cast(module_base)->e_lfanew + module_base); + + const auto section_header = + reinterpret_cast( + reinterpret_cast(nt_headers) + sizeof(DWORD) + + sizeof(IMAGE_FILE_HEADER) + nt_headers->FileHeader.SizeOfOptionalHeader); + + for (auto idx = 0u; idx < nt_headers->FileHeader.NumberOfSections; ++idx) + { + const auto _section_name = + reinterpret_cast( + section_header[idx].Name); + + // sometimes section names are not null terminated... + if (!strncmp(_section_name, section_name, strlen(section_name) - 1)) + { + const auto section_base = + reinterpret_cast( + module_base + section_header[idx].VirtualAddress); + + const auto section_end = + reinterpret_cast( + section_base + section_header[idx].Misc.VirtualSize); + + std::vector section_bin(section_base, section_end); + return { section_bin, section_header[idx].VirtualAddress }; + } + } + + return { {}, {} }; + } + + void each_section(section_callback_t callback, std::uintptr_t module_base) + { + if (!module_base) + return; + + const auto nt_headers = reinterpret_cast( + reinterpret_cast(module_base)->e_lfanew + module_base); + + const auto section_header = + reinterpret_cast( + reinterpret_cast(nt_headers) + sizeof(DWORD) + + sizeof(IMAGE_FILE_HEADER) + nt_headers->FileHeader.SizeOfOptionalHeader); + + for (auto idx = 0u; idx < nt_headers->FileHeader.NumberOfSections; ++idx) + { + const auto _section_name = + reinterpret_cast( + section_header[idx].Name); + + // keep looping until the callback returns false... + if (!callback(§ion_header[idx], module_base)) + return; + } + } + private: + explicit pe_t() {}; + }; +} \ No newline at end of file diff --git a/xtils/xtils.vcxproj b/xtils/xtils.vcxproj new file mode 100644 index 0000000..78a7dac --- /dev/null +++ b/xtils/xtils.vcxproj @@ -0,0 +1,63 @@ + + + + + Release + x64 + + + + 16.0 + Win32Proj + {47343e59-10b7-4f29-b7a0-561b106f7d0b} + xtils + 10.0 + + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + false + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Disabled + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/xtils/xtils.vcxproj.filters b/xtils/xtils.vcxproj.filters new file mode 100644 index 0000000..4863c55 --- /dev/null +++ b/xtils/xtils.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/xtils/xtils.vcxproj.user b/xtils/xtils.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/xtils/xtils.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file