Unmapped 002: Pattern Recognition
Pattern recognition is the bridge between raw dumps and usable knowledge. By analyzing the structure of memory writes we transform 195 opaque binary blobs into a complete blueprint of the cheats runtime state. This is the work that makes reversing and cracking a servermapped cheats (and or malware) possible.
IntroductionInitially, this blog was supposed to be halted and scraped, as I lost the source code files for the mapper and the dumps. After a few months, I randomly found it all which made continuing this blog series possible. The code for the finalized mapper is available at: https://github.com/pabeda1337/illusicrack Thank you.
Welcome back to Unmapped 002: Pattern Recognition. In our previous post, we successfully intercepted a servermapper-based cheat delivery system using kernel-level hook on NtWriteVirtualMemory. Our blackbox approach yielded 195 memory writes - a complete recording of how the servermapper constructs the cheat in the target process memory.
Now comes the detective work. We have raw data dumps, but we need to understand what each piece represents and how they fit together. Traditional reverse engineering would attempt to reconstruct the PE headers and rebuild a proper executable file. We're going to take a different path entirely and internally map the dumped module.
Quick Recap:
195 total memory writes captured from the servermapper
First 9 writes appear to be section data (we identified
.rdataand.text)Remaining 186 writes contain little-endian addresses pointing to imports
No PE headers - the cheat exists only as mapped code in memory
In this part, we'll analyze these patterns in detail, understand the servermapper's injection methodology, and reveal the technique that lets us bypass PE reconstruction completely.
This is part two of our three-part series:
Part 1: Initial reconnaissance and obtaining a clean memory dump ✓
Part 2 (this post): Pattern analysis, Import Address Table reconstruction, and bypass techniques
Part 3: Building a custom mapper and finalizing the crack
So let's dig ...
So what's really needed?
It's important to remember that while we miss certain parts of the dll being injected - the "blob" that is injected is still a PE file. Normally, when you load a DLL, the Windows loader handles PE parsing, section mapping, import resolution, and relocations. A servermapper does all this, but shifts all this work to the server—it does the entire mapping process remotely, then sends only the result: a headerless memory blob with resolved imports and relocated code. This means we don't have a PE file to reconstruct. So how do we reconstruct it, if we don't know what it looks like?
The (not-so) heavy lifting
The bare minimum of what's needed for a PE file to run is resolving imports, performing relocations, and invoking the entrypoint. For the sake of the article, I'm going to describe the import address table and the relocation table so we have a better understanding of what's actually happening under the hood of a manual mapper (or a servermapper in this case).
The Import Address Table (IAT) is a lookup table of resolved function pointers—when code calls an imported function, it dereferences an entry in the IAT to get the actual address. The relocation table contains metadata about which addresses in the code/data need to be patched when the module loads at a different base address than expected.
We know the Import Address Table is critical—and we already have its exact location from part 1. But what about relocations?
If the servermapper performed relocations before sending the blob (safe assumption), all absolute pointers are already adjusted for the server's base address. When we inject into our target process, we need to re-apply those relocations for our different base address... unless we do something smarter, the solution provides itself to us on a silver plate if we think about what relocations actually are.
Here's the key: relocations are just patches to absolute addresses. But if we control the injection process and allocate memory at the same base address the servermapper used (where the memory for the cheat was allocated), all the pointers are already correct. No relocation work needed at all.
So what's left to do?
- Allocate memory at the cheat's original base address
- Copy the cheat sections to that address
- Patch the IAT table with resolved import addresses
- Invoke the entrypoint
... but how do we know what the correct addresses for the IAT are? There's a catch.
The bypass
We can't hardcode the captured addresses because of ASLR. ASLR (Address Space Layout Randomization) is a Windows security feature that loads DLLs at random addresses each time a process starts. The crack would work in the current session, but fail the moment the user restarts their machine—the DLLs load at different addresses, making the hardcoded pointers invalid. So what do we do? That's right. We need to dump the cheat again and write some code to gather all exports of all the modules loaded in the target process. That way, we have all the information needed to run the memory blob, without PE headers and even without wondering about relocations. We're essentially taking a snapshot of the process at the moment of injection and building the ability to recreate that exact state ourselves.
After that, by mapping each captured address back to its export, we build a lookup table: 'this IAT slot needs kernel32!CreateRemoteThread, that one needs ntdll!NtAllocateVirtualMemory,' and so on.
In part 3, we will use that mapping to populate fresh IAT entries with addresses from our own process ASLR state and finalize this project.