Unmapped 001: Breaking a Servermapper
Servermappers have been the gold standard for evasive cheat delivery since the early CS:GO days, but most analysis attempts get stuck on PE reconstruction complexities. We bypassed the whole mess with one clever technique and walked away with their cheat in hand.
For those unfamiliar with the technique, servermapping (while not being really new) represents a significant evolution in cheat delivery systems. Rather than dropping files to disk where they can be scanned and analyzed, these systems perform the entire DLL loading and mapping process remotely on a server. The target machine receives only the mapped payload - no PE headers, no section information, just raw executable code with manually resolved imports.
This approach creates several layers of protection: there's no file signature to detect, no disk artifacts to recover, and most importantly for reverse engineers, no clean PE structure to work with. Traditional analysis workflows break down when faced with headerless, relocated code that exists only in memory.
The particular cheat we targeted has been advertising itself as "secure" due to its servermapper architecture. Their confidence wasn't unfounded - servermapper-based systems have historically proven difficult to crack, with most attempts failing at the PE reconstruction stage.
But sometimes the best way through a wall is to walk around it entirely, so let's start from the beginning...
This is part one of a three-part series documenting our complete servermapper analysis:
Part 1 (this post): Initial reconnaissance and obtaining a clean memory dump
Part 2: Pattern analysis, Import Address Table reconstruction, and bypass techniques
Part 3: Building a custom mapper and finalizing the crack
Each part builds upon the previous, taking you from initial hook setup through final crack execution. Let's dive in...
Initial reconnaissance
To start this off, we could try to bruteforce our way in and make assumptions since this is a typical pay-to-cheat loader, but that wouldn't make for a very good blog post. Instead, let's analyze the loader and see what we can grasp about it—that's always a solid starting point.
We begin by dropping the loader into Detect It Easy (DIE) to see what we can figure out about the sections and potentially identify a packer.

We noticed that the developers of Illusionary have thought about reverse engineers and that the section names are .THEMIDA which is not the correct section name format even for Themida, judging by the section names, the imports, and previous experience, I'm already confident this loader is protected by VMProtect—a popular Russian obfuscation/virtualization software. This means we won't get far by looking at the file statically with something like IDA; we'll need to get a runtime dump or unpack the file to get something worthwhile.
While we could go the full route of unpacking the loader and using an emulator like the Unicorn Engine to resolve IAT protection, this seems like way too much work for what we're interested in, especially since the most critical parts will surely be virtualized. From some reverse engineering I did years ago, I know that VMProtect calls GetCommandLineA before it invokes the original entry point. Hooking that function and pausing execution allows us to examine the loader without worrying about anti-debug measures. For the sake of this article, let's look at how such a hook might look—we'll spin up a message box as notification that the unpacking process has finished.
typedef LPSTR(WINAPI* GetCommandLineA_t)();
GetCommandLineA_t oGetCommandLineA = nullptr;
LPSTR WINAPI hkGetCommandLineA() {
// Calling this, the file is already unpacked and can be analyzed, we suspend
// all threads so we can safely attach a debugger
std::cout << "{ vmp-destroyer } unpacked." << std::endl;
SuspendAllThreads();
MessageBoxA(0, "Unpacked", "vmp-destroyer", 0);
return oGetCommandLineA();
}When the message box gets called, all execution within the thread should be suspended and we can safely attach/detach our debugger to make a dump. I am not going to describe completely how we find the original entry point or how we defeat VMP's import protection, because that's outside the scope of this blog post.
After analyzing the dump that we just made, we notice several strings hinting at some security practices deployed by the loader developers. Mainly, we notice an excess of Nt* functions being imported, hinting that they are being monitored for either hooks or patches. This leads us to taking a different approach to this...
Blackbox approach
Rather than spending weeks unpacking and reverse engineering the loader itself, we're going to take a blackbox approach to this problem. In blackbox cracking, we don't need to understand the internal workings of the loader—we simply monitor and intercept everything it does to the target process.
The concept is straightforward: if the servermapper is going to inject code into our target process, it has to make system calls to allocate memory and write data. By hooking the right API functions, we can capture every memory allocation and write operation the loader performs, essentially recording its entire injection process step-by-step.
This approach has several advantages over traditional static analysis:
We bypass all obfuscation and virtualization completely
We don't need to understand the loader's protection mechanisms, as we will hook the respective APIs in kernel space
We capture the actual runtime behavior, not just static code
We get the data exactly as it appears in the target process, allowing for easier reconstruction
The key APIs we need to monitor are:
NtAllocateVirtualMemory/VirtualAllocEx- to catch memory allocationsNtWriteVirtualMemory/WriteProcessMemory- to capture all data writesNtCreateThread/CreateRemoteThread- to detect thread creation
By logging these operations with their parameters (addresses, sizes, data content), we can essentially record a "recipe" of how the servermapper constructs the cheat in memory. Later, we can replay this process ourselves in our mapper and thus make cracking much easier.
Now that we know what we want to do, we can get right into the hooking. Our hook function takes in a TargetFunction pointer, a HookFunction pointer, CodeLength and an OriginalTrampoline pointer; our hook setup and hook implementation will look something like this, respectively:
// hook setup
auto NtWvm = utils::ntoskrnl_ptr + 0x7BD390;
hker::Hook((PVOID)NtWvm, (PVOID)hkNtWriteVirtualMemory, 22, (PVOID*)&OriginalNtWriteVirtualMemory);NTSTATUS(*OriginalNtWriteVirtualMemory)(HANDLE,
PVOID, PVOID,
ULONG, PULONG);
NTSTATUS hkNtWriteVirtualMemory(HANDLE ProcessHandle,
PVOID BaseAddress,
PVOID Buffer,
ULONG NumberOfBytesToWrite,
PULONG NumberOfBytesWritten) {
if (IsTargetProcess(ProcessHandle)) {
MEMORY_BASIC_INFORMATION memInfo;
SIZE_T returnLength;
NTSTATUS status = ZwQueryVirtualMemory(
ProcessHandle,
BaseAddress,
MemoryBasicInformation,
&memInfo,
sizeof(MEMORY_BASIC_INFORMATION),
&returnLength
);
if (NT_SUCCESS(status)) { // Now memInfo.Protect contains the protection flags
DbgPrint("[hker] Memory protection: 0x%X at address 0x%p\n",
memInfo.Protect, BaseAddress);
// We found and succesfully queried injected memory, dump to file
// We dump to C:\mem
SaveRegionToFile(BaseAddress, Buffer, NumberOfBytesToWrite, 1337, memInfo.Protect);
}
}
else {
// call wasnt targetting our target process, log or whatever
}
return OriginalNtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten);
}
As this is a tool for our use, we do not bother with resolving the addresses dynamically and so forth. Keep in mind that while this approach is much stealthier, we will get hit by Windows' Kernel Patch Protection (KPP) soon, so we need to work quickly.
Loading up the kernel driver and injecting the cheat, our desired folder (in our case C:\mem) will get filled with the dumped memory in the exact order as it came in from the loader. The folder might look something like this...

PROTIP: Our dumper sets filenames in the according format
#define FILENAME_FORMAT L"\\??\\C:\\mem\\%d_%llu_mem_0x%p_0x%p_0x%llx_%S.bin"ITERATOR_regionIndex_BufferPtr_BaseAddress_RegionSize_Protection
You might think to yourself that these are just so many writes to go through and that something must've gone wrong, but we can verify if what we got is definitely the cheat. For us, the target cheat is called Illusionary. Since we know not to expect a PE header, we assume that the first write is the .text section and the others should follow.
Opening the 3rd write (dumped memory) in HxD, we can immediately recognize that this is the .rdata section - meaning our dumper worked.

But if we have the cheat sections, what are the other 186 writes? Well, we need to open them up to really know.
We open writes 170, 171, 172 respectively... Notice the pattern?



For those who don't know, these are addresses represented in little-endian format. Write 170 would thus convert into 0x7FFE8EB6FB80. Can you try to guess what it is? That's right! The loader populates the IAT table.
What's Next?
Now that we've successfully captured the servermapper's delivery process and identified the key components - the section writes and IAT population - we have all the raw materials needed to reconstruct this cheat. But having 195 memory dumps is just the beginning.
NOTE: Shortly after publishing the blog; I swapped my devices and can no longer find the related files, the cheat we've managed to reverse has been down for months afterwards, and since then their security has drastically changed. This means that we'd have to rework this and Unmapped 002 has to be delayed. Sorry.. We'll try to get back to this at some point later on.
In Unmapped 002, we'll dive deep into analyzing these captures:
Understanding how the servermapper resolves and patches import addresses
Identifying the loading sequence and memory layout
Most importantly - discovering the technique that lets us bypass PE reconstruction entirely
The foundation is laid, the data is captured. Now comes the real reverse engineering work. See you in the next article!