外挂能看到或预测未发的牌吗?从源码说起
沿默认德扑的发牌、消息过滤和随机数链路检查,区分局中透视、提前预测与局后复算,并说明设备与服务器失守时的边界。
外挂能提前知道牌吗?
按这次检查的 BluffKing 默认德州扑克密封发牌源码,没有发现普通玩家仅靠修改客户端、查看浏览器内存或抓取自己的网络消息,就能在局中读到别人未公开底牌、或提前读出后续公共牌的路径;也没有发现可用公开信息实际预测牌序的弱随机数入口。这不是“外挂绝对不可能存在”的保证,而是对具体代码路径的结论。
先把“未发的底牌”分清:别人已经拿到但你看不见的两张牌,是未公开底牌;还没翻出的转牌、河牌以及尚在牌堆中的牌,是未发出的牌。两者都不能因为前端换了一个显示方式就凭空出现。另一种情况必须单独说明:本手结束后,默认承诺—揭示机制会让本手实际参赛者重建整副牌,包括弃牌和未发出的剩余牌。那是局后复算,不是局中透视。
本文依据 2026 年 10 月 22 日源码快照 f2b2c13e28c8f9704011a011606b082419e0230f,重点检查默认德州扑克发牌链路,不将结论推广到全部游戏或所有加密模式。这是源码审查,不是对线上部署版本的核验,也不是一次完整渗透测试。
抓包和改客户端,能看到什么?
如果一个游戏把所有人的牌都发给浏览器,只靠画一张牌背来隐藏,外挂确实可以读出来。所以关键是服务端发了什么,而不是界面画了什么。
这里默认密封发牌在独立发牌进程中生成完整牌堆。游戏连接给本手对应玩家发送座位取牌票据,发牌连接按票据返回该座位的底牌。票据来自 32 字节操作系统随机数,不是修改一个座位编号就能拿到别人的牌。票据是持有即可取牌的凭证,仍然必须保密;偷到另一位玩家的有效票据或控制其设备,超出了“只修改自己的客户端”的前提。
游戏 WebSocket 的底牌消息按观看者座位过滤;其他座位的牌值为 None,观战者连私牌消息都不接收。重连快照也按座位遮蔽底牌;公共牌事件只携带本次已经揭示的牌。默认密封发牌还让正常局中游戏状态不持有完整明文牌堆。源码依据见文末 [1]、[2]。
因此,在这些路径下,把牌背改成牌面、读取自己的浏览器内存或反复重连,不会产生原本没有收到的牌。恶意软件能读取你设备上已经收到的自己的牌,这可能造成信息泄露或串通,但不等于能读到服务端尚未发送的牌。
知道洗牌算法,能预测下一张吗?
算法是公开的,秘密是每手牌的随机种子。实际调用链是:
- 发牌进程为新手牌生成 32 字节的
server_seed,来源是OsRng;开局只发布其带域分离的 SHA-256 承诺。 - 将服务端种子、按座位排序的已收集客户端种子和本手
hand_id一起哈希,得到deck_seed。 - 用完整的 32 字节
deck_seed初始化ChaCha20Rng,再用 Fisher–Yates 洗牌;选取交换位置时使用拒绝采样避免取模偏差。
这不是用当前时间、房间号或上一手结果当随机种子。玩家知道自己的客户端种子和手牌编号,仍缺少局中未公开的服务端种子。看到种子哈希,也不等于拿到种子。在操作系统熵源正常、秘密未泄露且所用密码算法安全的前提下,这条链路没有提供实际可行的提前预测方法。[3]–[5]
固定种子可以重放同一副牌,这是公平性验证需要的能力;“可重放”不代表玩家开局就知道种子。牌序生成后固定,也不意味着能够从历史输赢或最近出现的牌推算下一张。算牌软件可以估计胜率、范围和概率,但概率不是这一手实际牌序。
局后能重建整副牌,这一点不能省略
正常完成路径先调用 finish_blind() 检查手牌已经结束,再取回用于结算与验证的完整牌堆。随后 SeedReveal 把服务端种子、客户端种子和牌堆种子交给本手实际获发底牌的玩家;公平性导出接口也检查实际参赛身份。旁观者或后来加入同一桌的人不因此获得这些种子。[6]、[7]
有了这些种子,本手参赛者就能复算所有位置,包括其他人的弃牌、未亮出的底牌和没有发出的剩余牌。即使历史界面仍把某些牌画成牌背,也不能将其理解为对本手参赛者永久保密。这个隐私边界是当前默认验证机制的真实行为。
局后种子用于核对这一手是否符合开局承诺,不会直接给出下一手的新随机种子。它也不能单独证明局中从未有人偷看、玩家没有串通,或服务器从未被入侵。验证方法见如何独立验证一手牌。
哪些情况仍可能让人知道隐藏牌?
- 控制其他玩家的设备或窃取取牌凭证:攻击者可能获得该玩家已经收到或有权领取的底牌。
- 多人串通:参与者互相交换自己的底牌,会改变他们掌握的信息,但不是破解随机数。
- 游戏进程、发牌进程或主机权限被攻破:完整牌堆确实存在于发牌进程内存中。内部调用有令牌与签名检查,但源码明确说明不防御已被控制的游戏进程或主机管理员。[8]
- 尚未发现的漏洞或部署偏差:源码审查不能排除所有越权、凭证泄露和线上配置问题,也不能替代对实际运行版本的检查。
默认密封发牌提供的是明确的进程与消息边界,不能宣传成“运营方在任何情况下都无法知道牌”。高级加密发牌采用另一套协议和信任假设,不能只因为仓库存在 mental-poker 目录,就把它的保证套到所有牌桌上;两者区别见德扑发牌公平与隐私。
所以,对“外挂能不能看到或预测未发的牌”的准确回答是:普通玩家仅凭自己的客户端和公开信息,在这次审查的默认链路中没有找到可行途径;局后参赛者可以复算,而设备、凭证或服务器失守属于另一种攻击条件。
对应源码
server/src/sealed_dealer.rs—SealedHand::new_committed / new_with_rngserver/src/protocol/translation.rs—translate_forserver/src/pf_dealing.rs—os_random_32 / seed_commit / derive_deck_seedengine/src/rng.rs—PokerRng::from_seed_bytesengine/src/deck.rs—Deck::new / rng_rangeserver/src/sealed_dealer.rs—finish_and_settleserver/src/session/fair_dealing.rs—pf_emit_revealserver/src/sealed_dealer/authorization.rs—Trust boundary
其他调用与权限检查:server/src/protocol/translation.rs (snapshot_for_user_with_equities_from); server/src/sealed_dealer.rs (sync_tickets / hole_ws); server/src/handlers/hands_fairness.rs (get_hand_fairness); server/src/session/hand_play.rs (play_hand); mental-poker/src/hash.rs (ds_hash).