mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-03-23 19:34:24 +03:00
Implement different mtproto proxy protocols; refactor obfuscated2
This commit is contained in:
parent
baa8970bb6
commit
b873aa67cc
|
@ -9,7 +9,7 @@ from datetime import datetime, timezone
|
||||||
from .. import version, __name__ as __base_name__
|
from .. import version, __name__ as __base_name__
|
||||||
from ..crypto import rsa
|
from ..crypto import rsa
|
||||||
from ..extensions import markdown
|
from ..extensions import markdown
|
||||||
from ..network import MTProtoSender, ConnectionTcpFull, ConnectionTcpMTProxy
|
from ..network import MTProtoSender, ConnectionTcpFull, TcpMTProxy
|
||||||
from ..sessions import Session, SQLiteSession, MemorySession
|
from ..sessions import Session, SQLiteSession, MemorySession
|
||||||
from ..tl import TLObject, functions, types
|
from ..tl import TLObject, functions, types
|
||||||
from ..tl.alltlobjects import LAYER
|
from ..tl.alltlobjects import LAYER
|
||||||
|
@ -64,7 +64,7 @@ class TelegramBaseClient(abc.ABC):
|
||||||
|
|
||||||
proxy (`tuple` | `list` | `dict`, optional):
|
proxy (`tuple` | `list` | `dict`, optional):
|
||||||
An iterable consisting of the proxy info. If `connection` is
|
An iterable consisting of the proxy info. If `connection` is
|
||||||
`ConnectionTcpMTProxy`, then it should contain MTProxy credentials:
|
one of `MTProxy`, then it should contain MTProxy credentials:
|
||||||
``('hostname', port, 'secret')``. Otherwise, it's meant to store
|
``('hostname', port, 'secret')``. Otherwise, it's meant to store
|
||||||
function parameters for PySocks, like ``(type, 'hostname', port)``.
|
function parameters for PySocks, like ``(type, 'hostname', port)``.
|
||||||
See https://github.com/Anorov/PySocks#usage-1 for more.
|
See https://github.com/Anorov/PySocks#usage-1 for more.
|
||||||
|
@ -249,8 +249,8 @@ class TelegramBaseClient(abc.ABC):
|
||||||
|
|
||||||
assert isinstance(connection, type)
|
assert isinstance(connection, type)
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
init_proxy = None if connection is not ConnectionTcpMTProxy else \
|
init_proxy = None if not issubclass(connection, TcpMTProxy) else \
|
||||||
types.InputClientProxy(*ConnectionTcpMTProxy.address_info(proxy))
|
types.InputClientProxy(*connection.address_info(proxy))
|
||||||
|
|
||||||
# Used on connection. Capture the variables in a lambda since
|
# Used on connection. Capture the variables in a lambda since
|
||||||
# exporting clients need to create this InvokeWithLayerRequest.
|
# exporting clients need to create this InvokeWithLayerRequest.
|
||||||
|
@ -551,7 +551,7 @@ class TelegramBaseClient(abc.ABC):
|
||||||
self._exported_sessions[cdn_redirect.dc_id] = session
|
self._exported_sessions[cdn_redirect.dc_id] = session
|
||||||
|
|
||||||
self._log[__name__].info('Creating new CDN client')
|
self._log[__name__].info('Creating new CDN client')
|
||||||
client = TelegramBareClient(
|
client = TelegramBaseClient(
|
||||||
session, self.api_id, self.api_hash,
|
session, self.api_id, self.api_hash,
|
||||||
proxy=self._sender.connection.conn.proxy,
|
proxy=self._sender.connection.conn.proxy,
|
||||||
timeout=self._sender.connection.get_timeout()
|
timeout=self._sender.connection.get_timeout()
|
||||||
|
|
|
@ -7,5 +7,7 @@ from .authenticator import do_authentication
|
||||||
from .mtprotosender import MTProtoSender
|
from .mtprotosender import MTProtoSender
|
||||||
from .connection import (
|
from .connection import (
|
||||||
ConnectionTcpFull, ConnectionTcpIntermediate, ConnectionTcpAbridged,
|
ConnectionTcpFull, ConnectionTcpIntermediate, ConnectionTcpAbridged,
|
||||||
ConnectionTcpObfuscated, ConnectionTcpMTProxy, ConnectionHttp
|
ConnectionTcpObfuscated, ConnectionTcpMTProxyAbridged,
|
||||||
|
ConnectionTcpMTProxyIntermediate,
|
||||||
|
ConnectionTcpMTProxyRandomizedIntermediate, ConnectionHttp, TcpMTProxy
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,5 +2,10 @@ from .tcpfull import ConnectionTcpFull
|
||||||
from .tcpintermediate import ConnectionTcpIntermediate
|
from .tcpintermediate import ConnectionTcpIntermediate
|
||||||
from .tcpabridged import ConnectionTcpAbridged
|
from .tcpabridged import ConnectionTcpAbridged
|
||||||
from .tcpobfuscated import ConnectionTcpObfuscated
|
from .tcpobfuscated import ConnectionTcpObfuscated
|
||||||
from .tcpmtproxy import ConnectionTcpMTProxy
|
from .tcpmtproxy import (
|
||||||
|
TcpMTProxy,
|
||||||
|
ConnectionTcpMTProxyAbridged,
|
||||||
|
ConnectionTcpMTProxyIntermediate,
|
||||||
|
ConnectionTcpMTProxyRandomizedIntermediate
|
||||||
|
)
|
||||||
from .http import ConnectionHttp
|
from .http import ConnectionHttp
|
||||||
|
|
|
@ -30,6 +30,8 @@ class Connection(abc.ABC):
|
||||||
self._connected = False
|
self._connected = False
|
||||||
self._send_task = None
|
self._send_task = None
|
||||||
self._recv_task = None
|
self._recv_task = None
|
||||||
|
self._codec = None
|
||||||
|
self._obfuscation = None # TcpObfuscated and MTProxy
|
||||||
self._send_queue = asyncio.Queue(1)
|
self._send_queue = asyncio.Queue(1)
|
||||||
self._recv_queue = asyncio.Queue(1)
|
self._recv_queue = asyncio.Queue(1)
|
||||||
|
|
||||||
|
|
|
@ -9,34 +9,36 @@ class ConnectionTcpAbridged(Connection):
|
||||||
only require 1 byte if the packet length is less than
|
only require 1 byte if the packet length is less than
|
||||||
508 bytes (127 << 2, which is very common).
|
508 bytes (127 << 2, which is very common).
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._codec = AbridgedPacket()
|
||||||
|
|
||||||
def _init_conn(self):
|
def _init_conn(self):
|
||||||
self._writer.write(b'\xef')
|
self._writer.write(self._codec.tag)
|
||||||
|
|
||||||
def _write(self, data):
|
|
||||||
"""
|
|
||||||
Define wrapper write methods for `TcpObfuscated` to override.
|
|
||||||
"""
|
|
||||||
self._writer.write(data)
|
|
||||||
|
|
||||||
async def _read(self, n):
|
|
||||||
"""
|
|
||||||
Define wrapper read methods for `TcpObfuscated` to override.
|
|
||||||
"""
|
|
||||||
return await self._reader.readexactly(n)
|
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data):
|
||||||
|
self._writer.write(self._codec.encode_packet(data))
|
||||||
|
|
||||||
|
async def _recv(self):
|
||||||
|
return await self._codec.read_packet(self._reader)
|
||||||
|
|
||||||
|
|
||||||
|
class AbridgedPacket:
|
||||||
|
tag = b'\xef'
|
||||||
|
mtproto_proxy_tag = b'\xef\xef\xef\xef'
|
||||||
|
|
||||||
|
def encode_packet(self, data):
|
||||||
length = len(data) >> 2
|
length = len(data) >> 2
|
||||||
if length < 127:
|
if length < 127:
|
||||||
length = struct.pack('B', length)
|
length = struct.pack('B', length)
|
||||||
else:
|
else:
|
||||||
length = b'\x7f' + int.to_bytes(length, 3, 'little')
|
length = b'\x7f' + int.to_bytes(length, 3, 'little')
|
||||||
|
return length + data
|
||||||
|
|
||||||
self._write(length + data)
|
async def read_packet(self, reader):
|
||||||
|
length = struct.unpack('<B', await reader.readexactly(1))[0]
|
||||||
async def _recv(self):
|
|
||||||
length = struct.unpack('<B', await self._read(1))[0]
|
|
||||||
if length >= 127:
|
if length >= 127:
|
||||||
length = struct.unpack(
|
length = struct.unpack(
|
||||||
'<i', await self._read(3) + b'\0')[0]
|
'<i', await reader.readexactly(3) + b'\0')[0]
|
||||||
|
|
||||||
return await self._read(length << 2)
|
return await reader.readexactly(length << 2)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import struct
|
import struct
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
|
||||||
from .connection import Connection
|
from .connection import Connection
|
||||||
|
|
||||||
|
@ -8,12 +10,47 @@ class ConnectionTcpIntermediate(Connection):
|
||||||
Intermediate mode between `ConnectionTcpFull` and `ConnectionTcpAbridged`.
|
Intermediate mode between `ConnectionTcpFull` and `ConnectionTcpAbridged`.
|
||||||
Always sends 4 extra bytes for the packet length.
|
Always sends 4 extra bytes for the packet length.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._codec = IntermediatePacket()
|
||||||
|
|
||||||
def _init_conn(self):
|
def _init_conn(self):
|
||||||
self._writer.write(b'\xee\xee\xee\xee')
|
self._writer.write(self._codec.tag)
|
||||||
|
|
||||||
def _send(self, data):
|
def _send(self, data):
|
||||||
self._writer.write(struct.pack('<i', len(data)) + data)
|
self._writer.write(self._codec.encode_packet(data))
|
||||||
|
|
||||||
async def _recv(self):
|
async def _recv(self):
|
||||||
return await self._reader.readexactly(
|
return await self.codec.read_packet(self._reader)
|
||||||
struct.unpack('<i', await self._reader.readexactly(4))[0])
|
|
||||||
|
|
||||||
|
class IntermediatePacket:
|
||||||
|
tag = b'\xee\xee\xee\xee'
|
||||||
|
mtproto_proxy_tag = tag
|
||||||
|
|
||||||
|
def encode_packet(self, data):
|
||||||
|
return struct.pack('<i', len(data)) + data
|
||||||
|
|
||||||
|
async def read_packet(self, reader):
|
||||||
|
length = struct.unpack('<i', await reader.readexactly(4))[0]
|
||||||
|
return await reader.readexactly(length)
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizedIntermediatePacket(IntermediatePacket):
|
||||||
|
"""
|
||||||
|
Data packets are aligned to 4bytes. This codec adds random bytes of size
|
||||||
|
from 0 to 3 bytes, which are ignored by decoder.
|
||||||
|
"""
|
||||||
|
mtproto_proxy_tag = b'\xdd\xdd\xdd\xdd'
|
||||||
|
|
||||||
|
def encode_packet(self, data):
|
||||||
|
pad_size = random.randint(0, 3)
|
||||||
|
padding = os.urandom(pad_size)
|
||||||
|
return super().encode_packet(data + padding)
|
||||||
|
|
||||||
|
async def read_packet(self, reader):
|
||||||
|
packet_with_padding = await super().read_packet(reader)
|
||||||
|
pad_size = len(packet_with_padding) % 4
|
||||||
|
if pad_size > 0:
|
||||||
|
return packet_with_padding[:-pad_size]
|
||||||
|
return packet_with_padding
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
from .tcpobfuscated import ConnectionTcpObfuscated
|
from .connection import Connection
|
||||||
|
from .tcpabridged import AbridgedPacket
|
||||||
|
from .tcpintermediate import IntermediatePacket, RandomizedIntermediatePacket
|
||||||
|
|
||||||
|
from ...crypto import AESModeCTR
|
||||||
|
|
||||||
|
|
||||||
class ConnectionTcpMTProxy(ConnectionTcpObfuscated):
|
class TcpMTProxy(Connection):
|
||||||
"""
|
"""
|
||||||
Wrapper around the "obfuscated2" mode that modifies it a little and allows
|
Connector which allows user to connect to the Telegram via proxy servers
|
||||||
user to connect to the Telegram proxy servers commonly known as MTProxy.
|
commonly known as MTProxy.
|
||||||
Implemented very ugly due to the leaky abstractions in Telethon networking
|
Implemented very ugly due to the leaky abstractions in Telethon networking
|
||||||
classes that should be refactored later (TODO).
|
classes that should be refactored later (TODO).
|
||||||
|
|
||||||
|
@ -15,6 +20,8 @@ class ConnectionTcpMTProxy(ConnectionTcpObfuscated):
|
||||||
The support for MTProtoProxies class is **EXPERIMENTAL** and prone to
|
The support for MTProtoProxies class is **EXPERIMENTAL** and prone to
|
||||||
be changed. You shouldn't be using this class yet.
|
be changed. You shouldn't be using this class yet.
|
||||||
"""
|
"""
|
||||||
|
packet_codec = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def address_info(proxy_info):
|
def address_info(proxy_info):
|
||||||
if proxy_info is None:
|
if proxy_info is None:
|
||||||
|
@ -25,19 +32,96 @@ class ConnectionTcpMTProxy(ConnectionTcpObfuscated):
|
||||||
proxy_host, proxy_port = self.address_info(proxy)
|
proxy_host, proxy_port = self.address_info(proxy)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
proxy_host, proxy_port, dc_id, loop=loop, loggers=loggers)
|
proxy_host, proxy_port, dc_id, loop=loop, loggers=loggers)
|
||||||
|
self._codec = self.packet_codec()
|
||||||
# TODO: Implement the dd-secret secure mode (adds noise to fool DPI)
|
secret = bytes.fromhex(proxy[2])
|
||||||
self._secret = bytes.fromhex(proxy[2])
|
is_dd = (len(secret) == 17) and (secret[0] == 0xDD)
|
||||||
if len(self._secret) != 16:
|
if is_dd and (self.packet_codec != RandomizedIntermediatePacket):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"MTProxy secure mode is not implemented for now"
|
"Only RandomizedIntermediate can be used with dd-secrets")
|
||||||
if len(self._secret) == 17 and self._secret[0] == 0xDD else
|
secret = secret[:-1] if is_dd else secret
|
||||||
"MTProxy secret must be a hex-string representing 16 bytes"
|
if len(secret) != 16:
|
||||||
)
|
raise ValueError(
|
||||||
|
"MTProxy secret must be a hex-string representing 16 bytes")
|
||||||
|
self._dc_id = dc_id
|
||||||
|
self._secret = secret
|
||||||
|
|
||||||
def _compose_key(self, data):
|
def _init_conn(self):
|
||||||
return hashlib.sha256(data + self._secret).digest()
|
self._obfuscation = MTProxyIO(self._reader, self._writer,
|
||||||
|
self._codec.mtproto_proxy_tag,
|
||||||
|
self._secret, self._dc_id)
|
||||||
|
self._writer.write(self._obfuscation.header)
|
||||||
|
|
||||||
def _compose_tail(self, data):
|
def _send(self, data):
|
||||||
dc_id_bytes = self._dc_id.to_bytes(2, "little", signed=True)
|
self._obfuscation.write(self._codec.encode_packet(data))
|
||||||
return super()._compose_tail(data[:60] + dc_id_bytes + data[62:])
|
|
||||||
|
async def _recv(self):
|
||||||
|
return await self._codec.read_packet(self._obfuscation)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTcpMTProxyAbridged(TcpMTProxy):
|
||||||
|
"""
|
||||||
|
Connect to proxy using abridged protocol
|
||||||
|
"""
|
||||||
|
packet_codec = AbridgedPacket
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTcpMTProxyIntermediate(TcpMTProxy):
|
||||||
|
"""
|
||||||
|
Connect to proxy using intermediate protocol
|
||||||
|
"""
|
||||||
|
packet_codec = IntermediatePacket
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTcpMTProxyRandomizedIntermediate(TcpMTProxy):
|
||||||
|
"""
|
||||||
|
Connect to proxy using randomized intermediate protocol (dd-secrets)
|
||||||
|
"""
|
||||||
|
packet_codec = RandomizedIntermediatePacket
|
||||||
|
|
||||||
|
|
||||||
|
class MTProxyIO:
|
||||||
|
"""
|
||||||
|
It's very similar to tcpobfuscated.ObfuscatedIO, but the way
|
||||||
|
encryption keys, protocol tag and dc_id are encoded is different.
|
||||||
|
"""
|
||||||
|
header = None
|
||||||
|
|
||||||
|
def __init__(self, reader, writer, protocol_tag, secret, dc_id):
|
||||||
|
self._reader = reader
|
||||||
|
self._writer = writer
|
||||||
|
# Obfuscated messages secrets cannot start with any of these
|
||||||
|
keywords = (b'PVrG', b'GET ', b'POST', b'\xee\xee\xee\xee')
|
||||||
|
while True:
|
||||||
|
random = os.urandom(64)
|
||||||
|
if (random[0] != 0xef and
|
||||||
|
random[:4] not in keywords and
|
||||||
|
random[4:4] != b'\0\0\0\0'):
|
||||||
|
break
|
||||||
|
|
||||||
|
random = bytearray(random)
|
||||||
|
random_reversed = random[55:7:-1] # Reversed (8, len=48)
|
||||||
|
|
||||||
|
# Encryption has "continuous buffer" enabled
|
||||||
|
encrypt_key = hashlib.sha256(
|
||||||
|
bytes(random[8:40]) + secret).digest()
|
||||||
|
encrypt_iv = bytes(random[40:56])
|
||||||
|
decrypt_key = hashlib.sha256(
|
||||||
|
bytes(random_reversed[:32]) + secret).digest()
|
||||||
|
decrypt_iv = bytes(random_reversed[32:48])
|
||||||
|
|
||||||
|
self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_iv)
|
||||||
|
self._aes_decrypt = AESModeCTR(decrypt_key, decrypt_iv)
|
||||||
|
|
||||||
|
random[56:60] = protocol_tag
|
||||||
|
|
||||||
|
dc_id_bytes = dc_id.to_bytes(2, "little", signed=True)
|
||||||
|
random = random[:60] + dc_id_bytes + random[62:]
|
||||||
|
random[56:64] = self._aes_encrypt.encrypt(bytes(random))[56:64]
|
||||||
|
|
||||||
|
self.header = random
|
||||||
|
|
||||||
|
async def readexactly(self, n):
|
||||||
|
return self._aes_decrypt.encrypt(await self._reader.readexactly(n))
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self._writer.write(self._aes_encrypt.encrypt(data))
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from .tcpabridged import AbridgedPacket
|
||||||
from .connection import Connection
|
from .connection import Connection
|
||||||
from .tcpabridged import ConnectionTcpAbridged
|
|
||||||
from ...crypto import AESModeCTR
|
from ...crypto import AESModeCTR
|
||||||
|
|
||||||
|
|
||||||
class ConnectionTcpObfuscated(ConnectionTcpAbridged):
|
class ConnectionTcpObfuscated(Connection):
|
||||||
"""
|
"""
|
||||||
Mode that Telegram defines as "obfuscated2". Encodes the packet
|
Mode that Telegram defines as "obfuscated2". Encodes the packet
|
||||||
just like `ConnectionTcpAbridged`, but encrypts every message with
|
just like `ConnectionTcpAbridged`, but encrypts every message with
|
||||||
|
@ -15,16 +16,26 @@ class ConnectionTcpObfuscated(ConnectionTcpAbridged):
|
||||||
def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None):
|
def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy)
|
ip, port, dc_id, loop=loop, loggers=loggers, proxy=proxy)
|
||||||
self._aes_encrypt = None
|
self._codec = AbridgedPacket()
|
||||||
self._aes_decrypt = None
|
|
||||||
|
|
||||||
def _write(self, data):
|
|
||||||
self._writer.write(self._aes_encrypt.encrypt(data))
|
|
||||||
|
|
||||||
async def _read(self, n):
|
|
||||||
return self._aes_decrypt.encrypt(await self._reader.readexactly(n))
|
|
||||||
|
|
||||||
def _init_conn(self):
|
def _init_conn(self):
|
||||||
|
self._obfuscation = ObfuscatedIO(
|
||||||
|
self._reader, self._writer, self._codec.mtproto_proxy_tag)
|
||||||
|
self._writer.write(self._obfuscation.header)
|
||||||
|
|
||||||
|
def _send(self, data):
|
||||||
|
self._obfuscation.write(self._codec.encode_packet(data))
|
||||||
|
|
||||||
|
async def _recv(self):
|
||||||
|
return await self._codec.read_packet(self._obfuscation)
|
||||||
|
|
||||||
|
|
||||||
|
class ObfuscatedIO:
|
||||||
|
header = None
|
||||||
|
|
||||||
|
def __init__(self, reader, writer, protocol_tag):
|
||||||
|
self._reader = reader
|
||||||
|
self._writer = writer
|
||||||
# Obfuscated messages secrets cannot start with any of these
|
# Obfuscated messages secrets cannot start with any of these
|
||||||
keywords = (b'PVrG', b'GET ', b'POST', b'\xee\xee\xee\xee')
|
keywords = (b'PVrG', b'GET ', b'POST', b'\xee\xee\xee\xee')
|
||||||
while True:
|
while True:
|
||||||
|
@ -38,23 +49,21 @@ class ConnectionTcpObfuscated(ConnectionTcpAbridged):
|
||||||
random_reversed = random[55:7:-1] # Reversed (8, len=48)
|
random_reversed = random[55:7:-1] # Reversed (8, len=48)
|
||||||
|
|
||||||
# Encryption has "continuous buffer" enabled
|
# Encryption has "continuous buffer" enabled
|
||||||
encrypt_key = self._compose_key(bytes(random[8:40]))
|
encrypt_key = bytes(random[8:40])
|
||||||
encrypt_iv = bytes(random[40:56])
|
encrypt_iv = bytes(random[40:56])
|
||||||
decrypt_key = self._compose_key(bytes(random_reversed[:32]))
|
decrypt_key = bytes(random_reversed[:32])
|
||||||
decrypt_iv = bytes(random_reversed[32:48])
|
decrypt_iv = bytes(random_reversed[32:48])
|
||||||
|
|
||||||
self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_iv)
|
self._aes_encrypt = AESModeCTR(encrypt_key, encrypt_iv)
|
||||||
self._aes_decrypt = AESModeCTR(decrypt_key, decrypt_iv)
|
self._aes_decrypt = AESModeCTR(decrypt_key, decrypt_iv)
|
||||||
|
|
||||||
random[56:60] = b'\xef\xef\xef\xef'
|
random[56:60] = protocol_tag
|
||||||
random[56:64] = self._compose_tail(bytes(random))
|
random[56:64] = self._aes_encrypt.encrypt(bytes(random))[56:64]
|
||||||
|
|
||||||
self._writer.write(random)
|
self.header = random
|
||||||
|
|
||||||
# Next functions provide the variable parts of the connection handshake.
|
async def readexactly(self, n):
|
||||||
# This is necessary to modify obfuscated2 the way that MTProxy requires.
|
return self._aes_decrypt.encrypt(await self._reader.readexactly(n))
|
||||||
def _compose_key(self, data):
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _compose_tail(self, data):
|
def write(self, data):
|
||||||
return self._aes_encrypt.encrypt(data)[56:64]
|
self._writer.write(self._aes_encrypt.encrypt(data))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user