找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4915

积分

0

好友

633

主题
发表于 昨天 23:29 | 查看: 4| 回复: 0

前言

补环境的难度,还得看国外的验证码盾。hCaptcha 的环境检测点少说也有千八百个,想完整补出来真得沉下心。我这次光补环境代码就写了三千多行。

另外它还设计了大量异步调用,很容易把人绕晕。

本文主要记录逆向分析的完整过程,以及补环境过程中遇到的关键检测点。

什么是 hCaptcha

hCaptcha 是一种现代、隐私优先的人机验证(CAPTCHA)服务,用来帮网站区分真实用户和自动化机器人,比如爬虫、垃圾发送器或恶意脚本,从而阻止滥用、欺诈和数据抓取。自 2018 年推出以来,hCaptcha 已经成了很多企业、政府机构以及开发者的验证方案选择,尤其是它比较重视用户隐私。

官网: https://www.hcaptcha.com

开发者文档: https://docs.hcaptcha.com

目标网站

这次分析的 demo 网站是 Steam 账号注册页面:

https://store.steampowered.com/join

如果访问不了,需要先处理网络环境。

抓包分析

打开注册页面后,正常情况下会看到验证框。如果本地浏览器环境干净,勾选后可以直接无感通过。

Steam 账号注册页面 hCaptcha 验证区域截图

如果环境异常,则会弹出图片验证码。验证码类型很多,拖拽、点击都有。

hCaptcha 无感验证框状态截图

下面这张截图是未通过验证时弹出来的图片选择任务。

hCaptcha 图片选择验证码示例

接下来打开控制台抓包分析。

第一个关键请求是 hcaptcha.html。地址里 v1 后面跟着的是版本号,大概一周更新一次。

hcaptcha.html 网络请求详情

第二个是 checksiteconfig。注意 sitekey 每个网站唯一。

checksiteconfig 接口请求 Headers

响应内容里有一段 JWT 格式的数据。

checksiteconfig 响应 JSON 数据

把它解码之后,可以看到 hsw 类型的字段,后面跟着一串加密字符串。

hsw JWT 解码结果

接着加载的是 hsw.js,这是核心加密文件。

hsw.js 核心加密文件预览

再往后就是最后的验证接口 getcaptcha。

getcaptcha 接口请求详情

可以看到请求体是一堆乱码,实际上就是二进制 buffer。

getcaptcha 请求二进制载荷

响应体同样是乱码,也是 buffer 格式。

getcaptcha 响应二进制数据

其实可以手动改它的代码,让接口改用标准 JSON 格式。在 hcaptcha.html 里找到下图中这个位置,把 accept 和 content-type 相关代码处理一下。

修改请求头代码注释位置

还有一处也要改。在 html 头部位置,这是内容安全策略(CSP),用来防止篡改。这里只保留 worker 相关属性就行。

CSP 内容安全策略修改截图

改完刷新页面重新验证,显示的内容就正常了。

表单数据结构与 motionData

表单字段整理如下:

  • v:hCaptcha 版本号
  • sitekey:网站唯一 key
  • host:网站域名
  • hl:语言
  • pdc、pem:包含时间相关参数
  • c:checksiteconfig 接口中的参数
  • motionData:包含时间戳、轨迹等环境信息
  • n:hsw.js 文件核心加密结果

验证接口的响应里,generated_pass_UUID 的值如果以 P1 开头,就表示验证通过。

验证通过响应 generated_pass_UUID

逆向分析

这一节主要分析怎么找到 n 值的加密入口位置。

先打上全局 XHR 断点,然后勾选验证码进行验证。断点会停在 hcaptcha.html 文件里,这时要发送的参数就是 ArrayBuffer 格式。

我这里没有提前改文件,如果改完就会显示明文。

XHR 断点定位加密发送位置

往上翻代码,发现当前正处在一个 Promise 链里。那我们在这个 Promise 之前再打个断点看看。

Promise 链式调用调试

这时 t.data 已经有值了,继续往上跟调用栈。

栈回溯定位 data 来源

跟了几个栈之后,找到了 data 的新位置 l。l 是前面 Promise 链式调用传过来的,继续跟 Promise。

请求对象参数与明文数据

l 是由 s 赋值过来的,s 就是明文的参数对象。

参数构造过程

这里可以看到所有参数都添加进了 s 里面。我们主要目标还是找 n 的来源。

getTaskData 函数与 proof

从代码关系看,n = r.solved,而 r = e.proof。再继续往上跟栈找 e。

proof 数据与 solved 字段

可以看到这又是 Promise 异步传过来的。proof 在代码里有多处,不确定的话就全部打上断点,看实际进哪个。

不过实际入口还在更上层,还要继续找 t。

调用栈定位 e 来源

下面这张图继续展示异步链路里的关键位置。

异步 Promise 调试

这里可以看到调用了 Promise.all 方法,迭代里有一个 xr 方法,它也是异步的。

Promise.all 与 xr 方法

可以直接在控制台调用 xr 看结果,它执行完后会输出我们想要的数据。

控制台输出 xr 结果

接着进入 xr 内部继续分析。xr 里面只有下图位置中的 solved 有值,打上断点跟过去。

n 值生成位置

到这里 n 值已经有了,是上面异步 return 出来的。

n(i.req,o) 调用入口

执行 n(i.req, o) 方法后,返回了我们需要的最终结果。传入了两个参数,分别是 JWT 解码后的请求数据和配置对象。

继续进入 n(i.req, o) 方法,这回终于进了 hsw.js 文件内部。

hsw.js 加密函数

首先它会将传进来的 JWT 数据进行解码。

JWT 解码与加密确认

最后又 return 了一个异步。aG 方法实际是加载了一个 WebAssembly 模块。

WebAssembly 加载

手动调用后也能正常返回结果。

wasm 手动调用返回结果

到此,加密入口的流程就分析完了。接下来就可以把 hsw.js 文件拿下来,开始补环境。

插桩分析点

因为用到了 WebAssembly,JS 会一直和 wasm 交互存取参数。所以我们需要找到交互过程中到底检测了哪些环境。

回到加密入口位置。

kc.Ob 加密方法定位

往下走,进入 kc.Ob 方法里。

Ob 函数与 wasm 方法

fm 里面都是 wasm 的方法。

fm 方法列表

先进入 Mv 方法里。

Mv 方法与环境数组

这里面操作了一个大数组。补环境的时候也要在这里对照一下,看自己补的和浏览器上是否一致。可以在相关位置打上日志。

日志插桩位置

日志跑起来之后,能看到很多环境调用的索引和参数。

环境检测日志输出

末尾就是日志的出值,可以拿自己的补环境结果跟这里一一对照。

浏览器环境校验日志

在补环境过程中,经常能见到一种方法调用:第一个参数是一串数字。进入方法最后的位置。

wasm 交互数字方法

可以看到,所有检测出来的环境信息都被存进了一个大数组。所以这也是一个很重要的校验点,补出来的内容最好和浏览器这边完全对齐。

环境检测大数组

补环境

下面说一些我遇到的环境检测点。

1. RTCPeerConnection

WebRTC 的核心接口,用于在浏览器之间建立点对点连接,实现实时音视频或数据传输。

这个在之前分析腾讯滑块时也见过,所以还算熟悉。

RTCPeerConnection 补环境代码

2. RTCRtpSender、RTCRtpReceiver

这两个接口用来处理音视频流的发送和接收。

RTCRtpSender/Receiver 补环境

3. OfflineAudioContext

需要模拟音频数据的处理过程。

OfflineAudioContext 模拟

4. WebGL2RenderingContext

这块参数非常多,建议直接从浏览器里复制下来。

WebGL2RenderingContext 模拟

5. 一堆描述符检测

这里要注意,创建函数时要用箭头函数,因为箭头函数没有 prototype 属性。

检测代码如下:

"prototype" in AudioBuffer.prototype.getChannelData

在浏览器中执行的结果是 false。

浏览器描述符检测结果

在本地 Node.js 里测试,如果写法不同,结果就会不一样。

Node.js 描述符检测对比

6. Math 精度差异

不同 JS 引擎的底层数学库有差异,会导致计算结果不完全一致。

浏览器中的结果:

浏览器 Math 精度测试

Node.js 中的结果:

Node.js Math 精度测试

7. 字体指纹

写个方法直接从浏览器里导出即可,主要涉及 measureText 的返回值模拟。

字体指纹 measureText 模拟

8. Canvas 指纹

同样可以通过导出的方式补齐,重点在于 toDataURL 的返回处理。

Canvas 指纹 toDataURL 模拟

9. Worker、SharedWorker

这两个也需要模拟,尤其是 postMessage 和消息事件的处理。

Worker/SharedWorker 模拟

结果验证

环境补好之后运行脚本,可以看到成功拿到了验证 key,返回的 pass 也是 True。

补环境成功验证结果

如果环境校验不过,服务端会返回验证码图片链接,要求继续做图片验证任务。

环境未通过返回验证码任务

到这里,hCaptcha 的逆向和补环境流程就跑通了。整篇文章涉及大量 JavaScript 调试和 WebAssembly 交互,真正动手的时候建议多对照浏览器日志,耐心对齐每一个检测点。如果你也在做类似验证码逆向,欢迎到云栈社区一起交流。




上一篇:深度学习必修课:进击算法工程师 从基础到Transformer与生成模型全面解析
下一篇:Chrome for macOS 扩展加载缓慢:谷歌已提升优先级调查点击卡顿问题
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-30 06:00 , Processed in 0.806850 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表