cscg is loooong
This commit is contained in:
BIN
2026/cscg/forensics/slippery-slope/decrypted_capture.pcapng
Normal file
BIN
2026/cscg/forensics/slippery-slope/decrypted_capture.pcapng
Normal file
Binary file not shown.
112
2026/cscg/forensics/slippery-slope/extract_streams.py
Normal file
112
2026/cscg/forensics/slippery-slope/extract_streams.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import pyshark
|
||||||
|
import struct
|
||||||
|
import zlib
|
||||||
|
import os
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
|
||||||
|
# --- Constants ---
|
||||||
|
SESSION_KEY = bytes.fromhex("fb2ed4f1837a722b149df99a4021177e")
|
||||||
|
SESSION_ID_LE = struct.pack('<I', 0x392DECAA)
|
||||||
|
|
||||||
|
os.makedirs("smm2_streams", exist_ok=True)
|
||||||
|
|
||||||
|
print("[*] Loading PCAP via PyShark...")
|
||||||
|
cap = pyshark.FileCapture('./decrypted_capture.pcapng', display_filter='udp')
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
|
||||||
|
for i, pkt in enumerate(cap):
|
||||||
|
try:
|
||||||
|
udp_payload = bytes.fromhex(pkt.udp.payload.replace(':', ''))
|
||||||
|
|
||||||
|
if not udp_payload.startswith(b'\x32\xab\x98\x64'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
src_mac_str = pkt.wlan.sa.replace(':', '')
|
||||||
|
src_mac = bytes.fromhex(src_mac_str)
|
||||||
|
|
||||||
|
conn_id = udp_payload[5:6]
|
||||||
|
header_nonce = udp_payload[8:16]
|
||||||
|
tag = udp_payload[16:32]
|
||||||
|
ciphertext = udp_payload[32:]
|
||||||
|
|
||||||
|
crc = zlib.crc32(SESSION_ID_LE + src_mac)
|
||||||
|
|
||||||
|
decrypted = False
|
||||||
|
plaintext = b""
|
||||||
|
|
||||||
|
for crc_endian in ['>I', '<I']:
|
||||||
|
crc_bytes = struct.pack(crc_endian, crc)[:3]
|
||||||
|
iv = crc_bytes + conn_id + header_nonce
|
||||||
|
|
||||||
|
try:
|
||||||
|
cipher_gcm = Cipher(algorithms.AES(SESSION_KEY), modes.GCM(iv, tag))
|
||||||
|
decryptor = cipher_gcm.decryptor()
|
||||||
|
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
||||||
|
decrypted = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if decrypted:
|
||||||
|
# --- PIA Message Parser ---
|
||||||
|
offset = 0
|
||||||
|
msg_count = 0
|
||||||
|
|
||||||
|
# State variables (inherit from previous message if missing)
|
||||||
|
msg_flags = 0
|
||||||
|
payload_size = 0
|
||||||
|
protocol_type = 0
|
||||||
|
|
||||||
|
while offset < len(plaintext):
|
||||||
|
msg_count += 1
|
||||||
|
presence_flags = plaintext[offset]
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
if presence_flags & 1:
|
||||||
|
msg_flags = plaintext[offset]
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
if presence_flags & 2:
|
||||||
|
payload_size = struct.unpack('>H', plaintext[offset:offset+2])[0]
|
||||||
|
offset += 2
|
||||||
|
|
||||||
|
if presence_flags & 4:
|
||||||
|
protocol_type = plaintext[offset]
|
||||||
|
offset += 4 # Skip protocol type (1) and port (3)
|
||||||
|
|
||||||
|
if presence_flags & 8:
|
||||||
|
offset += 8 # Skip destination (8)
|
||||||
|
|
||||||
|
if presence_flags & 16:
|
||||||
|
offset += 8 # Skip source constant ID (8)
|
||||||
|
|
||||||
|
is_compressed = bool(msg_flags & 0x10)
|
||||||
|
|
||||||
|
# Extract the pure application payload
|
||||||
|
raw_payload = plaintext[offset:offset+payload_size]
|
||||||
|
|
||||||
|
if protocol_type != 0x81:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
if payload_size >= 0xB:
|
||||||
|
if is_compressed:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
with open(f"smm2_streams/pkt{i}_msg{msg_count}_proto{protocol_type:02x}.bin", "wb") as f:
|
||||||
|
f.write(raw_payload)
|
||||||
|
success_count += 1
|
||||||
|
|
||||||
|
# PIA Messages are always padded to 4-byte boundaries
|
||||||
|
offset += payload_size
|
||||||
|
align = (4 - (offset % 4)) % 4
|
||||||
|
offset += align
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cap.close()
|
||||||
|
print(f"\n[+] Finished! Extracted {success_count} payloads to 'smm2_streams' folder.")
|
||||||
10089
2026/cscg/forensics/slippery-slope/output.txt
Normal file
10089
2026/cscg/forensics/slippery-slope/output.txt
Normal file
File diff suppressed because it is too large
Load Diff
60
2026/cscg/forensics/slippery-slope/recover_tk.py
Normal file
60
2026/cscg/forensics/slippery-slope/recover_tk.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
LDN Data Frame Key Derivation for Mario Maker 2
|
||||||
|
Requires: pip install pycryptodome scapy
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
# ── Nintendo key constants ────────────────────────────────────────────────────
|
||||||
|
MASTER_KEY_00 = bytes.fromhex("c2caaff089b9aed55694876055271c7d")
|
||||||
|
AES_KEK_GENERATION_SRC = bytes.fromhex("4d870986c45d20722fba1053da92e8a9")
|
||||||
|
AES_KEY_GENERATION_SRC = bytes.fromhex("89615ee05c31b6805fe58f3da24f7aa8")
|
||||||
|
DATA_INPUT_KEY = bytes.fromhex("f1e7018419a84f711da714c2cf919c9c")
|
||||||
|
NINTENDO_OUI = bytes([0x00, 0x22, 0xAA])
|
||||||
|
|
||||||
|
# ── Game-specific ─────────────────────────────────────────────────────────────
|
||||||
|
PASSWORD = b"LunchPack2DefaultPhrase"
|
||||||
|
|
||||||
|
# ── AES-ECB helper ───────────────────────────────────────────────────────────
|
||||||
|
def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
|
||||||
|
return AES.new(key, AES.MODE_ECB).decrypt(data)
|
||||||
|
|
||||||
|
# ── Step-by-step key derivation ──────────────────────────────────────────────
|
||||||
|
def derive_data_key(server_random: bytes, password: bytes) -> bytes:
|
||||||
|
assert len(server_random) == 16, "server_random must be 16 bytes"
|
||||||
|
|
||||||
|
# Step 1: kek_key
|
||||||
|
kek_key = aes_ecb_decrypt(MASTER_KEY_00, AES_KEK_GENERATION_SRC)
|
||||||
|
print(f"[1] kek_key: {kek_key.hex()}")
|
||||||
|
|
||||||
|
# Step 2: access_key (decrypt the data input key with kek_key)
|
||||||
|
access_key = aes_ecb_decrypt(kek_key, DATA_INPUT_KEY)
|
||||||
|
print(f"[2] access_key: {access_key.hex()}")
|
||||||
|
|
||||||
|
# Step 3: generation_key
|
||||||
|
generation_key = aes_ecb_decrypt(access_key, AES_KEY_GENERATION_SRC)
|
||||||
|
print(f"[3] generation_key: {generation_key.hex()}")
|
||||||
|
|
||||||
|
# Step 4: hash input buffer = server_random || password
|
||||||
|
buf = server_random + password
|
||||||
|
hash_input = hashlib.sha256(buf).digest()[:16]
|
||||||
|
print(f"[4] SHA256(buf)[0:16]: {hash_input.hex()}")
|
||||||
|
|
||||||
|
# Step 4: final data frame key
|
||||||
|
data_key = aes_ecb_decrypt(generation_key, hash_input)
|
||||||
|
print(f"[★] data_frame_key: {data_key.hex()}")
|
||||||
|
|
||||||
|
return data_key
|
||||||
|
|
||||||
|
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
pcap_path = sys.argv[1] if len(sys.argv) > 1 else "./capture.pcapng"
|
||||||
|
|
||||||
|
server_random = bytes.fromhex("aaec2d390000000005180000796265e7")
|
||||||
|
|
||||||
|
data_key = derive_data_key(bytes.fromhex("d33f9c317a7f2b77a27b68e40f596f89"), PASSWORD)
|
||||||
|
|
||||||
|
print(f"tk: {data_key.hex()}")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user