diff --git a/include/xrbx/engine/classdescriptor.h b/include/xrbx/engine/classdescriptor.h new file mode 100644 index 0000000..0ab25d3 --- /dev/null +++ b/include/xrbx/engine/classdescriptor.h @@ -0,0 +1,43 @@ +#pragma once + +#include "xrbx/engine/common.h" +#include "xrbx/process.h" +#include "offsets.h" +#include +#include + +namespace RBX { + class PropertyDescriptor : public InternalObjectWrapper { + public: + PropertyDescriptor(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {} + + inline std::string getName() const { + return process().readCPPString(process().read(VA() + OFF_PD_NAME)); + } + }; + + class ClassDescriptor : public InternalObjectWrapper { + public: + ClassDescriptor(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {} + + inline std::string getName() const { + try { + return process().readCPPString(process().read(VA() + OFF_CD_NAME)); + } catch (...) { + throw std::runtime_error(std::format("Failed to read class name for class @ %llx", uintptr_t(VA()))); + } + } + + inline std::vector getPropertyDescriptors() const { + std::vector propdescs; + BYTE *start = process().read(VA() + OFF_CD_PROP_DESCRIPTORS); + uint64_t length = process().read(VA() + OFF_CD_PROP_DESCRIPTORS + 8); + if (start == 0) return propdescs; + for (BYTE *current = start; (current - start) / 2 < length; current += 16) { + PropertyDescriptor pd(process().read(current), process()); + propdescs.push_back(pd); + } + return propdescs; + } + }; +} diff --git a/include/xrbx/engine/common.h b/include/xrbx/engine/common.h new file mode 100644 index 0000000..2d63ac1 --- /dev/null +++ b/include/xrbx/engine/common.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../process.h" + +namespace RBX { + class InternalObjectWrapper { + private: + BYTE *addr; + const Process &proc; + public: + InternalObjectWrapper(BYTE *va, const Process &process) : addr(va), proc(process) {} + inline BYTE *VA(void) const { return addr; } + inline const Process &process(void) const { return proc; } + }; +} diff --git a/include/xrbx/engine/instance.h b/include/xrbx/engine/instance.h new file mode 100644 index 0000000..74cecc1 --- /dev/null +++ b/include/xrbx/engine/instance.h @@ -0,0 +1,59 @@ +#pragma once + +#include "xrbx/process.h" +#include "offsets.h" +#include "common.h" +#include +#include +#include +#include +#include + +#include "classdescriptor.h" + +namespace RBX { + class Instance : public InternalObjectWrapper { + public: + Instance(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {} + + inline std::string getName() const { + return process().readCPPString(process().read(VA() + OFF_INSTANCE_NAME)); + } + + inline std::optional getParent() const { + BYTE *parent_va = process().read(VA() + OFF_INSTANCE_PARENT); + if (parent_va == 0) return std::nullopt; + return Instance(parent_va, process()); + } + + inline std::vector getChildren() const { + try { + return process().readVector(process().read(VA() + OFF_INSTANCE_CHILDREN)); + } catch (...) { + throw std::runtime_error(std::format("Failed to read children of instance '{}' @ {:x}", getName(), uintptr_t(VA()))); + } + } + + inline std::optional findFirstChild(const std::string &name) { + return process().readVectorElement(process().read(VA() + OFF_INSTANCE_CHILDREN), [&name](const Instance &inst) -> bool { + return inst.getName() == name; + }); + } + + inline std::optional findFirstChildOfClass(const std::string &name) { + return process().readVectorElement(process().read(VA() + OFF_INSTANCE_CHILDREN), [&name](const Instance &inst) -> bool { + return inst.getClassName() == name; + }); + } + + inline ClassDescriptor getClassDescriptor() const { + BYTE *va = process().read(VA() + OFF_INSTANCE_CLASS_DESCRIPTOR); + if (va == 0) throw std::runtime_error("invalid class"); + return ClassDescriptor(va, process()); + } + + inline std::string getClassName() const { + return getClassDescriptor().getName(); + } + }; +} diff --git a/include/xrbx/engine/offsets.h b/include/xrbx/engine/offsets.h index 47c178e..95703ea 100644 --- a/include/xrbx/engine/offsets.h +++ b/include/xrbx/engine/offsets.h @@ -6,3 +6,14 @@ #define RVA_TASK_SCHEDULER RVA(0x1484A58E0) // "AllTaskSchedulerJobs" #define OFF_SCHEDULER_JOBS 0xC8 // "TaskScheduler::Job:" #define OFF_JOB_NAME 0xF0 // "{}({};{})" +#define OFF_JOB_RUNSERVICE 0x1A0 + +#define OFF_INSTANCE_NAME 0x98 +#define OFF_INSTANCE_PARENT 0x68 +#define OFF_INSTANCE_CHILDREN 0x70 +#define OFF_INSTANCE_CLASS_DESCRIPTOR 0x18 + +#define OFF_CD_NAME 0x8 +#define OFF_CD_PROP_DESCRIPTORS 0x40 + +#define OFF_PD_NAME 0x8 diff --git a/include/xrbx/engine/scheduler.h b/include/xrbx/engine/scheduler.h index 7ce634e..a40846c 100644 --- a/include/xrbx/engine/scheduler.h +++ b/include/xrbx/engine/scheduler.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -8,45 +9,42 @@ #include "offsets.h" #include "../process.h" +#include "instance.h" +#include "xrbx/engine/common.h" namespace RBX { - class Scheduler { - private: - BYTE *addr; - const Process &proc; + class Scheduler : public InternalObjectWrapper { public: - class Job { - private: - BYTE *addr; - const Process &proc; + class Job : public InternalObjectWrapper { public: - Job(BYTE *va, const Process &process) : addr(va), proc(process) {} - - const BYTE * const VA() const { return addr; } + Job(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {} inline std::string getName() const { - BYTE *name_ptr = addr + OFF_JOB_NAME; - uint64_t length = proc.Read(name_ptr + 8); + BYTE *name_ptr = VA() + OFF_JOB_NAME; + uint64_t length = process().read(name_ptr + 8); std::string name(length, '\0'); - proc.ReadBytes(proc.Read(name_ptr), (BYTE *)name.data(), length); + process().readBytes(process().read(name_ptr), (BYTE *)name.data(), length); name[length] = 0; return name; } + + inline Instance getRunService() { + return Instance(process().read(VA() + OFF_JOB_RUNSERVICE), process()); + } }; - Scheduler(BYTE *va, const Process &process) : addr(va), proc(process) {} + Scheduler(BYTE *va, const Process &process) : InternalObjectWrapper(va, process) {} inline std::vector getJobs() const { - std::vector jobs; - BYTE *jobsStart = proc.Read(addr + OFF_SCHEDULER_JOBS); - BYTE *jobsEnd = proc.Read(addr + OFF_SCHEDULER_JOBS + 8); - for (BYTE *job = jobsStart; job != jobsEnd; job += 16) { - Job j(proc.Read(job), proc); - jobs.push_back(j); - } - return jobs; + return process().readVector(VA() + OFF_SCHEDULER_JOBS); } - Job getJob(const std::string &name); + Job getJob(const std::string &name) const { + auto j = process().readVectorElement(VA() + OFF_SCHEDULER_JOBS, [&name](const Job &job) -> bool { + return job.getName() == name; + }); + if (!j.has_value()) throw std::runtime_error(std::format("Job '{}' not found", name)); + return j.value(); + } }; } diff --git a/include/xrbx/nt/ntproc.h b/include/xrbx/nt/ntproc.h index 275d737..80adf0a 100644 --- a/include/xrbx/nt/ntproc.h +++ b/include/xrbx/nt/ntproc.h @@ -63,4 +63,70 @@ namespace Nt { PVOID Mutant; PVOID ImageBaseAddress; } PEB_PARTIAL; + + typedef struct _SYSTEM_THREAD_INFORMATION + { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + ULONG WaitTime; + PVOID StartAddress; + CLIENT_ID ClientId; + LONG Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + ULONG WaitReason; + } SYSTEM_THREAD_INFORMATION; + + + typedef struct _SYSTEM_PROCESS_INFORMATION + { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + + BYTE Reserved1[48]; + + UNICODE_STRING ImageName; + + KPRIORITY BasePriority; + + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + + ULONG HandleCount; + ULONG SessionId; + + SIZE_T UniqueProcessKey; + + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + + ULONG PageFaultCount; + + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + + SIZE_T PrivatePageCount; + + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + + SYSTEM_THREAD_INFORMATION Threads[1]; + + } SYSTEM_PROCESS_INFORMATION; } diff --git a/include/xrbx/process.h b/include/xrbx/process.h index 1225555..ecae3a3 100644 --- a/include/xrbx/process.h +++ b/include/xrbx/process.h @@ -1,6 +1,10 @@ #pragma once +#include #include +#include +#include +#include #include #include #include @@ -20,13 +24,14 @@ class Process { BYTE *getImageBase(void); public: static DWORD findProcessByName(const std::wstring &name); + std::vector getThreads(); inline BYTE *imageBase(void) const { return base; } template - inline T Read(BYTE *addr) const { + inline T read(BYTE *addr) const { T buf; SIZE_T bytesRead; NTSTATUS status = Nt::resolve("NtReadVirtualMemory")(proc, addr, &buf, sizeof(buf), &bytesRead); @@ -36,7 +41,7 @@ class Process { return buf; } - inline void ReadBytes(BYTE *addr, BYTE *buf, size_t bytes) const { + inline void readBytes(BYTE *addr, BYTE *buf, size_t bytes) const { SIZE_T bytesRead; NTSTATUS status = Nt::resolve("NtReadVirtualMemory")(proc, addr, buf, bytes, &bytesRead); if (status < 0) { @@ -44,6 +49,39 @@ class Process { } } + inline std::string readCPPString(BYTE *addr) const { + uint64_t length = read(addr + 16); + uint64_t capacity = read(addr + 24); + std::string str(length, '\0'); + readBytes(capacity < 16 ? addr : read(addr), (BYTE *)str.data(), length); + return str; + } + + template + inline std::vector readVector(BYTE *addr, uint8_t esz = 16) const { + std::vector vec; + if (addr == 0) return vec; + BYTE *start = read(addr); + BYTE *end = read(addr + 8); + for (BYTE *el = start; el != end; el += esz) { + T o(read(el), *this); + vec.push_back(o); + } + return vec; + } + + template + inline std::optional readVectorElement(BYTE *addr, std::function filter, uint8_t esz = 16) const { + if (addr == 0) return std::nullopt; + BYTE *start = read(addr); + BYTE *end = read(addr + 8); + for (BYTE *el = start; el != end; el += esz) { + T o(read(el), *this); + if (filter(o)) return o; + } + return std::nullopt; + } + inline BYTE *VA(uintptr_t rva) const { return base + rva; } diff --git a/src/process.cpp b/src/process.cpp index 1f219ee..2506c37 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -35,7 +35,7 @@ BYTE *Process::getImageBase(void) { throw std::runtime_error("Failed to get process information"); } - return (BYTE *)Read((BYTE *)pbi.PebBaseAddress).ImageBaseAddress; + return (BYTE *)read((BYTE *)pbi.PebBaseAddress).ImageBaseAddress; } DWORD Process::findProcessByName(const std::wstring &name) { diff --git a/tests/test.cpp b/tests/test.cpp index 51bbe40..a24d8e8 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1,16 +1,28 @@ +#include "xrbx/engine/instance.h" #include "xrbx/xrbx.h" #include "xrbx/engine/scheduler.h" +#include #include #include +void depth_first_print(RBX::Instance inst, int depth = 0) { + printf("%*s-> %s @ %llx (class %s @ %llx)\n", depth, "", inst.getName().c_str(), uintptr_t(inst.VA()), inst.getClassName().c_str(), uintptr_t(inst.getClassDescriptor().VA())); + for (const auto &prop : inst.getClassDescriptor().getPropertyDescriptors()) { + printf("%*s-> %s @ %llx\n", depth + 3, "", prop.getName().c_str(), uintptr_t(prop.VA())); + } + for (const auto &child : inst.getChildren()) { + depth_first_print(child, depth + 1); + } +} + int main() { Process rbx(L"RobloxPlayerBeta.exe"); std::cout << std::format("Image Base: {:X}", uintptr_t(rbx.imageBase())) << std::endl; - RBX::Scheduler sched(rbx.Read(rbx.VA(RVA_TASK_SCHEDULER)), rbx); + RBX::Scheduler sched(rbx.read(rbx.VA(RVA_TASK_SCHEDULER)), rbx); try { - for (const auto &job : sched.getJobs()) { - std::cout << std::format("Job {} @ {:x}", job.getName(), uintptr_t(job.VA())) << std::endl; - } + RBX::Scheduler::Job heartbeat = sched.getJob("Heartbeat(Heartbeat;LuaApp)"); + RBX::Instance runservice = heartbeat.getRunService(); + depth_first_print(runservice.getParent().value()); } catch (std::exception &e) { std::cout << "err: " << e.what() << std::endl; }