Your Duolingo Is Still Talking to ByteDance: How Pangle Fingerprints You Across Apps After You Said No

This is Part 2 of my Pangle SDK research. Part 1 covered how I broke the encryption. This post covers what I found when I started comparing the decrypted data across apps.

In Part 1, I decrypted 694 Pangle SDK payloads and documented what ByteDance collects: battery level, storage capacity, IP address, boot timestamps, and detailed hardware profiles from 40+ apps. The encryption turned out to be the cryptographic equivalent of taping the key to the doorframe.

But decrypting the data was only half the story. The real question was: what happens when you compare the same user's data across different apps?

The Setup

Apple's App Tracking Transparency (ATT) framework gives users a simple choice. When an app asks "Allow this app to track your activity across other companies' apps and websites?", you can say no. When you do, the app gets a zeroed-out IDFA (Apple's advertising identifier). The app is not supposed to be able to link your activity across other apps. That is the entire point of ATT.

I wanted to test whether the Pangle SDK respects this.

Using my decrypted dataset of 874 Pangle events across 26 apps and 32 devices, I looked for cases where the same device appeared in two or more unrelated apps with ATT denied.

What ATT Denial Looks Like in Pangle's Data

When a user denies tracking, Pangle's request payload shows the right signals on the surface:

{
  "atts": 2,
  "idfa": "00000000-0000-0000-0000-000000000000",
  "idfv": "[redacted]",
  "lmt": 1
}

atts: 2 means "denied." The IDFA is zeroed. Limit Ad Tracking is on. The IDFV is a per-developer identifier that changes across apps from different developers. So far, Apple's rules appear to be followed.

But scroll down in the same payload.

The Fingerprint That Survives ATT

Every Pangle request, regardless of ATT status, also includes:

{
  "device": {
    "device_model": "iPhone15,4",
    "device_type": "D37AP",
    "total_mem": "6108520448",
    "total_space": "128241598464",
    "cpu_num": 6,
    "screen_height": 852,
    "screen_width": 393,
    "boot": "1770482937261",
    "power_on_time": "84291553",
    "battery_remaining_pct": 50,
    "volume": 100,
    "low_power_mode": 0,
    "darkmode": 0,
    "font_size": 17,
    "sys_build_version": "23C71",
    "ip": "192.168.x.x",
    "carrier_name": "--"
  }
}

That total_space value, 128241598464 bytes, is not a round number. It is the exact byte count reported by the device filesystem. It varies based on OS version, provisioning, and device history. total_mem is similarly exact. boot is the precise millisecond the device last started up.

None of these fields are needed to serve an ad.

Cross-App Matching

I found three devices that appeared in two or more unrelated apps with ATT denied. In every case, the hardware fingerprint fields were identical across apps while the IDFV (which is supposed to be the only cross-referenceable identifier) was different.

Device 1: Triple Master and MealOrbit Weekly

Two completely unrelated apps. Different developers. Different IDFVs. ATT denied in both.

Triple Master MealOrbit Weekly
ATT Denied Denied
IDFA zeroed zeroed
IDFV F7C2A91E... 3B8D6F42...
Model iPhone15,4 iPhone15,4
RAM 6108520448 6108520448
Storage 128241598464 128241598464
Screen 393x852 393x852
CPU 6 cores 6 cores
Font size 17 17
Build 23C71 23C71
Device type D37AP D37AP

Every hardware field matches. ByteDance receives two separate requests from two separate apps with two separate IDFVs, but the device fingerprint is identical. A server-side join on (total_space, total_mem, model, build_version) links them instantly.

Device 2: QR Reader and ESM Timer

Same pattern. Two Japanese utility apps. Different developers. Different IDFVs. ATT denied in both.

QR Reader ESM Timer
ATT Denied Denied
IDFA zeroed zeroed
IDFV A1D47B3E... 82E6C5F1...
Model iPhone18,1 iPhone18,1
RAM 12418932736 12418932736
Storage 256109264896 256109264896
Screen 402x874 402x874
Font size 17 17
Build 23D127 23D127
Device type V53AP V53AP

Identical fingerprint. Different IDFVs. The user said no to tracking in both apps. ByteDance has what it needs to link them anyway.

Device 3: The Worst Case

This one used three apps with Pangle. ATT was denied in two of them. But in the third app, the user authorized tracking, giving Pangle the real IDFA.

Water Sort Puzzle Trivia Spin Find Differences
ATT Denied Denied Authorized
IDFA zeroed zeroed [real IDFA]
IDFV C4A83D16... 5B2E7A93... D91C48E7...
RAM (empty) 6042918912 6042918912
Storage (empty) 256387244032 256387244032
Model (empty) iPhone15,2 iPhone15,2
Build (empty) 23C55 23C55

This is the most damaging case. ByteDance receives the real IDFA from Find Differences alongside the hardware fingerprint. It receives the same hardware fingerprint from Trivia Spin, where ATT was denied. A trivial server-side join connects the denied user to their real advertising ID.

The user explicitly denied tracking in Trivia Spin. ByteDance has the data to ignore that decision.

How Unique Is the Fingerprint?

The total_space field alone is highly distinctive. It is not "256 GB" rounded. It is 256387244032 bytes or 128241598464 bytes, exact values that depend on OS version, provisioning, and device history. Across 32 devices in my dataset, I did not observe a single duplicate total_space value.

Combined with total_mem, device_model, sys_build_version, and font_size, the fingerprint entropy is more than sufficient to uniquely identify a device among millions.

And that is before considering the dynamic signals that Pangle also collects on every request: boot timestamp (exact millisecond), battery percentage, volume level, dark mode, low power mode, and local IP. Two requests from different apps made within the same minute would share nearly all of these dynamic values, providing additional correlation signals.

What Apple's Rules Say

Apple's App Store Review Guidelines, Section 5.1.2(i):

Apps must not derive data from a device for the purpose of uniquely identifying it.

Apple's Developer Program License Agreement, Section 3.3.9 prohibits sending device data to third parties via analytics or advertising SDKs when the user has opted out.

There is no carve-out that says "you can collect enough signals to fingerprint the device as long as you don't technically call it an identifier."

What I Cannot Prove

I can prove that Pangle collects hardware fingerprint signals that are identical across apps on the same device, even when ATT is denied. I can prove these signals are sufficient to uniquely identify a device.

I cannot prove from network traffic alone that ByteDance actually performs the server-side correlation. The data enables fingerprinting. Whether ByteDance acts on it requires access to their server-side systems.

However, the collection of total_space (exact byte count of device storage) and boot (exact millisecond timestamp) has no legitimate ad serving purpose. These fields are not needed to select, render, or measure an ad. Their only plausible use is device identification.

The Numbers

Metric Value
Total devices with Pangle traffic 32
Total apps using Pangle 26
Total decrypted events 874
Events where user denied tracking 240 (27.5%)
Events with zeroed IDFA 231
Devices with cross-app Pangle traffic 5
Devices with cross-app + ATT denied + matching fingerprints 3

27.5% of all Pangle events came from users who explicitly said "Ask App Not to Track." In every case, Pangle collected the full hardware fingerprint anyway.

Notable Apps Affected

Among the apps transmitting Pangle fingerprint data while ATT was denied:

  • Letterboxd (movie tracking, 57 denied events)
  • Webtoon (comics, 8 denied events)
  • Manga Plus by Shueisha (manga, 4 denied events)
  • Sleep If U Can (alarm, 51 denied events)
  • Cat Hole Puzzle (game, 10 denied events)
  • Happy Paint (game, 8 denied events)
  • iCookie Merge (game, 6 denied events)
  • Water Sort Puzzle (game, 6 denied events)
  • Nordcurrent Canteen (cooking game, 4 denied events)

These apps span entertainment, utilities, and gaming. Users downloading a movie tracker or a manga reader are unlikely to know that ByteDance receives their exact storage capacity and boot timestamp on every ad request.

What This Means

ATT gives users a sense of control. You see the prompt. You tap "Ask App Not to Track." You assume the app listened.

But ATT only restricts the IDFA. It does not restrict the dozens of hardware and system signals that the Pangle SDK collects in every single request. And the combination of those signals is enough to do exactly what ATT was supposed to prevent.

ByteDance zeroes the IDFA when you say no. They follow the letter of the rule. But they collect everything they need to reconstruct your identity without it.

Methodology

Analysis was conducted on decrypted Pangle SDK payloads using the methodology described in Part 1. No systems were accessed without authorization. No individual user data is disclosed. Device identifiers (IDFVs, IDFAs, IPs) shown in this post are redacted or truncated. All traffic was observed from consented participants in a research study.

Pangle SDK versions observed range from 6.2.0.7 through 7.8.0.8. The behavior described is consistent across all observed versions.

Subscribe to Buchodi's Threat Intel

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe