Tutorial How to write an Anti-Cheat Part 1: Detecting External Cheats

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47


Game: Return To Castle Wolfenstein
Engine: idTech 3
Studio: Grey Matter Interactive
Version: Steam
Buy: Steam

Chapters:

Chapter 0:
Setting up
Chapter 1: Detecting external cheat utilities

Introduction:

So I've been asked a few times to continue with my anti-debug / crack me series but instead I came up with something a bit more interesting and interactive. Instead of writing something from scratch and having it take forever what if instead we take something that already works and modify it? Since RTCW is an awesome game and the source code is available and actually compiles lets add some anticheat / antidebugging code to it and try to harden it against cheating.

Each chapter will discuss different techniques and ways a game can be hardened against even the most determined cheaters and hackers. For simplicity sake we're just going to lock-down single player however if you're feeling fancy you can grab the multiplayer source and apply all these techniques to that.

Things you'll need:
  1. Visual Studio Community 2019.
  2. RTCW Source Code.
  3. A copy of the game.
  4. A decent debugger.
NOTE: I decided to drop this early so I could gather feedback from the community. If you have any suggestions just drop them into the thread I can clean it up later.

Additional Resources:

https://guidedhacking.com/threads/gh-debugme-win32-app-with-anti-debugging-techniques.14627
 
Last edited:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
Chapter 0: Setting up

Something that crappier development studios do *cough* Bethesda *cough* is scan for "bad apps" there's a few different way to do this but generally most people seem to use EnumWindows and Process32Next. To get an idea of how poorly implemented this was for Fallout 76 take a look at Douggem's post on UC if anything it's good for a laugh or three.

So how about we do Bethesda's job for them and take their busted detection method and fix it?

Our fixed method is going to remove the first mistake they made: leaving clear text strings around. Remember when designing defensive code such as anti-cheat / anti-debug you need to deny your adversary as much information as possible. Strings are usually the first thing I dump in a process just to look around and perhaps quickly locate detection methods. So instead of clear text strings we'll use a hash algorithm.

There's tons of algorithms to choose but my favourite is xxHash. It's damn fast (especially on x64) and very easy to integrate into a project. Grab the files and move them into a new directory inside the RTCW source directory (we want to keep our code separate from the main game code).

directory.PNG

I decided to call our anti-debug "Deathshead" after a character from the game.

deathshead.jpg

*Laughs in monocle*

Generally when I'm working with existing code I will try keep it as separated as possible for both organisation and debugging purposes. We're going to change one line of code in the game's entry point and that is all everything else will remain untouched.

Open win_main.c under the wolf project then add this:

C++:
// win_main.h

#include "../deathshead/dh_main.h" // Or whatever you've called it
#include "../client/client.h"
#include "../qcommon/qcommon.h"
#include "win_local.h"
#include "resource.h"
#include <errno.h>
#include <float.h>
#include <fcntl.h>
#include <stdio.h>
#include <direct.h>
#include <io.h>
#include <conio.h>
C++:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
    char cwd[MAX_OSPATH];
    int startTime, endTime;

    // should never get a previous instance in Win32
    if ( hPrevInstance ) {
        return 0;
    }

    g_wv.hInstance = hInstance;
    Q_strncpyz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) );

    // done before Com/Sys_Init since we need this for error output
    Sys_CreateConsole();

    // no abort/retry/fail errors
    SetErrorMode( SEM_FAILCRITICALERRORS );

    // get the initial time base
    Sys_Milliseconds();

// re-enabled CD checking for proper 'setup.exe' file on game cd
// (SA) enable to do cd check for setup\setup.exe
//#if 1
#if 0
    // if we find the CD, add a +set cddir xxx command line
    if ( !Sys_ScanForCD() ) {
        Sys_Error( "Game CD not in drive" );
    }

#endif

    Sys_InitStreamThread();

    Com_Init( sys_cmdline );
    NET_Init();

    _getcwd( cwd, sizeof( cwd ) );
    Com_Printf( "Working directory: %s\n", cwd );

    DH_Entry(); // We want to called after the console is activated
Now compile the code and should work fine. Since we're dicking around with idTech we should also write all our debugging to the in game console. Inside your main.c file add this code:

C++:
#include "../client/client.h"
#include "dh_main.h"

void DH_Entry(void)
{
    // This is our entry point
    Com_Printf("DH_Entry()\n");
}
If everything compiled fine rename the original game exe and copy our WolfSP.exe to the game's folder and run it. If everything worked you should have this:

console-loggin.png

Lookin' good.

Now everything is setup and our code is separated and integrated so there should be zero issues from this point on. You can also add filters to the wolf project to separate it even further:

filters.PNG


Once you're happy with how you've configured everything read on for the actual anti-debug part.
 
Last edited:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
Chapter 1: Detecting external cheat utilities

So now our code is integrated and ready to go lets gather a list of windows on the desktop and hash them. Update your code to this:

C++:
#include "../client/client.h"
#include "dh_main.h"
#include "xxhash.h"

#include <Windows.h>

const XXH32_hash_t g_hashSeed = 77777UL;

BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
    char windowTitle[256] = { 0 };
    GetWindowText(hWnd, windowTitle, 255);

    if (windowTitle[0] == '\0')
        return TRUE;

    XXH32_hash_t hash = XXH32(windowTitle, strlen(windowTitle), g_hashSeed);

    Com_Printf("%s -> %x\n", windowTitle, hash);
    return TRUE;
}

void DH_Entry(void)
{
    const char titleText[] = "\n[ Deathshead v0.0.1 ]\nhttps://guidedhacking.com\n\n";
    // This is our entry point
    Com_Printf("%s", titleText);

    EnumWindows(EnumWindowsProc, NULL);
}
Now build it and overwrite the game exe again:

window-dump.PNG


You can see each window title now has a unique hash associated with it. But there's one window in particular we're searching for:

ce.PNG


Now we've got the hash for Cheat Engine we can add it to our list of "bad apps".

C++:
const XXH32_hash_t g_badApps[] = { 0xd51f5cb0, 0 };

BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
    char windowTitle[256] = { 0 };
    GetWindowText(hWnd, windowTitle, 255);

    if (windowTitle[0] == '\0')
        return TRUE;

    XXH32_hash_t hash = XXH32(windowTitle, strlen(windowTitle), g_hashSeed);

    for (int i = 0; g_badApps[i]; i++)
    {
        if (g_badApps[i] == hash)
        {
            Com_Printf("[DETECTED] Nice Cheat Engine you have there....\n");
        }
    }

    //Com_Printf("%s -> %x\n", windowTitle, hash);
    return TRUE;
}
gottem.PNG


We can repeat this process for every tool we want to ban. I've updated our array with some badapps I had laying around to save you the trouble:

C++:
const XXH32_hash_t g_badApps[] =
{
    0xd51f5cb0, 0xb891c31d, 0xe89a74a4, 0x9f2f6e6b,
    0x90647d49, 0x897bffb7, 0xaa2ffe9b, 0xe89a74a4,
    0xfdf41791, 0xc4da6343
};
It should cover x32dbg, most versions of Cheat Engine and of course the GuidedHacking Injector. At this point we could display a message or simply terminate it's up to you I'm going to be looking at some more interesting things you can do instead.

Processes:

Detecting by process name is basically the exact same method but using the toolhelp api:
C++:
void CheckProcessNames()
{
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, GetCurrentProcessId());
    if (snapshot == INVALID_HANDLE_VALUE)
        return;

    PROCESSENTRY32 pe = { 0 };
    pe.dwSize = sizeof(PROCESSENTRY32);

    Process32First(snapshot, &pe);

    do {
        XXH32_hash_t hash = XXH32(pe.szExeFile, strlen(pe.szExeFile), g_hashSeed);
        for (int i = 0; g_badApps[i]; i++)
        {
            if (g_badApps[i] == hash)
            {
                Com_Printf("[DETECTED] %x\n", hash);
            }
        }
    } while (Process32Next(snapshot, &pe));

    CloseHandle(snapshot);
}
Then we just update our bad apps array with some processes we dont like:

C++:
const XXH32_hash_t g_badApps[] =
{
    // Windows

    0xd51f5cb0, 0xb891c31d, 0xe89a74a4, 0x9f2f6e6b,
    0x90647d49, 0x897bffb7, 0xaa2ffe9b, 0xe89a74a4,
    0xfdf41791, 0xc4da6343,

    // Processes
    0xdbab90a7, 0x48a79b9c, 0xa7e638fc, 0x39e3587c,
};
And that's a very entry level anti-cheat preventing the user from running certain applications by checking for them on startup. Of course there's a number of issues with this code:
  1. The check only occurs once on start up. We can just launch the game then open our apps up.
  2. You can bypass this easily by renaming your tool or hacking it to change the strings (Like @Rake did with CE).
  3. It requires manual updates every time the utilities are updated.
  4. This wont prevent any type of injection.
Of course we're going to improve on this code and make things much more interesting.
 
Last edited:

KIWI

Dumb
Trump Tier Donator
Dank Tier Donator
Nobleman
May 16, 2014
66
3,443
7
Great guide! I will add a little bit from myself. You can detect DLL injection with ldrRegisterDllNotification. That can be bypassed with mmap injector or a simple byte patch callback function that ldrRegisterDllNotification call, but any way it's detect any module loading (loadlibrary injection). Maybe this info will be useful for any one.
 
Last edited:

dhanax26

0xF9D8C3F5D6D3
Dank Tier Donator
Nov 16, 2018
24
298
0
Plus you can add a signature/byte array of every known program like cheat engine etc... It worth rater than windows titles, of course this way its easy to bypass but they can stop a lot of begginers or lazy people that doens't want to make their own tools or edit the publics ones...

Ps: Good guide and thanks for sharing.
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
11,573
78,998
2,316
released from the premium forum to the plebs today!

donate for early access to tutorials
 

esp1z1

I know like 40% of what I’m doing. Ok...
Dank Tier VIP
Dank Tier Donator
Aug 6, 2018
182
7,043
4
And this how bypassing Valorant anti hank
 
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods League of Legends Accounts