Lift the game multi-opening restriction, close the mutex handle

There are many ways to limit the game

For example, traversing windows, traversing processes, configuration files, registry, mutexes, mac addresses, ip, public files, memory mapping, etc. There are many methods.

But the vast majority of games are limited by the use of mutexes.

In this lesson, we will explain how to close the mutex handle to achieve multiple openings. The example is CQYH

(The protection suggestion here is to add a variety of ways to limit multiple openings and increase the use of multiple mutexes in the logic, so as to avoid being directly maliciously closed)

1. Multiple authentication restrictions

First we open the game

Then open a second window

It is found that we have detected that we have opened a window, but there is no restriction

Restriction occurs when opening the third window

It is obvious that more openings are restricted, only 2 can be opened

2. The tool checks the mutex and closes it

We can use tools to view mutexes, you can use XT, PCH and other tools

You can also directly download the following tools from the official account Ren Niaofei reverse engineering resource download

Open the software, find our process, right click to view the handle

There are many handles, find the handle of Mutant type

Found that there are many, no name can be ignored

There are 20-30 handles with names left

Let’s close them one by one to see which one is closed and then we can open more windows

It is found that closing this is enough, the name is AOD_Game

Can open 3 windows

Three, write code, close the mutex handle

Add a header file “Mutex.h”

Used as a close mutex handle

#pragma once
#include <string.h>
#include <windows.h>
#include <winternl.h>
#include <TlHelp32.h>

// define the required macros
#define STATUS_SUCCESS 0x00UL
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004

#define SystemHandleInformation 16
#define SE_DEBUG_PRIVILEGE 0x14

// Define the structure that needs to be used
typedef enum _OBJECT_INFORMATION_CLASSEX {
  ObjBasicInformation = 0,
  ObjNameInformation,
  ObjTypeInformation,
} OBJECT_INFORMATION_CLASSEX;

typedef enum _PROCESSINFOCLASSEX
{
  ProcessHandleInformation = 20,
}PROCESSINFOCLASSEX;

typedef struct _SYSTEM_HANDLE
{
  ULONG ProcessId;
  BYTE ObjectTypeNumber;
  BYTE Flags;
  USHORT Handle;
  PVOID Object;
  ACCESS_MASK GrantAccess;
}SYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
  DWORD HandleCount;
  SYSTEM_HANDLE Handles[1];
}SYSTEM_HANDLE_INFORMATION;

typedef struct _OBJECT_NAME_INFORMATION
{
  UNICODE_STRING ObjectName;
}OBJECT_NAME_INFORMATION;

// Declares that the API is not exported
typedef NTSTATUS(WINAPI* ZwQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASSEX, LPVOID, DWORD, PDWORD);
ZwQueryInformationProcessProc ZwQueryInformationProcess;

typedef NTSTATUS(WINAPI* ZwQuerySystemInformationProc)(DWORD, PVOID, DWORD, DWORD*);
ZwQuerySystemInformationProc ZwQuerySystemInformation;

typedef NTSTATUS(WINAPI* ZwQueryObjectProc)(HANDLE, OBJECT_INFORMATION_CLASSEX, PVOID, ULONG, PULONG);
ZwQueryObjectProc ZwQueryObject;

typedef NTSTATUS(WINAPI* RtlAdjustPrivilegeProc)(DWORD, BOOL, BOOL, PDWORD);
RtlAdjustPrivilegeProc RtlAdjustPrivilege;

typedef DWORD(WINAPI* ZwSuspendProcessProc)(HANDLE);
ZwSuspendProcessProc ZwSuspendProcess;

typedef DWORD(WINAPI* ZwResumeProcessProc)(HANDLE);
ZwResumeProcessProc ZwResumeProcess;

#pragma warning (disable: 6011)
#pragma warning (disable: 6001)
#pragma warning (disable: 6387)
#include <stdio.h>

// Elevate process privileges
BOOL ElevatePrivileges()
{
  HANDLE hToken;
  TOKEN_PRIVILEGES tkp;
  tkp.PrivilegeCount = 1;
  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    return FALSE;
  LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
  tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
  {
    return FALSE;
  }

  return TRUE;
}

// Initialize unexported API
BOOL GetUnDocumentAPI()
{
  ZwSuspendProcess = (ZwSuspendProcessProc)
    GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSuspendProcess");

  ZwQuerySystemInformation = (ZwQuerySystemInformationProc)
    GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwQuerySystemInformation");

  ZwQueryObject = (ZwQueryObjectProc)
    GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwQueryObject");

  ZwResumeProcess = (ZwResumeProcessProc)
    GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwResumeProcess");

  ZwQueryInformationProcess = (ZwQueryInformationProcessProc)
    GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwQueryInformationProcess");

  if ((ZwSuspendProcess == NULL) || \
    (ZwQuerySystemInformation == NULL) || \
    (ZwQueryObject == NULL) || \
    (ZwResumeProcess == NULL) || \
    (ZwQueryInformationProcess == NULL))
    return FALSE;

  return TRUE;
}

// Close the specified Mutex. If there is a problem, follow the official account. Ren Niaofei reverse engineering
BOOL closeMutexHandle(UINT Proc_id, const wchar_t* Mutex_name)
{
  HANDLE duplicateHnd, sourceHnd = 0;
  DWORD procHndNum;
  SYSTEM_HANDLE* currnetHnd;
  DWORD buffLen = 0x1000;
  NTSTATUS status;
  SYSTEM_HANDLE_INFORMATION* buff = (SYSTEM_HANDLE_INFORMATION*) malloc(buffLen);
  UINT count = 0;
  if ((ElevatePrivileges() == FALSE) || (GetUnDocumentAPI() == FALSE))
    return FALSE;

  do
  {
    status = ZwQuerySystemInformation(SystemHandleInformation, buff, buffLen, & amp; buffLen);
    if (status == STATUS_INFO_LENGTH_MISMATCH)
    {
      free(buff);
      buff = (SYSTEM_HANDLE_INFORMATION*)malloc(buffLen);
    }
    else
      break;

  } while (1);

  OBJECT_NAME_INFORMATION* objNameInfo = (OBJECT_NAME_INFORMATION*) malloc(0x1000);
  OBJECT_NAME_INFORMATION* objTypeInfo = (OBJECT_NAME_INFORMATION*) malloc(0x1000);

  for (int idx = 0; idx < (int)buff->HandleCount; idx ++ )
  {
    currnetHnd = &(buff->Handles[idx]);

    if (currnetHnd->ProcessId == Proc_id)
    {
      sourceHnd = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, FALSE, Proc_id);
      (ZwSuspendProcess)(sourceHnd);
      (ZwQueryInformationProcess)(sourceHnd, ProcessHandleInformation, &procHndNum, sizeof(DWORD), NULL);
      //The effective handle of the process starts from 4 and increments by 4 each time
      unsigned short hndNum = 4;
      for (int idx = 0; idx < (int)procHndNum; hndNum + = 4)
      {
        //Judge whether it is a valid handle, return TRUE, it is a valid handle
        if (!DuplicateHandle(sourceHnd,
          (HANDLE)hndNum,
          GetCurrentProcess(),
           &duplicateHnd, 0, FALSE, DUPLICATE_SAME_ACCESS))
        {
          continue;
        }
        else
        {
          memset(objNameInfo, 0, 0x1000);
          memset(objTypeInfo, 0, 0x1000);

          ZwQueryObject((HANDLE)duplicateHnd, ObjNameInformation, objNameInfo, 0x1000, NULL);
          ZwQueryObject((HANDLE)duplicateHnd, ObjTypeInformation, objTypeInfo, 0x1000, NULL);

          // find mutex compare name
          if (wcscmp(objTypeInfo->ObjectName. Buffer, L"Mutant") == 0)
          {
            if (objNameInfo->ObjectName.Length != 0 & amp; & amp; wcsstr(objNameInfo->ObjectName.Buffer, Mutex_name) != 0)
            {
              printf("%ws\
", objNameInfo->ObjectName.Buffer);
              CloseHandle(duplicateHnd);

              if (DuplicateHandle(sourceHnd,
                (HANDLE)hndNum,
                GetCurrentProcess(),
                 &duplicateHnd, 0, FALSE, DUPLICATE_CLOSE_SOURCE))
              {
                CloseHandle(duplicateHnd);
                (ZwResumeProcess)(sourceHnd);
                return TRUE;
              }
            }
            count + + ;
            if (count == 20) { return FALSE; }
          }

          CloseHandle(duplicateHnd);
          idx++;
        }
      }
    }
  }
  return FALSE;
}

Then we call

closeMutexHandle(sm[i].pid,L"AOD_Game");

Fourth, adjust the window position to open more

Then we do the window layout

Ok, our effect is achieved.