前言 最近在 GitHub 上发现了一个很有意思的 Go 项目,它能够自动化注册 AWS Builder ID(Amazon 的开发者账号)。这个项目的技术含量非常高,涉及到很多我们在日常开发中不太接触,但又非常有价值的技术。
今天这篇文章,我想以一个小白的视角,来详细拆解这个项目用到的各种技术。即使你不懂 Go 语言,也能看懂。
项目概览 这个项目是做什么的? 简单来说,它可以自动化地注册 AWS 开发者账号 。整个过程包括:
创建邮箱
填写注册信息
接收验证码
设置密码
获取访问令牌
听起来很简单对吧?但难点在于:AWS 有很多反爬虫机制,如何绕过这些检测才是真正的技术挑战 。
技术栈一览
graph TD
A[Kiro 注册机技术架构] --> B[前端]
A --> C[后端]
A --> D[外部服务]
B --> B1[HTML + CSS + JavaScript]
B --> B2[Wails v2 框架]
C --> C1[Go 1.24]
C --> C2[核心注册模块]
C --> C3[反检测模块]
C --> C4[邮箱模块]
C --> C5[任务调度]
C2 --> C21[15步注册流程]
C2 --> C22[OAuth 2.0 + PKCE]
C2 --> C23[JWE 加密]
C3 --> C31[TLS 指纹模拟]
C3 --> C32[浏览器指纹伪造]
C3 --> C33[Canvas/WebGL 指纹]
C4 --> C41[Outlook IMAP]
C4 --> C42[MoeMail API]
C5 --> C51[并发控制]
C5 --> C52[信号量模式]
D --> D1[AWS OIDC]
D --> D2[IMAP 服务器]
D --> D3[MoeMail API]
一、什么是反爬虫?为什么 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)发送的这些信息是不同的!
graph LR
subgraph Chrome 浏览器
C1[JA3: e7d705a3286e19ea42f587b344ee6865]
C2[加密套件: TLS_AES_128_GCM_SHA256, ...]
C3[扩展: GREASE, server_name, ...]
C4[特征: 完整的浏览器 TLS 行为]
end
subgraph Go http.Client
G1[JA3: 3b5074b1b5d032e5c59fd32d...]
G2[加密套件: TLS_RSA_WITH_AES_128_CBC_SHA, ...]
G3[扩展: 较少]
G4[特征: 与浏览器明显不同 ❌]
end
subgraph tls-client 伪装后
F1[JA3: e7d705a3286e19ea42f587b344ee6865]
F2[加密套件: 与 Chrome 一致]
F3[扩展: 与 Chrome 一致]
F4[特征: 与 Chrome 完全一致 ✓]
F5[使用 bogdanfinn/tls-client 库]
end
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 什么是浏览器指纹? 浏览器指纹是网站用来识别你的一系列技术信息,包括:
graph TD
A[浏览器指纹] --> B[Canvas 指纹]
A --> C[WebGL 指纹]
A --> D[设备信息]
A --> E[浏览器属性]
A --> F[Math 精度]
B --> B1[渲染统计直方图]
B --> B2[256 个 bin 的分布]
B --> B3[不同显卡渲染结果不同]
C --> C1[GPU 型号]
C --> C2[驱动版本]
C --> C3[WebGL 扩展列表]
D --> D1["内存: 16GB"]
D --> D2["CPU 核心: 8"]
D --> D3["分辨率: 1920x1080"]
E --> E1["Chrome 版本: 120.0.0.0"]
E --> E2["User-Agent"]
E --> E3["sec-ch-ua"]
F --> F1["Math.tan() 精度差异"]
F --> F2["CPU 特征"]
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 }
通俗理解 :
graph LR
subgraph 真实浏览器渲染结果
R1["bin[0] = 12000 透明背景"]
R2["bin[102] = 600 橙色矩形"]
R3["bin[153] = 400 混合颜色"]
R4["bin[255] = 10000 Alpha通道"]
R5["其他 = 2~30 抗锯齿"]
end
subgraph 项目伪造的结果
F1["bin[0] = 11500 ✓"]
F2["bin[102] = 580 ✓"]
F3["bin[153] = 420 ✓"]
F4["bin[255] = 10500 ✓"]
F5["其他 = 3~25 ✓"]
end
R1 -.-> F1
R2 -.-> F2
R3 -.-> F3
R4 -.-> F4
R5 -.-> F5
note["关键: 按照真实浏览器统计规律生成"]
关键洞察 :项目不是随机生成数据,而是按照真实浏览器的统计规律 来生成!
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 的加密版本。
graph TD
A[JWE 结构] --> B[header]
A --> C[encryptedKey]
A --> D[iv]
A --> E[ciphertext]
A --> F[tag]
B --> B1[算法信息]
B --> B2[密钥 ID]
C --> C1[RSA-OAEP-256 加密后的 AES 密钥]
D --> D1[初始化向量 随机数]
E --> E1[AES-256-GCM 加密后的密码]
F --> F1[认证标签 防篡改]
4.3 混合加密原理 JWE 使用了混合加密 ,这是现代加密的常见模式:
graph TD
A[1. 生成随机 AES 密钥 256位] --> B[2. RSA 公钥加密 AES 密钥]
B --> C[3. AES 密钥加密密码]
C --> D[4. 组装 JWE 格式]
D --> E[5. 发送给 AWS 服务器]
E --> F[6. 服务器解密]
B --> B1[RSA-OAEP-256]
B --> B2[得到 encryptedKey]
C --> C1[AES-256-GCM]
C --> C2[生成随机 IV]
C --> C3[得到 ciphertext + tag]
D --> D1[header.encryptedKey.iv.ciphertext.tag]
F --> F1[RSA 私钥解密 AES 密钥]
F --> F2[AES 密钥解密密码]
F --> F3[验证 tag 防篡改]
note[为什么用混合加密? RSA慢不能加密大量数据, 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 工具、智能电视)。
sequenceDiagram
participant CLI as CLI 工具
participant AWS as AWS OIDC
participant Browser as 用户浏览器
CLI->>AWS: 1. POST /client/register
AWS-->>CLI: clientId, clientSecret
CLI->>AWS: 2. POST /device_authorization
AWS-->>CLI: deviceCode, userCode
CLI->>Browser: 3. 提示用户访问链接
Note right of Browser: https://aws.amazon.com/device
Browser->>AWS: 4. 输入 userCode,授权
AWS-->>Browser: 授权成功
loop 轮询等待
CLI->>AWS: 5. POST /token (每2秒)
AWS-->>CLI: 400 AuthorizationPending
end
AWS-->>CLI: 6. access_token
5.3 PKCE 流程 PKCE (Proof Key for Code Exchange)是 OAuth 2.0 的一个安全扩展,防止授权码被截获。
sequenceDiagram
participant Client as 客户端
participant Server as 授权服务器
Client->>Client: 1. 生成 code_verifier 随机字符串
Client->>Client: 2. 计算 code_challenge = SHA256(verifier)
Client->>Server: 3. 发送 code_challenge
Server->>Server: 4. 保存 code_challenge
Note over Client,Server: 用户授权
Client->>Server: 5. 发送 code + code_verifier
Server->>Server: 6. 验证 SHA256(verifier) == challenge?
alt 验证通过
Server-->>Client: access_token
else 验证失败
Server-->>Client: 400 InvalidGrant
end
Note over Server: 即使截获 code,没有 verifier 也无法换 token
六、IMAP 协议 —— 自动获取邮件验证码 6.1 什么是 IMAP? IMAP (Internet Message Access Protocol)是用于接收邮件的协议。你用的 Outlook、Gmail 客户端底层都是用 IMAP(或类似的协议)来收邮件的。
6.2 项目如何自动获取验证码?
sequenceDiagram
participant Prog as 注册程序
participant IMAP as IMAP 服务器
participant AWS as AWS
Prog->>IMAP: 1. 连接 IMAP 服务器
IMAP-->>Prog: 连接成功
Prog->>IMAP: 2. 记录当前邮件数量 (42封)
IMAP-->>Prog: EXISTS 42
Prog->>AWS: 3. 触发发送验证码
AWS->>IMAP: 发送验证码邮件
loop 轮询检查 (每5秒)
Prog->>IMAP: 4. NOOP / CHECK
IMAP-->>Prog: EXISTS 43 (新邮件!)
end
Prog->>IMAP: 5. FETCH 43 (BODY.PEEK[TEXT])
IMAP-->>Prog: 邮件内容
Prog->>Prog: 6. 正则提取 6位验证码
Note right of Prog: regexp: \b(\d{6})\b
Prog->>AWS: 7. 提交验证码
AWS-->>Prog: 验证成功
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) }
通俗理解 :
graph TD
A["任务队列 任务1~10"] --> B{"信号量 容量=5"}
B -->|获取信号量| C[执行中 goroutine 1~5]
B -->|信号量满| D[等待中 任务6~10]
C -->|释放信号量| B
D -->|等待空位| B
subgraph 工作流程
E["获取信号量: sem <- struct{}{}"] --> F{"信号量满?"}
F -->|是| G[阻塞等待]
F -->|否| H[执行任务]
H --> I["释放信号量: <-sem"]
I --> J[等待的 goroutine 可执行]
end
subgraph 停车场类比
K["容量 = 5 个车位"] --> L["车来 → 计数器 +1"]
L --> M{"计数器满?"}
M -->|是| N[等待]
M -->|否| O[进入]
O --> P["车走 → 计数器 -1"]
P --> Q[等待的车可以进入]
end
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 解决方案
graph TD
subgraph 内存缓存
A[_accountsCache 账号数据]
B[_accountsDirty 脏数据标记]
end
subgraph 磁盘
C[accounts.json]
end
subgraph 写入流程
D[1. 写入内存] --> E[2. 标记 dirty=true]
E --> F[3. 调度异步刷盘]
end
subgraph 刷盘流程
G[500ms 后] --> H[检查 dirty 标记]
H --> I[写入磁盘]
I --> J[重置 dirty=false]
end
F --> G
D --> A
I --> C
note[效果: 100 次写操作 → 可能只写磁盘 1~2 次 → 大幅减少 I/O]
通俗理解 :
1 2 3 4 想象一个记账本: ├── 传统方式:每记一笔账就重新抄一遍整个账本(慢!) ├── 优化方式:先记在便签上,攒够一定数量再统一抄写 └── 效果:减少了抄写次数,提高了效率
九、Wails 桌面应用框架 9.1 什么是 Wails? Wails 是一个用 Go 开发桌面应用的框架,类似于 Electron,但更轻量:
对比项
Electron
Wails
前端
HTML/CSS/JS
HTML/CSS/JS
后端
Node.js
Go
打包体积
~150MB
~10MB
内存占用
~200MB
~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) }
十、完整注册流程图 最后,让我们看看整个注册流程是如何串起来的:
graph TD
A["Step 1: OIDC 注册"] --> B["Step 2: 设备授权"]
B --> C["Step 3: 获取邮箱"]
C --> D["Step 4-5: Portal + 工作流初始化"]
D --> E["Step 6: 提交邮箱"]
E --> F["Step 7-8: 注册流程 + Profile 初始化"]
F --> G["Step 9-10: 发送并等待验证码"]
G --> H["Step 11-12: 创建身份 + 设置密码"]
H --> I["Step 13: 获取 SSO Token"]
I --> J["Step 14-15: Kiro 授权 + 令牌交换"]
J --> K["验活: 验证账号是否可用"]
K --> L["输出 accounts.json"]
A --> A1["POST /client/register"]
A --> A2["获取 clientId, clientSecret"]
B --> B1["POST /device_authorization"]
B --> B2["获取 deviceCode, userCode"]
C --> C1[Outlook 或临时邮箱]
D --> D1[获取 workflowHandle]
E --> E1["判断是登录还是注册"]
F --> F1[获取 workflowID]
G --> G1["通过 IMAP 自动获取 6位验证码"]
H --> H1["使用 JWE 加密密码"]
I --> I1[轮询等待授权完成]
J --> J1["PKCE 流程获取 access_token"]
K --> K1["刷新 token + 查询用量"]
总结 这个项目涉及的技术栈非常丰富,我来总结一下每个技术点的核心价值:
技术
核心价值
应用场景
TLS 指纹伪装
绕过传输层检测
反爬虫、安全测试
浏览器指纹伪造
绕过应用层检测
反爬虫、隐私保护
JWE 加密
安全传输敏感数据
API 安全、JWT
OAuth 2.0
标准授权流程
第三方登录、CLI 工具
IMAP 协议
自动化邮件处理
邮件机器人、监控
并发控制
提高性能
爬虫、任务队列
内存缓存
减少 I/O
高性能应用
Wails
轻量级桌面应用
工具开发
学习建议 :
先理解原理 :不要急着看代码,先理解每个技术是为了解决什么问题
动手实践 :自己尝试实现一个简单的 OAuth 流程或 IMAP 客户端
逆向思维 :学会从”攻击者”的角度思考,才能更好地防御
这篇文章分析的技术仅供学习交流,请勿用于非法用途。
如有问题,欢迎在评论区交流。