Last active
January 29, 2025 19:38
-
-
Save Leo-PL/051a0b2ba26c3495afa47746e4a809cd to your computer and use it in GitHub Desktop.
Delta update finder for ZTE MF283+, MF286, MF286A and MF286D routers
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/python3 | |
# Original script by frutis, mods by Leo-PL | |
# Usage: | |
# 1. uncomment proper section for your router (MF283+, MF286, MF286A, MF286D). | |
# 2. set correct IME if you have one | |
# 3. Set correct version ID (the integrate_version from NVRAM) for your device if not already found here | |
# 4. Run the script - it shall return the URL of data update, if available for the set base version. | |
from base64 import b64decode, b64encode | |
from hashlib import md5 | |
from secrets import choice | |
from string import printable, digits | |
import requests | |
from xml.dom.minidom import parseString | |
#import logging | |
#import http.client as http_client | |
#http_client.HTTPConnection.debuglevel = 1 | |
#logging.basicConfig() | |
#logging.getLogger().setLevel(logging.DEBUG) | |
#requests_log = logging.getLogger('requests.packages.urllib3') | |
#requests_log.setLevel(logging.DEBUG) | |
#requests_log.propagate = True | |
# MF286D | |
#model = 'MF286D' | |
#TELIA | |
#firmware = 'TELIA_MF286DV1.0.0B10' | |
#ELISA | |
#firmware = 'ELISA_FI_MF286DV1.0.0B03' | |
#TIM | |
#firmware = 'TIM_IT_MF286DV1.0.0B07' | |
#PLAY | |
#firmware = 'NTT_PL_MF286DV1.0.0B02' | |
#imei_start = '86675404' | |
#URI = 'https://dmeu.ztems.com/zxmdmp/dm' | |
# MF286A | |
#model = 'MF286' | |
# Tele2 LT | |
#firmware = 'TELE2_LT_QMF286AV1.0.0B01' | |
#imei_start = '86821902' | |
#URI = 'https://dmeu.ztems.com/zxmdmp/dm' | |
# MF286 | |
#model = 'MF286' | |
# EN_EEU (Orange PL) | |
#firmware = 'EN_EEU_MF286V1.0.1B01' | |
#imei_start = '86821902' | |
#URI = 'http://dms.ztems.com/zxmdmp/dm' | |
# MF283+ | |
model = 'MF283PLUS' | |
firmware = 'EN_ORA_PL_MF283+V1.0.0B12' | |
#firmware = 'EN_ORA_PL_MF283+V1.0.0B06' | |
#firmware = 'EN_PL_MF283PLUSV1.0.0B02' | |
imei_start = '864780021' | |
URI = 'http://dms.ztems.com/zxmdmp/dm' | |
#request_type = 'devicerequest' | |
request_type = 'userrequest' | |
username = 'ZTEDM' | |
password = 'ZTEDM' | |
def imei_get(number): | |
while len(number) < 14: | |
number += choice(digits) | |
alphabet='0123456789' | |
imei = number + alphabet[0] | |
n = len(alphabet) | |
imei = tuple(alphabet.index(i) | |
for i in reversed(str(imei))) | |
check_digit = (sum(imei[::2]) + | |
sum(sum(divmod(i * 2, n)) | |
for i in imei[1::2])) % n | |
number += alphabet[-check_digit] | |
return number | |
def nonce_get(): | |
nonce = ''.join((choice(printable) for x in range(16))) | |
return b64encode(nonce.encode('utf-8')).decode('utf-8') | |
def hmac_get(nonce, body, username, password, mark=':'): | |
auth = username + mark + password | |
mark_enc = mark.encode('utf-8') | |
auth_b64 = b64encode(md5(auth.encode('utf-8')).digest()) | |
nonce_md5 = b64decode(nonce) | |
body_b64 = b64encode(md5(body.encode('utf-8')).digest()) | |
hmac = auth_b64 + mark_enc + nonce_md5 + mark_enc + body_b64 | |
return b64encode(md5(hmac).digest()).decode('utf-8') | |
def body_get(body): | |
xml = '' | |
for line in body.splitlines(): | |
xml += line.strip() | |
return xml | |
imei = imei_get(imei_start) | |
print(f'Model: {model}') | |
print(f'Firmware: {firmware}') | |
print(f'IMEI: {imei}') | |
try: | |
body1 = f'''<?xml version="1.0" encoding="UTF-8"?> | |
<SyncML xmlns='SYNCML:SYNCML1.2'> | |
<SyncHdr> | |
<VerDTD>1.2</VerDTD> | |
<VerProto>DM/1.2</VerProto> | |
<SessionID>3E8</SessionID> | |
<MsgID>1</MsgID> | |
<Target> | |
<LocURI>{URI}</LocURI> | |
</Target> | |
<Source> | |
<LocURI>IMEI:{imei}</LocURI> | |
</Source> | |
<Meta> | |
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize> | |
</Meta> | |
</SyncHdr> | |
<SyncBody> | |
<Alert> | |
<CmdID>1</CmdID> | |
<Data>1226</Data> | |
<Item> | |
<Meta> | |
<Format xmlns='syncml:metinf'>int</Format> | |
<Type xmlns='syncml:metinf'>org.openmobilealliance.dm.firmwareupdate.{request_type}</Type> | |
<Mark xmlns='syncml:metinf'>indeterminate</Mark> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
</Alert> | |
<Alert> | |
<CmdID>2</CmdID> | |
<Data>1201</Data> | |
</Alert> | |
<Replace> | |
<CmdID>3</CmdID> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/Correlator</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/ErrCode</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>int</Format> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/InnerV</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data></Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/CompileT</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data></Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>node</Format> | |
</Meta> | |
<Data>CompileT/InnerV/ErrCode/Correlator</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/DmV</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>1.2</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Lang</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>en</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/DevId</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>IMEI:{imei}</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Man</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>ZTE</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Mod</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>{model}</Data> | |
</Item> | |
</Replace> | |
<Final/> | |
</SyncBody> | |
</SyncML>''' | |
body = body_get(body1) | |
hmac = hmac_get(nonce_get(), body, username, password) | |
headers = {'Accept-Charset': 'utf-8', | |
'Accept-Language': 'en', | |
'Connection': 'close', | |
'Cache-Control': 'no-cache', | |
'Content-Type': 'application/vnd.syncml.dm+xml', | |
'x-syncml-hmac': f'algorithm=MD5, username="{username}", mac={hmac}', | |
'User-Agent': None, | |
'Accept-Encoding': None} | |
response = requests.post(URI, data=body, headers=headers) | |
response.raise_for_status() | |
resp = parseString(response.text) | |
resp_uri = resp.getElementsByTagName('RespURI')[0].firstChild.nodeValue.replace('&', '&') | |
nonce = resp.getElementsByTagName('NextNonce')[0].firstChild.nodeValue | |
body2 = f'''<?xml version="1.0" encoding="UTF-8"?> | |
<SyncML xmlns='SYNCML:SYNCML1.2'> | |
<SyncHdr> | |
<VerDTD>1.2</VerDTD> | |
<VerProto>DM/1.2</VerProto> | |
<SessionID>3E8</SessionID> | |
<MsgID>2</MsgID> | |
<Target> | |
<LocURI><![CDATA[{resp_uri}]]></LocURI> | |
</Target> | |
<Source> | |
<LocURI>IMEI:{imei}</LocURI> | |
</Source> | |
<Meta> | |
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize> | |
</Meta> | |
</SyncHdr> | |
<SyncBody> | |
<Status> | |
<CmdID>1</CmdID> | |
<MsgRef>1</MsgRef> | |
<CmdRef>0</CmdRef> | |
<Cmd>SyncHdr</Cmd> | |
<TargetRef>IMEI:{imei}</TargetRef> | |
<SourceRef>{URI}</SourceRef> | |
<Chal> | |
<Meta> | |
<Format xmlns='syncml:metinf'>b64</Format> | |
<Type xmlns='syncml:metinf'>syncml:auth-MAC</Type> | |
<NextNonce xmlns='syncml:metinf'>{nonce_get()}</NextNonce> | |
</Meta> | |
</Chal> | |
<Data>401</Data> | |
</Status> | |
<Alert> | |
<CmdID>2</CmdID> | |
<Data>1226</Data> | |
<Item> | |
<Meta> | |
<Format xmlns='syncml:metinf'>int</Format> | |
<Type xmlns='syncml:metinf'>org.openmobilealliance.dm.firmwareupdate.{request_type}</Type> | |
<Mark xmlns='syncml:metinf'>indeterminate</Mark> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
</Alert> | |
<Alert> | |
<CmdID>3</CmdID> | |
<Data>1201</Data> | |
</Alert> | |
<Replace> | |
<CmdID>4</CmdID> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/Correlator</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/ErrCode</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>int</Format> | |
</Meta> | |
<Data>0</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/InnerV</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data></Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext/CompileT</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data></Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Ext</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>node</Format> | |
</Meta> | |
<Data>CompileT/InnerV/ErrCode/Correlator</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/DmV</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>1.2</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Lang</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>en</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/DevId</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>IMEI:{imei}</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Man</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>ZTE</Data> | |
</Item> | |
<Item> | |
<Source> | |
<LocURI>./DevInfo/Mod</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>{model}</Data> | |
</Item> | |
</Replace> | |
<Final/> | |
</SyncBody> | |
</SyncML>''' | |
body = body_get(body2) | |
hmac = hmac_get(nonce, body, username, password) | |
headers['x-syncml-hmac'] = f'algorithm=MD5, username="{username}", mac={hmac}' | |
response = requests.post(resp_uri, data=body, headers=headers) | |
response.raise_for_status() | |
resp = parseString(response.text) | |
nonce = resp.getElementsByTagName('NextNonce')[0].firstChild.nodeValue | |
body3 = f'''<?xml version="1.0" encoding="UTF-8"?> | |
<SyncML xmlns='SYNCML:SYNCML1.2'> | |
<SyncHdr> | |
<VerDTD>1.2</VerDTD> | |
<VerProto>DM/1.2</VerProto> | |
<SessionID>3E8</SessionID> | |
<MsgID>3</MsgID> | |
<Target> | |
<LocURI><![CDATA[{resp_uri}]]></LocURI> | |
</Target> | |
<Source> | |
<LocURI>IMEI:{imei}</LocURI> | |
</Source> | |
<Meta> | |
<MaxMsgSize xmlns='syncml:metinf'>5000</MaxMsgSize> | |
</Meta> | |
</SyncHdr> | |
<SyncBody> | |
<Status> | |
<CmdID>1</CmdID> | |
<MsgRef>2</MsgRef> | |
<CmdRef>0</CmdRef> | |
<Cmd>SyncHdr</Cmd> | |
<TargetRef>IMEI:{imei}</TargetRef> | |
<SourceRef><![CDATA[{resp_uri}]]></SourceRef> | |
<Chal> | |
<Meta> | |
<Format xmlns='syncml:metinf'>b64</Format> | |
<Type xmlns='syncml:metinf'>syncml:auth-MAC</Type> | |
<NextNonce xmlns='syncml:metinf'>{nonce_get()}</NextNonce> | |
</Meta> | |
</Chal> | |
<Data>200</Data> | |
</Status> | |
<Status> | |
<CmdID>2</CmdID> | |
<MsgRef>2</MsgRef> | |
<CmdRef>5</CmdRef> | |
<Cmd>Get</Cmd> | |
<TargetRef>./DevDetail/SwV</TargetRef> | |
<Data>200</Data> | |
</Status> | |
<Results> | |
<CmdID>3</CmdID> | |
<MsgRef>2</MsgRef> | |
<CmdRef>5</CmdRef> | |
<Item> | |
<Source> | |
<LocURI>./DevDetail/SwV</LocURI> | |
</Source> | |
<Meta> | |
<Format xmlns='syncml:metinf'>chr</Format> | |
</Meta> | |
<Data>{firmware}</Data> | |
</Item> | |
</Results> | |
<Final/> | |
</SyncBody> | |
</SyncML>''' | |
body = body_get(body3) | |
hmac = hmac_get(nonce, body, username, password) | |
headers['x-syncml-hmac'] = f'algorithm=MD5, username="{username}", mac={hmac}' | |
response = requests.post(resp_uri, data=body, headers=headers) | |
response.raise_for_status() | |
resp = parseString(response.text) | |
resp_replace = resp.getElementsByTagName('Replace') | |
if resp_replace: | |
data_uri = resp_replace[0].getElementsByTagName('Data')[1].firstChild.nodeValue.replace('&', '&') | |
response = requests.get(data_uri, headers=headers) | |
response.raise_for_status() | |
resp = parseString(response.text) | |
firm_desc = resp.getElementsByTagName('description')[0].firstChild.nodeValue | |
firm_uri = resp.getElementsByTagName('objectURI')[0].firstChild.nodeValue.replace('&', '&') | |
print(f'\nDescription: {firm_desc}') | |
print(f'URI: {firm_uri}') | |
else: | |
print('\nNothing found') | |
except requests.exceptions.HTTPError as http_error: | |
print(f'HTTP error: {http_error}') | |
except Exception as error: | |
print(f'Error: {error}') |
Frankly - no idea, I haven't touched the script for quite some time, and I'm not the original author either. But it stopped working for me as well, at least for MF286D:
HTTP error: 405 Client Error: Not Allowed for url: https://dmeu.ztems.com/zxmdmp/dm
Script fixed, check my fork!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hm, this doesn't appear to work anymore as it now returns "Nothing found" when run exactly as shown here. Any idea what ZTE could've changed?