我破解了 AppLovin 用於其廣告中介流量的加密協議,並解密了數千筆擷取的真實請求。結論很直接:加密的競價請求攜帶了足夠的裝置資料,足以在使用者拒絕 ATT 的情況下,跨不同發佈者的應用程式確定性地重新識別同一支 iPhone。只要使用者在玩遊戲,每次橫幅廣告載入,大約每 30 秒,這些資料負載就會傳送給 AppLovin 以及大約 12 家下游廣告聯播網。認為 ATT 是確定性識別使用者的唯一方法的假設是錯誤的。裝置指紋辨識同樣有效。
每個 AppLovin 中介請求都是 HTTPS POST 發送到 ms4.applovin.com/1.0/mediate。在 TLS 層內,資料負載被包裹在 AppLovin 建構的第二層加密中。經過 base64 解碼後,傳輸信封是這樣的:
三個以冒號分隔的欄位,然後是密文:
該加密協議需要兩種成分:一個鹽值 (salt) 和 SDK 金鑰。鹽值是一個 32 位元組的常數,內嵌在每個 AppLovin SDK 二進位檔中,包含 21 個有意義的位元組,後面跟著 11 個零位元組。在我檢查過的每個 IPA 和 APK(iOS 上的 Solitaire Associations Journey、Hypermarket3D、Ludo Star、Yik Yak;Android 上的 Hypermarket3D)中,這些位元組都是相同的。傳輸中的 40 個字元 protocol-id 欄位是 sha1(salt).hex()。
關於此建構的一些事實:
來自一次 ATT 拒絕請求的真實 device_info:
加上另外 35 個欄位:螢幕安全區域插入、可用記憶體、電信業者代碼、國家代碼、地區設定、方向、狀態列高度、單調時鐘、電池標誌、安全連線狀態。實際上是 iOS 暴露給第三方程式碼的每一個系統屬性。
使用者拒絕了 ATT。IDFA 為零。其他一切照常。
典型的發佈者應用程式編譯了約 18 個需求合作夥伴 SDK:Meta、Google、Mintegral、Vungle、ironSource、Unity、InMobi、BidMachine、Fyber、Moloco、TikTok、Pangle、Chartboost、Verve、MobileFuse、Bigo、Yandex,以及 AppLovin 自家的 SDK。當需要填補橫幅廣告時,AppLovin SDK 會在本地呼叫所有這些 SDK,並要求「準備競價信號」。每個需求 SDK 會獨立建構一個不透明的代幣,其中包含其發佈者後端想要的任何裝置資料。AppLovin SDK 將它們全部打包到 signal_data[] 中,並將整個東西裝入其加密信封中。然後,AppLovin 的伺服器透過伺服器對伺服器的 OpenRTB 將每個代幣轉發給該競價者的競價伺服器。
裝置發出一個網路呼叫。資料會到達十幾家不同的廣告科技公司。
在我將引用的請求中,18 個轉接器中有 12 個返回了競價信號,範圍從 29 位元組(Verve,可能只是個擷取代幣)到 14.4 KB(Unity Ads)。在這 12 個中,有 4 個在 AppLovin 信封內是可讀的;8 個被加密傳送給目標競價者,對 AppLovin 不透明,只能在接收者的競價伺服器上解碼。
這四個可讀的值得仔細研究。InMobi 的競價信號 — 36 個 URL 編碼的 key=value 對,完整解碼如下:
InMobi 的代幣包含 AppLovin 自家的 device_info 中沒有的信號:可用磁碟空間(以 MB 為單位,6,275 — 每小時變化,熵非常高)、總磁碟空間、電池電量、充電狀態、深色模式偏好設定、特定型號的安全區域插入尺寸。下游競價者收集的裝置資料比轉發它的中介者還要多。
BidMachine 的信號額外包含:IANA 時區字串(America/New_York,比數值偏移更具體)、電信業者代碼、SKAdNetwork 接受列表,以及一個獨立的 36 個字元 UUID,這是 BidMachine 自訂的每個使用者識別符。他們建構了自己的持久性跨應用程式金鑰,儲存在其 SDK 的 UserDefaults 中,每次請求都與 Apple 的 IDFV 一起傳送。
Fyber 的信號最保守:使用者代理 (User-Agent)、套件名稱 (bundle)、型號 (model)、作業系統 (OS)、IDFA、IDFV、地區設定 (locale),以及一堆內部的 A/B 測試標誌名稱。
在這四個可讀的迷你信封中,裝置指紋以四種不同的結構傳送給四家廣告科技公司。其他八家則將類似的資料負載傳送給另外八家公司,以我們無法從外部檢查的方式加密。每次橫幅廣告載入時,指紋都會擴散出去。
在 app_info 中,有一個 18 個字元的十六進位欄位稱為 api_did。AppLovin 的伺服器在首次 SDK 初始化時分配它 — SDK 在首次安裝時會將完整的 device_info + app_info POST 到 applovin.com/2.0/device,伺服器會回傳一個 device_id,SDK 會快取它並在每次後續請求中將其作為 api_did 回顯。這是伺服器發出的、持久性的、旨在跨應用程式使用。
在我觀察到的六部不同的實體 iPhone 上,即使 ATT 被拒絕 — 硬體版本不同、IDFV 不同、來自不同發佈者的不同應用程式 — 100% 的信封都具有相同的開頭八個十六進位字元:10badd1d。將這些位元組讀作 ASCII:0xBADD1D = BADDID =「壞裝置 ID」。這是一個標記 — AppLovin 的伺服器會為每個被拒絕 ATT 的呼叫者回傳相同的字首,並為每個應用程式加上一個隨機的後綴鹽值。對於我觀察到的獲得 ATT 授權的使用者,有三個不同的字首(1023c...、10621...、10ebf...)— 每個 IDFA 一個,證實當 ATT 被授權時,api_did 是 IDFA 的確定性轉換。當 ATT 被拒絕時,該字首不包含任何裝置識別資訊。
AppLovin 自家的伺服器發出的跨應用程式識別符能乾淨地遵守 ATT。功勞歸於他們。這是真實的,值得大聲說出來。
api_did 是一個欄位。IDFA 是另一個。加密信封包含大約 48 個額外的 device_info 欄位,加上 12 個迷你信封,每個信封都攜帶其自身的指紋副本。ATT 將一個識別符歸零。但它沒有觸及:
我從九個欄位構建了一個 SHA-256 指紋 — revision + os + tm + ndx + ndy + kb + font + locale + tz_offset — 涵蓋了我解密語料庫中的十部不同的實體 iPhone。結果:十部不同裝置的十個不同指紋。100% 的唯一性,包括多部相同硬體型號的裝置。
對於一個 ATT 被拒絕的使用者,其裝置出現在三個不同的應用程式(來自三個不同的發佈者)中,指紋雜湊在所有三個應用程式中都是相同的:321d60c4d72ddf2a。不同的套件 ID。不同的 IDFV。不同的 SDK 版本。
ATT 是對 Apple 發出的跨應用程式識別符 (IDFA) 的一種控制。它在傳輸層得到遵守,IDFA 在使用者拒絕時確實被歸零。AppLovin 自家的伺服器發出的識別符 (api_did) 也得到遵守,BADDID 標記會向每個被拒絕的使用者回傳相同的值。這兩種控制都作用於識別符層面。
AppLovin 和十二家下游廣告聯播網選擇互相傳送的協議,並不作用於識別符層面。它作用於裝置指紋層面,iOS 並未對此設限,Apple 也無法進行機械控制,ATT 也觸及不到。每個加密中介請求都會將 50 個裝置欄位傳送給 AppLovin,然後將 18 個不同的競價信號傳送給 18 家不同的廣告聯播網,再透過伺服器對伺服器的擴散傳送給這些競價者各自的下游 DSP。這些參與者中的每一個都有自己的裝置指紋、自己的識別符、以及跨發佈者和會話重新連結同一支實體 iPhone 的能力。