├── MemX ├── VMTInvoker.h ├── VMTHook.hpp └── MemX.h └── README.md /MemX/VMTInvoker.h: -------------------------------------------------------------------------------- 1 | // 2 | // VMTInvoker.h 3 | // 4 | // 5 | // Created by Euclid Jan Guillermo on 3/12/25. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Base VMT Invoker 17 | template 18 | class VMTInvokerBase 19 | { 20 | protected: 21 | void* Instance; 22 | int32_t FunctionIndex; 23 | FuncType* OriginalFunction; 24 | 25 | public: 26 | VMTInvokerBase(void* instance, int32_t index) : Instance(instance), FunctionIndex(index), OriginalFunction(nullptr) {} 27 | 28 | template 29 | auto Invoke(Args&&... args) const -> decltype(auto) 30 | { 31 | return (*OriginalFunction)(std::forward(args)...); 32 | } 33 | }; 34 | 35 | template 36 | class VMTInvoker : public VMTInvokerBase 37 | { 38 | public: 39 | using VMTInvokerBase::Instance; 40 | using VMTInvokerBase::FunctionIndex; 41 | using VMTInvokerBase::OriginalFunction; 42 | 43 | VMTInvoker(void* instance, int32_t index) : VMTInvokerBase(instance, index) 44 | { 45 | void** VTable = *reinterpret_cast(instance); 46 | OriginalFunction = reinterpret_cast(VTable[FunctionIndex]); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /MemX/VMTHook.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Credits to: ZarakiDev 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | template 15 | class VMTHook 16 | { 17 | private: 18 | void** OriginalVTable; 19 | void** DetourVTable; 20 | FuncType* OriginalFunction; 21 | FuncType* NewFunction; 22 | int32_t FunctionIndex; 23 | void* HookedInstance; 24 | 25 | int GetNumMethods(void** VTable) const 26 | { 27 | int Count = 0; 28 | while (VTable[Count]) { ++Count; } 29 | return Count; 30 | } 31 | 32 | bool IsValidPointer(uintptr_t address) { 33 | return address >= 0x100000000 && address < 0x3000000000; 34 | } 35 | 36 | public: 37 | VMTHook(FuncType* NewFunc, int32_t Index) : NewFunction(NewFunc), FunctionIndex(Index), HookedInstance(nullptr) {} 38 | 39 | void Swap(void* Class) 40 | { 41 | if (!Class || !NewFunction) 42 | return; 43 | 44 | void** VTable = *reinterpret_cast(Class); 45 | if (!VTable || VTable[FunctionIndex] == NewFunction || !IsValidPointer(reinterpret_cast(VTable))) 46 | return; 47 | 48 | // If the instance changed, reset first 49 | if (HookedInstance && HookedInstance != Class) 50 | { 51 | Reset(HookedInstance); 52 | } 53 | 54 | int MethodCount = GetNumMethods(VTable); 55 | DetourVTable = static_cast(malloc(MethodCount * sizeof(void*))); 56 | if (!DetourVTable) 57 | return; 58 | 59 | memcpy(DetourVTable, VTable, MethodCount * sizeof(void*)); 60 | OriginalVTable = VTable; 61 | OriginalFunction = reinterpret_cast(DetourVTable[FunctionIndex]); 62 | DetourVTable[FunctionIndex] = reinterpret_cast(NewFunction); 63 | *(void***)Class = DetourVTable; 64 | HookedInstance = Class; 65 | } 66 | 67 | void Reset(void* Class) 68 | { 69 | if (Class && OriginalVTable) 70 | { 71 | *(void***)Class = OriginalVTable; 72 | } 73 | if (DetourVTable) 74 | { 75 | free(DetourVTable); 76 | DetourVTable = nullptr; 77 | } 78 | OriginalVTable = nullptr; 79 | HookedInstance = nullptr; 80 | } 81 | 82 | template 83 | auto InvokeOriginal(Args&&... args) const -> decltype(auto) 84 | { 85 | return (*OriginalFunction)(std::forward(args)...); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MemX - Memory Manipulation and VMT Hooking Utility 2 | 3 | ## Overview 4 | MemX is a lightweight memory manipulation utility designed for reading, writing, and hooking functions in iOS applications. It provides efficient and safe methods to interact with memory while ensuring proper pointer validation and optimized memory operations. 5 | 6 | ## Features 7 | - Retrieve the base address of a loaded image. 8 | - Validate memory addresses to prevent crashes. 9 | - Read raw memory into buffers. 10 | - Read specific data types from memory. 11 | - Read strings safely with null termination handling. 12 | - Write values directly into memory. 13 | - Virtual Method Table (VMT) hooking for function swapping. 14 | - Safe invocation of original VMT functions. 15 | 16 | ## Usage 17 | ### Get Image Base Address 18 | ```cpp 19 | uintptr_t base = MemX::GetImageBase("ShooterGame"); 20 | ``` 21 | Retrieves the base address of the specified image. 22 | 23 | ### Read Memory 24 | ```cpp 25 | int value = MemX::Read(some_address); 26 | std::string str = MemX::ReadString(reinterpret_cast(string_address), 32); 27 | ``` 28 | Reads a value of type `T` from the given memory address. Supports reading strings with length constraints. 29 | 30 | ### Write Memory 31 | ```cpp 32 | MemX::Write(some_address, 100); 33 | ``` 34 | Writes an integer value to the specified memory address. 35 | 36 | ### VMT Hooking Example 37 | ```cpp 38 | #include "MemX.h" 39 | #include "VMTHook.h" 40 | 41 | static void MyControllerHook(UObject* object, UFunction* function, void* params); 42 | static VMTHook ControllerHookInstance(&MyControllerHook, 69); // Index 69th of the VTable 43 | 44 | // Hooked Function 45 | static void MyControllerHook(UObject* object, UFunction* function, void* params) { 46 | string name = function->GetName(); 47 | if (name == "ClientTravelInternal") { 48 | delayTrigger = true; 49 | } 50 | 51 | 52 | ControllerHookInstance.InvokeOriginal(object, function, params); 53 | } 54 | 55 | static void HookController() { 56 | uintptr_t MyController = GetMyController(); 57 | if (!MemX::IsValidPointer(MyController)) return; 58 | // MyController is a Class Instance of APlayerController. 59 | // Swapping 60 | ControllerHookInstance.Swap(reinterpret_cast(MyController)); 61 | } 62 | ``` 63 | Swaps the function at index `69` in the VMT of `APlayerController` with `HookedFunction`, executes the function, and then restores the original function. 64 | 65 | ### Reset Hook 66 | ```cpp 67 | hook.Reset(some_instance); 68 | ``` 69 | Restores the original VMT of `some_instance`. 70 | 71 | ### Invoke Original Function 72 | ```cpp 73 | hook.InvokeOriginal(); 74 | ``` 75 | Calls the original function before it was hooked. 76 | 77 | ### VMT Invoker Usage 78 | ```cpp 79 | // Calling Process Event 80 | // Assuming ProcessEvent is defined as: void ProcessEvent(uintptr_t Object, uintptr_t Function, void* Params); 81 | VMTInvoker invoker(some_instance, 69); // Assuming ProcessEvent is at 69th Index 82 | 83 | // Call ProcessEvent via VMTInvoker 84 | uintptr_t object = 0x12345678; 85 | uintptr_t function = 0x87654321; 86 | void* params = nullptr; 87 | 88 | invoker.Invoke(object, function, params); 89 | ``` 90 | Creates a VMT invoker to call a function at index `69` of `some_instance`. 91 | 92 | ## Safety Considerations 93 | - **Pointer Validation:** All memory accesses are validated using `IsValidPointer()` to ensure stability. 94 | - **Buffer Protection:** `ReadString()` ensures null-terminated strings to prevent buffer overflows. 95 | - **Restricted Address Range:** Ensures only safe memory regions are accessed within the valid iOS range. 96 | - **Proper Hook Management:** Hooks should be reset when no longer needed to prevent instability. 97 | 98 | ## License 99 | This utility is provided as-is without any warranties. Use it responsibly within legal and ethical boundaries. 100 | 101 | ## Disclaimer 102 | Memory manipulation and function hooking may violate the terms of service of certain applications. The author is not responsible for any misuse of this utility. 103 | 104 | -------------------------------------------------------------------------------- /MemX/MemX.h: -------------------------------------------------------------------------------- 1 | // 2 | // MemX.h 3 | // 4 | // Created by Aethereux on 3/9/25. 5 | // 6 | #pragma once 7 | 8 | #include 9 | #include // For memcpy 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #import 19 | 20 | 21 | namespace MemX { 22 | /* 23 | Example Usage: 24 | uintptr_t base = MemX::GetImageBase("ShooterGame"); 25 | */ 26 | static uintptr_t GetImageBase(const std::string& imageName) { 27 | static uintptr_t imageBase = 0; 28 | 29 | if (imageBase) return imageBase; 30 | 31 | for (uint32_t i = 0; i < _dyld_image_count(); ++i) { 32 | const char* dyldImageName = _dyld_get_image_name(i); 33 | if (strstr(dyldImageName, imageName.c_str())) { 34 | imageBase = reinterpret_cast(_dyld_get_image_header(i)); 35 | break; 36 | } 37 | } 38 | return imageBase; 39 | } 40 | struct AddrRange { 41 | uintptr_t start; 42 | uintptr_t end; 43 | }; 44 | 45 | // From Titanox's version 46 | // https://developer.apple.com/documentation/kernel/mach_header/ 47 | inline const std::vector& GetFullAddr() { 48 | static std::vector ranges; 49 | // we just need to get ranges once 50 | // calling over n over is redundant 51 | if (!ranges.empty()) { 52 | return ranges; 53 | } 54 | 55 | for (uint32_t i = 0; i < _dyld_image_count(); ++i) { 56 | const mach_header* header = _dyld_get_image_header(i); 57 | intptr_t slide = _dyld_get_image_vmaddr_slide(i); 58 | if (!header) continue; 59 | 60 | const uint8_t* ptr = reinterpret_cast(header); 61 | const load_command* cmd = nullptr; 62 | uint32_t ncmds = 0; 63 | 64 | switch (header->magic) { 65 | // ncmds -> https://developer.apple.com/documentation/kernel/mach_header/1525650-ncmds 66 | //64-bit 67 | case MH_MAGIC_64: { 68 | const auto* hdr = reinterpret_cast(ptr); 69 | cmd = reinterpret_cast(hdr + 1); 70 | ncmds = hdr->ncmds; 71 | break; 72 | } 73 | //32-bit 74 | case MH_MAGIC: { 75 | const auto* hdr = reinterpret_cast(ptr); 76 | cmd = reinterpret_cast(hdr + 1); 77 | ncmds = hdr->ncmds; 78 | break; 79 | } 80 | default: 81 | continue; 82 | } 83 | 84 | for (uint32_t j = 0; j < ncmds; ++j) { 85 | switch (cmd->cmd) { 86 | // https://developer.apple.com/documentation/kernel/segment_command_64 87 | // goes through the load commands 88 | case LC_SEGMENT_64: { 89 | const auto* seg = reinterpret_cast(cmd); 90 | uintptr_t start = static_cast(seg->vmaddr + slide); // ASLR start 91 | uintptr_t end = start + static_cast(seg->vmsize); // vmsize is end 92 | ranges.push_back({start, end}); 93 | break; 94 | } 95 | default: 96 | break; 97 | } 98 | cmd = reinterpret_cast( 99 | reinterpret_cast(cmd) + cmd->cmdsize); 100 | } 101 | } 102 | return ranges; 103 | } 104 | 105 | // better 'IsValidPointer' 106 | inline bool IsValidPointer(uintptr_t addr) { 107 | const auto& ranges = GetFullAddr(); 108 | for (const auto& r : ranges) { 109 | if (addr >= r.start && addr < r.end) { 110 | return true; 111 | } 112 | } 113 | return false; 114 | } 115 | 116 | inline bool _read(uintptr_t addr, void* buffer, size_t len) { 117 | if (!IsValidPointer(addr)) { 118 | return false; 119 | } 120 | 121 | std::memcpy(buffer, reinterpret_cast(addr), len); 122 | return true; 123 | } 124 | 125 | template 126 | inline T Read(uintptr_t address) { 127 | T data{}; 128 | if (!_read(address, &data, sizeof(T))) { 129 | return T{}; 130 | } 131 | return data; 132 | } 133 | 134 | inline std::string ReadString(void* address, size_t max_len) { 135 | if (!IsValidPointer(reinterpret_cast(address))) { 136 | return "Invalid Pointer!!"; 137 | } 138 | 139 | std::vector chars(max_len + 1, '\0'); // Ensure null termination 140 | if (!_read(reinterpret_cast(address), chars.data(), max_len)) { 141 | return ""; 142 | } 143 | 144 | return std::string(chars.data()); // Handles null termination properly 145 | } 146 | 147 | template 148 | inline void Write(uintptr_t address, const T& value) { 149 | if (!IsValidPointer(address)) { 150 | return; 151 | } 152 | *reinterpret_cast(address) = value; 153 | } 154 | } // namespace MemX 155 | --------------------------------------------------------------------------------