GetOfflineDeviceUniqueID: How Windows Derives Its Offline Device ID

mschuster911 pts0 comments

Inside GetOfflineDeviceUniqueID: How Windows Derives Its Offline Device ID

Introduction

Inside GetOfflineDeviceUniqueID: How Windows Derives Its Offline Device ID

Windows Internals

Inside GetOfflineDeviceUniqueID: How Windows Derives Its Offline Device ID

Posted<br>by

radulf

on July 20th, 2026

Windows exposes an undocumented function named clipc!GetOfflineDeviceUniqueID through clipc.dll, the Client Licensing Platform Client. The function returns a 32-byte, salt-scoped device identifier used by Windows licensing and device-identification components. Microsoft does not provide matching PDB symbols for clipc.dll and clipsvc.dll , so the internal function names, types, and prototypes shown below were reconstructed manually.<br>I began investigating the function after noticing that several commercial anti-cheat products call it alongside their existing hardware-identification routines. I wanted to determine exactly which machine-specific value Windows uses, where that value is stored, and how it is transformed into the final Offline Device ID.<br>This post follows the complete path from clipc.dll through its RPC interface into clipsvc.dll, reconstructs each supported retrieval method, identifies the Windows components that consume the result, and examines several broken behaviors in the function’s optional CRC outputs.

Throughout this post, I will follow the natural order in which I reversed it—from the moment I opened it in IDA, through every rabbit hole along the way, and up until the end, where I will provide a summary. First, I will start by analyzing the simplified decompilation of the function itself from the clipc.dll:<br>// clipc!GetOfflineDeviceUniqueID

enum RETRIEVAL_METHOD {<br>ODUID_DEFAULT = 0,<br>ODUID_TPM_EK, // TPM public endorsement key<br>ODUID_UEFI_VARIABLE_TPM, // OfflineUniqueIDEKPub UEFI variable<br>ODUID_UEFI_VARIABLE_RANDOMSEED, // OfflineUniqueIDRandomSeed UEFI variable<br>ODUID_UEFI_DEV_LOCK_UNLOCK, // no clipsvc impl (likely phone-only)<br>ODUID_XBOX_CONSOLE_ID, // unused here (Xbox takes another path)<br>ODUID_REGISTRY_ENTRY // HKLM\...\Services\TPM\ODUID\RandomSeed (RS2+)<br>};

HRESULT GetOfflineDeviceUniqueID(<br>DWORD cbSalt, // [in] salt length<br>const BYTE* pbSalt, // [in] salt bytes<br>RETRIEVAL_METHOD* pDeviceIDType, // [in,out] requested method / method used<br>DWORD* pcbDeviceID, // [in,out] buffer size; must be >= 32<br>BYTE* pbDeviceID, // [out] the 32-byte SHA-256 device ID<br>DWORD* pCookie, // [out,opt] extra field<br>DWORD* pCrcOk) // [out,opt] extra field<br>if (!pcbDeviceID || !pDeviceIDType)<br>return E_INVALIDARG;

if (*pcbDeviceID ClipSvcRelease (proc 1)<br>RETURN_IF_FAILED(ClipSvcAcquire(&ctx)); // proc 0

// Request: three tag-4 blobs.<br>ClipPropertyList in, out;<br>RETURN_IF_FAILED(in.SetBlob(0, &cbSalt, sizeof(DWORD))); // salt length<br>RETURN_IF_FAILED(in.SetBlob(1, pDeviceIDType, sizeof(DWORD))); // requested method<br>RETURN_IF_FAILED(in.SetBlob(2, pbSalt, cbSalt)); // the salt

RETURN_IF_FAILED(ClipSvcCall(ctx, /*cmd*/ 13, in, out)); // proc 2, cmd 13

// Reply: five tag-4 blobs.<br>const BYTE* hash = nullptr;<br>DWORD cookie = 0, crc_ok = 0;<br>RETURN_IF_FAILED(out.GetBlob (0, pDeviceIDType, sizeof(DWORD))); // method used<br>RETURN_IF_FAILED(out.GetBlob (1, pcbDeviceID, sizeof(DWORD))); // actual ID size<br>RETURN_IF_FAILED(out.GetBlobPtr(2, &hash, *pcbDeviceID)); // the ID hash<br>RETURN_IF_FAILED(out.GetBlob (3, &cookie, sizeof(DWORD)));<br>RETURN_IF_FAILED(out.GetBlob (4, &crc_ok, sizeof(DWORD)));

if (pCookie) *pCookie = cookie;<br>if (pCrcOk) *pCrcOk = crc_ok;

if (pbDeviceID) {<br>if (*pcbDeviceID > 32)<br>return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);<br>memcpy(pbDeviceID, hash, *pcbDeviceID);<br>return S_OK; // ctx / in / out cleaned up by their destructors

All snippets are cleaned x64 pseudocode reconstructed from decompiler output.

GetOfflineDeviceUniqueID seems to be an RPC wrapper over clipsvc.dll, which returns a 32-byte result into pbDeviceId. It communicates with RPCs via NdrClientCall3 to request to clipsvc.dll. So the actual implementation of where the data comes must be inside clipsvc.dll. The second argument of NdrClientCall3 is the nProcNum, which identifies the operation to dispatch. From my reversing, it seems like for ClipSvc, 0 is for Acquire, 1 is for release, and procNum 2 seems to be a generic command channel. ClipSvcCall seems to pass another kind of Operation id as 5th argument, for GetOfflineDeviceUniqueID, it is 13. 4th argument passed to the NdrClientCall3 is, the handle acquired.<br>CLIENT_CALL_RETURN RPC_VAR_ENTRY NdrClientCall3(<br>MIDL_STUBLESS_PROXY_INFO *pProxyInfo,<br>unsigned long nProcNum,<br>void *pReturnValue,<br>...<br>);

Here is the brief reconstruction of ClipSvcCall:<br>// clipc!ClipSvcCall (reconstructed, diagnostics removed)<br>//<br>// The single RPC worker behind the Clip* APIs. Flattens the input property<br>// list into a byte blob, invokes ClipSVC proc 2 with a command id, and<br>// rebuilds the reply blob into an output property list.<br>//<br>// handle a1 : ClipSVC session from ClipSvcAcquire (proc 0)<br>// cmd : operation...

dword return_if_failed getofflinedeviceuniqueid windows clipsvc device

Related Articles