Skip to content

Instantly share code, notes, and snippets.

@dixyes
Last active September 11, 2020 14:46
Show Gist options
  • Save dixyes/b76703a426f1a91fe9c7c05465496629 to your computer and use it in GitHub Desktop.
Save dixyes/b76703a426f1a91fe9c7c05465496629 to your computer and use it in GitHub Desktop.
zimifi router SMS receive / send
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# test python file for zmifi MF855
# using default password zimifi
# WTFPL
import re, hashlib, random, json, datetime
import requests
from collections import defaultdict
from xml.etree import ElementTree as ET
# from https://stackoverflow.com/questions/2148119/how-to-convert-an-xml-string-to-a-dictionary-in-python#10077069
def et2dict(t):
if isinstance(t, str):
t = ET.fromstring(t)
d = {t.tag: {} if t.attrib else None}
children = list(t)
if children:
dd = defaultdict(list)
for dc in map(et2dict, children):
for k, v in dc.items():
dd[k].append(v)
d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}}
if t.attrib:
d[t.tag].update(('@' + k, v) for k, v in t.attrib.items())
if t.text:
text = t.text.strip()
if children or t.attrib:
if text:
d[t.tag]['#text'] = text
else:
d[t.tag] = text
return d
def dict2str(d):
def _to_etree(d, root):
if not d:
pass
elif isinstance(d, str):
root.text = d
elif isinstance(d, dict):
for k,v in d.items():
assert isinstance(k, str)
if k.startswith('#'):
assert k == '#text' and isinstance(v, str)
root.text = v
elif k.startswith('@'):
assert isinstance(v, str)
root.set(k[1:], v)
elif isinstance(v, list):
for e in v:
_to_etree(e, ET.SubElement(root, k))
else:
_to_etree(v, ET.SubElement(root, k))
else:
raise TypeError('invalid type: ' + str(type(d)))
assert isinstance(d, dict) and len(d) == 1
tag, body = next(iter(d.items()))
node = ET.Element(tag)
_to_etree(body, node)
return ET.tostring(node)
fuckyEncode = lambda s : "".join(["%04x"%ord(x) for x in s])
_re2b = re.compile(r"([0-9a-fA-F]{4})")
fuckyDecode = lambda s : "".join([chr(int(x,16)) for x in _re2b.findall(s)])
commonHeaders = {
'Expires': '-1',
'Cache-Control': 'no-store, no-cache, must-revalidate',
'Pragma': 'no-cache',
'Content-Type': 'application/xml',
'Content-Encoding': 'UTF-8',
}
class Client():
def __init__(self, svr='http://192.168.21.1'):
self.svr = svr
self.nc = 0
self.nonce = None
self._HA1 = None
self.logedin = False
def getNonce(self):
r = requests.get("%s/login.cgi"%(self.svr))
wAuth = r.headers['WWW-Authenticate']
self.nonce = re.search(r"nonce=\"([A-Za-z0-9]+)\"", wAuth).groups()[0]
realm = re.search(r"realm=\"([A-Za-z0-9]+)\"", wAuth).groups()[0]
qop = re.search(r"qop=\"(auth|auth-int)\"", wAuth).groups()[0]
#print("got qop is %s, this should be auth"%qop)
#print("got realm is %s, this should be Highwmg"%realm)
def mkResponse(self, method, loc, cnonce):
if not self._HA1:
raise Exception("no password provide, login first!")
A2 = "%s:%s" % (method, loc) # A2 : method:URI
HA2 = hashlib.md5(A2.encode()).hexdigest()
self.nc += 1
#print(A1)
#print(A2)
restext = "%s:%s:%08x:%s:%s:%s"%(self._HA1, self.nonce, self.nc, cnonce, "auth", HA2)
#print(restext,cnonce)
response = hashlib.md5(restext.encode()).hexdigest()
#print("response is", response)
return response
def login(self,passwd="zimifi"):
A1 = "%s:%s:%s" % ("admin", "Highwmg", passwd) # A1 : username:realm:password
self._HA1 = hashlib.md5(A1.encode()).hexdigest()
#make location
cnonce = "%08x"%random.randint(0,0xffffffff)
respL = self.mkResponse("GET","/cgi/protected.cgi",cnonce)
loc = "/login.cgi?Action=Digest&username=admin&realm=Highwmg&nonce=%s&response=%s&qop=auth&cnonce=%s&temp=marvell&client=APP"%(self.nonce,respL,cnonce)
r = self._req("GET",loc)
if 200 != r.status_code:
raise Exception("bad password")
self.logedin = True
def _req(self, method, url, data = None, **kwargs):
#make auth header
cnonce = "%08x"%random.randint(0,0xffffffff)
respH = self.mkResponse(method,"/cgi/xml_action.cgi",cnonce)
if "headers" in kwargs:
headers=kwargs["headers"]
else:
headers = {}
headers["Authorization"] = 'Digest username="admin", realm="Highwmg", nonce="%s", uri="/cgi/xml_action.cgi", response="%s", qop=auth, nc=%08x, cnonce="%s", client=APP' % (self.nonce, respH, self.nc, cnonce)
r = requests.request(method, self.svr + url, data = data, headers = headers)
#print(r.text)
return r
def fuckApi(self, api, data = None):
if not self.logedin:
print("request api without logging in!")
if data:
rdata = b'<?xml version="1.0" encoding="US-ASCII"?> '+dict2str(data)
r = self._req("POST", api, rdata, headers=commonHeaders)
else:
r = self._req("GET", api, headers=commonHeaders)
t = ET.fromstring(r.text)
#print(json.dumps(et2dict(t), indent=2))
return et2dict(t)
#print(et2dict("<RGW><message><flag><message_flag>SEND_SMS</message_flag><sms_cmd>4</sms_cmd></flag><send_save_message><contacts>10010</contacts><content>004b00540056004f004c00540045</content><encode_type>GSM7_default</encode_type><sms_time>18,7,13,10,23,38,%2B8</sms_time></send_save_message></message></RGW>"))
#exit()
c =Client()
c.getNonce()
c.login()
#some apis
if False:
c.fuckApi('/xml_action.cgi?method=get&module=duster&file=status1')
c.fuckApi('/xml_action.cgi?method=get&module=duster&file=device_management')
c.fuckApi('/xml_action.cgi?method=get&module=duster&file=wan')
c.fuckApi('/xml_action.cgi?method=get&module=duster&file=smart_set')
c.fuckApi('/xml_action.cgi?method=get&module=duster&file=uapxb_wlan_security_settings')
#get SMS
if True:
msgPage = lambda x : {'RGW': {'message': {'flag': {'message_flag': 'GET_RCV_SMS_LOCAL'}, 'get_message': {'page_number': str(x)}}}}
parseMsg = lambda x : [(fuckyDecode(y["from"]),fuckyDecode(y["subject"])) for y in x["RGW"]["message"]["get_message"]["message_list"]["Item"]]
msgs = c.fuckApi('/xml_action.cgi?method=set&module=duster&file=message', msgPage(1))
total = int(msgs["RGW"]["message"]["get_message"]["total_number"])
print(parseMsg(msgs))
for i in range(2,total+1):
ret = c.fuckApi('/xml_action.cgi?method=set&module=duster&file=message', msgPage(i))
print(parseMsg(ret))
exit()
#c.fuckApi('/xml_action.cgi?method=get&module=duster&file=message')
#send SMS
if False:
smsTime = lambda dt, tz="%2B8" : '%d,%d,%d,%d,%d,%d,%s' % (dt.year%100, dt.month, dt.day, dt.hour, dt.minute, dt.second, tz)
sendMsg = lambda target, msg, t=datetime.datetime.now() : {'RGW': {'message': {'flag': {'message_flag': 'SEND_SMS', 'sms_cmd': '4'}, 'send_save_message': {'contacts': str(target), 'content': fuckyEncode(msg), 'encode_type': 'UNICODE', 'sms_time': smsTime(t)}}}}
c.fuckApi('/xml_action.cgi?method=set&module=duster&file=message', sendMsg("10086","ktvolte"))
@dixyes
Copy link
Author

dixyes commented Jul 13, 2018

需要requests: pip install requests

懒得要死不想艹别的api了 我主要想收发短信

@dixyes
Copy link
Author

dixyes commented Sep 11, 2019

突然想起来 我MF855挂了, 这东西很久不维护了,不保证能用,有兴趣可以自行抓包看看

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment