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