Hello all. We are once again injecting things into things. There’s a Covid joke in there somewhere. Anyways, today’s topic will be Manual Map Injecting. I’ve mentioned this a handful of times in previous posts, but now it’s finally time to do it.
Before You Read
It is recommended to read these related posts before reading this one, so that you don’t get lost during the discussion of the concepts:
What
You should hopefully already be aware of how DLL injection works. TLDR; you ask a process to load your DLL via CreateRemoteThread + LoadLibrary, and the Windows API handles the rest.
Manual map injection is slightly different. It is a form of process injection that relies on the injector recreating the process of what would actually happen when a module is loaded into a program. To get into the nitty gritty details, when a module (DLL) is loaded via LoadLibrary or similar, a few things happen.
It retrieves the module that is passed as an argument as a file path and then opens it.
It applies fixups to the module image which usually involves assigning import addresses, relocations, Thread Local Storage (TLS) callbacks, and, if in x64, exception function entries.
It allocates the space needed for the image within the calling process.
Moves the image into the allocated space.
A manual map injection, however, skips over the whole LoadLibrary part. It does all of the following steps manually, barely relying on the Windows API. Very sneaky, don’t you think? The thing about manual mapping is that Windows is almost completely unaware that a module has been loaded. There’s no module handle, it cannot be enumerated, and it isn’t even in the PEB. All that exists is a big executable page of memory that’s stuck inside of a process. Sneaky sneaky!
Why
Believe it or not, but manual mapping is very popular with video game cheats. Some (most?) anti-cheat software detects cheats by enumerating the loaded modules within a game, and quits if an anomaly module is detected. Manual mapping doesn’t officially load a module, so this detection is completely useless! I’m 99% sure manual mapping bypasses Valve Anti-Cheat (VAC), but don’t take my word for it nor blame me if you get caught.
On the malware hand, manual mapping is also common as it’s a method to bypass anti-tamper software that utilizes the same heuristics as anti-cheats. However, if your anti-tamper program is scanning for executable regions of code within the process, a manual map injection can be caught with relative ease (but maybe not before it has the chance to run some code).
There are a couple of disadvantages with manual mapping.
GetProcAddress/LoadLibraryA/GetModuleHandleA Checks
A common way to fix up the IAT is to parse it, grab the DLL name and function name, and then run LoadLibraryA/GetModuleHandleA + GetProcAddress on the set to assign the address. If the program you are injecting into has checks/hooks on these functions, then it may prevent the IAT from being successfully rebuilt. To bypass this, you would have toe walk the PEB each time you needed to import a function.
Debugging
LOL. I had to put this one. When I wrote my injector, I hated doing the manual map part, because it was so hard to debug. I ended up just copying and pasting most of the code from somewhere else and running with that. Manual mapping utilizes a lot of shellcode to rebuild the IAT and such, which isn’t exactly the easiest to debug.
How
Asking you to build your own manual map injector would be pretty ridiculous and also pointless. So, we’re just gonna “borrow” one. I like this one, namely because it was the first one to pop up when I googled it. So, let’s use it. It even has some DLLs in the repo that you can inject with. How convenient.
If you’re up for it, you can use the last paid post’s program and try to beat it. That’s what I’ll be doing here. If you just want to explore it, you can inject one of the provided DLLs into something like notepad.
Requirements
A disassembler
MSVC++
A debugger should you be able to disable any debug checks
So let’s pull up advanceddefense.exe in IDA once more. You can either use the original version or the patched version if you followed along last time. Regardless, a manual map injection should work.
Let’s take a quick glance at main()
The print functions are nice clues. You can see that your score is printed out. So, where does the score come from?
This is your average std::cout call, which you can tell by its presentation.
A call to a function that takes the address of a random qword and a string
Another function(s) right below it that calls a function that takes the return value of the previous function and literally anything else
The final function in the set that once again takes the return value of the previous function and another function in its parameters (this is std::endl)
Anyways, the dword is what we’re looking at, as it is presumably the score.
Let’s grab its Xrefs
The first Xref sets it to 0 (we can see this in the Text box), so we can look at the second one.
It’s another cout, so this pretty much proves that this is the score dword.
Let’s look at Xref 3 and 4 as they are in the same function.
The pseudocode window is smart enough to slap both references into a single line of code. Remember that +=
is just var = var +
.
If you played the game (you did, right?). You’ll know that you gain 10 points every time you eat a thingy. So yes, this is definitely the score.
This is just a single cheating vector, but it’s a big one. Imagine if this was a multiplayer game. You could smash the scoreboards if you cheat on this dword!
You have 2 real options here. Either 1, you set that 10 increment to something huge like 1000, or you could just set the dword’s value to a huge number like 2,000,000,000.
Let’s do the latter.
Pssst. Doing the former will be the topic of the next paid post. Do you know why???
Since this is just an example, there won’t be a need to sigscan to find the location of the dword. It’d be tricky too. You would have to sigscan to find one of the Xrefs, then perform a relative load to get the location of the dword. Way too much effort, so we’re just gonna grab it’s offset from its position in the binary
IDA set the base address to 140000000, so the offset is 0x6370C.
Here is the DLL source
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hModule);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)[](PVOID) -> DWORD
{
// Get base address of the attached process
BYTE *base = (BYTE *)GetModuleHandleA(0);
base += 0x6370C;
DWORD oldprotect;
VirtualProtect((void *)base, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &oldprotect);
*(DWORD *)base = 999999;
VirtualProtect((void *)base, sizeof(DWORD), oldprotect, &oldprotect);
return 0;
}, 0, 0, 0);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Now, launch the game.
And manual map inject!
Cool, huh?
That’s it for this post. Hope you all enjoyed. Have a great weekend.
Go!
-BowTiedCrawfish