From ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da Mon Sep 17 00:00:00 2001 From: RPRX <63339210+RPRX@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:51:28 +0000 Subject: [PATCH] Mainly reverse VLESS Client Hello https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3176751311 --- proxy/vless/encryption/client.go | 124 +++++++++++++++++-------------- proxy/vless/encryption/common.go | 18 ++--- proxy/vless/encryption/server.go | 122 ++++++++++++++++-------------- proxy/vless/encryption/xor.go | 2 +- 4 files changed, 143 insertions(+), 123 deletions(-) diff --git a/proxy/vless/encryption/client.go b/proxy/vless/encryption/client.go index d253eaaa..fb21e47d 100644 --- a/proxy/vless/encryption/client.go +++ b/proxy/vless/encryption/client.go @@ -20,19 +20,20 @@ import ( var ClientCipher byte func init() { - if !protocol.HasAESGCMHardwareSupport { + if protocol.HasAESGCMHardwareSupport { ClientCipher = 1 } } type ClientInstance struct { sync.RWMutex - eKeyNfs *mlkem.EncapsulationKey768 - xor uint32 - minutes time.Duration - expire time.Time - baseKey []byte - ticket []byte + nfsEKey *mlkem.EncapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + expire time.Time + baseKey []byte + ticket []byte } type ClientConn struct { @@ -48,19 +49,22 @@ type ClientConn struct { peerCache []byte } -func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData) - i.xor = xor +func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes) + if xor > 0 { + i.nfsEKeyBytes = nfsEKeyBytes + i.xor = xor + } i.minutes = minutes return } func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.eKeyNfs == nil { + if i.nfsEKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.eKeyNfs.Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ClientConn{Conn: conn} @@ -76,18 +80,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { i.RUnlock() } - nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate() - seed := make([]byte, 64) - rand.Read(seed) - dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed) - eKeyPfs := dKeyPfs.EncapsulationKey().Bytes() - padding := crypto.RandBetween(100, 1000) + pfsDKeySeed := make([]byte, 64) + rand.Read(pfsDKeySeed) + pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed) + pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes() + nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate() + paddingLen := crypto.RandBetween(100, 1000) - clientHello := make([]byte, 1088+1184+1+5+padding) - copy(clientHello, encapsulatedNfsKey) - copy(clientHello[1088:], eKeyPfs) - clientHello[2272] = ClientCipher - encodeHeader(clientHello[2273:], int(padding)) + clientHello := make([]byte, 1+1184+1088+5+paddingLen) + clientHello[0] = ClientCipher + copy(clientHello[1:], pfsEKeyBytes) + copy(clientHello[1185:], encapsulatedNfsKey) + encodeHeader(clientHello[2273:], int(paddingLen)) + rand.Read(clientHello[2278:]) if _, err := c.Conn.Write(clientHello); err != nil { return nil, err @@ -101,16 +106,16 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { encapsulatedPfsKey := peerServerHello[:1088] c.ticket = peerServerHello[1088:] - pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey) + pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey) if err != nil { return nil, err } - c.baseKey = append(nfsKey, pfsKey...) + c.baseKey = append(pfsKey, nfsKey...) authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey) - nonce := make([]byte, 12) - VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey) + hkdf.New(sha256.New, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Read(authKey) + nonce := [12]byte{ClientCipher} + VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes) if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message return nil, errors.New("invalid server").AtError() } @@ -130,33 +135,40 @@ func (c *ClientConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - var data []byte - if c.aead == nil { - c.random = make([]byte, 32) - rand.Read(c.random) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key) - c.aead = newAead(ClientCipher, key) - c.nonce = make([]byte, 12) + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in server's Read() + } + n += len(b) + var data []byte + if c.aead == nil { + c.random = make([]byte, 32) + rand.Read(c.random) + key := make([]byte, 32) + hkdf.New(sha256.New, c.baseKey, c.random, c.ticket).Read(key) + c.aead = newAead(ClientCipher, key) + c.nonce = make([]byte, 12) - data = make([]byte, 21+32+5+len(b)+16) - copy(data, c.ticket) - copy(data[21:], c.random) - encodeHeader(data[53:], len(b)+16) - c.aead.Seal(data[:58], c.nonce, b, data[53:58]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err + data = make([]byte, 21+32+5+len(b)+16) + copy(data, c.ticket) + copy(data[21:], c.random) + encodeHeader(data[53:], len(b)+16) + c.aead.Seal(data[:58], c.nonce, b, data[53:58]) + } else { + data = make([]byte, 5+len(b)+16) + encodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + } + increaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err + } } return len(b), nil } -func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() +func (c *ClientConn) Read(b []byte) (int, error) { if len(b) == 0 { return 0, nil } @@ -167,11 +179,11 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := decodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -186,7 +198,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() return 0, err } if c.random == nil { - return 0, errors.New("can not Read() first") + return 0, errors.New("empty c.random") } peerKey := make([]byte, 32) hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey) @@ -218,7 +230,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write() } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) increaseNonce(c.peerNonce) diff --git a/proxy/vless/encryption/common.go b/proxy/vless/encryption/common.go index 07edeae9..f3a5425e 100644 --- a/proxy/vless/encryption/common.go +++ b/proxy/vless/encryption/common.go @@ -28,18 +28,14 @@ func decodeHeader(b []byte) (int, error) { return 0, errors.New("invalid record's header") } -func newAead(c byte, k []byte) cipher.AEAD { - switch c { - case 0: - if block, err := aes.NewCipher(k); err == nil { - aead, _ := cipher.NewGCM(block) - return aead - } - case 1: - aead, _ := chacha20poly1305.New(k) - return aead +func newAead(c byte, k []byte) (aead cipher.AEAD) { + if c&1 == 1 { + block, _ := aes.NewCipher(k) + aead, _ = cipher.NewGCM(block) + } else { + aead, _ = chacha20poly1305.New(k) } - return nil + return } func increaseNonce(nonce []byte) { diff --git a/proxy/vless/encryption/server.go b/proxy/vless/encryption/server.go index 6507c8c0..88c3c354 100644 --- a/proxy/vless/encryption/server.go +++ b/proxy/vless/encryption/server.go @@ -25,10 +25,11 @@ type ServerSession struct { type ServerInstance struct { sync.RWMutex - dKeyNfs *mlkem.DecapsulationKey768 - xor uint32 - minutes time.Duration - sessions map[[21]byte]*ServerSession + nfsDKey *mlkem.DecapsulationKey768 + nfsEKeyBytes []byte + xor uint32 + minutes time.Duration + sessions map[[21]byte]*ServerSession } type ServerConn struct { @@ -44,9 +45,12 @@ type ServerConn struct { nonce []byte } -func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { - i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData) - i.xor = xor +func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) { + i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed) + if xor > 0 { + i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes() + i.xor = xor + } if minutes > 0 { i.minutes = minutes i.sessions = make(map[[21]byte]*ServerSession) @@ -55,9 +59,9 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat time.Sleep(time.Minute) now := time.Now() i.Lock() - for index, session := range i.sessions { + for ticket, session := range i.sessions { if now.After(session.expire) { - delete(i.sessions, index) + delete(i.sessions, ticket) } } i.Unlock() @@ -68,11 +72,11 @@ func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Durat } func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { - if i.dKeyNfs == nil { + if i.nfsDKey == nil { return nil, errors.New("uninitialized") } - if i.xor == 1 { - conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes()) + if i.xor > 0 { + conn = NewXorConn(conn, i.nfsEKeyBytes) } c := &ServerConn{Conn: conn} @@ -100,49 +104,50 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) { return nil, err } if l, _ := decodeHeader(peerHeader); l != 0 { - c.Conn.Write(make([]byte, crypto.RandBetween(100, 1000))) // make client do new handshake + noise := make([]byte, crypto.RandBetween(100, 1000)) + rand.Read(noise) + c.Conn.Write(noise) // make client do new handshake return nil, errors.New("invalid ticket") } - peerClientHello := make([]byte, 1088+1184+1) + peerClientHello := make([]byte, 1+1184+1088) copy(peerClientHello, peerTicketHello) copy(peerClientHello[53:], peerHeader) if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil { return nil, err } - encapsulatedNfsKey := peerClientHello[:1088] - eKeyPfsData := peerClientHello[1088:2272] - c.cipher = peerClientHello[2272] - if c.cipher != 0 && c.cipher != 1 { - return nil, errors.New("invalid cipher") - } + c.cipher = peerClientHello[0] + pfsEKeyBytes := peerClientHello[1:1185] + encapsulatedNfsKey := peerClientHello[1185:2273] - nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey) + pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes) if err != nil { return nil, err } - eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData) + nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey) if err != nil { return nil, err } - pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate() - c.baseKey = append(nfsKey, pfsKey...) + pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate() + c.baseKey = append(pfsKey, nfsKey...) authKey := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey) - nonce := make([]byte, 12) - c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey) + hkdf.New(sha256.New, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Read(authKey) + nonce := [12]byte{c.cipher} + c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce[:], []byte("VLESS"), pfsEKeyBytes) - padding := crypto.RandBetween(100, 1000) + paddingLen := crypto.RandBetween(100, 1000) - serverHello := make([]byte, 1088+21+5+padding) + serverHello := make([]byte, 1088+21+5+paddingLen) copy(serverHello, encapsulatedPfsKey) copy(serverHello[1088:], c.ticket) - encodeHeader(serverHello[1109:], int(padding)) + encodeHeader(serverHello[1109:], int(paddingLen)) + rand.Read(serverHello[1114:]) if _, err := c.Conn.Write(serverHello); err != nil { return nil, err } + // we can send more padding if needed if i.minutes > 0 { i.Lock() @@ -168,11 +173,11 @@ func (c *ServerConn) Read(b []byte) (int, error) { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { return 0, err } - peerPadding, _ := decodeHeader(peerHeader) - if peerPadding == 0 { + peerPaddingLen, _ := decodeHeader(peerHeader) + if peerPaddingLen == 0 { break } - if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil { + if _, err := io.ReadFull(c.Conn, make([]byte, peerPaddingLen)); err != nil { return 0, err } } @@ -212,7 +217,7 @@ func (c *ServerConn) Read(b []byte) (int, error) { } dst := peerData[:peerLength-16] if len(dst) <= len(b) { - dst = b[:len(dst)] // max=8192 is recommended for peer + dst = b[:len(dst)] // avoids another copy() } _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader) increaseNonce(c.peerNonce) @@ -226,31 +231,38 @@ func (c *ServerConn) Read(b []byte) (int, error) { return len(dst), nil } -func (c *ServerConn) Write(b []byte) (int, error) { // after first Read() +func (c *ServerConn) Write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } - var data []byte - if c.aead == nil { - if c.peerRandom == nil { - return 0, errors.New("can not Write() first") + for n := 0; n < len(b); { + b := b[n:] + if len(b) > 8192 { + b = b[:8192] // for avoiding another copy() in client's Read() + } + n += len(b) + var data []byte + if c.aead == nil { + if c.peerRandom == nil { + return 0, errors.New("empty c.peerRandom") + } + data = make([]byte, 32+5+len(b)+16) + rand.Read(data[:32]) + key := make([]byte, 32) + hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key) + c.aead = newAead(c.cipher, key) + c.nonce = make([]byte, 12) + encodeHeader(data[32:], len(b)+16) + c.aead.Seal(data[:37], c.nonce, b, data[32:37]) + } else { + data = make([]byte, 5+len(b)+16) + encodeHeader(data, len(b)+16) + c.aead.Seal(data[:5], c.nonce, b, data[:5]) + } + increaseNonce(c.nonce) + if _, err := c.Conn.Write(data); err != nil { + return 0, err } - data = make([]byte, 32+5+len(b)+16) - rand.Read(data[:32]) - key := make([]byte, 32) - hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key) - c.aead = newAead(c.cipher, key) - c.nonce = make([]byte, 12) - encodeHeader(data[32:], len(b)+16) - c.aead.Seal(data[:37], c.nonce, b, data[32:37]) - } else { - data = make([]byte, 5+len(b)+16) - encodeHeader(data, len(b)+16) - c.aead.Seal(data[:5], c.nonce, b, data[:5]) - } - increaseNonce(c.nonce) - if _, err := c.Conn.Write(data); err != nil { - return 0, err } return len(b), nil } diff --git a/proxy/vless/encryption/xor.go b/proxy/vless/encryption/xor.go index c8af2112..296fdd63 100644 --- a/proxy/vless/encryption/xor.go +++ b/proxy/vless/encryption/xor.go @@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) { return 0, err } if iv != nil { - b = b[16:] + b = b[16:] // for len(b) } return len(b), nil }