This commit is contained in:
dela
2026-01-26 15:04:02 +08:00
commit 4813449f9c
31 changed files with 8439 additions and 0 deletions

11
reference/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Reference 模块 - Sentinel 解决方案
包含 Sentinel Token 生成的完整实现
"""
from .sentinel_solver import SentinelSolver
from .js_executor import JSExecutor
from .pow_solver import ProofOfWorkSolver
__all__ = ["SentinelSolver", "JSExecutor", "ProofOfWorkSolver"]

14
reference/config.py Normal file
View File

@@ -0,0 +1,14 @@
"""
Reference 模块配置文件
供 Sentinel 解决器使用的配置项
"""
# 调试模式
DEBUG = False
# SDK JS 文件路径
SDK_JS_PATH = "/home/carry/myprj/gptAutoPlus/sdk/sdk.js"
# 导出
__all__ = ["DEBUG", "SDK_JS_PATH"]

167
reference/fingerprint.py Normal file
View File

@@ -0,0 +1,167 @@
# modules/fingerprint.py
"""浏览器指纹生成器"""
import uuid
import random
import time
from datetime import datetime
from typing import Dict, List, Any
from config import FINGERPRINT_CONFIG
class BrowserFingerprint:
"""生成符合 SDK 期望的浏览器指纹"""
def __init__(self, session_id: str = None):
self.session_id = session_id or str(uuid.uuid4())
# 新增: 使用确定性方法从 session_id 派生 Stripe 指纹
import hashlib
seed = hashlib.sha256(self.session_id.encode()).hexdigest()
# seed 是64个hex字符我们需要确保切片正确
# 从 seed 生成一致的 guid/muid/sid
# UUID需要32个hex字符去掉连字符额外部分直接拼接
self.stripe_guid = seed[:8] + '-' + seed[8:12] + '-' + seed[12:16] + '-' + seed[16:20] + '-' + seed[20:32] + seed[32:40]
self.stripe_muid = seed[:8] + '-' + seed[8:12] + '-' + seed[12:16] + '-' + seed[16:20] + '-' + seed[20:32] + seed[40:46]
self.stripe_sid = seed[:8] + '-' + seed[8:12] + '-' + seed[12:16] + '-' + seed[16:20] + '-' + seed[20:32] + seed[46:52]
self.user_agent = FINGERPRINT_CONFIG['user_agent']
self.screen_width = FINGERPRINT_CONFIG['screen_width']
self.screen_height = FINGERPRINT_CONFIG['screen_height']
self.languages = FINGERPRINT_CONFIG['languages']
self.hardware_concurrency = FINGERPRINT_CONFIG['hardware_concurrency']
def get_config_array(self) -> List[Any]:
"""
生成 SDK getConfig() 函数返回的 18 元素数组
对应 SDK 源码:
[0]: screen.width + screen.height
[1]: new Date().toString()
[2]: performance.memory.jsHeapSizeLimit (可选)
[3]: nonce (PoW 填充)
[4]: navigator.userAgent
[5]: 随机 script.src
[6]: build ID
[7]: navigator.language
[8]: navigator.languages.join(',')
[9]: 运行时间 (PoW 填充)
[10]: 随机 navigator 属性
[11]: 随机 document key
[12]: 随机 window key
[13]: performance.now()
[14]: session UUID
[15]: URL search params
[16]: navigator.hardwareConcurrency
[17]: performance.timeOrigin
"""
# 模拟的 script sources
fake_scripts = [
"https://sentinel.openai.com/sentinel/97790f37/sdk.js",
"https://chatgpt.com/static/js/main.abc123.js",
"https://cdn.oaistatic.com/_next/static/chunks/main.js",
]
# 模拟的 navigator 属性名
navigator_props = [
'hardwareConcurrency', 'language', 'languages',
'platform', 'userAgent', 'vendor'
]
# 模拟的 document keys
document_keys = ['body', 'head', 'documentElement', 'scripts']
# 模拟的 window keys
window_keys = ['performance', 'navigator', 'document', 'location']
current_time = time.time() * 1000
return [
self.screen_width + self.screen_height, # [0]
str(datetime.now()), # [1]
None, # [2] memory
None, # [3] nonce (placeholder)
self.user_agent, # [4]
random.choice(fake_scripts), # [5]
"97790f37", # [6] build ID
self.languages[0], # [7]
",".join(self.languages), # [8]
None, # [9] runtime (placeholder)
f"{random.choice(navigator_props)}{random.randint(1, 16)}", # [10]
random.choice(document_keys), # [11]
random.choice(window_keys), # [12]
current_time, # [13]
self.session_id, # [14]
"", # [15] URL params
self.hardware_concurrency, # [16]
current_time - random.uniform(100, 1000), # [17] timeOrigin
]
def get_cookies(self) -> Dict[str, str]:
"""生成初始 cookies"""
return {
'oai-did': self.session_id,
}
def get_stripe_fingerprint(self) -> Dict[str, str]:
"""获取 Stripe 支付指纹(与 session_id 一致派生)"""
return {
'guid': self.stripe_guid,
'muid': self.stripe_muid,
'sid': self.stripe_sid,
}
def get_headers(self, with_sentinel: str = None, host: str = 'auth.openai.com') -> Dict[str, str]:
"""生成 HTTP headers支持多域名"""
# 基础 headers
headers = {
'User-Agent': self.user_agent,
'Accept': 'application/json',
'Accept-Language': f"{self.languages[0]},{self.languages[1]};q=0.5",
# Note: urllib3/requests only auto-decompress brotli/zstd when optional
# deps are installed; avoid advertising unsupported encodings.
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Priority': 'u=1, i',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
}
# 根据域名设置特定 headers
if 'chatgpt.com' in host:
headers.update({
'Origin': 'https://chatgpt.com',
'Referer': 'https://chatgpt.com/',
})
else:
headers.update({
'Origin': 'https://auth.openai.com',
'Referer': 'https://auth.openai.com/create-account/password',
'Content-Type': 'application/json',
})
# Sentinel token
if with_sentinel:
headers['openai-sentinel-token'] = with_sentinel
# Datadog RUM tracing
trace_id = random.randint(10**18, 10**19 - 1)
parent_id = random.randint(10**18, 10**19 - 1)
headers.update({
'traceparent': f'00-0000000000000000{trace_id:016x}-{parent_id:016x}-01',
'tracestate': 'dd=s:1;o:rum',
'x-datadog-origin': 'rum',
'x-datadog-parent-id': str(parent_id),
'x-datadog-sampling-priority': '1',
'x-datadog-trace-id': str(trace_id),
})
return headers

245
reference/js_executor.py Normal file
View File

@@ -0,0 +1,245 @@
"""JavaScript 执行引擎封装
说明:
- OpenAI 的 `assets/sdk.js` 是浏览器脚本,包含多段 anti-debug 代码与 DOM 初始化逻辑。
- 直接用 PyExecJS/ExecJS 在 Node 环境执行时常见表现是「compile 成功但 call 卡死」。
- 这里改为通过 `node -` 运行一段自包含脚本:先做轻量净化 + browser shim再执行目标函数并强制输出结果。
"""
from __future__ import annotations
import json
import re
import subprocess
from pathlib import Path
from typing import Any, Dict, Optional
from reference.config import DEBUG, SDK_JS_PATH
class JSExecutor:
"""通过 Node.js 执行 Sentinel SDK 内部逻辑(支持 async Turnstile VM"""
def __init__(self):
self._sdk_code: str = ""
self._load_sdk()
def _load_sdk(self) -> None:
sdk_path = Path(SDK_JS_PATH)
if not sdk_path.exists():
raise FileNotFoundError(f"SDK not found at {SDK_JS_PATH}")
sdk_code = sdk_path.read_text(encoding="utf-8")
sdk_code = self._sanitize_sdk(sdk_code)
sdk_code = self._inject_internal_exports(sdk_code)
self._sdk_code = sdk_code
if DEBUG:
print("[JSExecutor] SDK loaded successfully (sanitized)")
def _sanitize_sdk(self, sdk_code: str) -> str:
"""移除会在 Node 环境中导致卡死/超慢的 anti-debug 片段。"""
# 1) 删除少量已知的顶层 anti-debug 直接调用(独占一行)
sdk_code = re.sub(r"(?m)^\s*[rugkU]\(\);\s*$", "", sdk_code)
sdk_code = re.sub(r"(?m)^\s*o\(\);\s*$", "", sdk_code)
# 2) 删除 `Pt(),` 这种逗号表达式里的 anti-debug 调用(避免语法破坏)
sdk_code = re.sub(r"\bPt\(\),\s*", "", sdk_code)
sdk_code = re.sub(r"\bPt\(\);\s*", "", sdk_code)
# 3) 删除 class 字段初始化里的 anti-debug 调用:`return n(), "" + Math.random();`
sdk_code = re.sub(
r'return\s+n\(\),\s*""\s*\+\s*Math\.random\(\)\s*;',
'return "" + Math.random();',
sdk_code,
)
# 4) 删除类似 `if ((e(), cond))` 的逗号 anti-debug 调用(保留 cond
# 仅处理极短标识符,避免误伤正常逻辑;保留 Turnstile VM 的 `vt()`。
def _strip_comma_call(match: re.Match[str]) -> str:
fn = match.group(1)
if fn == "vt":
return match.group(0)
return "("
sdk_code = re.sub(
r"\(\s*([A-Za-z_$][A-Za-z0-9_$]{0,2})\(\)\s*,",
_strip_comma_call,
sdk_code,
)
return sdk_code
def _inject_internal_exports(self, sdk_code: str) -> str:
"""把 SDK 内部对象导出到 `SentinelSDK` 上,便于在外部调用。"""
# SDK 末尾一般是:
# (t.init = un),
# (t.token = an),
# t
# );
pattern = re.compile(
r"\(\s*t\.init\s*=\s*un\s*\)\s*,\s*\(\s*t\.token\s*=\s*an\s*\)\s*,\s*t\s*\)",
re.MULTILINE,
)
replacement = (
"(t.init = un),"
"(t.token = an),"
"(t.__O = O),"
"(t.__P = P),"
"(t.__bt = bt),"
"(t.__kt = kt),"
"(t.__Kt = Kt),"
"t)"
)
new_code, n = pattern.subn(replacement, sdk_code, count=1)
if n != 1:
raise RuntimeError("Failed to patch SDK exports; SDK format may have changed.")
return new_code
def _node_script(self, payload: Dict[str, Any], entry: str) -> str:
payload_json = json.dumps(payload, ensure_ascii=False)
shim = r"""
// --- minimal browser shims for Node ---
if (typeof globalThis.window !== "object") globalThis.window = globalThis;
if (!window.top) window.top = window;
if (!window.location) window.location = { href: "https://auth.openai.com/create-account/password", search: "", pathname: "/create-account/password", origin: "https://auth.openai.com" };
if (!window.addEventListener) window.addEventListener = function(){};
if (!window.removeEventListener) window.removeEventListener = function(){};
if (!window.postMessage) window.postMessage = function(){};
if (!window.__sentinel_token_pending) window.__sentinel_token_pending = [];
if (!window.__sentinel_init_pending) window.__sentinel_init_pending = [];
if (typeof globalThis.document !== "object") globalThis.document = {};
if (!document.scripts) document.scripts = [];
if (!document.cookie) document.cookie = "";
if (!document.documentElement) document.documentElement = { getAttribute: () => null };
if (!document.currentScript) document.currentScript = null;
if (!document.body) document.body = { appendChild: function(){}, getAttribute: () => null };
if (!document.createElement) document.createElement = function(tag){
return {
tagName: String(tag||"").toUpperCase(),
style: {},
setAttribute: function(){},
getAttribute: function(){ return null; },
addEventListener: function(){},
removeEventListener: function(){},
src: "",
contentWindow: { postMessage: function(){}, addEventListener: function(){}, removeEventListener: function(){} },
};
};
if (typeof globalThis.navigator !== "object") globalThis.navigator = { userAgent: "ua", language: "en-US", languages: ["en-US","en"], hardwareConcurrency: 8 };
if (typeof globalThis.screen !== "object") globalThis.screen = { width: 1920, height: 1080 };
if (typeof globalThis.btoa !== "function") globalThis.btoa = (str) => Buffer.from(str, "binary").toString("base64");
if (typeof globalThis.atob !== "function") globalThis.atob = (b64) => Buffer.from(b64, "base64").toString("binary");
window.btoa = globalThis.btoa;
window.atob = globalThis.atob;
"""
wrapper = f"""
const __payload = {payload_json};
function __makeSolver(configArray) {{
const solver = new SentinelSDK.__O();
solver.sid = configArray?.[14];
// 强制使用 Python 传入的 configArray避免依赖真实浏览器对象
solver.getConfig = () => configArray;
return solver;
}}
async function __entry() {{
{entry}
}}
(async () => {{
try {{
const result = await __entry();
process.stdout.write(JSON.stringify({{ ok: true, result }}), () => process.exit(0));
}} catch (err) {{
const msg = (err && (err.stack || err.message)) ? (err.stack || err.message) : String(err);
process.stdout.write(JSON.stringify({{ ok: false, error: msg }}), () => process.exit(1));
}}
}})();
"""
return "\n".join([shim, self._sdk_code, wrapper])
def _run_node(self, payload: Dict[str, Any], entry: str, timeout_s: int = 30) -> Any:
script = self._node_script(payload, entry)
if DEBUG:
print("[JSExecutor] Running Node worker...")
try:
proc = subprocess.run(
["node", "-"],
input=script,
text=True,
capture_output=True,
timeout=timeout_s,
)
except FileNotFoundError as e:
raise RuntimeError("Node.js not found on PATH (required for Sentinel SDK execution).") from e
except subprocess.TimeoutExpired as e:
raise TimeoutError(f"Node worker timed out after {timeout_s}s") from e
stdout = (proc.stdout or "").strip()
if not stdout:
raise RuntimeError(f"Node worker produced no output (stderr={proc.stderr!r})")
try:
obj = json.loads(stdout)
except json.JSONDecodeError as e:
raise RuntimeError(f"Node worker returned non-JSON output: {stdout[:200]!r}") from e
if not obj.get("ok"):
raise RuntimeError(obj.get("error", "Unknown JS error"))
return obj.get("result")
def solve_pow(self, seed: str, difficulty: str, config_array: list) -> str:
if DEBUG:
print(f"[JSExecutor] Solving PoW: seed={seed[:10]}..., difficulty={difficulty}")
result = self._run_node(
{"seed": seed, "difficulty": difficulty, "configArray": config_array},
entry="return __makeSolver(__payload.configArray)._generateAnswerSync(__payload.seed, __payload.difficulty);",
timeout_s=60,
)
if DEBUG and isinstance(result, str):
print(f"[JSExecutor] PoW solved: {result[:50]}...")
return result
def generate_requirements(self, seed: str, config_array: list) -> str:
result = self._run_node(
{"seed": seed, "configArray": config_array},
entry=(
"const solver = __makeSolver(__payload.configArray);\n"
"solver.requirementsSeed = __payload.seed;\n"
"return solver._generateRequirementsTokenAnswerBlocking();"
),
timeout_s=30,
)
return result
def execute_turnstile(self, dx_bytecode: str, xor_key: str) -> str:
if DEBUG:
print("[JSExecutor] Executing Turnstile VM...")
result = self._run_node(
{"dx": dx_bytecode, "xorKey": xor_key},
entry=(
"SentinelSDK.__kt(__payload.xorKey);\n"
"return await SentinelSDK.__bt(__payload.dx);"
),
timeout_s=30,
)
if DEBUG and isinstance(result, str):
print(f"[JSExecutor] Turnstile result: {result[:50]}...")
return result

114
reference/pow_solver.py Normal file
View File

@@ -0,0 +1,114 @@
import time
import json
import base64
from typing import List
DEBUG = True
class ProofOfWorkSolver:
"""解决 OpenAI Sentinel 的 Proof of Work challenge"""
def __init__(self):
# FNV-1a 常量
self.FNV_OFFSET = 2166136261
self.FNV_PRIME = 16777619
def fnv1a_hash(self, data: str) -> str:
"""FNV-1a hash 算法"""
hash_value = self.FNV_OFFSET
for char in data:
hash_value ^= ord(char)
hash_value = (hash_value * self.FNV_PRIME) & 0xFFFFFFFF
# 额外的混合步骤(从 JS 代码复制)
hash_value ^= hash_value >> 16
hash_value = (hash_value * 2246822507) & 0xFFFFFFFF
hash_value ^= hash_value >> 13
hash_value = (hash_value * 3266489909) & 0xFFFFFFFF
hash_value ^= hash_value >> 16
# 转为 8 位十六进制字符串
return format(hash_value, '08x')
def serialize_array(self, arr: List) -> str:
"""模拟 JS 的 T() 函数JSON.stringify + Base64"""
json_str = json.dumps(arr, separators=(',', ':'))
return base64.b64encode(json_str.encode()).decode()
def build_fingerprint_array(self, nonce: int, elapsed_ms: int) -> List:
"""构建指纹数组(简化版)"""
return [
0, # [0] screen dimensions
"", # [1] timestamp
0, # [2] memory
nonce, # [3] nonce ← 关键
"", # [4] user agent
"", # [5] random element
"", # [6] script src
"", # [7] language
"", # [8] languages
elapsed_ms, # [9] elapsed time ← 关键
"", # [10] random function
"", # [11] keys
"", # [12] window keys
0, # [13] performance.now()
"", # [14] uuid
"", # [15] URL params
0, # [16] hardware concurrency
0 # [17] timeOrigin
]
def solve(self, seed: str, difficulty: str, max_iterations: int = 10000000) -> str:
"""
解决 PoW challenge
Args:
seed: Challenge seed
difficulty: 目标难度(十六进制字符串)
max_iterations: 最大尝试次数
Returns:
序列化的答案(包含 nonce
"""
if DEBUG:
print(f"[PoW] Solving challenge:")
print(f" Seed: {seed}")
print(f" Difficulty: {difficulty}")
start_time = time.time()
for nonce in range(max_iterations):
elapsed_ms = int((time.time() - start_time) * 1000)
# 构建指纹数组
fingerprint = self.build_fingerprint_array(nonce, elapsed_ms)
# 序列化
serialized = self.serialize_array(fingerprint)
# 计算 hash(seed + serialized)
hash_input = seed + serialized
hash_result = self.fnv1a_hash(hash_input)
# 检查是否满足难度要求
# 比较方式hash 的前 N 位(作为整数)<= difficulty作为整数
difficulty_len = len(difficulty)
hash_prefix = hash_result[:difficulty_len]
if hash_prefix <= difficulty:
elapsed = time.time() - start_time
if DEBUG:
print(f"[PoW] ✓ Found solution in {elapsed:.2f}s")
print(f" Nonce: {nonce}")
print(f" Hash: {hash_result}")
print(f" Serialized: {serialized[:100]}...")
# 返回 serialized + "~S" (表示成功)
return serialized + "~S"
# 每 100k 次迭代打印进度
if DEBUG and nonce > 0 and nonce % 100000 == 0:
print(f"[PoW] Tried {nonce:,} iterations...")
raise Exception(f"Failed to solve PoW after {max_iterations:,} iterations")

1129
reference/register.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
# modules/sentinel_solver.py
"""Sentinel 挑战求解器"""
import json
import uuid
from typing import Dict, Optional
from reference.js_executor import JSExecutor
from utils.fingerprint import BrowserFingerprint
from reference.config import DEBUG
class SentinelSolver:
"""协调指纹生成和 JS 执行,生成完整的 Sentinel tokens"""
def __init__(self, fingerprint: BrowserFingerprint):
self.fingerprint = fingerprint
self.js_executor = JSExecutor()
def generate_requirements_token(self) -> Dict[str, str]:
"""
生成 requirements token初始化时需要
Returns:
{'p': 'gAAAAAC...', 'id': 'uuid'}
"""
if DEBUG:
print("[Solver] Generating requirements token...")
# 生成随机 seed
req_seed = str(uuid.uuid4())
# 获取指纹配置
config_array = self.fingerprint.get_config_array()
# 调用 JS 求解
answer = self.js_executor.generate_requirements(req_seed, config_array)
token = {
'p': f'gAAAAAC{answer}',
'id': self.fingerprint.session_id,
}
if DEBUG:
print(f"[Solver] Requirements token: {token['p'][:30]}...")
return token
def solve_enforcement(self, enforcement_config: Dict) -> str:
"""
解决完整的 enforcement 挑战PoW + Turnstile
Args:
enforcement_config: 服务器返回的挑战配置
{
'proofofwork': {
'seed': '...',
'difficulty': '0003a',
'token': '...', # cached token
'turnstile': {
'dx': '...' # VM bytecode
}
}
}
Returns:
完整的 Sentinel token (JSON string)
"""
if DEBUG:
print("[Solver] Solving enforcement challenge...")
pow_data = enforcement_config.get('proofofwork', {})
# 1. 解决 PoW
seed = pow_data['seed']
difficulty = pow_data['difficulty']
config_array = self.fingerprint.get_config_array()
pow_answer = self.js_executor.solve_pow(seed, difficulty, config_array)
# 2. 执行 Turnstile如果有
turnstile_result = None
turnstile_data = pow_data.get('turnstile')
if turnstile_data and turnstile_data.get('dx'):
dx_bytecode = turnstile_data['dx']
xor_key = self.fingerprint.session_id # 通常用 session ID 作为密钥
turnstile_result = self.js_executor.execute_turnstile(dx_bytecode, xor_key)
# 3. 构建最终 token
sentinel_token = {
# enforcement token 前缀为 gAAAAABrequirements 为 gAAAAAC
'p': f'gAAAAAB{pow_answer}',
'id': self.fingerprint.session_id,
'flow': 'username_password_create',
}
# 添加可选字段
if turnstile_result:
sentinel_token['t'] = turnstile_result
if pow_data.get('token'):
sentinel_token['c'] = pow_data['token']
token_json = json.dumps(sentinel_token)
if DEBUG:
print(f"[Solver] Sentinel token generated: {token_json[:80]}...")
return token_json