Skip to content

Add TLS spoof support #5964

Open
Open

Description

@alipour66m

Please Add outbound TLS spoof and spoof_method fields. When enabled, a forged ClientHello carrying a whitelisted SNI is sent before the real handshake to fool SNI-filtering middleboxes. Requires CAP_NET_RAW + CAP_NET_ADMIN or root on Linux and macOS, and Administrator privileges on Windows (ARM64 is not supported). IP-literal server names are rejected.

Activity

  1. megasoheilsh commented on Apr 17, 2026

    @megasoheilsh

    Please Add outbound TLS spoof and spoof_method fields. When enabled, a forged ClientHello carrying a whitelisted SNI is sent before the real handshake to fool SNI-filtering middleboxes. Requires CAP_NET_RAW + CAP_NET_ADMIN or root on Linux and macOS, and Administrator privileges on Windows (ARM64 is not supported). IP-literal server names are rejected.

    I tested it on Linux and it works properly, but Windows doesn't have any fake packets that should have fake SNI inside, apparently there is a problem with windivert.

  2. megasoheilsh commented on Apr 17, 2026

    @megasoheilsh

    Please Add outbound TLS spoof and spoof_method fields. When enabled, a forged ClientHello carrying a whitelisted SNI is sent before the real handshake to fool SNI-filtering middleboxes. Requires CAP_NET_RAW + CAP_NET_ADMIN or root on Linux and macOS, and Administrator privileges on Windows (ARM64 is not supported). IP-literal server names are rejected.

    By the way, the idea and implementation of TLS spoof is for @patterniha , Singbox doesn't know anything else except copy paste.

  3. alipour66m commented on Apr 17, 2026

    @alipour66m
    Author

    I love Xray core.
    I tested on windows and working well with .exe from @patterniha

  4. Fangliding commented on Apr 17, 2026

    @Fangliding
    Member

    Singbox doesn't know anything else except copy paste.

    这个东西很久之前就有人提出过了 除了无效的序列号还有短ttl等等等等 俗称desync....

    至于这个功能本身我觉得现在这样放在一个单独的程序上就好 反正也没法跨平台 而且封禁并不难

  5. mehdi-saber commented on Apr 17, 2026

    @mehdi-saber

    @Fangliding
    Have you considered this might actually work in Iran, given the DPI is SNI-based, not sequence-aware?

  6. RPRX commented on Apr 17, 2026

    @RPRX
    Member

    给 finalmask 的 header-custom 加个 TTL 不就行了 @LjhAUMEM

  7. reopened this on Apr 17, 2026
  8. LjhAUMEM commented on Apr 18, 2026

    @LjhAUMEM
    Collaborator

    给的链接怎么都是 singbox,不如直接去用 singbox

    给 finalmask 的 header-custom 加个 TTL 不就行了 @LjhAUMEM

    最近开始忙了,不想研究 new feature 了,之前说的 xicmp 多 ip 可能也要推迟,xicmp 谁有兴趣重构也欢迎 pr

  9. megasoheilsh commented on Apr 18, 2026

    @megasoheilsh

    给 finalmask 的 header-custom 加个 TTL 不就行了 @LjhAUMEM

    我尝试了一个,但遇到了错误:tls: server did not echo the legacy session ID。

    "finalmask": {
    "tcp": [
    {
    "type": "header-custom",
    "settings": {
    "clients": [
    [
    {
    "delay": 0,
    "type": "hex",
    "packet": "16030107a8010007a403034852c52cc41358f9decaf36064e503cabc3be22bdbc3df0e82b2079b53689f4420c5dfa397eb9281864b71bfd4b0056fe273e6bc9a4678075509969a555432f19900208a8a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f00350100073b1a1a0000fe0d00ba000001000183002066ed8d3f6bc3d31f8f2d36764e8eb25767b72279d68ec6095daf37225066275900904c4f63288c9f11b26e2007b143a6cf5760752e6467deb231dee035342ebdddf54386fd62b178c8008740250d28fc7fdeb63d8a02b07ffc126c33a176f5bd41e96e70e608c8a0547a1a07d6ba67f19622437fa41ff4c3021a4d1ba89175415bfcbb22971fd7708f0020bbdd42b718998b554dd906abe19593f36f6a644b098e0919083e4e29fabd51935f77c2fb87dd110023000000120000000b00020100003304ef04ed9a9a00010011ec04c0c646357c8a8239d01cdeaa5e68f465357656222a732fcb8f60f3051de2b3f51b4c6dd55b610ca8de651a5507040302a950c67f23f50587102f6096889d52a6c2218d4c00336859c0a9e3656c6c63bff3273c37aac28bae54252c73f4149622ad5fbb94dcb065c986a892178956fcc705fab2af006b44ea4fe74a0a42bc736fdb1a869abcb7a92ec7a46393b61f90f84783c21b2b5c1a33bb7de0775627e9ba235466367580f37a552075c3fd027549f18dbd084154e45cda4764ea30c59e8420bcd20fa1973ae5e21d13b7226a118f18fa78f6616e05fa49de7a95dcc78de4a492754a0648a598ed8542646ab81130b035c7afaa2c6245806e40a1c01a8253c428284f1a10ce8456e65ba49f39c678f62de3f1c9b5c3bcfb116a07da46c69267aa904a92c8ab5cda6379593596a97a9226249f0712a6445402e3380bd20ade6a26aed43770364bb2a3113e5bc641195511c1c1cc404a90e11af39127f4d365c6c26cf2710e8f05be2a2c41489394712b19af045dbd329c6ca07ad75834abc02444184819345b09d04ce0ea847c571e5011a09a057d4266b5bfac1ba1a626e19b5e996b4a6c577129bc1ae54742e28bc4b703a20b257284d02785e5b64b095b77b8a51faac728d97efe49a3665629b92053a212351e358ebacb3be0d4966b712e75a50d0ff8b63c96bdd2054901969088b97c2a0177b10b787d29c2c793cb81126f8a7aa0161707b34aac6dfb64f401d0d9941798654b47637c6fac86a0b40434db2f08524433f9bd7703c0ba24246b0343dd882460c3c3a09c784af90db9337f570375480710b8f2404e9981bb42b0faa054204b391f3a399161a0bb4563493361e5d81d5899a1cb9549bcb459951ab681b56c2845af1df93c78bb16b676c56b5517b9516590d02b7dc89c7735cb1948bf203629a760345d2c945ca44a0be153069c8e8a7995f2a5ab8e4aa27994957113ba7ba14d8f35657e8461eb58850277b44686c4efb5ad7508278be08b568912bc1bb0eb27a0cefb979b3633bb669b5d5518d2a062f554a2cb7599eee6a4da7a44aff04411a6c8e3ea9b06a36f60f06f1b85630637be882c17abb2afae0104bfd710eacc4690d041e4c62e23c9c6b8dbcc34a361712c00342c830dc80c99bb2c571c166fd198e3c304bfe04352e4852d9b8e6936115e942099776c4d3a8ca3154b5d6b1caeb3031f632f2cdbbe3ddb2215c31488d0099b3255da003aa9fb77be511fbde6bc4466690539305ef677b056cc1947c5828ac34410c5f01380f7530c46c964cbb73f4d748b8425a3826698ae04174d9c4baaa5802a128a07844889e6cfef541b59f47cfc8247f893bb8a65a3ec468c8604c1c68199235209e80ba52723572cc4b227bc2968348483c6ba3095bcf315597fe5a31164a88fe764dea1621e36c81d598eafa14f58912e32989f90a9c35b64c64bb362ccf397489a6be531b739f4ca53c0c57fc07eafd402f0121abac464c5eb46bb320fb6194b6b3a667e866f66b7882d37b96f720746c3be0cb1353c7ab0a51575754638f014b7aa841a9243ad5a05a0c4c1b0f0583e340499c2605349999eb067203e06c9a04816f2787f64e0c3c8950107f1b71dc607434674ae4bfd86cccadbc705f534626b1bce7683358283382decfe74601340af23f0a2efc52fbdb007068767c5249f9e6facd2d11c8bb747deb47b734a8b9cce185d6c001d0020e34b7905cba69fa4e6f96948878d743b3d6fa967f1fbdda3f043372e04b7bf1c0017000044cd00050003026832002b0007062a2a03040303000a000c000a9a9a11ec001d00170018001b00030200020000001500130000107777772e68636170746368612e636f6d000500050100000000000d0012001004030804040105030805050108060601002d000201010010000e000c02683208687474702f312e31ff010001003a3a000100002900eb00c600c0b8f57aa277c0ccd113cfe97f12b201b7c20613644c49cdb55e803bbfff51bdf92d24f13494bcb4e4ec9af5f3d22848eae3891e460b0d55b502b6936e6f9b83101d3188478771dbd7cc00c3bc1ac815dfddf6f4b8e9a781122bebe91f5bbbbc903d93e5b7a720cd49a52bf1acf1437b835545a7549f28ed95e62910e35e9e32445f6f5897b0a2199d74f85f0f0b3013a7656cf7945a87c5138f141f71f583a21845dd9cd88998c74651d63ff6134fe7e4b226a2481d39c046abcc46ed43a1e4b0b0c0d84d0021208c2b59c00b837765fdd4200df298959dc3c1d94bc96281fcf5d660608a975aa6"
    }
    ]
    ],
    "servers": [[]],
    "errors": [[]]
    }
    }
    ]
    }

  10. RPRX commented on Apr 18, 2026

    @RPRX
    Member

    @megasoheilsh 首先这需要分包发送,其次第一个 CH 的 TTL 要设短些,只给 GFW 看却到不了 CDN(目前 Xray 还没有相关代码)

  11. montiego commented on Apr 18, 2026

    @montiego
  12. LjhAUMEM commented on Apr 18, 2026

    @LjhAUMEM
    Collaborator

    想了一下好像也不是 fm 能完成的,伪造 ip 包需要 raw socket,raw socket 需要 root,而 fm 并不负责 dial

    off-topic

    header-custom 现在污染的没眼看,本来 headerConn 就是用来判断使用独立 conn 还是 one shot serialize,多余判断一层 headerConnMode,既然打算起独立 conn 就新开个配置倒是

    type headerConn interface {
    HeaderConn()
    }
    type headerConnMode interface {
    UseHeaderConn() bool
    }

    而且封禁并不难

    赞同,先不说 server hello,多检查一遍 client hello 不就行了

  13. added
    PR welcomeUnplanned idea, but welcome anyone to open PR for implementation
    on Apr 25, 2026
  14. farshadl commented on May 24, 2026

    @farshadl

    This would be very useful for Iran / other censored regions

  15. therealtunge commented on May 26, 2026

    @therealtunge

    this could be implemented as "serverless"
    the way that people in iran are using TLS spoofing is as a fake server (not a proxy!)
    then pointing their VLESS configs at the spoofer
    when the spoofer recieves packets, it connects to a big cdn that they cant block (with the spoofed SNI) and forwards the packets

  16. rezakhosh78 commented on Aug 10, 2026

    @rezakhosh78

    I have a working PoC that may be useful for this proposal:

    https://github.com/rezakhosh78/BlackHole

    BlackHole keeps the real Xray TLS/SNI/transport configuration untouched and applies an external DPI-desync layer using winws2/Zapret2 + WinDivert.

    The interesting part is that, in testing, simple TLS fragmentation is not always sufficient, while combinations of the following techniques can make a significant difference:

    • TLS ClientHello split / multisplit
    • multidisorder
    • semantic split positions such as midsld and sniext+1
    • fake ClientHello / fake SNI decoys
    • TCP badseq
    • TTL / AutoTTL
    • badsum
    • md5sig
    • repeated fake packets

    I think this is closely related to #5964 and #5969.

    The main feature I would like to propose is not bundling winws2 into Xray, but introducing an optional native desync abstraction in Xray-core.

    For example:

    {
      "streamSettings": {
        "sockopt": {
          "desync": {
            "mode": "multidisorder",
            "splitPos": ["1", "sniext+1", "midsld"],
            "fake": {
              "sni": "example.org",
              "fooling": "badseq",
              "badSeqIncrement": -10000,
              "repeats": 1
            }
          }
        }
      }
    }

    Internally, different primitives could use different implementations:

    • TLS record / ClientHello splitting → pure Go
    • fake ClientHello generation → pure Go where possible
    • TTL manipulation → socket options
    • TCP sequence/disorder tricks → platform-specific backend
    • Windows → WinDivert-compatible backend if necessary
    • Linux → raw socket / NFQUEUE or another appropriate backend

    The advantage of implementing this inside Xray is that desync could be attached to a specific outbound/connection, instead of intercepting traffic globally.

    It would also allow Xray to keep the current lightweight fragment implementation while exposing more advanced techniques only when explicitly enabled.

    BlackHole can be used as a practical testbed/reference for configurations that currently require Xray + an external desync engine.

    If maintainers are interested in this direction, I can also document which individual primitives/combinations have been effective and map them into a minimal Xray-side API

  17. thatssoheil commented on Oct 5, 2026

    @thatssoheil
    Contributor

    Proposal: a client-side fake-segment finalmask.tcp mask ("tls spoof")

    A concrete spec for the narrow piece, following the guidance already given on #6103 ("add as a tcpmask", generic naming, base64 payloads). Happy to implement on current main if there is appetite.

    Mechanism (verified against sing-box common/tlsspoof @ 2ff3985c)

    Before the real ClientHello, send one hand-built TCP/IP segment carrying a fake ClientHello for a whitelisted SNI on the same 4-tuple, via raw socket (WinDivert on Windows). The segment is deliberately invalid so the origin server drops it, while a passive SNI filter still reads it:

    • wrong-sequence (default): seq = snd_nxt - len(fake) -> ends at the peer's rcv_nxt -> old duplicate, discarded
    • wrong-checksum: TCP checksum inverted (raw frames bypass offload)
    • wrong-ack: ack = rcv_nxt - 32767 -> not acceptable -> dropped
    • wrong-md5: zeroed TCP-MD5 option -> dropped per RFC 2385
    • wrong-timestamp: TSval backdated 1h -> PAWS discards (Linux/Windows)

    snd_nxt/rcv_nxt: TCP_REPAIR + TCP_QUEUE_SEQ (Linux), pcblist_n (macOS), captured packet (Windows). The fake CH is generated with crypto/tls, classical curves pinned so it stays under one MSS. Kernel TCP state is never touched; the real handshake proceeds unchanged.

    Placement

    Client-only finalmask.tcp mask. FinalMask.DialTCP already wraps the raw conn before TLS/REALITY in tcp/ws/grpc/xhttp/httpupgrade, so the first Write is the real ClientHello: no dialer changes, no tlsSettings coupling, server role no-op.

    Config sketch

    "finalmask": { "tcp": [ { "type": "<name>", "settings": {
      "sni": "www.bing.com", "payload": "", "payloadType": "base64",
      "method": "wrong-sequence", "ttl": 64, "count": 1 } } ] }

    payload = arbitrary fake bytes; sni auto-generates a CH (wins if both set); neither -> no-op. Name left to maintainers.

    Platforms / privileges

    Linux: CAP_NET_RAW + CAP_NET_ADMIN, IPv4/IPv6. macOS: root, no wrong-timestamp. Windows x64/x86: Administrator + WinDivert (ARM64 unsupported). Elsewhere: warn + no-op. Fails open (log + continue) when caps/driver missing - best-effort by design.

    Testing

    Unit: frame-builder goldens per method x IPv4/IPv6; fake CH under one MSS. Integration (root-gated, tcpdump): fake and real SNI both observed on the wire, server receives the real CH unchanged; full method matrix. Docs under finalmask.

    Notes

    • Credits to @codewithtamim: TLS : tls spoofing #6103 pioneered this and carries a similar port (draft since June, needs rebase; bundled with a larger rawpacket transport). This spec is meant to be the narrow, reviewable piece - happy to coordinate so nothing duplicates, or fold into TLS : tls spoofing #6103's work instead.
    • Iranian reports in TLS : tls spoofing #6103 showed detection on some operators; the ttl/count knobs and method choice (plus stacking with the existing fragment mask) target exactly that, and I can test from inside Iran.
    • Implementation would be written fresh from the technique (MIT-licensed reference) so provenance is clean for MPL-2.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    PR welcomeUnplanned idea, but welcome anyone to open PR for implementation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions