I am trying to inject a dylib into a game called Roblox and have found the memory addresses neccessary already. When I inject using DYLD_INSERT_LIBRARIES, the code does nothing and gives the error defined in my code: "Failed to get function pointers."
I am building with the flags: -std=c++11 -dynamiclib -lc++ -framework CoreFoundation. and using gcc to compile. I am on MacOS and have little coding experience.
main.cpp:
#include <cstdio>
#include <syslog.h>
#include <thread>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <exception>
#include <mach/mach.h> // For Mach-O headers
#include <mach-o/dyld.h> // For _dyld_get_image_vmaddr_slide
#include <dlfcn.h> // For dlopen and dlclose
enum colours : std::uint8_t
{
regular,
info,
warn,
error
};
// Typedef for the functions to be loaded from the executable library
using r_getdatamodel2_typedef = int* (*)();
using r_getdatamodel_typedef = int(*)(int* a1, int* a2);
// Function pointers for the functions to be loaded from the executable library
r_getdatamodel_typedef r_getdatamodel = nullptr;
r_getdatamodel2_typedef r_getdatamodel2 = nullptr;
// Function to print messages using syslog
void print(std::uint8_t color, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vsyslog(LOG_NOTICE, fmt, args);
va_end(args);
}
// Function to split a string into a vector of strings based on whitespaces
std::vector<std::string> split(std::string& str)
{
std::vector<std::string> myvector;
std::stringstream mystream(str);
while (!mystream.eof()) {
std::string newstr;
mystream >> newstr;
myvector.push_back(newstr);
}
return myvector;
}
// Function to find the first child instance with a given name
uintptr_t findfirstchild(uintptr_t instance, std::string childname)
{
uintptr_t childinfo = *reinterpret_cast<uintptr_t*>(instance + 0x30);
uintptr_t childstart = *reinterpret_cast<uintptr_t*>(childinfo + 0xC);
uintptr_t childend = *reinterpret_cast<uintptr_t*>(childinfo + 0x10);
for (auto i = childstart; i < childend; i += 8) {
uintptr_t instance = *reinterpret_cast<uintptr_t*>(i);
std::string* name = *reinterpret_cast<std::string**>(instance + 0x28);
if (!strcmp(name->c_str(), childname.c_str())) {
return instance;
}
}
std::cout << "no found!\n";
return 0;
}
// Entry point of the code (main function)
auto entry() -> void {
const auto printFunc = reinterpret_cast<void(*)(std::uint8_t, const char*, ...)>(_dyld_get_image_vmaddr_slide(0) + 0x100c46941);
void* handle = dlopen(nullptr, RTLD_NOW);
if (!handle) {
std::cerr << "Failed to open executable library." << std::endl;
return;
}
if (!r_getdatamodel || !r_getdatamodel2) {
std::cerr << "Failed to get function pointers." << std::endl;
dlclose(handle);
return;
}
// ... (existing code remains the same)
printFunc(colours::regular, "Function pointers loaded successfully.");
int a[2]{ 0 };
// Load the executable library
// Get the function pointers
r_getdatamodel = reinterpret_cast<r_getdatamodel_typedef>(dlsym(handle, "getDataModel"));
r_getdatamodel2 = reinterpret_cast<r_getdatamodel2_typedef>(dlsym(handle, "getDataModel2"));
// Check if the function pointers are valid
// Close the library handle after getting the function pointers
dlclose(handle);
// ...
// Get the print function pointer from the executable image slide
printFunc(colours::regular, "trying...");
// ...
// Define the function pointer for the setspeed function from the executable image slide
using setspeed_typedef = void(*)(int humanoid, float newspeed);
setspeed_typedef setspeed = reinterpret_cast<setspeed_typedef>(_dyld_get_image_vmaddr_slide(0) + 0x1032bbdc7);
// ...
// Call the r_getdatamodel function to get the data model pointer
r_getdatamodel(r_getdatamodel2(), a);
uintptr_t datamodel = a[0] + 0xC;
uintptr_t workspace = findfirstchild(datamodel, "Workspace");
uintptr_t localplayer = *reinterpret_cast<uintptr_t*>(findfirstchild(datamodel, "Players") + 0x128);
uintptr_t humanoid = findfirstchild(*reinterpret_cast<uintptr_t*>(localplayer + 0x7C), "Humanoid");
humanoid = findfirstchild(*reinterpret_cast<uintptr_t*>(localplayer + 0x7C), "Humanoid");
std::string store;
while (std::getline(std::cin, store)) {
std::vector<std::string> args = split(store);
if (args.size() != 0) {
if (!strcmp(args[0].c_str(), "gravity")) {
if (args.size() == 2) {
try {
printFunc(colours::warn, "gravity enabled?");
}
catch (std::exception e) {
std::cout << e.what() << std::endl;
printFunc(colours::warn, "gravity not enabled");
}
}
else {
printFunc(colours::warn, "Usage: gravity 100\n"); ;
}
}
else if (!strcmp(args[0].c_str(), "speed")) {
if (args.size() == 2) {
try {
humanoid = findfirstchild(*reinterpret_cast<uintptr_t*>(localplayer + 0x7C), "Humanoid");
int humanoidID = static_cast<int>(humanoid); // Perform static cast from uintptr_t to int
setspeed(humanoidID, std::stof(args[1]));
printFunc(colours::warn, "setspeed activated");
}
catch (std::exception e) {
std::cout << e.what() << std::endl;
}
}
else {
printFunc(colours::warn,"Usage: speed 100\n");
}
}
else if (!strcmp(args[0].c_str(), "jump")) {
}
else {
printFunc(colours::warn,"Invalid command!\n");
}
}
}
// Print success message and sleep for 100 milliseconds
printFunc(colours::regular, "success...");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// Constructor attribute to ensure the entry() function is called on library load
__attribute__((constructor))
static void dylib_main(void) {
std::thread{ entry }.detach();
}
(main.cpp is compiled into a dylib which is then injected into RobloxPlayer) I am getting these memory addresses from BinaryNinja Before anyone says anything, this is for educational purposes.
I have tried different ways of injection and tried to find other memory addresses to modify. I expect the speed inside the game to change and to print "success" inside the in-game console. I have the code the way it is because the game uses luau and the code changing it is: game.Players.LocalPlayer.Character.Humanoid.WalkSpeed = 100 i had to replicate in C++.