Mainly reverse VLESS Client Hello

https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3176751311
This commit is contained in:
RPRX
2025-08-12 11:51:28 +00:00
committed by GitHub
parent 7ffb555fc8
commit ec1cc35188
4 changed files with 143 additions and 123 deletions

View File

@@ -20,14 +20,15 @@ import (
var ClientCipher byte var ClientCipher byte
func init() { func init() {
if !protocol.HasAESGCMHardwareSupport { if protocol.HasAESGCMHardwareSupport {
ClientCipher = 1 ClientCipher = 1
} }
} }
type ClientInstance struct { type ClientInstance struct {
sync.RWMutex sync.RWMutex
eKeyNfs *mlkem.EncapsulationKey768 nfsEKey *mlkem.EncapsulationKey768
nfsEKeyBytes []byte
xor uint32 xor uint32
minutes time.Duration minutes time.Duration
expire time.Time expire time.Time
@@ -48,19 +49,22 @@ type ClientConn struct {
peerCache []byte peerCache []byte
} }
func (i *ClientInstance) Init(eKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData) i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
if xor > 0 {
i.nfsEKeyBytes = nfsEKeyBytes
i.xor = xor i.xor = xor
}
i.minutes = minutes i.minutes = minutes
return return
} }
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) { func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.eKeyNfs == nil { if i.nfsEKey == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
} }
if i.xor == 1 { if i.xor > 0 {
conn = NewXorConn(conn, i.eKeyNfs.Bytes()) conn = NewXorConn(conn, i.nfsEKeyBytes)
} }
c := &ClientConn{Conn: conn} c := &ClientConn{Conn: conn}
@@ -76,18 +80,19 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
i.RUnlock() i.RUnlock()
} }
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate() pfsDKeySeed := make([]byte, 64)
seed := make([]byte, 64) rand.Read(pfsDKeySeed)
rand.Read(seed) pfsDKey, _ := mlkem.NewDecapsulationKey768(pfsDKeySeed)
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed) pfsEKeyBytes := pfsDKey.EncapsulationKey().Bytes()
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes() nfsKey, encapsulatedNfsKey := i.nfsEKey.Encapsulate()
padding := crypto.RandBetween(100, 1000) paddingLen := crypto.RandBetween(100, 1000)
clientHello := make([]byte, 1088+1184+1+5+padding) clientHello := make([]byte, 1+1184+1088+5+paddingLen)
copy(clientHello, encapsulatedNfsKey) clientHello[0] = ClientCipher
copy(clientHello[1088:], eKeyPfs) copy(clientHello[1:], pfsEKeyBytes)
clientHello[2272] = ClientCipher copy(clientHello[1185:], encapsulatedNfsKey)
encodeHeader(clientHello[2273:], int(padding)) encodeHeader(clientHello[2273:], int(paddingLen))
rand.Read(clientHello[2278:])
if _, err := c.Conn.Write(clientHello); err != nil { if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err return nil, err
@@ -101,16 +106,16 @@ func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
encapsulatedPfsKey := peerServerHello[:1088] encapsulatedPfsKey := peerServerHello[:1088]
c.ticket = peerServerHello[1088:] c.ticket = peerServerHello[1088:]
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey) pfsKey, err := pfsDKey.Decapsulate(encapsulatedPfsKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.baseKey = append(nfsKey, pfsKey...) c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32) authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey) hkdf.New(sha256.New, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Read(authKey)
nonce := make([]byte, 12) nonce := [12]byte{ClientCipher}
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.ticket, encapsulatedPfsKey) VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce[:], c.ticket, pfsEKeyBytes)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
return nil, errors.New("invalid server").AtError() return nil, errors.New("invalid server").AtError()
} }
@@ -130,6 +135,12 @@ func (c *ClientConn) Write(b []byte) (int, error) {
if len(b) == 0 { if len(b) == 0 {
return 0, nil return 0, nil
} }
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 var data []byte
if c.aead == nil { if c.aead == nil {
c.random = make([]byte, 32) c.random = make([]byte, 32)
@@ -153,10 +164,11 @@ func (c *ClientConn) Write(b []byte) (int, error) {
if _, err := c.Conn.Write(data); err != nil { if _, err := c.Conn.Write(data); err != nil {
return 0, err return 0, err
} }
}
return len(b), nil 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 { if len(b) == 0 {
return 0, nil 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 { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err return 0, err
} }
peerPadding, _ := decodeHeader(peerHeader) peerPaddingLen, _ := decodeHeader(peerHeader)
if peerPadding == 0 { if peerPaddingLen == 0 {
break 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 return 0, err
} }
} }
@@ -186,7 +198,7 @@ func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
return 0, err return 0, err
} }
if c.random == nil { if c.random == nil {
return 0, errors.New("can not Read() first") return 0, errors.New("empty c.random")
} }
peerKey := make([]byte, 32) peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey) 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] dst := peerData[:peerLength-16]
if len(dst) <= len(b) { 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) _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce) increaseNonce(c.peerNonce)

View File

@@ -28,18 +28,14 @@ func decodeHeader(b []byte) (int, error) {
return 0, errors.New("invalid record's header") return 0, errors.New("invalid record's header")
} }
func newAead(c byte, k []byte) cipher.AEAD { func newAead(c byte, k []byte) (aead cipher.AEAD) {
switch c { if c&1 == 1 {
case 0: block, _ := aes.NewCipher(k)
if block, err := aes.NewCipher(k); err == nil { aead, _ = cipher.NewGCM(block)
aead, _ := cipher.NewGCM(block) } else {
return aead aead, _ = chacha20poly1305.New(k)
} }
case 1: return
aead, _ := chacha20poly1305.New(k)
return aead
}
return nil
} }
func increaseNonce(nonce []byte) { func increaseNonce(nonce []byte) {

View File

@@ -25,7 +25,8 @@ type ServerSession struct {
type ServerInstance struct { type ServerInstance struct {
sync.RWMutex sync.RWMutex
dKeyNfs *mlkem.DecapsulationKey768 nfsDKey *mlkem.DecapsulationKey768
nfsEKeyBytes []byte
xor uint32 xor uint32
minutes time.Duration minutes time.Duration
sessions map[[21]byte]*ServerSession sessions map[[21]byte]*ServerSession
@@ -44,9 +45,12 @@ type ServerConn struct {
nonce []byte nonce []byte
} }
func (i *ServerInstance) Init(dKeyNfsData []byte, xor uint32, minutes time.Duration) (err error) { func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData) i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
if xor > 0 {
i.nfsEKeyBytes = i.nfsDKey.EncapsulationKey().Bytes()
i.xor = xor i.xor = xor
}
if minutes > 0 { if minutes > 0 {
i.minutes = minutes i.minutes = minutes
i.sessions = make(map[[21]byte]*ServerSession) 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) time.Sleep(time.Minute)
now := time.Now() now := time.Now()
i.Lock() i.Lock()
for index, session := range i.sessions { for ticket, session := range i.sessions {
if now.After(session.expire) { if now.After(session.expire) {
delete(i.sessions, index) delete(i.sessions, ticket)
} }
} }
i.Unlock() 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) { func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.dKeyNfs == nil { if i.nfsDKey == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
} }
if i.xor == 1 { if i.xor > 0 {
conn = NewXorConn(conn, i.dKeyNfs.EncapsulationKey().Bytes()) conn = NewXorConn(conn, i.nfsEKeyBytes)
} }
c := &ServerConn{Conn: conn} c := &ServerConn{Conn: conn}
@@ -100,49 +104,50 @@ func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
return nil, err return nil, err
} }
if l, _ := decodeHeader(peerHeader); l != 0 { 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") return nil, errors.New("invalid ticket")
} }
peerClientHello := make([]byte, 1088+1184+1) peerClientHello := make([]byte, 1+1184+1088)
copy(peerClientHello, peerTicketHello) copy(peerClientHello, peerTicketHello)
copy(peerClientHello[53:], peerHeader) copy(peerClientHello[53:], peerHeader)
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil { if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
return nil, err return nil, err
} }
encapsulatedNfsKey := peerClientHello[:1088] c.cipher = peerClientHello[0]
eKeyPfsData := peerClientHello[1088:2272] pfsEKeyBytes := peerClientHello[1:1185]
c.cipher = peerClientHello[2272] encapsulatedNfsKey := peerClientHello[1185:2273]
if c.cipher != 0 && c.cipher != 1 {
return nil, errors.New("invalid cipher")
}
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey) pfsEKey, err := mlkem.NewEncapsulationKey768(pfsEKeyBytes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData) nfsKey, err := i.nfsDKey.Decapsulate(encapsulatedNfsKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate() pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
c.baseKey = append(nfsKey, pfsKey...) c.baseKey = append(pfsKey, nfsKey...)
authKey := make([]byte, 32) authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey) hkdf.New(sha256.New, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Read(authKey)
nonce := make([]byte, 12) nonce := [12]byte{c.cipher}
c.ticket = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey) 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, encapsulatedPfsKey)
copy(serverHello[1088:], c.ticket) 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 { if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err return nil, err
} }
// we can send more padding if needed
if i.minutes > 0 { if i.minutes > 0 {
i.Lock() i.Lock()
@@ -168,11 +173,11 @@ func (c *ServerConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil { if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err return 0, err
} }
peerPadding, _ := decodeHeader(peerHeader) peerPaddingLen, _ := decodeHeader(peerHeader)
if peerPadding == 0 { if peerPaddingLen == 0 {
break 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 return 0, err
} }
} }
@@ -212,7 +217,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
} }
dst := peerData[:peerLength-16] dst := peerData[:peerLength-16]
if len(dst) <= len(b) { 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) _, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce) increaseNonce(c.peerNonce)
@@ -226,14 +231,20 @@ func (c *ServerConn) Read(b []byte) (int, error) {
return len(dst), nil 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 { if len(b) == 0 {
return 0, nil return 0, nil
} }
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 var data []byte
if c.aead == nil { if c.aead == nil {
if c.peerRandom == nil { if c.peerRandom == nil {
return 0, errors.New("can not Write() first") return 0, errors.New("empty c.peerRandom")
} }
data = make([]byte, 32+5+len(b)+16) data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32]) rand.Read(data[:32])
@@ -252,5 +263,6 @@ func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
if _, err := c.Conn.Write(data); err != nil { if _, err := c.Conn.Write(data); err != nil {
return 0, err return 0, err
} }
}
return len(b), nil return len(b), nil
} }

View File

@@ -38,7 +38,7 @@ func (c *XorConn) Write(b []byte) (int, error) {
return 0, err return 0, err
} }
if iv != nil { if iv != nil {
b = b[16:] b = b[16:] // for len(b)
} }
return len(b), nil return len(b), nil
} }