(Ongoing) Enumerate Handle Table
Kento Oki edited this page 2020-10-26 23:48:26 +09:00

The driver has a vulnerable IOCTL that allows us to validate specific handle by calling ExEnumHandleTable eventually in driver context.

This function called by the encryption-dedicated IOCTL handler as follows:

PAGE:FFFFF800188CD2D1                 cmp     ebx, 81094000h
PAGE:FFFFF800188CD2D7                 jnz     loc_FFFFF800188CD62B
PAGE:FFFFF800188CD2DD                 mov     rax, [rsp+30h]
PAGE:FFFFF800188CD2E2                 mov     ecx, [rax]
PAGE:FFFFF800188CD2E4                 call    sub_FFFFF800188C35B0 // <- [SearchForHandleTableByProcessId_FUN_000135b0]

As you can see the IOCTL code is 0x81094000.
Also the function SearchForHandleTableByProcessId_FUN_000135b0 is like:

ulonglong SearchForHandleTableByProcessId_FUN_000135b0
                    (uint param_1_ProcessId,undefined8 param_2,undefined8 param_3,undefined8 param_4
                    )

{
  int iVar1;
  ulonglong local_res10 [3];
  
  local_res10[0] = 0;
  iVar1 = PsLookupProcessByProcessId
                    ((ulonglong)param_1_ProcessId,local_res10,param_3,param_4,0xffffffff);
  if (iVar1 < 0) {
    local_res10[0] = LookupHandleTable_FUN_000134a8(param_1_ProcessId);
  }
  else {
    ObfDereferenceObject(local_res10[0]);
  }
  return (ulonglong)(local_res10[0] != 0);
}

As we can see there is a some weird code there...
Especially in if (iVar1 < 0) which if the PsLookupProcessByProcessId failure, it calls LookupHandleTable_FUN_000134a8 as follows:

ulonglong LookupHandleTable_FUN_000134a8(uint param_1_ProcessId)

{
  char cVar1;
  ulonglong uVar2_HandleTableEntry;
  ulonglong uVar2;
  ulonglong uVar3_ReturnHandleTableEntry;
  
  uVar2_HandleTableEntry = ExEnumHandleTable_FUN_0001556c(param_1_ProcessId);
  cVar1 = MmIsAddressValid(uVar2_HandleTableEntry);
  if ((((cVar1 == '\0') || (uVar2_HandleTableEntry == 0xffffffffffffffff)) ||
      (uVar2_HandleTableEntry == 0)) ||
     ((cVar1 = MmIsAddressValid(uVar2_HandleTableEntry - 0x30), cVar1 == '\0' ||
      (*(longlong *)(uVar2_HandleTableEntry - 0x30) == 0)))) {
    uVar3_ReturnHandleTableEntry = 0;
  }
  else {
    uVar2 = UnknownProbe_FUN_00013528(uVar2_HandleTableEntry);
    uVar3_ReturnHandleTableEntry = 0;
    if ((char)uVar2 != '\x01') {
      uVar3_ReturnHandleTableEntry = uVar2_HandleTableEntry;
    }
  }
  return uVar3_ReturnHandleTableEntry;
}

I've debugged the driver by hooking ExEnumHandleTable and some another major function, and confirmed it's correct.

[DBGPROT] HookedPsLookupProcessByProcessId Called with 6195 and FFFFF88665811378 -> Result: 0xC000000B
[DBGPROT] mhyprot Called MmGetSystemRoutineAddress With PsLookupProcessByProcessId
[DBGPROT] HookedMmIsAddressValid Called with FFFFF806449EE910 -> Result: 1
... (loop about 4000-5000 times)
[DBGPROT] HookedMmIsAddressValid Called with FFFFC90F5AB79080 -> Result: 1
[DBGPROT] HookedMmIsAddressValid Called with FFFFC90F5A2CA380 -> Result: 1
[DBGPROT] HookedMmIsAddressValid Called with FFFFC90F5A2CA668 -> Result: 1
[DBGPROT] HookedExEnumHandleTable Called With FFFFF80649C65730 and 0 -> 0 // Here
[DBGPROT] HookedMmIsAddressValid Called with 0000000000000000 -> Result: 0
[DBGPROT] HookedExAllocatePool Called With 0 and 0xC // encryption behavior, freeing crypt buffer

The prototype code is:

typedef struct _MHYPROT_ENUM_HANDLE_TABLE_REQUEST
{
	uint64_t response;
	uint32_t process_id;
} MHYPROT_ENUM_HANDLE_TABLE_REQUEST, * PMHYPROT_ENUM_HANDLE_TABLE_REQUEST;

MHYPROT_ENUM_HANDLE_TABLE_REQUEST payload;
payload.process_id = process_id + 999; // pass the wrong process id to validate it

encrypt_payload(&payload, sizeof(payload));

request_ioctl(0x81094000, &payload, sizeof(payload));

...

the result is exact same as what I expected, the if statement must be returned with failure.
I still do not know what is a purpose of this function but I guess it's a kind of a way to check out that the all of processes do not have the game's process handle.

I'll drop some related function here:
Also if there are any updates, I'll re-write this document later.

ulonglong ExEnumHandleTable_FUN_0001556c(uint param_1_ProcessId)

{
  ulonglong uVar1_Result;
  ulonglong *puVar2_Context;
  code *pcVar1_FuncEnumeratorRoutine;
  
  if ((DAT_0001a6d0 == 0) && (DAT_0001a6d0 = FUN_00015954(), DAT_0001a6d0 == 0)) {
    return 0;
  }
  puVar2_Context = (ulonglong *)ExAllocatePool(1);
  pcVar1_FuncEnumeratorRoutine = ProbeForThreadHandle?_FUN_00015680;
  *puVar2_Context = (ulonglong)param_1_ProcessId;
  if (DAT_0001a748 != 0x3d) {
    pcVar1_FuncEnumeratorRoutine = EnumerateHandleRoutine_FUN_00015730;
  }
  ExEnumHandleTable(DAT_0001a6d0,pcVar1_FuncEnumeratorRoutine,puVar2_Context,0);
                    /* PHANDLE_TABLE_ENTRY */
  uVar1_Result = puVar2_Context[1];
  ExFreePoolWithTag(puVar2_Context,0);
  return uVar1_Result;
}
ulonglong EnumerateHandleRoutine_FUN_00015730
                    (longlong param_1,ulonglong *param_2,uint param_3,uint *param_4)

{
  ulonglong uVar1;
  ulonglong uVar2;
  
  uVar1 = ProbeForThreadHandle?_FUN_00015680(param_2,param_3,param_4);
  LOCK();
  uVar2 = *param_2;
  *param_2 = *param_2 + 1;
  uVar2 = ExfUnblockPushLock(param_1 + 0x30,0,uVar2);
  return uVar2 & 0xffffffffffffff00 | uVar1 & 0xff;
}