前言
最近在 GitHub 上发现了一个很有意思的 Go 项目 kirox,它能够自动化注册 AWS Builder ID(Amazon 的开发者账号)。这个项目的技术含量非常高,涉及到很多我们在日常开发中不太接触,但又非常有价值的技术。
今天这篇文章,我想以一个小白的视角,来详细拆解这个项目用到的各种技术。即使你不懂 Go 语言,也能看懂。
项目地址:https://github.com/huey1in/kirox
阅读提示:这篇文章偏原理拆解,尽量用通俗语言解释。但文中涉及 OAuth、浏览器指纹、JWE、IMAP 等概念,如果你第一次接触,建议先顺着往下读一遍,第二遍再回头看代码块。
项目概览
这个项目是做什么的?
简单来说,它可以自动化地注册 AWS 开发者账号。整个过程包括:
- 创建邮箱
- 填写注册信息
- 接收验证码
- 设置密码
- 获取访问令牌
听起来很简单对吧?但难点在于:AWS 有很多反爬虫机制,如何绕过这些检测才是真正的技术挑战。
技术架构图
AI 生成的项目技术架构意象图
技术栈一览
| 模块 |
技术 |
作用 |
| 前端 |
HTML + CSS + JavaScript |
Wails 框架的 UI 层 |
| 后端 |
Go 语言 |
核心业务逻辑 |
| HTTP 客户端 |
tls-client |
模拟浏览器 TLS 指纹 |
| 加密 |
JWE + XXTEA |
密码加密和指纹加密 |
| 邮件 |
IMAP + MoeMail API |
自动获取验证码 |
| 认证 |
OAuth 2.0 |
Device Code + PKCE 流程 |
一、什么是反爬虫?为什么 AWS 要检测?
1.1 反爬虫的基本概念
反爬虫(Anti-Bot)是网站用来识别和阻止自动化程序访问的技术。
想象一下:如果你是一个网站管理员,你不希望有人用脚本批量注册账号、刷票、爬取数据,对吧?所以你需要想办法区分:
- 真人用户:用鼠标点击、用键盘输入、有思考时间
- 机器人:瞬间完成操作、没有鼠标移动、行为模式固定
1.2 AWS 检测了什么?
AWS 的反爬虫系统会检测以下几个维度:
| 检测维度 |
真人特征 |
机器人特征 |
| 浏览器指纹 |
真实的 Canvas/WebGL 渲染结果 |
伪造或缺失的指纹 |
| TLS 指纹 |
Chrome 的 TLS 握手特征 |
Go/curl 的特征 |
| 交互行为 |
有鼠标移动、按键间隔不均匀 |
瞬间完成、间隔固定 |
| 请求频率 |
正常人速度 |
极快的请求速度 |
二、TLS 指纹伪装 – 让 Go 程序”假装”是 Chrome
2.1 什么是 TLS 指纹?
当你用浏览器访问一个网站时,浏览器会和服务器进行 TLS 握手(就是那个小锁🔒的连接过程)。
在这个过程中,浏览器会告诉服务器:
- 我支持哪些加密算法
- 我支持哪些 TLS 版本
- 我有哪些扩展功能
问题是:不同的客户端(Chrome、Firefox、Go、curl)发送的这些信息是不同的!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌─────────────────────────────────────────────────────────────┐ │ TLS 指纹示意 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Chrome 浏览器: │ │ ├── 加密套件: TLS_AES_128_GCM_SHA256, ... │ │ ├── 扩展: GREASE, server_name, ... │ │ └── 特征: JA3 = "abc123..." │ │ │ │ Go 语言 http.Client: │ │ ├── 加密套件: TLS_RSA_WITH_AES_128_CBC_SHA, ... │ │ ├── 扩展: 较少 │ │ └── 特征: JA3 = "def456..." (和 Chrome 不同!) │ │ │ │ AWS 可以通过 JA3 指纹识别:这是真人还是脚本? │ │ │ └─────────────────────────────────────────────────────────────┘
|
2.2 项目如何解决?
这个项目使用了一个叫 bogdanfinn/tls-client 的库,它可以模拟 Chrome 的 TLS 指纹:
1 2 3 4 5 6 7 8 9
| func NewTLSClient(proxy string, followRedirect bool) tls_client.HttpClient { opts := []tls_client.HttpClientOption{ tls_client.WithTimeoutSeconds(60), tls_client.WithClientProfile(profiles.Chrome_144), } client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), opts...) return client }
|
通俗理解:就像一个人要假装是日本人,他需要学习日语的发音方式、说话习惯。这个库让 Go 程序学会了 Chrome 的”说话方式”。
2.3 小白必知
- TLS 指纹 = 浏览器在建立加密连接时暴露的”身份特征”
- JA3 = 一种 TLS 指纹的计算方法
- 解决方法 = 使用专门的库来模拟真实浏览器的 TLS 行为
三、浏览器指纹 – 比你想象的更复杂
3.1 什么是浏览器指纹?
浏览器指纹是网站用来识别你的一系列技术信息,包括:
AI 生成的浏览器指纹与反爬检测示意图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌─────────────────────────────────────────────────────────────┐ │ 浏览器指纹包含什么? │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. Canvas 指纹 │ │ └── 网站在隐藏的画布上画图,不同显卡渲染结果不同 │ │ │ │ 2. WebGL 指纹 │ │ └── 你的 GPU 型号、驱动版本等信息 │ │ │ │ 3. Math 精度指纹 │ │ └── 不同 CPU 计算 Math.tan() 的精度有微小差异 │ │ │ │ 4. 屏幕分辨率 │ │ └── 1920x1080 还是 2560x1440? │ │ │ │ 5. 设备内存/CPU 核心数 │ │ └── navigator.deviceMemory, hardwareConcurrency │ │ │ └─────────────────────────────────────────────────────────────┘
|
3.2 Canvas 指纹详解
这是最有趣的部分!让我详细解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <canvas id="fp" width="150" height="60"></canvas> <script> const ctx = document.getElementById('fp').getContext('2d');
ctx.fillStyle = '#f60'; ctx.fillRect(100, 1, 62, 20);
ctx.font = '11pt Arial'; ctx.fillText('Hello World', 2, 15);
const imageData = ctx.getImageData(0, 0, 150, 60);
const histogram = new Array(256).fill(0); for (let i = 0; i < imageData.data.length; i++) { histogram[imageData.data[i]]++; } </script>
|
为什么这能作为指纹?
因为不同显卡、不同驱动程序渲染同一个 Canvas 的结果是不完全相同的!
所以:Canvas 像素数据 = 你的硬件指纹
3.3 项目如何伪造浏览器指纹?
这个项目的做法非常巧妙:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func generateCanvasData() (int32, [256]int) { var bins [256]int
bins[0] = 5000 + rand.Intn(10001)
bins[255] = 6000 + rand.Intn(10001)
spike1Pos := 100 + rand.Intn(6) bins[spike1Pos] = 500 + rand.Intn(200)
digest := sha256.Sum256(raw) hash := int32(binary.LittleEndian.Uint32(digest[:4]))
return hash, bins }
|
通俗理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 真实浏览器的 Canvas 渲染结果: ┌─────────────────────────────────────────┐ │ bin[0] ████████████████████ 12000 │ ← 透明背景 │ bin[102] ███ 600 │ ← 橙色矩形 │ bin[153] ██ 400 │ ← 混合颜色 │ bin[255] ██████████████████ 10000 │ ← Alpha 通道 │ 其他 █~██ 2~30 │ ← 抗锯齿 └─────────────────────────────────────────┘
项目伪造的结果: ┌─────────────────────────────────────────┐ │ bin[0] ████████████████████ 11500 │ ✓ 符合规律 │ bin[102] ███ 580 │ ✓ 符合规律 │ bin[153] ██ 420 │ ✓ 符合规律 │ bin[255] ██████████████████ 10500 │ ✓ 符合规律 │ 其他 █~██ 3~25 │ ✓ 符合规律 └─────────────────────────────────────────┘
|
关键洞察:项目不是随机生成数据,而是按照真实浏览器的统计规律来生成!
3.4 为什么随机整合不会被发现?
你可能会问:既然是随机组合不同硬件的特征,会不会出现”RTX 4090 + 4GB 内存”这种奇怪组合?
答案是:目前 AWS 没有检测这个!
AWS 更关注的是:
- Canvas 真实性:histogram 分布是否符合渲染规律
- 指纹一致性:UA 版本和 sec-ch-ua 版本是否匹配
- 交互合理性:是否有真实的用户行为
而不是硬件组合是否合理。
四、JWE 加密 – 密码不能明文传输
4.1 为什么要加密密码?
这个是常识了:密码绝对不能明文传输!
如果有人抓包看到你的密码是 MyPassword123,那你的账号就完了。
4.2 什么是 JWE?
JWE(JSON Web Encryption)是一种标准的加密格式,常用于 JWT 的加密版本。
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────────────────┐ │ JWE 的结构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ header.encryptedKey.iv.ciphertext.tag │ │ │ │ │ │ │ │ │ │ │ │ │ └─ 认证标签(防篡改) │ │ │ │ │ └─ 加密后的密码 │ │ │ │ └─ 初始化向量(随机数) │ │ │ └─ 加密后的会话密钥 │ │ └─ 算法信息、密钥 ID │ │ │ └─────────────────────────────────────────────────────────────┘
|
4.3 混合加密原理
JWE 使用了混合加密,这是现代加密的常见模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────┐ │ 混合加密流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 生成随机的 AES 密钥(会话密钥) │ │ └── 比如:a1b2c3d4... │ │ │ │ 2. 用 RSA 公钥加密这个 AES 密钥 │ │ └── RSA 加密(a1b2c3d4...) = X9Y8Z7... │ │ │ │ 3. 用 AES 密钥加密密码 │ │ └── AES 加密("MyPassword") = 密文... │ │ │ │ 4. 把加密后的 AES 密钥 + 密文 一起发送 │ │ │ │ 为什么这样? │ │ ├── RSA 很慢,不能直接加密大量数据 │ │ ├── AES 很快,但需要安全地传递密钥 │ │ └── 所以:RSA 加密 AES 密钥,AES 加密数据 │ │ │ └─────────────────────────────────────────────────────────────┘
|
4.4 项目中的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func (j *JWEEncryptor) Encrypt(password string, publicKey map[string]string) (string, error) { cek := make([]byte, 32) rand.Read(cek)
pubKey, _ := jwkToPublicKey(publicKey) encryptedCEK, _ := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, cek, nil)
iv := make([]byte, 12) rand.Read(iv) block, _ := aes.NewCipher(cek) gcm, _ := cipher.NewGCM(block) ciphertext := gcm.Seal(nil, iv, plaintext, []byte(headerB64))
return fmt.Sprintf("%s.%s.%s.%s.%s", headerB64, b64url(encryptedCEK), b64url(iv), b64url(ct), b64url(tag)), nil }
|
五、OAuth 2.0 – 现代认证的标准流程
5.1 什么是 OAuth?
OAuth 是一种授权框架,允许第三方应用在不获取你密码的情况下,代表你访问资源。
生活中的例子:
- 你用微信登录某个 App
- App 只获得了”访问你头像和昵称”的权限
- App 不知道你的微信密码
5.2 Device Code 流程
这个项目使用的是 Device Authorization Grant(设备授权),适用于没有浏览器的场景(比如 CLI 工具、智能电视)。
AI 生成的 OAuth Device Code 授权流程示意图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────────────────────┐ │ Device Code 流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. CLI 工具向 AWS 注册,获取 clientId │ │ └── POST /client/register │ │ │ │ 2. CLI 工具获取 device_code 和 user_code │ │ └── POST /device_authorization │ │ │ │ 3. 用户在浏览器中打开链接,输入 user_code │ │ └── https://aws.amazon.com/device │ │ │ │ 4. CLI 工具不断轮询,等待用户授权 │ │ └── POST /token (每 2 秒轮询一次) │ │ │ │ 5. 用户授权后,CLI 工具获得 access_token │ │ │ └─────────────────────────────────────────────────────────────┘
|
5.3 PKCE 流程
PKCE(Proof Key for Code Exchange)是 OAuth 2.0 的一个安全扩展,防止授权码被截获。
AI 生成的 PKCE 安全机制示意图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────┐ │ PKCE 流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 生成随机的 code_verifier │ │ └── 比如:dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk │ │ │ │ 2. 计算 code_challenge = SHA256(code_verifier) │ │ └── E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM │ │ │ │ 3. 发送 code_challenge 给授权服务器 │ │ │ │ 4. 用户授权后,获得 authorization_code │ │ │ │ 5. 用 code + code_verifier 换取 token │ │ └── 服务器验证 SHA256(verifier) == challenge │ │ │ │ 为什么安全? │ │ └── 即使有人截获了 code,没有 verifier 也无法换 token │ │ │ └─────────────────────────────────────────────────────────────┘
|
六、IMAP 协议 – 自动获取邮件验证码
6.1 什么是 IMAP?
IMAP(Internet Message Access Protocol)是用于接收邮件的协议。你用的 Outlook、Gmail 客户端底层都是用 IMAP(或类似的协议)来收邮件的。
6.2 项目如何自动获取验证码?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────────────────────┐ │ 自动获取验证码流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 记录当前收件箱邮件数量(比如 42 封) │ │ │ │ 2. 触发 AWS 发送验证码邮件 │ │ │ │ 3. 轮询检查收件箱: │ │ └── 每 5 秒检查一次 │ │ └── 如果邮件数量 > 42,说明收到了新邮件 │ │ │ │ 4. 获取新邮件内容 │ │ └── FETCH 43 (BODY.PEEK[TEXT]) │ │ │ │ 5. 用正则表达式提取 6 位验证码 │ │ └── regexp: `\b(\d{6})\b` │ │ │ └─────────────────────────────────────────────────────────────┘
|
6.3 XOAUTH2 认证
连接 IMAP 服务器需要认证,项目使用的是 XOAUTH2 方式:
1 2 3 4 5
| func buildXOAuth2(email, accessToken string) string { auth := fmt.Sprintf("user=%s\x01auth=Bearer %s\x01\x01", email, accessToken) return base64.StdEncoding.EncodeToString([]byte(auth)) }
|
通俗理解:就像用 API Key 访问 API 一样,XOAUTH2 是用 OAuth Token 来访问邮箱。
七、并发控制 – 同时注册多个账号
7.1 为什么需要并发?
如果要注册 100 个账号,串行执行太慢了:
- 串行:每个 30 秒 → 100 × 30 = 3000 秒 = 50 分钟
- 并发 10 个:3000 / 10 = 300 秒 = 5 分钟
7.2 信号量模式
项目使用了信号量来控制并发数:
1 2 3 4 5 6 7 8 9 10 11
| sem := make(chan struct{}, 5)
for i := 0; i < 100; i++ { sem <- struct{}{}
go func(idx int) { defer func() { <-sem }() doTask(idx) }(i) }
|
通俗理解:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 信号量就像停车场的车位计数器:
停车场容量 = 5 ├── 车来了 → 计数器 +1 ├── 计数器 = 5 → 后面的车必须等待 ├── 车走了 → 计数器 -1 └── 等待的车可以进入
代码中的对应: ├── goroutine 启动 → sem <- struct{}{} ├── sem 满了 → goroutine 阻塞等待 ├── goroutine 完成 → <-sem └── 等待的 goroutine 可以执行
|
7.3 熔断机制
如果检测到严重错误(比如 IP 被封),需要立即停止所有任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var otpKillOnce sync.Once
func doTask(i int) { result := reg.Run()
if isKillSwitchError(result["error"]) { otpKillOnce.Do(func() { close(stopCh) }) return } }
select { case <-stopCh: return default: }
|
八、内存缓存 + 异步刷盘 – 高性能存储设计
8.1 为什么需要缓存?
如果每次读写都操作磁盘:
- 100 个并发任务同时读写 accounts.json
- 文件锁冲突 → 性能下降
- 频繁的磁盘 I/O → 速度慢
8.2 解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────────────────────┐ │ 内存缓存 + 异步刷盘 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 读操作:直接读内存 │ │ └── GetAccountsCached() → return _accountsCache │ │ │ │ 写操作:写内存 + 标记 dirty + 调度异步刷盘 │ │ └── ModifyAccountsCached(fn) │ │ ├── _accountsCache = fn(_accountsCache) // 写内存 │ │ ├── _accountsDirty = true // 标记脏数据│ │ └── scheduleFlush() // 调度刷盘 │ │ │ │ 异步刷盘:500ms 后统一写磁盘 │ │ └── time.AfterFunc(500*time.Millisecond, flushToDisk) │ │ │ │ 效果:100 次写操作 → 可能只写磁盘 1~2 次 │ │ │ └─────────────────────────────────────────────────────────────┘
|
通俗理解:
1 2 3 4
| 想象一个记账本: ├── 传统方式:每记一笔账就重新抄一遍整个账本(慢!) ├── 优化方式:先记在便签上,攒够一定数量再统一抄写 └── 效果:减少了抄写次数,提高了效率
|
九、Wails 桌面应用框架
9.1 什么是 Wails?
Wails 是一个用 Go 开发桌面应用的框架,类似于 Electron,但更轻量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌─────────────────────────────────────────────────────────────┐ │ Wails vs Electron │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Electron: │ │ ├── 前端:HTML/CSS/JS │ │ ├── 后端:Node.js │ │ ├── 打包体积:~150MB │ │ └── 内存占用:~200MB │ │ │ │ Wails: │ │ ├── 前端:HTML/CSS/JS │ │ ├── 后端:Go │ │ ├── 打包体积:~10MB │ │ └── 内存占用:~50MB │ │ │ └─────────────────────────────────────────────────────────────┘
|
9.2 前后端通信
Wails 使用 JS Bridge 实现前后端通信:
1 2 3 4 5 6
| async function startTask() { const result = await window.go.main.App.StartTask(config); console.log(result); }
|
1 2 3 4
| func (a *App) StartTask(req task.StartTaskRequest) map[string]interface{} { return task.StartTask(req) }
|
十、完整注册流程图
最后,让我们看看整个注册流程是如何串起来的:
AI 生成的 AWS Builder ID 注册与验证场景图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| ┌─────────────────────────────────────────────────────────────┐ │ AWS Builder ID 注册流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ [1] OIDC 注册 ─────────────────────────────────────────┐ │ │ └── 获取 clientId, clientSecret │ │ │ │ │ [2] 设备授权 ─────────────────────────────────────────┐ │ │ └── 获取 deviceCode, userCode │ │ │ │ │ [3] 获取邮箱 ─────────────────────────────────────────┐ │ │ └── Outlook 或临时邮箱 │ │ │ │ │ [4-5] Portal + 工作流初始化 ─────────────────────────┐ │ │ └── 获取 workflowHandle │ │ │ │ │ [6] 提交邮箱 ────────────────────────────────────────┐ │ │ └── 判断是“登录”还是“注册” │ │ │ │ │ [7-8] 注册流程 + Profile 初始化 ────────────────────┐ │ │ └── 获取 workflowID │ │ │ │ │ [9-10] 发送并等待验证码 ────────────────────────────┐ │ │ └── 通过 IMAP 自动获取 6 位验证码 │ │ │ │ │ [11-12] 创建身份 + 设置密码 ───────────────────────┐ │ │ └── 使用 JWE 加密密码 │ │ │ │ │ [13] 获取 SSO Token ──────────────────────────────┐ │ │ └── 轮询等待授权完成 │ │ │ │ │ [14-15] Kiro 授权 + 令牌交换 ────────────────────┐ │ │ └── PKCE 流程获取 access_token │ │ │ │ │ [验活] 验证账号是否可用 ─────────────────────────┐ │ │ └── 刷新 token + 查询用量 │ │ │ │ └─────────────────────────────────────────────────────────────┘
|
总结
这个项目涉及的技术栈非常丰富,我来总结一下每个技术点的核心价值:
| 技术 |
核心价值 |
应用场景 |
| TLS 指纹伪装 |
绕过传输层检测 |
反爬虫、安全测试 |
| 浏览器指纹伪造 |
绕过应用层检测 |
反爬虫、隐私保护 |
| JWE 加密 |
安全传输敏感数据 |
API 安全、JWT |
| OAuth 2.0 |
标准授权流程 |
第三方登录、CLI 工具 |
| IMAP 协议 |
自动化邮件处理 |
邮件机器人、监控 |
| 并发控制 |
提高性能 |
爬虫、任务队列 |
| 内存缓存 |
减少 I/O |
高性能应用 |
| Wails |
轻量级桌面应用 |
工具开发 |
学习建议:
- 先理解原理:不要急着看代码,先理解每个技术是为了解决什么问题
- 动手实践:自己尝试实现一个简单的 OAuth 流程或 IMAP 客户端
- 逆向思维:学会从”攻击者”的角度思考,才能更好地防御
这篇文章分析的技术仅供学习交流,请勿用于非法用途。
如有问题,欢迎在评论区交流。