Advertisement
Guest User

Untitled

a guest
Dec 26th, 2019
370
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.08 KB | None | 0 0
  1. import socket
  2. import threading
  3. import pyaes
  4. import zlib
  5. import struct
  6. import json
  7. import cStringIO
  8. import random
  9. import time
  10. import Queue
  11. import string
  12. from collections import namedtuple
  13. import BaseHTTPServer
  14. from ctypes import *
  15.  
  16. SOCKET_MAC_ADDRESS = None
  17.  
  18. """
  19. A Packet
  20. """
  21. TOrviboPkt = namedtuple('TOrviboPkt', ['header', 'body'])
  22.  
  23. """
  24. The header of every packet
  25. """
  26. class TOrviboPktHeader(BigEndianStructure):
  27. _pack_ = 1
  28. _fields_ = [('signature', c_char*2),
  29. ('pkt_size', c_ushort),
  30. ('pkt_type', c_char*2),
  31. ('crc32_body', c_uint),
  32. ('session_id', c_char*32)]
  33.  
  34.  
  35. # Packets read from socket
  36. socket_read_Q = Queue.Queue()
  37.  
  38. # Packets to be sent to socket
  39. socket_write_Q = Queue.Queue()
  40.  
  41. # Packets read from homemate server
  42. server_read_Q = Queue.Queue()
  43.  
  44. # Packets to be sent to homemate server
  45. server_write_Q = Queue.Queue()
  46.  
  47. class OrviboSession:
  48. def __init__(self):
  49. self.aes_key = 'khggd54865SNJHGF'
  50. self.session_id = None
  51. self.mutex = threading.Lock()
  52.  
  53. def get_aes_key(self):
  54. self.mutex.acquire()
  55. key = self.aes_key
  56. self.mutex.release()
  57. return key
  58.  
  59. def set_aes_key(self, key):
  60. self.mutex.acquire()
  61. self.aes_key = key
  62. self.mutex.release()
  63.  
  64. def get_session_id(self):
  65. return self.session_id
  66.  
  67. def set_session_id(self, sid):
  68. self.session_id = sid
  69.  
  70.  
  71. """
  72. Reads numBytes from a network socket
  73. """
  74. def read_socket(sock, numBytes):
  75. nRead = 0
  76. buf = cStringIO.StringIO()
  77.  
  78. while nRead < numBytes:
  79. buf.write(sock.recv(numBytes - nRead))
  80. nRead = buf.tell()
  81.  
  82. return buf.getvalue()
  83.  
  84.  
  85. """
  86. Writes a buffer to a network socket
  87. """
  88. def write_socket(sock, buf):
  89. sock.sendall(buf)
  90.  
  91.  
  92. """
  93. Read one packet, returns header, body (which is encrypted)
  94. """
  95. def read_packet(sock):
  96. header = TOrviboPktHeader.from_buffer_copy(read_socket(sock, sizeof(TOrviboPktHeader)))
  97. body_size = header.pkt_size - sizeof(TOrviboPktHeader)
  98. body = read_socket(sock, body_size)
  99. return TOrviboPkt(header, body)
  100.  
  101.  
  102. """
  103. Write one packet, body must be encrypted before
  104. """
  105. def write_packet(sock, pkt):
  106. write_socket(sock, string_at(addressof(pkt.header), sizeof(TOrviboPktHeader)) + pkt.body)
  107.  
  108. """
  109. Function to read packets from a network socket and put in a Q
  110. """
  111. def read_worker(sock, Q):
  112. while True:
  113. pkt = read_packet(sock)
  114. Q.put(pkt)
  115.  
  116.  
  117. """
  118. Function to write packets from a Q to a network socket
  119. """
  120. def write_worker(sock, Q):
  121. while True:
  122. pkt = Q.get()
  123. write_packet(sock, pkt)
  124.  
  125. """
  126. Reads from src_Q and writes to dst_Q
  127. """
  128. def pipe_queues_worker(session, src_Q, dst_Q):
  129. while True:
  130. pkt = src_Q.get()
  131. pkt = process_packet(session, pkt, src_Q, dst_Q)
  132. if pkt is not None:
  133. dst_Q.put(pkt)
  134.  
  135. """
  136. Encrypts buffer using AES-128 ECB PKCS#7
  137. """
  138. def decrypt_buffer(ct, key):
  139. decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationECB(key))
  140. pt = ''
  141. pt += decrypter.feed(ct)
  142. pt += decrypter.feed()
  143. return pt
  144.  
  145.  
  146. """
  147. Decrypts buffer using AES-128 ECB PKCS#7
  148. """
  149. def encrypt_buffer(pt, key):
  150. encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationECB(key))
  151. ct = ''
  152. ct += encrypter.feed(pt)
  153. ct += encrypter.feed()
  154. return ct
  155.  
  156.  
  157. """
  158. Process a packet
  159. """
  160. def process_packet(session, pkt, src_Q, dst_Q):
  161. global SOCKET_MAC_ADDRESS
  162. msg_json = json.loads(decrypt_buffer(pkt.body, session.get_aes_key()).strip('\x00'))
  163.  
  164. # Packets from socket to server
  165. if src_Q == socket_read_Q and dst_Q == server_write_Q:
  166. print ('')
  167. print ('[+] socket -> server')
  168. print (msg_json)
  169.  
  170. if SOCKET_MAC_ADDRESS is None and 'uid' in msg_json:
  171. SOCKET_MAC_ADDRESS = msg_json['uid']
  172.  
  173. if 'clientSessionId' in msg_json:
  174. if msg_json['clientSessionId'].startswith('drop_me_'):
  175. print ('[*] Dropping...')
  176. pkt = None
  177.  
  178. # Packets from server to socket
  179. elif src_Q == server_read_Q and dst_Q == socket_write_Q:
  180. print ('[+] server -> socket')
  181. print (msg_json)
  182. print ('')
  183. if 'key' in msg_json:
  184. session.set_aes_key(msg_json['key'])
  185. assert pkt.header.pkt_type == 'pk'
  186. session.set_session_id(pkt.header.session_id)
  187.  
  188. return pkt
  189.  
  190.  
  191. """
  192. Construct a packet from JSON dict
  193. """
  194. def construct_packet_from_json(session_id, aes_key, body_json, pkt_type = 'dk'):
  195. enc_body = encrypt_buffer(json.dumps(body_json), aes_key)
  196. header = TOrviboPktHeader(signature = 'hd',
  197. pkt_size = sizeof(TOrviboPktHeader) + len(enc_body),
  198. pkt_type = pkt_type,
  199. crc32_body = zlib.crc32(enc_body) & 0xFFFFFFFF,
  200. session_id = session_id)
  201.  
  202. return TOrviboPkt(header, enc_body)
  203.  
  204. """
  205. Generate a random string
  206. """
  207. def generate_random_string(size, chars=string.ascii_lowercase + string.digits):
  208. return ''.join(random.choice(chars) for _ in range(size))
  209.  
  210.  
  211. """
  212. Constructs an ON/OFF packet
  213. """
  214. def construct_on_off_packet(session, switch='off'):
  215. body_json = {'userName': '[email protected]', #Not checked
  216. 'clientSessionId': 'drop_me_' + generate_random_string(24),
  217. 'ver': '3.7.0',
  218. 'uid': SOCKET_MAC_ADDRESS, #mac address of socket
  219. 'propertyResponse': 0,
  220. 'debugInfo': 'Android_ZhiJia365_23_3.7.0.302',
  221. 'cmd': 15,
  222. 'defaultResponse': 1,
  223. 'qualityOfService': 1,
  224. 'value4': 0,
  225. 'value3': 0,
  226. 'value2': 0,
  227. 'value1': 1 if switch == 'off' else 0,
  228. 'serial': random.randrange(1<<28, 1<<30), # A large number
  229. 'delayTime': 0,
  230. 'order': switch,
  231. 'deviceId': 'a'*25} #Not checked
  232.  
  233. return construct_packet_from_json(session.get_session_id(), session.get_aes_key(), body_json)
  234.  
  235. """
  236. Toggles the socket at regular intervals
  237. """
  238. def switch_toggler(session, interval):
  239. current = 'off'
  240. while True:
  241. time.sleep(interval)
  242. if current == 'off':
  243. pkt = construct_on_off_packet(session, 'on')
  244. print ('[+] Switching ON')
  245. current = 'on'
  246. else:
  247. pkt = construct_on_off_packet(session, 'off')
  248. print ('[+] Switching OFF')
  249. current = 'off'
  250.  
  251. socket_write_Q.put(pkt)
  252.  
  253.  
  254. the_session = None
  255.  
  256. # Create a website with the following items
  257. class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
  258. # Respond to a GET request
  259. def do_GET(s):
  260. s.send_response(200)
  261. s.send_header("Content-type", "text/html")
  262. s.end_headers()
  263.  
  264. if s.path == '/':
  265. s.wfile.write('<html><head><title>Orvibo S20C Socket Controller</title></head>')
  266. s.wfile.write('<body><h2>Orvibo S20C Socket Controller</h2>')
  267. s.wfile.write('<a href="/on"><button type="button">Send ON command</button></a>')
  268. s.wfile.write('<br><a href="/off"><button type="button">Send OFF command</button></a>')
  269. s.wfile.write("</body></html>")
  270.  
  271. elif s.path == '/on':
  272. pkt = construct_on_off_packet(the_session, 'on')
  273. socket_write_Q.put(pkt)
  274. s.wfile.write('<html><head><title>Orvibo S20C Socket Controller</title></head>')
  275. s.wfile.write('<body><h2>Orvibo S20C Socket Controller</h2>')
  276. s.wfile.write('<a href="/on"><button type="button">Send ON command</button></a>')
  277. s.wfile.write('<br><a href="/off"><button type="button">Send OFF command</button></a>')
  278. s.wfile.write('<h3>Turned ON!</h3>')
  279. s.wfile.write("</body></html>")
  280.  
  281. elif s.path == '/off':
  282. pkt = construct_on_off_packet(the_session, 'off')
  283. socket_write_Q.put(pkt)
  284. s.wfile.write('<html><head><title>Orvibo S20C Socket Controller</title></head>')
  285. s.wfile.write('<body><h2>Orvibo S20C Socket Controller</h2>')
  286. s.wfile.write('<a href="/on"><button type="button">Send ON command</button></a>')
  287. s.wfile.write('<br><a href="/off"><button type="button">Send OFF command</button></a>')
  288. s.wfile.write('<h3>Turned OFF!</h3>')
  289. s.wfile.write("</body></html>")
  290.  
  291. else:
  292. s.wfile.write('<html><head><title>Orvibo S20C Socket Controller</title></head>')
  293. s.wfile.write('<body><h2>404 Not Found</h2>')
  294. s.wfile.write("</body></html>")
  295.  
  296. '''
  297. A socket is just an abstraction of a communication end point.
  298. Original communication between the entities may use different methods for communication.
  299. INET sockets in linux are Internet Sockets i.e IP protocol based sockets but there are many other types.
  300.  
  301.  
  302. AF_INET is an address family that is used to designate the type of addresses that your socket can communicate with (in this case, Internet Protocol v4 addresses).
  303. For the most part, sticking with AF_INET for socket programming over a network is the safest option.
  304. There is also AF_INET6 for Internet Protocol v6 addresses.
  305.  
  306. SOCK_STREAM means that it is a TCP socket.
  307. SOCK_DGRAM means that it is a UDP socket.
  308.  
  309. socket.bind(address)
  310. Bind the socket to address. The socket must not already be bound.
  311. bind() defines a relationship between the socket you created and the addresses that are available on your host.
  312. For example you can bind a socket on all addresses or on a specific IP which has been configured on a network adapter by the host's operating system.
  313. bind a socket to a catch-all address in this example.
  314.  
  315. socket.listen(backlog) Listen for connections made to the socket.
  316. The backlog argument specifies the maximum number of queued connections and should be at least 1; the maximum value is system-dependent (usually 5).
  317. Calling listen() puts the socket into server mode, and accept() waits for an incoming connection.
  318.  
  319. accept() returns an open connection between the server and client, along with the address of the client.
  320. '''
  321.  
  322. def main():
  323. global the_session
  324. # Create a TCP/IP socket for client endpoint
  325. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  326. # Bind the socket to the port. Alternatively, sock.bind(('localhost', 8080))
  327. sock.bind(('', 8080))
  328. # Listen for incoming connections
  329. sock.listen(1)
  330. # Wait for a connection
  331. csock, _ = sock.accept()
  332. print ('[+] Client - smart plug connected')
  333.  
  334. # Create a TCP/IP socket for server
  335. ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  336. # Connect to a remote socket at address.
  337. ssock.connect(('homemate.orvibo.com', 10001))
  338. print ('[+] Remote Server homemate.orvibo.com connected')
  339.  
  340. # Create a new instance of OrviboSession Class as defined above and assign to session object
  341. session = OrviboSession()
  342. the_session = session
  343.  
  344. socket_rw = threading.Thread(target=read_worker, args=(csock, socket_read_Q))
  345. socket_ww = threading.Thread(target=write_worker, args=(csock, socket_write_Q))
  346. server_rw = threading.Thread(target=read_worker, args=(ssock, server_read_Q))
  347. server_ww = threading.Thread(target=write_worker, args=(ssock, server_write_Q))
  348.  
  349. socket_to_server_w = threading.Thread(target=pipe_queues_worker, args=(session, socket_read_Q, server_write_Q))
  350. server_to_socket_w = threading.Thread(target=pipe_queues_worker, args=(session, server_read_Q, socket_write_Q))
  351.  
  352. # toggler = threading.Thread(target=switch_toggler, args=(session, 10))
  353.  
  354. socket_rw.start()
  355. socket_ww.start()
  356. server_rw.start()
  357. server_ww.start()
  358. socket_to_server_w.start()
  359. server_to_socket_w.start()
  360. # toggler.start()
  361.  
  362. server_class = BaseHTTPServer.HTTPServer
  363. httpd = server_class(('', 5555), Handler)
  364. print ('[+] HTTP server started on localhost:5555')
  365. try:
  366. httpd.serve_forever()
  367. except KeyboardInterrupt:
  368. pass
  369. httpd.server_close()
  370.  
  371.  
  372. if __name__ == '__main__':
  373. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement