← 返回博客

·

外挂能看到或预测未发的牌吗?从源码说起

沿默认德扑的发牌、消息过滤和随机数链路检查,区分局中透视、提前预测与局后复算,并说明设备与服务器失守时的边界。

外挂能提前知道牌吗?

按这次检查的 BluffKing 默认德州扑克密封发牌源码,没有发现普通玩家仅靠修改客户端、查看浏览器内存或抓取自己的网络消息,就能在局中读到别人未公开底牌、或提前读出后续公共牌的路径;也没有发现可用公开信息实际预测牌序的弱随机数入口。这不是“外挂绝对不可能存在”的保证,而是对具体代码路径的结论。

先把“未发的底牌”分清:别人已经拿到但你看不见的两张牌,是未公开底牌;还没翻出的转牌、河牌以及尚在牌堆中的牌,是未发出的牌。两者都不能因为前端换了一个显示方式就凭空出现。另一种情况必须单独说明:本手结束后,默认承诺—揭示机制会让本手实际参赛者重建整副牌,包括弃牌和未发出的剩余牌。那是局后复算,不是局中透视。

本文依据 2026 年 10 月 22 日源码快照 f2b2c13e28c8f9704011a011606b082419e0230f,重点检查默认德州扑克发牌链路,不将结论推广到全部游戏或所有加密模式。这是源码审查,不是对线上部署版本的核验,也不是一次完整渗透测试。

抓包和改客户端,能看到什么?

如果一个游戏把所有人的牌都发给浏览器,只靠画一张牌背来隐藏,外挂确实可以读出来。所以关键是服务端发了什么,而不是界面画了什么。

这里默认密封发牌在独立发牌进程中生成完整牌堆。游戏连接给本手对应玩家发送座位取牌票据,发牌连接按票据返回该座位的底牌。票据来自 32 字节操作系统随机数,不是修改一个座位编号就能拿到别人的牌。票据是持有即可取牌的凭证,仍然必须保密;偷到另一位玩家的有效票据或控制其设备,超出了“只修改自己的客户端”的前提。

游戏 WebSocket 的底牌消息按观看者座位过滤;其他座位的牌值为 None,观战者连私牌消息都不接收。重连快照也按座位遮蔽底牌;公共牌事件只携带本次已经揭示的牌。默认密封发牌还让正常局中游戏状态不持有完整明文牌堆。源码依据见文末 [1]、[2]。

因此,在这些路径下,把牌背改成牌面、读取自己的浏览器内存或反复重连,不会产生原本没有收到的牌。恶意软件能读取你设备上已经收到的自己的牌,这可能造成信息泄露或串通,但不等于能读到服务端尚未发送的牌。

知道洗牌算法,能预测下一张吗?

算法是公开的,秘密是每手牌的随机种子。实际调用链是:

  1. 发牌进程为新手牌生成 32 字节的 server_seed,来源是 OsRng;开局只发布其带域分离的 SHA-256 承诺。
  2. 将服务端种子、按座位排序的已收集客户端种子和本手 hand_id 一起哈希,得到 deck_seed。
  3. 用完整的 32 字节 deck_seed 初始化 ChaCha20Rng,再用 Fisher–Yates 洗牌;选取交换位置时使用拒绝采样避免取模偏差。

这不是用当前时间、房间号或上一手结果当随机种子。玩家知道自己的客户端种子和手牌编号,仍缺少局中未公开的服务端种子。看到种子哈希,也不等于拿到种子。在操作系统熵源正常、秘密未泄露且所用密码算法安全的前提下,这条链路没有提供实际可行的提前预测方法。[3]–[5]

固定种子可以重放同一副牌,这是公平性验证需要的能力;“可重放”不代表玩家开局就知道种子。牌序生成后固定,也不意味着能够从历史输赢或最近出现的牌推算下一张。算牌软件可以估计胜率、范围和概率,但概率不是这一手实际牌序。

局后能重建整副牌,这一点不能省略

正常完成路径先调用 finish_blind() 检查手牌已经结束,再取回用于结算与验证的完整牌堆。随后 SeedReveal 把服务端种子、客户端种子和牌堆种子交给本手实际获发底牌的玩家;公平性导出接口也检查实际参赛身份。旁观者或后来加入同一桌的人不因此获得这些种子。[6]、[7]

有了这些种子,本手参赛者就能复算所有位置,包括其他人的弃牌、未亮出的底牌和没有发出的剩余牌。即使历史界面仍把某些牌画成牌背,也不能将其理解为对本手参赛者永久保密。这个隐私边界是当前默认验证机制的真实行为。

局后种子用于核对这一手是否符合开局承诺,不会直接给出下一手的新随机种子。它也不能单独证明局中从未有人偷看、玩家没有串通,或服务器从未被入侵。验证方法见如何独立验证一手牌。

哪些情况仍可能让人知道隐藏牌?

  • 控制其他玩家的设备或窃取取牌凭证:攻击者可能获得该玩家已经收到或有权领取的底牌。
  • 多人串通:参与者互相交换自己的底牌,会改变他们掌握的信息,但不是破解随机数。
  • 游戏进程、发牌进程或主机权限被攻破:完整牌堆确实存在于发牌进程内存中。内部调用有令牌与签名检查,但源码明确说明不防御已被控制的游戏进程或主机管理员。[8]
  • 尚未发现的漏洞或部署偏差:源码审查不能排除所有越权、凭证泄露和线上配置问题,也不能替代对实际运行版本的检查。

默认密封发牌提供的是明确的进程与消息边界,不能宣传成“运营方在任何情况下都无法知道牌”。高级加密发牌采用另一套协议和信任假设,不能只因为仓库存在 mental-poker 目录,就把它的保证套到所有牌桌上;两者区别见德扑发牌公平与隐私。

所以,对“外挂能不能看到或预测未发的牌”的准确回答是:普通玩家仅凭自己的客户端和公开信息,在这次审查的默认链路中没有找到可行途径;局后参赛者可以复算,而设备、凭证或服务器失守属于另一种攻击条件。

对应源码

  1. server/src/sealed_dealer.rs — SealedHand::new_committed / new_with_rng
  2. server/src/protocol/translation.rs — translate_for
  3. server/src/pf_dealing.rs — os_random_32 / seed_commit / derive_deck_seed
  4. engine/src/rng.rs — PokerRng::from_seed_bytes
  5. engine/src/deck.rs — Deck::new / rng_range
  6. server/src/sealed_dealer.rs — finish_and_settle
  7. server/src/session/fair_dealing.rs — pf_emit_reveal
  8. server/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).