10 changed files with 228 additions and 332 deletions
@ -1,299 +0,0 @@
|
||||
package reality |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"crypto/ecdh" |
||||
"crypto/ed25519" |
||||
"crypto/hmac" |
||||
"crypto/sha256" |
||||
"crypto/sha512" |
||||
gotls "crypto/tls" |
||||
"crypto/x509" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"reflect" |
||||
"regexp" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
"unsafe" |
||||
|
||||
"github.com/cloudflare/circl/sign/mldsa/mldsa65" |
||||
utls "github.com/refraction-networking/utls" |
||||
"github.com/xtls/reality" |
||||
"github.com/xtls/xray-core/common/crypto" |
||||
"github.com/xtls/xray-core/common/errors" |
||||
"github.com/xtls/xray-core/common/net" |
||||
"github.com/xtls/xray-core/common/utils" |
||||
"github.com/xtls/xray-core/core" |
||||
"github.com/xtls/xray-core/transport/internet/tls" |
||||
"golang.org/x/crypto/hkdf" |
||||
"golang.org/x/net/http2" |
||||
) |
||||
|
||||
type Conn struct { |
||||
*reality.Conn |
||||
} |
||||
|
||||
func (c *Conn) HandshakeAddress() net.Address { |
||||
if err := c.Handshake(); err != nil { |
||||
return nil |
||||
} |
||||
state := c.ConnectionState() |
||||
if state.ServerName == "" { |
||||
return nil |
||||
} |
||||
return net.ParseAddress(state.ServerName) |
||||
} |
||||
|
||||
func Server(c net.Conn, config *reality.Config) (net.Conn, error) { |
||||
realityConn, err := reality.Server(context.Background(), c, config) |
||||
return &Conn{Conn: realityConn}, err |
||||
} |
||||
|
||||
type UConn struct { |
||||
*utls.UConn |
||||
Config *Config |
||||
ServerName string |
||||
AuthKey []byte |
||||
Verified bool |
||||
} |
||||
|
||||
func (c *UConn) HandshakeAddress() net.Address { |
||||
if err := c.Handshake(); err != nil { |
||||
return nil |
||||
} |
||||
state := c.ConnectionState() |
||||
if state.ServerName == "" { |
||||
return nil |
||||
} |
||||
return net.ParseAddress(state.ServerName) |
||||
} |
||||
|
||||
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
||||
if c.Config.Show { |
||||
localAddr := c.LocalAddr().String() |
||||
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768) |
||||
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0) |
||||
} |
||||
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") |
||||
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) |
||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok { |
||||
h := hmac.New(sha512.New, c.AuthKey) |
||||
h.Write(pub) |
||||
if bytes.Equal(h.Sum(nil), certs[0].Signature) { |
||||
if len(c.Config.Mldsa65Verify) > 0 { |
||||
if len(certs[0].Extensions) > 0 { |
||||
h.Write(c.HandshakeState.Hello.Raw) |
||||
h.Write(c.HandshakeState.ServerHello.Raw) |
||||
verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify) |
||||
if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) { |
||||
c.Verified = true |
||||
return nil |
||||
} |
||||
} |
||||
} else { |
||||
c.Verified = true |
||||
return nil |
||||
} |
||||
} |
||||
} |
||||
opts := x509.VerifyOptions{ |
||||
DNSName: c.ServerName, |
||||
Intermediates: x509.NewCertPool(), |
||||
} |
||||
for _, cert := range certs[1:] { |
||||
opts.Intermediates.AddCert(cert) |
||||
} |
||||
if _, err := certs[0].Verify(opts); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) { |
||||
localAddr := c.LocalAddr().String() |
||||
uConn := &UConn{ |
||||
Config: config, |
||||
} |
||||
utlsConfig := &utls.Config{ |
||||
VerifyPeerCertificate: uConn.VerifyPeerCertificate, |
||||
ServerName: config.ServerName, |
||||
InsecureSkipVerify: true, |
||||
SessionTicketsDisabled: true, |
||||
KeyLogWriter: KeyLogWriterFromConfig(config), |
||||
} |
||||
if utlsConfig.ServerName == "" { |
||||
utlsConfig.ServerName = dest.Address.String() |
||||
} |
||||
uConn.ServerName = utlsConfig.ServerName |
||||
fingerprint := tls.GetFingerprint(config.Fingerprint) |
||||
if fingerprint == nil { |
||||
return nil, errors.New("REALITY: failed to get fingerprint").AtError() |
||||
} |
||||
uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint) |
||||
{ |
||||
uConn.BuildHandshakeState() |
||||
hello := uConn.HandshakeState.Hello |
||||
hello.SessionId = make([]byte, 32) |
||||
copy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID`
|
||||
hello.SessionId[0] = core.Version_x |
||||
hello.SessionId[1] = core.Version_y |
||||
hello.SessionId[2] = core.Version_z |
||||
hello.SessionId[3] = 0 // reserved
|
||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix())) |
||||
copy(hello.SessionId[8:], config.ShortId) |
||||
if config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\thello.SessionId[:16]: %v\n", localAddr, hello.SessionId[:16]) |
||||
} |
||||
publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey) |
||||
if err != nil { |
||||
return nil, errors.New("REALITY: publicKey == nil") |
||||
} |
||||
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe |
||||
if ecdhe == nil { |
||||
ecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe |
||||
} |
||||
if ecdhe == nil { |
||||
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.") |
||||
} |
||||
uConn.AuthKey, _ = ecdhe.ECDH(publicKey) |
||||
if uConn.AuthKey == nil { |
||||
return nil, errors.New("REALITY: SharedKey == nil") |
||||
} |
||||
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil { |
||||
return nil, err |
||||
} |
||||
aead := crypto.NewAesGcm(uConn.AuthKey) |
||||
if config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead) |
||||
} |
||||
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw) |
||||
copy(hello.Raw[39:], hello.SessionId) |
||||
} |
||||
if err := uConn.HandshakeContext(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
if config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified) |
||||
} |
||||
if !uConn.Verified { |
||||
errors.LogError(ctx, "REALITY: received real certificate (potential MITM or redirection)") |
||||
go func() { |
||||
client := &http.Client{ |
||||
Transport: &http2.Transport{ |
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) { |
||||
if config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\tDialTLSContext\n", localAddr) |
||||
} |
||||
return uConn, nil |
||||
}, |
||||
}, |
||||
} |
||||
prefix := []byte("https://" + uConn.ServerName) |
||||
maps.Lock() |
||||
if maps.maps == nil { |
||||
maps.maps = make(map[string]map[string]struct{}) |
||||
} |
||||
paths := maps.maps[uConn.ServerName] |
||||
if paths == nil { |
||||
paths = make(map[string]struct{}) |
||||
paths[config.SpiderX] = struct{}{} |
||||
maps.maps[uConn.ServerName] = paths |
||||
} |
||||
firstURL := string(prefix) + getPathLocked(paths) |
||||
maps.Unlock() |
||||
get := func(first bool) { |
||||
var ( |
||||
req *http.Request |
||||
resp *http.Response |
||||
err error |
||||
body []byte |
||||
) |
||||
if first { |
||||
req, _ = http.NewRequest("GET", firstURL, nil) |
||||
} else { |
||||
maps.Lock() |
||||
req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil) |
||||
maps.Unlock() |
||||
} |
||||
if req == nil { |
||||
return |
||||
} |
||||
utils.TryDefaultHeadersWith(req.Header, "nav") |
||||
if first && config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) |
||||
} |
||||
times := 1 |
||||
if !first { |
||||
times = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5])) |
||||
} |
||||
for j := 0; j < times; j++ { |
||||
if !first && j == 0 { |
||||
req.Header.Set("Referer", firstURL) |
||||
} |
||||
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))}) |
||||
if resp, err = client.Do(req); err != nil { |
||||
break |
||||
} |
||||
defer resp.Body.Close() |
||||
req.Header.Set("Referer", req.URL.String()) |
||||
if body, err = io.ReadAll(resp.Body); err != nil { |
||||
break |
||||
} |
||||
maps.Lock() |
||||
for _, m := range href.FindAllSubmatch(body, -1) { |
||||
m[1] = bytes.TrimPrefix(m[1], prefix) |
||||
if !bytes.Contains(m[1], dot) { |
||||
paths[string(m[1])] = struct{}{} |
||||
} |
||||
} |
||||
req.URL.Path = getPathLocked(paths) |
||||
if config.Show { |
||||
fmt.Printf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer()) |
||||
fmt.Printf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body)) |
||||
fmt.Printf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths)) |
||||
} |
||||
maps.Unlock() |
||||
if !first { |
||||
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
|
||||
} |
||||
} |
||||
} |
||||
get(true) |
||||
concurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3])) |
||||
for i := 0; i < concurrency; i++ { |
||||
go get(false) |
||||
} |
||||
// Do not close the connection
|
||||
}() |
||||
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
|
||||
return nil, errors.New("REALITY: processed invalid connection").AtWarning() |
||||
} |
||||
return uConn, nil |
||||
} |
||||
|
||||
var ( |
||||
href = regexp.MustCompile(`href="([/h].*?)"`) |
||||
dot = []byte(".") |
||||
) |
||||
|
||||
var maps struct { |
||||
sync.Mutex |
||||
maps map[string]map[string]struct{} |
||||
} |
||||
|
||||
func getPathLocked(paths map[string]struct{}) string { |
||||
stopAt := int(crypto.RandBetween(0, int64(len(paths)-1))) |
||||
i := 0 |
||||
for s := range paths { |
||||
if i == stopAt { |
||||
return s |
||||
} |
||||
i++ |
||||
} |
||||
return "/" |
||||
} |
||||
Loading…
Reference in new issue