Skip to content

Commit 8799631

Browse files
committed
abcd
1 parent e311c9e commit 8799631

6 files changed

Lines changed: 558 additions & 5 deletions

File tree

pyrogram/connection/connection.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from pyrogram import utils
2424

25-
from .transport import TCP, TCPAbridged
25+
from .transport import TCP, TCPAbridged, TCPMTProxy
2626

2727
log = logging.getLogger(__name__)
2828

@@ -51,6 +51,12 @@ def __init__(
5151
self.media = media
5252
self.protocol_factory = protocol_factory
5353
self.crypto_executor_workers = crypto_executor_workers
54+
self.is_mtproxy = False
55+
56+
# if isinstance(proxy, str) and proxy.lower().startswith("mtproxy://"):
57+
# self.is_mtproxy = True
58+
if isinstance(proxy, dict) and proxy.get("scheme", "").lower() == "mtproxy":
59+
self.is_mtproxy = True
5460

5561
self.protocol: Optional[TCP] = None
5662

@@ -61,8 +67,10 @@ def __init__(
6167

6268
async def connect(self) -> None:
6369
for i in range(Connection.MAX_CONNECTION_ATTEMPTS):
64-
self.protocol = self.protocol_factory(ipv6=self.ipv6, proxy=self.proxy, crypto_executor_workers=self.crypto_executor_workers, loop=self.loop)
65-
70+
if self.is_mtproxy:
71+
self.protocol = TCPMTProxy(dc_id=self.dc_id, ipv6=self.ipv6, proxy=self.proxy, crypto_executor_workers=self.crypto_executor_workers, loop=self.loop)
72+
else:
73+
self.protocol = self.protocol_factory(ipv6=self.ipv6, proxy=self.proxy, crypto_executor_workers=self.crypto_executor_workers, loop=self.loop)
6674
try:
6775
log.info("Connecting...")
6876
await self.protocol.connect((self.server_address, self.port))

pyrogram/connection/transport/tcp/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from .tcp_full import TCPFull
2323
from .tcp_intermediate import TCPIntermediate
2424
from .tcp_intermediate_o import TCPIntermediateO
25+
from .tcp_mtproxy import TCPMTProxy
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import os
2+
import struct
3+
import random
4+
5+
# Faithful Python port of Telegram tgnet's TlsHello (ConnectionSocket.cpp).
6+
# Produces a modern Chrome-like ClientHello (GREASE, TLS 1.3 cipher suites,
7+
# permuted extensions, X25519 + ML-KEM-768 hybrid key_share, ECH, ALPN, padding).
8+
# The 32-byte client-random field at offset [11:43] is left zeroed; the caller
9+
# fills it with HMAC(secret, hello)[:28] + (HMAC[28:32] XOR timestamp).
10+
11+
MAX_GREASE = 8
12+
P25519 = 2 ** 255 - 19
13+
14+
15+
# ---- curve25519 public-key generation (matches generate_public_key) ----
16+
17+
def _get_y2(x, mod):
18+
# y^2 = x^3 + 486662*x^2 + x
19+
y = (x + 486662) % mod
20+
y = (y * x) % mod
21+
y = (y + 1) % mod
22+
y = (y * x) % mod
23+
return y
24+
25+
26+
def _get_double_x(x, mod):
27+
# x_2 = (x^2 - 1)^2 / (4 * y^2)
28+
denominator = (_get_y2(x, mod) * 4) % mod
29+
numerator = (x * x - 1) % mod
30+
numerator = (numerator * numerator) % mod
31+
denominator_inv = pow(denominator, mod - 2, mod)
32+
return (numerator * denominator_inv) % mod
33+
34+
35+
def generate_public_key():
36+
mod = P25519
37+
pw = (mod - 1) // 2 # 2^254 - 10
38+
while True:
39+
key = bytearray(os.urandom(32))
40+
key[31] &= 127
41+
x = int.from_bytes(key, "big")
42+
x = (x * x) % mod
43+
if pow(_get_y2(x, mod), pw, mod) == 1:
44+
break
45+
for _ in range(3):
46+
x = _get_double_x(x, mod)
47+
return x.to_bytes(32, "big")[::-1]
48+
49+
50+
# ---- ML-KEM-768 fake key generation (matches generate_key_ml_kem_768) ----
51+
52+
def generate_key_ml_kem_768():
53+
Q = 3329
54+
N = 384
55+
key = bytearray(1184)
56+
values = struct.unpack("<%dI" % (N * 2), os.urandom(N * 2 * 4))
57+
for i in range(N):
58+
a = values[i * 2] % Q
59+
b = values[i * 2 + 1] % Q
60+
key[i * 3 + 0] = a & 0xFF
61+
key[i * 3 + 1] = ((a >> 8) | ((b & 0x0F) << 4)) & 0xFF
62+
key[i * 3 + 2] = (b >> 4) & 0xFF
63+
key[1152:1184] = os.urandom(32)
64+
return bytes(key)
65+
66+
67+
# ---- GREASE values (matches TlsHello constructor) ----
68+
69+
def _gen_grease():
70+
g = bytearray(os.urandom(MAX_GREASE))
71+
for a in range(MAX_GREASE):
72+
g[a] = (g[a] & 0xf0) + 0x0A
73+
for i in range(1, MAX_GREASE, 2):
74+
if i + 1 < MAX_GREASE and g[i] == g[i + 1]:
75+
g[i] ^= 0x10
76+
return g
77+
78+
79+
# ---- op interpreter (matches writeOp) ----
80+
# Op forms (tuples):
81+
# ('str', b'...') ('rand', n) ('K',) ('M',) ('E',)
82+
# ('zero', n) ('domain',) ('grease', seed)
83+
# ('begin',) ('end',) ('P',) ('perm', [[ops], ...])
84+
85+
def _write_op(op, out, scopes, grease, domain):
86+
t = op[0]
87+
if t == 'str':
88+
out += op[1]
89+
elif t == 'rand':
90+
out += os.urandom(op[1])
91+
elif t == 'K':
92+
out += generate_public_key()
93+
elif t == 'M':
94+
out += generate_key_ml_kem_768()
95+
elif t == 'zero':
96+
out += b"\x00" * op[1]
97+
elif t == 'domain':
98+
d = domain.encode("ascii", "ignore")[:253]
99+
out += d
100+
elif t == 'grease':
101+
g = grease[op[1]]
102+
out += bytes([g, g])
103+
elif t == 'begin':
104+
scopes.append(len(out))
105+
out += b"\x00\x00"
106+
elif t == 'end':
107+
begin = scopes.pop()
108+
size = len(out) - begin - 2
109+
out[begin] = (size >> 8) & 0xFF
110+
out[begin + 1] = size & 0xFF
111+
elif t == 'E':
112+
length = (144, 176, 208, 240)[random.randrange(4)]
113+
out += os.urandom(length)
114+
elif t == 'P':
115+
length = len(out)
116+
if length <= 513:
117+
_write_op(('str', b"\x00\x15"), out, scopes, grease, domain)
118+
_write_op(('begin',), out, scopes, grease, domain)
119+
_write_op(('zero', 513 - length), out, scopes, grease, domain)
120+
_write_op(('end',), out, scopes, grease, domain)
121+
elif t == 'perm':
122+
parts = list(op[1])
123+
n = len(parts)
124+
for i in range(n - 1):
125+
j = i + random.randrange(n - i)
126+
if i != j:
127+
parts[i], parts[j] = parts[j], parts[i]
128+
for part in parts:
129+
for o in part:
130+
_write_op(o, out, scopes, grease, domain)
131+
else:
132+
raise ValueError("unknown op %r" % (t,))
133+
134+
135+
def _default_ops():
136+
s = lambda b: ('str', b)
137+
return [
138+
s(b"\x16\x03\x01"),
139+
('begin',),
140+
s(b"\x01\x00"),
141+
('begin',),
142+
s(b"\x03\x03"),
143+
('zero', 32),
144+
s(b"\x20"),
145+
('rand', 32),
146+
s(b"\x00\x20"),
147+
('grease', 0),
148+
s(b"\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00"),
149+
('begin',),
150+
('grease', 2),
151+
s(b"\x00\x00"),
152+
('perm', [
153+
[
154+
s(b"\x00\x00"),
155+
('begin',),
156+
('begin',),
157+
s(b"\x00"),
158+
('begin',),
159+
('domain',),
160+
('end',),
161+
('end',),
162+
('end',),
163+
],
164+
[s(b"\x00\x05\x00\x05\x01\x00\x00\x00\x00")],
165+
[
166+
s(b"\x00\x0a\x00\x0c\x00\x0a"),
167+
('grease', 4),
168+
s(b"\x11\xec\x00\x1d\x00\x17\x00\x18"),
169+
],
170+
[s(b"\x00\x0b\x00\x02\x01\x00")],
171+
[s(b"\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01")],
172+
[s(b"\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31")],
173+
[s(b"\x00\x12\x00\x00")],
174+
[s(b"\x00\x17\x00\x00")],
175+
[s(b"\x00\x1b\x00\x03\x02\x00\x02")],
176+
[s(b"\x00\x23\x00\x00")],
177+
[
178+
s(b"\x00\x2b\x00\x07\x06"),
179+
('grease', 6),
180+
s(b"\x03\x04\x03\x03"),
181+
],
182+
[s(b"\x00\x2d\x00\x02\x01\x01")],
183+
[
184+
s(b"\x00\x33\x04\xef\x04\xed"),
185+
('grease', 4),
186+
s(b"\x00\x01\x00\x11\xec\x04\xc0"),
187+
('M',),
188+
('K',),
189+
s(b"\x00\x1d\x00\x20"),
190+
('K',),
191+
],
192+
[s(b"\x44\xcd\x00\x05\x00\x03\x02\x68\x32")],
193+
[
194+
s(b"\xfe\x0d"),
195+
('begin',),
196+
s(b"\x00\x00\x01\x00\x01"),
197+
('rand', 1),
198+
s(b"\x00\x20"),
199+
('rand', 32),
200+
('begin',),
201+
('E',),
202+
('end',),
203+
('end',),
204+
],
205+
[s(b"\xff\x01\x00\x01\x00")],
206+
]),
207+
('grease', 3),
208+
s(b"\x00\x01\x00"),
209+
('P',),
210+
('end',),
211+
('end',),
212+
('end',),
213+
]
214+
215+
216+
def build_fake_tls_client_hello(domain: str) -> bytearray:
217+
grease = _gen_grease()
218+
out = bytearray()
219+
scopes = []
220+
for op in _default_ops():
221+
_write_op(op, out, scopes, grease, domain)
222+
return out

pyrogram/connection/transport/tcp/tcp.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ProxyDict(TypedDict):
3636
scheme: str
3737
hostname: str
3838
port: int
39+
secret: Optional[str]
3940
username: Optional[str]
4041
password: Optional[str]
4142

@@ -154,7 +155,7 @@ async def _connect_via_direct(self, destination: Tuple[str, int]) -> None:
154155
log.info("Connection established")
155156

156157
async def _connect(self, destination: Tuple[str, int]) -> None:
157-
if self.proxy:
158+
if self.proxy and isinstance(self.proxy, dict) and self.proxy.get("scheme", "").lower() != "mtproxy":
158159
await self._connect_via_proxy(destination)
159160
else:
160161
await self._connect_via_direct(destination)
@@ -245,4 +246,4 @@ async def recv(self, length: int = 0) -> Optional[bytes]:
245246
return None
246247

247248
log.debug("Recv complete: %d bytes", len(data))
248-
return data
249+
return data

0 commit comments

Comments
 (0)