How to set up anti-tamper (protection) for the MU Online client
Harden your MU Online server's Main.exe against editing, injection and memory hacks by combining integrity checksums, anti-debug, module verification and a signed handshake with the server.
Protecting the MU Online client is a permanent arms race. On the other side are hex editors, DLL injectors, memory trainers and speed, wallhack and auto-combo cheats that take advantage of an unprotected Main.exe. Anti-tamper is the set of defenses that makes it harder to edit the binary, attach a d
Protecting the MU Online client is a permanent arms race. On the other side are hex editors, DLL injectors, memory trainers and speed, wallhack and auto-combo cheats that take advantage of an unprotected Main.exe. Anti-tamper is the set of defenses that makes it harder to edit the binary, attach a debugger, inject code and alter values in memory — and, just as important, lets the server detect when the client is not trustworthy. In this tutorial you will build these layers in a practical way: checksum integrity, anti-debug, verification of loaded modules, memory protection and a signed handshake with the server. All the concrete values (offsets, ports, section names) appear as examples and vary by season/client.
Prerequisites
Before hardening anything, you need a working base. If you are still building the server, start with how to create a MU Online server and come back here once the client already connects normally. Hardening a client that can't even log in yet only multiplies the variables when something breaks.
| Requirement | Detail (example — varies by season/client) |
|---|---|
| Working client | Main.exe that connects and logs in with no protection yet |
| Binary backup | Untouched copy Main_original.exe outside the client folder |
| Analysis tools | HxD, CFF Explorer, x64dbg, Process Hacker |
| Your own launcher | Executable that validates and starts the client (ideal) |
| Access to the ConnectServer | To add server-side validation |
| Isolated test environment | Clean machine, separate from the development one |
The least obvious item is your own launcher. A lot of protection is better placed in a component you control completely and can recompile at will, rather than trying to patch the Main.exe that came in the package. The launcher verifies integrity, talks to the server and only then launches the client.
Threat model: what you are protecting against
Before choosing techniques, define what the attacker wants to do. Generic protection wastes CPU and generates false positives; protection aimed at the real attack is worth the effort.
| Attack | Typical vector | Main defense |
|---|---|---|
| Edit IP/version in the binary | HxD, patcher | Integrity checksum |
| Attach a debugger | x64dbg, Cheat Engine | Anti-debug |
| Inject a cheat DLL | Injector, LoadLibrary | Module verification |
| Alter values in memory | Cheat Engine, trainer | Variable encryption/obfuscation + server-side validation |
| Speed hack | Clock accelerators | Time checking + server-side validation |
| Forge packets | Proxy, bot | Signed handshake + server-side validation |
Notice that server-side validation appears on almost every row. That is the central lesson: the client runs on the enemy's machine and will never be fully trustworthy. Anti-tamper raises the cost of the attack and feeds the server with signals; the final verdict belongs to the server.
Layer 1 — Checksum integrity
The cheapest defense against binary editing is to check whether it is bit-for-bit identical to what you distributed. Generate a strong hash of the original Main.exe and verify it in two places: in the launcher, before starting, and on the server, during the handshake.
Step 1 — Generate the reference hash
On Windows, with the final binary ready (already patched with the correct IP and version):
certutil -hashfile Main.exe SHA256
Keep that hash. It is the "truth" against which every client will be compared. Every time you re-edit Main.exe, the hash changes — automate the regeneration so you don't distribute a client with an outdated hash.
Step 2 — Verify in the launcher
The launcher computes the SHA-256 of the on-disk Main.exe and compares it with the expected value before executing. Example in C++ (sketch):
// Pseudocode — varies by season/client and by how you embed the hash
bool VerifyIntegrity(const wchar_t* path, const std::string& expectedHash) {
std::string currentHash = ComputeSHA256(path); // via CryptoAPI / bcrypt
if (currentHash != expectedHash) {
ShowError(L"Tampered client. Download again through the launcher.");
return false;
}
return true;
}
> Don't embed the expected hash as a plain-text string that is easy to find. Obfuscate it, split it into parts or derive it from something else. An attacker who locates the string simply swaps it for the hash of their edited binary.
Step 3 — Section checksum at runtime (self-check)
Beyond the on-disk check, the client itself can verify the checksum of its own code section (.text) in memory while running. This catches patches applied after loading (code injection). The idea: at build time, compute the CRC/hash of the .text section and, at runtime, recompute it periodically, comparing it with the stored value. If it diverges, report to the server.
Because .text is mapped with relocations and the expected value has to match, this check usually requires control of the build process or a protector that already does it for you (see Layer 6).
Layer 2 — Anti-debug
Cheat Engine and x64dbg rely on attaching a debugger to the process. Detecting that presence makes dynamic analysis more expensive.
Step 4 — Basic checks
// Classic examples — combine several, never rely on just one
bool DebuggerPresent() {
if (IsDebuggerPresent()) return true;
BOOL remote = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &remote);
if (remote) return true;
// PEB->BeingDebugged and NtGlobalFlag are also checked by deeper protections
return false;
}
Step 5 — Detect tools by window/process
Scan windows and processes for known signatures (class names and titles of Cheat Engine, x64dbg, OllyDbg, Process Hacker). Keep the list external and updatable, because names change.
> Don't kill the client on detection. Recording overlays, accessibility tools and even some antiviruses trigger these checks. The safe pattern is to report to the server with the signal and leave the ban to human analysis or accumulated heuristics. Killing the process on the spot is the recipe for a forum full of complaints from legitimate players.
Layer 3 — Verification of loaded modules
Cheats frequently enter as an injected DLL. Enumerating the process's modules and comparing them against an allowlist detects obvious injections.
Step 6 — Enumerate and classify modules
// Sketch — the allowlist varies A LOT by season/client and by the user's drivers
void ScanModules() {
// EnumProcessModules -> for each module, GetModuleFileName
// Classify: system (system32), the client's own, and "unknown"
// Unknown, unsigned modules -> signal to the server
}
Watch out for false positives: overlays (Discord, Steam, GPU), IMEs and audio software inject legitimate DLLs. That is why the ideal output is a report to the server with the suspicious modules, not a blind local block. Digitally signing and verifying the signature (Authenticode) greatly reduces the noise.
Layer 4 — Protecting values in memory
Trainers edit HP, zen, coordinates and speed directly in RAM with Cheat Engine. You can't stop the reading, but you can make the useful writing harder.
- Don't store sensitive values in plain text. Keep HP, zen and speed encrypted/obfuscated and decrypt them only at the point of use. Cheat Engine finds the value
1000easily; finding the encrypted value that changes representation, not so much. - Keep a shadow copy. Maintain a second derived value (a checksum) and, periodically, verify that they match. A divergence = someone wrote directly.
- Validate on the server. HP, zen, position and speed that exist only on the client are useless: the server must be the authority. If the client says it walked 50 cells in 100 ms, the server rejects it, regardless of any local protection.
Layer 5 — Signed handshake with the server
This is the layer the attacker does not control, and therefore the most valuable. Instead of trusting that the client is intact, the server demands proof each session.
Step 7 — Challenge-response on connection
- On connecting, the ConnectServer/GameServer sends a random nonce (single-use value).
- The client combines that nonce with the hash of its own binary and an embedded secret, and responds with an HMAC.
- The server recomputes the HMAC with the expected hash and the same secret. If they don't match, it refuses the session.
Server -> Client : nonce = 0x9F3A... (random per session)
Client -> Server : resp = HMAC_SHA256(secret, nonce || binaryHash)
Server : recomputes and compares. Different => drops the connection.
Because the nonce changes every session, an attacker can't record a valid response and replay it later (anti-replay). And because the server knows the expected binaryHash, an edited Main.exe produces a different HMAC and is blocked — even if the attacker has neutralized the local check.
> The secret embedded in the client is the weak link: whoever reverse-engineers the binary can extract it. Rotate the secret with every client update, combine it with build data and treat it as something that will eventually leak. The real strength comes from adding this to behavior validation on the server.
Layer 6 — Commercial packers and protectors
If you don't have the client's source code, the protectors applied to the finished binary deliver much of layers 1, 2 and part of 4 at once.
| Tool | Focus | Trade-off (example) |
|---|---|---|
| Themida | Anti-debug, virtualization, integrity | Heavy; AV false positives; slow startup |
| VMProtect | Virtualization of critical stretches | FPS loss if you virtualize hot code |
| Enigma Protector | Integrity, licensing, anti-dump | Extensive configuration |
Golden rules when using a protector: virtualize only the critical routines (handshake, checks), never the render loop, or the FPS collapses on weak machines; keep an internal build without protection so you can debug; and test antiviruses before launch, because packers trigger heuristics and you will need whitelisting/reputation.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Client closes on launch for many players | Aggressive anti-debug catching an overlay/AV | Switch the block to a report; refine the allowlist |
| Antivirus deletes Main.exe | Packer with no reputation | Sign the binary; submit it for whitelisting; build reputation on VirusTotal |
| Hash always invalid in the launcher | Reference hash outdated after re-editing | Automate hash regeneration in the build |
| FPS dropped a lot after protecting | Virtualization of the render loop | Virtualize only critical routines |
| Handshake refuses legitimate clients | Clock/nonce out of sync or wrong version | Check the time tolerance and AcceptVersion |
| False positive with Discord/Steam | Blind module verification | Sign/allowlist known signed modules |
| Handshake secret leaked | Plain-text string in the binary | Obfuscate/rotate the secret on every update |
Distribution and rotation
Protection is not "set it and forget it". Every new client version should generate a new hash, a new handshake secret and, if possible, small variations in the check routines to prevent one public bypass solution from working forever. Always distribute through the launcher (which validates before running), publish the hash alongside the download and scan the final binary before uploading it. Treat every bypass reported on your Discord as a high-priority bug.
Launch checklist
- Backup
Main_original.exekept outside the client folder - Reference SHA-256 hash generated from the final binary
- Integrity verification active in the launcher (obfuscated hash)
- Anti-debug in report mode, not kill mode
- Module verification with an allowlist of known signed modules
- Sensitive values (HP/zen/speed) encrypted in the client
- Server-side validation of HP, position, speed and packets
- Handshake nonce + HMAC active in the ConnectServer/GameServer
- Handshake secret rotatable and obfuscated
- Protector applied only to critical routines (FPS tested on a weak PC)
- Binary signed and submitted for antivirus reputation
- Tested on a clean machine, separate from the development one
- Internal debug build without protection kept for your own debugging
- Hash/secret rotation plan documented for every update
Frequently asked questions
Does anti-tamper replace the server's anti-cheat?
No. Anti-tamper protects the client binary and local memory, while server-side validation checks whether the actions and values it receives make sense. They are complementary layers: a hardened client with a naive server still falls to forged packets, and vice versa. Always validate on the server in addition to protecting the client.
Can I use Themida or VMProtect on Main.exe?
Yes, commercial packers like Themida, VMProtect and Enigma are widely used to make reverse engineering and debugging harder. The cost is higher CPU/RAM usage, slower startup and false positives in some antiviruses. Test performance on weak machines before launch and keep a debug build without the packer so you can debug it yourself.
Can the client checksum be bypassed?
A determined attacker can always neutralize a local check, because the code that verifies it runs on their machine. The goal of anti-tamper isn't to be unbreakable, but to raise the cost of the attack and let the server detect inconsistencies. That is why the signed handshake with the server, which the attacker doesn't control, is the most valuable part.
Will anti-debug get in the way of legitimate players?
It can, if it is too aggressive. Techniques like IsDebuggerPresent and checking for tool windows rarely affect players, but killing the process on detecting anything suspicious produces false positives with overlays, screen recorders and accessibility software. Prefer to report to the server and leave the ban decision to analysis, rather than killing the client on the spot.
Do I need to recompile Main.exe to add protection?
It depends. If you have the client's source code (rare), you compile the protection in. Without the source, the path is to apply an external packer/protector to the binary and, if there is support via a loader or a DLL injected by your launcher, add checks to that component. Integrity verification and the handshake can live in the launcher and the ConnectServer without touching Main.exe.