diff --git a/.gitignore b/.gitignore index ae3318a..4c552e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build .clangd .zed +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 6625b35..91cf4e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,8 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(xrbx STATIC - src/xrbx.cpp + src/xrbx.cpp + src/process.cpp ) target_include_directories(xrbx diff --git a/include/xrbx/nt/nt.h b/include/xrbx/nt/nt.h new file mode 100644 index 0000000..9e6df72 --- /dev/null +++ b/include/xrbx/nt/nt.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "ntproc.h" + +namespace Nt { + inline HMODULE ntdll() { + static HMODULE mod = LoadLibraryA("ntdll.dll"); + return mod; + } + + template + T resolve(const char *name) { + static T fn = reinterpret_cast(GetProcAddress(ntdll(), name)); + return fn; + } +} diff --git a/include/xrbx/nt/ntproc.h b/include/xrbx/nt/ntproc.h new file mode 100644 index 0000000..275d737 --- /dev/null +++ b/include/xrbx/nt/ntproc.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +namespace Nt { + typedef NTSTATUS(NTAPI* OpenProcess)( + PHANDLE ProcessHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + CLIENT_ID *ClientId + ); + + typedef NTSTATUS(NTAPI* DuplicateObject)( + HANDLE SourceProcessHandle, + HANDLE SourceHandle, + HANDLE TargetProcessHandle, + PHANDLE TargetHandle, + ACCESS_MASK DesiredAccess, + ULONG HandleAttributes, + ULONG Options + ); + + typedef NTSTATUS(NTAPI *Close)( + HANDLE Handle + ); + + typedef NTSTATUS(NTAPI *QuerySystemInformation)( + SYSTEM_INFORMATION_CLASS, + PVOID, + ULONG, + PULONG + ); + + typedef NTSTATUS(NTAPI *QueryInformationProcess)( + HANDLE, + PROCESSINFOCLASS, + PVOID, + ULONG, + PULONG + ); + + struct PROCESS_BASIC_INFORMATION { + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + LONG BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; + }; + + typedef NTSTATUS(NTAPI *ReadVirtualMemory)( + HANDLE, + PVOID, + PVOID, + SIZE_T, + PSIZE_T + ); + + typedef struct _PEB_PARTIAL + { + BYTE Reserved1[4]; + PVOID Mutant; + PVOID ImageBaseAddress; + } PEB_PARTIAL; +} diff --git a/include/xrbx/process.h b/include/xrbx/process.h new file mode 100644 index 0000000..0d19e1a --- /dev/null +++ b/include/xrbx/process.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "nt/nt.h" +#include "xrbx/nt/ntproc.h" + +class Process { + private: + DWORD pid; + HANDLE proc; + BYTE *base; + + HANDLE openProcess(); + BYTE *getImageBase(void); + public: + static DWORD findProcessByName(const std::wstring &name); + + inline BYTE *imageBase(void) { + return base; + } + + template + inline T Read(BYTE *addr) { + T buf; + SIZE_T bytesRead; + NTSTATUS status = Nt::resolve("NtReadVirtualMemory")(proc, addr, &buf, sizeof(buf), &bytesRead); + if (status < 0) { + throw std::runtime_error(std::format("Failed to read {} bytes from {:X}", sizeof(buf), uintptr_t(addr))); + } + return buf; + } + + inline Process(const std::wstring &name) { + pid = findProcessByName(name); + if (pid == 0) { + throw std::runtime_error("Process not found"); + } + proc = openProcess(); + base = getImageBase(); + } + + inline ~Process() { + Nt::resolve("NtClose")(proc); + } +}; diff --git a/include/xrbx/xrbx.h b/include/xrbx/xrbx.h index b0abee2..8ebc5fe 100644 --- a/include/xrbx/xrbx.h +++ b/include/xrbx/xrbx.h @@ -1,3 +1,5 @@ #pragma once +#include "process.h" + void helloWorld(void); diff --git a/src/process.cpp b/src/process.cpp new file mode 100644 index 0000000..1f219ee --- /dev/null +++ b/src/process.cpp @@ -0,0 +1,70 @@ +#include "xrbx/process.h" + +HANDLE Process::openProcess() { + if (pid == 0) { + return nullptr; + } + + CLIENT_ID cid{}; + cid.UniqueProcess = (HANDLE)pid; + cid.UniqueThread = nullptr; + + OBJECT_ATTRIBUTES oa{}; + oa.Length = sizeof oa; + + HANDLE proc; + + if(Nt::resolve("NtOpenProcess")(&proc, PROCESS_ALL_ACCESS, &oa, &cid) < 0) { + throw std::runtime_error("Failed to open process"); + } + + HANDLE duplicate; + if(Nt::resolve("NtDuplicateObject")(GetCurrentProcess(), proc, GetCurrentProcess(), &duplicate, PROCESS_ALL_ACCESS, 0, 0) < 0) { + Nt::resolve("NtClose")(proc); + throw std::runtime_error("Failed to duplicate process"); + } + Nt::resolve("NtClose")(proc); + + return duplicate; +} + +BYTE *Process::getImageBase(void) { + Nt::PROCESS_BASIC_INFORMATION pbi{}; + + if (Nt::resolve("NtQueryInformationProcess")(proc, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr) < 0) { + throw std::runtime_error("Failed to get process information"); + } + + return (BYTE *)Read((BYTE *)pbi.PebBaseAddress).ImageBaseAddress; +} + +DWORD Process::findProcessByName(const std::wstring &name) { + auto NtQuerySystemInformation = Nt::resolve("NtQuerySystemInformation"); + + ULONG size = 0; + NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &size); + + std::vector buffer(size); + NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer.data(), size, &size); + if (status < 0) return 0; + + auto entry = reinterpret_cast(buffer.data()); + + while (true) { + if (entry->ImageName.Buffer) { + if (_wcsicmp(entry->ImageName.Buffer, name.c_str()) == 0) { + return DWORD(ULONG_PTR(entry->UniqueProcessId)); + } + } + + if (entry->NextEntryOffset == 0) + break; + + entry = reinterpret_cast( + reinterpret_cast(entry) + + entry->NextEntryOffset + ); + } + + return 0; +} diff --git a/tests/test.cpp b/tests/test.cpp index 196c9c2..21c718f 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1,6 +1,9 @@ #include "xrbx/xrbx.h" +#include +#include int main() { - helloWorld(); + Process rbx(L"notepad.exe"); + std::cout << std::format("Image Base: {:X}", uintptr_t(rbx.imageBase())) << std::endl; return 0; }