Introduction

Hello once again, welcome to another short blog regarding a nifty little WinApi feature that we can use to check if the game has been tampered with before our main thread has been executed. The magic function behind the blog will be NtQueryInformationProcess. An undocumented Winapi function that allows the developer to query quite a lot of information about the process passed. In the next chapter we’ll speak more about NtQueryInformationProcess and how it’s used.

NtQueryInformationProcess

So, the function is pretty simple to use. The function delegate goes as follows;

typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)(
    IN  HANDLE ProcessHandle,
    IN  PROCESSINFOCLASS ProcessInformationClass,
    OUT PVOID ProcessInformation,
    IN  ULONG ProcessInformationLength,
    OUT PULONG ReturnLength    OPTIONAL
    );

As we can see from the first argument, you’re able to actually query information about any process as long as you have a valid handle for said application. An example of this being used could be Process Hacker. You might’ve noticed that Process Hacker has quite a lot of interesting information about the process that could potentially expose malicious users. It’s always quite fascinating looking at Process Hacker and replicating parts of it and using it to combat cheaters. It’s a powerful tool and quite a handful of the information gathered is through this Win32 API function. As for the second argument, you can see it takes a PROCESSINFOCLASS but since the default one that Windows offers in User mode is quite bare, you can implement your own full version of the enum. In the next chapter, we’ll take a look at a somewhat full enum.

_PROCESS_INFORMATION_CLASS

typedef enum _PROCESS_INFORMATION_CLASS {
    ProcessBasicInformation,
    ProcessQuotaLimits,
    ProcessIoCounters,
    ProcessVmCounters,
    ProcessTimes,
    ProcessBasePriority,
    ProcessRaisePriority,
    ProcessDebugPort,
    ProcessExceptionPort,
    ProcessAccessToken,
    ProcessLdtInformation,
    ProcessLdtSize,
    ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers,
    ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup,
    ProcessPriorityClass,
    ProcessWx86Information,
    ProcessHandleCount, // What we're using today!
    ProcessAffinityMask,
    ProcessPriorityBoost,
    MaxProcessInfoClass
} PROCESS_INFORMATION_CLASS, *PPROCESS_INFORMATION_CLASS;

Essentially, after throwing one of the values from this enum into the function call, it goes through a large switch-case statement and returns appropriate information each time. This is a really useful function for debugging a process either internally or externally to check for any possible tampering.

ProcessHandleCount

Let’s intially start by explaining what passing ProcessHandleCount does. In short, after passing this parameter through NtQueryInformationProcess it’ll get picked up in the appropriate case and it’ll make a call directly to ObGetProcessHandleCount() which directly parses the handle table for the HandleCount entry. If there is no handle table please remember that the HandleCount will directly be set to 0 which is not what we want so we must always be wary of the return values. Below is a simplified version of the code for the Case statement for ProcessHandleCount in NtQueryInformationProcess

case ProcessHandleCount:
  /* Reference the process */
  Status = ObReferenceObjectByHandle(ProcessHandle,
            PROCESS_QUERY_INFORMATION,
            PsProcessType,
            PreviousMode,
            (PVOID*)&Process,
            NULL);

  /* Count the number of handles this process has */
  HandleCount = ObGetProcessHandleCount(Process);

  /* Return the count of handles */
  *(PULONG)ProcessInformation = HandleCount;

We can see just how simple this code really is, this is more ideal for us because it lessens the chances of the results being spoofed which is not what we want.

How this all relates to self-debugging

Now that we’ve described all the WinAPI sides that we’ll be using, let’s talk about how exactly we are going to utilise all this for our purpose of preventing code execution before the game is initialised. To do this, we’ll have to talk about a certain vulnerability within some applications that are not protected properly. Some cheaters will launch the process in suspended mode, ensuring there is no possible execution that can halt their injection. After launching the process in suspended mode this will ensure they are the first to touch the binary and the game will typically think that nothing malicious has executed. In this scenario, the cheater has launched the process in a suspended state, created a thread, and resumed the process. Without proper thread verification, the game will simply think that this thread has existed since the beginning and is potentially one of their own and any anti-thread creation will NOT affect this thread because the thread was created before the anti-thread creation was ever processed. In this case, you might be wondering, what could the solution to all this be? Simple, check the HandleCount! On the entry point of the application, check the handle count of the application and if it’s bigger than usual you can start doing heuristics on the user, a good idea would be looping through the handle table and calling NtQueryObject with the ObjectTypeInformation parameter to check the object’s type name. The idea is to be running your anti-cheat work as a dependency within the main applications IAT. This is because of how windows applications work, their dependencies run before the actual game. So by running this code as a dependency which is called relatively early you can get a raw low handle count which will make this a thousand times more accurate.

Drawbacks

Although this seems like a somewhat viable idea, it’s still not meant to be used mainly alone. The reason why I say this is because closing the handle will basically decrement the HandleCount (Obviously). Terminating a thread without closing the handle will still leave the handle.

In this picture, the thread was terminated and yet the handle still remains.

Now, let’s show an example of how the application looks like when started in Suspended mode As you can see, the handle list will be completely empty which makes it easier for our check to see if something is wrong when the anti tamper dependency is loaded.

The entire point of a small check like this would be to automate more of the “Which client shall we start doing extra checks on?” scenario. If a user has a weirdly larger amount of handle count than other people and checking the handles table shows the server that this user in particular has weird threads being created and running in the background. Perhaps the thread has some IPC work that requires an open handle to make a connection. There are just so many small non-noticeable instances where handles are leaking and are potentially from a malicious application.

Conclusion

In conclusion, although this check is not the greatest in terms of sole detection. It’s definitely viable in some sense because there are cases where the handle must stay open. Using this to check weird increases on the handle count all of a sudden before the game has been initialised could tell the client that it needs to start checking this user because it could’ve potentially bypassed quite a lot of security measures before the process was initialised. This is a common occurrence in some applications.