64 lines
5.3 KiB
Python
64 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
# pip install pycryptodome
|
|
import hashlib
|
|
from Crypto.Cipher import AES
|
|
|
|
AES_KEK_GENERATION_SOURCE = bytes.fromhex("4d870986c45d20722fba1053da92e8a9")
|
|
AES_KEY_GENERATION_SOURCE = bytes.fromhex("89615ee05c31b6805fe58f3da24f7aa8")
|
|
|
|
# Advertisement frame input key (from kinnay wiki)
|
|
ADV_INPUT_KEY = bytes.fromhex("191884743e24c77d87c69e4207d0c438")
|
|
# Data frame input key
|
|
DATA_INPUT_KEY = bytes.fromhex("f1e7018419a84f711da714c2cf919c9c")
|
|
|
|
def aes_unwrap(data: bytes, key: bytes) -> bytes:
|
|
return AES.new(key, AES.MODE_ECB).decrypt(data)
|
|
|
|
def derive_key(master_key_00: bytes, input_key: bytes, input_buffer: bytes) -> bytes:
|
|
kek = aes_unwrap(AES_KEK_GENERATION_SOURCE, master_key_00)
|
|
kek2 = aes_unwrap(input_key, kek)
|
|
kek3 = aes_unwrap(AES_KEY_GENERATION_SOURCE, kek2)
|
|
hash16 = hashlib.sha256(input_buffer).digest()[:16]
|
|
return aes_unwrap(hash16, kek3)
|
|
|
|
def decrypt_advertisement(master_key_00: bytes, raw_frame: bytes):
|
|
# Advertisement payload starts at byte 0x45 (after radiotap + 802.11 + action header)
|
|
adv_payload = raw_frame[0x45:]
|
|
|
|
session_info = adv_payload[0x00:0x20] # 32 bytes, plaintext
|
|
nonce_bytes = adv_payload[0x24:0x28] # 4-byte nonce
|
|
encrypted = adv_payload[0x28:] # hash + advertisement data (encrypted)
|
|
|
|
# Derive advertisement decryption key using session_info as input buffer
|
|
adv_key = derive_key(master_key_00, ADV_INPUT_KEY, session_info)
|
|
|
|
# AES-CTR: nonce is 4 bytes, padded to 16 with zeros, counter starts at 1
|
|
ctr_nonce = nonce_bytes + b'\x00' * 8 # 12-byte nonce
|
|
cipher = AES.new(adv_key, AES.MODE_CTR, nonce=ctr_nonce, initial_value=1)
|
|
decrypted = cipher.decrypt(encrypted)
|
|
|
|
# Layout: 32-byte hash, then advertisement data
|
|
# Advertisement data[0x00:0x10] = server random
|
|
server_random = decrypted[32:48]
|
|
return session_info, server_random
|
|
|
|
def derive_data_key(master_key_00: bytes, server_random: bytes, passphrase: str) -> bytes:
|
|
input_buffer = server_random + passphrase.encode()
|
|
return derive_key(master_key_00, DATA_INPUT_KEY, input_buffer)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
master_key_00 = bytes.fromhex("c2caaff089b9aed55694876055271c7d")
|
|
passphrase = "LunchPack2DefaultPhrase"
|
|
|
|
raw_frame = bytes.fromhex(
|
|
"000021002a4008a0200800a02008000000009e098004e6000000070003de00e601d0000000ffffffffffff98b6e9038247ffffffffffffe0cb7f0022aa040001010000000001009b90006dc000000000010000000004dd5b26140f143755d0adea03aad9b004020500217be113cb40f9c24b5707e06d1eab6454724bed49112bb42b9634816fd1583b0ea1ad6a47c1df57f4fb07b2c60cd447707a0c02e3f4b7f238559e8493a42814071c0423ac6d5c41767c3573e7c4dc406ba6e81a7a6c0a30c6f99a1a7b29e42c6f0cefd4add4436c68fc622a5a27ae772e3b5959d785bd6af34f8f01d069385efb2d75281a69d7f4e2b0e42cf3fdc5d9ba7fef1b7484a77c38437e4e0b22b1d31d9f6cbacfd508e7dc85d1902094102a7296672d4bd1b3481cd1a137228fecb22c1798fb828f21a8c785f4487f0d02f8e0fb64b19f967c606f048cda44673b940444c3e8ead46871ef72ba21b6786739948b6bdb04be9ef3612207f5be747d08789ad4c98a4d6765050661f96cfbc36f771d3eb5738b164ae167297c34e1ea23fb90f11826e6bd41182160a9b38168b8a7a4473b9b016322bce2bb4715d1b940357fc277c71121fb05cacc14560a3e459dcc631054d95908b178a5a7a2387922147f8cc90a91875d337d24c7ac3fcdb8c4c8c0bd18722d23f77a2aafebbb4dc458d725491875e40c21605ab76ff23bf66572d0898c9b002e4576d9552471599a39438f7ffe053f9861a7342f4afecc0020c788b69b723b03a6a44f0f15cfc2b806e70d738584ede19fd3324939ad3075a309247be67d7389bb1ff6f9c89ba801fe942c6134553315440f19702e9ad54f8b981d668566bc349dbedff418dadcc21bf70cdf7342addcba7f639b347be49d696d993fe67ca9537b52f153ac80dd657b9006a637b69e0ac9b6bb4e47c7eaad76b162710c1ebaa23664e564aed69ef6549cecb4606b957bdb2fba5c46399faf9bc0bc0e9aa5aaec6c9c97b7c52b10103529dcc7ee5252a83b705981a8ebbc4452405eec83f7077f2616ea359d327daf5f7ca53aab6da258d9fd08d719ef60315d1ab1d678ca07cc0344e88e9bec226332b105877109c297bf8d0c7c87337f422031488883cf24ea44b40783ad22bdd67bceb97783056107e666cd34f5047d23a4e1d226f8896285ad2f7b92767a84a7dfefecad281f759ac8975072687d1cc175672c92a87c1fb8f6eae33c80a1609015f9c12e799c548369fe5a25da28009330732ac5b73ece4805c9ffc3a10a22569630c410bcd22d65965927c292359c3dcf69dc7e39a1cf4b60afc65b6a95ab501c63ece017b8f68c3fa98fbf09236ada72214f2d560ac7da7b5720bf9eedcf1377072506259968c1c961b0253743630bb698003f61fa32bd712b49bb6a0daff159eccc7d0a19967ffc1fb01a861fdd076925bcab7d171c4dd561c6988a37d3b609e14550e5da78c0859ed2dbf71e43d5d681ce09be726b224b15a444acb90962d3d22d30006a1da94f20bfcf9f7a4977b98026a6d250d879c2fec5f8849c54896600fc4252bcbf188c3c1c2ecb872ece09d6b8ad707567630f8aad89982bf0d5186b6d91ee4ffb236b5dbb75a79610ac9a24c4c70a40e011f74c1de15cb31381819458f4d2215e6dc554833aaec12a0847a779b1707e6b79d82cc9ce90d36b15805a4c056ead365bf3587423e6adff5a402c69772c98ef33905848c51e897b342c993c013b74b03cd11773e8ee18a750faa68833d360992e9c9f3fdc2282736ecc361886900349bb90f47b382b78b10847ef57a4d6fb499dcbb401fc1e346f41069f8ede4f00ffc3d5a4efa085f3a2d3c9630e643c03bec4536f883f44ae7304d94e8b100e0ebc414a86e17fda4b742fba5f34ad2c397da51972208bb89f6a6ea8927103a305b467d90e8bb70b7bbaeb0210d729f7aba685d7f543ed21925f5ae3e82362c6daa6db4c5d05187e10741debc4273bdb710af4195fa6ed33a8ff62ec39de4e"
|
|
)
|
|
|
|
session_info, server_random = decrypt_advertisement(master_key_00, raw_frame)
|
|
print(f"Session info: {session_info.hex()}")
|
|
print(f"Server random: {server_random.hex()}")
|
|
|
|
data_key = derive_data_key(master_key_00, server_random, passphrase)
|
|
print(f"Data frame key: {data_key.hex()}")
|