From c3e8e8b1feb5bbb12bd5a4c2fb009721efeb6ec6 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 25 Mar 2021 17:33:22 +0300 Subject: [PATCH] Add base faketls processing --- go.mod | 1 + go.sum | 2 + mtglib/internal/faketls/client_hello.go | 20 +++--- mtglib/internal/faketls/init.go | 17 ++++- mtglib/internal/faketls/pools.go | 21 ++++++ mtglib/internal/faketls/welcome.go | 88 +++++++++++++++++++++++++ mtglib/proxy.go | 16 +++-- 7 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 mtglib/internal/faketls/pools.go create mode 100644 mtglib/internal/faketls/welcome.go diff --git a/go.mod b/go.mod index a09b213..ad324f1 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/tylertreat/BoomFilters v0.0.0-20200520150052-42a7b4300c0c github.com/xeipuuv/gojsonschema v1.2.0 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect ) diff --git a/go.sum b/go.sum index d9e0113..0cf083c 100644 --- a/go.sum +++ b/go.sum @@ -324,6 +324,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/mtglib/internal/faketls/client_hello.go b/mtglib/internal/faketls/client_hello.go index 3ee9aa0..6f31ee6 100644 --- a/mtglib/internal/faketls/client_hello.go +++ b/mtglib/internal/faketls/client_hello.go @@ -11,9 +11,10 @@ import ( ) type ClientHello struct { - Time time.Time - Digest [RandomLen]byte - SessionID []byte + Time time.Time + Random [RandomLen]byte + SessionID []byte + CipherSuite uint16 } func ParseClientHello(secret, handshake []byte) (ClientHello, error) { @@ -27,7 +28,7 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) { return hello, fmt.Errorf("unknown handshake type %#x", handshake[0]) } - copy(hello.Digest[:], handshake[ClientHelloRandomOffset:]) + copy(hello.Random[:], handshake[ClientHelloRandomOffset:]) for i := ClientHelloRandomOffset; i < ClientHelloRandomOffset+RandomLen; i++ { handshake[i] = 0 @@ -45,23 +46,26 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) { mac := hmac.New(sha256.New, secret) rec.Dump(mac) - computedDigest := mac.Sum(nil) + computedRandom := mac.Sum(nil) for i := 0; i < RandomLen; i++ { - computedDigest[i] ^= hello.Digest[i] + computedRandom[i] ^= hello.Random[i] } for i := 0; i < RandomLen-4; i++ { - if computedDigest[i] != 0 { + if computedRandom[i] != 0 { return hello, ErrBadDigest } } - timestamp := int64(binary.LittleEndian.Uint32(computedDigest[RandomLen-4:])) + timestamp := int64(binary.LittleEndian.Uint32(computedRandom[RandomLen-4:])) hello.Time = time.Unix(timestamp, 0) hello.SessionID = make([]byte, handshake[ClientHelloSessionIDOffset]) copy(hello.SessionID, handshake[ClientHelloSessionIDOffset+1:]) + cipherSuiteOffset := ClientHelloSessionIDOffset + 1 + len(hello.SessionID) + 2 + hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2]) + return hello, nil } diff --git a/mtglib/internal/faketls/init.go b/mtglib/internal/faketls/init.go index 081a9ba..5166cbf 100644 --- a/mtglib/internal/faketls/init.go +++ b/mtglib/internal/faketls/init.go @@ -7,12 +7,27 @@ const ( ClientHelloRandomOffset = 6 ClientHelloSessionIDOffset = ClientHelloRandomOffset + RandomLen - ClientHelloMinLen = ClientHelloSessionIDOffset + 1 + ClientHelloMinLen = ClientHelloSessionIDOffset + 1 + + WelcomePacketRandomOffset = 11 HandshakeTypeClient = 0x01 + HandshakeTypeServer = 0x02 ) var ( ErrBadDigest = errors.New("bad digest") ErrAntiReplayAttack = errors.New("antireplay attack was detected") + + serverHelloSuffix = []byte{ + 0x00, // no compression + 0x00, 0x2e, // 46 bytes of data + 0x00, 0x2b, // Extension - Supported Versions + 0x00, 0x02, // 2 bytes are following + 0x03, 0x04, // TLS 1.3 + 0x00, 0x33, // Extension - Key Share + 0x00, 0x24, // 36 bytes + 0x00, 0x1d, // x25519 curve + 0x00, 0x20, // 32 bytes of key + } ) diff --git a/mtglib/internal/faketls/pools.go b/mtglib/internal/faketls/pools.go new file mode 100644 index 0000000..35a7aea --- /dev/null +++ b/mtglib/internal/faketls/pools.go @@ -0,0 +1,21 @@ +package faketls + +import ( + "bytes" + "sync" +) + +var bytesBufferPool = sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, +} + +func acquireBytesBuffer() *bytes.Buffer { + return bytesBufferPool.Get().(*bytes.Buffer) +} + +func releaseBytesBuffer(b *bytes.Buffer) { + b.Reset() + bytesBufferPool.Put(b) +} diff --git a/mtglib/internal/faketls/welcome.go b/mtglib/internal/faketls/welcome.go new file mode 100644 index 0000000..d943900 --- /dev/null +++ b/mtglib/internal/faketls/welcome.go @@ -0,0 +1,88 @@ +package faketls + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "io" + mrand "math/rand" + + "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" + "golang.org/x/crypto/curve25519" +) + +func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error { + buf := acquireBytesBuffer() + defer releaseBytesBuffer(buf) + + rec := record.AcquireRecord() + defer record.ReleaseRecord(rec) + + rec.Type = record.TypeHandshake + rec.Version = record.Version12 + + generateServerHello(&rec.Payload, clientHello) + rec.Dump(buf) + rec.Reset() + + rec.Type = record.TypeChangeCipherSpec + rec.Version = record.Version12 + rec.Payload.WriteByte(0x01) + + rec.Dump(buf) + rec.Reset() + + rec.Type = record.TypeApplicationData + rec.Version = record.Version12 + + if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.Intn(3092))); err != nil { + panic(err) + } + + rec.Dump(buf) + + packet := buf.Bytes() + mac := hmac.New(sha256.New, secret) + + mac.Write(clientHello.Random[:]) + mac.Write(packet) + + copy(packet[WelcomePacketRandomOffset:], mac.Sum(nil)) + + if _, err := writer.Write(packet); err != nil { + return err // nolint: wrapcheck + } + + return nil +} + +func generateServerHello(writer io.Writer, clientHello ClientHello) { + bodyBuf := acquireBytesBuffer() + defer releaseBytesBuffer(bodyBuf) + + sliceBuf := [2]byte{} + digest := [RandomLen]byte{} + + binary.BigEndian.PutUint16(sliceBuf[:], uint16(record.Version12)) + bodyBuf.Write(sliceBuf[:]) + bodyBuf.Write(digest[:]) + bodyBuf.WriteByte(byte(len(clientHello.SessionID))) + bodyBuf.Write(clientHello.SessionID) + + binary.BigEndian.PutUint16(sliceBuf[:], clientHello.CipherSuite) + bodyBuf.Write(sliceBuf[:]) + bodyBuf.Write(serverHelloSuffix) + + scalar := [32]byte{} + rand.Read(scalar[:]) + curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint) + bodyBuf.Write(curve) + + header := [4]byte{0, 0, 0, 0} + binary.BigEndian.PutUint32(header[:], uint32(bodyBuf.Len())) + header[0] = HandshakeTypeServer + + writer.Write(header[:]) + bodyBuf.WriteTo(writer) +} diff --git a/mtglib/proxy.go b/mtglib/proxy.go index 8ce024d..b9032c0 100644 --- a/mtglib/proxy.go +++ b/mtglib/proxy.go @@ -122,15 +122,14 @@ func (p *Proxy) Shutdown() { } func (p *Proxy) doFakeTLSHandshake(ctx *streamContext, conn net.Conn) error { - clientHelloRecord := record.AcquireRecord() - defer record.ReleaseRecord(clientHelloRecord) + rec := record.AcquireRecord() + defer record.ReleaseRecord(rec) - if err := clientHelloRecord.Read(conn); err != nil { + if err := rec.Read(conn); err != nil { return fmt.Errorf("cannot read client hello: %w", err) } - hello, err := faketls.ParseClientHello(p.secret.Key[:], - clientHelloRecord.Payload.Bytes()) + hello, err := faketls.ParseClientHello(p.secret.Key[:], rec.Payload.Bytes()) if err != nil { return fmt.Errorf("cannot parse client hello: %w", err) } @@ -138,13 +137,18 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext, conn net.Conn) error { if err := p.timeAttackDetector.Valid(hello.Time); err != nil { return fmt.Errorf("invalid time: %w", err) } + if p.antiReplayCache.SeenBefore(hello.SessionID) { p.logger.Warning("anti replay attack was detected") return fmt.Errorf("anti replay attack from %s", ctx.ClientIP().String()) } - return fmt.Errorf("SUCCESS") + if err := faketls.SendWelcomePacket(conn, p.secret.Key[:], hello); err != nil { + return fmt.Errorf("cannot send a welcome packet: %w", err) + } + + return nil } func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {