https://github.com/Thorioum/Charon
I remember, years ago, when I was one of many roblox hacker kids that would download Krnl, Fluxus, Synapse X, and go crazy on games I joined. I would fly around, duplicate items, auto farm and it was great. The pinnacle of my hacking carrier on roblox involved using Fluxus, many android emulators, and a very long custom script that would automatically play the entire game of blox fruits on multiple accounts until they reached max level. From there I would then list each account on ebay for 15$, I was living the high life.
In the community eventually learned however that Roblox was not going to be turning a blind eye to these blatant hacks any longer, and in October of 2022 everyone eventually fell victim to Roblox's release of their Hyperion anti cheat. This killed every working commercial exploit for a while, and it was now up to the developers to take matters into their own hack to reverse engineer this software. Back in 2022, I decided I was not going to be one of those people however, now that I couldn't just hit an "Inject" and actually had to put some effort or even money into buying or developing some exploits, my motivation was killed, and I left hacking on roblox in the dust.
About 1 month ago, with a random spark of refuelled passion in exploiting and reverse engineering, I decided I would shoot my shot at looking closer into Roblox's anti cheat to see what I could learn
Injection, where to start?
After browsing lots of discords and forums I have found that there are plenty of various techniques that people use to inject dll's in processes, and I eventually figured out it would be up to me to pick some options and try to make them work.
I learned about IAT hooking, EAT hooking, inline function hooking, data ptr swap, APC injection, and lots of others but eventually decided to settle on inline function hooking.
During my research I stumbled upon a mmap-injector that gave me a great framework into how inline function hooking with manual map injection could work: github.com/YuB-W/RBX-MMP-Injector
I didn't want to just shamelessly copy code without learning anything, so I began studying every line of code grasping the key concepts I could pick out. This injector didn't even work on my PC but it still provided a good resource.
The plan was formulated. Pick a WINAPI function to overwrite with a jump to a detour, this detour function would contain assembly that would fix load and fix import references in the allocated DLL. Then the detour function would call the DLL entry point, then to make sure control flow remained, I would use the original bytes of the function that was overwritten and jump back to where it was supposed to be executing. This was essentially like adding extra code to a function.
Analysis
The injector I inspected previously made a inline function hook to NtQuerySystemInformation in ntdll.dll, but not directly. Roblox had picked a variety of functions in various loaded modules to hook themselves. Some of the functions they hooked involved jumping from the ntdll.dll function, to another piece of allocated memory that contains an absolute jump to then another piece of allocated memory. in this final section, some checks that admittedly am not smart enough to understand myself are made, and control flow is eventually redirected back to the origial ntdll.dll function with two more jumps.
In order to properly analyze all the hooks roblox had implemented in their process, I made a program that would scan a process for differences in bytes between modules loaded in a target process, and the local unmodified process. It would be able to detect inline function hooks, EAT hooks, and IAT hooks. From there I could then dissasemble the addresses, as well as follow all the jmps that were overwritten there to be able to anaylze where control flow was going. Thors-Hook-Detector
Provided below is an overview of how the hook in NtQuerySystemInformation works.
This same system of jmp's is also on a variety of different of roblox's hooked functions, and you could pick any of them. As it turned out, Roblox implemented some black magic that made it so running VirtualProtect on the code in their ntdll.dll would fail, and you could not make it writable externally. This means that hooking this function was a no go. As you can see in the second image however, there is a second jmp that is made before reaching roblox's check, and this jmp would be made the target for our hook.
the hook would jmp to our detour, the detour would setup and call DllMain, then the detour would jmp to where the jmp it overwrote was originally pointing, restoring control flow.
Creating the program
When making the detour itself I decided to take inspiration in the structure the earlier injector used for stack creation. This involved having a placeholder stack pointer in the bytecode that at first doesnt point to anything (because we havent allocated the memory so we dont know where to point it yet), but then after the detour is allocated, we make sure a little extra space is at the end to put all the values we want on the stack. Then after its allocated we can fill the stack placeholder with the address that we now know. The stack contains values that we want the detour to be able to use, like a pointer to the loadlibrary function, or the getmodulehandle function.
A simplified version of the detour function is this.
#pragma optimize("", off); void dllDetourFunc() { DEFINE_STACK; ULONGLONG dll = (ULONGLONG)(Stack[0]); DllMain(reinterpret_cast<HMODULE>(dll), DLL_PROCESS_ATTACH, NULL); END_MARKER; } #pragma optimize("", on);
#pragma optimize("", off); will turn off opimizations, allowing our placeholder bytes to not be removed from the compiler
DEFINE_STACK; is a macro that translates to this
void** Stack = *const_cast<void***>(&reinterpret_cast<const void**>(STACK_PLACEHOLDER))
, and defines our stack pointer. STACK_PLACEHOLDER is the set of bytes we can sig scan for and replace with our correct stack address
END_MARKER; is just simply a unique set of instructions that wouldnt appear normally we can sig scan for them, and properly estimate the end point of our function
Essentially we will scan the bytes of the dllDetourFunc, loaded into our local processes memory and copy those bytes into an allocation into Roblox, and then fill the stack. Using our function size estimation, we also replace the ret instruction with an absolute jump back to the original. I also added a few instructions to the beginning of my hook to immediatly restore the original jmp, ensuring it isnt run a second time
There is some other vital code that must be added to the detour function as well before the dll call, and that is to fix relocations and imports in the dll. i push pointers to the functions GetProcAddress, LoadLibraryA, and GetModuleHandleA to our detours custom stack
so that they may be used in the detour to parse the dll's import address table, and patch it to the correct addresses that are in Roblox.
The last step is to fix the relocation table because the dll is most likely not loaded at its preferred address, so relative pointers are inaccurate and need to be fixed
Also, I changed my hook to hook a function GetCurrentProcessId in KERNELBASE. Mostly because calls to this function and then to my detour, seemed to be on a main thread. This would mean when debugging errors in my detour, roblox would crash entirely and output the exit code for me to use
So the steps are: allocate the dll and write its bytes, allocate the detour and write its bytes, and overwrite the jmp in the Roblox allocation to point to our detour, which will then restore the jmp immediatly, patch and run our dll, and restore control flow and return back to the original jmp.
There are a few detection measures that roblox employes to prevent memory manipulation like this, and I didn't dive deep into what exactly it scans for, but I did just enough to get around it.
Byfron starts a few watcher threads that immediatly detects unwhitelisted executable memory and makes it unexecutable. It also scans threads and ends immediatly threads that deal in unwhitelisted regions. The thread thing isn't too much of a problem as this injector is essentially hijacking a thread by hooking a function. But to get around memory protection issues, I simply. . . suspended the threads that makes my memory unexecutable. I iterated all the threads in Roblox, and if it's start address was in the RobloxPlayerBeta.dll/byfron module, i suspend it. Now my memory stays executable as the hook runs.
This bypass didn't seem to last forever though, as either the byfron threads restart or theres another one I dont know about making it unexecutable. If your dll doesn't need to run for long or in a loop, then these techniques will work just fine. I added one final thing to the end however, which is a while loop that spams virtualprotectex on the target dll, which suprisingly works in the race condition of keeping the memory executable.
End
Injection protections cant be too great on usermode anti cheats. Making something in an injected dll to actually execute luau code is a whole other story and is arguablly harder. And yes there is about an 100% chance what i'm doing is getting flagged and im getting banned in the next wave.
But thats okay right? The ban waves are months apart who cares.
The way they ban you, is they detect tampering and send a packet to roblox servers to get you flagged. This simple idea that it's just a packet sparks the idea that maybe a proxy could be made to capture packets, and block these flagging packets. Then no matter how bad an injector is you will never get any consequences. Maybe i'll look into it later
Code to the injector is listed under the Charon repo on my github.
If you want to learn about making injectors that work for longer and are undetected, I suggest joining discords, reading forums, or checking out writeups. I'll list some good ones below
https://discord.gg/KfdkZvJ6dK https://blog.nestra.tech/ https://v3rm.net/