diff --git a/faketls/client_protocol.go b/faketls/client_protocol.go new file mode 100644 index 0000000..620a89d --- /dev/null +++ b/faketls/client_protocol.go @@ -0,0 +1,35 @@ +package faketls + +import ( + "bufio" + + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/obfuscated2" + "github.com/9seconds/mtg/wrappers/stream" +) + +type ClientProtocol struct { + obfuscated2.ClientProtocol +} + +func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) { + rewinded := stream.NewRewind(socket) + bufferedReader := bufio.NewReader(rewinded) + + for _, expected := range faketlsStartBytes { + if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected { + return nil, c.simulateWebsite(rewinded) + } + } + + if err := c.tlsHandshake(rewinded); err != nil { + return nil, c.simulateWebsite(rewinded) + } + + conn, err := c.ClientProtocol.Handshake(socket) + if err != nil { + return nil, err + } + + return conn, err +} diff --git a/faketls/consts.go b/faketls/consts.go new file mode 100644 index 0000000..66061d0 --- /dev/null +++ b/faketls/consts.go @@ -0,0 +1,21 @@ +package faketls + +const ( + TLSHandshakeLength = 1 + 2 + 2 + 512 +) + +var ( +faketlsStartBytes = [...]byte{ + 0x16, + 0x03, + 0x01, + 0x02, + 0x00, + 0x01, + 0x00, + 0x01, + 0xfc, + 0x03, + 0x03, +} +) diff --git a/faketls/telegram_protocol.go b/faketls/telegram_protocol.go new file mode 100644 index 0000000..9c1cd85 --- /dev/null +++ b/faketls/telegram_protocol.go @@ -0,0 +1,10 @@ +package faketls + +import ( + "github.com/9seconds/mtg/conntypes" + "github.com/9seconds/mtg/protocol" +) + +func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) { + return nil, nil +} diff --git a/wrappers/stream/blockcipher.go b/wrappers/stream/blockcipher.go index 34ff7d8..1a40076 100644 --- a/wrappers/stream/blockcipher.go +++ b/wrappers/stream/blockcipher.go @@ -1,7 +1,6 @@ package stream import ( - "bytes" "crypto/aes" "crypto/cipher" "fmt" @@ -15,7 +14,7 @@ import ( ) type wrapperBlockCipher struct { - buf bytes.Buffer + bufferedReader parent conntypes.StreamReadWriteCloser encryptor cipher.BlockMode @@ -40,43 +39,6 @@ func (w *wrapperBlockCipher) WriteTimeout(p []byte, timeout time.Duration) (int, return w.parent.WriteTimeout(encrypted, timeout) } -func (w *wrapperBlockCipher) Read(p []byte) (int, error) { - if w.buf.Len() > 0 { - return w.flush(p) - } - - var currentBuffer []byte - for len(currentBuffer) == 0 || len(currentBuffer)%aes.BlockSize != 0 { - rv, err := utils.ReadFull(w.parent) - if err != nil { - return 0, fmt.Errorf("cannot read data: %w", err) - } - - currentBuffer = append(currentBuffer, rv...) - } - - w.decryptor.CryptBlocks(currentBuffer, currentBuffer) - w.buf.Write(currentBuffer) - - return w.flush(p) -} - -func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int, error) { - return w.Read(p) -} - -func (w *wrapperBlockCipher) flush(p []byte) (int, error) { - if w.buf.Len() > len(p) { - return w.buf.Read(p) - } - - sizeToReturn := w.buf.Len() - copy(p, w.buf.Bytes()) - w.buf.Reset() - - return sizeToReturn, nil -} - func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) { if len(p)%aes.BlockSize > 0 { return nil, fmt.Errorf("incorrect block size %d", len(p)) @@ -110,9 +72,25 @@ func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr { func newBlockCipher(parent conntypes.StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) conntypes.StreamReadWriteCloser { - return &wrapperBlockCipher{ + cipher := &wrapperBlockCipher{ parent: parent, encryptor: encryptor, decryptor: decryptor, } + + cipher.readFunc = func() ([]byte, error) { + var currentBuffer []byte + for len(currentBuffer) == 0 || len(currentBuffer)%aes.BlockSize != 0 { + rv, err := utils.ReadFull(cipher.parent) + if err != nil { + return nil, fmt.Errorf("cannot read data: %w", err) + } + currentBuffer = append(currentBuffer, rv...) + } + cipher.decryptor.CryptBlocks(currentBuffer, currentBuffer) + + return currentBuffer, nil + } + + return cipher } diff --git a/wrappers/stream/buffered_reader.go b/wrappers/stream/buffered_reader.go new file mode 100644 index 0000000..8b23627 --- /dev/null +++ b/wrappers/stream/buffered_reader.go @@ -0,0 +1,43 @@ +package stream + +import ( + "bytes" + "time" +) + +type bufferedReaderReadFunc func() ([]byte, error) + +type bufferedReader struct { + buf bytes.Buffer + readFunc bufferedReaderReadFunc +} + +func (b *bufferedReader) Read(p []byte) (int, error) { + if b.buf.Len() > 0 { + return b.flush(p) + } + + res, err := b.readFunc() + if err != nil { + return 0, err + } + b.buf.Write(res) + + return b.flush(p) +} + +func (b *bufferedReader) ReadTimeout(p []byte, _ time.Duration) (int, error) { + return b.Read(p) +} + +func (b *bufferedReader) flush(p []byte) (int, error) { + if b.buf.Len() > len(p) { + return b.buf.Read(p) + } + + sizeToReturn := b.buf.Len() + copy(p, b.buf.Bytes()) + b.buf.Reset() + + return sizeToReturn, nil +} diff --git a/wrappers/stream/faketls.go b/wrappers/stream/faketls.go new file mode 100644 index 0000000..7c60a8f --- /dev/null +++ b/wrappers/stream/faketls.go @@ -0,0 +1,145 @@ +package stream + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +var ( + errFakeTLSTimeout = errors.New("timeout") + fakeTLSWritePrefix = []byte{0x17, 0x03, 0x03} +) + +const faketlsMaxChunkSize = 16384 + 24 + +type wrapperFakeTLS struct { + bufferedReader + + parent conntypes.StreamReadWriteCloser +} + +func (w *wrapperFakeTLS) Write(p []byte) (int, error) { + return w.write(p, func(b []byte) (int, error) { + return w.parent.Write(b) + }) +} + +func (w *wrapperFakeTLS) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + startTime := time.Now() + + return w.write(p, func(b []byte) (int, error) { + elapsed := time.Since(startTime) + if elapsed > timeout { + return w.parent.WriteTimeout(b, timeout-elapsed) + } + return 0, errFakeTLSTimeout + }) +} + +func (w *wrapperFakeTLS) write(p []byte, writeFunc func([]byte) (int, error)) (int, error) { + sum := 0 + size := [2]byte{} + + for len(p) > 0 { + chunkSize := faketlsMaxChunkSize + if chunkSize > len(p) { + chunkSize = len(p) + } + + if _, err := writeFunc(fakeTLSWritePrefix); err != nil { + return sum, err + } + + binary.BigEndian.PutUint16(size[:], uint16(chunkSize)) + + if _, err := writeFunc(size[:]); err != nil { + return sum, err + } + + n, err := writeFunc(p[:chunkSize]) + sum += n + + if err != nil { + return sum, err + } + + p = p[chunkSize:] + } + + return sum, nil +} + +func (w *wrapperFakeTLS) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperFakeTLS) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("faketls") +} + +func (w *wrapperFakeTLS) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperFakeTLS) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperFakeTLS) Close() error { + return w.parent.Close() +} + +func NewFakeTLS(socket conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser { + faketls := &wrapperFakeTLS{ + parent: socket, + } + + faketls.readFunc = func() ([]byte, error) { + data := &bytes.Buffer{} + buf := [2]byte{} + recordType := byte(0x14) + + for recordType == 0x14 { + if _, err := io.ReadFull(faketls.parent, buf[:1]); err != nil { + return nil, fmt.Errorf("cannot read record type: %w", err) + } + + switch buf[0] { + case 0x14, 0x17: + recordType = buf[0] + default: + return nil, fmt.Errorf("incorrect record type %v", buf[0]) + } + + if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { + return nil, fmt.Errorf("cannot read version: %w", err) + } + + if !bytes.Equal(buf[:], []byte{0x03, 0x03}) { + return nil, fmt.Errorf("unknown tls version %v", buf) + } + + if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil { + return nil, fmt.Errorf("cannot read data length: %w", err) + } + + dataLength := binary.BigEndian.Uint16(buf[:]) + if _, err := io.CopyN(data, faketls.parent, int64(dataLength)); err != nil { + return nil, fmt.Errorf("cannot copy frame data: %w", err) + } + } + + return data.Bytes(), nil + } + + return faketls +} diff --git a/wrappers/stream/rewind.go b/wrappers/stream/rewind.go new file mode 100644 index 0000000..b2f4d52 --- /dev/null +++ b/wrappers/stream/rewind.go @@ -0,0 +1,98 @@ +package stream + +import ( + "bytes" + "io" + "net" + "sync" + "time" + + "go.uber.org/zap" + + "github.com/9seconds/mtg/conntypes" +) + +type ReadWriteCloseRewinder interface { + conntypes.StreamReadWriteCloser + Rewind() +} + +type wrapperRewind struct { + parent conntypes.StreamReadWriteCloser + buf bytes.Buffer + mutex sync.Mutex + rewinded bool +} + +func (w *wrapperRewind) Write(p []byte) (int, error) { + return w.parent.Write(p) +} + +func (w *wrapperRewind) WriteTimeout(p []byte, timeout time.Duration) (int, error) { + return w.parent.WriteTimeout(p, timeout) +} + +func (w *wrapperRewind) Read(p []byte) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.rewinded { + if n, err := w.buf.Read(p); err != io.EOF { + return n, err + } + } + + n, err := w.parent.Read(p) + w.buf.Write(p[:n]) + + return n, err +} + +func (w *wrapperRewind) ReadTimeout(p []byte, timeout time.Duration) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.rewinded { + if n, err := w.buf.Read(p); err != io.EOF { + return n, err + } + } + + n, err := w.parent.ReadTimeout(p, timeout) + w.buf.Write(p[:n]) + + return n, err +} + +func (w *wrapperRewind) Conn() net.Conn { + return w.parent.Conn() +} + +func (w *wrapperRewind) Logger() *zap.SugaredLogger { + return w.parent.Logger().Named("rewinded") +} + +func (w *wrapperRewind) LocalAddr() *net.TCPAddr { + return w.parent.LocalAddr() +} + +func (w *wrapperRewind) RemoteAddr() *net.TCPAddr { + return w.parent.RemoteAddr() +} + +func (w *wrapperRewind) Close() error { + w.buf.Reset() + return w.parent.Close() +} + +func (w *wrapperRewind) Rewind() { + w.mutex.Lock() + w.rewinded = true + w.mutex.Unlock() +} + +func NewRewind(parent conntypes.StreamReadWriteCloser) ReadWriteCloseRewinder { + return &wrapperRewind{ + parent: parent, + } +}