Skip to content

Commit 2617912

Browse files
committed
damn
1 parent efd975f commit 2617912

File tree

10 files changed

+881
-0
lines changed

10 files changed

+881
-0
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
---
2+
created: 2025-01-11T01:17
3+
updated: 2025-01-12T18:38
4+
unsolved: true
5+
solves: 3
6+
---
7+
8+
Another minecraft pcap challenge?
9+
10+
After a bit of scanning, I realised that the communication is encrypted, keys were exchanged and `login_encryption_request` was triggered.
11+
12+
My old writeup does not handle crypto so it won't work anymore.
13+
## packet reading.
14+
15+
```python
16+
def packet_login_encryption_request(self, buff):
17+
p_server_id = buff.unpack_string()
18+
def unpack_array(b): return b.read(b.unpack_varint(max_bits=16))
19+
p_public_key = unpack_array(buff)
20+
p_verify_token = unpack_array(buff)
21+
22+
self.public_key = crypto.import_public_key(p_public_key)
23+
self.verify_token = p_verify_token
24+
print(f"login_encryption_request {p_server_id=} {len(p_public_key)=} {p_verify_token=}")
25+
print(f"{self.public_key.public_numbers()=}")
26+
```
27+
28+
After `login_encryption_request` and `login_encryption_response` the bytes are encrypted.
29+
30+
Here we have the public key, but breaking RSA is basically impossible.
31+
32+
```
33+
self.public_key.public_numbers()=<RSAPublicNumbers(e=65537, n=130461568758849331505036135484014114043992644491371593716583161711577760699108564114633147451910541252566337707008956518324286452684332343041659860224701474509989481060885383984791170320441956230799736307044647353927078847073897040487341909288238283432126001878262186673758416165688244773338744035128369896631)>
34+
```
35+
36+
So I decided to look into the server's source code.
37+
38+
## server source
39+
40+
We can obtain the server's version via decompiling, `1.21.4`.
41+
42+
Let's check the provided `server.jar` file with the official one.
43+
44+
```bash
45+
$ sha256sum server.jar
46+
9429e99dc0cd0993ef1664549245d497ae1615bbee428d71bccbb0f35b20d026 server.jar
47+
$ sha256sum server.real.jar
48+
1066970b09e9c671844572291c4a871cc1ac2b85838bf7004fa0e778e10f1358 server.real.jar
49+
```
50+
51+
The hashes are different!
52+
53+
Let's examine their keypair generation.
54+
55+
```java
56+
protected void V() {
57+
l.info("Generating keypair");
58+
59+
try {
60+
this.ad = axx.b();
61+
} catch (axy $$0) {
62+
throw new IllegalStateException("Failed to generate key pair", $$0);
63+
}
64+
}
65+
```
66+
67+
```java [axx.class]
68+
public static KeyPair b() throws axy {
69+
try {
70+
BigInteger var0;
71+
BigInteger var1;
72+
do {
73+
var0 = BigInteger.probablePrime(512, new SecureRandom());
74+
var1 = var0.shiftRight(256).or(var0.mod(BigInteger.TWO.pow(256)).shiftLeft(256));
75+
} while(!var1.isProbablePrime(100));
76+
77+
KeyFactory var2 = KeyFactory.getInstance("RSA");
78+
return new KeyPair(var2.generatePublic(new RSAPublicKeySpec(var0.multiply(var1), new BigInteger("65537"))), var2.generatePrivate(new RSAPrivateKeySpec(var0.multiply(var1), (new BigInteger("65537")).modInverse(var0.subtract(BigInteger.ONE).multiply(var1.subtract(BigInteger.ONE))))));
79+
} catch (Exception var4) {
80+
throw new axy(var4);
81+
}
82+
}
83+
```
84+
85+
Meanwhile the real minecraft server does this.
86+
87+
```java [real/axx.class]
88+
public static KeyPair b() throws axy {
89+
try {
90+
KeyPairGenerator $$0 = KeyPairGenerator.getInstance("RSA");
91+
$$0.initialize(1024);
92+
return $$0.generateKeyPair();
93+
} catch (Exception $$1) {
94+
throw new axy($$1);
95+
}
96+
}
97+
```
98+
99+
The server.jar we got has weak crypto!
100+
101+
$$
102+
\begin{align}
103+
p & = H_1 || H_0= H_1 \cdot 2^{256} + H_0 \\
104+
q & = H_0 || H_1= H_0 \cdot 2^{256} + H_1 \\
105+
N & = (H_1 \cdot 2^{256} + H_0) \cdot (H_0 \cdot 2^{256} + H_1) \\
106+
& = H_1 \cdot H_0 \cdot 2^{512} + (H_1^2 + H_0^2) \cdot 2^{256} + H_0 \cdot H_1
107+
\end{align}
108+
$$
109+
110+
## breaking the crypto
111+
112+
*"By the ancient keys of cipher and code, I call forth Maximxls, the solver of enigmas, the breaker of chains! Let the digital winds carry my plea, and may your brilliance illuminate the darkest of cryptographic realms! Step forth, Maximxls, and let the challenges bow before your unyielding might!"*
113+
114+
My teammate @Maximxls helped me.
115+
116+
```python
117+
N=130461568758849331505036135484014114043992644491371593716583161711577760699108564114633147451910541252566337707008956518324286452684332343041659860224701474509989481060885383984791170320441956230799736307044647353927078847073897040487341909288238283432126001878262186673758416165688244773338744035128369896631
118+
p=12392410804664940156372899354158974363722257799949007097165521217543677200745499651061566614970955433161042932829539349273403482979240433003830514342967791
119+
q=10527537443298684008931726136394941222037097596968664524093225803422432813136644482794182361090071851369773768313662498322489166067545495287091879391949241
120+
d=120444591875645494952655925316390343931394097303056872878249907675954230604079583620792507844085742381960209697767320965885087077828791803343693331139593837503325523117472420712626828421711064671479313284923248605961051041209084189016216843605796126868866948541735980213741309193588914265655575714268414627473
121+
p*q == N: True
122+
```
123+
124+
## verification
125+
126+
Let's verify the author's uuid with his username.
127+
128+
```
129+
login_start name='__toad_'
130+
uuid='25f1fa23-0d8e-4e57-810d-e45f60c3bb74'
131+
```
132+
133+
[Minecraft UUID / Username Converter](https://mcuuid.net/?q=__toad_)
134+
135+
```
136+
25f1fa23-0d8e-4e57-810d-e45f60c3bb74
137+
```
138+
139+
Yay it is correct!
140+
141+
## decoding
142+
143+
I got stuck here for 1 entire day.
144+
145+
I was able to salvage some info though.
146+
147+
![image.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1736724521/2025/01/5a25efd5f9d0e4ffa410e702bf1945ce.png)
148+
149+
The chunks are intact in a ring pattern, not sure why.
150+
151+
The centre dots are blocks changed in block events.
152+
153+
I am forever getting bad packets, those packets start with bad lengths, causing the errors to cascade due to it consuming bytes of the next packet.
154+
155+
For example, right after compression headers start, I have to manually remove some data or it won't parse correctly (packet id not recognized.)
156+
157+
```python
158+
if direction == 'upstream' and lost == b'/\x80-\x9be\x13,r)\x0f_\xf4\x9bs\x1f\x12:brand\x07vanilla\x10\x00\x00\x05en_us\x16\x00\x01\x7f\x01\x01\x01\x00g\xf3\xce\x06\xef\x8a\xc7\x84\xf4\xf4\xfe\xc9\x9d\x9f\x9fjore\x061.21.4':
159+
recv_buff.add(b'\x10\x00\x00\x05en_us\x16\x00\x01\x7f\x01\x01\x01\x00g\xf3\xce\x06\xef\x8a\xc7\x84\xf4\xf4\xfe\xc9\x9d\x9f\x9fjore\x061.21.4')
160+
recv_buff.save()
161+
```
162+
163+
```
164+
loading... kernel32
165+
loading... kernel32
166+
167+
--- upstream init --- s_set_protocol 0x0
168+
handshake proto=769 localhost:25565 state=2
169+
170+
--- upstream login --- s_login_start 0x0
171+
login_start name='__toad_'
172+
173+
--- downstream login --- c_encryption_begin 0x1
174+
login_encryption_request p_server_id='' len(p_public_key)=162
175+
validate keys True
176+
p_verify_token.hex()='0ec1d269'
177+
178+
--- upstream login --- s_encryption_begin 0x1
179+
180+
--- downstream login --- c_compress 0x3
181+
login_set_compression threshold=256
182+
183+
--- downstream login --- c_success 0x2
184+
uuid='25f1fa23-0d8e-4e57-810d-e45f60c3bb74'
185+
name='__toad_'
186+
property_name='textures'
187+
property_value='ewogICJ0aW1lc3RhbXAiIDogMTczNDMwMjY5MjE3OSwKICAicHJvZmlsZUlkIiA6ICIyNWYxZmEyMzBkOGU0ZTU3ODEwZGU0NWY2MGMzYmI3NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJfX3RvYWRfIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzUwMWY1YmE1YTM2OGYwNTQ2OGU2NzU4MDg2NDQ0ZmUzMzEwZmIxODkxMjczZjAwNGEwODRmMTA4OGRlMWVkZDQiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=='
188+
property_signature='sYCm/hA/Dr7fX0N5lpFGfrAF2KM7pEs5XLnoVoSeby745lHmVLT7U6LYp4xd0dpPbM4WcYYiM5F5H92GMDz/HhXY9GJYTG03t3KKMPbzj2xoAw6VfyJ96BrBqgpldYb8s0nc6jQhj+d9pETmFXaVtb1Fu2tERJiOy5Ms7QvOMQmqg4eEU7LS+X3oFggQ3vMrCYt/di6jSPc2o0IK1nPLwxzDzY7sPZUq8RgbZIAiqxoU6vLoChUfzCg1g8DLloICEwKaZiLSriSiINcwnLlkG6yBeQ2lDcOh85KKJlU1CR33YqoLI/wp+zTDdB6qkiOghYmmhRlInW97agwpdMJUFFpd6ohPnO6cpnxu1R9u9b0eSUMtp32TVAtULlDRgsgk1rvY8qn+VpJafIkaLXKwIFUTnndy4jS0hbKDsYpVdLxNRmnzLRDWbTb8gaZ7RJLGnDIeWb3/S7pRTj1UxQVmHMweHlu5xvR3Z4AVmsF/5cxvJgDD0VRLAuPSCDOpyutHy+eIIBqLiyA8Xs2U79GXDnouJAP1flDFwgYXFQ0uTTem6wrsZlRxXZxX+LnlezV80jLlogTbpNxFLJV76gjQgl9i19RJ90rQPIvoLnOUGYY4iZioBjAiJrr5beI3AOPZ0DDNdU6JHwTE8bXDYgUkxodNMazuburfFdKqg3YwvYE='
189+
190+
(data fix skipped 73 bytes)
191+
192+
--- upstream configuration --- s_settings 0x0
193+
locale en_us
194+
view_distance 22
195+
chat_mode 0
196+
chat_colors True
197+
displayed_skin_parts 127
198+
main_hand 1
199+
enable_text_filtering True
200+
allow_server_listing True
201+
particle 0
202+
203+
(data fix skipped 27 bytes)
204+
205+
--- downstream configuration --- c_feature_flags 0xc
206+
207+
--- downstream configuration --- c_select_known_packs 0xe
208+
downstream configuration KeyError losing 3069 bytes
209+
b'f>\x94K\x8a\xba#\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\...
210+
663e944b8aba239efb5a3f084fc8f44d090cf300a6a628e607d8b3e34b5cea...
211+
possible zip at 746 b'\x07\x16minecraft:trim_pattern\x12\x0eminecraft:bolt\x00\x0fminecraf'
212+
possible zip at 1304 b'\x07\x1aminecraft:painting_variant2\x0fminecraft:alban\x00\x0fmin'
213+
possible zip at 1773 b'\x07\x15minecraft:damage_type1\x0fminecraft:arrow\x00\x1bminecraf'
214+
possible zip at 2194 b'\x07\x18minecraft:banner_pattern+\x0eminecraft:base\x00\x10minecr'
215+
possible zip at 2527 b'\x07\x15minecraft:enchantment*\x17minecraft:aqua_affinity\x00\x1c'
216+
possible zip at 2912 b'\x07\x16minecraft:jukebox_song\x13\x0cminecraft:11\x00\x0cminecraft:'
217+
218+
--- downstream configuration --- c_registry_data 0x7
219+
220+
--- downstream configuration --- c_tags 0xd
221+
```
222+
223+
`KeyError` means the packet id is not recognized.
224+
225+
I don't know why that is the case, due to me fixing the data on previous packets this shouldn't happen but for some reason it does.
226+
227+
I had to manually sieve through all the broken packets to guess where the next packet should start.
228+
229+
```
230+
loading... kernel32
231+
varint(0) 102 0x66 b'>\x94K\x8a\xba#\x9e\xfbZ?\x08O\xc8\xf4M' ?
232+
varint(1) 62 0x3e b'\x94K\x8a\xba#\x9e\xfbZ?\x08O\xc8\xf4M\t' ?
233+
varint(2) 9620 0x2594 b'\x8a\xba#\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3' ?
234+
varint(3) 75 0x4b b'\x8a\xba#\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3' ?
235+
varint(4) 580874 0x8dd0a b'\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6' ?
236+
varint(5) 4538 0x11ba b'\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6' ?
237+
varint(6) 35 0x23 b'\x9e\xfbZ?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6' ?
238+
varint(7) 1490334 0x16bd9e b'?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07' ?
239+
varint(8) 11643 0x2d7b b'?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07' ?
240+
varint(9) 90 0x5a b'?\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07' ?
241+
varint(10) 63 0x3f b'\x08O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8' ?
242+
varint(11) 8 0x8 b'O\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3' ?
243+
varint(12) 79 0x4f b'\xc8\xf4M\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3' ?
244+
varint(13) 1276488 0x137a48 b'\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xea' ?
245+
varint(14) 9972 0x26f4 b'\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xea' ?
246+
varint(15) 77 0x4d b'\t\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xea' ?
247+
varint(16) 9 0x9 b'\x0c\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xeaX' ?
248+
varint(17) 12 0xc b'\xf3\x00\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9d' c_feature_flags
249+
array
250+
251+
varint(18) 115 0x73 b'\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9dx\xda' ?
252+
varint(19) 0 0x0 b'\xa6\xa6(\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9dx\xda' ?
253+
varint(20) 660262 0xa1326 b'\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9dx\xda\xaf\x92\x9c' ?
254+
varint(21) 5158 0x1426 b'\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9dx\xda\xaf\x92\x9c' ?
255+
varint(22) 40 0x28 b'\xe6\x07\xd8\xb3\xe3K\\\xeaX\x9dx\xda\xaf\x92\x9c' ?
256+
varint(23) 998 0x3e6 b'\xd8\xb3\xe3K\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a' ?
257+
varint(24) 7 0x7 b'\xd8\xb3\xe3K\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a' c_registry_data
258+
string array
259+
260+
varint(25) 158915032 0x978d9d8 b'\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a\xaf\xc7\x8d\x93' ?
261+
varint(26) 1241523 0x12f1b3 b'\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a\xaf\xc7\x8d\x93' ?
262+
varint(27) 9699 0x25e3 b'\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a\xaf\xc7\x8d\x93' ?
263+
varint(28) 75 0x4b b'\\\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a\xaf\xc7\x8d\x93' ?
264+
varint(29) 92 0x5c b'\xeaX\x9dx\xda\xaf\x92\x9c\xb8\x9a\xaf\xc7\x8d\x93j' ?
265+
```
266+
267+
## find all
268+
269+
The crypto part is definitely working due to correct exchange of bytes right after the encryption packets.
270+
271+
So instead I dumped all info to `client.all.txt` and `server.all.txt` and simply looked for all matching headers.
272+
273+
For uncompressed data I couldn't find anything useful.
274+
### zlib header
275+
276+
```python
277+
p = re.compile(rb'\x78[\x01\x5e\x9c\xda]')
278+
```
279+
280+
For each point I will run the following checks to ensure it is indeed a valid packet.
281+
Then I will handle it.
282+
283+
```python
284+
s1, s2 = varint_lookback(by, st-1, 2)
285+
buff = Buffer(by[s2:])
286+
# print('varints', by[s2:s1].hex(), by[s1:st].hex())
287+
packet_len = buff.unpack_varint()
288+
buff = Buffer(buff.read(packet_len))
289+
data_len = buff.unpack_varint()
290+
data = buff.read()
291+
data = zlib.decompress(data)
292+
if 'uoft' in data:
293+
print(data)
294+
exit()
295+
assert len(data) == data_len
296+
unzipped_out.append(data)
297+
buff = Buffer(data)
298+
pid = buff.unpack_varint()
299+
name = get_packet_name(pid, 'play', direction)
300+
pids.add(pid)
301+
fn = globals().get('packet_'+name, None)
302+
if fn:
303+
fn(buff)
304+
else:
305+
print()
306+
print(hex(pid), name)
307+
print(f"{packet_len=} {data_len=}")
308+
print(buff.buff[:50])
309+
```
310+
311+
More chunks are salvaged, however a lot of data is still missing.
312+
313+
![image.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1736724771/2025/01/a8de8866ae7c1fb160b2c45387796737.png)
314+
315+
![image.png](https://res.cloudinary.com/kumonochisanaka/image/upload/v1736724962/2025/01/40f92cde7e061be655d611383c7d6364.png)
316+
317+
The individual block update events are combined with data I got before and mod 100.
318+
(some positions are y=-2539520, which is obviously wrong but I included them just in case)
319+
320+
## conclusion
321+
322+
I couldn't solve it.
323+
324+
I tried a lot of fixes but none of them work, the packets just keep getting misaligned.
325+
326+
Maybe it is coz the library I share much of the serialisation logic with has discontinued and is stuck in version 1.19, or maybe more steps are needed in the capture phase, like some tcp packets should be discarded or something.
327+
328+
Welp.

0 commit comments

Comments
 (0)