// Генерация ClientHello по логике Telegram Desktop (mtproto_tls_socket.cpp, PrepareClientHelloRules). // См. https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp package faketls import ( "crypto/ed25519" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "math/big" "time" ) const ( kMaxGrease = 8 kClientHelloLimit = 2048 kHelloDigestLen = 32 kLengthSize = 2 kElementsM = 384 kAddedM = 32 ) // BuildTdesktopClientHello строит запись TLS 1.2 ClientHello как в tdesktop (Ed25519, permutation, ECH padding, HMAC). func BuildTdesktopClientHello(secretKey []byte, domain []byte) (*ClientHello, error) { if len(secretKey) != 16 { return nil, fmt.Errorf("secret key must be 16 bytes") } greases := prepareGreases() p := newTdesktopPart(domain, greases) writeClientHelloRules(p) if p.err { return nil, fmt.Errorf("tdesktop client hello: build failed") } p.finalize(secretKey) if p.err { return nil, fmt.Errorf("tdesktop client hello: finalize failed") } digest := p.extractDigest() out := p.take() if len(out) == 0 { return nil, fmt.Errorf("tdesktop client hello: empty buffer") } return &ClientHello{ Record: out, RandomField: append([]byte(nil), digest...), SessionID: p.sessionID(), }, nil } func prepareGreases() []byte { result := make([]byte, kMaxGrease) if _, err := rand.Read(result); err != nil { return bytesRepeat(0x0a, kMaxGrease) } for i := range result { result[i] = (result[i] & 0xf0) + 0x0a } for i := 0; i < kMaxGrease; i += 2 { if result[i] == result[i+1] { result[i+1] ^= 0x10 } } return result } func bytesRepeat(b byte, n int) []byte { out := make([]byte, n) for i := range out { out[i] = b } return out } type tdesktopPart struct { domain []byte greases []byte buf []byte digestPosition int err bool } func newTdesktopPart(domain []byte, greases []byte) *tdesktopPart { return &tdesktopPart{ domain: domain, greases: greases, digestPosition: -1, } } func (p *tdesktopPart) grow(n int) []byte { if p.err || n <= 0 || len(p.buf)+n > kClientHelloLimit { p.err = true return nil } off := len(p.buf) p.buf = append(p.buf, make([]byte, n)...) return p.buf[off : off+n] } func (p *tdesktopPart) writeString(b []byte) { st := p.grow(len(b)) if st == nil { return } copy(st, b) } func (p *tdesktopPart) writeZeros(n int) { already := len(p.buf) st := p.grow(n) if st == nil { return } if n == kHelloDigestLen && p.digestPosition < 0 { p.digestPosition = already } clear(st) } func (p *tdesktopPart) writeGrease(seed int) { if seed < 0 || seed >= len(p.greases) { p.err = true return } st := p.grow(2) if st == nil { return } // В tdesktop: bytes::set_with_const(storage, _greases[seed]) — оба байта равны значению seed. st[0] = p.greases[seed] st[1] = p.greases[seed] } func (p *tdesktopPart) writeRandom(n int) { st := p.grow(n) if st == nil { return } if _, err := rand.Read(st); err != nil { p.err = true } } func (p *tdesktopPart) writeDomain() { st := p.grow(len(p.domain)) if st == nil { return } copy(st, p.domain) } func (p *tdesktopPart) writeEd25519PublicKey() { pub, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { p.err = true return } st := p.grow(32) if st == nil { return } copy(st, pub) } func (p *tdesktopPart) writeScope(f func()) { st := p.grow(kLengthSize) if st == nil { return } already := len(p.buf) f() length := len(p.buf) - already if length > 65535 { p.err = true return } binary.BigEndian.PutUint16(p.buf[already-2:already], uint16(length)) } func (p *tdesktopPart) newSubPart() *tdesktopPart { return newTdesktopPart(p.domain, p.greases) } func (p *tdesktopPart) writePermutation(elements []func(*tdesktopPart)) { var parts [][]byte for _, el := range elements { sub := p.newSubPart() el(sub) if sub.err { p.err = true return } parts = append(parts, sub.take()) } shuffleByteSlices(parts) for _, b := range parts { if p.err { return } st := p.grow(len(b)) if st == nil { return } copy(st, b) } } func shuffleByteSlices(s [][]byte) { n := len(s) for i := n - 1; i > 0; i-- { jBig, err := rand.Int(rand.Reader, big.NewInt(int64(i+1))) j := 0 if err != nil { j = i } else { j = int(jBig.Int64()) } s[i], s[j] = s[j], s[i] } } func (p *tdesktopPart) writeM() { kElements := kElementsM kAdded := kAddedM storage := p.grow(kElements*3 + kAdded) if storage == nil { return } random := make([]byte, kElements*8+kAdded) if _, err := rand.Read(random); err != nil { p.err = true return } chars := storage ints := make([]uint32, len(random)/4) for i := 0; i < len(ints); i++ { ints[i] = binary.LittleEndian.Uint32(random[i*4 : i*4+4]) } ci := 0 for i := 0; i < kElements; i++ { a := int(ints[i*2] % 3329) b := int(ints[i*2+1] % 3329) chars[ci] = byte(a & 255) chars[ci+1] = byte((a >> 8) + ((b & 15) << 4)) chars[ci+2] = byte(b >> 4) ci += 3 } tail := storage[kElements*3:] if _, err := rand.Read(tail); err != nil { p.err = true } } func (p *tdesktopPart) writeE() { lengths := []int{144, 176, 208, 240} var pick [1]byte if _, err := rand.Read(pick[:]); err != nil { p.err = true return } length := lengths[int(pick[0])%len(lengths)] p.writeRandom(length) } func (p *tdesktopPart) writePadding() { if p.err { return } cur := len(p.buf) if cur >= 513 { return } z := 513 - cur p.writeString([]byte{0x00, 0x15}) p.writeScope(func() { p.writeZeros(z) }) } func (p *tdesktopPart) finalize(key []byte) { if p.err || p.digestPosition < 0 { p.err = true return } mac := hmac.New(sha256.New, key) mac.Write(p.buf) sum := mac.Sum(nil) copy(p.buf[p.digestPosition:p.digestPosition+kHelloDigestLen], sum) injectTimestamp(p.buf[p.digestPosition : p.digestPosition+kHelloDigestLen]) } func injectTimestamp(digest32 []byte) { if len(digest32) != 32 { return } st := digest32[28:32] u := binary.LittleEndian.Uint32(st) ts := uint32(time.Now().Unix()) u ^= ts binary.LittleEndian.PutUint32(st, u) } func (p *tdesktopPart) extractDigest() []byte { if p.digestPosition < 0 { return nil } return append([]byte(nil), p.buf[p.digestPosition:p.digestPosition+kHelloDigestLen]...) } func (p *tdesktopPart) take() []byte { if p.err { return nil } return append([]byte(nil), p.buf...) } // sessionID: первый 32-байтный Random сразу после префикса 0x20 (session id в ClientHello). func (p *tdesktopPart) sessionID() []byte { if p.err { return nil } return firstR32Payload(p.buf) } func firstR32Payload(buf []byte) []byte { pat := []byte{0x03, 0x03} i := bytesIndex(buf, pat) if i < 0 { return nil } j := i + len(pat) + 32 // после Z(32) digest if j+1+32 > len(buf) { return nil } if buf[j] != 0x20 { return nil } return append([]byte(nil), buf[j+1:j+1+32]...) } func bytesIndex(hay []byte, needle []byte) int { outer: for i := 0; i+len(needle) <= len(hay); i++ { for k := range needle { if hay[i+k] != needle[k] { continue outer } } return i } return -1 } var cipherList32 = []byte{ 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, } func writeClientHelloRules(p *tdesktopPart) { p.writeString([]byte{0x16, 0x03, 0x01}) p.writeScope(func() { p.writeString([]byte{0x01, 0x00}) p.writeScope(func() { p.writeString([]byte{0x03, 0x03}) p.writeZeros(32) p.writeString([]byte{0x20}) p.writeRandom(32) p.writeString([]byte{0x00, 0x20}) p.writeGrease(0) p.writeString(cipherList32) p.writeScope(func() { p.writeGrease(2) p.writeString([]byte{0x00, 0x00}) p.writePermutation(tdesktopPermutationElements()) p.writeGrease(3) p.writeString([]byte{0x00, 0x01, 0x00}) p.writePadding() }) }) }) } func tdesktopPermutationElements() []func(*tdesktopPart) { return []func(*tdesktopPart){ func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x00}) p.writeScope(func() { p.writeScope(func() { p.writeString([]byte{0x00}) p.writeScope(func() { p.writeDomain() }) }) }) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a}) p.writeGrease(4) p.writeString([]byte{0x11, 0xec, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}) }, func(p *tdesktopPart) { p.writeString([]byte{ 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, }) }, func(p *tdesktopPart) { p.writeString([]byte{ 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, }) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x12, 0x00, 0x00}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x17, 0x00, 0x00}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x23, 0x00, 0x00}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x2b, 0x00, 0x07, 0x06}) p.writeGrease(6) p.writeString([]byte{0x03, 0x04, 0x03, 0x03}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x2d, 0x00, 0x02, 0x01, 0x01}) }, func(p *tdesktopPart) { p.writeString([]byte{0x00, 0x33, 0x04, 0xef, 0x04, 0xed}) p.writeGrease(4) p.writeString([]byte{0x00, 0x01, 0x00, 0x11, 0xec, 0x04, 0xc0}) p.writeM() p.writeEd25519PublicKey() p.writeString([]byte{0x00, 0x1d, 0x00, 0x20}) p.writeEd25519PublicKey() }, func(p *tdesktopPart) { p.writeString([]byte{0x44, 0xcd, 0x00, 0x05, 0x00, 0x03, 0x02, 0x68, 0x32}) }, func(p *tdesktopPart) { p.writeString([]byte{0xfe, 0x0d}) p.writeScope(func() { p.writeString([]byte{0x00, 0x00, 0x01, 0x00, 0x01}) p.writeRandom(1) p.writeString([]byte{0x00, 0x20}) p.writeRandom(32) p.writeScope(func() { p.writeE() }) }) }, func(p *tdesktopPart) { p.writeString([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) }, } }