網站如何分辨真實手機與租用的 Android 實例:GPU 渲染字串、感測器缺失、硬體高度雷同,且網頁端無認證機制可用。
「雲端手機」是運行在遠端伺服器上的真實(或虛擬化)Android 實例,透過串流畫面或遠端 API 操控,而不是握在你手中的實體裝置。它們可以按分鐘計費租用、一次開通成千上萬台,並隨時重置回乾淨狀態,已成為偽造桌面瀏覽器指紋之外更新一代的替代手法——請求看起來確實來自一支真正的手機,因為就技術層面而言,其中某一層的確是。要偵測它們,重點不在於抓住 User-Agent 字串裡的謊言,而在於檢視行動瀏覽器回報的每一項資訊,是否都與一支真實的實體手機內部一致。本文將梳理能回答這個問題的各項訊號。
核心要點
- 雲端與模擬(emulated)的 Android 實例通常無法產生真實的 GPU。實體手機會透過
WEBGL_debug_renderer_info回報Adreno、Mali或PowerVR;虛擬化實例則會回報SwiftShader、llvmpipe或virtio-gpu這類軟體或直通(passthrough)渲染器。 - 真實手機即使靜置在桌上,也會暴露帶有裝置專屬校準雜訊的加速度計與陀螺儀讀數;雲端實例在這些感測器 API 背後並沒有實體晶片,因此要麼完全不回報任何值,要麼回報一個可疑地維持恆定的數值。
- 單一租用實例看起來只是一位行動訪客。但成千上萬個「不同使用者」回報著完全相同的
hardwareConcurrency、deviceMemory與螢幕解析度,這是農場規模的關聯性,而非巧合。 - 原生 Android 應用程式可以要求硬體背書的 Play Integrity 驗證結果,雲端實例很難產生這樣的結果;開放網頁則沒有對應的機制,這正是為什麼瀏覽器端偵測,必須回退到本文所述的這套一致性檢查。
- 你可以透過 BrowserInsight 的指紋檢查,查看自己裝置的 GPU 渲染器、感測器暴露情形與硬體並行數(hardware-concurrency)數值。
從桌面設定檔到租用的 Android 實例
指紋偽造工具多年來一直聚焦在桌面瀏覽器上:替換 User-Agent、修補 Canvas 雜湊、注入看似合理的 GPU 字串,然後祈禱這些拼湊出來的碎片能兜得起來。偵測技術也跟上了腳步——反偵測瀏覽器偵測如今已能例行性地抓出這種拼湊而成的桌面設定檔,方法就是檢查其各項訊號彼此之間是否連貫一致。雲端手機則以不同的方式繞開這場攻防:它不是從一台桌機上偽造行動裝置指紋,而是在資料中心某處運行一套真實(或虛擬化)的 Android 系統堆疊,並把該工作階段真實、未經偽造的瀏覽器當作「裝置」呈現出來。User-Agent 並沒有說謊。真正缺少的,是一部實體手機所具備、而伺服器機架不具備的一切:GPU 晶片、動作感測硬體,以及每位使用者各自擁有一支手機所帶來的自然硬體多樣性。
這正是本文要回答的長青問題——不是哪家業者出租這些實例、也不是如何讓農場看起來更逼真,而是網站如何從瀏覽器工作階段內部,判斷請求背後的行動裝置究竟是實體手機,還是一個虛擬化的替身。
訊號一:GPU 渲染器字串
每一顆行動裝置 GPU 都是一枚有名字的實體晶片,這個名字會出現在 WebGL 的 WEBGL_debug_renderer_info 擴充功能中,與桌面端的情形相同——關於渲染器字串與底層硬體之間綁定得有多緊密,可參見不可能的指紋組合。一支真實的 Android 手機,會回報少數幾個行動 GPU 家族之一的供應商字串:
const gl = document.createElement('canvas').getContext('webgl');
const dbg = gl.getExtension('WEBGL_debug_renderer_info');
gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL);
// 真實手機:"Adreno (TM) 740"、"Mali-G715-Immortalis" 或 "PowerVR Rogue GE8320"
// 雲端實例:"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)"
// 模擬 Android:"llvmpipe (LLVM 15.0.7, 256 bits)",或某個 virtio-gpu 直通字串
SwiftShader、llvmpipe 與 virtio-gpu 全都是軟體或虛擬化層級的渲染器——它們之所以存在,正是因為運行瀏覽器的機器背後沒有專屬的行動 GPU 可以交棒。伺服器託管的 Android 實例,要麼退而回報上述其中一種,要麼直接轉發屬於桌面級 NVIDIA 或 AMD 顯卡的宿主 GPU 字串,而這本身就是破綻:世界上沒有任何一支手機搭載 NVIDIA GeForce 晶片。無論哪一種情況,渲染器字串所指名的硬體,都是一支真實手機不可能擁有的。
不過,光憑這一項還不足以蓋棺論定:並不是每一個託管的 Android 實例都跑在 x86 伺服器上。有些業者用 ARM 伺服器主機板來建置機隊,有些則乾脆把成排的實體手機架設在機房裡再遠端開放出來——這類實例完全可能回報一個貨真價實的 Adreno 或 Mali 字串,因為背後確實有一顆行動 GPU 在做渲染。這個推論只能單向成立:軟體或虛擬化渲染器是虛擬化環境的有力證據,但一個看起來合理的行動渲染器,並不能反過來證明那是一支握在某個人手中的實體手機。正因為這種不對稱,當 GPU 字串看起來乾淨時,真正吃重的就是接下來這幾項訊號。
訊號二:不存在的動作感測器
真實手機都配備加速度計與陀螺儀,而且——如裝置感測器指紋識別一文所述——即使手機靜止不動,這些晶片的讀數中依然會留下穩定、且因裝置而異的校準特徵。雲端或模擬實例的硬體鏈中,任何地方都沒有這樣的晶片。在這類實例上讀取 DeviceMotionEvent API,或利用 Sensor API 系列建構一個 Accelerometer 物件,通常會出現以下兩種破綻之一。要麼讀數根本沒有真正送達——devicemotion 監聽器始終不觸發,或者觸發了但加速度各欄位全是 null,又或者建構 Accelerometer 實例時直接報錯,因為平台根本不認為存在這樣的感測器;要麼模擬層塞進了合成數值,於是讀數乾淨到可疑:一個恆定不變的值,完全沒有真實晶片因製造公差而在各軸上產生的細微漂移。實體手機的背景雜訊從來不會是一條完全平坦的線;而指令碼寫死的預設值往往就是。
訊號三:農場規模的硬體同質化
單獨檢視任何一個雲端手機工作階段,看起來都可能相當合理——真正洩露裝置農場行蹤的,是大量工作階段之間呈現出的模式。真實的行動訪客群體,天生就帶有硬體多樣性:不同晶片組回報不同的 hardwareConcurrency 核心數、不同的 deviceMemory 分級,以及橫跨各世代手機、散布不一的螢幕解析度。裝置農場則是從同樣寥寥幾個虛擬機器映像檔中,批量佈建出大量實例,於是數百甚至數千個聲稱來自不同使用者的工作階段,會同時收斂到完全相同的核心數、記憶體分級、螢幕尺寸與 GPU 渲染器字串上。單一工作階段本身並非不可能——偵測系統評分的,是工作階段之間的關聯性,這與行為機器人偵測套用在互動模式而非靜態裝置屬性上的,是同一套聚合層級的推理邏輯。
訊號四:觸控、指標與可視區域的自我一致性
真實手機的觸控與可視區域訊號,天生就會彼此吻合:navigator.maxTouchPoints 回報非零值、CSS 的 (pointer: coarse) 媒體特性相符,且可視區域尺寸落在某款實際出貨螢幕、搭配合理裝置像素比的範圍之內。而透過串流遠端畫面、或以遠端控制 API 暴露瀏覽器的雲端手機基礎架構,在上述任何一項上都可能出現偏差:由滑鼠驅動的控制層餵入合成的觸控事件、可視區域被調整成符合串流視窗尺寸而非真實螢幕的物理尺寸,或是裝置像素比根本不對應任何一款實際販售的手機。單獨來看,這些都算不上鐵證——真實手機上調整過大小的瀏覽器視窗,看起來也可能有點不尋常——但當它們與不相符的 GPU 字串、缺失的感測器疊加在一起時,就會拼湊出同一幅圖景。
為何網頁端沒有認證機制可回退
原生 Android 應用程式握有一項強得多的工具:裝置認證(device attestation)。應用程式可以呼叫 Google 的 Play Integrity API,取得一個由信任執行環境(trusted execution environment)簽署、硬體背書的判定結果,直接斷言裝置與應用程式是真實且未經竄改的——而不是從一堆可被偽造的訊號中推論出來。Google 在針對 Play Integrity 威脅偵測的更新中說明,該 API 的 deviceIntegrity 判定是用來告訴應用程式:它目前是否運行在一台正版、通過 Play Protect 認證的 Android 裝置上——而租用的雲端實例通常過不了這一關,因為它背後沒有真實裝置的硬體背書金鑰(也就無法產生有效的認證權杖,attestation token)。
開放網頁則完全沒有對應的機制。Google 曾提議的 Web Environment Integrity API——也就是 Play Integrity 在瀏覽器端的對應物——在正式推出前就已撤回,因此目前沒有任何瀏覽器會向網站暴露認證權杖。這正是為什麼雲端手機的瀏覽器端偵測,必須回退到前述的一致性檢查:在沒有密碼學證明可供檢驗的情況下,網站只能藉由判斷 GPU、感測器、硬體規格與觸控訊號,是否像一支真正手機那樣彼此吻合,來推論「這究竟是實體手機,還是虛擬化的替身」。
| 訊號 | 實體手機 | 雲端/模擬實例 |
|---|---|---|
| GPU 渲染器字串 | Adreno、Mali、PowerVR 等供應商字串 | SwiftShader、llvmpipe、virtio-gpu,或桌面級 NVIDIA/AMD 字串 |
| 動作感測器 | 即時讀數,帶有依裝置而異的校準雜訊 | 缺席、拋出例外,或可疑地恆定不變的合成數值 |
| 跨工作階段的硬體規格 | 核心數、記憶體分級、螢幕尺寸天生多樣 | 大量工作階段收斂到完全相同的數值 |
| 觸控/可視區域 | 非零觸控點、粗略型指標,符合真實裝置的可視區域 | 指標類型不符、非標準可視區域或像素比 |
| 認證機制(僅限原生應用程式) | 有效的 Play Integrity/App Attest 權杖 | 驗證失敗,或無法產生權杖 |
與相關訊號的關係定位
本文專門討論如何分辨虛擬化的 Android 工作階段與真實工作階段——這與幾個鄰近主題有所重疊,但並不完全相同。行動裝置瀏覽器指紋識別涵蓋的是任何手機都會暴露的更廣泛訊號集合,無論真假。不可能的指紋涵蓋的是桌面級 GPU/作業系統/字型之間的矛盾,是一項相關但不同的檢查。裝置認證涵蓋的是網頁所缺乏的、原生應用程式層級的密碼學證明。而反偵測瀏覽器偵測談的則是桌面指紋偽造,而非租用的行動基礎架構。雲端手機偵測正好座落在這些主題的交集:檢查行動訊號在內部與跨工作階段之間是否連貫一致,且在完全沒有認證機制可回退的情況下進行。
檢查你自己的訊號
BrowserInsight 的指紋檢查會回報你裝置實際的 GPU 渲染器字串、動作感測器暴露情形、hardwareConcurrency 與 deviceMemory——正是本文一路討論的這些屬性。分別在一支真實手機、以及一個遠端或虛擬化 Android 工作階段內的瀏覽器中執行它,兩者之間的落差會立刻一目瞭然。
常見問題
雲端手機和行動裝置模擬器是同一回事嗎?
並不完全是,儘管網站往往也無法單從瀏覽器端把兩者區分開來。雲端手機通常是把運行在遠端伺服器硬體上的真實(或虛擬化)Android 系統,透過串流或代理方式呈現出來;行動裝置模擬器則完全運行在一台桌機上,單純模擬 Android,本身沒有任何一部分是實體行動裝置。兩者通常都缺乏真實的行動 GPU 與動作感測器,這也是為什麼它們會觸發本文所述的相同偵測訊號。
雲端手機服務商能偽造出看似真實的 GPU 渲染器字串嗎?
它可以回報出一個字串,但 WebGL 渲染測試實際輸出的像素,終究來自底層真正在運算的硬體——這正是桌面端 GPU 偽造之所以能被偵測的同一種不對稱性。當聲稱的渲染器字串與實際渲染輸出不符,或者所描述的晶片,其 GPU 家族特性與指紋其餘部分不一致時,這本身就是一個破綻。
所有偵測系統都會檢查動作感測器嗎?
不會——在許多情境下,存取感測器需要先提出請求,或受到權限機制的限制,因此並非每一套偵測堆疊都會在每次造訪時讀取它。而在能夠取得的情況下,這是一項高可信度的訊號,原因正是要令人信服地偽造校準雜訊,遠比偽造 User-Agent 字串這類靜態屬性困難得多。
為什麼網頁端沒有類似 Play Integrity 的機制?
Google 曾提議推出瀏覽器端的對應機制 Web Environment Integrity,但在正式推出前就撤回了,起因是瀏覽器廠商與隱私倡議者反彈,擔憂讓網站得以拒絕未經認證的瀏覽器,會帶來守門人風險。原生應用程式商店會在平台層級強制執行 Play Integrity 與 App Attest,而開放網頁在設計上就不具備這樣的機制。
結論
雲端手機不需要在 User-Agent 上說謊,就能看起來像一位行動訪客——運行一個真正的行動瀏覽器工作階段,本來就是它的整個重點所在。它無法輕易偽造的,是底層的硬體:真實 GPU 晶片的渲染器字串、即時的動作感測器校準雜訊,以及一群各自擁有手機的使用者所帶來的自然多樣性——而不是少數幾份複製出來的虛擬機器映像檔。在沒有網頁原生認證機制可回退的情況下,橫跨 GPU、感測器與硬體規格的這套一致性檢查,就是瀏覽器端偵測手上最強的訊號;而這正是同一套「一切是否彼此吻合」的邏輯,用來揪出偽造的桌面設定檔,只是套用在另一組僅限行動裝置的訊號上而已。
推薦閱讀:


