> Windows Syscalls
ntoskrnl.exeT1106

NtCompareObjects

Returns STATUS_SUCCESS when two handles refer to the same underlying kernel object.

Prototype

NTSTATUS NtCompareObjects(
  HANDLE FirstObjectHandle,
  HANDLE SecondObjectHandle
);

Arguments

NameTypeDirDescription
FirstObjectHandleHANDLEinFirst handle to compare; any access mask is accepted.
SecondObjectHandleHANDLEinSecond handle to compare; any access mask is accepted.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x95win10-1507
Win10 16070x96win10-1607
Win10 17030x97win10-1703
Win10 17090x98win10-1709
Win10 18030x99win10-1803
Win10 18090x99win10-1809
Win10 19030x99win10-1903
Win10 19090x99win10-1909
Win10 20040x9Bwin10-2004
Win10 20H20x9Bwin10-20h2
Win10 21H10x9Bwin10-21h1
Win10 21H20x9Bwin10-21h2
Win10 22H20x9Bwin10-22h2
Win11 21H20x9Dwin11-21h2
Win11 22H20x9Dwin11-22h2
Win11 23H20x9Dwin11-23h2
Win11 24H20x9Fwin11-24h2
Server 20160x96winserver-2016
Server 20190x99winserver-2019
Server 20220x9Dwinserver-2022
Server 20250x9Fwinserver-2025

Kernel module

ntoskrnl.exeNtCompareObjects

Related APIs

CompareObjectHandlesNtDuplicateObjectNtQueryObjectNtQuerySystemInformation

Syscall stub

4C 8B D1            mov r10, rcx
B8 9F 00 00 00      mov eax, 0x9F
F6 04 25 08 03 FE 7F 01   test byte ptr [0x7FFE0308], 1
75 03               jne short +3
0F 05               syscall
C3                  ret
CD 2E               int 2Eh
C3                  ret

Undocumented notes

Added in Windows 10 1507 to give user-mode an honest equality test for kernel objects. Before this syscall, the only reliable way to know whether two handles aliased the same object was a stack of fallible heuristics (compare object names where available, compare PIDs for process handles, etc.). Internally `NtCompareObjects` resolves both handles to their underlying `_OBJECT_HEADER *` via `ObReferenceObjectByHandle` and tests pointer equality, then dereferences both. The implementation is in `ObCompareObjectsForCommonSecurity`. Return is binary: `STATUS_SUCCESS` (same object), `STATUS_NOT_SAME_OBJECT` (different objects), or one of the usual `STATUS_INVALID_HANDLE` variants. Type does not need to match — comparing a `Process` handle to a `Token` handle just returns `STATUS_NOT_SAME_OBJECT`. There is *no* public Win32 wrapper; the closest you get is `CompareObjectHandles` shipped only via the Windows SDK as a ucrt-side inline that calls into `KernelBase.dll`'s undocumented `CompareObjectHandles` (Win10 1809+).

Common malware usage

Low malware signal. Used in two places where it does appear: (1) some EDR introspection code that wants to confirm a duplicated handle still points at the same target object, and (2) certain implant designs (CobaltStrike BOFs, some Sliver extensions) use it as a lightweight OPSEC check before, for example, calling `NtDuplicateObject` on a handle they suspect was already grafted by an EDR-controlled process. It is also a convenient handle-to-handle equality test inside post-exploitation tooling that walks the system handle table (`NtQuerySystemInformation(SystemHandleInformation)`) and wants to deduplicate aliases. It does *not* itself bypass anything — it's a primitive used by other primitives.

Detection opportunities

There is no ETW provider that emits per-call telemetry for this syscall and Sysmon does not surface it. Defenders generally do not alert on `NtCompareObjects` because legitimate use is rare-but-real (KernelBase!CompareObjectHandles is called by parts of `Win32k`, `ImmersiveShell`, and `RuntimeBroker.exe`). The only place this becomes interesting is when an unsigned or freshly-loaded module suddenly issues many `NtCompareObjects` calls in a tight loop against the system handle table — that pattern is the post-exploitation handle-walk signature, but the call itself is a poor primary indicator.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtCompareObjects (SSN 0x9F on Win11 24H2 / Server 2025)
NtCompareObjects PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 9Fh          ; SSN — drifts; resolve dynamically for portability
    syscall
    ret
NtCompareObjects ENDP

cDeduplicate aliased handles after NtQuerySystemInformation

// Walks a handle list and collapses entries that alias the same object —
// a common post-exploitation cleanup before reporting unique findings.
for (ULONG i = 0; i < count; ++i) {
    for (ULONG j = i + 1; j < count; ++j) {
        if (NtCompareObjects(handles[i], handles[j]) == STATUS_SUCCESS) {
            // aliased -> drop handles[j] from the result set
            handles[j] = NULL;
        }
    }
}

rustCompareObjectHandles wrapper (windows-sys)

// Cargo: windows-sys = "0.59" (Win32_Foundation, Win32_System_Threading)
// CompareObjectHandles is a thin KernelBase wrapper over NtCompareObjects.
use windows_sys::Win32::Foundation::{CompareObjectHandles, HANDLE};

fn same_object(a: HANDLE, b: HANDLE) -> bool {
    unsafe { CompareObjectHandles(a, b) != 0 }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20