Last active
May 21, 2023 12:49
-
-
Save jmceleney/890532f8924e1e17048a0b427577ddd3 to your computer and use it in GitHub Desktop.
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
#!/usr/bin/env python3 | |
# Author: John McEleney | |
# The main purpose of this script is to enable netmode4 on Xiaomi RB01 routers. | |
# To do this the script poses as a mesh slave, which causes the mesh master to enable netmode4. | |
# It does this by blindly sending HEX strings captured from an actual mesh negotiation between | |
# two RB01 routers. | |
# Enabling netmode is needed as one step in unlocking the router and flashing OpenWrt. | |
# The router should already have been taken through basic set-up before running this script. | |
# | |
# Parts of script based on https://raw.githubusercontent.com/YangWang92/AX6S-unlock/master/unlock_pwd.py | |
# credit goes to zhoujiazhao: | |
# https://blog.csdn.net/zhoujiazhao/article/details/102578244 | |
# | |
# Some code also taken from: | |
# https://github.com/scientifichackers/pymiwifi | |
# Usage: | |
# ./unlock_mi.py -p pAsSwOrd | |
# ./unlock_mi.py -p pAsSwOrd -H 192.168.31.1 | |
import sys | |
import argparse | |
import hashlib | |
import random | |
import time | |
import uuid | |
import requests | |
import ssl | |
import socket | |
if sys.version_info < (3,7): | |
print("python version is not supported", file=sys.stderr) | |
sys.exit(1) | |
parser = argparse.ArgumentParser(description="Unlock Xiaomi", | |
formatter_class=argparse.ArgumentDefaultsHelpFormatter) | |
parser.add_argument("-p", "--password", help="Web password") | |
parser.add_argument("-H", "--hostname", help="Hostname or IP", default='192.168.31.1') | |
parser.add_argument("-t", "--type", help="miwifi_type", default=0) | |
args = parser.parse_args() | |
config = vars(args) | |
PUBLIC_KEY = "a2ffa5c9be07488bbb04a3a47d3c5f6a" | |
salt = {'r1d': 'A2E371B0-B34B-48A5-8C40-A7133F3B5D88', | |
'others': 'd44fb0960aa0-a5e6-4a30-250f-6d2df50a'} | |
def sha1(x: str): | |
return hashlib.sha1(x.encode()).hexdigest() | |
def get_mac_address(): | |
as_hex = f"{uuid.getnode():012x}" | |
return ":".join(as_hex[i : i + 2] for i in range(0, 12, 2)) | |
def generate_nonce(miwifi_type=0): | |
return f"{miwifi_type}_{get_mac_address()}_{int(time.time())}_{int(random.random() * 1000)}" | |
def generate_password_hash(nonce, password): | |
return sha1(nonce + sha1(password + PUBLIC_KEY)) | |
def send_hex_string(ssl_sock, hex_string, byte_count=2048): | |
byte_string = bytes.fromhex(hex_string) | |
ssl_sock.send(byte_string) | |
response = ssl_sock.recv(byte_count) | |
return response | |
class MiWiFi: | |
def __init__(self, address="http://192.168.31.1/", miwifi_type=0): | |
if address.endswith("/"): | |
address = address[:-1] | |
self.address = address | |
self.token = None | |
self.miwifi_type = miwifi_type | |
self.router_ip = address.replace("http://","") | |
self.mesh_port = 19553 | |
def enable_telnet(self): | |
# Create an SSL context object and configure it for the client | |
ssl_context = ssl.create_default_context() | |
ssl_context.check_hostname = False | |
ssl_context.verify_mode = ssl.CERT_NONE | |
ssl_context.set_ciphers('DEFAULT') | |
# Create a TCP socket object | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# Wrap the socket with SSL encryption using the context object | |
ssl_sock = ssl_context.wrap_socket(sock, server_hostname=self.router_ip) | |
# Connect to the server | |
ssl_sock.connect((self.router_ip, self.mesh_port)) | |
send_hex_string(ssl_sock,'100100a3000438633a64653a66393a62663a35643a6236000038633a64653a66393a62663a35643a6237000061646435353662636461303730380000503151527567767a6d78746b35502f70316b2b46566a724a4c716d6568494546424a6563477062516a76383d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033433a43443a35373a32323a31433a36310000') | |
send_hex_string(ssl_sock,'10010020000538633a64653a66393a62663a35643a6236000038633a64653a66393a62663a35643a623700000100000000000000000000000000000000000000000000000000000000000000') | |
resp = send_hex_string(ssl_sock,'10010020000738633a64653a66393a62663a35643a6236000038633a64653a66393a62663a35643a62370000017265637620636f6e6669672073796e6320636f72726563746c792e0a000000') | |
ssl_sock.close() | |
return resp | |
def login(self, password): | |
nonce = generate_nonce(self.miwifi_type) | |
response = requests.post( | |
f"{self.address}/cgi-bin/luci/api/xqsystem/login", | |
data={ | |
"username": "admin", | |
"logtype": "2", | |
"password": generate_password_hash(nonce, password), | |
"nonce": nonce, | |
}, | |
) | |
if response.status_code == 200: | |
self.token = response.json()["token"] | |
return response | |
def get_api_endpoint(self, endpoint): | |
return requests.get( | |
f"{self.address}/cgi-bin/luci/;stok={self.token}/api/{endpoint}" | |
).json() | |
def status(self): | |
return self.get_api_endpoint("misystem/status") | |
def get_netmode(self): | |
return self.get_api_endpoint("xqnetwork/get_netmode") | |
def get_salt(sn): | |
if "/" not in sn: | |
return salt["r1d"] | |
return "-".join(reversed(salt["others"].split("-"))) | |
def calc_passwd(sn): | |
passwd = sn + get_salt(sn) | |
m = hashlib.md5(passwd.encode()) | |
return m.hexdigest()[:8] | |
if __name__ == "__main__": | |
if not config['password']: | |
parser.print_help() | |
sys.exit(2) | |
miwifi = MiWiFi(address="http://{}/".format(config['hostname']), miwifi_type=config['type']) | |
try: | |
miwifi.login(config['password']) | |
except: | |
print('Login failed - check host availability and password') | |
sys.exit(1) | |
status=miwifi.status() | |
#print(status) | |
serial = status['hardware']['sn'] | |
print("Serial Number: {}".format(serial)) | |
netmode = miwifi.get_netmode()['netmode'] | |
if netmode != 4: | |
print("netmode is {}. Attempting to set netmode to 4".format(netmode)) | |
response = miwifi.enable_telnet() | |
print("Response:\n{}\nWaiting to check result".format(response)) | |
time.sleep(5) | |
netmode = miwifi.get_netmode()['netmode'] | |
print("Netmode is {}".format(netmode)) | |
print("telnet password: {}".format(calc_passwd(serial))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment