Igor Segalla
Posted on October 9, 2020
If you have ever been curious about how an anti-cheat works or if you plan to write your own, then in this post I'm going to give you a few tips on how to detect and protect your application from these well-known cheaters.
It is worth noting that in this post I'm going to emphasize the client-side part, and not the server. To avoid future problems and have a totally secure application, the best you can do is to have a well written program that doesn't allow the exploitation of faults by third parties in any way.
Anti-Debugging
One of the best known and most used methods to protect a program from an intruder is to prevent users from debugging it and thus reverse engineer it.
Reverse engineering is the process of discovering the technological principles and functioning of a device, object or system, through the analysis of its structure, function and operation.
I made a collection of 12 different anti-debugging methods while working on my anti-cheat. All the ones I found are efficient and fully functional. Below you can check out these methods and use them as you like. Just keep in mind that the code with all the methods is in a separate Gist so as not to pollute this post.
Find Window
It is a very common method among the existing anti-cheats, which consists of searching for a process that is running by the name of the window. As an example, it is to make that whenever there is a window with the word hacker, the anti-cheat detects this process as malicious software (is quite common for this method to detect false positive).
I use two possible ways of Find Window: one of them is to check if there is a window with the keyword chosen by me; and the other one is to analyze all HANDLES made so far and check if it could be a malicious software.
Find by Window name
As previously said, this method is nothing more than using the FindWindow function of the Windows API and check if it has any window with the chosen words. If it returns a valid HANDLE, it means that the window has been found.
if( FindWindow( "hacker", NULL ) != NULL )
HackerDetected();
Deep search for a Window
This method does a deeper search for each opened process, trying to search for some pattern that identifies that program as malicious software.
We used the EnumWindows and EnumChildWindows function to iterate through the existing HANDLEs recursively.
EnumWindows( (WNDENUMPROC)EnumWindowsProc, ( LPARAM )nullptr );
void EnumWindowsProc( HWND hWnd, LPARAM lParam ) {
EnumChildWindows(...);
}
If we have the HANDLE of each window, and of each element within the window (every UI object in Win32 is a HANDLE), we can make some observations and an analysis together to check whether that is or isn't a malicious program. Observations such as:
- Having a specfic text anywhere inside the window
- Having an UI element with a specific Window Style (size, color)
- Keyword is found in any Win32 UI element
You can take a cheat tool and identify the pattern of its UI, such as the size of the buttons, the style, the text etc, and so have our anti-cheat whenever it encounters these patterns, already identify it as a malicious program, for example.
Avoiding injected modules
Obviously, we don't want any DLL to be injected into our process and so it has access to our code while executing, memory etc.
To avoid this kind of problem, the solution is quite simple: we check all the modules present within our application, and if there is any unknown module in the middle, we detect it as an intruder and we expel it from our execution.
Duplicated process instance
A very common method that people use for handling a process's memory is using ReadProcessMemory and WriteProcessMemory. You must first perform an OpenProcess, to assure all the necessary privileges for improper memory manipulation in oder to use these two functions.
The OpenProcess function works by creating a new instance of our process, making it as if two distinct HANDLEs point to the same running process.
We check all instances related to the running process in order to avoid this, and if that instance is from an unknown process, we close the HANDLE of it, not allowing any more memory manipulation (either writing or reading).
Hooking Function
This protection is quite simple, but it can be pretty effective against cheaters who do not have as much knowledge in reverse engineering. This protection can also be added to the others mentioned in this post, therefore, getting even better.
The protection consists of simply checking if a function has been hooked, checking if the memory of the function we want to protect is using Assembly statements of type JMP.
bool __forceinline IsHookAPI( BYTE* pbFunction )
{
//JMP
if( pbFunction[0] == 0xE9 )
return true;
//JMP DWORD PTR DS:[...]
if( pbFunction[0] == 0xFF && pbFunction[1] == 0x25 )
return true;
return false;
}
To use the IsHookAPI function, we just call it by giving the memory address of the function we want to check. Example:
if( IsHookAPI( (BYTE*)&GetTickCount ) )
//Do something...
In the example above we are checking if the GetTickCount function was hooked to another place, and if it was, the check will return positive and so we can detect a cheater.
Checksum
This is one of the most used methods when it comes to protection, and it can be used in several places. What we do with this method, is basically to check the integrity of our file or memory, hoping that it matches the original. If this integrity check doesn't match the expected, we can conclude that there has been a change in the file/memory and identify the user as a cheater.
A checksum is a small-sized datum derived from a block of digital data for the purpose of detecting errors that may have been introduced during its transmission or storage. By themselves, checksums are often used to verify data integrity but are not relied upon to verify data authenticity. Source: Wikipedia
You can use this method in important files of your program, the executable file itself or even the section .text from your executable.
The .text section of the executable is where the program code is located. In it we have the permission to read and execute. Therefore, if any user makes any changes in this section, we know that the code has been tampered with.
Calculating the Checksum
The one I choose to use was CRC32, although, there are several ways to calculate the checksum of something. This kind of verification is widely used for error detection in network protocols and storage devices.
This kind of algorithm may not be the best choice for our case, but I chose it because it is relatively simple and very easy to implement.
int VerifyChecksum::GetChecksum( void* pBuffer, DWORD dwSize )
{
//Calculate the checksum of .text section
boost::crc_32_type result;
result.process_bytes( pBuffer, dwSize );
return result.checksum();
}
If you do not want to implement it on your own, you can use the boost library, where we already have a function made for this.
Protecting function calls
As I mentioned at the beginning of this post, the best way not to need to protect the client side of your program, is to do as much as you can on the server side. However, there are still those things that we have to leave on the client side and we would not like in any way that the user could use.
What we do in this function is to check if the return address of the function we are protecting is within the scope of the program, and also, if it is being executed by a known thread. If any of these checks return negative, we know that the function may be being called by some external program, such as one .dll injected, and thus detect as an improper call.
You can avoid future headaches and be able to protect your project from cheaters with those simple tips. Although some of these methods are quite simple, the cheater must have a considerable level of reverse engineering, and be able to get through all the existing protections (which will be scattered throughout your program).
I emphasize once again, that the best way to protect your program, is to leave delicate workings to the server side, preventing your users from having access to resources that they should not have.
So, architect your server properly!🤗
Posted on October 9, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.