@mseep/anything-analyzer 3.6.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/.codeartsdoer/.codebaseignore +0 -0
  2. package/.codeartsdoer/AGENTS.md +12 -0
  3. package/.github/workflows/build.yml +146 -0
  4. package/README.en.md +264 -0
  5. package/README.md +276 -0
  6. package/RELEASE_NOTES.md +16 -0
  7. package/USAGE.md +490 -0
  8. package/color-preview-r3.html +414 -0
  9. package/color-preview.html +414 -0
  10. package/dev-app-update.yml +3 -0
  11. package/electron-builder.yml +36 -0
  12. package/electron.vite.config.ts +40 -0
  13. package/package.json +53 -0
  14. package/report-2026-04-13-copilot-claude-sonnet-4.6.md +955 -0
  15. package/resources/doloffer-logo.png +0 -0
  16. package/resources/entitlements.mac.plist +12 -0
  17. package/resources/icon.ico +0 -0
  18. package/resources/icon.png +0 -0
  19. package/src/main/ai/ai-analyzer.ts +517 -0
  20. package/src/main/ai/crypto-script-extractor.ts +206 -0
  21. package/src/main/ai/data-assembler.ts +205 -0
  22. package/src/main/ai/llm-router.ts +1120 -0
  23. package/src/main/ai/prompt-builder.ts +349 -0
  24. package/src/main/ai/scene-detector.ts +302 -0
  25. package/src/main/capture/capture-engine.ts +130 -0
  26. package/src/main/capture/interaction-recorder.ts +171 -0
  27. package/src/main/capture/js-injector.ts +57 -0
  28. package/src/main/capture/replay-engine.ts +256 -0
  29. package/src/main/capture/storage-collector.ts +76 -0
  30. package/src/main/cdp/cdp-manager.ts +233 -0
  31. package/src/main/db/database.ts +41 -0
  32. package/src/main/db/migrations.ts +235 -0
  33. package/src/main/db/repositories.ts +574 -0
  34. package/src/main/fingerprint/http-spoofing.ts +48 -0
  35. package/src/main/fingerprint/presets.ts +173 -0
  36. package/src/main/fingerprint/profile-generator.ts +115 -0
  37. package/src/main/fingerprint/profile-store.ts +52 -0
  38. package/src/main/index.ts +260 -0
  39. package/src/main/ipc.ts +856 -0
  40. package/src/main/logger.ts +42 -0
  41. package/src/main/mcp/mcp-config.ts +66 -0
  42. package/src/main/mcp/mcp-manager.ts +155 -0
  43. package/src/main/mcp/mcp-server.ts +1038 -0
  44. package/src/main/prompt-templates.ts +170 -0
  45. package/src/main/proxy/ca-manager.ts +204 -0
  46. package/src/main/proxy/cert-download-page.ts +171 -0
  47. package/src/main/proxy/cert-installer.ts +242 -0
  48. package/src/main/proxy/mitm-proxy-config.ts +37 -0
  49. package/src/main/proxy/mitm-proxy-server.ts +1085 -0
  50. package/src/main/proxy/system-proxy.ts +248 -0
  51. package/src/main/session/session-manager.ts +724 -0
  52. package/src/main/tab-manager.ts +582 -0
  53. package/src/main/updater.ts +111 -0
  54. package/src/main/window.ts +235 -0
  55. package/src/preload/hook-script.ts +270 -0
  56. package/src/preload/index.ts +211 -0
  57. package/src/preload/interaction-hook.ts +286 -0
  58. package/src/preload/stealth-script.ts +302 -0
  59. package/src/preload/target-preload.ts +15 -0
  60. package/src/renderer/App.tsx +656 -0
  61. package/src/renderer/components/AiLogDetail.tsx +173 -0
  62. package/src/renderer/components/AiLogList.tsx +101 -0
  63. package/src/renderer/components/AiLogView.module.css +364 -0
  64. package/src/renderer/components/AiLogView.tsx +86 -0
  65. package/src/renderer/components/AnalyzeBar.module.css +79 -0
  66. package/src/renderer/components/AnalyzeBar.tsx +104 -0
  67. package/src/renderer/components/BrowserPanel.module.css +67 -0
  68. package/src/renderer/components/BrowserPanel.tsx +90 -0
  69. package/src/renderer/components/ControlBar.module.css +47 -0
  70. package/src/renderer/components/ControlBar.tsx +205 -0
  71. package/src/renderer/components/HookLog.tsx +132 -0
  72. package/src/renderer/components/InteractionLog.tsx +183 -0
  73. package/src/renderer/components/MCPServerModal.tsx +427 -0
  74. package/src/renderer/components/PromptTemplateModal.tsx +254 -0
  75. package/src/renderer/components/ReportView.module.css +413 -0
  76. package/src/renderer/components/ReportView.tsx +429 -0
  77. package/src/renderer/components/RequestDetail.module.css +191 -0
  78. package/src/renderer/components/RequestDetail.tsx +202 -0
  79. package/src/renderer/components/RequestLog.module.css +69 -0
  80. package/src/renderer/components/RequestLog.tsx +208 -0
  81. package/src/renderer/components/SessionList.module.css +245 -0
  82. package/src/renderer/components/SessionList.tsx +247 -0
  83. package/src/renderer/components/SettingsModal.tsx +100 -0
  84. package/src/renderer/components/StatusBar.module.css +44 -0
  85. package/src/renderer/components/StatusBar.tsx +102 -0
  86. package/src/renderer/components/StorageView.module.css +41 -0
  87. package/src/renderer/components/StorageView.tsx +178 -0
  88. package/src/renderer/components/TabBar.module.css +88 -0
  89. package/src/renderer/components/TabBar.tsx +70 -0
  90. package/src/renderer/components/Titlebar.module.css +254 -0
  91. package/src/renderer/components/Titlebar.tsx +169 -0
  92. package/src/renderer/components/settings/FingerprintSection.tsx +198 -0
  93. package/src/renderer/components/settings/GeneralSection.tsx +164 -0
  94. package/src/renderer/components/settings/LLMSection.tsx +148 -0
  95. package/src/renderer/components/settings/MCPServerSection.tsx +136 -0
  96. package/src/renderer/components/settings/MitmProxySection.tsx +320 -0
  97. package/src/renderer/components/settings/ProxySection.tsx +110 -0
  98. package/src/renderer/css-modules.d.ts +4 -0
  99. package/src/renderer/hooks/useCapture.ts +383 -0
  100. package/src/renderer/hooks/useConfirm.tsx +91 -0
  101. package/src/renderer/hooks/useSession.ts +136 -0
  102. package/src/renderer/hooks/useTabs.ts +103 -0
  103. package/src/renderer/i18n/en.ts +167 -0
  104. package/src/renderer/i18n/index.ts +47 -0
  105. package/src/renderer/i18n/zh.ts +170 -0
  106. package/src/renderer/index.html +12 -0
  107. package/src/renderer/main.tsx +15 -0
  108. package/src/renderer/styles/global.css +144 -0
  109. package/src/renderer/styles/themes/ayu-dark.css +59 -0
  110. package/src/renderer/styles/themes/catppuccin.css +59 -0
  111. package/src/renderer/styles/themes/discord.css +59 -0
  112. package/src/renderer/styles/themes/dracula.css +59 -0
  113. package/src/renderer/styles/themes/github-dark.css +59 -0
  114. package/src/renderer/styles/themes/gruvbox.css +59 -0
  115. package/src/renderer/styles/themes/index.css +11 -0
  116. package/src/renderer/styles/themes/light.css +59 -0
  117. package/src/renderer/styles/themes/nord.css +59 -0
  118. package/src/renderer/styles/themes/one-dark.css +59 -0
  119. package/src/renderer/styles/themes/tokyo-night.css +59 -0
  120. package/src/renderer/styles/tokens.css +137 -0
  121. package/src/renderer/theme.ts +31 -0
  122. package/src/renderer/ui/Badge.module.css +38 -0
  123. package/src/renderer/ui/Badge.tsx +36 -0
  124. package/src/renderer/ui/Button.module.css +142 -0
  125. package/src/renderer/ui/Button.tsx +46 -0
  126. package/src/renderer/ui/Collapse.module.css +49 -0
  127. package/src/renderer/ui/Collapse.tsx +57 -0
  128. package/src/renderer/ui/CopyableBlock.module.css +56 -0
  129. package/src/renderer/ui/CopyableBlock.tsx +42 -0
  130. package/src/renderer/ui/Empty.module.css +19 -0
  131. package/src/renderer/ui/Empty.tsx +34 -0
  132. package/src/renderer/ui/Icons.tsx +346 -0
  133. package/src/renderer/ui/Input.module.css +103 -0
  134. package/src/renderer/ui/Input.tsx +94 -0
  135. package/src/renderer/ui/InputNumber.module.css +68 -0
  136. package/src/renderer/ui/InputNumber.tsx +104 -0
  137. package/src/renderer/ui/Modal.module.css +83 -0
  138. package/src/renderer/ui/Modal.tsx +67 -0
  139. package/src/renderer/ui/Popconfirm.module.css +73 -0
  140. package/src/renderer/ui/Popconfirm.tsx +74 -0
  141. package/src/renderer/ui/Progress.module.css +35 -0
  142. package/src/renderer/ui/Progress.tsx +30 -0
  143. package/src/renderer/ui/Select.module.css +91 -0
  144. package/src/renderer/ui/Select.tsx +100 -0
  145. package/src/renderer/ui/Spinner.module.css +44 -0
  146. package/src/renderer/ui/Spinner.tsx +27 -0
  147. package/src/renderer/ui/Switch.module.css +39 -0
  148. package/src/renderer/ui/Switch.tsx +43 -0
  149. package/src/renderer/ui/Tabs.module.css +76 -0
  150. package/src/renderer/ui/Tabs.tsx +53 -0
  151. package/src/renderer/ui/Tag.module.css +66 -0
  152. package/src/renderer/ui/Tag.tsx +47 -0
  153. package/src/renderer/ui/Timeline.module.css +42 -0
  154. package/src/renderer/ui/Timeline.tsx +29 -0
  155. package/src/renderer/ui/Toast.module.css +99 -0
  156. package/src/renderer/ui/Toast.tsx +90 -0
  157. package/src/renderer/ui/Tooltip.module.css +26 -0
  158. package/src/renderer/ui/Tooltip.tsx +23 -0
  159. package/src/renderer/ui/VirtualTable.module.css +230 -0
  160. package/src/renderer/ui/VirtualTable.tsx +416 -0
  161. package/src/renderer/ui/index.ts +55 -0
  162. package/src/shared/types.ts +695 -0
  163. package/tests/main/ai/crypto-script-extractor.test.ts +281 -0
  164. package/tests/main/ai/llm-router.test.ts +1537 -0
  165. package/tests/main/ai/prompt-builder.test.ts +178 -0
  166. package/tests/main/ai/scene-detector.test.ts +212 -0
  167. package/tests/main/db/migrations.test.ts +134 -0
  168. package/tests/main/release-workflow.test.ts +59 -0
  169. package/tsconfig.json +7 -0
  170. package/tsconfig.node.json +23 -0
  171. package/tsconfig.web.json +24 -0
  172. package/vitest.config.ts +13 -0
@@ -0,0 +1,955 @@
1
+ # Cursor.com 加密机制深度分析报告
2
+
3
+ ## 一、整体架构概览
4
+
5
+ ```
6
+ 用户访问 cursor.com
7
+
8
+
9
+ ┌─────────────────────────────────────────────────────┐
10
+ │ KasadaBot Protection Layer │
11
+ │ (149e9513-01fa-4fb0-aad4-566afd725d1b 路径标识) │
12
+ └──────────────┬──────────────────────────────────────┘
13
+
14
+ ┌──────────┴──────────┐
15
+ ▼ ▼
16
+ /fp 指纹采集 /tl 令牌提交
17
+ (fingerprint) (token layer)
18
+
19
+
20
+ ┌─────────────────────┐
21
+ │ KP_UIDz Cookie │ → 携带至业务请求
22
+ └──────────┬──────────┘
23
+
24
+
25
+ POST /api/chat (SSE流式响应)
26
+ ```
27
+
28
+ ---
29
+
30
+ ## 二、加密算法识别
31
+
32
+ ### 2.1 算法清单
33
+
34
+ | 编号 | 算法 | 用途 | 位置 | 具体方法 |
35
+ |------|------|------|------|----------|
36
+ | A1 | **AES-CBC (256-bit)** | 指纹数据加密 | c.js `D()` 函数 | `crypto.subtle.encrypt({name:'AES-CBC'})` |
37
+ | A2 | **PBKDF2** | AES密钥派生 | c.js `D()` 函数 | `crypto.subtle.deriveBits` → `importKey` |
38
+ | A3 | **SHA-256** | PBKDF2哈希参数 | c.js `D()` 函数 | `{hash:'SHA-256'}` |
39
+ | A4 | **RC4变体 (自定义KSA)** | 字符串混淆解密 | c.js `y()` 函数 | 手写KSA/PRGA,无标准库 |
40
+ | A5 | **XEW (XOR编码)** | 数值混淆 | c.js 常量表 | `^` 运算符 |
41
+ | A6 | **Base64** | 最终编码 | c.js `btoa()` | 原生btoa |
42
+ | A7 | **HMAC (推测)** | `/tl` 请求体签名 | 二进制请求体 | 不可见(混淆后运行时) |
43
+
44
+ ### 2.2 `D()` 函数核心代码还原(去混淆)
45
+
46
+ ```javascript
47
+ // 原始混淆版本关键结构抽取:
48
+ async function D(plaintext_key, data_object) {
49
+ // 1. 生成随机盐 (16字节)
50
+ let salt = crypto.getRandomValues(new Uint8Array(16));
51
+
52
+ // 2. 生成随机IV (16字节)
53
+ let iv = crypto.getRandomValues(new Uint8Array(16));
54
+
55
+ // 3. 导入原始密钥材料 (密码 = swsWp字段值)
56
+ let rawKey = await crypto.subtle.importKey(
57
+ 'raw',
58
+ new TextEncoder().encode(plaintext_key), // 'JoFJh' → 密码字符串
59
+ 'PBKDF2',
60
+ false,
61
+ ['deriveBits', 'deriveKey'] // 'hXWsW' + 'KwHKs'
62
+ );
63
+
64
+ // 4. PBKDF2 派生 AES-256 密钥
65
+ let aesKey = await crypto.subtle.deriveKey(
66
+ {
67
+ name: 'PBKDF2', // 'JoFJh'
68
+ salt: salt,
69
+ iterations: 100000, // 0x186a0
70
+ hash: 'SHA-256' // 'jBgDZ'
71
+ },
72
+ rawKey,
73
+ {name: 'AES-CBC', length: 256}, // 'CLVUw'
74
+ false,
75
+ ['encrypt'] // 'BaHKn'
76
+ );
77
+
78
+ // 5. AES-CBC 加密
79
+ let ciphertext = await crypto.subtle.encrypt(
80
+ {name: 'AES-CBC', iv: iv},
81
+ aesKey,
82
+ new TextEncoder().encode(JSON.stringify(data_object))
83
+ );
84
+
85
+ // 6. 拼接: salt(16) + iv(16) + ciphertext → Base64
86
+ return btoa(String.fromCharCode(...salt, ...iv, ...new Uint8Array(ciphertext)));
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 三、加密流程完整 Pipeline
93
+
94
+ ### 3.1 指纹数据加密流程
95
+
96
+ ```
97
+ ┌─────────────────────────────────────────────────────────────────────┐
98
+ │ 数据流转图 │
99
+ ├─────────────────────────────────────────────────────────────────────┤
100
+ │ │
101
+ │ 环境指纹采集 J() │
102
+ │ ┌───────────────────────────────────────────┐ │
103
+ │ │ p: 是否自动化浏览器检测 (devtools/phantom)│ │
104
+ │ │ S: 0.4043... (固定混淆数值) │ │
105
+ │ │ w: WebGL渲染器信息 t(window) │ │
106
+ │ │ s: 导航器/驱动器检测 W(window) │ │
107
+ │ │ h: UserAgent检测 N(window) │ │
108
+ │ │ b: console检测 e() │ │
109
+ │ │ d: devtools开启状态 j() │ │
110
+ │ └──────────────────┬────────────────────────┘ │
111
+ │ │ │
112
+ │ ▼ │
113
+ │ 密钥生成 │
114
+ │ ┌──────────────────────────────┐ │
115
+ │ │ k = btoa('juvsu') + ... │ ← RC4解混淆后的硬编码密钥片段 │
116
+ │ │ a = btoa('EoXvw') + ... │ ← 多段字符串拼接 │
117
+ │ │ password = k + a (concat) │ │
118
+ │ └──────────────────┬───────────┘ │
119
+ │ │ │
120
+ │ ▼ │
121
+ │ PBKDF2 密钥派生 │
122
+ │ ┌────────────────────────────────────────┐ │
123
+ │ │ 输入: password (UTF-8) │ │
124
+ │ │ salt: crypto.getRandomValues(16B) │ │
125
+ │ │ iter: 100,000 次 │ │
126
+ │ │ hash: SHA-256 │ │
127
+ │ │ 输出: AES-256-CBC Key │ │
128
+ │ └──────────────────┬─────────────────────┘ │
129
+ │ │ │
130
+ │ ▼ │
131
+ │ AES-256-CBC 加密 │
132
+ │ ┌────────────────────────────────────────┐ │
133
+ │ │ 明文: JSON.stringify(fingerprint_obj) │ │
134
+ │ │ IV: crypto.getRandomValues(16B) │ │
135
+ │ │ Key: 上步派生结果 │ │
136
+ │ └──────────────────┬─────────────────────┘ │
137
+ │ │ │
138
+ │ ▼ │
139
+ │ 拼接编码 │
140
+ │ ┌─────────────────────────────────────┐ │
141
+ │ │ output = Base64(salt + iv + cipher)│ 共 32 + len(cipher) 字节 │
142
+ │ └──────────────────┬──────────────────┘ │
143
+ │ │ │
144
+ │ ▼ │
145
+ │ POST /tl (octet-stream 二进制请求体) │
146
+ │ ← 响应: {"reload": true} 表示令牌有效 │
147
+ │ │
148
+ └─────────────────────────────────────────────────────────────────────┘
149
+ ```
150
+
151
+ ### 3.2 KP_UIDz Cookie 生命周期
152
+
153
+ ```
154
+ /fp 请求
155
+ └─→ 加载 ips.js (携带 KP_UID 参数)
156
+ └─→ Set-Cookie: KP_UIDz-ssn = <会话令牌>
157
+ └─→ Set-Cookie: KP_UIDz = <持久令牌>
158
+ └─→ 后续所有请求携带此Cookie
159
+ └─→ /api/chat 等业务接口鉴权
160
+ ```
161
+
162
+ ### 3.3 Kasada `/tl` 请求体结构分析
163
+
164
+ ```
165
+ 二进制请求体布局 (推测结构):
166
+ ┌─────────────────────────────────────────┐
167
+ │ [0-3] Magic / Version Header │
168
+ │ [4-7] Timestamp (Unix ms) │
169
+ │ [8-23] KP_UIDz Token Binding │
170
+ │ [24-39] HMAC-SHA256 of above │
171
+ │ [40+] AES加密的指纹JSON │
172
+ │ [40-55] salt (16 bytes) │
173
+ │ [56-71] IV (16 bytes) │
174
+ │ [72+] ciphertext │
175
+ └─────────────────────────────────────────┘
176
+ ```
177
+
178
+ ---
179
+
180
+ ## 四、密钥管理分析
181
+
182
+ ### 4.1 密钥来源详情
183
+
184
+ | 密钥 | 来源类型 | 格式 | 长度 | 说明 |
185
+ |------|----------|------|------|------|
186
+ | PBKDF2 password (`k+a`) | **半硬编码** | UTF-8字符串 | ~32字节 | RC4混淆存储在JS中,运行时拼接 |
187
+ | AES salt | **随机生成** | Uint8Array | 16 bytes | 每次请求随机,嵌入密文 |
188
+ | AES IV | **随机生成** | Uint8Array | 16 bytes | 每次请求随机,嵌入密文 |
189
+ | KP_UIDz | **服务端颁发** | Base64-like URL-safe | ~128字节 | 服务端验证后通过Cookie下发 |
190
+ | Challenge nonce | **服务端提供** | URL参数 | ~64字节 | `x-kpsdk-im` 参数 |
191
+
192
+ ### 4.2 密钥 `k` 和 `a` 的还原逻辑
193
+
194
+ ```javascript
195
+ // k 的生成 (t()函数末尾):
196
+ // btoa("juvsu") = "anV2c3U="
197
+ // 加上一系列 String.fromCharCode 拼接的字符
198
+
199
+ // a 的生成 (W()函数末尾):
200
+ // btoa("5p9?en") → 对应 kniaX
201
+ // btoa("EoXvw") → 对应 mgiimgtq7 的base64
202
+ // btoa("7bjXis") → 对应 yHLWo
203
+ ```
204
+
205
+ ---
206
+
207
+ ## 五、签名/校验机制
208
+
209
+ ### 5.1 Kasada 挑战-应答机制
210
+
211
+ ```
212
+ 服务端 客户端
213
+ │ │
214
+ │── GET /fp?x-kpsdk-v=j-1.2.308 ──→│
215
+ │ │ 解析响应中的 KPSDK:MC:... 消息
216
+ │ │ 包含: challenge_token
217
+ │ │
218
+ │← POST x-kpsdk-im (challenge) ────│
219
+ │ │ 执行 c.js 中的 PoW 计算
220
+ │ │ 采集浏览器指纹
221
+ │ │ 加密指纹数据
222
+ │ │
223
+ │← POST /tl (binary payload) ──────│
224
+ │ Headers: │
225
+ │ KP_UIDz-ssn, KP_UIDz cookies │
226
+ │ │
227
+ │── Set-Cookie: KP_UIDz=... ───────→│
228
+ │ {"reload": true} │
229
+ ```
230
+
231
+ ### 5.2 Vercel Analytics 事件签名
232
+
233
+ ```json
234
+ // #82 请求体 (明文,无加密)
235
+ {
236
+ "o": "https://cursor.com/cn/docs/api", // origin
237
+ "sv": "0.1.3", // sdk version
238
+ "sdkn": "@vercel/analytics/next",
239
+ "sdkv": "1.6.1",
240
+ "ts": 1775918082253, // 时间戳 (ms)
241
+ "dp": "/[lang]/docs/[...slug]", // dynamic path
242
+ "r": "", // referrer
243
+ "en": "Submit prompt", // event name
244
+ "ed": { // event data
245
+ "message": "hi",
246
+ "page": "/docs/api",
247
+ "turnNumber": 1
248
+ }
249
+ }
250
+ // 无签名,仅依赖Cookie鉴权
251
+ ```
252
+
253
+ ---
254
+
255
+ ## 六、完整 Python 复现代码
256
+
257
+ ```python
258
+ #!/usr/bin/env python3
259
+ """
260
+ Cursor.com Kasada 绕过 + AI Chat 接口调用复现
261
+ 注意: 本代码仅用于安全研究和学习目的
262
+
263
+ 依赖: pip install httpx cryptography
264
+ """
265
+
266
+ import os
267
+ import json
268
+ import base64
269
+ import struct
270
+ import time
271
+ import asyncio
272
+ import hashlib
273
+ import secrets
274
+ from typing import Optional
275
+
276
+ import httpx
277
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
278
+ from cryptography.hazmat.primitives import hashes, padding
279
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
280
+ from cryptography.hazmat.backends import default_backend
281
+
282
+
283
+ # ============================================================
284
+ # 1. 常量与配置
285
+ # ============================================================
286
+
287
+ BASE_URL = "https://cursor.com"
288
+ USER_AGENT = (
289
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
290
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
291
+ "Chrome/134.0.6998.205 Safari/537.36"
292
+ )
293
+
294
+ # Kasada 路径标识符 (从请求日志中提取)
295
+ KASADA_UUID_1 = "149e9513-01fa-4fb0-aad4-566afd725d1b"
296
+ KASADA_UUID_2 = "2d206a39-8ed7-437e-a3be-862e0f06eea3"
297
+
298
+ # PBKDF2 密码 (从 c.js 混淆代码中提取的半硬编码值)
299
+ # 注意: 实际值需要运行 c.js 才能得到完整字符串
300
+ # 以下是根据代码分析的近似结构
301
+ PBKDF2_PASSWORD_K_SEED = "juvsu" # juvsu → btoa → "anV2c3U="
302
+ PBKDF2_PASSWORD_A_SEED = "EoXvw" # mgiimgtq7 相关
303
+
304
+ PBKDF2_ITERATIONS = 100_000
305
+
306
+
307
+ # ============================================================
308
+ # 2. 加密工具函数
309
+ # ============================================================
310
+
311
+ class AESCBCEncryptor:
312
+ """AES-256-CBC 加密器,对应 c.js 中的 D() 函数"""
313
+
314
+ @staticmethod
315
+ def derive_key(password: str, salt: bytes) -> bytes:
316
+ """
317
+ PBKDF2-SHA256 密钥派生
318
+ 对应: crypto.subtle.deriveKey({name:'PBKDF2', hash:'SHA-256', iterations:100000})
319
+ """
320
+ kdf = PBKDF2HMAC(
321
+ algorithm=hashes.SHA256(),
322
+ length=32, # AES-256 = 32 bytes
323
+ salt=salt,
324
+ iterations=PBKDF2_ITERATIONS,
325
+ backend=default_backend()
326
+ )
327
+ return kdf.derive(password.encode('utf-8'))
328
+
329
+ @staticmethod
330
+ def encrypt(password: str, plaintext: str) -> str:
331
+ """
332
+ 完整加密流程:
333
+ password + random_salt → PBKDF2 → AES_key
334
+ plaintext → PKCS7 padding → AES-CBC(key, random_iv) → ciphertext
335
+ output = Base64(salt[16] + iv[16] + ciphertext)
336
+ """
337
+ # 生成随机 salt 和 IV
338
+ salt = secrets.token_bytes(16)
339
+ iv = secrets.token_bytes(16)
340
+
341
+ # 派生 AES 密钥
342
+ aes_key = AESCBCEncryptor.derive_key(password, salt)
343
+
344
+ # PKCS7 填充
345
+ padder = padding.PKCS7(128).padder()
346
+ padded_data = padder.update(plaintext.encode('utf-8')) + padder.finalize()
347
+
348
+ # AES-CBC 加密
349
+ cipher = Cipher(
350
+ algorithms.AES(aes_key),
351
+ modes.CBC(iv),
352
+ backend=default_backend()
353
+ )
354
+ encryptor = cipher.encryptor()
355
+ ciphertext = encryptor.update(padded_data) + encryptor.finalize()
356
+
357
+ # 拼接: salt + iv + ciphertext → Base64
358
+ combined = salt + iv + ciphertext
359
+ return base64.b64encode(combined).decode('ascii')
360
+
361
+ @staticmethod
362
+ def decrypt(password: str, encoded: str) -> str:
363
+ """解密验证函数"""
364
+ data = base64.b64decode(encoded)
365
+ salt = data[:16]
366
+ iv = data[16:32]
367
+ ciphertext = data[32:]
368
+
369
+ aes_key = AESCBCEncryptor.derive_key(password, salt)
370
+
371
+ cipher = Cipher(
372
+ algorithms.AES(aes_key),
373
+ modes.CBC(iv),
374
+ backend=default_backend()
375
+ )
376
+ decryptor = cipher.decryptor()
377
+ padded = decryptor.update(ciphertext) + decryptor.finalize()
378
+
379
+ unpadder = padding.PKCS7(128).unpadder()
380
+ plaintext = unpadder.update(padded) + unpadder.finalize()
381
+ return plaintext.decode('utf-8')
382
+
383
+
384
+ # ============================================================
385
+ # 3. 指纹数据构造
386
+ # ============================================================
387
+
388
+ class FingerprintCollector:
389
+ """
390
+ 模拟 c.js 中 J() 函数的指纹采集
391
+ 对应字段: p, S, w, s, h, b, d
392
+ """
393
+
394
+ @staticmethod
395
+ def check_automation() -> bool:
396
+ """p: 检测自动化工具特征"""
397
+ return False # 正常浏览器返回 False
398
+
399
+ @staticmethod
400
+ def get_fixed_value() -> float:
401
+ """S: 固定混淆数值"""
402
+ return 0.4043074801008981
403
+
404
+ @staticmethod
405
+ def get_webgl_info() -> Optional[dict]:
406
+ """
407
+ w: WebGL渲染器信息
408
+ 对应 t(window) 函数,获取 UNMASKED_VENDOR/RENDERER
409
+ """
410
+ return {
411
+ "v": "Google Inc. (NVIDIA)",
412
+ "r": "ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0, D3D11)"
413
+ }
414
+
415
+ @staticmethod
416
+ def check_navigator() -> str:
417
+ """
418
+ s: 导航器/WebDriver检测结果
419
+ 对应 W(window) 函数
420
+ """
421
+ # btoa("5p9?en") 相关
422
+ return base64.b64encode(b"5p9?en").decode() + \
423
+ base64.b64encode(b"EoXvw").decode() + \
424
+ "false"
425
+
426
+ @staticmethod
427
+ def check_useragent(ua: str) -> bool:
428
+ """h: UA 中是否包含 'headless'"""
429
+ return "headless" in ua.lower()
430
+
431
+ @staticmethod
432
+ def check_console() -> bool:
433
+ """b: console 对象检测"""
434
+ return False
435
+
436
+ @staticmethod
437
+ def get_devtools_state() -> dict:
438
+ """d: DevTools 开启状态"""
439
+ return {
440
+ "isOpen": False,
441
+ "orientation": None
442
+ }
443
+
444
+ def collect(self) -> dict:
445
+ """收集完整指纹"""
446
+ ua = USER_AGENT
447
+ return {
448
+ "p": self.check_automation(),
449
+ "S": self.get_fixed_value(),
450
+ "w": self.get_webgl_info(),
451
+ "s": self.check_navigator(),
452
+ "h": self.check_useragent(ua),
453
+ "b": self.check_console(),
454
+ "d": self.get_devtools_state()
455
+ }
456
+
457
+
458
+ # ============================================================
459
+ # 4. 密码生成 (还原 c.js 中 k 和 a 的生成逻辑)
460
+ # ============================================================
461
+
462
+ class PasswordGenerator:
463
+ """
464
+ 还原 c.js 中 k (来自t函数) 和 a (来自W函数) 的生成
465
+ """
466
+
467
+ @staticmethod
468
+ def _rc4_decrypt(ciphertext_b64: str, key: str) -> str:
469
+ """
470
+ 还原 c.js 中的 y() 函数 (RC4-like KSA/PRGA)
471
+ 用于解密混淆字符串
472
+ """
473
+ # Base64 解码
474
+ decoded = base64.b64decode(ciphertext_b64).decode('latin-1')
475
+
476
+ # KSA
477
+ S = list(range(256))
478
+ j = 0
479
+ for i in range(256):
480
+ j = (j + S[i] + ord(key[i % len(key)])) % 256
481
+ S[i], S[j] = S[j], S[i]
482
+
483
+ # PRGA + XOR
484
+ result = []
485
+ i = j = 0
486
+ for char in decoded:
487
+ i = (i + 1) % 256
488
+ j = (j + S[i]) % 256
489
+ S[i], S[j] = S[j], S[i]
490
+ result.append(chr(ord(char) ^ S[(S[i] + S[j]) % 256]))
491
+
492
+ return ''.join(result)
493
+
494
+ @staticmethod
495
+ def generate_k_fragment() -> str:
496
+ """
497
+ t() 函数末尾生成的 k 值
498
+ 基于 btoa("juvsu") 和一系列 String.fromCharCode 调用
499
+ """
500
+ # btoa("juvsu") = "anV2c3U="
501
+ part1 = base64.b64encode(b"juvsu").decode()
502
+
503
+ # 以下 fromCharCode 值从混淆代码的数学表达式计算
504
+ # 示例: String.fromCharCode(0x1*0x9bf + -0xa*-0x36f + -0x2be0 - ...)
505
+ # 实际需要执行JS才能获得精确值,以下是近似还原
506
+ char_codes_k = [
507
+ 102, 51, 56, 100, 118, # "f38dv" → juvsu相关
508
+ 55, 98, 106, 88, 105, 115 # "7bjXis" → yHLWo相关
509
+ ]
510
+ part2 = ''.join(chr(c) for c in char_codes_k)
511
+
512
+ # btoa("7bjXis")
513
+ part3 = base64.b64encode(b"7bjXis").decode()
514
+
515
+ return part1 + part2 + part3
516
+
517
+ @staticmethod
518
+ def generate_a_fragment() -> str:
519
+ """
520
+ W() 函数末尾生成的 a 值
521
+ 基于 btoa("5p9?en") 和 btoa("mgiimgtq7") 等
522
+ """
523
+ part1 = base64.b64encode(b"5p9?en").decode()
524
+
525
+ char_codes_a = [
526
+ 109, 103, 105, 109, 103, 116, 113, 55 # "mgiimgtq7"
527
+ ]
528
+ part2 = ''.join(chr(c) for c in char_codes_a)
529
+
530
+ part3 = base64.b64encode(b"EoXvw").decode() # btoa("mgiimgtq7")
531
+
532
+ return part1 + part2 + part3
533
+
534
+ def get_password(self) -> str:
535
+ """获取完整 PBKDF2 密码"""
536
+ k = self.generate_k_fragment()
537
+ a = self.generate_a_fragment()
538
+ return k + a
539
+
540
+
541
+ # ============================================================
542
+ # 5. Kasada 请求处理
543
+ # ============================================================
544
+
545
+ class KasadaClient:
546
+ """处理 Kasada bot-protection 的完整流程"""
547
+
548
+ def __init__(self):
549
+ self.session = httpx.Client(
550
+ base_url=BASE_URL,
551
+ headers={"User-Agent": USER_AGENT},
552
+ follow_redirects=True,
553
+ timeout=30.0
554
+ )
555
+ self.kp_uid = None
556
+ self.kp_uid_ssn = None
557
+ self.fp_gen = FingerprintCollector()
558
+ self.pw_gen = PasswordGenerator()
559
+
560
+ def get_fingerprint_page(self) -> str:
561
+ """
562
+ 步骤1: GET /fp 获取挑战页面
563
+ 提取 KPSDK:MC:... 消息中的 challenge token
564
+ """
565
+ url = f"/{KASADA_UUID_1}/{KASADA_UUID_2}/fp"
566
+ params = {"x-kpsdk-v": "j-1.2.308"}
567
+
568
+ resp = self.session.get(url, params=params)
569
+ resp.raise_for_status()
570
+
571
+ # 提取 challenge token
572
+ html = resp.text
573
+ import re
574
+ match = re.search(r"postMessage\('KPSDK:MC:([^']+)'", html)
575
+ if match:
576
+ challenge_token = match.group(1)
577
+ print(f"[+] Challenge token: {challenge_token[:50]}...")
578
+ return challenge_token
579
+
580
+ raise ValueError("无法提取 challenge token")
581
+
582
+ def build_tl_payload(self, challenge_token: str) -> bytes:
583
+ """
584
+ 步骤2: 构造 /tl 的二进制请求体
585
+
586
+ 结构: 版本头 + 时间戳 + 挑战响应 + 加密指纹
587
+ """
588
+ # 收集指纹
589
+ fingerprint = self.fp_gen.collect()
590
+
591
+ # 获取加密密码
592
+ password = self.pw_gen.get_password()
593
+
594
+ # 加密指纹数据
595
+ encrypted_fp = AESCBCEncryptor.encrypt(
596
+ password=password,
597
+ plaintext=json.dumps(fingerprint, separators=(',', ':'))
598
+ )
599
+
600
+ # 构造时间戳
601
+ ts = int(time.time() * 1000)
602
+
603
+ # 解析 challenge token 中的各字段
604
+ # 格式: AALsWax...:XgpV...:Rgxn...:Wwtb...:VwtX...
605
+ token_parts = challenge_token.split(':')
606
+
607
+ # 构造二进制 payload
608
+ # 注意: 实际格式是 Kasada 私有协议,以下为近似结构
609
+ payload_json = {
610
+ "st": ts,
611
+ "d": encrypted_fp,
612
+ "ct": challenge_token,
613
+ "v": "j-1.2.308"
614
+ }
615
+
616
+ # Kasada 使用自定义二进制序列化,这里用 JSON 作为近似
617
+ return json.dumps(payload_json).encode('utf-8')
618
+
619
+ def submit_token(self, challenge_token: str) -> bool:
620
+ """
621
+ 步骤3: POST /tl 提交令牌
622
+ """
623
+ url = f"/{KASADA_UUID_1}/{KASADA_UUID_2}/tl"
624
+ payload = self.build_tl_payload(challenge_token)
625
+
626
+ headers = {
627
+ "Content-Type": "application/octet-stream",
628
+ "Origin": BASE_URL,
629
+ "Referer": f"{BASE_URL}/{KASADA_UUID_1}/{KASADA_UUID_2}/fp?x-kpsdk-v=j-1.2.308",
630
+ }
631
+
632
+ # 添加已有 Cookie
633
+ if self.kp_uid:
634
+ self.session.cookies.set("KP_UIDz", self.kp_uid)
635
+ if self.kp_uid_ssn:
636
+ self.session.cookies.set("KP_UIDz-ssn", self.kp_uid_ssn)
637
+
638
+ resp = self.session.post(url, content=payload, headers=headers)
639
+
640
+ # 提取新的 KP_UIDz Cookie
641
+ if "KP_UIDz" in resp.cookies:
642
+ self.kp_uid = resp.cookies["KP_UIDz"]
643
+ print(f"[+] KP_UIDz: {self.kp_uid[:40]}...")
644
+ if "KP_UIDz-ssn" in resp.cookies:
645
+ self.kp_uid_ssn = resp.cookies["KP_UIDz-ssn"]
646
+
647
+ result = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
648
+
649
+ if result.get("reload"):
650
+ print("[+] Token accepted (reload=true)")
651
+ return True
652
+
653
+ print(f"[-] Token response: {resp.text[:100]}")
654
+ return False
655
+
656
+ def setup(self) -> bool:
657
+ """完整的 Kasada 初始化流程"""
658
+ try:
659
+ challenge_token = self.get_fingerprint_page()
660
+ return self.submit_token(challenge_token)
661
+ except Exception as e:
662
+ print(f"[-] Kasada setup failed: {e}")
663
+ return False
664
+
665
+
666
+ # ============================================================
667
+ # 6. Cursor Chat API 调用
668
+ # ============================================================
669
+
670
+ class CursorChatClient:
671
+ """调用 cursor.com/api/chat 接口"""
672
+
673
+ def __init__(self, kasada_client: KasadaClient):
674
+ self.kasada = kasada_client
675
+ self.conversation_id = self._generate_id()
676
+
677
+ @staticmethod
678
+ def _generate_id(length: int = 16) -> str:
679
+ """生成类似 'ZkU2LSyKjo9UIoTo' 的随机ID"""
680
+ chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
681
+ return ''.join(secrets.choice(chars) for _ in range(length))
682
+
683
+ def build_chat_payload(
684
+ self,
685
+ user_message: str,
686
+ context_path: str = "/docs/api",
687
+ turn_number: int = 1
688
+ ) -> dict:
689
+ """
690
+ 构造 /api/chat 请求体
691
+ 对应 #81 请求的 Body 结构
692
+ """
693
+ return {
694
+ "context": [
695
+ {
696
+ "type": "file",
697
+ "content": "",
698
+ "filePath": context_path
699
+ }
700
+ ],
701
+ "id": self.conversation_id,
702
+ "messages": [
703
+ {
704
+ "parts": [
705
+ {
706
+ "type": "text",
707
+ "text": user_message
708
+ }
709
+ ],
710
+ "id": self._generate_id(),
711
+ "role": "user"
712
+ }
713
+ ],
714
+ "trigger": "submit-message"
715
+ }
716
+
717
+ def chat(self, message: str, context_path: str = "/docs/api") -> str:
718
+ """
719
+ 发送消息并接收 SSE 响应
720
+ """
721
+ payload = self.build_chat_payload(message, context_path)
722
+
723
+ cookies = {
724
+ "generaltranslation.locale-routing-enabled": "true",
725
+ "generaltranslation.referrer-locale": "cn",
726
+ }
727
+
728
+ if self.kasada.kp_uid:
729
+ cookies["KP_UIDz"] = self.kasada.kp_uid
730
+ if self.kasada.kp_uid_ssn:
731
+ cookies["KP_UIDz-ssn"] = self.kasada.kp_uid_ssn
732
+
733
+ headers = {
734
+ "Content-Type": "application/json",
735
+ "Origin": BASE_URL,
736
+ "Referer": f"{BASE_URL}/cn{context_path}",
737
+ "User-Agent": USER_AGENT,
738
+ "Accept": "text/event-stream",
739
+ }
740
+
741
+ full_text = []
742
+
743
+ with httpx.Client(timeout=60.0) as client:
744
+ with client.stream(
745
+ "POST",
746
+ f"{BASE_URL}/api/chat",
747
+ json=payload,
748
+ headers=headers,
749
+ cookies=cookies
750
+ ) as resp:
751
+ resp.raise_for_status()
752
+
753
+ # 解析 SSE 流
754
+ for line in resp.iter_lines():
755
+ if not line.startswith("data: "):
756
+ continue
757
+
758
+ data_str = line[6:] # 去掉 "data: " 前缀
759
+
760
+ try:
761
+ data = json.loads(data_str)
762
+ except json.JSONDecodeError:
763
+ continue
764
+
765
+ event_type = data.get("type")
766
+
767
+ if event_type == "text-delta":
768
+ delta = data.get("delta", "")
769
+ full_text.append(delta)
770
+ print(delta, end="", flush=True)
771
+
772
+ elif event_type == "text-end":
773
+ print() # 换行
774
+
775
+ elif event_type == "end":
776
+ break
777
+
778
+ return ''.join(full_text)
779
+
780
+ def send_analytics_event(
781
+ self,
782
+ message: str,
783
+ page: str,
784
+ turn_number: int
785
+ ):
786
+ """
787
+ 发送 Vercel Analytics 事件 (对应 #82 请求)
788
+ 无加密,仅记录用户行为
789
+ """
790
+ payload = {
791
+ "o": f"{BASE_URL}/cn{page}",
792
+ "sv": "0.1.3",
793
+ "sdkn": "@vercel/analytics/next",
794
+ "sdkv": "1.6.1",
795
+ "ts": int(time.time() * 1000),
796
+ "dp": "/[lang]/docs/[...slug]",
797
+ "r": "",
798
+ "en": "Submit prompt",
799
+ "ed": {
800
+ "message": message,
801
+ "page": page,
802
+ "turnNumber": turn_number
803
+ }
804
+ }
805
+
806
+ cookies = {}
807
+ if self.kasada.kp_uid:
808
+ cookies["KP_UIDz"] = self.kasada.kp_uid
809
+
810
+ with httpx.Client() as client:
811
+ resp = client.post(
812
+ f"{BASE_URL}/_vercel/insights/event",
813
+ json=payload,
814
+ headers={
815
+ "Content-Type": "application/json",
816
+ "Origin": BASE_URL,
817
+ "Referer": f"{BASE_URL}/cn{page}",
818
+ },
819
+ cookies=cookies
820
+ )
821
+ return resp.text
822
+
823
+
824
+ # ============================================================
825
+ # 7. 加密验证测试
826
+ # ============================================================
827
+
828
+ def test_aes_encryption():
829
+ """验证 AES-CBC 加密/解密的正确性"""
830
+ print("\n=== AES-256-CBC 加密测试 ===")
831
+
832
+ password = "test_password_from_js"
833
+ plaintext = json.dumps({
834
+ "p": False,
835
+ "S": 0.4043074801008981,
836
+ "w": {"v": "Google Inc.", "r": "ANGLE (NVIDIA)"},
837
+ "s": "dGVzdA==",
838
+ "h": False,
839
+ "b": False,
840
+ "d": {"isOpen": False, "orientation": None}
841
+ })
842
+
843
+ # 加密
844
+ encrypted = AESCBCEncryptor.encrypt(password, plaintext)
845
+ print(f"加密结果 (Base64): {encrypted[:60]}...")
846
+ print(f"总长度: {len(base64.b64decode(encrypted))} bytes")
847
+ print(f" - salt: 16 bytes [0:16]")
848
+ print(f" - iv: 16 bytes [16:32]")
849
+ print(f" - data: {len(base64.b64decode(encrypted)) - 32} bytes [32:]")
850
+
851
+ # 解密验证
852
+ decrypted = AESCBCEncryptor.decrypt(password, encrypted)
853
+ assert json.loads(decrypted) == json.loads(plaintext), "解密验证失败!"
854
+ print(f"[✓] 解密验证通过")
855
+
856
+ return encrypted
857
+
858
+
859
+ def test_password_generation():
860
+ """测试密码生成器"""
861
+ print("\n=== 密码生成测试 ===")
862
+ pw_gen = PasswordGenerator()
863
+ password = pw_gen.get_password()
864
+ print(f"生成的密码: {password[:40]}...")
865
+ print(f"密码长度: {len(password)} chars")
866
+ return password
867
+
868
+
869
+ def test_fingerprint_collection():
870
+ """测试指纹采集"""
871
+ print("\n=== 指纹采集测试 ===")
872
+ collector = FingerprintCollector()
873
+ fp = collector.collect()
874
+ print(f"指纹数据:")
875
+ for k, v in fp.items():
876
+ print(f" {k}: {v}")
877
+ return fp
878
+
879
+
880
+ # ============================================================
881
+ # 8. 主程序入口
882
+ # ============================================================
883
+
884
+ def main():
885
+ print("=" * 60)
886
+ print("Cursor.com 加密机制复现 - 仅用于研究目的")
887
+ print("=" * 60)
888
+
889
+ # 运行单元测试
890
+ test_aes_encryption()
891
+ test_password_generation()
892
+ test_fingerprint_collection()
893
+
894
+ # 初始化 Kasada 客户端
895
+ print("\n=== Kasada 初始化流程 ===")
896
+ kasada = KasadaClient()
897
+
898
+ # 注意: 实际 Kasada 需要完整的浏览器环境执行 JS
899
+ # 这里展示流程,实际 /tl 请求体格式是私有协议
900
+ print("[!] 注意: 完整绕过需要执行 c.js 中的 PoW 计算")
901
+ print("[!] 建议使用 Playwright/Puppeteer 执行真实 JS")
902
+
903
+ # 模拟已有 Cookie 的情况
904
+ # 实际使用时替换为真实从浏览器获取的 Cookie
905
+ kasada.kp_uid = "your_kp_uid_cookie_here"
906
+
907
+ # 初始化 Chat 客户端
908
+ chat_client = CursorChatClient(kasada)
909
+
910
+ print("\n=== Chat API 调用演示 ===")
911
+ print("发送消息: 'hi'")
912
+ print("响应: ", end="")
913
+
914
+ # 注意: 需要有效的 KP_UIDz Cookie 才能成功调用
915
+ # response = chat_client.chat("hi", "/docs/api")
916
+ # print(f"\n完整响应: {response}")
917
+
918
+ print("\n[i] 请先通过浏览器获取有效的 KP_UIDz Cookie")
919
+ print("[i] 然后替换 kasada.kp_uid 的值再运行")
920
+
921
+
922
+ if __name__ == "__main__":
923
+ main()
924
+ ```
925
+
926
+ ---
927
+
928
+ ## 七、关键发现总结
929
+
930
+ ### 7.1 安全机制分层
931
+
932
+ ```
933
+ 层级 1 (最外层): Kasada Bot Protection
934
+ ├─ 采集 JavaScript 指纹 (12+项检测)
935
+ ├─ AES-256-CBC 加密指纹 + PBKDF2 密钥派生
936
+ ├─ 二进制 PoW 令牌提交
937
+ └─ 颁发 KP_UIDz Cookie
938
+
939
+ 层级 2: 业务接口鉴权
940
+ └─ 依赖 KP_UIDz Cookie (无独立签名)
941
+
942
+ 层级 3: 行为追踪
943
+ └─ Vercel Analytics (明文,无加密)
944
+ ```
945
+
946
+ ### 7.2 弱点分析
947
+
948
+ | 风险点 | 描述 | 风险级别 |
949
+ |--------|------|----------|
950
+ | 密钥半硬编码 | PBKDF2 密码嵌入 JS,仅靠混淆保护 | 中 |
951
+ | Cookie 无绑定 | KP_UIDz 无 IP/UA 强绑定 | 中 |
952
+ | 无请求签名 | /api/chat 请求体未签名 | 低 |
953
+ | 明文分析事件 | Analytics 事件可被篡改 | 低 |
954
+
955
+ > ⚠️ **免责声明**: 本分析仅用于安全研究和学习目的,请勿用于任何违反服务条款的行为。