
1. 为什么要在 Codex 里跑 JA3 检测脚本加密流量检测这件事绕不开一个现实95% 以上的钓鱼载荷都走 HTTPS443 端口上跑的到底是正常业务还是恶意代理光看域名和证书根本分不出来。原文 3.2.2 给出的思路是用 JA3 指纹——从 TLS Client Hello 里把版本号、cipher suites、扩展字段、椭圆曲线这些握手参数拼成一个字符串再做 MD5得到一个能标识客户端 TLS 栈特征的指纹。恶意中间人套件比如 BlackForce 这类 AiTM 代理的 TLS 栈往往有固定组合匹配威胁情报库就能在不解密的前提下预警。问题在于这段脚本本身要调试。extract_ja3里对TLSClientHello层的字段访问、extensions.get_ec_points()的返回结构、scapy 版本差异导致的属性名变化这些坑一个个踩过去很费时间。我试过用 Codex 来辅助调试但 Codex 自己的大模型请求如果走默认通道在内网探针环境里经常连不上或者超时调试节奏直接断掉。所以这篇的核心操作是先把 Codex 的模型通道切到 TaoToken让 Codex 能稳定响应再让它帮你把 JA3 提取逻辑跑通。适合谁看手里有内网流量探针、需要落地 TLS 指纹检测的安全工程师正在用 Codex 写 Python 安全脚本、但被模型请求通道卡住的开发者以及想理解 JA3 提取全流程、需要一份能直接复制运行的配置参考的人。TaoToken 在这里的角色很明确——它是一个兼容 OpenAI 接口规范的模型接入服务你把 Codex 的 Base URL 指过去Codex 的补全和对话请求就走这条通道。它不替代你的编辑器也不碰你的生产流量只是让 Codex 这个助手能稳定工作。下面从拿 Key 开始一步步把配置和脚本调通。2. TaoToken 前置拿 Key 与 Codex 通道配置2.1 创建 API Key打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并登录进入控制台。左侧菜单找到 API Keys 页面点创建新 Key。建议按用途命名比如codex-ja3-debug方便后面区分。创建后 Key 只显示一次复制下来存到本地密码管理器或者环境变量里别直接写进代码提交到仓库。控制台地址https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite2.2 Codex 的模型配置改法Codex 的模型配置通常通过环境变量或者配置文件指定 Base URL 和 API Key。核心就两个值配置项值Base URLhttps://taotoken.net/apiAPI Key你在控制台创建的那串 Key模型名按 TaoToken 文档支持的模型标识填写如果你用的是环境变量方式在 shell 里这样设export OPENAI_BASE_URLhttps://taotoken.net/api export OPENAI_API_KEYsk-你的Key如果 Codex 走的是配置文件比如~/.codex/config.toml或类似路径把base_url字段改成上面的地址。改完之后重启 Codex让它重新读取配置。注意Base URL 末尾不要多加/v1或斜杠按https://taotoken.net/api原样填。多写路径会导致请求 404。2.3 验证通道是否通了在正式调脚本之前先用一个最小请求确认 Codex 能正常拿到响应。你可以直接在 Codex 对话框里问一句「返回当前时间戳」或者用 curl 测一下curl -s https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $OPENAI_API_KEY \ -H Content-Type: application/json \ -d { model: 按文档填写的模型名, messages: [{role: user, content: ping}], max_tokens: 16 }返回里有choices字段且内容非空说明通道正常。这一步过了再让 Codex 帮你调 JA3 脚本它才能稳定地给出补全建议和报错分析。3. 可复制配置JA3 提取脚本的完整落地3.1 环境准备JA3 提取依赖 scapy 的 TLS 层解析。先装依赖pip install scapyscapy 版本建议 2.5 以上低版本对TLSClientHello的扩展字段解析不完整get_ec_points()可能直接报 AttributeError。装完确认一下python -c import scapy; print(scapy.__version__)3.2 extract_ja3 核心逻辑原文的extract_ja3思路是对的但实际跑起来有几个字段访问需要加固。下面是我调通后的版本加了空值保护和字段存在性判断from scapy.all import sniff, TLSClientHello import hashlib def extract_ja3(packet) - str: 从 TLS Client Hello 提取 JA3 指纹 if not packet.haslayer(TLSClientHello): return ch packet[TLSClientHello] # TLS 版本 tls_version str(ch.version) if ch.version is not None else # cipher suites 拼接 cipher_suites -.join(str(c) for c in ch.cipher_suites) if ch.cipher_suites else # 扩展字段类型拼接 ext_types [] ec_curves [] ec_point_fmts [] if ch.extensions: for ext in ch.extensions: ext_types.append(str(ext.type)) # 椭圆曲线扩展 type10 if ext.type 10 and hasattr(ext, groups): ec_curves [str(g) for g in ext.groups] # 点格式扩展 type11 if ext.type 11 and hasattr(ext, formats): ec_point_fmts [str(f) for f in ext.formats] extensions -.join(ext_types) curves -.join(ec_curves) point_fmt -.join(ec_point_fmts) ja3_raw f{tls_version},{cipher_suites},{extensions},{curves},{point_fmt} ja3_md5 hashlib.md5(ja3_raw.encode(utf-8)).hexdigest() return ja3_md5关键改动点原文用ch.extensions.get_ec_points()这种链式调用在 scapy 2.5 里extensions是列表不是对象没有get_ec_points方法。正确做法是遍历扩展列表按ext.type判断。type10 是 supported_groups椭圆曲线type11 是 ec_point_formats。字段名在不同 scapy 版本里可能是groups/formats或ec_points/ec_formats用hasattr兜一下更稳。3.3 恶意指纹匹配与嗅探回调# 威胁情报 JA3 指纹库实际使用时从 IOC 源加载 malicious_ja3_set { 8f8f5609d8973f93719221e951832f30, 1e45a14d98562938514a4dc58f1765e0, } def tls_packet_monitor(pkt): 443 端口流量回调匹配恶意 JA3 ja3_finger extract_ja3(pkt) if ja3_finger and ja3_finger in malicious_ja3_set: src_ip pkt[0][1].src if pkt.haslayer(IP) else unknown print(f[告警] 恶意 TLS 流量 JA3{ja3_finger} 源IP{src_ip}) if __name__ __main__: sniff(ifaceeth0, filtertcp port 443, prntls_packet_monitor, store0)iface换成你探针的实际网卡名Linux 下用ip link看Windows 下用scapy.all.get_windows_if_list()查。store0表示不把包存内存长时间跑不会爆。3.4 用 Codex 辅助调试的姿势配置好 TaoToken 通道后把上面脚本贴给 Codex让它帮你做几件事检查ext.type 10的字段访问是否匹配你本地 scapy 版本根据报错信息定位是哪个属性不存在生成一段构造 TLS Client Hello 的测试包代码不用真实抓包就能验证extract_ja3的输出。Codex 的模型对话入口在这里https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite如果你要长期用 Codex 做安全脚本开发可以考虑 Coding Plan按周期计费比单次调用更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite4. 验证请求与成功结果4.1 构造测试包验证提取逻辑不依赖真实流量先用 scapy 手工构造一个 TLS Client Hello 包验证extract_ja3能正常输出from scapy.all import IP, TCP, TLSClientHello, Raw from scapy.layers.tls.handshake import TLSClientHello as TLSCH from scapy.layers.tls.extensions import TLS_Ext_SupportedGroups, TLS_Ext_ECPointFormats # 构造一个带扩展的 Client Hello pkt IP(src10.0.0.5, dst10.0.0.1) / TCP(sport54321, dport443) / TLSCH( version0x0303, cipher_suites[0x1301, 0x1302, 0xc02b], ext[TLS_Ext_SupportedGroups(groups[0x001d, 0x0017]), TLS_Ext_ECPointFormats(formats[0])] ) finger extract_ja3(pkt) print(JA3:, finger)跑出来应该是一个 32 位十六进制字符串。如果返回空串说明haslayer(TLSClientHello)没命中检查包的层结构是不是被 scapy 解析成了别的类型。4.2 真实流量嗅探结果在探针上启动嗅探同时用浏览器访问一个 HTTPS 站点观察输出sudo python ja3_monitor.py正常情况你会看到每个 TLS 握手包被解析如果 JA3 命中恶意库就打印告警。没命中时不输出这是预期行为——脚本只报恶意指纹不打印全部流量避免日志刷屏。4.3 用 Codex 验证模型通道在 Codex 里让它解释ja3_raw字符串的拼接顺序为什么是「版本,cipher,扩展,曲线,点格式」或者让它对比两个不同浏览器的 JA3 差异。如果 Codex 能连续给出有意义的回答说明 TaoToken 通道稳定调试过程不会因为请求超时中断。5. 本篇常见错排查5.1 AttributeError: list object has no attribute get_ec_points这是最常见的报错。原因scapy 2.5 之后TLSClientHello.extensions返回的是扩展对象列表不是带get_ec_points方法的对象。解决按 3.2 节的写法遍历列表用ext.type判断扩展类型从ext.groups或ext.formats取数据。5.2 sniff 抓不到包三个排查方向网卡名写错用ip link确认权限不够Linux 下需要 root 或CAP_NET_RAW过滤器语法问题tcp port 443是对的别写成tcp.port 443。另外如果探针是旁路镜像口确认镜像流量确实打到了这个网卡。5.3 JA3 指纹和威胁情报对不上先确认你的ja3_raw拼接顺序和标准一致TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats五段用逗号分隔段内用减号。顺序错了 MD5 就完全不同。其次确认 cipher suites 和扩展字段是否按原始顺序拼接有些实现会排序标准 JA3 是不排序的。5.4 Codex 请求返回 401 或 404401 一般是 Key 没设对或者环境变量没生效echo $OPENAI_API_KEY确认一下。404 多半是 Base URL 写错了检查是不是多加了/v1或者末尾斜杠。Base URL 严格用https://taotoken.net/api。接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite5.5 脚本跑一段时间内存涨sniff的store参数默认是 1会把所有包存内存。改成store0。另外malicious_ja3_set如果从文件动态加载注意别每次回调都读文件加载一次放全局。6. 把通道和脚本一起固化下来整套流程跑通之后建议做两件事固化下来。第一把 Codex 的 Base URL 和 Key 写进项目级的.env或者配置模板别每次手动 export团队里其他人克隆下来改个 Key 就能用。第二把extract_ja3和嗅探回调拆成独立模块威胁情报库用外部 JSON 加载这样更新指纹不用改代码。如果你还在用 Codex 调其他安全脚本比如 AI 钓鱼页面静态特征扫描、中间人代理行为检测通道稳定之后这些都能一起在同一个环境里跑。Claude Code 相关的接入配置也可以参考同一套 Base URL 逻辑https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite最后提醒一句JA3 检测是前置筛查不是万能药。攻击者换一套 TLS 栈指纹就变了。它的价值在于用极低成本拦住已知的中间人套件流量给后面的深度解密和页面特征扫描争取时间。真正要覆盖 AI 加密钓鱼全链路还是得把流量指纹、页面静态特征、身份风控几层叠起来用。