mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 20:24:03 +03:00
Can correctly accept faketls messages
This commit is contained in:
+8
-7
@@ -9,13 +9,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrSecretEmpty = errors.New("secret is empty")
|
ErrSecretEmpty = errors.New("secret is empty")
|
||||||
ErrSecretInvalid = errors.New("secret is invalid")
|
ErrSecretInvalid = errors.New("secret is invalid")
|
||||||
ErrNetworkIsNotDefined = errors.New("network is not defined")
|
ErrNetworkIsNotDefined = errors.New("network is not defined")
|
||||||
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
|
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
|
||||||
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
|
ErrTimeAttackDetectorIsNotDefined = errors.New("time attack detector is not defined")
|
||||||
ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
|
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
|
||||||
ErrLoggerIsNotDefined = errors.New("logger is not defined")
|
ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
|
||||||
|
ErrLoggerIsNotDefined = errors.New("logger is not defined")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package clienthello
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientHello struct {
|
||||||
|
Time time.Time
|
||||||
|
Digest [RandomLen]byte
|
||||||
|
SessionID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseHandshake(secret, handshake []byte) (ClientHello, error) {
|
||||||
|
hello := ClientHello{}
|
||||||
|
|
||||||
|
if len(handshake) < MinLen {
|
||||||
|
return hello, fmt.Errorf("lengh of handshake is too small: %d", len(handshake))
|
||||||
|
}
|
||||||
|
|
||||||
|
if handshake[0] != HandshakeTypeClient {
|
||||||
|
return hello, fmt.Errorf("unknown handshake type %#x", handshake[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(hello.Digest[:], handshake[RandomOffset:])
|
||||||
|
|
||||||
|
for i := RandomOffset; i < RandomOffset+RandomLen; i++ {
|
||||||
|
handshake[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := record.AcquireRecord()
|
||||||
|
defer record.ReleaseRecord(rec)
|
||||||
|
|
||||||
|
rec.Type = record.TypeHandshake
|
||||||
|
rec.Version = record.Version10
|
||||||
|
rec.Payload.Write(handshake)
|
||||||
|
|
||||||
|
// mac is calculated for the whole record, not only
|
||||||
|
// for the payload part
|
||||||
|
mac := hmac.New(sha256.New, secret)
|
||||||
|
rec.Dump(mac)
|
||||||
|
|
||||||
|
computedDigest := mac.Sum(nil)
|
||||||
|
|
||||||
|
for i := 0; i < RandomLen; i++ {
|
||||||
|
computedDigest[i] ^= hello.Digest[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < RandomLen-4; i++ {
|
||||||
|
if computedDigest[i] != 0 {
|
||||||
|
return hello, ErrBadDigest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := int64(binary.LittleEndian.Uint32(computedDigest[RandomLen-4:]))
|
||||||
|
hello.Time = time.Unix(timestamp, 0)
|
||||||
|
|
||||||
|
hello.SessionID = make([]byte, handshake[SessionIDOffset])
|
||||||
|
copy(hello.SessionID, handshake[SessionIDOffset+1:])
|
||||||
|
|
||||||
|
return hello, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package clienthello
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
RandomLen = 32
|
||||||
|
RandomOffset = 6
|
||||||
|
SessionIDOffset = RandomOffset + RandomLen
|
||||||
|
MinLen = SessionIDOffset + 1
|
||||||
|
|
||||||
|
HandshakeTypeClient = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadDigest = errors.New("bad digest")
|
||||||
|
ErrAntiReplayAttack = errors.New("antireplay attack was detected")
|
||||||
|
)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Type uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeChangeCipherSpec Type = 0x14
|
||||||
|
TypeHandshake Type = 0x16
|
||||||
|
TypeApplicationData Type = 0x17
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
switch t {
|
||||||
|
case TypeChangeCipherSpec:
|
||||||
|
return "changeCipher(0x14)"
|
||||||
|
case TypeHandshake:
|
||||||
|
return "handshake(0x16)"
|
||||||
|
case TypeApplicationData:
|
||||||
|
return "applicationData(0x17)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("unknown(%#x)", byte(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Type) Valid() error {
|
||||||
|
switch t {
|
||||||
|
case TypeChangeCipherSpec, TypeHandshake, TypeApplicationData:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown type %#x", byte(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Version uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version10 Version = 769 // 0x03 0x01
|
||||||
|
Version11 Version = 770 // 0x03 0x02
|
||||||
|
Version12 Version = 771 // 0x03 0x03
|
||||||
|
Version13 Version = 772 // 0x03 0x04
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
switch v {
|
||||||
|
case Version10:
|
||||||
|
return "tls1.0"
|
||||||
|
case Version11:
|
||||||
|
return "tls1.1"
|
||||||
|
case Version12:
|
||||||
|
return "tls1.2"
|
||||||
|
case Version13:
|
||||||
|
return "tls1.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("tls(%d)", uint16(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Valid() error {
|
||||||
|
switch v {
|
||||||
|
case Version10, Version11, Version12, Version13:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown version %d", uint16(v))
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var recordPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &Record{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func AcquireRecord() *Record {
|
||||||
|
return recordPool.Get().(*Record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReleaseRecord(r *Record) {
|
||||||
|
r.Reset()
|
||||||
|
recordPool.Put(r)
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Type Type
|
||||||
|
Version Version
|
||||||
|
Payload bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) String() string {
|
||||||
|
return fmt.Sprintf("<tlsRecord(type=%v, version=%v, payload=%s)>",
|
||||||
|
r.Type,
|
||||||
|
r.Version,
|
||||||
|
base64.StdEncoding.EncodeToString(r.Payload.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) Reset() {
|
||||||
|
r.Payload.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) Read(reader io.Reader) error {
|
||||||
|
r.Reset()
|
||||||
|
|
||||||
|
buf := [2]byte{}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(reader, buf[:1]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Type = Type(buf[0])
|
||||||
|
if err := r.Type.Valid(); err != nil {
|
||||||
|
return fmt.Errorf("invalid type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Version = Version(binary.BigEndian.Uint16(buf[:]))
|
||||||
|
if err := r.Version.Valid(); err != nil {
|
||||||
|
return fmt.Errorf("invalid version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read payload length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
length := int64(binary.BigEndian.Uint16(buf[:]))
|
||||||
|
if _, err := io.CopyN(&r.Payload, reader, length); err != nil {
|
||||||
|
return fmt.Errorf("cannot read payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) Dump(writer io.Writer) error {
|
||||||
|
buf := [2]byte{byte(r.Type), 0}
|
||||||
|
|
||||||
|
if _, err := writer.Write(buf[:1]); err != nil {
|
||||||
|
return fmt.Errorf("cannot dump type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(buf[:], uint16(r.Version))
|
||||||
|
|
||||||
|
if _, err := writer.Write(buf[:]); err != nil {
|
||||||
|
return fmt.Errorf("cannot dump version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(buf[:], uint16(r.Payload.Len()))
|
||||||
|
|
||||||
|
if _, err := writer.Write(buf[:]); err != nil {
|
||||||
|
return fmt.Errorf("cannot dump payload length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := writer.Write(r.Payload.Bytes()); err != nil {
|
||||||
|
return fmt.Errorf("cannot dump payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+42
-15
@@ -8,6 +8,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/clienthello"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||||
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
|
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
|
||||||
"github.com/9seconds/mtg/v2/mtglib/internal/relay"
|
"github.com/9seconds/mtg/v2/mtglib/internal/relay"
|
||||||
"github.com/9seconds/mtg/v2/mtglib/internal/telegram"
|
"github.com/9seconds/mtg/v2/mtglib/internal/telegram"
|
||||||
@@ -24,11 +26,12 @@ type Proxy struct {
|
|||||||
workerPool *ants.PoolWithFunc
|
workerPool *ants.PoolWithFunc
|
||||||
telegram *telegram.Telegram
|
telegram *telegram.Telegram
|
||||||
|
|
||||||
secret Secret
|
secret Secret
|
||||||
antiReplayCache AntiReplayCache
|
antiReplayCache AntiReplayCache
|
||||||
ipBlocklist IPBlocklist
|
timeAttackDetector TimeAttackDetector
|
||||||
eventStream EventStream
|
ipBlocklist IPBlocklist
|
||||||
logger Logger
|
eventStream EventStream
|
||||||
|
logger Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) ServeConn(conn net.Conn) {
|
func (p *Proxy) ServeConn(conn net.Conn) {
|
||||||
@@ -55,6 +58,12 @@ func (p *Proxy) ServeConn(conn net.Conn) {
|
|||||||
ctx.logger.Info("Stream has been finished")
|
ctx.logger.Info("Stream has been finished")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if err := p.doFakeTLSHandshake(ctx); err != nil {
|
||||||
|
p.logger.InfoError("faketls handshake is failed", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.doObfuscated2Handshake(ctx); err != nil {
|
if err := p.doObfuscated2Handshake(ctx); err != nil {
|
||||||
p.logger.InfoError("obfuscated2 handshake is failed", err)
|
p.logger.InfoError("obfuscated2 handshake is failed", err)
|
||||||
|
|
||||||
@@ -112,6 +121,21 @@ func (p *Proxy) Shutdown() {
|
|||||||
p.workerPool.Release()
|
p.workerPool.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) error {
|
||||||
|
clientHelloRecord := record.AcquireRecord()
|
||||||
|
defer record.ReleaseRecord(clientHelloRecord)
|
||||||
|
|
||||||
|
if err := clientHelloRecord.Read(ctx.clientConn); err != nil {
|
||||||
|
return fmt.Errorf("cannot read client hello: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hello, _ := clienthello.ParseHandshake(p.secret.Key[:],
|
||||||
|
clientHelloRecord.Payload.Bytes())
|
||||||
|
fmt.Println(hello)
|
||||||
|
|
||||||
|
return fmt.Errorf("SUCCESS")
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
|
func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
|
||||||
dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(p.secret.Key[:], ctx.clientConn)
|
dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(p.secret.Key[:], ctx.clientConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,6 +197,8 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) { // nolint: cyclop
|
|||||||
return nil, ErrIPBlocklistIsNotDefined
|
return nil, ErrIPBlocklistIsNotDefined
|
||||||
case opts.EventStream == nil:
|
case opts.EventStream == nil:
|
||||||
return nil, ErrEventStreamIsNotDefined
|
return nil, ErrEventStreamIsNotDefined
|
||||||
|
case opts.TimeAttackDetector == nil:
|
||||||
|
return nil, ErrTimeAttackDetectorIsNotDefined
|
||||||
case opts.Logger == nil:
|
case opts.Logger == nil:
|
||||||
return nil, ErrLoggerIsNotDefined
|
return nil, ErrLoggerIsNotDefined
|
||||||
case !opts.Secret.Valid():
|
case !opts.Secret.Valid():
|
||||||
@@ -201,16 +227,17 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) { // nolint: cyclop
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
proxy := &Proxy{
|
proxy := &Proxy{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ctxCancel: cancel,
|
ctxCancel: cancel,
|
||||||
secret: opts.Secret,
|
secret: opts.Secret,
|
||||||
antiReplayCache: opts.AntiReplayCache,
|
antiReplayCache: opts.AntiReplayCache,
|
||||||
ipBlocklist: opts.IPBlocklist,
|
timeAttackDetector: opts.TimeAttackDetector,
|
||||||
eventStream: opts.EventStream,
|
ipBlocklist: opts.IPBlocklist,
|
||||||
logger: opts.Logger.Named("proxy"),
|
eventStream: opts.EventStream,
|
||||||
idleTimeout: idleTimeout,
|
logger: opts.Logger.Named("proxy"),
|
||||||
bufferSize: int(bufferSize),
|
idleTimeout: idleTimeout,
|
||||||
telegram: tg,
|
bufferSize: int(bufferSize),
|
||||||
|
telegram: tg,
|
||||||
}
|
}
|
||||||
|
|
||||||
pool, err := ants.NewPoolWithFunc(int(concurrency), func(arg interface{}) {
|
pool, err := ants.NewPoolWithFunc(int(concurrency), func(arg interface{}) {
|
||||||
|
|||||||
Reference in New Issue
Block a user