news 2026/6/3 16:47:13

指纹浏览器中的BatteryStatusAPI指纹与电量状态模拟技术

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
指纹浏览器中的BatteryStatusAPI指纹与电量状态模拟技术

一、BatteryStatusAPI的起源、设计初衷与隐私争议

BatteryStatusAPI是W3C设备API工作组早期提出的一项浏览器接口规范,于2012年左右进入草案阶段。其设计初衷是帮助网页开发者根据设备的电池状态优化应用行为。例如,当检测到设备电量低于15%且未充电时,网页可以自动降低视频播放码率、减少动画帧率、暂停后台同步任务,或者弹出提示引导用户连接充电器。这种根据电量自适应调整的能力,对于移动端Web应用和渐进式Web应用具有实际价值。

该 API 的具体使用方式是通过navigator.getBattery方法返回一个Promise对象,解析后获得BatteryManager实例。BatteryManager包含以下只读属性:

  • charging:布尔值,表示设备是否正在连接电源充电。

  • chargingTime:数字,表示预计充满电所需的剩余秒数。如果已经充满或未充电,此值为0。

  • dischargingTime:数字,表示当前电量下预计还能使用的剩余秒数。如果正在充电,此值为Infinity。

  • level:浮点数,范围0到1,表示剩余电量百分比,例如0.75表示75%。

此外,BatteryManager还定义了四个事件:chargingchangechargingtimechangedischargingtimechangelevelchange,当对应属性发生变化时触发。网页可以注册这些事件的监听器,实时响应电池状态的改变。

然而,该API上线不久就被隐私研究人员发现存在严重的指纹泄露风险。2015年,西班牙马德里卡洛斯三世大学的研究团队发表论文,详细阐述了BatteryStatusAPI如何被用于追踪用户。研究者发现,电池电量和充电状态的组合具有足够的熵值来区分设备——即使在同型号设备上,由于电池老化程度、充电习惯、使用模式的差异,电量和剩余时间的组合也呈现出随机性。实验数据显示,仅使用leveldischargingTime两个参数的组合,就可以在约1400万用户中唯一识别出超过99%的设备。

这一发现引发了浏览器厂商的迅速反应。Mozilla在Firefox52版本中限制了对chargingTimedischargingTime的访问,将其四舍五入到最接近的分钟级别。Google在Chrome88版本中彻底限制了该API,不再提供精确的chargingTimedischargingTimelevel也被限制为只有0、0.5、1 三个离散值。Apple在Safari 中没有实现完整的BatteryStatusAPI,且在iOS上完全禁用了该功能。Edge跟随Chromium的决策。

尽管主流浏览器已经大幅限制或弃用了该 API,但在某些特定环境下——例如基于旧版Chromium内核的定制浏览器、企业内部分发的Web应用、或者用户手动启用了实验性标志的浏览器中——该API仍然可以被用于指纹采集。此外,一些风控系统可能会检查浏览器是否实现了getBattery方法,如果一个浏览器声称是最新版本却缺少这个方法,或者方法签名异常,也可能被视为可疑信号。因此,对于追求完整环境指纹模拟的指纹浏览器而言,正确处理BatteryStatusAPI仍然是一个不可忽视的细节。像中屹指纹浏览器这类产品,在环境配置中包含了电池状态的模拟选项,允许用户为每个环境独立设置电量行为。

二、BatteryStatusAPI的底层数据获取路径与Chromium实现剖析

要有效地伪造电池状态,首先需要理解Chromium中该API 的完整数据流。以下以Windows平台为例,详细说明从操作系统底层到JavaScript层的传输过程。

在 Windows 系统中,电池信息通过GetSystemPowerStatusAPI 获取。该函数定义在winbase.h中,属于Windows系统核心服务的一部分。GetSystemPowerStatus接收一个SYSTEM_POWER_STATUS结构体指针,填充以下字段:

  • ACLineStatus:0 表示离线(电池供电),1 表示在线(交流电源),255 表示未知。

  • BatteryFlag:电池状态标志,例如 1-高电量,2-低电量,4-临界电量,8-充电中,128-无电池等。

  • BatteryLifePercent:剩余电量百分比,0-100,255 表示未知。

  • BatteryLifeTime:剩余使用秒数,-1 表示未知,-2 表示正在充电。

  • BatteryFullLifeTime:充满电所需秒数,-1 表示未知,-2 表示未充电。

Chromium 的base模块中有一个PowerMonitor类,专门负责监听系统电源状态变化。在 Windows 上,PowerMonitor通过注册WM_POWERBROADCAST窗口消息来接收电池事件,同时定期调用GetSystemPowerStatus轮询状态。当检测到变化时,PowerMonitor会通知上层的BatteryMonitor类。

BatteryMonitor位于content/browser/battery/battery_monitor_impl.cc。它是浏览器进程中的一个服务,负责响应渲染进程中网页发起的getBattery请求。当渲染进程调用getBattery()时,实际发生的是一个跨进程的 Mojo 调用。渲染进程向浏览器进程发送一个BatteryMonitor.GetBatteryStatus请求,浏览器进程中的BatteryMonitorImpl::QueryNextStatus方法被触发。该方法通过PowerMonitor获取当前电池状态,然后构造一个BatteryStatus结构体(包含chargingchargingTimedischargingTimelevel四个字段),通过 Mojo 管道返回给渲染进程。

渲染进程收到响应后,BatteryManager对象更新其内部属性,并触发对应的change事件。整个过程是异步的,因此getBattery返回的是一个 Promise。

从以上数据流可以看出,要在 Chromium 层面伪造电池状态,最直接的方法是修改BatteryMonitorImpl::QueryNextStatus方法,让它在返回数据时不再调用PowerMonitor,而是读取一个预设的配置文件中的值。这种修改需要重新编译 Chromium,但优点是修改位置非常集中,且对上层完全透明——从渲染进程的 JavaScript 代码来看,所有 API 行为与原生实现完全一致,没有任何篡改痕迹。

另一种更轻量级的方案是在 JavaScript 层面做注入。指纹浏览器在每次创建新环境时,向页面注入一段脚本,这段脚本会检测navigator.getBattery的存在,然后用一个自定义函数替换它。自定义函数返回一个伪造的 Promise,这个 Promise 解析出的BatteryManager对象的所有属性和事件也都是伪造的。这种方案的优点是无需重新编译浏览器,可以快速迭代,但缺点是有可能被风控脚本检测到,因为风控脚本可以检查getBattery.toString()是否返回原生代码字符串,或者通过其他方式验证函数的完整性。

三、动态电量状态模拟的有限状态机设计与实现

伪造电池状态不能简单地使用固定值。一个永远显示 100% 电量且永远在充电中的浏览器环境,在逻辑上是不合理的——真实用户不可能永远不消耗电量,也不可能永远连接电源。同时,如果电池状态在不同的会话之间总是回到相同的初始值,也会显得不自然。因此,需要设计一个有限状态机来模拟电量的自然变化。

状态机的核心设计要素包括:

状态定义

  • State枚举包含两种主要状态:CHARGING(充电中)和DISCHARGING(放电中)。

  • 充电状态又可细分为CHARGING_LOW(低电量充电)、CHARGING_NORMALCHARGING_NEAR_FULL等,但简单的状态机不需要如此复杂。

状态变量

  • level:当前电量,浮点数范围 0.0 到 1.0。

  • chargeRate:充电速率,单位 %/秒,典型值 0.001 到 0.005(即每 100 秒充 0.1% 到 0.5%)。

  • dischargeRate:放电速率,同样范围。

  • lastUpdateTimestamp:上次更新时间戳,用于计算经过的时间。

状态转移逻辑

  • state = CHARGING时,level随时间增加,直到达到 1.0 后自动切换到FULL子状态(充电停止,或转为涓流充电)。

  • state = DISCHARGING时,level随时间减少,直到达到 0.0。通常真实设备不会达到 0.0 就会自动关机,因此在模拟时可以在level低于 0.05 时随机决定是否切换到充电状态或保持放电直到关机。

  • 状态切换也可以由外部事件触发。例如,可以模拟用户插拔电源线的行为,在随机的时间间隔内(如每 30-120 分钟)以一定概率切换charging状态。

剩余时间的计算

  • chargingTime:当state = CHARGING时,(1.0 - level) / chargeRate;当level >= 1.0时,0;否则Infinity

  • dischargingTime:当state = DISCHARGING时,level / dischargeRate;当state = CHARGING时,Infinity

为了实现跨会话的状态持久化,每个指纹环境需要在本地保存当前的电池状态。建议的存储格式是一个 JSON 对象,包含以下字段:levelchargingchargeRatedischargeRatelastUpdateTimestamp。每次环境启动时,读取存储的状态,根据当前时间与lastUpdateTimestamp的差值,演化状态机得到最新的levelcharging值,然后再将新的状态保存回存储。这样模拟出来的电池状态具有连续性和历史记忆,不会出现电量回跳或无故归零的异常。

此外,为了避免所有环境在同一时间点状态过于相似,可以为每个环境随机生成初始参数。初始level建议在 0.2 到 0.95 之间均匀随机,charging以 70% 的概率为true(多数用户在使用设备时会连接电源),chargeRatedischargeRate可以在 0.001 到 0.008 之间随机选取。这些参数应当作为环境指纹的一部分,随环境配置一起导出和导入,以便在迁移到另一台机器后保持状态连续性。

四、多环境下的电池状态隔离与跨维度一致性约束

当在一台物理电脑上同时运行多个指纹浏览器环境时,每个环境的电池状态必须完全独立。如果所有环境都同时充电或同时放电,或者所有环境的电量变化步调一致,平台可以通过对多个账号的交叉比对发现异常。

实现独立性的关键是每个环境使用独立的持久化存储。Chromium 的用户数据目录(Profile 目录)天然提供了这种隔离——每个 Profile 有自己的 LocalStorage、IndexedDB 和文件系统。电池状态数据可以存储在每个 Profile 的偏好设置文件中,或者单独存储在一个 JSON 文件中。指纹浏览器需要确保在环境启动时,读取的是该环境专属的存储路径,而不是全局共享的存储。

跨维度一致性是另一个需要关注的方面。电池状态应当与指纹中设置的设备类型(navigator.platformuserAgent中的设备标识)保持松散的一致性:

  • 如果环境模拟的是台式机(Windows 或 macOS 的桌面版本),台式机通常长期连接电源,因此charging应该为truelevel接近 1.0,dischargingTimeInfinity。如果模拟的是笔记本或移动设备,则可以同时支持充电和放电两种状态。

  • 如果环境模拟的是手机(User Agent 中包含Mobile关键字),电池状态应该更加动态,电量变化速率也可以稍快一些,因为移动设备的功耗通常高于台式机。

  • 对于模拟服务器或云端环境的场景,电池状态甚至可以完全省略(即getBattery方法返回null或抛出异常),但这种情况不太常见,因为普通用户不会在服务器上运行浏览器。

此外,电池状态还可以与 Network Information API 产生间接关联。当设备电量低且未充电时,某些操作系统会主动限制网络活动,例如降低 WiFi 扫描频率或切换到更省电的移动网络模式。虽然这种关联非常弱,但风控系统不太可能去验证这种深层次的一致性,因此不需要过于严格地模拟。

五、Battery Status API 指纹的检测技术与反反指纹对抗

风控系统如果仍然依赖 Battery Status API,通常采用以下几种检测策略:

单次快照:页面加载时立即调用getBattery()一次,记录levelchargingdischargingTime的组合。这种方法简单高效,但获取的信息有限。

多帧采样:在几秒内多次调用getBattery(),观察返回值的变化。真实设备的电池状态在短时间(<5秒)内几乎不会变化;而简单的随机伪装可能会每次返回不同的值,从而暴露。因此,伪造的电池状态在单次页面会话中必须保持稳定。状态机虽然会随时间演化,但每秒的变化量极低(0.001% 级别),在几秒的采样窗口内可以视为恒定。

事件监听测试:脚本注册levelchangechargingchange监听器,然后通过某种方式尝试触发变化。如果监听器从不触发,或者触发模式不符合预期,也可能被检测。不过这种测试较为罕见,因为触发条件不容易控制。

跨 API 一致性校验:将电池状态与其他 API 返回的信息做交叉验证。例如,navigator.getBattery返回的chargingTime与系统的电源设置是否匹配?这种验证在 Web 端几乎不可能实现,因为网站无法访问操作系统的电源配置。但平台可以通过服务端的统计模型来发现异常:如果大量来自不同 IP 的设备都报告完全相同的电池状态,或者报告的状态与设备型号的典型状态不符,就会触发警报。

为了对抗这些检测,指纹浏览器在模拟电池状态时需要注意以下几点:

  • 每次新页面加载时的初始状态不应该完全随机,而应该基于存储的历史状态演化而来,保证连续性。

  • 不同环境的电池状态应该具有多样性,避免出现大量环境共享相同或相近的状态值。

  • 如果风控系统已知某个特定地区的用户普遍使用笔记本并频繁充电,可以在该地区的环境配置中适当提高charging的概率。

六、主流浏览器的现状与未来趋势

截至 2025 年,Battery Status API 在主流浏览器中的状态如下:

  • Chrome/Edge:默认情况下,getBattery仍然可用,但chargingTimedischargingTime返回固定值0Infinitylevel被四舍五入到最接近的离散值(0、0.5、1)。实际可用的信息只有charging和粗粒度的level

  • Firefox:仍然支持,但同样限制了精度。

  • Safari:从未完整实现,在 iOS 和 macOS 上仅返回基本的安全信息。

由于该 API 的指纹价值已经大大降低,许多风控系统已经不再依赖它。但对于使用旧版 Chromium 内核的定制浏览器,或者需要模拟特定旧环境的场景,正确的电池状态模拟仍然有其意义。指纹浏览器可以将此功能作为一个可选的高级选项,让用户决定是否需要启用。

七、总结

Battery Status API 代表了浏览器指纹技术中一个正在消失但仍然有趣的维度。它的核心价值不在于单独识别设备,而在于作为辅助信号与其他指纹组合,以及作为跨维度一致性的验证点。一个完善的指纹伪装方案,需要对所有可能被采集的 API 进行全面的分析和处理,即使是那些已经或即将被废弃的 API。因为风控系统的逻辑往往是多层次、多信号的,忽略任何一个看似无关紧要的细节,都可能累积成最终被识别的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 16:39:53

3步方案:零门槛掌握抖音内容批量下载的智能工具

3步方案&#xff1a;零门槛掌握抖音内容批量下载的智能工具 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖…

作者头像 李华
网站建设 2026/6/3 16:39:10

智慧职教自动刷课脚本:3步实现全平台自动化学习终极指南

智慧职教自动刷课脚本&#xff1a;3步实现全平台自动化学习终极指南 【免费下载链接】auto-play-course 简单好用的刷课脚本[支持平台:职教云,智慧职教,资源库] 项目地址: https://gitcode.com/gh_mirrors/hc/auto-play-course 在职业教育在线学习日益普及的今天&#x…

作者头像 李华