Skip to content

Instantly share code, notes, and snippets.

@jmceleney
Last active May 21, 2023 12:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmceleney/890532f8924e1e17048a0b427577ddd3 to your computer and use it in GitHub Desktop.
Save jmceleney/890532f8924e1e17048a0b427577ddd3 to your computer and use it in GitHub Desktop.
#!/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