Maplestory Hax 1337

I started playing Maplestory in around 2006 upon the recommendation of my friend and my childhood is filled with fond memories of it. I played it on and off in my adolescent years but grew increasingly frustrated with the direction of the game so I quit permanently. In high school, I knew that Maplestory hacks existed and even how to get them running, but I had always wondered how they worked and aspired to create my own hacks.

Well, after completing an entire degree in computer science, I came back to this question with renewed interest and newfound expertise. I had some experience in reverse engineering, but basically none in reverse engineering low-level binaries and I wasn’t confident I could crack Maplestory’s anti-cheat right off the bat, so I honed my skills on other singleplayer games without anti-cheat first. Then, with some practice under my belt, I directed my aim to Maplestory.

The first part to hacking a game is to do research on the available hacks. How do they work? What can I learn from them? Unfortunately, Maplestory is a pretty unpopular game by today’s standards and the hacking resources out there were really out of date. Even worse, Maplestory switched from 32-bit binaries to 64-bit binaries relatively recently, which invalidated a ton of resources. Still, I pieced together what I could in the hopes of crossing the first great barrier: disabling their anti-cheat.

I don’t think I can legally put most of this information online (hell, hacking the game privately may even be illegal — I mean… allegedly hacking the game privately). Although it’s unlikely Nexon would ever come after me for this especially since this information exists in other, more well-known websites, and I think in general, trying to hack games and publishing reverse engineered information online is morally permissible, I still feel some moral pull to not contribute to it here (at least in detail). Also, this project happened months ago and I’m too lazy to dredge up the details 😛

Anti-cheat bypassing

After I compiled Cheat Engine to hopefully prevent detection by the anti-cheat, one of the first things I tried was to modify some instructions in memory. Of course, an integrity check caught this and the game exited immediately. Integrity checks must scan the memory I modified, so I made some changes and tried to find what was reading that memory, which lead me to some obfuscated code in the Maplestory binary. I could only see a repe movsb instruction followed by a jmp, and instructions before were clearly mangled, so it seems like the instruction was reached through another jmp. In any case, I broke on that instruction to find the start and end region of the copy and found that it was just the region occupied by the Maplestory module. So, I copied that region into somewhere else in memory and then hooked the instruction to change the region to the copied one. I changed some instructions here and there and… it worked!

Next, I knew that as part of the NGS (Nexon Game Security) anti-cheat suite, another process called BlackCipher64 was spawned. I wasn’t too sure of what that did, but I figured I needed to hijack it, considering I had seen some forum threads talking about how to disable it in previous versions. I guessed that it would scan through the Maplestory process for an extra integrity check and for possibly unknown modules. When I was looking for integrity checks earlier, I couldn’t find anything indicating BlackCipher was doing this though, nor could I find any calls to ReadProcessMemory to remotely read the Maplestory process’s memory though so I tried to catch BlackCipher64 trying to scan for modules in Maplestory. Sure enough, I found a call to Module32Next, which is used to enumerate modules. I tried to place a sleep in Module32Next, but BlackCipher64 and Maplestory would both exit after a while, which indicated that BlackCipher was subject to integrity checks as well. I knew that integrity checks might be difficult to perform across modules, so I guessed that the module containing Module32Next had an integrity check separate from the module running BlackCipher64.aes. I traced the Module32Next call back into the BlackCipher64.aes module and tried to place the sleep in there, and the game didn’t exit! It seems like the main module didn’t have any integrity checks that forced an immediate exit upon failure.

I didn’t know how well these bypasses really worked. I mean, there could be another mechanism that detects my bypasses and silently reports it. The BlackCipher bypass, for example, really shouldn’t have worked: why wasn’t there an integrity check for the main module? I was too excited that my bypasses seemed to work that I didn’t think about this and continued onward.

All of my work had been prototyped using Cheat Engine, but I wanted to transfer it into a DLL I could inject for ease of use. There was the potential that the DLL would be detected still, but this was a challenge I wanted to overcome. So, after a lot of head-scratching in C++, I transferred my work into DLLs to inject into the BlackCipher process and the Maplestory process. Sure enough, they worked.

Network protocol reversing

The pinnacle of a hacked MMO client, as I see it, is a headless client sending and receiving packets conforming to the network protocol of the game. This demonstrates complete mastery over what can be known about the client by the server and is likely to reveal attack vectors that would be otherwise difficult to find through simple client modifications. In practice, this is immensely difficult. Reverse engineering the network protocol is already a daunting task, but trying to do it through obfuscated code makes it difficult enough that it’s just not worth it. I was doing this for sport, not out of a desire to create competent hacks, though. So I set my eyes on understanding the network protocol.

The first matter is that packets are encrypted, meaning we can’t MITM spy on packets past the application layer with Wireshark or something. We have to find hook and log the packets before they’re encrypted and after they’re decrypted. Finding the encryption and decryption functions took me almost a week. The decryption function isn’t too bad because I can just break on recv and monitor reads at the memory that the recv populates and follow it from there. The encryption function is really difficult because by the time the send function is called, you have no idea where the memory came from or how it was constructed. I’d normally backtrace by breaking on the send and tracing up the call stack, but some regions of the code is extremely obfuscated, so the stack during send looks completely messed up and I can’t reasonably follow it that way. As I mentioned before, it’s been months since I started this project and honestly, I have no idea how I even backtraced the encryption function. I’m looking through my notes and I think I pieced together a global structure I called NetworkManager which contained structures I called MessageHolder and used information on how they relate and how MessageHolders are created/destroyed/modified in NetworkManager to find the encrypt function. I remember feeling like pulling my hair out doing this and jumping out of my chair in excitement when I finally found the encrypt function though.

After hooking onto the encrypt and decrypt functions, I started logging the unencrypted packets as an array of bytes, hoping to maybe just eyeball some information. Yeah, that was incredibly naive. Packets were flying by so fast I couldn’t even read them before the next batch came in. I figured that there had to be a sort of opcode for the packets somewhere. I’d usually expect them as the first few bytes, but they were actually 2-byte opcodes starting on the 4th byte. I found this out later, but the first 4 bytes actually contained a 2-byte packet length that had to be decoded in a certain way. I’m not actually sure why they did this. Possibly for redundancy?

For a while, I was grouping the packets by opcode and trying to connect them to an action I did, like opening a menu, moving, going to a map, etc. I could connect opcodes to actions, but the packet body was basically a complete black box. Great, I know that upgrading a skill send a packet with opcode 0x17c, but what were the 6 bytes after? Skill ID? Amount of points invested? Maybe I could hazard I guess with such a small packet body, but I had no idea where to even start for longer bodies or variable length bodies.

I was doing this inefficient, stupid method until I was reading the decompiled source of the binary and just happened to stumble upon a constant that matched an opcode I knew. I searched the surrounding functions and sure enough, I found the code for constructing the packet for that opcode. I didn’t find this earlier because I didn’t even know what to look for. I mean, sure I could try searching the entire binary for an opcode, but there would be way too many hits. I didn’t know what packet construction looked like structurally either and I couldn’t backtrace it easily from before the encrypt function because the code was passing through an obfuscation layer during the encrypt function. From there, I noticed a function that looked suspiciously like an initializer for something like a Packet struct, so I hooked onto it and verified that I was correct. Since the initializer was called without obfuscation, I was able to hook onto it and print out the opcode it was initialized with and the call stack to see the rest of the packet creation. Using this, I was able to efficiently reverse engineer many packets. Unfortunately the creation of many of the sensitive packets, like heartbeat packets and packets to send client information, were called through an obfuscation layer, but there still were a ton of useful packets that weren’t obfuscated.

This applies to only outbound packets, of course. For inbound packets, I was never able to figure out how they were processed. I could find them being decrypted and I could make sense of some of the packets from just black-box reverse engineering, but after a considerable effort, I concluded that all of the inbound packet handling code on the client lived in obfuscation and there wasn’t any analog to the packet creation code for packet handling.

Real Hax

Eventually, I got a bit bored of playing with the network protocol and decided to play around a bit with writing useful hacks. This is where I ran into a lot of trouble. When I was scanning for pointers to things like HP, MP, player position, etc., I would frequently get disconnected for “suspicious behavior.” I wasn’t sure if my bypass was failing or if they had server-side checks with heuristics for suspicious client behavior. I couldn’t reasonably make anything useful when I was frequently getting disconnected and sometimes banned. So instead, I decided I’d try to update some of the cheats people had previously published. I did this by downloading older versions of the game and finding what the cheats were modifying in the old version and then trying to find the analogous code on the current version and mimicking it. This was pretty successful! With only some effort, I was able to dodge every attack, disable collision, jump multiple times, etc.

Unfortunately, I would get banned a day or two after doing these more invasive cheats. Previously, I was just logging packets and I hadn’t been banned after I perfected the code. I was pretty confused at how I was getting caught because I figured that the packet logging should have been caught already. At a later time, I got into contact with another hacker who told me there was another integrity check I failed to bypass. I guess the basic bypass I had only allowed me to do certain things.

Disaster Strikes

As I was trying to figure out what was still getting me banned, my computer died, which lead to a wild goose chase lasting over a week trying to diagnose the problem through ordering new parts, testing them, failing, and repeating. The last thing I thought it would be was a CPU failure because I was booting into the BIOS fine and I could even boot into memtest. I replaced almost everything else — motherboard, power supply, GPU. The only things left of my original computer were my CPU and RAM. As I was testing my RAM to find out which ones I could keep in the new build and still crashing in the memtest, I noticed I was crashing on the same test. I searched up what that test did and it said it was a multi-core test. That’s when I finally realized it could be possible that my test results were consistent with one or more, but less than all CPU cores failing. Indeed, when I ran the memtest in single core mode, I didn’t crash.

I have never even heard of this happening and it caused me such a headache because I couldn’t figure out what was going wrong with the computer. I was about to make the Computer of Theseus!

During this period, I couldn’t continue on the project and I lost all of my momentum from the break. Later on, I got into contact with another hacker which is how I found out about what my bypass lacked. Unfortunately, I haven’t had the motivation to continue the project, so this is all I can write about for now. I wasn’t even going to write about this, but I figured I should complete the reverse engineering article series I started and give an update on where I was in regards to that. Be tuned for a part 2 in another… I don’t know, whenever I get bored enough again.

Leave a Reply

Your email address will not be published. Required fields are marked *