Direct proxy works

This commit is contained in:
9seconds
2019-09-04 10:19:01 +03:00
parent 07985cf418
commit 2492a47d0a
87 changed files with 1883 additions and 1447 deletions
+94
View File
@@ -0,0 +1,94 @@
package obfuscated2
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"io"
"time"
"github.com/juju/errors"
"github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers"
)
const clientProtocolHandshakeTimeout = 10 * time.Second
type ClientProtocol struct {
protocol.BaseProtocol
}
func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) {
fm, err := c.ReadFrame(socket)
if err != nil {
return nil, errors.Annotate(err, "Cannot make client handshake")
}
decHasher := sha256.New()
decHasher.Write(fm.Key()) // nolint: errcheck
decHasher.Write(config.C.Secret) // nolint: errcheck
decryptor := utils.MakeStreamCipher(decHasher.Sum(nil), fm.IV())
invertedFrame := fm.Invert()
encHasher := sha256.New()
encHasher.Write(invertedFrame.Key()) // nolint: errcheck
encHasher.Write(config.C.Secret) // nolint: errcheck
encryptor := utils.MakeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
decryptedFrame := Frame{}
decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes())
magic := decryptedFrame.Magic()
switch {
case bytes.Equal(magic, conntypes.ConnectionTagAbridged):
c.ConnectionType = conntypes.ConnectionTypeAbridged
case bytes.Equal(magic, conntypes.ConnectionTagIntermediate):
c.ConnectionType = conntypes.ConnectionTypeIntermediate
case bytes.Equal(magic, conntypes.ConnectionTagSecure):
c.ConnectionType = conntypes.ConnectionTypeSecure
default:
return nil, errors.New("Unknown connection type")
}
c.ConnectionProtocol = conntypes.ConnectionProtocolIPv4
if socket.LocalAddr().IP.To4() == nil {
c.ConnectionProtocol = conntypes.ConnectionProtocolIPv6
}
buf := bytes.NewReader(decryptedFrame.DC())
if err := binary.Read(buf, binary.LittleEndian, &c.DC); err != nil {
c.DC = conntypes.DCDefaultIdx
}
antiReplayKey := decryptedFrame.Unique()
if antireplay.Has(antiReplayKey) {
return nil, errors.New("Replay attack is detected")
}
antireplay.Add(antiReplayKey)
return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil
}
func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err error) {
if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil {
err = errors.Annotate(err, "Cannot extract obfuscated2 frame")
}
return
}
type handshakeReader struct {
parent wrappers.StreamReader
}
func (h handshakeReader) Read(p []byte) (int, error) {
return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout)
}
func MakeClientProtocol() protocol.ClientProtocol {
return &ClientProtocol{}
}
+28 -95
View File
@@ -1,17 +1,5 @@
package obfuscated2
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
)
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
const (
frameLenKey = 32
frameLenIV = 16
@@ -24,98 +12,43 @@ const (
frameOffsetMagic = frameOffsetIV + frameLenMagic
frameOffsetDC = frameOffsetMagic + frameLenDC
FrameLen = 64
frameLen = 64
)
// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2
// initialization data first.
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
type Frame []byte
// Key returns AES encryption key.
func (f Frame) Key() []byte {
return f[frameOffsetFirst:frameOffsetKey]
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
type Frame struct {
data [frameLen]byte
}
// IV returns AES encryption initialization vector
func (f Frame) IV() []byte {
return f[frameOffsetKey:frameOffsetIV]
func (f *Frame) Bytes() []byte {
return f.data[:]
}
// Magic returns magic bytes from last 8 bytes of frame. Telegram checks
// for values there. If after decryption magic is not as expected,
// connection considered as failed.
func (f Frame) Magic() []byte {
return f[frameOffsetIV:frameOffsetMagic]
func (f *Frame) Key() []byte {
return f.data[frameOffsetFirst:frameOffsetKey]
}
// DC returns number of datacenter IP client wants to use.
func (f Frame) DC() (n int16) {
buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC])
if err := binary.Read(buf, binary.LittleEndian, &n); err != nil {
n = 1
func (f *Frame) IV() []byte {
return f.data[frameOffsetKey:frameOffsetIV]
}
func (f *Frame) Magic() []byte {
return f.data[frameOffsetIV:frameOffsetMagic]
}
func (f *Frame) DC() []byte {
return f.data[frameOffsetMagic:frameOffsetDC]
}
func (f *Frame) Unique() []byte {
return f.data[frameOffsetFirst:frameOffsetDC]
}
func (f *Frame) Invert() (nf Frame) {
nf = *f
for i := 0; i < frameLenKey+frameLenIV; i++ {
nf.data[frameOffsetFirst+i] = f.data[frameOffsetIV-1-i]
}
return
}
// ConnectionType identifies connection type of the handshake frame.
func (f Frame) ConnectionType() (mtproto.ConnectionType, error) {
return mtproto.ConnectionTagFromHandshake(f.Magic())
}
// Invert inverts frame for extracting encryption keys. Pkease check that link:
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
func (f Frame) Invert() Frame {
reversed := make(Frame, FrameLen)
copy(reversed, f)
for i := 0; i < frameLenKey+frameLenIV; i++ {
reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i]
}
return reversed
}
// ExtractFrame extracts exact obfuscated2 handshake frame from given reader.
func ExtractFrame(conn io.Reader) (Frame, error) {
frame := make(Frame, FrameLen)
buf := bytes.NewBuffer(frame)
buf.Reset()
if _, err := io.CopyN(buf, conn, FrameLen); err != nil {
return nil, errors.Annotate(err, "Cannot extract obfuscated header")
}
copy(frame, buf.Bytes())
return frame, nil
}
func generateFrame(connectionType mtproto.ConnectionType) Frame {
frame := make(Frame, FrameLen)
for {
if _, err := rand.Read(frame); err != nil {
continue
}
if frame[0] == 0xef {
continue
}
val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0])
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
continue
}
val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4])
if val == 0x00000000 {
continue
}
// error has to be checked before calling this function
tag, _ := connectionType.Tag() // nolint: errcheck, gosec
copy(frame.Magic(), tag)
return frame
}
}
-106
View File
@@ -1,106 +0,0 @@
package obfuscated2
import (
"bytes"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/9seconds/mtg/mtproto"
)
func TestFrameKey(t *testing.T) {
toCompare := make([]byte, 32)
for i := 0; i < 32; i++ {
toCompare[i] = byte(1)
}
assert.Equal(t, toCompare, makeFrame().Key())
}
func TestFrameIV(t *testing.T) {
toCompare := make([]byte, 16)
for i := 0; i < 16; i++ {
toCompare[i] = byte(2)
}
assert.Equal(t, toCompare, makeFrame().IV())
}
func TestFrameMagic(t *testing.T) {
toCompare := make([]byte, 4)
for i := 0; i < 4; i++ {
toCompare[i] = 0xee
}
assert.Equal(t, toCompare, makeFrame().Magic())
}
func TestFrameDC(t *testing.T) {
assert.Equal(t, int16(771), makeFrame().DC())
}
func TestFrameValid(t *testing.T) {
frame := makeFrame()
connType, err := frame.ConnectionType()
assert.Nil(t, err)
assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate)
frame[8+32+16+2] = byte(3)
_, err = frame.ConnectionType()
assert.NotNil(t, err)
}
func TestFrameDoubleInvert(t *testing.T) {
frame := makeFrame()
assert.True(t, bytes.Equal(frame, frame.Invert().Invert()))
}
func TestFrameInvert(t *testing.T) {
frame := makeFrame()
reversed := frame.Invert()
assert.Exactly(t, frame[:8], reversed[:8])
assert.Exactly(t, frame[56:], reversed[56:])
toCompare := make([]byte, 48)
for i := 0; i < 48; i++ {
toCompare[i] = frame[55-i]
}
assert.Equal(t, []byte(reversed[8:56]), toCompare)
}
func TestFrameGenerateValid(t *testing.T) {
validTests := []mtproto.ConnectionType{
mtproto.ConnectionTypeIntermediate,
mtproto.ConnectionTypeAbridged,
}
for _, test := range validTests {
t.Run(strconv.Itoa(int(test)), func(tt *testing.T) {
frame := generateFrame(test) // nolint: scopelint
conType, err := frame.ConnectionType()
assert.Nil(tt, err)
assert.Equal(tt, conType, test) // nolint: scopelint
})
}
}
func makeFrame() Frame {
f := make(Frame, FrameLen)
for i := 8; i < (8 + 32); i++ {
f[i] = byte(1)
}
for i := (8 + 32); i < (8 + 32 + 16); i++ {
f[i] = byte(2)
}
for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ {
f[i] = 0xee
}
for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ {
f[i] = byte(3)
}
return f
}
-81
View File
@@ -1,81 +0,0 @@
package obfuscated2
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
)
// Obfuscated2 contains AES CTR encryption and decryption streams
// for telegram connection.
type Obfuscated2 struct {
Decryptor cipher.Stream
Encryptor cipher.Stream
}
// ParseObfuscated2ClientFrame parses client frame. Please check this link for
// details: http://telegra.ph/telegram-blocks-wtf-05-26
//
// Beware, link above is in russian.
func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) {
decHasher := sha256.New()
decHasher.Write(frame.Key()) // nolint: errcheck, gosec
decHasher.Write(secret) // nolint: errcheck, gosec
decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV())
invertedFrame := frame.Invert()
encHasher := sha256.New()
encHasher.Write(invertedFrame.Key()) // nolint: errcheck, gosec
encHasher.Write(secret) // nolint: errcheck, gosec
encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
decryptedFrame := make(Frame, FrameLen)
decryptor.XORKeyStream(decryptedFrame, frame)
connType, err := decryptedFrame.ConnectionType()
if err != nil {
return nil, nil, errors.Annotate(err, "Unknown protocol")
}
obfs := &Obfuscated2{
Decryptor: decryptor,
Encryptor: encryptor,
}
connOpts := &mtproto.ConnectionOpts{
DC: decryptedFrame.DC(),
ConnectionType: connType,
}
return obfs, connOpts, nil
}
// MakeTelegramObfuscated2Frame creates new handshake frame to send to
// Telegram.
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, Frame) {
frame := generateFrame(opts.ConnectionType)
encryptor := makeStreamCipher(frame.Key(), frame.IV())
decryptorFrame := frame.Invert()
decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV())
copyFrame := make(Frame, FrameLen)
copy(copyFrame[:frameOffsetIV], frame[:frameOffsetIV])
encryptor.XORKeyStream(frame, frame)
copy(frame[:frameOffsetIV], copyFrame[:frameOffsetIV])
obfs := &Obfuscated2{
Decryptor: decryptor,
Encryptor: encryptor,
}
return obfs, frame
}
func makeStreamCipher(key, iv []byte) cipher.Stream {
block, _ := aes.NewCipher(key) // nolint: gosec
return cipher.NewCTR(block, iv)
}
-97
View File
@@ -1,97 +0,0 @@
package obfuscated2
import (
"crypto/sha256"
"testing"
"github.com/stretchr/testify/assert"
"github.com/9seconds/mtg/mtproto"
)
func TestObfs2TelegramFrameDecrypt(t *testing.T) {
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
_, frame := MakeTelegramObfuscated2Frame(connOpts)
decryptor := makeStreamCipher(frame.Key(), frame.IV())
decrypted := make(Frame, FrameLen)
decryptor.XORKeyStream(decrypted, frame)
_, err := decrypted.ConnectionType()
assert.Nil(t, err)
}
func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
obfs2, frame := MakeTelegramObfuscated2Frame(connOpts)
inverted := frame.Invert()
encryptor := makeStreamCipher(inverted.Key(), inverted.IV())
data := []byte{1, 2, 3}
encrypted := make([]byte, 3)
encryptor.XORKeyStream(encrypted, data)
decrypted := make([]byte, 3)
obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
assert.Equal(t, data, decrypted)
}
func TestObfs2Full(t *testing.T) {
secret := []byte{1, 2, 3, 4, 5}
clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
clientHasher := sha256.New()
clientHasher.Write(clientFrame.Key()) // nolint: errcheck, gosec
clientHasher.Write(secret) // nolint: errcheck, gosec
clientKey := clientHasher.Sum(nil)
encryptor := makeStreamCipher(clientKey, clientFrame.IV())
encrypted := make(Frame, FrameLen)
encryptor.XORKeyStream(encrypted, clientFrame)
copy(encrypted[:56], clientFrame[:56])
invertedClientFrame := clientFrame.Invert()
clientHasher = sha256.New()
clientHasher.Write(invertedClientFrame.Key()) // nolint: errcheck, gosec
clientHasher.Write(secret) // nolint: errcheck, gosec
invertedClientKey := clientHasher.Sum(nil)
clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV())
clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted)
assert.Nil(t, err)
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts)
tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV())
decrypted := make(Frame, FrameLen)
tgDecryptor.XORKeyStream(decrypted, tgFrame)
_, err = decrypted.ConnectionType()
assert.Nil(t, err)
tgInvertedFrame := tgFrame.Invert()
tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV())
message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
tgEncryptedMessage := make([]byte, len(message))
tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage))
tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage)
assert.Equal(t, message, tgEncDecryptedMessage)
clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage))
clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage)
finalMessage := make([]byte, len(clientEncryptedMessage))
clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
assert.Equal(t, finalMessage, message)
}
+78
View File
@@ -0,0 +1,78 @@
package obfuscated2
import (
"crypto/rand"
"github.com/juju/errors"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers"
)
type TelegramProtocol struct {
protocol.BaseProtocol
dialer telegram.Telegram
}
func (t *TelegramProtocol) Handshake(req *protocol.TelegramRequest) (wrappers.Wrap, error) {
socket, err := t.dialer.Dial(req.Ctx,
req.Cancel,
req.ClientProtocol.GetDC(),
req.ClientProtocol.GetConnectionProtocol())
if err != nil {
return nil, errors.Annotate(err, "Cannot dial to Telegram")
}
fm := generateFrame(req.ClientProtocol)
data := fm.Bytes()
encryptor := utils.MakeStreamCipher(fm.Key(), fm.IV())
decryptedFrame := fm.Invert()
decryptor := utils.MakeStreamCipher(decryptedFrame.Key(), decryptedFrame.IV())
copyFrame := make([]byte, frameLen)
copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV])
encryptor.XORKeyStream(data, data)
copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV])
if _, err := socket.Write(data); err != nil {
return nil, errors.Annotate(err, "Cannot write handshate frame to Telegram")
}
return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil
}
func MakeTelegramProtocol(dialer telegram.Telegram) protocol.TelegramProtocol {
return &TelegramProtocol{
dialer: dialer,
}
}
func generateFrame(cp protocol.ClientProtocol) (fm Frame) {
data := fm.Bytes()
for {
if _, err := rand.Read(data); err != nil {
continue
}
if data[0] == 0xef {
continue
}
val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0])
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
continue
}
val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4])
if val == 0x00000000 {
continue
}
copy(fm.Magic(), cp.GetConnectionType().Tag())
return
}
}