Last active
September 11, 2020 14:46
-
-
Save dixyes/b76703a426f1a91fe9c7c05465496629 to your computer and use it in GitHub Desktop.
zimifi router SMS receive / send
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 | |
# -*- 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")) |
突然想起来 我MF855挂了, 这东西很久不维护了,不保证能用,有兴趣可以自行抓包看看
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
需要requests:
pip install requests
懒得要死不想艹别的api了 我主要想收发短信