While digging through the assembly code of the Android Widevine library libwvhidl.so (API Level 26, Android 8.0) and also libwvdrmengine.so (API Level 32, Android 12.0), I stumbled upon something strange: a UUID that had no business being there.

More recent versions are stripped and have changed a little, but I found a similar reference in sub_50F54 inside android.hardware.drm-service.widevine (API Level 36, Android 15.0) when searching for the byte sequence EA 83 BC F2:

bool __fastcall sub_50F54(_QWORD *a1)
{
  if ( *a1 == 0xCE4AD679A98BEFEDLL && a1[1] == 0xED211DD5DC27C8A3LL )
    return 1;
  return *a1 == 0x344AC73CE41F7029LL && a1[1] == 0x479A43C790AE5B8CLL;
}

## Discovery of the suspicious code

Widevines `isCryptoSchemeSupported` function contained this interesting check:

```cpp
bool __fastcall wvdrm::isWidevineUUID(wvdrm *this, const unsigned __int8 *a2)
{
  return !(*(_QWORD *)this ^ 0xCE4AD679A98BEFEDLL | *((_QWORD *)this + 1) ^ 0xED211DD5DC27C8A3LL)
      || (*(_QWORD *)this ^ 0x344AC73CE41F7029LL | *((_QWORD *)this + 1) ^ 0x479A43C790AE5B8CLL) == 0;
}

Two UUIDs were hardcoded directly in the binary. The first one I recognized immediately — clearly Widevine. But the second? No idea.

The logic was simple: if the UUID passed in parameter matches one of the two values, the function returns true. Otherwise, false. Nothing exceptional — but why two UUIDs?

Decoding the UUIDs from their memory representation

Decoding UUIDs from their little-endian representation was annoying. The UUID specification is clear about the format, but when you’re dealing with raw 64-bit memory values, you need to rebuild the fields in the right order.

def uuid_from_little_endian_bytes(low64, high64):
    # Convert 64-bit values to bytes (little-endian)
    bytes_low = low64.to_bytes(8, 'little')
    bytes_high = high64.to_bytes(8, 'little')
    
    # Reconstruct UUID fields
    # (note: the first 3 fields are big-endian in the final UUID format)
    time_low = int.from_bytes(bytes_low[0:4], 'big')
    time_mid = int.from_bytes(bytes_low[4:6], 'big') 
    time_hi = int.from_bytes(bytes_low[6:8], 'big')
    clock_seq = int.from_bytes(bytes_high[0:2], 'big')
    node = int.from_bytes(bytes_high[2:8], 'big')
    
    return f"{time_low:08x}-{time_mid:04x}-{time_hi:04x}-{clock_seq:04x}-{node:012x}"

# Decode both UUIDs found
print(uuid_from_little_endian_bytes(0xCE4AD679A98BEFED, 0xED211DD5DC27C8A3))
# Result: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed (official Widevine UUID)

print(uuid_from_little_endian_bytes(0x344AC73CE41F7029, 0x479A43C790AE5B8CLL))
# Result: 29701fe4-3cc7-4a34-8c5b-ae90c7439a47 (mysterious UUID)

The first UUID matches the official Widevine identifier according to DASHIF. But the second one appears nowhere in official documentation.

Investigation of the mysterious UUID

I dug through archives and forums to understand where this 29701fe4-3cc7-4a34-8c5b-ae90c7439a47 came from. The history is:

2015 — First mentioned on the Apple Developer Forums, suspected to be related to early FairPlay 2.0.

2017 — An official issue on DASH-IF proposes replacing it with the official FairPlay UUID 94ce86fb-07ff-4f43-adb8-93d2fa968ca2.

2018 — A commit in Shaka Packager adds support for this UUID for FairPlay.

2023 — Shaka Packager migrates to the official UUID, but keeps the old one “for backwards compatibility only.”

2025 — A pastebin claims Netflix still uses this unofficial UUID for FairPlay.

In short, this UUID seems to have been an early, unofficial FairPlay identifier used before standardization.

2015 : Première mention sur les forums Apple Developer, soupçonné d’être lié à FairPlay 2.0.

2017 : Une issue officielle sur DASH-IF propose de le remplacer par l’UUID FairPlay officiel 94ce86fb-07ff-4f43-adb8-93d2fa968ca2.

2018 : Commit dans Shaka Packager qui ajoute le support de cet UUID pour FairPlay.

2023 : Shaka Packager migre vers l’UUID officiel, mais garde l’ancien “for backwards compatibility only”.

2025 : Un pastebin mentionne que Netflix utiliserait encore cet UUID non-officiel pour FairPlay.

What I eventually concluded

Widevine’s isCryptoSchemeSupported function accepts two UUIDs:

  • The official Widevine UUID (edef8ba9-79d6-4ace-a3c8-27dcd51d21ed)
  • An old, unofficial FairPlay UUID (29701fe4-3cc7-4a34-8c5b-ae90c7439a47)
long double __usercall wvdrm::hardware::drm::V1_4::widevine::WVCryptoFactory::isCryptoSchemeSupported@<Q0>(
        wvdrm *this@<X1>,
        __int64 a2@<X8>)
{
  bool isWidevineUUID;
  long double result;

  // Check whether the UUID matches Widevine OR the old FairPlay ID
  isWidevineUUID = wvdrm::isWidevineUUID(this, (const unsigned __int8 *)this);
  
  // Initialize output buffer and store result
  *(_OWORD *)&result = 0u;
  *(_BYTE *)(a2 + 32) = 0;
  *(_OWORD *)a2 = 0u;
  *(_OWORD *)(a2 + 16) = 0u;
  *(_BYTE *)(a2 + 33) = isWidevineUUID;  // The actual result
  
  return result;
}

The function merely sets a flag in a buffer (offset 33) and always returns 0. It’s not strict validation — more like an informational detection for upper layers.

Which brings me to the question:

Why does this UUID 29701fe4-3cc7-4a34-8c5b-ae90c7439a47, historically tied to unofficial FairPlay, appear inside an Android library related to Widevine?

Netflix APK

2025-11-15 Update: After analyzing an Netflix APK with jadx, I confirmed that it still contains the 29701fe4-3cc7-4a34-8c5b-ae90c7439a47 UUID within its Widevine-related libraries. jadx.png

The UUID built from those two long values is:

static final UUID NetflixWidevineUUID =
    new UUID(2985921618079337012L, -8332874748677350841L);

Using the Java UUID constructor (mostSigBits, leastSigBits), the resulting UUID is: 29701fe4-3cc7-4a34-8c5b-ae90c7439a47!

I still don’t know why Netflix would include this old FairPlay UUID in their Widevine code, but it’s definitely there. Mystery remains unsolved.